| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- <template>
- <div>
- <a-radio-group v-model="skuType" @change="skuTypeChange">
- <a-radio-button
- v-for="item of skuInfo.categoryOptions"
- :value="item.key"
- :key="item.key"
- :disabled="item.disabled">{{ item.label }}</a-radio-button>
- </a-radio-group>
- <vxe-grid
- row-id="id"
- ref="tableRef"
- min-height="260"
- resizable
- :columns="tableColumn"
- :data="skuResults"
- :radio-config="{ reserve: true }"
- @cell-click="skuChange"
- @radio-change="skuChange">
- <template v-slot:empty>
- <loader :loading="skuLoading || !canSkuShow" />
- </template>
- </vxe-grid>
- <div class="sku-pagebar">
- <vxe-pager
- :current-page.sync="skuPage.currentPage"
- :page-size.sync="skuPage.pageSize"
- :total="skuPage.totalResult"
- :layouts="['PrevJump', 'PrevPage', 'Jump', 'PageCount', 'NextPage', 'NextJump', 'Total']"
- @page-change="skuPageChangeHandle" />
- <div class="mt-1" v-if="selectedTip">{{$t('compute.text_171', [ selectedTip ])}}</div>
- </div>
- <div class="mt-1" v-if="unfindTip" style="color: red;">{{$t('compute.sku_unfind_tip', [ unfindTip ])}}</div>
- <div class="mt-1" v-if="disableSkuType && !supportSkuTypes.length" style="color: red;">{{$t('compute.disable_sku_type_tip')}}</div>
- </div>
- </template>
- <script>
- import * as R from 'ramda'
- import { ALL_SKU_CATEGORY_OPT, SKU_CATEGORY_MAP } from '@Compute/constants'
- import { Manager } from '@/utils/manager'
- import { PROVIDER_MAP, HYPERVISORS_MAP } from '@/constants'
- import { sizestr } from '@/utils/utils'
- import i18n from '@/locales'
- const keys = ['hour_price', 'month_price', 'year_price']
- const units = [i18n.t('compute.text_172'), i18n.t('compute.text_173'), i18n.t('compute.text_174')]
- export default {
- name: 'SKU',
- props: {
- billType: {
- type: String,
- },
- hypervisor: {
- validator: val => {
- if (val) return R.is(String, val)
- return true
- },
- },
- value: { // v-decorator 的props
- required: true,
- },
- priceUnit: {
- type: Object,
- validator: val => {
- if (!R.isNil(val.key) || !R.isNil(val.unit)) {
- return keys.includes(val.key) && units.includes(val.unit)
- }
- return false
- },
- default: () => ({
- key: 'hour_price',
- unit: i18n.t('compute.text_172'),
- }),
- },
- skuParams: {
- type: Object,
- default: () => ({}),
- },
- type: {
- type: String,
- required: true,
- validator: val => ['idc', 'private', 'public'].includes(val),
- },
- hasMeterService: {
- type: Boolean,
- default: true,
- },
- instanceType: {
- type: String,
- },
- requireSysDiskType: {
- type: Array,
- },
- requireDataDiskTypes: {
- type: Array,
- },
- skuFilter: {
- type: Function,
- default: (items) => { return items },
- },
- canSkuShow: {
- type: Boolean,
- default: () => true,
- },
- skuDisabled: {
- type: Boolean,
- default: () => false,
- },
- dataSku: {
- type: Object,
- default: () => ({}),
- },
- isAdjustConfig: {
- type: Boolean,
- default: () => false,
- },
- dataList: {
- type: Array,
- default: () => [],
- },
- initSkuData: {
- type: Object,
- default: () => ({}),
- },
- supportSkuTypes: {
- type: Array,
- default: () => [],
- },
- disableSkuType: {
- type: Boolean,
- default: () => false,
- },
- },
- data () {
- return {
- skuList: [], // 套餐列表
- ratesList: [], // 套餐价格列表
- rateLoading: false,
- skuLoading: false,
- selectedSkuData: {},
- skuType: ALL_SKU_CATEGORY_OPT.key,
- skuPage: {
- currentPage: 1,
- pageSize: 10,
- totalResult: 0,
- },
- skuTypes: [],
- hasOriginSku: false,
- unfindTip: '',
- }
- },
- computed: {
- isSameSku () {
- return this.dataList.length === 1 || this.dataList.map(item => item.instance_type).filter(item => item).length === 1
- },
- customConfig () {
- return {
- checkMethod: ({ row }) => {
- return true
- },
- }
- },
- isPublic () {
- return this.type === 'public'
- },
- isPrivate () {
- return this.type === 'private'
- },
- isIDC () {
- return this.type === 'idc'
- },
- tableColumn () {
- const column = [
- { field: 'instance_type_category_i18n', title: this.$t('compute.text_175') },
- { field: 'region', title: this.$t('compute.text_177') },
- { field: 'name', title: this.$t('compute.text_178') },
- { field: 'cpu_core_count', title: this.$t('compute.text_179') },
- { field: 'cpu_arch', title: this.$t('compute.cpu_arch'), slots: { default: ({ row }) => { return row.cpu_arch ? this.$t(`compute.cpu_arch.${row.cpu_arch}`) : '-' } } },
- { field: 'memory_size_mb_compute', title: this.$t('compute.text_180') },
- ]
- if (this.skuDisabled) {
- column.unshift({
- field: 'radio',
- width: 40,
- slots: {
- default: ({ row }) => {
- if (row.id === this.selectedSkuData?.id) {
- return [<vxe-radio disabled></vxe-radio>]
- }
- return [<vxe-radio disabled value={false}></vxe-radio>]
- },
- },
- })
- } else {
- column.unshift({
- field: 'radio',
- width: 40,
- slots: {
- default: ({ row }) => {
- if (row.id === this.selectedSkuData?.id) {
- return [<vxe-radio></vxe-radio>]
- }
- if (this.disableSkuType && this.supportSkuTypes.length && !this.supportSkuTypes.includes(row.name)) {
- return [<vxe-radio disabled value={false}></vxe-radio>]
- }
- return [<vxe-radio value={false}></vxe-radio>]
- },
- },
- })
- }
- const providerColumn = {
- field: 'provider',
- title: this.$t('compute.text_176'),
- slots: {
- default: ({ row }) => {
- return [
- this.getHypervisor(row),
- ]
- },
- },
- }
- if (this.isPublic) {
- column.splice(1, 0, providerColumn)
- if (this.hasMeterService) {
- column.push({
- field: 'hour_price',
- title: this.$t('compute.text_181'),
- slots: {
- default: ({ row }) => {
- const price = this.getFormatPrice(row.hour_price)
- if (price > 0) {
- let ret = [<a-icon type="loading" />]
- if (!this.rateLoading) {
- ret = [
- <span style="color: rgb(230, 139, 80);">{ price }</span>,
- <span> {this.$t(`currencys.${row.currency}`)} / { this.priceUnit.unit }</span>,
- ]
- }
- return ret
- }
- return [<span style="color: rgb(230, 139, 80);">--</span>]
- },
- },
- })
- }
- }
- return column
- },
- skuInfo () {
- let currentCategory = SKU_CATEGORY_MAP[this.hypervisor] || []
- if (this.isPublic) {
- currentCategory = SKU_CATEGORY_MAP.public_cloud
- }
- const categoryOptions = {
- [ALL_SKU_CATEGORY_OPT.key]: {
- ...ALL_SKU_CATEGORY_OPT,
- disabled: false,
- },
- }
- currentCategory.forEach(item => {
- let type = this.hypervisor
- if (this.isPublic) {
- type = 'public_cloud'
- }
- categoryOptions[item] = {
- label: this.getI18NValue(`skuCategoryOptions.${type}.${item}`, item),
- key: item,
- disabled: !this.skuTypes.includes(item) || this.skuDisabled,
- }
- })
- const skuOptions = {
- [ALL_SKU_CATEGORY_OPT.key]: [],
- }
- // 套餐去重
- const skuSet = new Set()
- for (let i = 0, len = this.skuList.length; i < len; i++) {
- const item = this.skuList[i]
- const flag = `${item.name}-${item.provider}-${item.region_ext_id}`
- if (skuSet.has(flag)) {
- continue
- }
- skuSet.add(flag)
- const key = item.local_category
- const category = item.instance_type_category
- if (!skuOptions[key]) {
- skuOptions[key] = []
- }
- let hypervisor = this.hypervisor
- if (this.isPublic) {
- hypervisor = item.provider.toLowerCase()
- }
- // 翻译类型
- item.instance_type_category_i18n = this.getI18NValue(`skuCategoryOptions['${hypervisor}']['${category}']`, category)
- item.memory_size_mb_compute = item.memory_size_mb / 1024
- if (this.isPublic) {
- item.rate_key = this.genRateKey(item)
- if (this.ratesMap[item.rate_key]) {
- item.hour_price = this.ratesMap[item.rate_key][this.priceUnit.key]
- item.currency = this.ratesMap[item.rate_key].currency
- }
- }
- if (this.isSkuEnabled(item)) {
- skuOptions[key].push(item)
- skuOptions[ALL_SKU_CATEGORY_OPT.key].push(item)
- }
- }
- return {
- categoryOptions,
- skuOptions,
- }
- },
- ratesMap () {
- const ret = {}
- for (let i = 0, len = this.ratesList.length; i < len; i++) {
- const item = this.ratesList[i]
- ret[item.data_key] = item
- }
- return ret
- },
- skuResults () {
- if (!this.canSkuShow) return []
- const ret = this.skuInfo.skuOptions[this.skuType]
- if (ret && ret.length > 0 && ret[0].hour_price) {
- ret.sort((a, b) => a.hour_price - b.hour_price)
- }
- return ret
- },
- selectedTip () {
- if (this.selectedSkuData?.id) {
- return this.$t('compute.text_182', [
- this.selectedSkuData.name,
- this.selectedSkuData.instance_type_category_i18n,
- this.selectedSkuData.cpu_core_count,
- sizestr(this.selectedSkuData.memory_size_mb, 'M', 1024),
- ])
- }
- return null
- },
- },
- watch: {
- skuParams: {
- handler (val, oldV) {
- if (!R.isEmpty(val)) {
- if (!R.equals(val, oldV)) {
- this.resetPageInfo()
- this.fetchData()
- }
- } else {
- this.skuList = []
- this.setSku({})
- }
- },
- },
- skuResults: {
- handler (val, oldV) {
- if (!R.equals(val, oldV)) {
- if (val.length) {
- this.setSku(val[0], false)
- } else {
- this.setSku({})
- }
- }
- },
- deep: true,
- },
- },
- created () {
- this.skusM = new Manager('serverskus')
- this.ratesM = new Manager('cloud_sku_rates', 'v1')
- if (this.skuParams && !R.isEmpty(this.skuParams)) {
- this.fetchData()
- }
- },
- methods: {
- fetchData () {
- this.fetchSkuTypes()
- this.fetchSkuList().then(this.fetchCloudSkuRatesList)
- },
- getFormatPrice (price) {
- if (price) {
- return price.toFixed(2)
- }
- return '0'
- },
- skuChange ({ row } = {}) {
- if (this.skuDisabled) return
- if (!row) return
- // 与 tableColumn 中单选列一致:非当前选中且不在 supportSkuTypes 内时为 disabled,不触发切换
- if (
- this.disableSkuType &&
- this.supportSkuTypes.length &&
- !this.supportSkuTypes.includes(row.name) &&
- row.id !== this.selectedSkuData?.id
- ) {
- return
- }
- this.setSku(row, true)
- },
- skuTypeChange () {
- this.fetchData()
- if (this.skuResults && this.skuResults.length) {
- this.setSku(this.skuResults[0], true)
- }
- },
- setSku (skuData, isSkuChange) {
- if (!skuData) return
- let chooseSku = skuData
- if (!isSkuChange && this.instanceType) {
- const extSku = this.skuList.find(item => item.name === this.instanceType)
- if (extSku) {
- chooseSku = extSku
- } else {
- if (this.isAdjustConfig && this.isSameSku && !this.hasOriginSku) {
- chooseSku = this.dataSku
- this.unfindTip = this.dataSku?.name
- }
- }
- }
- if (!isSkuChange && this.initSkuData?.name && this.skuList.some(item => item.name && item.name === this.initSkuData?.name)) {
- const sku = this.skuList.find(item => item.name && item.name === this.initSkuData?.name)
- if (sku) {
- chooseSku = sku
- }
- }
- this.$nextTick(() => {
- this.selectedSkuData = chooseSku
- this.$refs.tableRef && this.$refs.tableRef.setRadioRow(chooseSku)
- this.$emit('change', chooseSku)
- })
- },
- getHypervisor (data) {
- return data.provider ? PROVIDER_MAP[data.provider].label : PROVIDER_MAP.OneCloud.label
- },
- genRateKey (data) {
- const provider = this.getSkuItemProvider(data)
- let ret = `${provider}::${data.region_ext_id}::${data.name}`
- if (provider === HYPERVISORS_MAP.ucloud.key || provider === HYPERVISORS_MAP.azure.key) {
- ret = `${provider}::${data.region_ext_id}::::instance::${data.name}`
- }
- return ret
- },
- getI18NValue (key, originVal) {
- if (this.$te(key)) {
- return this.$t(key)
- }
- return originVal
- },
- async fetchSkuList () {
- try {
- this.skuLoading = true
- this.skuList = []
- if (this.skuParams.cpu_core_count === 0) {
- delete this.skuParams.cpu_core_count
- }
- if (this.skuParams.memory_size_mb === 0) {
- delete this.skuParams.memory_size_mb
- }
- const params = {
- ...this.skuParams,
- limit: this.skuPage.pageSize,
- offset: (this.skuPage.currentPage - 1) * this.skuPage.pageSize,
- '@local_category': this.skuType,
- // prepaid_status: 'available',
- }
- if (this.skuType === 'all') {
- delete params['@local_category']
- }
- if (!this.skuParams.zone_id) {
- params.distinct = true
- }
- params.enabled = true
- let { data } = await this.skusM.list({ params: params })
- this.skuPage.pageSize = data.limit || 10
- this.skuPage.totalResult = data.total || 0
- if (typeof this.skuFilter === 'function') {
- data = this.skuFilter(data.data)
- }
- if (this.skuParams && !R.isEmpty(this.skuParams)) { // 防止网络延迟导致 skuParams 已经为空了,但却赋值了
- this.skuList = data
- if (this.skuList && this.skuList.length) {
- if (this.isAdjustConfig && this.isSameSku && !this.hasOriginSku) {
- if (this.skuList.some(item => item.name && item.name === this.dataSku?.name)) {
- this.hasOriginSku = true
- }
- }
- this.setSku(this.skuResults[0], false)
- }
- }
- this.skuLoading = false
- return data
- } catch (error) {
- this.skuLoading = false
- throw error
- }
- },
- fetchCloudSkuRatesList () { // 公有云套餐价格
- if (!this.hasMeterService) return // 没有 meter 服务
- if (!this.isPublic) return
- if (!this.skuList || !this.skuList.length) return
- let paramKeys = this.skuList.map(item => {
- const provider = this.getSkuItemProvider(item)
- let ret = `${provider}::${item.region_ext_id || 'NA'}::${item.name || 'NA'}`
- if (provider === HYPERVISORS_MAP.ucloud.key || provider === HYPERVISORS_MAP.azure.key) {
- ret = `${provider}::${item.region_ext_id}::::instance::${item.name}`
- }
- return ret
- })
- paramKeys = Array.from(new Set(paramKeys))
- const params = {
- param_keys: paramKeys.join('$'),
- }
- this.rateLoading = true
- this.ratesM.list({ params })
- .then(({ data: { data = [] } }) => {
- this.ratesList = data
- this.rateLoading = false
- })
- .catch(() => {
- this.rateLoading = false
- })
- },
- getSkuItemProvider (item) { // 兼容阿里金融云
- let provider = item.provider.toLowerCase()
- if (this.isPublic && item.cloud_env) {
- provider = item.cloud_env.toLowerCase()
- }
- return provider
- },
- isSupportDiskTypes (supported, required) {
- for (let i = 0; i < required.length; i++) {
- if (!supported.includes(required[i])) {
- return false
- }
- }
- return true
- },
- isSkuEnabled (item) {
- if (this.requireSysDiskType && item.sys_disk_type && !this.isSupportDiskTypes(item.sys_disk_type.split(','), this.requireSysDiskType)) {
- return false
- }
- if (this.requireDataDiskTypes && item.data_disk_types && !this.isSupportDiskTypes(item.data_disk_types.split(','), this.requireDataDiskTypes)) {
- return false
- }
- return true
- },
- skuPageChangeHandle ({ currentPage = 1, pageSize = 10 }) {
- this.skuPage = {
- ...this.skuPage,
- currentPage,
- pageSize,
- }
- this.fetchData()
- },
- async fetchSkuTypes () {
- try {
- const params = {
- ...this.skuParams,
- field: 'local_category',
- // postpaid_status: 'available',
- }
- delete params.limit
- delete params.offset
- delete params['@local_category']
- if (this.skuParams.cpu_core_count === 0) {
- delete params.cpu_core_count
- }
- if (this.skuParams.memory_size_mb === 0) {
- delete params.memory_size_mb
- }
- const { data } = await this.skusM.get({ id: 'distinct-field', params })
- this.skuTypes = data[params.field]
- } catch (error) {
- console.log(error)
- }
- },
- resetPageInfo () {
- this.skuPage = {
- currentPage: 1,
- pageSize: 10,
- totalResult: 0,
- }
- },
- },
- }
- </script>
- <style lang="scss" scoped>
- .sku-pagebar {
- display: flex;
- flex-direction: row-reverse;
- justify-content: space-between;
- align-items: center;
- }
- </style>
|