From a75c2d99ad982de2279bd9b4c14497d07625e75e Mon Sep 17 00:00:00 2001 From: wangfukang <15630117759@163.com> Date: Sun, 20 Sep 2026 11:04:36 +0800 Subject: [PATCH] 1 --- src/api/app.js | 8 + src/api/index.js | 13 + src/views/app/business/courier/courier.vue | 235 ++++++++++++++++- .../app/business/logistics/logistics.vue | 57 +++- src/views/app/business/luckey/luckey.vue | 4 +- src/views/app/shop/addEdit.vue | 133 ++++++++++ src/views/app/shop/dictManage.vue | 42 ++- src/views/app/shop/mernage.vue | 139 ++++++++-- src/views/app/shop/shop.vue | 13 +- src/views/app/shopArea/shopArea.vue | 140 +++++----- src/views/my-components/hiver/map.vue | 244 +++++++++++++++++- 11 files changed, 907 insertions(+), 121 deletions(-) diff --git a/src/api/app.js b/src/api/app.js index d301c29..8043a6d 100644 --- a/src/api/app.js +++ b/src/api/app.js @@ -469,6 +469,14 @@ export const editShop = (params) => { return putRequest('/app/shop/edit', params) } +export const bindShopPrinter = (params) => { + return postRequest('/app/shop/bindPrinter', params) +} + +export const getShopPrintConfig = (params) => { + return getRequest('/app/shop/printConfig', params) +} + export const findShopWorkerByMobile = (params) => { return getRequest('/app/shop/findWorkerByMobile', params) } diff --git a/src/api/index.js b/src/api/index.js index 066442b..cbdaedd 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -744,6 +744,11 @@ export const setWorkerFullTime = (id, params) => { return postRequest(`/worker/admin/fullTime/${id}`, params) } +// 设置配送员中转接单状态 +export const setWorkerTransferOrder = (id, params) => { + return postRequest(`/worker/admin/transferOrder/${id}`, params) +} + // 抢单工上线 export const onLineWorker = (params) => { return postRequest(`/worker/onLine`, params) @@ -808,6 +813,14 @@ export const orderScheduling = (params) => { export const orderSchedulingAdmin = (params) => { return postBodyRequest('/mall/delivery/admin/pagebyworker', params) } +// 后台分页查询配送员配送单量排名 +export const getWorkerDeliveryOrderCountPage = (params) => { + return postBodyRequest('/mall/delivery/countOrdersPage', params) +} +// 后台分页查询中转配送员配送单量排名 +export const getWorkerTransferDeliveryOrderCountPage = (params) => { + return postBodyRequest('/mall/delivery/countTransferOrdersPage', params) +} // 查询区域配送时长 export const getDeliveryTimeSetting = (params) => { return getRequest('/mall/admin/seckillGroup/deliveryTime/get', params) diff --git a/src/views/app/business/courier/courier.vue b/src/views/app/business/courier/courier.vue index 485e89b..8959edb 100644 --- a/src/views/app/business/courier/courier.vue +++ b/src/views/app/business/courier/courier.vue @@ -36,6 +36,10 @@ +
+ + +
@@ -90,6 +94,50 @@
+ +
+ + + + + + + +
+
+ + + +
+ +
+
@@ -104,7 +152,10 @@ offLineWorker, onLineWorker, setWorkerFullTime, + setWorkerTransferOrder, getWorkerRelaPriceByWorkerId, + getWorkerDeliveryOrderCountPage, + getWorkerTransferDeliveryOrderCountPage, } from "@/api/index"; import { shortcuts @@ -148,6 +199,49 @@ loadingExport: true, ruleVisible: false, ruleLoading: false, + orderCountVisible: false, + orderCountLoading: false, + orderCountMode: "delivery", + orderCountTitle: "查看配送单量", + orderCountSelectDate: null, + orderCountForm: { + regionId: JSON.parse(this.getStore("user")).departmentId, + startDate: "", + endDate: "", + pageNum: 1, + pageSize: 10, + }, + orderCountData: [], + orderCountTotal: 0, + orderCountColumns: [ + { + title: "排名", + width: 70, + align: "center", + render: (h, params) => { + const rank = + (this.orderCountForm.pageNum - 1) * this.orderCountForm.pageSize + + params.index + + 1; + return h("span", rank); + }, + }, + { + title: "配送员名称", + key: "workerName", + minWidth: 140, + }, + { + title: "手机号", + key: "mobile", + minWidth: 160, + }, + { + title: "配送单量", + key: "orderCount", + minWidth: 120, + }, + ], exportModalVisible: false, importModalVisible: false, drop: false, @@ -210,6 +304,14 @@ marginTop: "4px", }, }, params.row.isFullTime == 1 ? "全职配送员" : "非全职配送员"), + h("Tag", { + props: { + color: params.row.canTransferOrder == 1 ? "orange" : "default", + }, + style: { + marginTop: "4px", + }, + }, params.row.canTransferOrder == 1 ? "可接中转" : "不接中转"), ]); }, }, @@ -351,7 +453,7 @@ { title: "操作", key: "action", - width: 420, + width: 470, align: "center", fixed: "right", render: (h, params) => { @@ -424,6 +526,17 @@ }, fullTimeText ); + const transferText = params.row.canTransferOrder == 1 ? "取消中转" : "设置中转"; + const transferAction = h( + "a", { + on: { + click: () => { + this.changeTransferOrder(params.row); + }, + }, + }, + transferText + ); return h("div", [ /* h( "a", { @@ -488,6 +601,12 @@ type: "vertical", }, }), + transferAction, + h("Divider", { + props: { + type: "vertical", + }, + }), h( "a", { on: { @@ -619,6 +738,100 @@ openWithdrawRecords(v) { this.$refs.withdrawRecordModal.open(v, "worker"); }, + formatToday() { + const d = new Date(); + const m = d.getMonth() + 1; + const day = d.getDate(); + return ( + d.getFullYear() + + "-" + + (m < 10 ? "0" + m : m) + + "-" + + (day < 10 ? "0" + day : day) + ); + }, + openOrderCountModal() { + const today = this.formatToday(); + this.orderCountMode = "delivery"; + this.orderCountTitle = "查看配送单量"; + this.orderCountVisible = true; + this.orderCountSelectDate = [today, today]; + this.orderCountForm = { + regionId: JSON.parse(this.getStore("user")).departmentId, + startDate: today, + endDate: today, + pageNum: 1, + pageSize: 10, + }; + this.getOrderCountList(); + }, + openTransferOrderCountModal() { + const today = this.formatToday(); + this.orderCountMode = "transfer"; + this.orderCountTitle = "查看中转单量"; + this.orderCountVisible = true; + this.orderCountSelectDate = [today, today]; + this.orderCountForm = { + regionId: JSON.parse(this.getStore("user")).departmentId, + startDate: today, + endDate: today, + pageNum: 1, + pageSize: 10, + }; + this.getOrderCountList(); + }, + selectOrderCountDateRange(v) { + if (v && v[0] && v[1]) { + this.orderCountForm.startDate = v[0]; + this.orderCountForm.endDate = v[1]; + } else { + this.orderCountForm.startDate = ""; + this.orderCountForm.endDate = ""; + } + }, + handleOrderCountSearch() { + this.orderCountForm.pageNum = 1; + this.getOrderCountList(); + }, + handleOrderCountReset() { + const today = this.formatToday(); + this.orderCountSelectDate = [today, today]; + this.orderCountForm.startDate = today; + this.orderCountForm.endDate = today; + this.orderCountForm.pageNum = 1; + this.orderCountForm.pageSize = 10; + this.getOrderCountList(); + }, + changeOrderCountPage(v) { + this.orderCountForm.pageNum = v; + this.getOrderCountList(); + }, + changeOrderCountPageSize(v) { + this.orderCountForm.pageSize = v; + this.orderCountForm.pageNum = 1; + this.getOrderCountList(); + }, + getOrderCountList() { + this.orderCountLoading = true; + const request = this.orderCountMode === "transfer" + ? getWorkerTransferDeliveryOrderCountPage + : getWorkerDeliveryOrderCountPage; + request(this.orderCountForm) + .then((res) => { + this.orderCountLoading = false; + if (res.success) { + this.orderCountData = res.result.records || []; + this.orderCountTotal = res.result.total || 0; + if (this.orderCountData.length == 0 && this.orderCountForm.pageNum > 1) { + this.orderCountForm.pageNum -= 1; + this.getOrderCountList(); + } + } + }) + .catch(() => { + this.orderCountLoading = false; + }); + }, handleSearch() { this.searchForm.pageNum = 1; this.searchForm.pageSize = 10; @@ -831,6 +1044,26 @@ }, }); }, + changeTransferOrder(v) { + const canTransferOrder = v.canTransferOrder == 1 ? 0 : 1; + const actionText = canTransferOrder == 1 ? "设置为可接中转单" : "取消接中转单"; + this.$Modal.confirm({ + title: actionText, + content: "您确认要将配送员 " + v.workerName + " " + actionText + "吗?", + loading: true, + onOk: () => { + setWorkerTransferOrder(v.workerId, { + canTransferOrder + }).then((res) => { + this.$Modal.remove(); + if (res.success) { + this.$Message.success("操作成功"); + this.getDataList(); + } + }); + }, + }); + }, remove(v) { this.$Modal.confirm({ title: "确认删除", diff --git a/src/views/app/business/logistics/logistics.vue b/src/views/app/business/logistics/logistics.vue index c7b390d..af6df3f 100644 --- a/src/views/app/business/logistics/logistics.vue +++ b/src/views/app/business/logistics/logistics.vue @@ -65,6 +65,14 @@ word-break: break-all; } + .transfer-worker-info { + margin-top: 4px; + color: #515a6e; + font-size: 12px; + line-height: 18px; + word-break: break-all; + } + .transfer-worker-disabled-row td { background-color: #f7f7f7 !important; color: #c5c8ce; @@ -91,6 +99,8 @@ + +
@@ -280,7 +290,9 @@ }; const DELIVERY_STATUS_MAP = { daiqu: 1, - daisong: 2 + daisong: 2, + wancheng: 3, + quxiao: 4 }; export default { @@ -532,6 +544,9 @@ style: { color: "red", marginTop: "4px", fontWeight: 700 } }, "商家已出餐:" + this.formatDateTime(params.row.shopMakeTime)) : null, h("span", { class: "must-finish-time" }, "要求送达:" +this.formatDateTime(params.row.mustFinishTime)), + Number(params.row.status) === 3 && params.row.finishTime ? h("div", { + style: { color: "#2d8cf0", marginTop: "4px", fontWeight: 700 } + }, "实际送达:" + this.formatDateTime(params.row.finishTime)) : null, this.isNearMustFinishTime(params.row) ? h("div", { style: { color: "#515a6e", marginTop: "4px", fontWeight: 700 } }, this.getMustFinishWarningText(params.row)) : null @@ -549,13 +564,13 @@ }, { title: "配送员", key: "workerName", - width: 170, + width: 160, align: "center", render: (h, params) => this.renderWorkerInfo(h, params.row, clickableWorker) },{ title: "配送类型", key: "deliveryType", - width: 140, + width: 150, align: "center", render: (h, params) => this.renderDeliveryTags(h, params.row) }, { @@ -607,7 +622,10 @@ this.deliverySearchForm.pageNum = 1; this.deliverySearchForm.status = DELIVERY_STATUS_MAP[v]; this.getLogisticsInfo(); - this.refreshOrderCounts(); + // 已完成/已取消不统计数量,仅查询列表 + if (v === "daiqu" || v === "daisong") { + this.refreshOrderCounts(); + } }, handleGetAreaChange(v) { if (this.suppressAreaChange) return; @@ -1227,6 +1245,12 @@ if (row.transferPickupCode) { tags.push(h("div", { class: "transfer-pickup-code" }, "中转码:" + row.transferPickupCode)); } + if (row.transferWorkerName || row.transferWorkerMobile) { + tags.push(h("div", { class: "transfer-worker-info" }, [ + h("div", "中转员:" + (row.transferWorkerName || "-")), + row.transferWorkerMobile ? h("div", "电话:" + row.transferWorkerMobile) : null + ])); + } } if (row.isBig == 1) { tags.push(h("Tag", { props: { color: "red" } }, "超大/超重")); @@ -1328,13 +1352,34 @@ return new Date(text.replace(/-/g, "/")).getTime(); }, isNearMustFinishTime(item) { - const mustFinishTime = this.parseDeliveryTime(item && item.mustFinishTime); + if (!item) return false; + const status = Number(item.status); + // 已取消不显示超时 + if (status === 4) return false; + const mustFinishTime = this.parseDeliveryTime(item.mustFinishTime); if (isNaN(mustFinishTime)) return false; + // 已完成:用实际送达时间 finishTime 判断是否超时 + if (status === 3) { + const finishTime = this.parseDeliveryTime(item.finishTime); + if (isNaN(finishTime)) return false; + return finishTime > mustFinishTime; + } + // 待取货/待送达:仍用当前时间 return mustFinishTime - Date.now() <= 10 * 60 * 1000; }, getMustFinishWarningText(item) { const mustFinishTime = this.parseDeliveryTime(item && item.mustFinishTime); - if (!isNaN(mustFinishTime) && Date.now() > mustFinishTime) { + if (isNaN(mustFinishTime)) return ""; + const status = Number(item && item.status); + if (status === 4) return ""; + if (status === 3) { + const finishTime = this.parseDeliveryTime(item.finishTime); + if (!isNaN(finishTime) && finishTime > mustFinishTime) { + return "已超时"; + } + return ""; + } + if (Date.now() > mustFinishTime) { return "已超时"; } return "临近送达"; diff --git a/src/views/app/business/luckey/luckey.vue b/src/views/app/business/luckey/luckey.vue index 1ec3cf8..337f973 100644 --- a/src/views/app/business/luckey/luckey.vue +++ b/src/views/app/business/luckey/luckey.vue @@ -2,7 +2,7 @@
- 当前校区:{{ regionName || '未识别(请确认账号已绑定校区)' }} + @@ -556,7 +556,7 @@ ] if (!this.isPoolDrawn(params.row)) { actions.unshift(h('Button', { props: { type: 'text', size: 'small' }, on: { click: () => this.openPool(params.row) } }, '编辑')) - actions.push(h('Button', { props: { type: 'text', size: 'small' }, style: { color: '#ed4014' }, on: { click: () => this.delPool(params.row) } }, '删除')) + /* actions.push(h('Button', { props: { type: 'text', size: 'small' }, style: { color: '#ed4014' }, on: { click: () => this.delPool(params.row) } }, '删除')) */ } return h('div', actions) } diff --git a/src/views/app/shop/addEdit.vue b/src/views/app/shop/addEdit.vue index eebab34..b1d41f0 100644 --- a/src/views/app/shop/addEdit.vue +++ b/src/views/app/shop/addEdit.vue @@ -40,6 +40,21 @@ + + + + + + + + + + + + + + + @@ -109,6 +124,34 @@ + 云打印机 + + 已绑定:{{ form.printerName || form.shopName || "云打印机" }}({{ form.printerSn }}) + + + + + + + + + + + + + + + + + + + + + + + + + 商家资质 @@ -264,6 +307,8 @@ import { addShop, editShop, + bindShopPrinter, + getShopPrintConfig, findShopWorkerByMobile, initShopArea, loadShopArea, @@ -302,12 +347,19 @@ passColor: "", submitLoading: false, workerLoading: false, + printerBindLoading: false, workerMobile: "", + printerForm: { + sn: "", + key: "", + name: "", + }, maxHeight: 510, form: { address: [], merchantType: 1, isStudent: 0, + startingPrice: 0, openKitchenVideoUrl: "", supportDineIn: 0, qita: [], @@ -477,8 +529,75 @@ } }); }, + bindPrinter() { + if (!this.form.id) { + this.$Message.warning("请先保存店铺后再绑定打印机"); + return; + } + const sn = (this.printerForm.sn || "").trim(); + const key = (this.printerForm.key || "").trim(); + const name = (this.printerForm.name || this.form.shopName || "").trim(); + if (!sn) { + this.$Message.warning("请输入打印机SN"); + return; + } + if (!key) { + this.$Message.warning("请输入设备绑定码"); + return; + } + this.printerBindLoading = true; + bindShopPrinter({ + shopId: this.form.id, + sn, + key, + name, + shopName: this.form.shopName || "", + }).then((res) => { + this.printerBindLoading = false; + if (res.success) { + if (res.result) { + this.$set(this.form, "printerSn", res.result.printerSn || sn); + this.$set(this.form, "printerName", res.result.printerName || name); + this.$set(this.printerForm, "name", res.result.printerName || name); + } + this.$Message.success(res.message || "打印机绑定成功"); + } else { + this.$Message.error(res.message || "打印机绑定失败"); + } + }).catch(() => { + this.printerBindLoading = false; + }); + }, + loadPrintConfig() { + if (!this.form.id) return; + getShopPrintConfig({ + shopId: this.form.id + }).then((res) => { + if (res.success && res.result) { + const printerSn = res.result.printerSn || ""; + const printerName = res.result.printerName || this.form.shopName || ""; + this.$set(this.form, "printerSn", printerSn); + this.$set(this.form, "printerName", printerName); + this.$set(this.form, "printingMethod", res.result.printingMethod || "bluetooth"); + this.printerForm = { + sn: printerSn, + key: "", + name: printerName, + }; + } + }).catch(() => {}); + }, handleSelectShopAreaTree(v) { this.form.shopArea = Array.isArray(v) ? (v[0] || "") : v; + if (!this.form.shopArea) { + this.form.shopAreaTitle = ""; + return; + } + this.$nextTick(() => { + if (this.$refs.shopAreaTree) { + this.form.shopAreaTitle = this.$refs.shopAreaTree.shopAreaTitle || ""; + } + }); }, handleSelectTransferAddress(v) { const value = Array.isArray(v) ? v[0] : v; @@ -574,6 +693,7 @@ this.form.merchantType = Number(this.form.merchantType); this.form.isStudent = Number(this.form.isStudent); this.form.supportDineIn = Number(this.form.supportDineIn); + this.form.startingPrice = this.form.merchantType == 1 ? Number(this.form.startingPrice || 0) : 0; this.form.openKitchenVideoUrl = this.form.openKitchenVideoUrl || ""; if (!this.form.contactPhone && this.form.mobile) { this.form.contactPhone = this.form.mobile; @@ -723,6 +843,7 @@ this.form = Object.assign({}, registerData, { merchantType: registerData.merchantType == null ? 1 : Number(registerData.merchantType), isStudent: registerData.isStudent == null ? 0 : Number(registerData.isStudent), + startingPrice: registerData.startingPrice == null ? 0 : Number(registerData.startingPrice), openKitchenVideoUrl: registerData.openKitchenVideoUrl || "", supportDineIn: registerData.supportDineIn == null ? 0 : Number(registerData.supportDineIn), contactPhone: registerData.contactPhone || registerData.mobile || "", @@ -742,6 +863,12 @@ transferAddressId: data.transferAddressId || "", transferAddressName: data.transferAddressName || "", }); + this.printerForm = { + sn: this.form.printerSn || "", + key: "", + name: this.form.printerName || this.form.shopName || "", + }; + this.loadPrintConfig(); this.workerMobile = this.form.workerPhone || ""; this.$nextTick(() => { // 商圈 @@ -758,6 +885,7 @@ address: [],*/ merchantType: 1, isStudent: 0, + startingPrice: 0, openKitchenVideoUrl: "", supportDineIn: 0, qita: [], @@ -765,6 +893,11 @@ orderBkge: 0, supportTransferDelivery: 0, }; + this.printerForm = { + sn: "", + key: "", + name: "", + }; this.workerMobile = ""; this.$nextTick(() => { this.$refs.shopAreaTree.setData("", ""); diff --git a/src/views/app/shop/dictManage.vue b/src/views/app/shop/dictManage.vue index 93df18c..147263e 100644 --- a/src/views/app/shop/dictManage.vue +++ b/src/views/app/shop/dictManage.vue @@ -151,7 +151,7 @@
+ style="width: 300px" /> @@ -257,7 +257,7 @@ - + @@ -275,17 +275,17 @@ - + - + - + - + - +
新增拼团 @@ -618,6 +618,14 @@ key: "productName", minWidth: 60, }, + { + title: "原价", + key: "specPrice", + width: 100, + render: (h, params) => { + return h("span", this.getOriginalPrice(params.row.attributeListPrice)); + }, + }, { title: "排名", key: "orderFiled", @@ -877,7 +885,7 @@ { title: "属性值名称", key: "value", - minWidth: 160, + minWidth: 170, }, { title: "操作", @@ -924,6 +932,19 @@ } }, methods: { + getOriginalPrice(attributeListPrice) { + if (!attributeListPrice) return ""; + let list = attributeListPrice; + if (typeof attributeListPrice == "string") { + try { + list = JSON.parse(attributeListPrice); + } catch (e) { + return ""; + } + } + if (!Array.isArray(list) || list.length == 0) return ""; + return list[0].specPrice || ""; + }, isIntegerInRange(value, min, max) { if (value === "" || value === null || value === undefined) return false; const text = String(value); @@ -1585,12 +1606,13 @@ }, changePage(v) { this.searchForm.pageNum = v; - // this.getDataList(); + this.getDataList(); this.clearSelectAll(); }, changePageSize(v) { this.searchForm.pageSize = v; - // this.getDataList(); + this.searchForm.pageNum = 1; + this.getDataList(); }, getDataList() { this.loading = true; diff --git a/src/views/app/shop/mernage.vue b/src/views/app/shop/mernage.vue index 4f56820..15006c6 100644 --- a/src/views/app/shop/mernage.vue +++ b/src/views/app/shop/mernage.vue @@ -23,20 +23,18 @@ - - - - - - - - - - - - + +
+ + + + +
+ +
@@ -142,6 +140,13 @@ form: {}, takeData: {}, isAdd: true, + shopType: [], + yesorno: [], + status: [], + businessHourPeriods: [{ + begin: "", + end: "" + }], formValidate: { // 表单验证规则 username: [{ @@ -193,6 +198,9 @@ }, methods: { init(data) { + if (!data || !data.id) { + return; + } this.getRoleList(); getByShopId({ "shopId": data.id @@ -204,9 +212,14 @@ this.form.shopName = data.shopName this.form.regionId = data.regionId this.isAdd = true + this.businessHourPeriods = [{ + begin: "", + end: "" + }] } else { this.isAdd = false this.form = res.result + this.businessHourPeriods = this.parseBusinessHourPeriods(res.result) for (let i = 0; i < this.shopType.length; i++) { if (this.shopType[i].title == this.form.type) { this.form.type = this.shopType[i].value @@ -222,6 +235,81 @@ }); }, + formatTimeValue(value) { + if (!value) return ""; + if (typeof value === "string") { + return value.length >= 5 ? value.substring(0, 5) : value; + } + const date = value instanceof Date ? value : new Date(value); + if (isNaN(date.getTime())) return ""; + const h = date.getHours(); + const m = date.getMinutes(); + return (h < 10 ? "0" + h : h) + ":" + (m < 10 ? "0" + m : m); + }, + parseBusinessHourPeriods(takeaway) { + if (!takeaway) { + return [{ + begin: "", + end: "" + }]; + } + let list = takeaway.businessHourList; + if ((!list || !list.length) && takeaway.businessHours) { + try { + list = typeof takeaway.businessHours === "string" ? + JSON.parse(takeaway.businessHours) : + takeaway.businessHours; + } catch (e) { + list = []; + } + } + if (list && list.length) { + return list.map((item) => ({ + begin: this.formatTimeValue(item.begin), + end: this.formatTimeValue(item.end), + })); + } + if (takeaway.businessHourBegin || takeaway.businessHourEnd) { + return [{ + begin: this.formatTimeValue(takeaway.businessHourBegin), + end: this.formatTimeValue(takeaway.businessHourEnd), + }]; + } + return [{ + begin: "", + end: "" + }]; + }, + addBusinessHour() { + this.businessHourPeriods.push({ + begin: "", + end: "" + }); + }, + removeBusinessHour(index) { + if (this.businessHourPeriods.length <= 1) return; + this.businessHourPeriods.splice(index, 1); + }, + buildBusinessHoursPayload() { + const periods = []; + for (let i = 0; i < this.businessHourPeriods.length; i++) { + const begin = this.formatTimeValue(this.businessHourPeriods[i].begin); + const end = this.formatTimeValue(this.businessHourPeriods[i].end); + if (!begin || !end) { + this.$Message.warning("请完善第" + (i + 1) + "个营业时段"); + return null; + } + periods.push({ + begin, + end + }); + } + if (!periods.length) { + this.$Message.warning("请至少设置一个营业时段"); + return null; + } + return periods; + }, getRoleList() { getUsersByRoleName("ROLE_OWNER").then((res) => { if (res.success) { @@ -254,11 +342,17 @@ this.form.passStrength = strength; }, submit1() { + const periods = this.buildBusinessHoursPayload(); + if (!periods) return; + this.form.businessHours = JSON.stringify(periods); + this.form.businessHourBegin = periods[0].begin; + this.form.businessHourEnd = periods[0].end; + // form 提交不能绑定 List,只传 businessHours JSON + delete this.form.businessHourList; this.form.shoprank = 0 // 处理商品分类的名称 if (this.form.type != '' && this.form.type != null) { this.shopType.forEach(item => { - debugger; if (this.form.type == item.value) { this.form.type = item.value; } @@ -323,11 +417,22 @@ }, }, mounted() { - this.init(); + this.getRoleList(); }, }; \ No newline at end of file + + .business-hour-row { + display: flex; + align-items: center; + margin-bottom: 8px; + } + + .business-hour-sep { + margin: 0 10px; + color: #808695; + } + diff --git a/src/views/app/shop/shop.vue b/src/views/app/shop/shop.vue index ee4266f..7ce9c22 100644 --- a/src/views/app/shop/shop.vue +++ b/src/views/app/shop/shop.vue @@ -31,8 +31,8 @@ - - + @@ -170,6 +170,7 @@ searchForm: { id: "", keyWord: "", + shopName: "", shopOwnerId: "", shopMangerId: "", shopArea: "", @@ -201,9 +202,10 @@ fixed: "left", }, { - type: "index", - width: 60, - align: "center", + title: "排名", + key: "shoprank", + minWidth: 60, + sortable: true, fixed: "left", }, { @@ -470,6 +472,7 @@ this.$refs.searchForm.resetFields(); this.searchForm.pageNumber = 1; this.searchForm.pageSize = 10; + this.searchForm.keyWord = ""; this.searchForm.startDate = ""; this.searchForm.endDate = ""; if (this.$refs.dep) { diff --git a/src/views/app/shopArea/shopArea.vue b/src/views/app/shopArea/shopArea.vue index d890e9c..65ed190 100644 --- a/src/views/app/shopArea/shopArea.vue +++ b/src/views/app/shopArea/shopArea.vue @@ -172,22 +172,15 @@
社会区域首页展示待成团,并隐藏配送入口
- + -
用于小程序定位自动绑定校区;请用高德地图选点(GCJ-02)。未设置则不参与自动匹配
-
- - - +
用于小程序定位自动绑定区域;请用高德地图绘制多边形范围(GCJ-02)。未绘制则不参与自动匹配
@@ -319,22 +312,15 @@
社会区域首页展示待成团,并隐藏配送入口
- + -
用于小程序定位自动绑定校区;请用高德地图选点(GCJ-02)。未设置则不参与自动匹配
-
- - - +
用于小程序定位自动绑定区域;请用高德地图绘制多边形范围(GCJ-02)。未绘制则不参与自动匹配
180 || + lat < -90 || + lat > 90 + ) { + this.$Message.warning("匹配范围坐标不合法,请重新绘制"); + return null; + } + normalized.push([ + Number(lng.toFixed(6)), + Number(lat.toFixed(6)), + ]); + } + return JSON.stringify(normalized); }, - applyGeoPosition(target) { + applyGeoPolygon(target) { if (!this.isRootArea(target.parentId)) { this.clearGeoValue(target); return true; } - const position = (target.geoPosition || "").trim(); - if (!position) { - this.clearGeoValue(target); - return true; - } - const parts = position.split(","); - if (parts.length < 2) { - this.$Message.warning("区域中心坐标格式应为:经度, 纬度"); - return false; - } - const lng = Number(parts[0].trim()); - const lat = Number(parts[1].trim()); - if (!Number.isFinite(lng) || !Number.isFinite(lat)) { - this.$Message.warning("区域中心坐标不合法"); + const polygon = this.normalizePolygonValue(target.geoPolygon); + if (polygon === null) { return false; } - if (lng < -180 || lng > 180 || lat < -90 || lat > 90) { - this.$Message.warning("区域中心坐标超出范围"); - return false; - } - const radius = Number(target.radiusMeters); - if (!Number.isInteger(radius) || radius <= 0) { - this.$Message.warning("请设置有效的匹配半径(米)"); - return false; + if (!polygon) { + this.clearGeoValue(target); + return true; } - this.$set(target, "centerLng", lng); - this.$set(target, "centerLat", lat); - this.$set(target, "radiusMeters", radius); + this.$set(target, "geoPolygon", polygon); + this.$set(target, "centerLng", null); + this.$set(target, "centerLat", null); + this.$set(target, "radiusMeters", ""); + this.$set(target, "geoPosition", ""); return true; }, normalizeParentAgent(target) { @@ -775,20 +778,9 @@ export default { } }, syncGeoFromEntity(target) { - this.$set( - target, - "geoPosition", - this.buildGeoPosition(target.centerLng, target.centerLat) - ); - if ( - target.radiusMeters === "" || - target.radiusMeters === "null" || - target.radiusMeters == null - ) { - this.$set(target, "radiusMeters", ""); - } else { - this.$set(target, "radiusMeters", String(target.radiusMeters)); - } + this.$set(target, "geoPolygon", target.geoPolygon || ""); + this.$set(target, "geoPosition", ""); + this.$set(target, "radiusMeters", ""); }, init() { this.getParentList(); @@ -944,6 +936,7 @@ export default { centerLng: null, centerLat: null, radiusMeters: "", + geoPolygon: "", geoPosition: "", }; this.editTitle = ""; @@ -984,7 +977,7 @@ export default { this.$Message.warning("请先点击选择要修改的区域"); return; } - if (!this.applyGeoPosition(this.form)) { + if (!this.applyGeoPolygon(this.form)) { return; } this.submitLoading = true; @@ -996,6 +989,7 @@ export default { if (payload.centerLng == null) payload.centerLng = ""; if (payload.centerLat == null) payload.centerLat = ""; if (payload.radiusMeters == null) payload.radiusMeters = ""; + if (payload.geoPolygon == null) payload.geoPolygon = ""; editShopArea(payload).then((res) => { this.submitLoading = false; if (res.success) { @@ -1012,7 +1006,7 @@ export default { this.normalizeParentAgent(this.formAdd); this.$refs.formAdd.validate((valid) => { if (valid) { - if (!this.applyGeoPosition(this.formAdd)) { + if (!this.applyGeoPolygon(this.formAdd)) { return; } this.submitLoading = true; @@ -1022,6 +1016,7 @@ export default { if (payload.centerLng == null) payload.centerLng = ""; if (payload.centerLat == null) payload.centerLat = ""; if (payload.radiusMeters == null) payload.radiusMeters = ""; + if (payload.geoPolygon == null) payload.geoPolygon = ""; addShopArea(payload).then((res) => { this.submitLoading = false; if (res.success) { @@ -1088,6 +1083,7 @@ export default { centerLng: null, centerLat: null, radiusMeters: "", + geoPolygon: "", geoPosition: "", }; this.modalVisible = true; diff --git a/src/views/my-components/hiver/map.vue b/src/views/my-components/hiver/map.vue index f2e3027..6066d23 100644 --- a/src/views/my-components/hiver/map.vue +++ b/src/views/my-components/hiver/map.vue @@ -100,10 +100,14 @@
- 当前选点坐标:{{ currentValue || "未选择" }}{{ valueSummaryLabel }}:{{ valueSummaryText }} -
坐标为高德 GCJ-02;可搜索、定位或点击地图选点
+
{{ locateTip }}
+
+ + +
@@ -193,12 +197,21 @@ export default { type: Boolean, default: true, }, + mode: { + type: String, + default: "point", + }, }, data() { return { data: this.value, map: null, marker: null, + referenceMarker: null, + polygon: null, + polygonLine: null, + polygonEditor: null, + vertexMarkers: [], placeSearch: null, currentValue: "", showModal: false, @@ -210,6 +223,26 @@ export default { mapReady: false, }; }, + computed: { + isPolygonMode() { + return this.mode === "polygon"; + }, + valueSummaryLabel() { + return this.isPolygonMode ? "当前匹配范围" : "当前选点坐标"; + }, + valueSummaryText() { + if (!this.isPolygonMode) { + return this.currentValue || "未选择"; + } + const points = this.parsePolygonValue(this.currentValue); + return points.length ? `${points.length} 个点` : "未绘制"; + }, + locateTip() { + return this.isPolygonMode + ? "坐标为高德 GCJ-02;点击地图依次绘制多边形顶点,至少 3 个点;未绘制则不参与自动匹配" + : "坐标为高德 GCJ-02;可搜索、定位或点击地图选点"; + }, + }, methods: { getAmapKey() { return (typeof config !== "undefined" && config.amapKey) || ""; @@ -223,6 +256,12 @@ export default { if (decimal > 14) decimal = 14; return Number(lng).toFixed(decimal) + ", " + Number(lat).toFixed(decimal); }, + formatPoint(lng, lat) { + let decimal = this.decimal; + if (decimal < 0) decimal = 0; + if (decimal > 14) decimal = 14; + return [Number(Number(lng).toFixed(decimal)), Number(Number(lat).toFixed(decimal))]; + }, parsePosition(value) { if (!value || !String(value).trim()) return null; const parts = String(value).split(","); @@ -253,11 +292,15 @@ export default { }); this.map.on("click", (e) => { if (!e || !e.lnglat) return; - this.placeMarker(e.lnglat.getLng(), e.lnglat.getLat()); + if (this.isPolygonMode) { + this.addPolygonPoint(e.lnglat.getLng(), e.lnglat.getLat()); + } else { + this.placeMarker(e.lnglat.getLng(), e.lnglat.getLat()); + } }); return new Promise((resolve) => { AMap.plugin( - ["AMap.ToolBar", "AMap.Scale", "AMap.PlaceSearch", "AMap.Geolocation"], + ["AMap.ToolBar", "AMap.Scale", "AMap.PlaceSearch", "AMap.Geolocation", "AMap.PolygonEditor"], () => { this.map.addControl( new AMap.ToolBar({ @@ -307,10 +350,158 @@ export default { lat, ]); }, + placeReferenceMarker(lng, lat, zoom, title) { + if (!this.map || typeof window.AMap === "undefined") return; + if (this.referenceMarker) { + this.referenceMarker.setMap(null); + this.referenceMarker = null; + } + this.referenceMarker = new window.AMap.Marker({ + position: [lng, lat], + title: title || "当前位置", + icon: "https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png", + }); + this.referenceMarker.setMap(this.map); + this.map.setZoomAndCenter(zoom || Math.max(this.map.getZoom(), 17), [ + lng, + lat, + ]); + }, + parsePolygonValue(value) { + if (!value || !String(value).trim()) return []; + try { + const parsed = JSON.parse(value); + if (!Array.isArray(parsed)) return []; + return parsed + .map((item) => { + if (Array.isArray(item) && item.length >= 2) { + return this.formatPoint(item[0], item[1]); + } + if (item && typeof item === "object") { + return this.formatPoint(item.lng, item.lat); + } + return null; + }) + .filter((item) => { + if (!item) return false; + const lng = item[0]; + const lat = item[1]; + return Number.isFinite(lng) && Number.isFinite(lat) && lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90; + }); + } catch (e) { + return []; + } + }, + formatPolygonValue(points) { + return JSON.stringify((points || []).map((item) => this.formatPoint(item[0], item[1]))); + }, + getPolygonPathPoints() { + if (!this.polygon || typeof this.polygon.getPath !== "function") { + return this.parsePolygonValue(this.currentValue); + } + return this.polygon.getPath().map((item) => { + const lng = typeof item.getLng === "function" ? item.getLng() : item.lng; + const lat = typeof item.getLat === "function" ? item.getLat() : item.lat; + return this.formatPoint(lng, lat); + }); + }, + syncPolygonValue(points) { + this.currentValue = points && points.length ? this.formatPolygonValue(points) : ""; + this.$emit("on-click", this.currentValue); + }, + addPolygonPoint(lng, lat) { + const points = this.parsePolygonValue(this.currentValue); + points.push(this.formatPoint(lng, lat)); + this.syncPolygonValue(points); + this.renderPolygon(points, false); + }, + clearPolygonOverlays() { + if (this.polygonEditor) { + this.polygonEditor.close(); + this.polygonEditor = null; + } + if (this.polygon) { + this.polygon.setMap(null); + this.polygon = null; + } + if (this.polygonLine) { + this.polygonLine.setMap(null); + this.polygonLine = null; + } + this.vertexMarkers.forEach((item) => item.setMap(null)); + this.vertexMarkers = []; + }, + renderPolygon(points, fitView) { + if (!this.map || typeof window.AMap === "undefined") return; + this.clearPolygonOverlays(); + const path = (points || []).map((item) => item.slice(0, 2)); + path.forEach((item, index) => { + const marker = new window.AMap.Marker({ + position: item, + content: `
${index + 1}
`, + offset: new window.AMap.Pixel(-8, -8), + }); + marker.setMap(this.map); + this.vertexMarkers.push(marker); + }); + if (path.length >= 3) { + this.polygon = new window.AMap.Polygon({ + path, + strokeColor: "#2d8cf0", + strokeWeight: 2, + fillColor: "#2d8cf0", + fillOpacity: 0.18, + }); + this.polygon.setMap(this.map); + this.polygonEditor = new window.AMap.PolygonEditor(this.map, this.polygon); + ["adjust", "addnode", "removenode", "end"].forEach((eventName) => { + this.polygonEditor.on(eventName, () => { + this.syncPolygonValue(this.getPolygonPathPoints()); + }); + }); + this.polygonEditor.open(); + if (fitView) { + this.map.setFitView([this.polygon]); + } + } else if (path.length > 0) { + this.polygonLine = new window.AMap.Polyline({ + path, + strokeColor: "#2d8cf0", + strokeWeight: 2, + strokeStyle: "dashed", + }); + this.polygonLine.setMap(this.map); + if (fitView) { + this.map.setFitView([...this.vertexMarkers, this.polygonLine]); + } + } + }, + syncPolygonFromValue() { + const points = this.parsePolygonValue(this.data); + this.currentValue = points.length ? this.formatPolygonValue(points) : ""; + this.renderPolygon(points, true); + if (!points.length && this.map) { + this.map.setZoomAndCenter(this.zoom, this.center); + } + }, + undoPolygonPoint() { + const points = this.getPolygonPathPoints(); + points.pop(); + this.syncPolygonValue(points); + this.renderPolygon(points, false); + }, + clearPolygonValue() { + this.syncPolygonValue([]); + this.clearPolygonOverlays(); + }, syncMarkerFromValue() { + if (this.isPolygonMode) { + this.syncPolygonFromValue(); + return; + } const pos = this.parsePosition(this.data); if (pos) { - this.currentValue = this.data; + this.currentValue = this.data; this.placeMarker(pos.lng, pos.lat, 16); } else { this.currentValue = ""; @@ -355,7 +546,11 @@ export default { if (!item || !item.location) return; const lng = item.location.lng; const lat = item.location.lat; - this.placeMarker(lng, lat, 17); + if (this.isPolygonMode) { + this.placeReferenceMarker(lng, lat, 17, item.name || "搜索位置"); + } else { + this.placeMarker(lng, lat, 17); + } this.searchResults = []; this.keyword = item.name || this.keyword; }, @@ -374,7 +569,11 @@ export default { .then((pos) => { loading(); this.locating = false; - this.placeMarker(pos.lng, pos.lat, 17); + if (this.isPolygonMode) { + this.placeReferenceMarker(pos.lng, pos.lat, 17, "当前位置"); + } else { + this.placeMarker(pos.lng, pos.lat, 17); + } if (pos.accuracy && pos.accuracy > 1000) { this.$Message.warning( `定位成功,精度约 ${Math.round(pos.accuracy)} 米,请核对后确认` @@ -440,6 +639,14 @@ export default { } }, handelSubmit() { + if (this.isPolygonMode) { + const points = this.getPolygonPathPoints(); + if (points.length > 0 && points.length < 3) { + this.$Message.warning("匹配范围至少需要 3 个点"); + return; + } + this.currentValue = points.length ? this.formatPolygonValue(points) : ""; + } this.data = this.currentValue; this.$emit("input", this.data); this.$emit("on-change", this.data); @@ -451,7 +658,12 @@ export default { this.marker.setMap(null); this.marker = null; } + if (this.referenceMarker) { + this.referenceMarker.setMap(null); + this.referenceMarker = null; + } if (this.map) { + this.clearPolygonOverlays(); this.map.destroy(); this.map = null; } @@ -526,6 +738,22 @@ export default { font-size: 12px; line-height: 1.4; } +.polygon-actions { + margin-top: 8px; + button + button { + margin-left: 8px; + } +} +.amap-vertex-marker { + width: 16px; + height: 16px; + line-height: 16px; + text-align: center; + color: #fff; + background: #2d8cf0; + border-radius: 50%; + font-size: 12px; +} .modal-fullscreen { position: absolute; right: 42px;