更新模型配置和订阅页面

- 修改模型模块的字段名称,从 `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

@@ -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>
@@ -249,7 +223,7 @@
<!-- 虚拟资产配置 -->
<template v-if="ruleForm.type === 'virtual'">
<el-divider content-position="left">虚拟资产配置</el-divider>
<!-- 虚拟类型选择 -->
<el-row :gutter="24">
<el-col :span="8">
@@ -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>
@@ -346,7 +342,7 @@
<!-- 服务资产配置 -->
<template v-if="ruleForm.type === 'service'">
<el-divider content-position="left">服务资产配置</el-divider>
<!-- 服务类型选择 -->
<el-row :gutter="24">
<el-col :span="8">
@@ -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 '';
@@ -980,7 +982,7 @@ const openDialog = (row?: any, edit?: boolean) => {
if (data.type === 'virtual' && data.virtualAssetConfig) {
// 先处理 headers 和 params 为数组格式,再赋值
const config = { ...data.virtualAssetConfig };
// 确保 apiConfig 存在
if (!config.apiConfig) {
config.apiConfig = {
@@ -1036,7 +1038,7 @@ const openDialog = (row?: any, edit?: boolean) => {
exc.exceptionType = 'dayOfWeek';
} else {
// 默认值
exc.exceptionType = 'date';
exc.exceptionType = 'date';
}
}
});
@@ -1121,7 +1123,7 @@ const onCancel = () => {
// 上传图片并返回URL
const uploadImage = async (file: File): Promise<string> => {
const res: any = await uploadAssetImage(file);
// 1. 尝试获取并设置 fileAddressPrefix
// 优先检查顶层,再检查 data 内部
if (res.fileAddressPrefix) {
@@ -1133,7 +1135,7 @@ const uploadImage = async (file: File): Promise<string> => {
// 2. 尝试获取 fileURL / url
// 优先检查顶层 fileURL
if (res.fileURL) return res.fileURL;
// 检查 data 对象中的 fileURL 或 url
if (res.data && typeof res.data === 'object') {
if (res.data.fileURL) return res.data.fileURL;
@@ -1205,7 +1207,7 @@ const buildRequestBody = async (): Promise<any> => {
} else if (ruleForm.type === 'virtual') {
// 深拷贝 virtualAssetConfig 以避免修改原对象
const virtualConfig = JSON.parse(JSON.stringify(ruleForm.virtualAssetConfig));
// 将数组转换为对象
if (virtualConfig.apiConfig) {
if (Array.isArray(virtualConfig.apiConfig.headers)) {
@@ -1215,7 +1217,7 @@ const buildRequestBody = async (): Promise<any> => {
});
virtualConfig.apiConfig.headers = headersObj;
}
if (Array.isArray(virtualConfig.apiConfig.params)) {
const paramsObj: Record<string, string> = {};
virtualConfig.apiConfig.params.forEach((item: KeyValuePair) => {
@@ -1224,7 +1226,7 @@ const buildRequestBody = async (): Promise<any> => {
virtualConfig.apiConfig.params = paramsObj;
}
}
body.virtualAssetConfig = virtualConfig;
} else if (ruleForm.type === 'service') {
body.serviceAssetConfig = ruleForm.serviceAssetConfig;
@@ -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);
@@ -1269,13 +1269,13 @@ const buildRequestBody = async (): Promise<any> => {
const onSubmit = async () => {
const form = formRef.value;
if (!form) return;
form.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true;
try {
const fullRequestBody = await buildRequestBody();
let requestBody: any;
if (isEdit.value) {
// 编辑模式:通过 _originalData 让拦截器自动处理最小化传参
@@ -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,15 +318,15 @@ 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' },
{
{
validator: (rule: any, value: any, callback: any) => {
if (skuForm.unlimitedStock) {
callback();
@@ -324,12 +335,11 @@ const skuRules: FormRules = {
} else {
callback();
}
},
trigger: 'blur'
}
},
trigger: 'blur',
},
],
imageUrl: [{ required: true, message: '请上传SKU图片', trigger: 'change' }],
};
// 打开弹窗
@@ -560,14 +570,14 @@ const onGenerateStock = async (row: any) => {
currentSkuName.value = row.skuName;
stockFormVisible.value = true;
stockFormLoading.value = true;
// 重置表单
Object.keys(stockForm).forEach((key) => delete stockForm[key]);
try {
const res = await getStockFormFields(row.id);
stockFormFields.value = res.data.fields || [];
// 设置默认值,根据字段类型转换
stockFormFields.value.forEach((field) => {
if (field.default !== undefined) {
@@ -584,7 +594,6 @@ const onGenerateStock = async (row: any) => {
}
});
} catch (error) {
console.error('获取库存表单字段失败:', error);
ElMessage.error('获取库存表单字段失败');
stockFormVisible.value = false;
} finally {
@@ -596,7 +605,7 @@ const onGenerateStock = async (row: any) => {
const onSubmitStock = async () => {
const form = stockFormRef.value;
if (!form) return;
form.validate(async (valid: boolean) => {
if (valid) {
stockSubmitLoading.value = true;
@@ -613,13 +622,13 @@ const onSubmitStock = async () => {
submitData[field.name] = value;
}
});
await stockOperation(submitData as any);
ElMessage.success('库存生成成功');
stockFormVisible.value = false;
getSkuList();
} catch (error) {
console.error('库存操作失败:', error);
ElMessage.error('库存操作失败');
} finally {
stockSubmitLoading.value = false;
}
@@ -702,7 +711,7 @@ const formatImageUrl = (url?: string) => {
// 上传图片
const uploadImage = async (file: File): Promise<string> => {
const res: any = await uploadAssetImage(file);
if (res.fileAddressPrefix) {
fileAddressPrefix.value = res.fileAddressPrefix;
} else if (res.data && typeof res.data === 'object' && res.data.fileAddressPrefix) {
@@ -835,7 +844,7 @@ const onSubmitSku = async () => {
specsUnit: skuForm.specsUnit,
specsCount: skuForm.specsCount,
};
const changedFields = skuFormDiff.getChanges(currentFormData, {
alwaysInclude: ['id'],
transformers: {
@@ -844,15 +853,15 @@ const onSubmitSku = async () => {
imageUrl: (val) => val || undefined,
},
});
// 添加 id
const changedData: Record<string, any> = { id: editSkuId.value, ...changedFields };
// 比较规格属性
if (specValuesMapDiff.hasChanges(specValuesMap) && specValues.length > 0) {
changedData.specValues = specValues;
}
await updateAssetSku(changedData as any);
} else {
// 新增模式:传递所有字段