diff --git a/manager/src/components/editor/index.vue b/manager/src/components/editor/index.vue index 6ee79b75..1ca77c58 100644 --- a/manager/src/components/editor/index.vue +++ b/manager/src/components/editor/index.vue @@ -15,6 +15,10 @@ export default { components:{uploadImage}, name: "Tinymce", props: { + modelValue: { + type: String, + default: "", + }, value: { type: String, default: "", @@ -24,6 +28,11 @@ export default { default:'500px' } }, + computed: { + editorContent() { + return this.modelValue ?? this.value ?? ""; + }, + }, data() { return { // 引入编辑器的配置 @@ -41,19 +50,23 @@ export default { this.init(); }, watch: { - value: { + editorContent: { handler(val) { if (!this.hasChange && this.hasInit) { // 当内容有更改且编辑器已初始化时,更新编辑器的内容 - this.$nextTick(() => - window.tinymce.get(this.tinymceId).setContent(val || "") - ); + this.$nextTick(() => { + const editor = window.tinymce?.get(this.tinymceId); + if (editor) editor.setContent(val || ""); + }); } }, - deep: true, }, }, methods: { + emitContent(content) { + this.$emit("update:modelValue", content); + this.$emit("input", content); + }, // 数据返回并给富文本框插入图片 insertImage(arr){ arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(``)) @@ -68,19 +81,16 @@ export default { selector: `#${this.tinymceId}`, convert_urls: false, init_instance_callback: (editor) => { - if (_this.value) { + if (_this.editorContent) { // 如果有初始值,则设置编辑器的内容为初始值 - this.$nextTick(() => editor.setContent(_this.value)); + _this.$nextTick(() => editor.setContent(_this.editorContent)); } _this.hasInit = true; // 监听编辑器内容的变化 - editor.on("NodeChange Change KeyUp SetContent", (event) => { - if (_this.value) { - // 内容发生更改 - this.hasChange = true; - } - // 通过 input 事件将编辑器的内容传递给父组件 - this.$emit("input", editor.getContent()); + editor.on("NodeChange Change KeyUp SetContent", () => { + _this.hasChange = true; + // Vue3 v-model 使用 update:modelValue,同时保留 input 兼容旧用法 + _this.emitContent(editor.getContent()); }); }, setup(editor) { diff --git a/manager/src/libs/lazyLoading.js b/manager/src/libs/lazyLoading.js index 499ef52f..518ba1db 100644 --- a/manager/src/libs/lazyLoading.js +++ b/manager/src/libs/lazyLoading.js @@ -1,7 +1,10 @@ const viewContext = require.context("@/views", true, /\.vue$/); /** 后端菜单 frontRoute 与前端实际路径不一致时的映射 */ -const VIEW_ALIASES = {}; +const VIEW_ALIASES = { + "customWords/index": "custom-words/index", + "sensitiveWords/index": "sensitive-words/index", +}; function resolveViewPath(url) { return VIEW_ALIASES[url] || url; diff --git a/manager/src/libs/util.js b/manager/src/libs/util.js index 4d13a881..3fa19bcd 100644 --- a/manager/src/libs/util.js +++ b/manager/src/libs/util.js @@ -4,6 +4,15 @@ import Cookies from "js-cookie"; let util = {}; +/** 已弃用、前端未实现的菜单 frontRoute,注册路由时跳过 */ +const DEPRECATED_FRONT_ROUTES = new Set([ + "distribution/distrbutionGrade", +]); + +function isDeprecatedMenu(menu) { + return menu.frontRoute && DEPRECATED_FRONT_ROUTES.has(menu.frontRoute); +} + /** 动态菜单路由 name,用于登出或重新注册时清理 */ util.dynamicRouteNames = []; @@ -383,6 +392,9 @@ util.initRouterNode = function(routers, data) { // data为所有子菜单数据 for (let item of data) { + if (isDeprecatedMenu(item)) { + continue; + } const menu = Object.assign({}, item); const hasChildren = item.children && item.children.length > 0; diff --git a/manager/src/views/goods/goods-manage/parameter-edit.vue b/manager/src/views/goods/goods-manage/parameter-edit.vue index a7e84523..ece22608 100644 --- a/manager/src/views/goods/goods-manage/parameter-edit.vue +++ b/manager/src/views/goods/goods-manage/parameter-edit.vue @@ -25,35 +25,37 @@ - - - - - - - - -
- 新增 +
+ + + + + + + + +
+ 新增 +
- + 选择分类 {{ selectedCategoryNamesText }} - 已选择{{ selectedCategoryIds.length }}个分类 + 已选择{{ (form.categoryIds || []).length }}个分类 @@ -189,6 +191,28 @@ const buildCategoryIdNameMap = (list, map) => { }); }; +const buildFormState = (source, parameterId) => { + const opts = normalizeOptions(source?.options); + const options = opts.length > 0 ? opts.map((x) => ({ value: x })) : [{ value: "" }]; + const categoryList = Array.isArray(source?.categoryParameterList) + ? source.categoryParameterList + : []; + const categoryIds = toStringArray( + categoryList.map((x) => x && x.categoryId).filter(Boolean) + ); + const formState = { + paramName: source?.paramName || "", + options, + required: normalize01(source?.required, 0), + isIndex: normalize01(source?.isIndex, 0), + sort: Number(source?.sort ?? 0) || 0, + categoryIds, + }; + const id = source?.id || parameterId; + if (id) formState.id = id; + return formState; +}; + export default { name: "parameterEdit", data() { @@ -209,6 +233,7 @@ export default { required: 0, isIndex: 0, sort: 0, + categoryIds: [], }, formValidate: { paramName: [regular.REQUIRED, regular.VARCHAR5], @@ -216,6 +241,15 @@ export default { required: [{ required: true, validator: validateRadioRequired("请选择是否必填"), trigger: "change" }], isIndex: [{ required: true, validator: validateRadioRequired("请选择是否索引"), trigger: "change" }], sort: [regular.REQUIRED, regular.INTEGER], + categoryIds: [ + { + type: "array", + required: true, + min: 1, + message: "请选择关联分类", + trigger: "change", + }, + ], }, }; }, @@ -262,15 +296,24 @@ export default { back() { this.$router.push({ name: "goods-parameter" }); }, + syncCategoryIds(ids) { + const categoryIds = toStringArray(ids); + this.selectedCategoryIds = categoryIds; + if (!this.form) this.form = {}; + this.form.categoryIds = categoryIds; + }, touchOptionsValidate() { - if (this.$refs.form && this.$refs.form.validateField) { - this.$refs.form.validateField("options"); - } + this.$nextTick(() => { + const form = this.$refs.form; + if (form && typeof form.validateField === "function") { + // validateField rejects on failure; catch to avoid dev-server [object Object] overlay + 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 = []; @@ -290,17 +333,8 @@ export default { 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)); + this.form = buildFormState(dto, parameterId); + this.selectedCategoryIds = [...this.form.categoryIds]; }, async openCategoryModal() { this.categoryModalVisible = true; @@ -320,7 +354,13 @@ export default { } }, onCategoryTreeCheckChange(_data, checkedInfo) { - this.selectedCategoryIds = toStringArray(checkedInfo?.checkedKeys || []); + this.syncCategoryIds(checkedInfo?.checkedKeys || []); + this.$nextTick(() => { + const form = this.$refs.form; + if (form && typeof form.validateField === "function") { + form.validateField("categoryIds", () => {}); + } + }); }, initForm() { if (this.id) { @@ -329,15 +369,8 @@ export default { 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, - }; + this.form = buildFormState(row, row.id); + this.selectedCategoryIds = [...this.form.categoryIds]; } catch (e) { this.modalType = 0; } @@ -348,7 +381,7 @@ export default { if (!Array.isArray(this.form.options) || this.form.options.length === 0) { this.form.options = [{ value: "" }]; } - this.selectedCategoryIds = []; + this.syncCategoryIds([]); } }, handleSubmit() { @@ -356,7 +389,7 @@ export default { if (!valid) return; this.submitLoading = true; const options = normalizeOptions(this.form.options); - const categoryIds = toStringArray(this.selectedCategoryIds); + const categoryIds = toStringArray(this.form.categoryIds); const payload = { ...this.form, options: options.join(","), @@ -404,4 +437,14 @@ export default { }; - + diff --git a/manager/src/views/goods/goods-manage/parameter.vue b/manager/src/views/goods/goods-manage/parameter.vue index ccfc859e..b9c147ec 100644 --- a/manager/src/views/goods/goods-manage/parameter.vue +++ b/manager/src/views/goods/goods-manage/parameter.vue @@ -44,7 +44,7 @@ diff --git a/manager/src/views/page/article-manage/articleList.vue b/manager/src/views/page/article-manage/articleList.vue index 65f5f127..651f4627 100644 --- a/manager/src/views/page/article-manage/articleList.vue +++ b/manager/src/views/page/article-manage/articleList.vue @@ -1,20 +1,19 @@ - + +
- - + - + @@ -155,6 +155,25 @@ import { updateArticleStatus, } from "@/api/pages"; import tinymec from "@/components/editor/index.vue"; +import { regular } from "@/utils"; + +const validateArticleContent = (rule, value, callback) => { + const html = String(value || "").trim(); + if (!html) { + callback(new Error("请填写文章内容")); + return; + } + const text = html + .replace(/<[^>]+>/g, "") + .replace(/ /gi, " ") + .trim(); + const hasImage = /]/i.test(html); + if (!text && !hasImage) { + callback(new Error("请填写文章内容")); + return; + } + callback(); +}; export default { components: { @@ -190,6 +209,12 @@ export default { content: "", id: "", }, + formValidate: { + title: [{ required: true, message: "请输入文章标题", trigger: "blur" }], + categoryId: [{ required: true, message: "请选择文章分类", trigger: "change" }], + sort: [regular.REQUIRED, regular.INTEGER], + content: [{ required: true, validator: validateArticleContent, trigger: "change" }], + }, list: [], treeValue: "", treeData: [], @@ -213,6 +238,15 @@ export default { }, }, methods: { + getDefaultForm() { + return { + openStatus: false, + title: "", + categoryId: "", + sort: 1, + content: "", + }; + }, init() { this.getDataList(); this.getAllList(0); @@ -224,8 +258,8 @@ export default { handleCateChange(data) { if (!data) return; const { value, title } = data; - this.list.push({ value, title }); - this.searchForm.categoryId = value; + this.searchForm.categoryId = + value === "0" || value === 0 ? "" : value; this.searchTreeValue = title; }, changeSwitch(v) { @@ -308,10 +342,24 @@ export default { } return arr; }, + buildArticleSearchParams() { + const params = { ...this.searchForm }; + const categoryId = params.categoryId; + if ( + categoryId === "" || + categoryId === null || + categoryId === undefined || + categoryId === 0 || + categoryId === "0" + ) { + delete params.categoryId; + } + return params; + }, getDataList(val) { if (val) this.form = {}; this.loading = true; - getArticle(this.searchForm) + getArticle(this.buildArticleSearchParams()) .then((res) => { if (res.success) { this.total = res.result.total; @@ -323,6 +371,9 @@ export default { }); }, handleSubmit() { + if (this.$refs.editor?.getContent) { + this.form.content = this.$refs.editor.getContent(); + } this.$refs.form.validate((valid) => { if (valid) { this.submitLoading = true; @@ -353,20 +404,17 @@ export default { this.modalType = 0; this.modalTitle = "添加文章"; this.treeValue = ""; - this.form = { - sort: 1, - content: "", - }; - this.$refs.form?.resetFields(); - delete this.form.id; + this.form = this.getDefaultForm(); this.modalVisible = true; + this.$nextTick(() => { + this.$refs.form?.clearValidate?.(); + }); }, edit(data) { this.modalType = 1; this.modalTitle = "编辑文章"; this.treeValue = ""; - this.form = { content: "" }; - this.$refs.form?.resetFields(); + this.form = this.getDefaultForm(); seeArticle(data.id).then((res) => { if (res.result) { this.modalVisible = true; @@ -402,3 +450,35 @@ export default { }, }; + + diff --git a/manager/src/views/promotions/coupon-activity/coupon-publish.vue b/manager/src/views/promotions/coupon-activity/coupon-publish.vue index bca307e9..ec76c611 100644 --- a/manager/src/views/promotions/coupon-activity/coupon-publish.vue +++ b/manager/src/views/promotions/coupon-activity/coupon-publish.vue @@ -13,11 +13,11 @@ style="width: 260px" />
- + { - if (valid) { - const params = JSON.parse(JSON.stringify(this.form)); - this.submitLoading = true; - delete params.id; - saveActivityCoupon(params).then((res) => { - this.submitLoading = false; - if (res.success) { - this.$Message.success("优惠券活动创建成功"); - this.closeCurrentPage(); - } - }); - } - }); - } else { - this.$Message.info("自动发券只能全用户发送"); - this.form.couponActivityType = "SPECIFY"; - this.form.activityScope = "ALL"; + this.$refs.form.validate((valid) => { + if (!valid) { + return; } - } else { - this.form.startTime = this.$filters.unixToDate(this.rangeTime[0] / 1000); - this.form.endTime = this.$filters.unixToDate(this.rangeTime[1] / 1000); - this.$refs.form.validate((valid) => { - if (valid) { - const params = JSON.parse(JSON.stringify(this.form)); - this.submitLoading = true; - delete params.id; - saveActivityCoupon(params).then((res) => { - this.submitLoading = false; - if (res.success) { - this.$Message.success("优惠券活动创建成功"); - this.closeCurrentPage(); - } - }); + if (!this.form.couponActivityItems.length) { + this.$Message.warning("请至少选择一张优惠券"); + return; + } + + if ( + this.form.couponActivityType === "SPECIFY" && + this.form.activityScope === "DESIGNATED" && + !this.form.memberDTOS.length + ) { + this.$Message.warning("请选择会员"); + return; + } + + const [start, end] = this.form.rangeTime; + const startDate = start instanceof Date ? start : new Date(start); + const endDate = end instanceof Date ? end : new Date(end); + const params = JSON.parse(JSON.stringify(this.form)); + params.startTime = this.$filters.unixToDate(startDate.getTime() / 1000); + params.endTime = this.$filters.unixToDate(endDate.getTime() / 1000); + delete params.rangeTime; + delete params.id; + + this.submitLoading = true; + saveActivityCoupon(params).then((res) => { + this.submitLoading = false; + if (res.success) { + this.$Message.success("优惠券活动创建成功"); + this.closeCurrentPage(); } }); - } + }); }, closeCurrentPage() { this.$store.commit("removeTag", "add-platform-coupon"); diff --git a/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue b/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue index 42a192e7..e064b141 100644 --- a/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue +++ b/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue @@ -6,7 +6,7 @@

商品信息

- + 选择商品 @@ -116,13 +116,30 @@ export default { modalType: 0, form: { promotionGoodsList: [], + rangeTime: [], }, id: this.$route.query.id, submitLoading: false, selectedGoods: [], promotionGoodsList: [], formRule: { - rangeTime: [{ required: true, message: "请选择活动时间" }], + promotionGoodsList: [ + { + type: "array", + required: true, + min: 1, + message: "请至少选择一个商品", + trigger: "change", + }, + ], + rangeTime: [ + { + type: "array", + required: true, + message: "请选择活动时间", + trigger: "change", + }, + ], }, options: { disabledDate(date) { @@ -140,78 +157,80 @@ export default { methods: { handleSubmit() { this.$refs.form.validate((valid) => { - if (valid) { - const params = JSON.parse(JSON.stringify(this.form)); - if (this.form.rangeTime[0] === "" || this.form.rangeTime[0] === "") { - this.$Message.error("请选择活动时间"); - return; - } - params.startTime = this.$filters.unixToDate( - this.form.rangeTime[0] / 1000 - ); - params.endTime = this.$filters.unixToDate( - this.form.rangeTime[1] / 1000 - ); - delete params.rangeTime; - let checkResult = true; - if (this.form.promotionGoodsList.length > 0) { - this.form.promotionGoodsList.forEach((res) => { - if (res.stock <= 0 || res.stock > res.quantity) { - checkResult = false; - this.$Message.error("活动库存不能为0且不能超过商品库存"); - return; - } - - if (!regular.money.test(res.settlementPrice)) { - checkResult = false; - this.$Message.error("结算价格金额格式不正确"); - return; - } - if (res.settlementPrice < 0 || res.settlementPrice > res.price) { - checkResult = false; - this.$Message.error("结算价格金额不能为0且不能超过商品价格"); - return; - } - if (!regular.money.test(res.highestPrice)) { - checkResult = false; - this.$Message.error("最高可砍金额格式错误"); - return; - } - if (res.highestPrice <= 0 || res.highestPrice > res.price) { - checkResult = false; - this.$Message.error("最高可砍金额不能为0且不能超过商品价格"); - return; - } - if (!regular.money.test(res.lowestPrice)) { - checkResult = false; - this.$Message.error("最低可砍金额格式错误"); - return; - } - if (res.lowestPrice <= 0 || res.lowestPrice > res.price) { - checkResult = false; - this.$Message.error("最低可砍金额不能为0"); - return; - } - if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) { - checkResult = false; - this.$Message.error("最低砍价金额不能大于最高砍价金额"); - return; - } - }); - } - - if (!checkResult) { - return; - } - this.submitLoading = true; - saveKanJiaActivityGoods(params).then((res) => { - this.submitLoading = false; - if (res.success) { - this.$Message.success("砍价活动修改成功"); - this.closeCurrentPage(); - } - }); + if (!valid) { + return; } + if (!this.form.promotionGoodsList.length) { + this.$Message.warning("请至少选择一个商品"); + return; + } + + const params = JSON.parse(JSON.stringify(this.form)); + const [start, end] = this.form.rangeTime || []; + const startDate = start instanceof Date ? start : new Date(start); + const endDate = end instanceof Date ? end : new Date(end); + params.startTime = this.$filters.unixToDate(startDate.getTime() / 1000); + params.endTime = this.$filters.unixToDate(endDate.getTime() / 1000); + delete params.rangeTime; + + let checkResult = true; + this.form.promotionGoodsList.forEach((res) => { + if (res.stock <= 0 || res.stock > res.quantity) { + checkResult = false; + this.$Message.error("活动库存不能为0且不能超过商品库存"); + return; + } + + if (!regular.money.test(res.settlementPrice)) { + checkResult = false; + this.$Message.error("结算价格金额格式不正确"); + return; + } + if (res.settlementPrice < 0 || res.settlementPrice > res.price) { + checkResult = false; + this.$Message.error("结算价格金额不能为0且不能超过商品价格"); + return; + } + if (!regular.money.test(res.highestPrice)) { + checkResult = false; + this.$Message.error("最高可砍金额格式错误"); + return; + } + if (res.highestPrice <= 0 || res.highestPrice > res.price) { + checkResult = false; + this.$Message.error("最高可砍金额不能为0且不能超过商品价格"); + return; + } + if (!regular.money.test(res.lowestPrice)) { + checkResult = false; + this.$Message.error("最低可砍金额格式错误"); + return; + } + if (res.lowestPrice <= 0 || res.lowestPrice > res.price) { + checkResult = false; + this.$Message.error("最低可砍金额不能为0"); + return; + } + if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) { + checkResult = false; + this.$Message.error("最低砍价金额不能大于最高砍价金额"); + return; + } + }); + + if (!checkResult) { + return; + } + this.submitLoading = true; + saveKanJiaActivityGoods(params).then((res) => { + this.submitLoading = false; + if (res.success) { + this.$Message.success( + this.modalType === 1 ? "砍价活动修改成功" : "砍价活动添加成功" + ); + this.closeCurrentPage(); + } + }); }); }, closeCurrentPage() { @@ -232,6 +251,15 @@ export default { }, delGoods(index) { this.form.promotionGoodsList.splice(index, 1); + this.touchPromotionGoodsValidate(); + }, + touchPromotionGoodsValidate() { + this.$nextTick(() => { + const form = this.$refs.form; + if (form && typeof form.validateField === "function") { + form.validateField("promotionGoodsList", () => {}); + } + }); }, selectedGoodsData(item) { let list = []; @@ -252,6 +280,7 @@ export default { }); }); this.form.promotionGoodsList = list; + this.touchPromotionGoodsValidate(); }, }, }; diff --git a/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue b/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue index 081ee760..e243966c 100644 --- a/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue +++ b/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue @@ -62,7 +62,7 @@ style="width: 260px" /> - +
@@ -252,4 +251,12 @@ h4 { line-height: 40px; text-align: left; } + +.kanjia-activity-time-form-item { + :deep(.el-date-editor) { + width: 360px !important; + max-width: 360px; + flex-grow: 0; + } +} diff --git a/manager/src/views/seller/shop/shopOperation.vue b/manager/src/views/seller/shop/shopOperation.vue index 17b697f9..3b3737a1 100644 --- a/manager/src/views/seller/shop/shopOperation.vue +++ b/manager/src/views/seller/shop/shopOperation.vue @@ -370,8 +370,9 @@ :show-close="true" > @@ -566,7 +567,6 @@ export default { //选择会员 selectMember() { - this.$refs["memberLayout"].selectedMember = true; this.memberModalFlag = true; }, diff --git a/manager/src/views/sys/slider/slider.vue b/manager/src/views/sys/slider/slider.vue index 3d1b5a62..85c80eec 100644 --- a/manager/src/views/sys/slider/slider.vue +++ b/manager/src/views/sys/slider/slider.vue @@ -44,7 +44,7 @@