mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-08-06 02:47:29 +08:00
- 将多个组件中的 require 替换为 import,提升代码一致性。 - 更新上传组件中的回调函数,简化图片选择逻辑。 - 调整样式以适配 Element Plus 组件,确保界面一致性。 - 删除不再使用的样式,优化项目结构。
408 lines
14 KiB
Vue
408 lines
14 KiB
Vue
<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>
|