更新模型配置和订阅页面

- 修改模型模块的字段名称,从 `keyword` 更改为 `modelName`,以提高一致性。
- 添加模型类型和访问类型的选择功能,增强用户交互体验。
- 移除不必要的调试日志,优化代码整洁性。
- 更新订阅页面的错误处理逻辑,确保用户在加载失败时获得清晰反馈。
This commit is contained in:
2026-05-11 13:48:20 +08:00
parent 76420713fa
commit 0a42e700e2
9 changed files with 617 additions and 249 deletions

View File

@@ -468,7 +468,6 @@
try {
const token = getToken();
console.log('[subscribe] token:', token);
const headers = {
'Content-Type': 'application/json',
@@ -483,7 +482,6 @@
// 先获取文本检查是否为有效JSON
const text = await response.text();
console.log('[subscribe] response text:', text);
let result;
try {
@@ -502,7 +500,6 @@
renderTypeList(tenantModuleTypes);
renderSkuList(assetData.skus || []);
} catch (error) {
console.error('加载失败:', error);
showError(error.message || '加载套餐信息失败,请稍后重试');
} finally {
showLoading(false);
@@ -692,8 +689,6 @@
// 延迟跳转回原页面
const targetUrl = decodeURIComponent(returnUrl);
// console.log('[subscribe] 开通成功,即将跳转到:', targetUrl);
// console.log('[subscribe] 原始 returnUrl:', returnUrl);
setTimeout(() => {
let finalUrl;

View File

@@ -3,7 +3,7 @@ import request from '/@/utils/request';
export interface ModelModuleListParams {
pageNum?: number;
pageSize?: number;
keyword?: string;
modelName?: string;
}
export interface ModelFormItem {
@@ -13,6 +13,54 @@ export interface ModelFormItem {
type: 'input' | 'number' | 'textarea' | 'switch' | string;
}
export interface ModelFormEntry {
key: string;
value: string;
}
/** 模型类型listType 接口项,字段名以后端为准,前端做兼容解析) */
export interface ModelTypeListItem {
id?: number | string;
typeId?: number | string;
modelsType?: number | string;
name?: string;
typeName?: string;
label?: string;
}
/** listType 标准返回data.type 为 Record<id, 名称>,如 { "1": "推理模型", "2": "图片模型" } */
export function normalizeModelTypeOptions(res: { data?: unknown }): Array<{ id: number | string; label: string }> {
const data = res?.data as { type?: Record<string, string> } | undefined;
const typeMap = data?.type;
if (typeMap && typeof typeMap === 'object' && !Array.isArray(typeMap)) {
return Object.entries(typeMap)
.map(([key, label]) => {
const n = Number(key);
const id = Number.isNaN(n) ? key : n;
return { id, label: String(label ?? '') };
})
.sort((a, b) => {
const na = Number(a.id);
const nb = Number(b.id);
if (!Number.isNaN(na) && !Number.isNaN(nb)) {
return na - nb;
}
return String(a.id).localeCompare(String(b.id));
});
}
/** 兼容旧结构data 为数组或 data.list */
const raw = res?.data;
const arr: ModelTypeListItem[] = Array.isArray(raw) ? raw : ((raw as { list?: ModelTypeListItem[] })?.list ?? []);
return arr
.map((item) => {
const id = item.id ?? item.typeId ?? item.modelsType;
const label = item.name ?? item.typeName ?? item.label ?? (id != null && id !== '' ? String(id) : '');
return { id: id as number | string, label: label || String(id) };
})
.filter((x) => x.id !== undefined && x.id !== null && x.id !== '');
}
export interface ModelModuleItem {
id: number | string;
tenantId?: number;
@@ -23,18 +71,28 @@ export interface ModelModuleItem {
deletedAt?: string | null;
isDeleted?: boolean;
modelName: string;
/** 模型类型 ID与 listType 返回项对应 */
modelsType?: number | string;
baseUrl: string;
route: string;
route?: string;
httpMethod: string;
apiKey?: string;
isPrivate?: number;
isChatModel?: number;
enabled: number;
maxConcurrency: number;
queueLimit: number;
timeoutMs: number;
timeoutMs?: number;
timeoutSeconds?: number;
expectedSeconds?: number;
retryTimes: number;
retryQueueMaxSeconds: number;
autoCleanSeconds: number;
remark?: string;
headMsg?: string;
form?: ModelFormEntry[] | Record<string, { value: string }>;
requestMapping?: Record<string, unknown>;
responseMapping?: Record<string, unknown>;
}
export interface ModelModuleListResponse {
@@ -48,17 +106,23 @@ export interface ModelModuleListResponse {
export interface CreateModelParams {
modelName: string;
modelsType: number;
/** 与 listType 返回的类型 id 一致,可能为数字或字符串 */
modelsType: number | string;
baseUrl: string;
route: string;
httpMethod: string;
httpMethod?: string;
headMsg?: string;
isPrivate: number;
enabled: number;
maxConcurrency: number;
queueLimit: number;
isChatModel: number;
apiKey?: string;
form: ModelFormEntry[];
requestMapping?: Record<string, unknown>;
responseMapping?: Record<string, unknown>;
maxConcurrency?: number;
queueLimit?: number;
timeoutSeconds: number;
expectedSeconds: number;
retryTimes: number;
retryTimes?: number;
retryQueueMaxSeconds: number;
autoCleanSeconds: number;
remark?: string;
@@ -93,6 +157,16 @@ export function getModelModuleList(params?: ModelModuleListParams) {
});
}
/**
* 获取模型类型列表(用于下拉与列表回显)
*/
export function getModelTypeList() {
return request({
url: '/model-gateway/model/listType',
method: 'get',
});
}
/**
* 新增模型配置
*/
@@ -110,7 +184,7 @@ export function addModelModule(data: CreateModelParams) {
export function updateModelModule(data: Partial<CreateModelParams> & { id: number | string }) {
return request({
url: '/model-gateway/model/updateModel',
method: 'post',
method: 'put',
data,
});
}
@@ -121,17 +195,18 @@ export function updateModelModule(data: Partial<CreateModelParams> & { id: numbe
export function deleteModelModule(id: number | string) {
return request({
url: '/model-gateway/model/deleteModel',
method: 'post',
method: 'delete',
data: { id },
});
}
/**
* 获取模型配置(按类型分组
* 获取单条模型配置详情(编辑用,需传 id
*/
export function getModelConfig() {
export function getModelModuleDetail(id: number | string) {
return request({
url: '/model-gateway/model/getModel',
method: 'get',
params: { id },
});
}

View File

@@ -2,8 +2,10 @@
<el-dialog v-model="visible" title="选择模型" width="1000px" :close-on-click-modal="false" @close="handleClose">
<div class="model-selector-header">
<div class="search-bar">
<el-input v-model="searchParams.keyword" placeholder="搜索模型名称" clearable @clear="handleSearch">
<template #prefix><el-icon><Search /></el-icon></template>
<el-input v-model="searchParams.modelName" placeholder="搜索模型名称" clearable @clear="handleSearch">
<template #prefix
><el-icon><Search /></el-icon
></template>
</el-input>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</div>
@@ -94,7 +96,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<Emits>();
const visible = ref(false);
const searchParams = reactive({ keyword: '' });
const searchParams = reactive({ modelName: '' });
const pagination = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const modelList = ref<ModelItem[]>([]);
const loading = ref(false);
@@ -133,7 +135,7 @@ const fetchModelList = async () => {
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
keyword: searchParams.keyword || undefined,
modelName: searchParams.modelName || undefined,
};
const res = await getModelModuleList(params, { errorMode: 'message' });
modelList.value = res.data?.list || [];

View File

@@ -41,7 +41,6 @@ export function redirectToSubscribePage(assetId: string) {
const returnUrl = encodeURIComponent(window.location.href);
// 构建跳转URL
const url = `${SUBSCRIBE_PAGE_URL}?assetId=${assetId}&returnUrl=${returnUrl}`;
console.log('[redirectToSubscribePage] 跳转到开通页面:', url);
window.location.href = url;
}
@@ -49,17 +48,13 @@ export function redirectToSubscribePage(assetId: string) {
* 处理 402 错误码(模块未开通)
*/
export function handleModuleNotEnabled(routePath: string): boolean {
console.log('[模块未开通] 当前路由路径:', routePath);
const assetInfo = getAssetInfoByRoute(routePath);
console.log('[模块未开通] 匹配到的资产信息:', assetInfo);
if (assetInfo) {
redirectToSubscribePage(assetInfo.assetId);
return true;
}
// 如果没有匹配到路由,尝试使用默认的资产管理
console.warn('[模块未开通] 未匹配到路由,使用默认资产管理');
redirectToSubscribePage('696b4acd1be1c8b76c4b4c15');
return true;
}

View File

@@ -209,7 +209,6 @@ const responseInterceptor = (response: AxiosResponse) => {
} else {
// 未知的 code统一提示后端异常
errorMsg = '后端异常,请联系管理员';
console.error(`未知的业务错误码: ${code}, 原始消息: ${message}`);
}
showErrorMessage(errorMsg, config);

View File

@@ -19,7 +19,7 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="8">
<el-form-item label="资产分类" prop="categoryId">
<el-cascader
v-model="ruleForm.categoryId"
@@ -33,7 +33,6 @@
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8">
@@ -98,12 +97,7 @@
class="w100"
clearable
>
<el-option
v-for="opt in attr.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
<el-option v-for="opt in attr.options" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
<!-- 多选类型 -->
<el-select
@@ -114,12 +108,7 @@
multiple
clearable
>
<el-option
v-for="opt in attr.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
<el-option v-for="opt in attr.options" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
<!-- 文本类型 -->
<el-input
@@ -128,11 +117,7 @@
:placeholder="'请输入' + getAttrLabel(attr)"
/>
<!-- 数字类型 -->
<el-input-number
v-else-if="attr.type === 'number'"
v-model="ruleForm.metadata[getAttrKey(attr)]"
class="w100"
/>
<el-input-number v-else-if="attr.type === 'number'" v-model="ruleForm.metadata[getAttrKey(attr)]" class="w100" />
<!-- 日期类型 -->
<el-date-picker
v-else-if="attr.type === 'date'"
@@ -142,10 +127,7 @@
class="w100"
/>
<!-- 布尔类型 -->
<el-switch
v-else-if="attr.type === 'boolean'"
v-model="ruleForm.metadata[getAttrKey(attr)]"
/>
<el-switch v-else-if="attr.type === 'boolean'" v-model="ruleForm.metadata[getAttrKey(attr)]" />
<!-- 图片类型 -->
<div v-else-if="attr.type === 'image'" class="w100">
<el-upload
@@ -185,13 +167,7 @@
<el-col :span="8">
<el-form-item label="主图" prop="mainImage">
<div class="w100">
<el-upload
class="avatar-uploader"
:show-file-list="false"
:auto-upload="true"
:http-request="handleMainImageUpload"
accept="image/*"
>
<el-upload class="avatar-uploader" :show-file-list="false" :auto-upload="true" :http-request="handleMainImageUpload" accept="image/*">
<img v-if="mainImagePreview" :src="mainImagePreview" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
@@ -220,7 +196,6 @@
</el-col>
</el-row>
<!-- 资产描述 -->
<el-divider content-position="left">资产描述</el-divider>
<el-form-item label="描述内容" label-width="100px">
@@ -229,7 +204,6 @@
</div>
</el-form-item>
<!-- 实物资产配置 -->
<template v-if="ruleForm.type === 'physical'">
<el-divider content-position="left">实物资产配置</el-divider>
@@ -302,9 +276,17 @@
<el-input v-model="item.key" placeholder="Key" style="width: 200px" />
<span class="separator">:</span>
<el-input v-model="item.value" placeholder="Value" style="width: 200px" />
<el-button type="danger" :icon="Delete" circle size="small" @click="removeKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.headers, index)" />
<el-button
type="danger"
:icon="Delete"
circle
size="small"
@click="removeKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.headers, index)"
/>
</div>
<el-button type="primary" :icon="Plus" size="small" @click="addKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.headers)">添加请求头</el-button>
<el-button type="primary" :icon="Plus" size="small" @click="addKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.headers)"
>添加请求头</el-button
>
</div>
</el-form-item>
</el-col>
@@ -317,9 +299,17 @@
<el-input v-model="item.key" placeholder="Key" style="width: 200px" />
<span class="separator">:</span>
<el-input v-model="item.value" placeholder="Value" style="width: 200px" />
<el-button type="danger" :icon="Delete" circle size="small" @click="removeKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.params, index)" />
<el-button
type="danger"
:icon="Delete"
circle
size="small"
@click="removeKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.params, index)"
/>
</div>
<el-button type="primary" :icon="Plus" size="small" @click="addKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.params)">添加请求参数</el-button>
<el-button type="primary" :icon="Plus" size="small" @click="addKeyValuePair(ruleForm.virtualAssetConfig.apiConfig.params)"
>添加请求参数</el-button
>
</div>
</el-form-item>
</el-col>
@@ -337,7 +327,13 @@
</el-col>
<el-col :span="16" v-if="ruleForm.virtualAssetConfig.apiConfig.authType !== 'none'">
<el-form-item label="认证配置" prop="virtualAssetConfig.apiConfig.authConfig">
<el-input v-model="ruleForm.virtualAssetConfig.apiConfig.authConfig" type="textarea" :rows="2" placeholder="请输入认证配置" class="w100" />
<el-input
v-model="ruleForm.virtualAssetConfig.apiConfig.authConfig"
type="textarea"
:rows="2"
placeholder="请输入认证配置"
class="w100"
/>
</el-form-item>
</el-col>
</el-row>
@@ -386,9 +382,13 @@
<!-- 时间段配置 -->
<el-form-item label="服务时间" prop="serviceAssetConfig.serviceAssetArrivalConfig.schedule.timeSlots">
<div class="config-list-container">
<div v-for="(slot, index) in ruleForm.serviceAssetConfig.serviceAssetArrivalConfig.schedule.timeSlots" :key="index" class="config-list-item">
<div
v-for="(slot, index) in ruleForm.serviceAssetConfig.serviceAssetArrivalConfig.schedule.timeSlots"
:key="index"
class="config-list-item"
>
<el-select v-model="slot.dayOfWeek" placeholder="星期" style="width: 100px">
<el-option v-for="d in 7" :key="d" :label="'周' + ['一','二','三','四','五','六','日'][d-1]" :value="String(d)" />
<el-option v-for="d in 7" :key="d" :label="'周' + ['一', '二', '三', '四', '五', '六', '日'][d - 1]" :value="String(d)" />
</el-select>
<el-time-picker v-model="slot.startTime" format="HH:mm" value-format="HH:mm" placeholder="开始" style="width: 100px" />
<span class="separator">-</span>
@@ -396,29 +396,33 @@
<el-input-number v-model="slot.capacity" :min="1" placeholder="容量" style="width: 100px" controls-position="right" />
<el-button type="danger" :icon="Delete" circle size="small" @click="removeTimeSlot(index)" />
</div>
<el-button
type="primary"
:icon="Plus"
size="small"
@click="addTimeSlot"
:disabled="isTimeSlotLimitReached"
>
添加时间段
</el-button>
<el-button type="primary" :icon="Plus" size="small" @click="addTimeSlot" :disabled="isTimeSlotLimitReached"> 添加时间段 </el-button>
</div>
</el-form-item>
<!-- 例外日期配置 -->
<el-form-item label="休息时间">
<div class="config-list-container">
<div v-for="(exc, index) in ruleForm.serviceAssetConfig.serviceAssetArrivalConfig.schedule.exceptions" :key="index" class="config-list-item">
<div
v-for="(exc, index) in ruleForm.serviceAssetConfig.serviceAssetArrivalConfig.schedule.exceptions"
:key="index"
class="config-list-item"
>
<el-select v-model="exc.exceptionType" placeholder="类型" style="width: 100px" @change="onExceptionTypeChange(exc)">
<el-option label="指定日期" value="date" />
<el-option label="指定星期" value="dayOfWeek" />
</el-select>
<el-date-picker v-if="exc.exceptionType === 'date'" v-model="exc.date" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="日期" style="width: 130px" />
<el-date-picker
v-if="exc.exceptionType === 'date'"
v-model="exc.date"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="日期"
style="width: 130px"
/>
<el-select v-if="exc.exceptionType === 'dayOfWeek'" v-model="exc.dayOfWeek" placeholder="星期" style="width: 100px">
<el-option v-for="d in 7" :key="d" :label="'周' + ['一','二','三','四','五','六','日'][d-1]" :value="String(d)" />
<el-option v-for="d in 7" :key="d" :label="'周' + ['一', '二', '三', '四', '五', '六', '日'][d - 1]" :value="String(d)" />
</el-select>
<el-select v-model="exc.status" placeholder="状态" style="width: 90px">
<el-option label="可用" :value="1" />
@@ -591,8 +595,6 @@ const assetFormDiff = createFormDiff<Record<string, any>>();
// 获取租户ID
const tenantId = ref(Session.get('userInfo')?.tenantId || '');
console.log(tenantId.value,'租户id');
const formatImageUrl = (url?: string) => {
if (!url) return '';
@@ -1252,9 +1254,7 @@ const buildRequestBody = async (): Promise<any> => {
// 只有单选和多选类型才传递 options且只传递选中的值对应的选项
if ((attr.type === 'select' || attr.type === 'multi_select') && attr.options && value) {
const selectedValues = Array.isArray(value) ? value : [value];
metaItem.options = attr.options.filter((opt: { label: string; value: string }) =>
selectedValues.includes(opt.value)
);
metaItem.options = attr.options.filter((opt: { label: string; value: string }) => selectedValues.includes(opt.value));
}
metadataArray.push(metaItem);
@@ -1296,7 +1296,7 @@ const onSubmit = async () => {
closeDialog();
emit('getAssetList');
} catch (error) {
console.error('提交失败:', error);
ElMessage.error('提交失败');
} finally {
submitLoading.value = false;
}

View File

@@ -1,5 +1,5 @@
<template>
<el-dialog v-model="dialogVisible" :title="`SKU管理 - ${assetName}`" width="1200px" :close-on-click-modal="false" @close="onClose">
<el-dialog v-model="dialogVisible" :title="`SKU管理 - ${assetName}`" width="1200px" :close-on-click-modal="false" @close="onClose">
<!-- 搜索区域 -->
<div class="sku-search mb15">
<el-button type="primary" @click="onOpenAddSku">
@@ -28,10 +28,15 @@
{{ item.name }}: {{ item.options?.[0]?.label || (Array.isArray(item.value) ? item.value[0] : item.value) }}
</el-tag>
</span>
<span v-else-if="scope.row.specValues && typeof scope.row.specValues === 'object' && !Array.isArray(scope.row.specValues) && Object.keys(scope.row.specValues).length > 0">
<el-tag v-for="(value, key) in scope.row.specValues" :key="key" size="small" style="margin-right: 4px">
{{ key }}: {{ value }}
</el-tag>
<span
v-else-if="
scope.row.specValues &&
typeof scope.row.specValues === 'object' &&
!Array.isArray(scope.row.specValues) &&
Object.keys(scope.row.specValues).length > 0
"
>
<el-tag v-for="(value, key) in scope.row.specValues" :key="key" size="small" style="margin-right: 4px"> {{ key }}: {{ value }} </el-tag>
</span>
<span v-else>-</span>
</template>
@@ -47,9 +52,7 @@
</template>
</el-table-column>
<el-table-column prop="price" label="价格" width="90" align="center">
<template #default="scope">
¥{{ (scope.row.price / 100).toFixed(2) }}
</template>
<template #default="scope"> ¥{{ (scope.row.price / 100).toFixed(2) }} </template>
</el-table-column>
<el-table-column prop="stock" label="库存数量" width="100" align="center">
<template #default="scope">
@@ -122,7 +125,15 @@
<div class="spec-values-container">
<div v-for="attr in assetSpecAttrs" :key="attr.name" class="spec-item">
<span class="spec-label">{{ attr.name }}</span>
<el-select v-if="attr.options && attr.options.length > 0" v-model="specValuesMap[attr.name]" placeholder="请选择" style="width: 120px" filterable allow-create clearable>
<el-select
v-if="attr.options && attr.options.length > 0"
v-model="specValuesMap[attr.name]"
placeholder="请选择"
style="width: 120px"
filterable
allow-create
clearable
>
<el-option v-for="opt in attr.options" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
<el-input v-else v-model="specValuesMap[attr.name]" placeholder="请输入" style="width: 120px" />
@@ -144,12 +155,7 @@
<span style="margin-left: 8px; color: #909399; font-size: 12px">数值越小越靠前</span>
</el-form-item>
<el-form-item label="图片" prop="imageUrl" required>
<el-upload
class="sku-image-uploader"
:show-file-list="false"
:http-request="handleSkuImageUpload"
accept="image/*"
>
<el-upload class="sku-image-uploader" :show-file-list="false" :http-request="handleSkuImageUpload" accept="image/*">
<img v-if="skuImagePreview" :src="skuImagePreview" class="sku-image" />
<el-icon v-else class="sku-image-uploader-icon"><ele-Plus /></el-icon>
</el-upload>
@@ -194,13 +200,7 @@
style="width: 200px"
/>
<!-- 文本类型 -->
<el-input
v-else
v-model="stockForm[field.name]"
:maxlength="field.maxLength"
placeholder="请输入"
style="width: 200px"
/>
<el-input v-else v-model="stockForm[field.name]" :maxlength="field.maxLength" placeholder="请输入" style="width: 200px" />
</el-form-item>
</template>
</el-form>
@@ -217,7 +217,18 @@
import { ref, reactive } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import { listAssetSkus, createAssetSku, updateAssetSku, deleteAssetSku, getAssetSku, getAsset, uploadAssetImage, getSpecsUnitOptions, getStockFormFields, stockOperation } from '/@/api/assets/asset';
import {
listAssetSkus,
createAssetSku,
updateAssetSku,
deleteAssetSku,
getAssetSku,
getAsset,
uploadAssetImage,
getSpecsUnitOptions,
getStockFormFields,
stockOperation,
} from '/@/api/assets/asset';
import { createFormDiff } from '/@/utils/diffUtils';
import OperationLogDialog from '../../component/operationLogDialog.vue';
import type { UploadRequestOptions, UploadUserFile } from 'element-plus';
@@ -307,11 +318,11 @@ const skuRules: FormRules = {
specsUnit: [{ required: true, message: '请选择规格单位', trigger: 'change' }],
specsCount: [
{ required: true, message: '请输入规格数量', trigger: 'blur' },
{ type: 'number', min: 1, message: '规格数量必须大于0', trigger: 'blur' }
{ type: 'number', min: 1, message: '规格数量必须大于0', trigger: 'blur' },
],
price: [
{ required: true, message: '请输入价格', trigger: 'blur' },
{ type: 'number', min: 0.01, message: '价格必须大于0', trigger: 'blur' }
{ type: 'number', min: 0.01, message: '价格必须大于0', trigger: 'blur' },
],
stock: [
{ required: true, message: '请输入库存数量', trigger: 'blur' },
@@ -325,11 +336,10 @@ const skuRules: FormRules = {
callback();
}
},
trigger: 'blur'
}
trigger: 'blur',
},
],
imageUrl: [{ required: true, message: '请上传SKU图片', trigger: 'change' }],
};
// 打开弹窗
@@ -584,7 +594,6 @@ const onGenerateStock = async (row: any) => {
}
});
} catch (error) {
console.error('获取库存表单字段失败:', error);
ElMessage.error('获取库存表单字段失败');
stockFormVisible.value = false;
} finally {
@@ -619,7 +628,7 @@ const onSubmitStock = async () => {
stockFormVisible.value = false;
getSkuList();
} catch (error) {
console.error('库存操作失败:', error);
ElMessage.error('库存操作失败');
} finally {
stockSubmitLoading.value = false;
}

View File

@@ -1,7 +1,14 @@
<template>
<div class="system-edit-module-container">
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="900px">
<el-form ref="editModuleFormRef" :model="state.ruleForm" :rules="state.rules" size="default" label-width="140px">
<el-form
ref="editModuleFormRef"
v-loading="state.dialog.detailLoading"
:model="state.ruleForm"
:rules="state.rules"
size="default"
label-width="140px"
>
<!-- 基础配置 -->
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@@ -12,20 +19,18 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="模型类型" prop="modelsType">
<el-select v-model="state.ruleForm.modelsType" placeholder="请选择模型类型" clearable style="width: 100%">
<el-option label="图片模型" :value="1"></el-option>
<el-option label="语音模型" :value="2"></el-option>
<el-option label="推理模型" :value="3"></el-option>
<el-option
v-for="t in modelTypeOptions"
:key="String(t.id)"
:label="t.label"
:value="typeOptionValue(t.id)"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="模型地址" prop="baseUrl">
<el-input v-model="state.ruleForm.baseUrl" placeholder="请输入模型地址" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由" prop="route">
<el-input v-model="state.ruleForm.route" placeholder="请输入路由" clearable></el-input>
<el-form-item label="模型服务地址" prop="baseUrl">
<el-input v-model="state.ruleForm.baseUrl" placeholder="请输入模型服务地址" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@@ -38,6 +43,33 @@
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="访问类型" prop="isPrivate">
<el-radio-group v-model="state.ruleForm.isPrivate" @change="onIsPrivateChange">
<el-radio :label="0">私有</el-radio>
<el-radio :label="1">公共</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="API 密钥" prop="apiKey">
<el-input
v-model="state.ruleForm.apiKey"
type="password"
show-password
placeholder="请输入 API 密钥字符串"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否对话模型" prop="isChatModel">
<el-radio-group v-model="state.ruleForm.isChatModel">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="请求头绑定" prop="headMsg">
<el-button @click="showHeaderDialog = true" style="width: 100%"> 配置请求头 ({{ state.headers.length }}) </el-button>
@@ -110,13 +142,43 @@
<el-input-number v-model="state.ruleForm.autoCleanSeconds" :min="0" :max="86400" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="请求映射" prop="requestMappingJson">
<el-input
v-model="state.ruleForm.requestMappingJson"
type="textarea"
:rows="4"
placeholder='JSON 对象,例如 {}'
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="响应映射" prop="responseMappingJson">
<el-input
v-model="state.ruleForm.responseMappingJson"
type="textarea"
:rows="4"
placeholder='JSON 对象,例如 {}'
clearable
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-collapse-transition>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default" :loading="state.dialog.loading">{{ state.dialog.submitTxt }}</el-button>
<el-button
type="primary"
@click="onSubmit"
size="default"
:loading="state.dialog.loading"
:disabled="state.dialog.detailLoading"
>
{{ state.dialog.submitTxt }}
</el-button>
</span>
</template>
</el-dialog>
@@ -161,11 +223,32 @@
</template>
<script setup lang="ts" name="systemEditModule">
import { reactive, ref } from 'vue';
import { reactive, ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
import { addModelModule, updateModelModule } from '/@/api/digitalHuman/modelConfig/modelModule/index';
import {
addModelModule,
updateModelModule,
getModelModuleDetail,
type ModelFormEntry,
} from '/@/api/digitalHuman/modelConfig/modelModule/index';
export type ModelTypeOption = { id: number | string; label: string };
const props = withDefaults(
defineProps<{
modelTypes?: ModelTypeOption[];
}>(),
{ modelTypes: () => [] as ModelTypeOption[] }
);
const modelTypeOptions = computed(() => props.modelTypes);
const typeOptionValue = (id: number | string): number | string => {
const n = Number(id);
return Number.isNaN(n) ? id : n;
};
const editModuleFormRef = ref();
const emit = defineEmits(['refresh']);
@@ -175,12 +258,14 @@ const state = reactive({
ruleForm: {
id: '',
modelName: '',
modelsType: 1,
modelsType: null as number | string | null,
baseUrl: '',
route: '',
httpMethod: 'POST',
headMsg: '',
isPrivate: 0,
apiKey: '',
enabled: 1,
isChatModel: 0,
maxConcurrency: 10,
queueLimit: 100,
timeoutSeconds: 30,
@@ -189,13 +274,67 @@ const state = reactive({
retryQueueMaxSeconds: 60,
autoCleanSeconds: 300,
remark: '',
requestMappingJson: '{}',
responseMappingJson: '{}',
},
rules: {
modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
modelsType: [{ required: true, message: '请选择模型类型', trigger: 'change' }],
baseUrl: [{ required: true, message: '请输入模型地址', trigger: 'blur' }],
route: [{ required: true, message: '请输入路由', trigger: 'blur' }],
modelsType: [
{
validator: (_rule: unknown, value: unknown, callback: (e?: Error) => void) => {
if (value === null || value === undefined || value === '') {
callback(new Error('请选择模型类型'));
} else {
callback();
}
},
trigger: 'change',
},
],
baseUrl: [{ required: true, message: '请输入模型服务地址', trigger: 'blur' }],
httpMethod: [{ required: true, message: '请选择请求方式', trigger: 'change' }],
requestMappingJson: [
{
validator: (_rule: unknown, value: string, callback: (e?: Error) => void) => {
if (!value || !String(value).trim()) {
callback(new Error('请输入请求映射 JSON'));
return;
}
try {
const o = JSON.parse(value);
if (o === null || typeof o !== 'object' || Array.isArray(o)) {
callback(new Error('请求映射须为 JSON 对象'));
return;
}
callback();
} catch {
callback(new Error('请求映射 JSON 格式无效'));
}
},
trigger: 'blur',
},
],
responseMappingJson: [
{
validator: (_rule: unknown, value: string, callback: (e?: Error) => void) => {
if (!value || !String(value).trim()) {
callback(new Error('请输入响应映射 JSON'));
return;
}
try {
const o = JSON.parse(value);
if (o === null || typeof o !== 'object' || Array.isArray(o)) {
callback(new Error('响应映射须为 JSON 对象'));
return;
}
callback();
} catch {
callback(new Error('响应映射 JSON 格式无效'));
}
},
trigger: 'blur',
},
],
maxConcurrency: [{ required: true, message: '请输入最大并发数', trigger: 'blur' }],
queueLimit: [{ required: true, message: '请输入排队队列上限', trigger: 'blur' }],
timeoutSeconds: [{ required: true, message: '请输入请求超时时间', trigger: 'blur' }],
@@ -207,58 +346,85 @@ const state = reactive({
title: '',
submitTxt: '',
loading: false,
detailLoading: false,
},
showAdvanced: false, // 是否展开高级配置
headers: [] as Array<{ key: string; value: string }>, // 请求头列表
showAdvanced: false,
headers: [] as Array<{ key: string; value: string }>,
formFields: [] as Array<{ key: string; value: string }>,
});
// 解析 headMsg 字符串为数组
const parseHeaders = (headMsg: string) => {
if (!headMsg) return [];
const parseHeaders = (headMsg: string) => parseKeyValueString(headMsg);
// 解析 form支持数组 [{ key, value }] 或历史对象 { k: { value } }
const parseFormFields = (form: unknown) => {
if (!form) return [];
if (Array.isArray(form)) {
return (form as ModelFormEntry[])
.filter((item) => item && (item.key !== undefined || item.value !== undefined))
.map((item) => ({
key: String(item.key ?? '').trim(),
value: String(item.value ?? '').trim(),
}));
}
if (typeof form === 'object') {
const fields: Array<{ key: string; value: string }> = [];
Object.keys(form as Record<string, unknown>).forEach((key) => {
const v = (form as Record<string, { value?: string }>)[key];
if (v && typeof v === 'object' && v.value !== undefined) {
fields.push({ key, value: String(v.value) });
}
});
return fields;
}
return [];
};
const buildFormArray = (): ModelFormEntry[] => {
return state.formFields
.filter((field) => field.key?.trim() && field.value?.trim())
.map((field) => ({ key: field.key.trim(), value: field.value.trim() }));
};
const parseKeyValueString = (raw: string) => {
if (!raw) return [];
const headers: Array<{ key: string; value: string }> = [];
const pairs = headMsg.split(',');
const pairs = raw.split(',');
pairs.forEach((pair) => {
const [key, value] = pair.split(':');
if (key && value) {
headers.push({ key: key.trim(), value: value.trim() });
const idx = pair.indexOf(':');
if (idx === -1) return;
const key = pair.slice(0, idx).trim();
const value = pair.slice(idx + 1).trim();
if (key) {
headers.push({ key, value });
}
});
return headers;
};
// 解析 form 对象为数组
const parseFormFields = (form: any) => {
if (!form || typeof form !== 'object') return [];
const fields: Array<{ key: string; value: string }> = [];
Object.keys(form).forEach((key) => {
if (form[key] && typeof form[key] === 'object' && form[key].value !== undefined) {
fields.push({ key, value: form[key].value });
}
});
return fields;
};
// 将数组转换为 form 对象
const stringifyFormFields = () => {
const formObj: Record<string, { value: string }> = {};
state.formFields.forEach((field) => {
const key = field.key?.trim();
const value = field.value?.trim();
if (key && value) {
formObj[key] = { value: value };
}
});
return formObj;
};
// 将数组转换为 headMsg 字符串
const stringifyHeaders = () => {
return state.headers
.filter((h) => h.key && h.value)
.map((h) => `${h.key}:${h.value}`)
.filter((h) => h.key?.trim() && h.value?.trim())
.map((h) => `${h.key.trim()}:${h.value.trim()}`)
.join(',');
};
const onIsPrivateChange = (val: string | number | boolean | undefined) => {
if (val === 0 || val === '0') {
state.ruleForm.apiKey = '';
}
};
const parseJsonObjectField = (raw: string, fallback: Record<string, unknown>) => {
try {
const o = JSON.parse(raw || '{}');
if (o && typeof o === 'object' && !Array.isArray(o)) {
return o as Record<string, unknown>;
}
} catch {
/* ignore */
}
return fallback;
};
// 添加请求头
const addHeader = () => {
state.headers.push({ key: '', value: '' });
@@ -289,43 +455,121 @@ const confirmFormFields = () => {
showFormDialog.value = false;
};
// 打开弹窗
const openDialog = (type: string, row?: any) => {
const ensureKeyValueRows = (rows: Array<{ key: string; value: string }>) => (rows.length ? rows : [{ key: '', value: '' }]);
/** 从 getModel 返回的 data 中取出单条模型对象 */
const unwrapModelDetailPayload = (data: unknown): Record<string, unknown> | null => {
if (data == null) return null;
if (Array.isArray(data)) return null;
if (typeof data !== 'object') return null;
const o = data as Record<string, unknown>;
if (typeof o.modelName === 'string') return o;
if (o.model && typeof o.model === 'object' && !Array.isArray(o.model)) {
return o.model as Record<string, unknown>;
}
if (o.detail && typeof o.detail === 'object' && !Array.isArray(o.detail)) {
return o.detail as Record<string, unknown>;
}
if (o.info && typeof o.info === 'object' && !Array.isArray(o.info)) {
return o.info as Record<string, unknown>;
}
return o;
};
const fillFormFromDetailRow = (row: Record<string, unknown>) => {
const timeoutSeconds =
row.timeoutSeconds != null && row.timeoutSeconds !== ''
? Number(row.timeoutSeconds)
: row.timeoutMs != null
? Math.floor(Number(row.timeoutMs) / 1000)
: 30;
const isPrivate = row.isPrivate !== undefined && row.isPrivate !== null ? Number(row.isPrivate) : 0;
state.ruleForm = {
id: row.id as string,
modelName: String(row.modelName ?? ''),
modelsType:
row.modelsType !== undefined && row.modelsType !== null
? typeOptionValue(row.modelsType as number | string)
: null,
baseUrl: String(row.baseUrl ?? ''),
httpMethod: String(row.httpMethod || 'POST'),
headMsg: String(row.headMsg || ''),
isPrivate,
apiKey: isPrivate === 1 ? String(row.apiKey ?? '') : '',
enabled: Number(row.enabled ?? 1),
isChatModel: row.isChatModel !== undefined && row.isChatModel !== null ? Number(row.isChatModel) : 0,
maxConcurrency: Number(row.maxConcurrency ?? 10),
queueLimit: Number(row.queueLimit ?? 100),
timeoutSeconds,
expectedSeconds: Number(row.expectedSeconds ?? 15),
retryTimes: Number(row.retryTimes ?? 3),
retryQueueMaxSeconds: Number(row.retryQueueMaxSeconds ?? 60),
autoCleanSeconds: Number(row.autoCleanSeconds ?? 300),
remark: String(row.remark || ''),
requestMappingJson: JSON.stringify(
row.requestMapping && typeof row.requestMapping === 'object' ? row.requestMapping : {},
null,
2
),
responseMappingJson: JSON.stringify(
row.responseMapping && typeof row.responseMapping === 'object' ? row.responseMapping : {},
null,
2
),
};
state.headers = ensureKeyValueRows(parseHeaders(String(row.headMsg || '')));
state.formFields = ensureKeyValueRows(parseFormFields(row.form));
};
// 打开弹窗(编辑时会请求 /model/getModel 详情)
const openDialog = async (type: string, row?: Record<string, unknown>) => {
state.dialog.type = type;
state.dialog.isShowDialog = true;
state.showAdvanced = false;
state.dialog.detailLoading = false;
if (type === 'edit') {
// 编辑时需要转换字段
state.ruleForm = {
id: row.id,
modelName: row.modelName,
modelsType: row.modelsType || 1,
baseUrl: row.baseUrl,
route: row.route,
httpMethod: row.httpMethod,
headMsg: row.headMsg || '',
enabled: row.enabled,
maxConcurrency: row.maxConcurrency,
queueLimit: row.queueLimit,
timeoutSeconds: Math.floor(row.timeoutMs / 1000), // 毫秒转秒
expectedSeconds: row.expectedSeconds || 15,
retryTimes: row.retryTimes,
retryQueueMaxSeconds: row.retryQueueMaxSeconds,
autoCleanSeconds: row.autoCleanSeconds,
remark: row.remark || '',
};
// 解析请求头
state.headers = parseHeaders(row.headMsg || '');
state.formFields = parseFormFields(row.form);
const listRowId = row?.id;
if (listRowId === undefined || listRowId === null || listRowId === '') {
ElMessage.error('缺少模型 ID');
state.dialog.isShowDialog = false;
return;
}
state.dialog.title = '修改模型配置';
state.dialog.submitTxt = '修 改';
state.dialog.detailLoading = true;
try {
const res: any = await getModelModuleDetail(listRowId as string | number);
if (res.code !== 0) {
ElMessage.error(res.message || '获取模型详情失败');
state.dialog.isShowDialog = false;
return;
}
const detail = unwrapModelDetailPayload(res.data) ?? row ?? null;
if (!detail || typeof detail !== 'object') {
ElMessage.error('获取模型详情失败');
state.dialog.isShowDialog = false;
return;
}
fillFormFromDetailRow(detail as Record<string, unknown>);
} catch {
ElMessage.error('获取模型详情失败');
state.dialog.isShowDialog = false;
} finally {
state.dialog.detailLoading = false;
}
} else {
state.ruleForm = {
id: '',
modelName: '',
modelsType: 1,
modelsType: null,
baseUrl: '',
route: '',
httpMethod: 'POST',
headMsg: '',
isPrivate: 0,
apiKey: '',
enabled: 1,
isChatModel: 0,
maxConcurrency: 10,
queueLimit: 100,
timeoutSeconds: 30,
@@ -334,21 +578,20 @@ const openDialog = (type: string, row?: any) => {
retryQueueMaxSeconds: 60,
autoCleanSeconds: 300,
remark: '',
requestMappingJson: '{}',
responseMappingJson: '{}',
};
// 初始化一个空的请求头
state.headers = [{ key: '', value: '' }];
state.formFields = [{ key: '', value: '' }];
state.dialog.title = '新增模型配置';
state.dialog.submitTxt = '新 增';
}
state.dialog.type = type;
state.dialog.isShowDialog = true;
state.showAdvanced = false; // 每次打开弹窗时重置为收起状态
};
// 关闭弹窗
const closeDialog = () => {
state.dialog.isShowDialog = false;
state.dialog.detailLoading = false;
editModuleFormRef.value?.resetFields();
};
@@ -364,19 +607,34 @@ const onSubmit = () => {
state.dialog.loading = true;
try {
// 将 headMsg 转换为 form 的 JSON 格式
// headMsg: "X-API-Key:xxx,operation:true"
// 转换为 form: { "X-API-Key": { "value": "xxx" }, "operation": { "value": "true" } }
const formObj = stringifyFormFields();
// 提交数据
state.ruleForm.headMsg = stringifyHeaders();
const requestMapping = parseJsonObjectField(state.ruleForm.requestMappingJson, {});
const responseMapping = parseJsonObjectField(state.ruleForm.responseMappingJson, {});
const submitData = {
...state.ruleForm,
form: formObj,
modelName: state.ruleForm.modelName,
modelsType: state.ruleForm.modelsType as number | string,
baseUrl: state.ruleForm.baseUrl,
httpMethod: state.ruleForm.httpMethod || 'POST',
headMsg: state.ruleForm.headMsg,
isPrivate: state.ruleForm.isPrivate,
enabled: state.ruleForm.enabled,
isChatModel: state.ruleForm.isChatModel,
apiKey: state.ruleForm.isPrivate === 1 ? String(state.ruleForm.apiKey ?? '').trim() : '',
form: buildFormArray(),
requestMapping,
responseMapping,
maxConcurrency: state.ruleForm.maxConcurrency,
queueLimit: state.ruleForm.queueLimit,
timeoutSeconds: state.ruleForm.timeoutSeconds,
expectedSeconds: state.ruleForm.expectedSeconds,
retryTimes: state.ruleForm.retryTimes,
retryQueueMaxSeconds: state.ruleForm.retryQueueMaxSeconds,
autoCleanSeconds: state.ruleForm.autoCleanSeconds,
remark: state.ruleForm.remark || '',
};
if (state.dialog.type === 'edit') {
await updateModelModule(submitData);
await updateModelModule({ ...submitData, id: state.ruleForm.id });
ElMessage.success('修改成功');
} else {
await addModelModule(submitData);

View File

@@ -2,7 +2,8 @@
<div class="system-user-container layout-padding">
<el-card shadow="hover" class="layout-padding-auto">
<div class="system-user-search mb15">
<el-input v-model="state.tableData.param.keyword" size="default" placeholder="请输入模型名称" style="max-width: 180px" clearable> </el-input>
<el-input v-model="state.tableData.param.modelName" size="default" placeholder="请输入模型名称" style="max-width: 180px" clearable>
</el-input>
<el-button size="default" type="primary" class="ml10" @click="getTableData">
<el-icon>
<ele-Search />
@@ -19,8 +20,17 @@
<el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="modelName" label="模型名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="baseUrl" label="模型地址" show-overflow-tooltip width="200"></el-table-column>
<el-table-column prop="route" label="路由" show-overflow-tooltip></el-table-column>
<el-table-column label="模型类型" width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ resolveModelTypeLabel(row.modelsType) }}
</template>
</el-table-column>
<!-- <el-table-column prop="baseUrl" label="模型服务地址" show-overflow-tooltip width="200"></el-table-column> -->
<el-table-column prop="isPrivate" label="访问类型" width="100">
<template #default="scope">
<el-tag :type="scope.row.isPrivate === 1 ? 'primary' : 'info'">{{ scope.row.isPrivate === 1 ? '公共' : '私有' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="httpMethod" label="请求方式" width="100"></el-table-column>
<el-table-column prop="enabled" label="状态" width="100">
<template #default="scope">
@@ -31,6 +41,7 @@
<el-table-column prop="queueLimit" label="队列上限" width="100"></el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip></el-table-column>
<el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip width="160"></el-table-column>
<el-table-column prop="updatedAt" label="修改时间" show-overflow-tooltip width="160"></el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenEditModule('edit', scope.row)">修改</el-button>
@@ -52,19 +63,25 @@
>
</el-pagination>
</el-card>
<EditModule ref="editModuleRef" @refresh="getTableData()" />
<EditModule ref="editModuleRef" :model-types="state.modelTypes" @refresh="getTableData()" />
</div>
</template>
<script setup lang="ts" name="digitalHumanModelModule">
import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { getModelModuleList, deleteModelModule } from '/@/api/digitalHuman/modelConfig/modelModule/index';
import {
getModelModuleList,
getModelTypeList,
deleteModelModule,
normalizeModelTypeOptions,
} from '/@/api/digitalHuman/modelConfig/modelModule/index';
const EditModule = defineAsyncComponent(() => import('/@/views/digitalHuman/modelConfig/modelModule/component/editModule.vue'));
const editModuleRef = ref();
const state = reactive({
modelTypes: [] as Array<{ id: number | string; label: string }>,
tableData: {
data: [],
total: 0,
@@ -72,11 +89,30 @@ const state = reactive({
param: {
pageNum: 1,
pageSize: 10,
keyword: '',
modelName: '',
},
},
});
const resolveModelTypeLabel = (modelsType: number | string | undefined | null) => {
if (modelsType === undefined || modelsType === null || modelsType === '') {
return '—';
}
const hit = state.modelTypes.find((t) => String(t.id) === String(modelsType));
return hit?.label ?? String(modelsType);
};
const loadModelTypes = async () => {
try {
const res: any = await getModelTypeList();
if (res.code === 0) {
state.modelTypes = normalizeModelTypeOptions(res);
}
} catch (e) {
ElMessage.error('获取模型类型失败:');
}
};
// 初始化表格数据
const getTableData = async () => {
state.tableData.loading = true;
@@ -87,7 +123,6 @@ const getTableData = async () => {
state.tableData.total = res.data.total || 0;
}
} catch (error) {
console.error('获取模型列表失败:', error);
ElMessage.error('获取模型列表失败');
} finally {
state.tableData.loading = false;
@@ -117,7 +152,6 @@ const onRowDel = (row: any) => {
ElMessage.success('删除成功');
getTableData();
} catch (error) {
console.error('删除失败:', error);
ElMessage.error('删除失败');
}
})
@@ -137,7 +171,8 @@ const onHandleCurrentChange = (val: number) => {
};
// 页面加载时
onMounted(() => {
onMounted(async () => {
await loadModelTypes();
getTableData();
});
</script>