Files
lilishop-ui/manager/src/views/goods/goods-manage/parameter-edit.vue
田香琪 a4659ed1a2 refactor: 统一组件导入方式与样式调整
- 将多个组件中的 require 替换为 import,提升代码一致性。
- 更新上传组件中的回调函数,简化图片选择逻辑。
- 调整样式以适配 Element Plus 组件,确保界面一致性。
- 删除不再使用的样式,优化项目结构。
2026-07-08 16:22:44 +08:00

408 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="search">
<el-card>
<el-form ref="form" :model="form" label-width="100px" :rules="formValidate">
<el-form-item label="参数名称" prop="paramName">
<el-input v-model="form.paramName" clearable style="width: 520px" />
</el-form-item>
<el-form-item label="是否必填" prop="required">
<el-radio-group v-model="form.required">
<el-radio :value="0"></el-radio>
<el-radio :value="1"></el-radio>
</el-radio-group>
<span style="margin-left: 10px; color: #999; font-size: 12px">商品发布时参数是否必填</span>
</el-form-item>
<el-form-item label="是否索引" prop="isIndex">
<el-radio-group v-model="form.isIndex">
<el-radio :value="0"></el-radio>
<el-radio :value="1"></el-radio>
</el-radio-group>
<span style="margin-left: 10px; color: #999; font-size: 12px">
开启索引后用户将可以通过该参数筛选商品索引开关不影响商详页的参数展示
</span>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number :min="0" v-model="form.sort" style="width: 520px" />
</el-form-item>
<el-form-item label="参数值" prop="options">
<el-table :data="form.options" border size="small" style="width: 520px">
<el-table-column label="参数值" min-width="420">
<template #default="{ row, $index }">
<el-input
v-if="row"
v-model="form.options[$index].value"
clearable
@blur="touchOptionsValidate"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ row, $index }">
<a v-if="row" class="link-text" @click="removeOptionRow($index)">
删除
</a>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 10px">
<el-button type="primary" @click="addOptionRow">新增</el-button>
</div>
</el-form-item>
<el-form-item label="关联分类">
<el-button @click="openCategoryModal">选择分类</el-button>
<span v-if="selectedCategoryNamesText" style="margin-left: 10px; color: #999; font-size: 12px">
{{ selectedCategoryNamesText }}</span>
<span v-else style="margin-left: 10px; color: #999; font-size: 12px">
已选择{{ selectedCategoryIds.length }}个分类
</span>
</el-form-item>
<el-form-item>
<el-button @click="back">返回</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-card>
<el-dialog
v-model="categoryModalVisible"
:title="categoryModalTitle"
width="700px"
:close-on-click-modal="false"
>
<div v-loading="categoryTreeLoading" style="position: relative; max-height: 520px; overflow: auto">
<el-tree
ref="categoryTree"
:key="categoryTreeKey"
:data="categoryTreeData"
:props="{ label: 'title', children: 'children' }"
node-key="id"
show-checkbox
default-expand-all
:default-checked-keys="selectedCategoryIds"
@check="onCategoryTreeCheckChange"
/>
</div>
<template #footer>
<el-button @click="categoryModalVisible = false">取消</el-button>
<el-button type="primary" style="margin-left: 8px" @click="categoryModalVisible = false">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import {
getCategoryTree,
getGoodsParamsDetail,
insertGoodsParams,
updateGoodsParams,
} from "@/api/goods";
import { regular } from "@/utils";
import { ElMessage } from "element-plus";
const getOptionText = (value) => {
if (value && typeof value === "object") return value.value;
return value;
};
const normalizeOptions = (value) => {
const arr = Array.isArray(value)
? value
: typeof value === "string"
? value.split(",")
: [];
const cleaned = arr
.map((x) => String(getOptionText(x) ?? "").trim())
.filter((x) => x.length > 0);
return Array.from(new Set(cleaned));
};
const validateOptions = (rule, value, callback) => {
const arr = Array.isArray(value)
? value
: typeof value === "string"
? value.split(",")
: [];
const options = normalizeOptions(arr);
if (options.length === 0) {
callback(new Error("请填写参数值"));
return;
}
const joined = options.join(",");
if (!/^.{1,255}$/.test(joined)) {
callback(new Error("超出最大长度限制"));
return;
}
callback();
};
const normalize01 = (value, fallback = 0) => {
const n = Number(value);
if (n === 0 || n === 1) return n;
return fallback;
};
const buildSpringFormPayload = (payload) => {
const out = {};
if (payload && payload.id !== undefined && payload.id !== null && String(payload.id)) {
out.id = String(payload.id);
}
out.paramName = payload && payload.paramName !== undefined && payload.paramName !== null ? String(payload.paramName) : "";
out.options = payload && payload.options !== undefined && payload.options !== null ? String(payload.options) : "";
out.required = payload ? Number(payload.required) : 0;
out.isIndex = payload ? Number(payload.isIndex) : 0;
out.sort = payload ? Number(payload.sort) : 0;
const categoryList = payload && Array.isArray(payload.categoryParameterList) ? payload.categoryParameterList : [];
const categoryIds = categoryList
.map((x) => (x && x.categoryId !== undefined && x.categoryId !== null ? String(x.categoryId) : ""))
.filter((x) => x.length > 0);
categoryIds.forEach((categoryId, index) => {
out[`categoryParameterList[${index}].categoryId`] = categoryId;
});
return out;
};
const validateRadioRequired = (message) => (rule, value, callback) => {
const n = normalize01(value, NaN);
if (!(n === 0 || n === 1)) {
callback(new Error(message));
return;
}
callback();
};
const toStringArray = (arr) => {
if (!Array.isArray(arr)) return [];
return arr.map((x) => String(x)).filter((x) => x.length > 0);
};
const cacheKey = (id) => `goods-parameter-edit:${id}`;
const buildCategoryIdNameMap = (list, map) => {
if (!Array.isArray(list) || list.length === 0) return;
list.forEach((item) => {
if (!item) return;
const id = item.id !== undefined && item.id !== null ? String(item.id) : "";
if (id) map[id] = item.name;
buildCategoryIdNameMap(item.children || [], map);
});
};
export default {
name: "parameterEdit",
data() {
return {
submitLoading: false,
modalType: 0,
categoryModalVisible: false,
categoryModalTitle: "关联分类",
categoryTreeLoading: false,
categoryTreeData: [],
categoryTreeSource: [],
categoryIdNameMap: {},
categoryTreeKey: 0,
selectedCategoryIds: [],
form: {
paramName: "",
options: [{ value: "" }],
required: 0,
isIndex: 0,
sort: 0,
},
formValidate: {
paramName: [regular.REQUIRED, regular.VARCHAR5],
options: [{ required: true, validator: validateOptions, trigger: "change" }],
required: [{ required: true, validator: validateRadioRequired("请选择是否必填"), trigger: "change" }],
isIndex: [{ required: true, validator: validateRadioRequired("请选择是否索引"), trigger: "change" }],
sort: [regular.REQUIRED, regular.INTEGER],
},
};
},
computed: {
id() {
return this.$route.query && this.$route.query.id ? String(this.$route.query.id) : "";
},
selectedCategoryNamesText() {
if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) return "";
const selectedSet = new Set(toStringArray(this.selectedCategoryIds));
const isSelected = (node) => {
if (!node) return false;
const nodeId = node.id !== undefined && node.id !== null ? String(node.id) : "";
if (nodeId && selectedSet.has(nodeId)) return true;
const children = Array.isArray(node.children) ? node.children : [];
if (children.length === 0) return false;
return children.every(isSelected);
};
const collect = (node, out) => {
if (!node) return;
if (isSelected(node)) {
if (node.name) out.push(node.name);
return;
}
const children = Array.isArray(node.children) ? node.children : [];
children.forEach((c) => collect(c, out));
};
const names = [];
this.categoryTreeSource.forEach((n) => collect(n, names));
return names.join("");
},
},
watch: {
"form.required"(val) {
const n = normalize01(val, 0);
if (val !== n) this.form.required = n;
},
"form.isIndex"(val) {
const n = normalize01(val, 0);
if (val !== n) this.form.isIndex = n;
},
},
methods: {
back() {
this.$router.push({ name: "goods-parameter" });
},
touchOptionsValidate() {
if (this.$refs.form && this.$refs.form.validateField) {
this.$refs.form.validateField("options");
}
},
addOptionRow() {
if (!Array.isArray(this.form.options)) this.form.options = [];
this.form.options.push({ value: "" });
this.touchOptionsValidate();
},
removeOptionRow(index) {
if (!Array.isArray(this.form.options)) this.form.options = [];
this.form.options.splice(index, 1);
this.touchOptionsValidate();
},
buildCategoryTreeNodes(list) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => ({
id: item.id,
title: item.name,
children: this.buildCategoryTreeNodes(item.children || []),
}));
},
async loadDetail(parameterId) {
if (!parameterId) return;
const res = await getGoodsParamsDetail(parameterId).catch(() => null);
if (!(res && res.success && res.result)) return;
const dto = res.result;
const opts = normalizeOptions(dto.options);
this.form = {
id: dto.id || parameterId,
paramName: dto.paramName || "",
options: opts.map((x) => ({ value: x })),
required: normalize01(dto.required, 0),
isIndex: normalize01(dto.isIndex, 0),
sort: Number(dto.sort ?? 0) || 0,
};
const list = Array.isArray(dto.categoryParameterList) ? dto.categoryParameterList : [];
this.selectedCategoryIds = toStringArray(list.map((x) => x && x.categoryId).filter(Boolean));
},
async openCategoryModal() {
this.categoryModalVisible = true;
this.categoryTreeLoading = true;
this.categoryTreeKey += 1;
try {
if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) {
const treeRes = await getCategoryTree();
this.categoryTreeSource = treeRes && treeRes.success ? treeRes.result || [] : [];
const map = {};
buildCategoryIdNameMap(this.categoryTreeSource, map);
this.categoryIdNameMap = map;
}
this.categoryTreeData = this.buildCategoryTreeNodes(this.categoryTreeSource || []);
} finally {
this.categoryTreeLoading = false;
}
},
onCategoryTreeCheckChange(_data, checkedInfo) {
this.selectedCategoryIds = toStringArray(checkedInfo?.checkedKeys || []);
},
initForm() {
if (this.id) {
this.modalType = 1;
const cached = window.sessionStorage.getItem(cacheKey(this.id));
if (cached) {
try {
const row = JSON.parse(cached);
const opts = normalizeOptions(row.options);
this.form = {
id: row.id,
paramName: row.paramName || "",
options: opts.map((x) => ({ value: x })),
required: normalize01(row.required, 0),
isIndex: normalize01(row.isIndex, 0),
sort: Number(row.sort ?? 0) || 0,
};
} catch (e) {
this.modalType = 0;
}
}
this.loadDetail(String(this.id));
} else {
this.modalType = 0;
if (!Array.isArray(this.form.options) || this.form.options.length === 0) {
this.form.options = [{ value: "" }];
}
this.selectedCategoryIds = [];
}
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
const options = normalizeOptions(this.form.options);
const categoryIds = toStringArray(this.selectedCategoryIds);
const payload = {
...this.form,
options: options.join(","),
required: Number(this.form.required),
isIndex: Number(this.form.isIndex),
sort: Number(this.form.sort || 0),
categoryParameterList: categoryIds.map((categoryId) => ({ categoryId })),
};
if (this.modalType === 0) {
delete payload.id;
insertGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
ElMessage.success("操作成功");
this.back();
})
.finally(() => {
this.submitLoading = false;
});
} else {
updateGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
ElMessage.success("操作成功");
this.back();
})
.finally(() => {
this.submitLoading = false;
});
}
});
},
},
mounted() {
getCategoryTree().then((res) => {
if (res && res.success) {
this.categoryTreeSource = res.result || [];
const map = {};
buildCategoryIdNameMap(this.categoryTreeSource, map);
this.categoryIdNameMap = map;
}
});
this.initForm();
},
};
</script>
<style lang="scss"></style>