ClonePublicSkuUpdateDialog.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <template>
  2. <base-dialog @cancel="cancelDialog">
  3. <div slot="header">{{$t('compute.text_983')}}</div>
  4. <div slot="body">
  5. <dialog-selected-tips :name="$t('dictionary.sku')" :count="params.data.length" :action="$t('compute.text_983')" />
  6. <dialog-table :data="params.data" :columns="params.columns.slice(0, 3)" />
  7. <a-form
  8. :form="form.fc"
  9. v-bind="formItemLayout">
  10. <a-form-item :label="$t('compute.text_177')" class="mb-0">
  11. <cloudregion-zone
  12. :zone-params="zoneParams"
  13. :cloudregion-params="cloudregionParams"
  14. :decorator="decorators.cloudregionZone" />
  15. </a-form-item>
  16. <a-form-item
  17. :label="$t('compute.text_228')"
  18. :help="nameRepeatedHelp"
  19. :validate-status="nameRepeatedValidateStatus">
  20. <a-input disabled v-decorator="decorators.name" />
  21. </a-form-item>
  22. <a-form-item :label="$t('compute.sys_disk_type')">
  23. <base-select
  24. v-decorator="decorators.sys_disk_type"
  25. :options="sysDiskTypeOptions"
  26. :select-props="{ placeholder: $t('common.tips.select', [$t('compute.sys_disk_type')]), mode: 'multiple' }" />
  27. </a-form-item>
  28. <a-form-item :label="$t('compute.data_disk_type')">
  29. <base-select
  30. v-decorator="decorators.data_disk_types"
  31. :options="dataDiskTypeOptions"
  32. :select-props="{ placeholder: $t('common.tips.select', [$t('compute.data_disk_type')]), mode: 'multiple' }" />
  33. </a-form-item>
  34. <a-form-item :label="$t('compute.sku.postpaid_status')">
  35. <a-radio-group v-decorator="decorators.postpaid_status">
  36. <a-radio value="available">{{ $t('status.sku.available') }}</a-radio>
  37. <a-radio value="soldout">{{ $t('status.sku.soldout') }}</a-radio>
  38. </a-radio-group>
  39. </a-form-item>
  40. <a-form-item :label="$t('compute.sku.prepaid_status')">
  41. <a-radio-group v-decorator="decorators.prepaid_status">
  42. <a-radio value="available">{{ $t('status.sku.available') }}</a-radio>
  43. <a-radio value="soldout">{{ $t('status.sku.soldout') }}</a-radio>
  44. </a-radio-group>
  45. </a-form-item>
  46. </a-form>
  47. </div>
  48. <div slot="footer">
  49. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  50. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  51. </div>
  52. </base-dialog>
  53. </template>
  54. <script>
  55. import DialogMixin from '@/mixins/dialog'
  56. import WindowsMixin from '@/mixins/windows'
  57. import CloudregionZone from '@/sections/CloudregionZone'
  58. import { isRequired } from '@/utils/validate'
  59. import { STORAGE_TYPES } from '@Compute/constants'
  60. export default {
  61. name: 'ClonePublicSkuUpdateDialog',
  62. components: {
  63. CloudregionZone,
  64. },
  65. mixins: [DialogMixin, WindowsMixin],
  66. provide () {
  67. return {
  68. form: this.form,
  69. }
  70. },
  71. data () {
  72. const initData = this.params.data[0] || {}
  73. const initSysDiskType = initData.sys_disk_type ? initData.sys_disk_type.split(',').filter(item => item) : []
  74. const initDataDiskTypes = initData.data_disk_types ? initData.data_disk_types.split(',').filter(item => item) : []
  75. return {
  76. loading: false,
  77. data: initData,
  78. nameRepeatedChecking: false,
  79. nameRepeatedInZone: false,
  80. nameRepeatedReqId: 0,
  81. form: {
  82. fd: {
  83. cloudregion: { key: initData.cloudregion_id || '', label: initData.cloudregion || '' },
  84. zone: { key: initData.zone_id || '', label: initData.zone || '' },
  85. },
  86. },
  87. decorators: {
  88. name: [
  89. 'name',
  90. {
  91. initialValue: initData.name || '',
  92. },
  93. ],
  94. cloudregionZone: {
  95. cloudregion: [
  96. 'cloudregion',
  97. {
  98. initialValue: { key: initData.cloudregion_id || '', label: initData.cloudregion || '' },
  99. rules: [
  100. { validator: isRequired(), message: this.$t('compute.text_212') },
  101. ],
  102. },
  103. ],
  104. zone: [
  105. 'zone',
  106. {
  107. initialValue: { key: initData.zone_id || '', label: initData.zone || '' },
  108. rules: [
  109. { validator: isRequired(), message: this.$t('compute.text_213') },
  110. ],
  111. },
  112. ],
  113. },
  114. sys_disk_type: [
  115. 'sys_disk_type',
  116. {
  117. initialValue: initSysDiskType,
  118. rules: [
  119. { required: false, message: this.$t('common.tips.select', [this.$t('compute.sys_disk_type')]) },
  120. ],
  121. },
  122. ],
  123. data_disk_types: [
  124. 'data_disk_types',
  125. {
  126. initialValue: initDataDiskTypes,
  127. rules: [
  128. { required: false, message: this.$t('common.tips.select', [this.$t('compute.data_disk_type')]) },
  129. ],
  130. },
  131. ],
  132. postpaid_status: [
  133. 'postpaid_status',
  134. {
  135. initialValue: initData.postpaid_status || 'available',
  136. },
  137. ],
  138. prepaid_status: [
  139. 'prepaid_status',
  140. {
  141. initialValue: initData.prepaid_status || 'available',
  142. },
  143. ],
  144. },
  145. formItemLayout: {
  146. wrapperCol: {
  147. span: 21,
  148. },
  149. labelCol: {
  150. span: 3,
  151. },
  152. },
  153. }
  154. },
  155. computed: {
  156. zoneId () {
  157. return this.form?.fd?.zone?.key
  158. },
  159. cloudregionParams () {
  160. return {
  161. cloud_env: 'public',
  162. // usable: true,
  163. show_emulated: false,
  164. scope: this.$store.getters.scope,
  165. provider: this.data.provider,
  166. }
  167. },
  168. zoneParams () {
  169. return {
  170. // usable: true,
  171. show_emulated: false,
  172. order_by: 'created_at',
  173. cloudregion_id: this.form.fd.cloudregion.key,
  174. order: 'asc',
  175. scope: this.$store.getters.scope,
  176. }
  177. },
  178. allStorageTypes () {
  179. if (STORAGE_TYPES[(this.data.provider || '').toLowerCase()]) {
  180. return Object.values(STORAGE_TYPES[(this.data.provider || '').toLowerCase()]).map(item => {
  181. return {
  182. id: item.key || item.value,
  183. name: item.label,
  184. ...item,
  185. }
  186. })
  187. }
  188. return []
  189. },
  190. sysDiskTypeOptions () {
  191. // sysUnusable
  192. const ret = [...this.allStorageTypes].filter(item => !item.sysUnusable)
  193. if (this.data.sys_disk_type) {
  194. const list = this.data.sys_disk_type.split(',').filter(item => item)
  195. list.forEach(key => {
  196. if (!ret.some(item => item.id === key)) {
  197. ret.push({ id: key, name: key })
  198. }
  199. })
  200. }
  201. return ret
  202. },
  203. dataDiskTypeOptions () {
  204. const ret = [...this.allStorageTypes]
  205. if (this.data.data_disk_types) {
  206. const list = this.data.data_disk_types.split(',').filter(item => item)
  207. list.forEach(key => {
  208. if (!ret.some(item => item.id === key)) {
  209. ret.push({ id: key, name: key })
  210. }
  211. })
  212. }
  213. return ret
  214. },
  215. nameRepeatedHelp () {
  216. if (this.nameRepeatedChecking) return ''
  217. if (this.nameRepeatedInZone) return this.$t('compute.sku.name_repeated_in_zone')
  218. return ''
  219. },
  220. nameRepeatedValidateStatus () {
  221. if (this.nameRepeatedInZone) return 'error'
  222. return ''
  223. },
  224. },
  225. watch: {
  226. zoneId: {
  227. immediate: true,
  228. handler (val, oldVal) {
  229. if (val && val !== oldVal) {
  230. this.checkSkuNameRepeatedInZone()
  231. } else if (!val) {
  232. this.nameRepeatedInZone = false
  233. }
  234. },
  235. },
  236. },
  237. created () {
  238. // 在 created 钩子中创建表单,确保 form.fd 已经初始化
  239. this.form.fc = this.$form.createForm(this, {
  240. onValuesChange: (props, values) => {
  241. if (this.form && this.form.fd) {
  242. if (values.hasOwnProperty('cloudregion')) {
  243. this.form.fd.cloudregion = values.cloudregion
  244. }
  245. if (values.hasOwnProperty('zone')) {
  246. this.form.fd.zone = values.zone
  247. }
  248. }
  249. },
  250. })
  251. },
  252. methods: {
  253. async checkSkuNameRepeatedInZone () {
  254. const zoneId = this.zoneId
  255. const provider = this.data?.provider
  256. const name = this.data?.name
  257. if (!zoneId || !provider || !name) {
  258. this.nameRepeatedInZone = false
  259. return
  260. }
  261. const reqId = ++this.nameRepeatedReqId
  262. this.nameRepeatedChecking = true
  263. try {
  264. const manager = new this.$Manager('serverskus')
  265. const safeName = String(name).replace(/'/g, '\\\'')
  266. const { data: { data = [] } = {} } = await manager.list({
  267. params: {
  268. scope: this.$store.getters.scope,
  269. limit: 1,
  270. zone_id: zoneId,
  271. provider,
  272. filter: `name.equals('${safeName}')`,
  273. },
  274. })
  275. if (reqId === this.nameRepeatedReqId) {
  276. this.nameRepeatedInZone = data.length > 0
  277. }
  278. } catch (e) {
  279. if (reqId === this.nameRepeatedReqId) {
  280. // 请求失败时不阻断流程,默认不提示重名
  281. this.nameRepeatedInZone = false
  282. }
  283. } finally {
  284. if (reqId === this.nameRepeatedReqId) {
  285. this.nameRepeatedChecking = false
  286. }
  287. }
  288. },
  289. validateForm () {
  290. return new Promise((resolve, reject) => {
  291. this.form.fc.validateFields((err, values) => {
  292. if (!err) {
  293. resolve(values)
  294. } else {
  295. reject(err)
  296. }
  297. })
  298. })
  299. },
  300. doCreate (data) {
  301. return this.params.onManager('create', {
  302. managerArgs: {
  303. data,
  304. },
  305. })
  306. },
  307. async handleConfirm () {
  308. this.loading = true
  309. const keys = ['hyperevisor', 'cpu_arch', 'cpu_core_count', 'instance_type_category', 'instance_type_family', 'local_category', 'memory_size_mb', 'name']
  310. try {
  311. let values = await this.validateForm()
  312. values = {
  313. cloudregion_id: this.form.fd.cloudregion.key,
  314. zone_id: this.form.fd.zone.key,
  315. sys_disk_type: values.sys_disk_type.join(','),
  316. data_disk_types: values.data_disk_types.join(','),
  317. postpaid_status: values.postpaid_status,
  318. prepaid_status: values.prepaid_status,
  319. }
  320. keys.forEach(key => {
  321. values[key] = values[key] || this.data[key]
  322. })
  323. this.loading = true
  324. await this.doCreate(values)
  325. this.loading = false
  326. this.cancelDialog()
  327. } catch (error) {
  328. this.loading = false
  329. }
  330. },
  331. },
  332. }
  333. </script>