Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
5d3142e4
authored
Aug 05, 2026
by
renyizhao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
动态规格(未完成)
parent
ccbdbc30
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
273 additions
and
250 deletions
+273
-250
src/api/compute/resourcespu/index.ts
+3
-0
src/views/compute/resourcespu/form/index.vue
+187
-163
src/views/compute/resourcespu/index.vue
+83
-87
No files found.
src/api/compute/resourcespu/index.ts
View file @
5d3142e4
...
@@ -26,6 +26,9 @@ export interface ResourceSpu {
...
@@ -26,6 +26,9 @@ export interface ResourceSpu {
sliderPicUrls
?:
string
;
// 商品轮播图地址,以逗号分隔,最多15张
sliderPicUrls
?:
string
;
// 商品轮播图地址,以逗号分隔,最多15张
sales
?:
number
;
// 商品销量
sales
?:
number
;
// 商品销量
status
?:
number
;
// 状态(0 下架,1 上架,2 回收)
status
?:
number
;
// 状态(0 下架,1 上架,2 回收)
specConfigIds
?:
number
[];
// 规格配置项 ID 列表(关联 compute_resource_config.id)
specMap
?:
Record
<
string
,
string
>
;
// 规格展示 Map(key=configCategory, value=configOption)
specConfigIdsByCategory
:
Record
<
string
,
number
[]
>
;
// 前端辅助字段:按 configCategory 分组的多选绑定(提交时扁平化为 specConfigIds)
}
}
// 算力资源SPU表(基础配置信息) API
// 算力资源SPU表(基础配置信息) API
...
...
src/views/compute/resourcespu/form/index.vue
View file @
5d3142e4
<
template
>
<
template
>
<ContentWrap
v-loading=
"formLoading"
>
<ContentWrap
v-loading=
"formLoading"
>
<el-form
ref=
"formRef"
:model=
"formData"
:rules=
"!isDetailMode ? formRules :
{}" label-width="120px">
<el-form
ref=
"formRef"
:model=
"formData"
:rules=
"!isDetailMode ? formRules :
{}"
label-width="120px"
>
<!-- 1. 算力资源名称 -->
<!-- 1. 算力资源名称 -->
<el-form-item
label=
"算力资源名称"
prop=
"name"
>
<el-form-item
label=
"算力资源名称"
prop=
"name"
>
<el-input
<el-input
...
@@ -20,7 +25,13 @@
...
@@ -20,7 +25,13 @@
<!-- 3. 算力资源分类 -->
<!-- 3. 算力资源分类 -->
<el-form-item
label=
"算力资源分类"
prop=
"categoryId"
>
<el-form-item
label=
"算力资源分类"
prop=
"categoryId"
>
<el-select
v-model=
"formData.categoryId"
placeholder=
"请选择算力资源分类"
clearable
style=
"width: 200px;"
:disabled=
"isDetailMode"
>
<el-select
v-model=
"formData.categoryId"
placeholder=
"请选择算力资源分类"
clearable
style=
"width: 200px"
:disabled=
"isDetailMode"
>
<el-option
<el-option
v-for=
"category in categoryList"
v-for=
"category in categoryList"
:key=
"category.id"
:key=
"category.id"
...
@@ -32,7 +43,13 @@
...
@@ -32,7 +43,13 @@
<!-- 3.1 算力来源 -->
<!-- 3.1 算力来源 -->
<el-form-item
label=
"算力来源"
prop=
"source"
>
<el-form-item
label=
"算力来源"
prop=
"source"
>
<el-select
v-model=
"formData.source"
placeholder=
"请选择算力来源"
clearable
style=
"width: 200px;"
:disabled=
"isDetailMode"
>
<el-select
v-model=
"formData.source"
placeholder=
"请选择算力来源"
clearable
style=
"width: 200px"
:disabled=
"isDetailMode"
>
<el-option
<el-option
v-for=
"dict in getStrDictOptions(DICT_TYPE.COMPUTE_RESOURCE_SOURCE)"
v-for=
"dict in getStrDictOptions(DICT_TYPE.COMPUTE_RESOURCE_SOURCE)"
:key=
"dict.value"
:key=
"dict.value"
...
@@ -42,95 +59,32 @@
...
@@ -42,95 +59,32 @@
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<!-- 4. 硬件配置 - inline布局 -->
<!-- 4. 规格配置 - 按 configCategory 分组多选 -->
<div
style=
"display: flex; margin-bottom: 16px;"
>
<el-form-item
label=
"规格配置"
prop=
"specConfigIds"
>
<el-form-item
label=
"CPU配置"
prop=
"cpu"
style=
"margin-right: 20px; margin-bottom: 0;"
>
<div
class=
"spec-config-wrapper"
>
<el-select
v-model=
"formData.cpu"
placeholder=
"请选择CPU配置"
clearable
style=
"width: 180px;"
:disabled=
"isDetailMode"
>
<div
v-for=
"category in specCategories"
:key=
"category.value"
class=
"spec-category-row"
>
<el-option
<span
class=
"spec-category-label"
>
{{
category
.
label
}}
:
</span>
v-for=
"option in cpuOptions"
<el-checkbox-group
:key=
"option.id"
v-model=
"formData.specConfigIdsByCategory[category.value]"
:label=
"option.configOption"
:disabled=
"isDetailMode"
:value=
"option.configOption"
>
/>
<el-checkbox
v-for=
"opt in category.options"
:key=
"opt.id"
:value=
"opt.id"
>
</el-select>
{{
opt
.
configOption
}}
</el-form-item>
<el-tooltip
v-if=
"opt.detailRemark"
:content=
"opt.detailRemark"
placement=
"top"
>
<el-form-item
label=
"GPU配置"
prop=
"gpu"
style=
"margin-bottom: 0;"
>
<Icon
icon=
"ep:info-filled"
class=
"ml-1"
/>
<el-select
v-model=
"formData.gpu"
placeholder=
"请选择GPU配置"
clearable
style=
"width: 180px;"
:disabled=
"isDetailMode"
>
</el-tooltip>
<el-option
</el-checkbox>
v-for=
"option in gpuOptions"
</el-checkbox-group>
:key=
"option.id"
:label=
"option.configOption"
:value=
"option.configOption"
/>
</el-select>
</el-form-item>
</div>
</div>
<div
v-if=
"!specCategories.length"
class=
"spec-empty"
<div
style=
"display: flex; margin-bottom: 16px;"
>
>
暂无规格类别,请在「系统管理 → 字典管理」配置
</div
<el-form-item
label=
"CPU核心数"
prop=
"coreCount"
style=
"margin-bottom: 0;"
>
>
<el-input
v-model=
"formData.coreCount"
placeholder=
"请输入CPU核心数"
style=
"width: 180px;"
:disabled=
"isDetailMode"
/>
</el-form-item>
<el-form-item
label=
"算力"
prop=
"compute"
style=
"margin-bottom: 0;"
>
<el-input
v-model=
"formData.compute"
placeholder=
"请输入算力值(TFLOPS)"
style=
"width: 180px;"
:disabled=
"isDetailMode"
/>
</el-form-item>
<el-form-item
label=
"GPU卡数量"
prop=
"gpuCount"
style=
"margin-bottom: 0;"
>
<el-input
v-model=
"formData.gpuCount"
placeholder=
"请输入GPU卡数量"
style=
"width: 180px;"
:disabled=
"isDetailMode"
/>
</el-form-item>
</div>
</div>
<div
style=
"display: flex; margin-bottom: 24px;"
>
<el-form-item
label=
"内存配置"
prop=
"ram"
style=
"margin-right: 20px; margin-bottom: 0;"
>
<el-select
v-model=
"formData.ram"
placeholder=
"请选择内存配置"
clearable
style=
"width: 180px;"
:disabled=
"isDetailMode"
>
<el-option
v-for=
"option in ramOptions"
:key=
"option.id"
:label=
"option.configOption"
:value=
"option.configOption"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"内存容量"
prop=
"memoryCapacity"
style=
"margin-bottom: 0;"
>
<el-input
v-model=
"formData.memoryCapacity"
placeholder=
"请输入内存容量"
style=
"width: 180px;"
:disabled=
"isDetailMode"
/>
</el-form-item>
<el-form-item
label=
"存储配置"
prop=
"storage"
style=
"margin-bottom: 0;"
>
<el-select
v-model=
"formData.storage"
placeholder=
"请选择存储配置"
clearable
style=
"width: 180px;"
:disabled=
"isDetailMode"
>
<el-option
v-for=
"option in storageOptions"
:key=
"option.id"
:label=
"option.configOption"
:value=
"option.configOption"
/>
</el-select>
</el-form-item>
</el-form-item>
</div>
<!-- 新增:电源和网卡配置 -->
<!-- 5. 选择地区 / 服务器所在地 -->
<div
style=
"display: flex; margin-bottom: 24px;"
>
<div
style=
"display: flex; margin-bottom: 24px"
>
<el-form-item
label=
"电源配置"
prop=
"powerSupply"
style=
"margin-right: 20px; margin-bottom: 0;"
>
<el-form-item
label=
"选择地区"
prop=
"areaId"
style=
"margin-bottom: 0"
>
<el-select
v-model=
"formData.powerSupply"
placeholder=
"请选择电源配置"
clearable
style=
"width: 180px;"
:disabled=
"isDetailMode"
>
<el-option
v-for=
"option in powerSupplyOptions"
:key=
"option.id"
:label=
"option.configOption"
:value=
"option.configOption"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"网卡配置"
prop=
"nic"
style=
"margin-bottom: 0;"
>
<el-select
v-model=
"formData.nic"
placeholder=
"请选择网卡配置"
clearable
style=
"width: 180px;"
:disabled=
"isDetailMode"
>
<el-option
v-for=
"option in nicOptions"
:key=
"option.id"
:label=
"option.configOption"
:value=
"option.configOption"
/>
</el-select>
</el-form-item>
</div>
<!-- 5. 服务器信息 -->
<div
style=
"display: flex; margin-bottom: 24px;"
>
<el-form-item
label=
"选择地区"
prop=
"areaId"
style=
"margin-bottom: 0;"
>
<el-cascader
<el-cascader
v-if=
"areaList.length > 0"
v-if=
"areaList.length > 0"
v-model=
"formData.areaId"
v-model=
"formData.areaId"
...
@@ -144,52 +98,49 @@
...
@@ -144,52 +98,49 @@
placeholder="请选择省市区"
placeholder="请选择省市区"
clearable
clearable
filterable
filterable
style="width: 300px
;
"
style="width: 300px"
:disabled="isDetailMode"
:disabled="isDetailMode"
@change="handleAreaChange"
@change="handleAreaChange"
/>
/>
<el-text
v-else
type=
"info"
>
加载中...
</el-text>
<el-text
v-else
type=
"info"
>
加载中...
</el-text>
</el-form-item>
</el-form-item>
</div>
</div>
<div
style=
"display: flex; margin-bottom: 24px;"
>
<div
style=
"display: flex; margin-bottom: 24px"
>
<el-form-item
label=
"服务器所在地"
prop=
"location"
style=
"margin-right: 20px; margin-bottom: 0;"
>
<el-form-item
label=
"服务器所在地"
prop=
"location"
style=
"margin-bottom: 0"
>
<el-input
v-model=
"formData.location"
placeholder=
"选择地区后自动显示"
style=
"width: 180px;"
disabled
/>
<el-input
</el-form-item>
v-model=
"formData.location"
<el-form-item
label=
"服务器IP"
prop=
"ip"
style=
"margin-bottom: 0;"
>
placeholder=
"选择地区后自动显示"
<el-input
v-model=
"formData.ip"
placeholder=
"请输入服务器IP"
style=
"width: 180px;"
:disabled=
"isDetailMode"
/>
style=
"width: 180px"
</el-form-item>
disabled
</div>
/>
<div
style=
"display: flex; margin-bottom: 24px;"
>
<el-form-item
label=
"初始用户名"
prop=
"initUsername"
style=
"margin-right: 20px; margin-bottom: 0;"
>
<el-input
v-model=
"formData.initUsername"
placeholder=
"请输入初始用户名"
style=
"width: 180px;"
autocomplete=
"off"
:disabled=
"isDetailMode"
/>
</el-form-item>
<el-form-item
label=
"初始密码"
prop=
"initPassword"
style=
"margin-bottom: 0;"
>
<el-input
v-model=
"formData.initPassword"
placeholder=
"请输入初始密码"
style=
"width: 180px;"
autocomplete=
"off"
:disabled=
"isDetailMode"
/>
</el-form-item>
</el-form-item>
</div>
</div>
<!-- 6. 商品销量 -->
<!-- 9. 商品销量 -->
<div
style=
"display: flex; margin-bottom: 24px;"
>
<div
style=
"display: flex; margin-bottom: 24px"
>
<el-form-item
label=
"商品销量"
prop=
"sales"
style=
"margin-bottom: 0;"
>
<el-form-item
label=
"商品销量"
prop=
"sales"
style=
"margin-bottom: 0"
>
<el-input
v-model=
"formData.sales"
placeholder=
"请输入商品销量"
style=
"width: 150px;"
:disabled=
"isDetailMode"
/>
<el-input
v-model=
"formData.sales"
placeholder=
"请输入商品销量"
style=
"width: 150px"
:disabled=
"isDetailMode"
/>
</el-form-item>
</el-form-item>
</div>
</div>
<!-- 10. 商品简介 -->
<!-- 7. 商品简介 -->
<el-form-item
label=
"商品简介"
prop=
"intro"
>
<el-form-item
label=
"商品简介"
prop=
"intro"
>
<el-input
<el-input
v-model=
"formData.intro"
v-model=
"formData.intro"
placeholder=
"请输入商品简介"
placeholder=
"请输入商品简介"
type=
"textarea"
type=
"textarea"
rows=
"3"
:
rows=
"3"
style=
"width: 400px
;
"
style=
"width: 400px"
:disabled=
"isDetailMode"
:disabled=
"isDetailMode"
/>
/>
</el-form-item>
</el-form-item>
<!--
8
. 轮播图地址 - 已隐藏 -->
<!--
11
. 轮播图地址 - 已隐藏 -->
<!--
<el-form-item
label=
"轮播图地址"
prop=
"sliderPicUrls"
>
<!--
<el-form-item
label=
"轮播图地址"
prop=
"sliderPicUrls"
>
<el-input
<el-input
v-model=
"formData.sliderPicUrls"
v-model=
"formData.sliderPicUrls"
...
@@ -200,7 +151,7 @@
...
@@ -200,7 +151,7 @@
/>
/>
</el-form-item>
-->
</el-form-item>
-->
<!--
9
. 状态 -->
<!--
12
. 状态 -->
<el-form-item
label=
"状态"
prop=
"status"
>
<el-form-item
label=
"状态"
prop=
"status"
>
<el-radio-group
v-model=
"formData.status"
:disabled=
"isDetailMode"
>
<el-radio-group
v-model=
"formData.status"
:disabled=
"isDetailMode"
>
<el-radio
<el-radio
...
@@ -214,11 +165,17 @@
...
@@ -214,11 +165,17 @@
</el-form-item>
</el-form-item>
<!-- 操作按钮 -->
<!-- 操作按钮 -->
<el-form-item
style=
"text-align: center; margin-top: 30px;"
>
<el-form-item
style=
"text-align: center; margin-top: 30px"
>
<el-button
v-if=
"!isDetailMode"
:loading=
"formLoading"
type=
"primary"
size=
"large"
@
click=
"submitForm"
>
<el-button
v-if=
"!isDetailMode"
:loading=
"formLoading"
type=
"primary"
size=
"large"
@
click=
"submitForm"
>
保存
保存
</el-button>
</el-button>
<el-button
size=
"large"
style=
"margin-left: 20px
;
"
@
click=
"close"
>
<el-button
size=
"large"
style=
"margin-left: 20px"
@
click=
"close"
>
{{
isDetailMode
?
'返回'
:
'取消'
}}
{{
isDetailMode
?
'返回'
:
'取消'
}}
</el-button>
</el-button>
</el-form-item>
</el-form-item>
...
@@ -230,8 +187,8 @@
...
@@ -230,8 +187,8 @@
import
{
DICT_TYPE
,
getIntDictOptions
,
getStrDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
,
getStrDictOptions
}
from
'@/utils/dict'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
useResourceConfigStore
}
from
'@/store/modules/compute/hardwareConfig'
import
{
ResourceSpu
,
ResourceSpuApi
}
from
'@/api/compute/resourcespu'
import
{
ResourceSpu
,
ResourceSpuApi
}
from
'@/api/compute/resourcespu'
import
{
ResourceConfigApi
}
from
'@/api/compute/resourceconfig'
import
*
as
AreaApi
from
'@/api/system/area'
import
*
as
AreaApi
from
'@/api/system/area'
defineOptions
({
name
:
'ResourceSpuAdd'
})
defineOptions
({
name
:
'ResourceSpuAdd'
})
...
@@ -242,7 +199,7 @@ const { push, currentRoute } = useRouter() // 路由
...
@@ -242,7 +199,7 @@ const { push, currentRoute } = useRouter() // 路由
const
{
params
,
name
}
=
useRoute
()
// 查询参数
const
{
params
,
name
}
=
useRoute
()
// 查询参数
// 获取路由参数
// 获取路由参数
const
{
mode
,
id
}
=
params
as
{
mode
?:
string
,
id
?:
string
}
const
{
mode
,
id
}
=
params
as
{
mode
?:
string
;
id
?:
string
}
// 判断当前模式
// 判断当前模式
const
currentMode
=
computed
(()
=>
mode
||
'add'
)
const
currentMode
=
computed
(()
=>
mode
||
'add'
)
...
@@ -250,70 +207,97 @@ const isDetailMode = computed(() => currentMode.value === 'detail')
...
@@ -250,70 +207,97 @@ const isDetailMode = computed(() => currentMode.value === 'detail')
const
isEditMode
=
computed
(()
=>
currentMode
.
value
===
'edit'
)
const
isEditMode
=
computed
(()
=>
currentMode
.
value
===
'edit'
)
const
isAddMode
=
computed
(()
=>
currentMode
.
value
===
'add'
)
const
isAddMode
=
computed
(()
=>
currentMode
.
value
===
'add'
)
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
resourceConfigStore
=
useResourceConfigStore
()
// 资源配置store
const
formLoading
=
ref
(
false
)
// 表单的加载中
const
formLoading
=
ref
(
false
)
// 表单的加载中
const
formRef
=
ref
()
// 表单 Ref
const
formRef
=
ref
()
// 表单 Ref
const
categoryList
=
ref
<
any
[]
>
([])
const
categoryList
=
ref
<
any
[]
>
([])
const
areaList
=
ref
<
any
[]
>
([])
// 地区树数据
const
areaList
=
ref
<
any
[]
>
([])
// 地区树数据
// 从store获取硬件配置选项
// 规格分组(按 configCategory 分组多选)
const
cpuOptions
=
computed
(()
=>
resourceConfigStore
.
getOptionsByType
(
'cpu'
))
interface
SpecOption
{
const
gpuOptions
=
computed
(()
=>
resourceConfigStore
.
getOptionsByType
(
'gpu'
))
id
:
number
const
ramOptions
=
computed
(()
=>
resourceConfigStore
.
getOptionsByType
(
'ram'
))
configOption
:
string
const
storageOptions
=
computed
(()
=>
resourceConfigStore
.
getOptionsByType
(
'storage'
))
detailRemark
?:
string
const
powerSupplyOptions
=
computed
(()
=>
resourceConfigStore
.
getOptionsByType
(
'powerSupply'
))
}
const
nicOptions
=
computed
(()
=>
resourceConfigStore
.
getOptionsByType
(
'nic'
))
interface
SpecCategory
{
value
:
string
// 字典 value(对应 configCategory)
label
:
string
// 字典 label
options
:
SpecOption
[]
}
const
specCategories
=
ref
<
SpecCategory
[]
>
([])
const
formData
=
ref
<
ResourceSpu
>
({
const
formData
=
ref
<
ResourceSpu
>
({
id
:
undefined
,
id
:
undefined
,
name
:
undefined
,
name
:
undefined
,
cpu
:
undefined
,
gpu
:
undefined
,
ram
:
undefined
,
storage
:
undefined
,
powerSupply
:
undefined
,
nic
:
undefined
,
ip
:
undefined
,
initUsername
:
undefined
,
initPassword
:
undefined
,
intro
:
undefined
,
intro
:
undefined
,
categoryId
:
undefined
,
categoryId
:
undefined
,
source
:
undefined
,
source
:
undefined
,
picUrl
:
undefined
,
picUrl
:
undefined
,
sliderPicUrls
:
undefined
,
sliderPicUrls
:
undefined
,
compute
:
undefined
,
gpuCount
:
undefined
,
coreCount
:
undefined
,
memoryCapacity
:
undefined
,
sales
:
undefined
,
sales
:
undefined
,
location
:
undefined
,
status
:
undefined
,
areaId
:
undefined
,
// 规格相关
status
:
undefined
specConfigIds
:
[],
specConfigIdsByCategory
:
{}
})
})
const
formRules
=
reactive
({
const
formRules
=
reactive
({
name
:
[{
required
:
true
,
message
:
'算力资源名称不能为空'
,
trigger
:
'blur'
}],
name
:
[{
required
:
true
,
message
:
'算力资源名称不能为空'
,
trigger
:
'blur'
}],
cpu
:
[{
required
:
true
,
message
:
'CPU配置不能为空'
,
trigger
:
'blur'
}],
gpu
:
[{
required
:
true
,
message
:
'GPU配置不能为空'
,
trigger
:
'blur'
}],
ram
:
[{
required
:
true
,
message
:
'内存配置不能为空'
,
trigger
:
'blur'
}],
compute
:
[{
required
:
true
,
message
:
'算力不能为空'
,
trigger
:
'blur'
}],
gpuCount
:
[{
required
:
true
,
message
:
'GPU卡数量不能为空'
,
trigger
:
'blur'
}],
coreCount
:
[{
required
:
true
,
message
:
'CPU数量不能为空'
,
trigger
:
'blur'
}],
memoryCapacity
:
[{
required
:
true
,
message
:
'内存大小不能为空'
,
trigger
:
'blur'
}],
storage
:
[{
required
:
true
,
message
:
'存储配置不能为空'
,
trigger
:
'blur'
}],
powerSupply
:
[{
required
:
true
,
message
:
'电源配置不能为空'
,
trigger
:
'blur'
}],
nic
:
[{
required
:
true
,
message
:
'网卡配置不能为空'
,
trigger
:
'blur'
}],
ip
:
[{
required
:
true
,
message
:
'服务器ip不能为空'
,
trigger
:
'blur'
}],
initUsername
:
[{
required
:
true
,
message
:
'初始用户名不能为空'
,
trigger
:
'blur'
}],
initPassword
:
[{
required
:
true
,
message
:
'初始密码不能为空'
,
trigger
:
'blur'
}],
categoryId
:
[{
required
:
true
,
message
:
'算力资源分类编号不能为空'
,
trigger
:
'blur'
}],
categoryId
:
[{
required
:
true
,
message
:
'算力资源分类编号不能为空'
,
trigger
:
'blur'
}],
picUrl
:
[{
required
:
true
,
message
:
'商品封面图不能为空'
,
trigger
:
'blur'
}],
picUrl
:
[{
required
:
true
,
message
:
'商品封面图不能为空'
,
trigger
:
'blur'
}],
sales
:
[{
required
:
true
,
message
:
'商品销量不能为空'
,
trigger
:
'blur'
}],
sales
:
[{
required
:
true
,
message
:
'商品销量不能为空'
,
trigger
:
'blur'
}],
areaId
:
[{
required
:
true
,
message
:
'服务器所在地不能为空'
,
trigger
:
'blur'
}],
status
:
[{
required
:
true
,
message
:
'状态不能为空'
,
trigger
:
'blur'
}]
status
:
[{
required
:
true
,
message
:
'状态不能为空'
,
trigger
:
'blur'
}]
})
})
/**
* 加载规格分组选项:
* 1. 从字典 compute_resource_config_category 读取所有类别
* 2. 对每个类别调 listSimpleConfigByCategory 拿该类别的所有规格项
*/
const
loadSpecOptions
=
async
()
=>
{
const
categoryDicts
=
getStrDictOptions
(
DICT_TYPE
.
COMPUTE_RESOURCE_CONFIG_CATEGORY
)
if
(
!
categoryDicts
.
length
)
{
specCategories
.
value
=
[]
return
}
const
results
=
await
Promise
.
all
(
categoryDicts
.
map
(
async
(
cat
)
=>
{
const
options
=
await
ResourceConfigApi
.
listSimpleConfigByCategory
(
cat
.
value
as
string
)
return
{
value
:
cat
.
value
as
string
,
label
:
cat
.
label
,
options
:
(
options
||
[])
as
SpecOption
[]
}
})
)
specCategories
.
value
=
results
// 预填 specConfigIdsByCategory 键,确保 el-checkbox-group v-model 能正常绑定
for
(
const
cat
of
specCategories
.
value
)
{
if
(
!
formData
.
value
.
specConfigIdsByCategory
[
cat
.
value
])
{
formData
.
value
.
specConfigIdsByCategory
[
cat
.
value
]
=
[]
}
}
}
/**
* 把后端返回的扁平 specConfigIds 按 configCategory 分布到各分组
*/
const
distributeSpecConfigIds
=
()
=>
{
const
flat
=
new
Set
(
formData
.
value
.
specConfigIds
||
[])
// 先清空所有分组
for
(
const
cat
of
specCategories
.
value
)
{
formData
.
value
.
specConfigIdsByCategory
[
cat
.
value
]
=
[]
}
// 再按 configId 匹配回填
for
(
const
cat
of
specCategories
.
value
)
{
for
(
const
opt
of
cat
.
options
)
{
if
(
flat
.
has
(
opt
.
id
))
{
formData
.
value
.
specConfigIdsByCategory
[
cat
.
value
].
push
(
opt
.
id
)
}
}
}
}
/** 地区选择变化时,自动设置 location 为市级名称(第二级) */
/** 地区选择变化时,自动设置 location 为市级名称(第二级) */
const
handleAreaChange
=
(
value
:
number
[])
=>
{
const
handleAreaChange
=
(
value
:
number
[])
=>
{
if
(
!
value
||
value
.
length
===
0
)
{
if
(
!
value
||
value
.
length
===
0
)
{
...
@@ -345,14 +329,24 @@ const handleAreaChange = (value: number[]) => {
...
@@ -345,14 +329,24 @@ const handleAreaChange = (value: number[]) => {
formData
.
value
.
location
=
findCityName
(
areaList
.
value
,
cityId
)
||
undefined
formData
.
value
.
location
=
findCityName
(
areaList
.
value
,
cityId
)
||
undefined
}
}
/** 提交表单 */
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
const
submitForm
=
async
()
=>
{
await
formRef
.
value
.
validate
()
await
formRef
.
value
.
validate
()
// 校验:至少选 1 个规格
const
allSpecIds
=
(
Object
.
values
(
formData
.
value
.
specConfigIdsByCategory
||
{}).
flat
()
||
[])
as
number
[]
if
(
!
allSpecIds
.
length
)
{
message
.
error
(
'请至少选择 1 个规格'
)
return
}
// 扁平化写入 formData.specConfigIds
formData
.
value
.
specConfigIds
=
allSpecIds
formLoading
.
value
=
true
formLoading
.
value
=
true
try
{
try
{
const
data
=
formData
.
value
as
unknown
as
ResourceSpu
const
data
=
{
...
formData
.
value
}
as
unknown
as
ResourceSpu
// 提交时不发送 specConfigIdsByCategory(仅前端辅助字段)
delete
(
data
as
any
).
specConfigIdsByCategory
if
(
isEditMode
.
value
)
{
if
(
isEditMode
.
value
)
{
// 编辑
// 编辑
await
ResourceSpuApi
.
updateResourceSpu
(
data
)
await
ResourceSpuApi
.
updateResourceSpu
(
data
)
...
@@ -379,11 +373,11 @@ const close = () => {
...
@@ -379,11 +373,11 @@ const close = () => {
/** 初始化 */
/** 初始化 */
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
try
{
try
{
// 并行加载分类列表、
硬件配置选项和地区树
// 并行加载分类列表、
地区树和规格分组
const
[
categoryResponse
,
_
,
areaResponse
]
=
await
Promise
.
all
([
const
[
categoryResponse
,
areaResponse
]
=
await
Promise
.
all
([
ResourceSpuApi
.
listSimpleCategory
(),
ResourceSpuApi
.
listSimpleCategory
(),
resourceConfigStore
.
loadConfigOptions
(),
AreaApi
.
getAreaTree
(),
AreaApi
.
getAreaTree
()
loadSpecOptions
()
])
])
categoryList
.
value
=
categoryResponse
categoryList
.
value
=
categoryResponse
areaList
.
value
=
areaResponse
areaList
.
value
=
areaResponse
...
@@ -393,6 +387,12 @@ onMounted(async () => {
...
@@ -393,6 +387,12 @@ onMounted(async () => {
formLoading
.
value
=
true
formLoading
.
value
=
true
try
{
try
{
formData
.
value
=
await
ResourceSpuApi
.
getResourceSpu
(
Number
(
id
))
formData
.
value
=
await
ResourceSpuApi
.
getResourceSpu
(
Number
(
id
))
// 初始化 specConfigIdsByCategory 为空对象(接口可能不返回)
if
(
!
formData
.
value
.
specConfigIdsByCategory
)
{
formData
.
value
.
specConfigIdsByCategory
=
{}
}
// 把 specConfigIds 分布到各分组
distributeSpecConfigIds
()
}
finally
{
}
finally
{
formLoading
.
value
=
false
formLoading
.
value
=
false
}
}
...
@@ -402,3 +402,27 @@ onMounted(async () => {
...
@@ -402,3 +402,27 @@ onMounted(async () => {
}
}
})
})
</
script
>
</
script
>
<
style
scoped
>
.spec-config-wrapper
{
width
:
100%
;
}
.spec-category-row
{
display
:
flex
;
align-items
:
flex-start
;
margin-bottom
:
12px
;
}
.spec-category-label
{
width
:
80px
;
text-align
:
right
;
padding-right
:
12px
;
color
:
#606266
;
line-height
:
32px
;
flex-shrink
:
0
;
}
.spec-empty
{
color
:
#909399
;
font-size
:
13px
;
padding
:
8px
0
;
}
</
style
>
src/views/compute/resourcespu/index.vue
View file @
5d3142e4
...
@@ -18,12 +18,7 @@
...
@@ -18,12 +18,7 @@
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"CPU"
prop=
"cpu"
>
<el-form-item
label=
"CPU"
prop=
"cpu"
>
<el-select
<el-select
v-model=
"queryParams.cpu"
placeholder=
"请选择CPU配置"
clearable
class=
"!w-240px"
>
v-model=
"queryParams.cpu"
placeholder=
"请选择CPU配置"
clearable
class=
"!w-240px"
>
<el-option
<el-option
v-for=
"option in resourceConfigStore.getOptionsByType('cpu')"
v-for=
"option in resourceConfigStore.getOptionsByType('cpu')"
:key=
"option.id"
:key=
"option.id"
...
@@ -33,12 +28,7 @@
...
@@ -33,12 +28,7 @@
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item
label=
"GPU"
prop=
"gpu"
>
<el-form-item
label=
"GPU"
prop=
"gpu"
>
<el-select
<el-select
v-model=
"queryParams.gpu"
placeholder=
"请选择GPU配置"
clearable
class=
"!w-240px"
>
v-model=
"queryParams.gpu"
placeholder=
"请选择GPU配置"
clearable
class=
"!w-240px"
>
<el-option
<el-option
v-for=
"option in resourceConfigStore.getOptionsByType('gpu')"
v-for=
"option in resourceConfigStore.getOptionsByType('gpu')"
:key=
"option.id"
:key=
"option.id"
...
@@ -77,33 +67,33 @@
...
@@ -77,33 +67,33 @@
/>
/>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<!--
<el-form-item
label=
"服务器ip"
prop=
"ip"
>
-->
<!--
<el-form-item
label=
"服务器ip"
prop=
"ip"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.ip"-->
<!-- v-model="queryParams.ip"-->
<!-- placeholder="请输入服务器ip"-->
<!-- placeholder="请输入服务器ip"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"初始用户名"
prop=
"initUsername"
>
-->
<!--
<el-form-item
label=
"初始用户名"
prop=
"initUsername"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.initUsername"-->
<!-- v-model="queryParams.initUsername"-->
<!-- placeholder="请输入初始用户名"-->
<!-- placeholder="请输入初始用户名"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"初始密码"
prop=
"initPassword"
>
-->
<!--
<el-form-item
label=
"初始密码"
prop=
"initPassword"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.initPassword"-->
<!-- v-model="queryParams.initPassword"-->
<!-- placeholder="请输入初始密码"-->
<!-- placeholder="请输入初始密码"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<el-form-item
label=
"商品简介"
prop=
"intro"
>
<el-form-item
label=
"商品简介"
prop=
"intro"
>
<el-input
<el-input
v-model=
"queryParams.intro"
v-model=
"queryParams.intro"
...
@@ -158,49 +148,44 @@
...
@@ -158,49 +148,44 @@
/>
/>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<!--
<el-form-item
label=
"商品封面图"
prop=
"picUrl"
>
-->
<!--
<el-form-item
label=
"商品封面图"
prop=
"picUrl"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.picUrl"-->
<!-- v-model="queryParams.picUrl"-->
<!-- placeholder="请输入商品封面图"-->
<!-- placeholder="请输入商品封面图"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"商品轮播图地址"
prop=
"sliderPicUrls"
>
-->
<!--
<el-form-item
label=
"商品轮播图地址"
prop=
"sliderPicUrls"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.sliderPicUrls"-->
<!-- v-model="queryParams.sliderPicUrls"-->
<!-- placeholder="请输入商品轮播图地址"-->
<!-- placeholder="请输入商品轮播图地址"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"总库存数量"
prop=
"stock"
>
-->
<!--
<el-form-item
label=
"总库存数量"
prop=
"stock"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.stock"-->
<!-- v-model="queryParams.stock"-->
<!-- placeholder="请输入总库存数量"-->
<!-- placeholder="请输入总库存数量"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"商品销量"
prop=
"sales"
>
-->
<!--
<el-form-item
label=
"商品销量"
prop=
"sales"
>
-->
<!--
<el-input-->
<!--
<el-input-->
<!-- v-model="queryParams.sales"-->
<!-- v-model="queryParams.sales"-->
<!-- placeholder="请输入商品销量"-->
<!-- placeholder="请输入商品销量"-->
<!-- clearable-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- />-->
<!--
</el-form-item>
-->
<!--
</el-form-item>
-->
<el-form-item
label=
"状态"
prop=
"status"
>
<el-form-item
label=
"状态"
prop=
"status"
>
<el-select
<el-select
v-model=
"queryParams.status"
placeholder=
"请选择状态"
clearable
class=
"!w-240px"
>
v-model=
"queryParams.status"
placeholder=
"请选择状态"
clearable
class=
"!w-240px"
>
<el-option
<el-option
v-for=
"dict in statusOptions"
v-for=
"dict in statusOptions"
:key=
"dict.value"
:key=
"dict.value"
...
@@ -264,7 +249,7 @@
...
@@ -264,7 +249,7 @@
@
selection-change=
"handleRowCheckboxChange"
@
selection-change=
"handleRowCheckboxChange"
>
>
<el-table-column
type=
"selection"
width=
"55"
/>
<el-table-column
type=
"selection"
width=
"55"
/>
<!--
<el-table-column
label=
"主键ID"
align=
"center"
prop=
"id"
/>
-->
<!--
<el-table-column
label=
"主键ID"
align=
"center"
prop=
"id"
/>
-->
<el-table-column
label=
"算力资源名称"
min-width=
"200"
>
<el-table-column
label=
"算力资源名称"
min-width=
"200"
>
<template
#
default=
"
{ row }">
<template
#
default=
"
{ row }">
<div
class=
"flex"
>
<div
class=
"flex"
>
...
@@ -285,6 +270,18 @@
...
@@ -285,6 +270,18 @@
<!-- 硬件配置字段已隐藏 -->
<!-- 硬件配置字段已隐藏 -->
<!-- <el-table-column label="算力资源分类编号" align="center" prop="categoryId" />-->
<!-- <el-table-column label="算力资源分类编号" align="center" prop="categoryId" />-->
<el-table-column
label=
"算力资源分类"
align=
"center"
prop=
"categoryName"
min-width=
"150"
/>
<el-table-column
label=
"算力资源分类"
align=
"center"
prop=
"categoryName"
min-width=
"150"
/>
<el-table-column
label=
"规格"
min-width=
"240"
>
<
template
#
default=
"{ row }"
>
<el-tag
v-for=
"(value, key) in row.specMap"
:key=
"key"
size=
"small"
class=
"!mr-1"
>
{{
key
}}
:
{{
value
}}
</el-tag>
</
template
>
</el-table-column>
<el-table-column
label=
"算力来源"
align=
"center"
prop=
"source"
min-width=
"120"
>
<el-table-column
label=
"算力来源"
align=
"center"
prop=
"source"
min-width=
"120"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.COMPUTE_RESOURCE_SOURCE"
:value=
"scope.row.source"
/>
<dict-tag
:type=
"DICT_TYPE.COMPUTE_RESOURCE_SOURCE"
:value=
"scope.row.source"
/>
...
@@ -353,7 +350,7 @@ import { dateFormatter } from '@/utils/formatTime'
...
@@ -353,7 +350,7 @@ import { dateFormatter } from '@/utils/formatTime'
import
download
from
'@/utils/download'
import
download
from
'@/utils/download'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
ResourceSpuApi
,
ResourceSpu
}
from
'@/api/compute/resourcespu'
import
{
ResourceSpuApi
,
ResourceSpu
}
from
'@/api/compute/resourcespu'
import
{
DICT_TYPE
,
getIntDictOptions
,
getStrDictOptions
}
from
"@/utils/dict"
;
import
{
DICT_TYPE
,
getIntDictOptions
,
getStrDictOptions
}
from
'@/utils/dict'
import
{
useResourceConfigStore
}
from
'@/store/modules/compute/hardwareConfig'
import
{
useResourceConfigStore
}
from
'@/store/modules/compute/hardwareConfig'
const
{
push
}
=
useRouter
()
const
{
push
}
=
useRouter
()
...
@@ -393,7 +390,6 @@ const queryParams = reactive({
...
@@ -393,7 +390,6 @@ const queryParams = reactive({
const
queryFormRef
=
ref
()
// 搜索的表单
const
queryFormRef
=
ref
()
// 搜索的表单
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
statusOptions
=
getIntDictOptions
(
DICT_TYPE
.
COMPUTE_RESOURCE_SPU_STATUS
)
const
statusOptions
=
getIntDictOptions
(
DICT_TYPE
.
COMPUTE_RESOURCE_SPU_STATUS
)
/** 查询列表 */
/** 查询列表 */
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
...
@@ -453,15 +449,15 @@ const handleDeleteBatch = async () => {
...
@@ -453,15 +449,15 @@ const handleDeleteBatch = async () => {
try
{
try
{
// 删除的二次确认
// 删除的二次确认
await
message
.
delConfirm
()
await
message
.
delConfirm
()
await
ResourceSpuApi
.
deleteResourceSpuList
(
checkedIds
.
value
)
;
await
ResourceSpuApi
.
deleteResourceSpuList
(
checkedIds
.
value
)
message
.
success
(
t
(
'common.delSuccess'
))
message
.
success
(
t
(
'common.delSuccess'
))
await
getList
()
;
await
getList
()
}
catch
{}
}
catch
{}
}
}
const
checkedIds
=
ref
<
number
[]
>
([])
const
checkedIds
=
ref
<
number
[]
>
([])
const
handleRowCheckboxChange
=
(
records
:
ResourceSpu
[])
=>
{
const
handleRowCheckboxChange
=
(
records
:
ResourceSpu
[])
=>
{
checkedIds
.
value
=
records
.
map
((
item
)
=>
item
.
id
)
;
checkedIds
.
value
=
records
.
map
((
item
)
=>
item
.
id
)
}
}
/** 商品图预览 */
/** 商品图预览 */
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment