mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-13 04:18:55 +08:00
refactor: 四端完成 Options API → script setup + vuex → pinia 迁移
- buyer(95) / seller(169) / im(53) / manager(280) 共 597 个组件全部从 Options API 转换为 <script setup> - 四端 vuex 全部移除,状态管理统一迁移至 pinia 4(stores/ 目录) - util.js 等基础设施去除 vm 首参依赖;全局属性($Message/$filters 等)在 script 内改为具名导入 - 被外部 ref 访问的公共组件均已补齐 defineExpose(清单见各端 MIGRATION-VUE3.md) - 各端 eslint 启用 vue/component-api-style 防止新代码回退 Options API - 四端 yarn build 全部通过;迁移约定与注意事项已更新至各端 MIGRATION-VUE3.md
This commit is contained in:
@@ -4,33 +4,36 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import Cookies from "js-cookie";
|
||||
import util from "@/libs/util";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { getCategoryTree } from "@/api/goods.js";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
mounted() {
|
||||
const loggedIn =
|
||||
this.getStore("accessToken") || Cookies.get("userInfoSeller");
|
||||
if (loggedIn) {
|
||||
util.bootstrapDynamicRoutesFromCache();
|
||||
util.initRouter(this);
|
||||
this.$store.commit("setOpenedList");
|
||||
this.$store.commit("initCachepage");
|
||||
if (!localStorage.getItem("category")) {
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
defineOptions({ name: "App" });
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
onMounted(() => {
|
||||
const loggedIn = getStore("accessToken") || Cookies.get("userInfoSeller");
|
||||
if (loggedIn) {
|
||||
util.bootstrapDynamicRoutesFromCache();
|
||||
util.initRouter();
|
||||
appStore.setOpenedList();
|
||||
appStore.initCachepage();
|
||||
if (!localStorage.getItem("category")) {
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -29,99 +29,105 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "setPassword",
|
||||
props: {
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
defineOptions({ name: "setPassword" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符",
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change"],
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.modelValue ?? this.value ?? "",
|
||||
tipStyle: "password-tip-none",
|
||||
strengthValue: 0,
|
||||
progressStatus: "",
|
||||
strength: "无",
|
||||
grade: 0,
|
||||
};
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
methods: {
|
||||
checkStrengthValue(v) {
|
||||
let grade = 0;
|
||||
if (/\d/.test(v)) grade++;
|
||||
if (/[a-z]/.test(v)) grade++;
|
||||
if (/[A-Z]/.test(v)) grade++;
|
||||
if (/\W/.test(v)) grade++;
|
||||
if (v.length >= 10) grade++;
|
||||
this.grade = grade;
|
||||
return grade;
|
||||
},
|
||||
strengthChange() {
|
||||
if (!this.currentValue) {
|
||||
this.tipStyle = "password-tip-none";
|
||||
this.strength = "无";
|
||||
this.strengthValue = 0;
|
||||
return;
|
||||
}
|
||||
const grade = this.checkStrengthValue(this.currentValue);
|
||||
if (grade <= 1) {
|
||||
this.progressStatus = "exception";
|
||||
this.tipStyle = "password-tip-weak";
|
||||
this.strength = "弱";
|
||||
this.strengthValue = 33;
|
||||
} else if (grade >= 2 && grade <= 4) {
|
||||
this.progressStatus = "";
|
||||
this.tipStyle = "password-tip-middle";
|
||||
this.strength = "中";
|
||||
this.strengthValue = 66;
|
||||
} else {
|
||||
this.progressStatus = "success";
|
||||
this.tipStyle = "password-tip-strong";
|
||||
this.strength = "强";
|
||||
this.strengthValue = 100;
|
||||
}
|
||||
},
|
||||
handleChange() {
|
||||
this.strengthChange();
|
||||
this.$emit("update:modelValue", this.currentValue);
|
||||
this.$emit("input", this.currentValue);
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) return;
|
||||
this.currentValue = value ?? "";
|
||||
this.strengthChange();
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
|
||||
const currentValue = ref(props.modelValue ?? props.value ?? "");
|
||||
const tipStyle = ref("password-tip-none");
|
||||
const strengthValue = ref(0);
|
||||
const progressStatus = ref("");
|
||||
const strength = ref("无");
|
||||
const grade = ref(0);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
|
||||
function checkStrengthValue(v) {
|
||||
// 局部变量原名 grade 与顶层 ref 同名,改名避免遮蔽
|
||||
let g = 0;
|
||||
if (/\d/.test(v)) g++;
|
||||
if (/[a-z]/.test(v)) g++;
|
||||
if (/[A-Z]/.test(v)) g++;
|
||||
if (/\W/.test(v)) g++;
|
||||
if (v.length >= 10) g++;
|
||||
grade.value = g;
|
||||
return g;
|
||||
}
|
||||
|
||||
function strengthChange() {
|
||||
if (!currentValue.value) {
|
||||
tipStyle.value = "password-tip-none";
|
||||
strength.value = "无";
|
||||
strengthValue.value = 0;
|
||||
return;
|
||||
}
|
||||
const grade = checkStrengthValue(currentValue.value);
|
||||
if (grade <= 1) {
|
||||
progressStatus.value = "exception";
|
||||
tipStyle.value = "password-tip-weak";
|
||||
strength.value = "弱";
|
||||
strengthValue.value = 33;
|
||||
} else if (grade >= 2 && grade <= 4) {
|
||||
progressStatus.value = "";
|
||||
tipStyle.value = "password-tip-middle";
|
||||
strength.value = "中";
|
||||
strengthValue.value = 66;
|
||||
} else {
|
||||
progressStatus.value = "success";
|
||||
tipStyle.value = "password-tip-strong";
|
||||
strength.value = "强";
|
||||
strengthValue.value = 100;
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange() {
|
||||
strengthChange();
|
||||
emit("update:modelValue", currentValue.value);
|
||||
emit("input", currentValue.value);
|
||||
emit("on-change", currentValue.value, grade.value, strength.value);
|
||||
}
|
||||
|
||||
function setCurrentValue(value) {
|
||||
if (value === currentValue.value) return;
|
||||
currentValue.value = value ?? "";
|
||||
strengthChange();
|
||||
emit("on-change", currentValue.value, grade.value, strength.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -5,54 +5,56 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, getCurrentInstance } from "vue";
|
||||
import { unitPrice } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "priceColorScheme",
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
validator(val) {
|
||||
return (
|
||||
val === null ||
|
||||
val === undefined ||
|
||||
typeof val === "number" ||
|
||||
typeof val === "string"
|
||||
);
|
||||
},
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: "¥",
|
||||
},
|
||||
dot: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
defineOptions({ name: "priceColorScheme" });
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
default: 0,
|
||||
validator(val) {
|
||||
return (
|
||||
val === null ||
|
||||
val === undefined ||
|
||||
typeof val === "number" ||
|
||||
typeof val === "string"
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
displayText() {
|
||||
const val = this.value;
|
||||
if (val === null || val === undefined || val === "" || val === "null") {
|
||||
return `${this.unit || "¥"}0.00`;
|
||||
}
|
||||
return unitPrice(val, this.unit);
|
||||
},
|
||||
priceStyle() {
|
||||
const resolvedColor = this.color || this.$mainColor || "";
|
||||
return resolvedColor
|
||||
? { color: resolvedColor, ...this.customStyle }
|
||||
: { ...this.customStyle };
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: "¥",
|
||||
},
|
||||
};
|
||||
dot: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
// $mainColor 为 main.js 注册的全局属性
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const displayText = computed(() => {
|
||||
const val = props.value;
|
||||
if (val === null || val === undefined || val === "" || val === "null") {
|
||||
return `${props.unit || "¥"}0.00`;
|
||||
}
|
||||
return unitPrice(val, props.unit);
|
||||
});
|
||||
const priceStyle = computed(() => {
|
||||
const resolvedColor = props.color || proxy.$mainColor || "";
|
||||
return resolvedColor
|
||||
? { color: resolvedColor, ...props.customStyle }
|
||||
: { ...props.customStyle };
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,7 @@ import lazyLoading from "./lazyLoading.js";
|
||||
import { router } from "@/router/index";
|
||||
import Cookies from "js-cookie";
|
||||
import config from "@/config/index";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
let util = {};
|
||||
|
||||
@@ -199,13 +200,14 @@ util.handleTitle = function (vm, item) {
|
||||
return item.title;
|
||||
};
|
||||
|
||||
util.setCurrentPath = function (vm, name) {
|
||||
util.setCurrentPath = function (name) {
|
||||
const appStore = useAppStore();
|
||||
let title = "";
|
||||
let isOtherRouter = false;
|
||||
vm.$store.state.app.routers.forEach((item) => {
|
||||
appStore.routers.forEach((item) => {
|
||||
if (item.children.length == 1) {
|
||||
if (item.children[0].name == name) {
|
||||
title = util.handleTitle(vm, item);
|
||||
title = util.handleTitle(null, item);
|
||||
if (item.name == "otherRouter") {
|
||||
isOtherRouter = true;
|
||||
}
|
||||
@@ -213,7 +215,7 @@ util.setCurrentPath = function (vm, name) {
|
||||
} else {
|
||||
item.children.forEach((child) => {
|
||||
if (child.name == name) {
|
||||
title = util.handleTitle(vm, child);
|
||||
title = util.handleTitle(null, child);
|
||||
if (item.name == "otherRouter") {
|
||||
isOtherRouter = true;
|
||||
}
|
||||
@@ -226,8 +228,8 @@ util.setCurrentPath = function (vm, name) {
|
||||
currentPathArr = [
|
||||
{
|
||||
title: util.handleTitle(
|
||||
vm,
|
||||
util.getRouterObjByName(vm.$store.state.app.routers, "home_index")
|
||||
null,
|
||||
util.getRouterObjByName(appStore.routers, "home_index")
|
||||
),
|
||||
path: "",
|
||||
name: "home_index",
|
||||
@@ -237,8 +239,8 @@ util.setCurrentPath = function (vm, name) {
|
||||
currentPathArr = [
|
||||
{
|
||||
title: util.handleTitle(
|
||||
vm,
|
||||
util.getRouterObjByName(vm.$store.state.app.routers, "home_index")
|
||||
null,
|
||||
util.getRouterObjByName(appStore.routers, "home_index")
|
||||
),
|
||||
path: "/home",
|
||||
name: "home_index",
|
||||
@@ -250,7 +252,7 @@ util.setCurrentPath = function (vm, name) {
|
||||
},
|
||||
];
|
||||
} else {
|
||||
let currentPathObj = vm.$store.state.app.routers.filter((item) => {
|
||||
let currentPathObj = appStore.routers.filter((item) => {
|
||||
if (item.children.length <= 1) {
|
||||
return item.children[0].name == name;
|
||||
}
|
||||
@@ -311,22 +313,20 @@ util.setCurrentPath = function (vm, name) {
|
||||
];
|
||||
}
|
||||
}
|
||||
vm.$store.commit("setCurrentPath", currentPathArr);
|
||||
appStore.setCurrentPath(currentPathArr);
|
||||
|
||||
return currentPathArr;
|
||||
};
|
||||
|
||||
util.openNewPage = function (vm, name, argu, query) {
|
||||
if (!vm.$store) {
|
||||
return;
|
||||
}
|
||||
let storeOpenedList = vm.$store.state.app.storeOpenedList;
|
||||
util.openNewPage = function (name, argu, query) {
|
||||
const appStore = useAppStore();
|
||||
let storeOpenedList = appStore.storeOpenedList;
|
||||
let openedPageLen = storeOpenedList.length;
|
||||
let i = 0;
|
||||
let tagHasOpened = false;
|
||||
while (i < openedPageLen) {
|
||||
if (name == storeOpenedList[i].name) {
|
||||
vm.$store.commit("storeOpenedList", {
|
||||
appStore.updateOpenedList({
|
||||
index: i,
|
||||
argu: argu,
|
||||
query: query,
|
||||
@@ -337,7 +337,7 @@ util.openNewPage = function (vm, name, argu, query) {
|
||||
i++;
|
||||
}
|
||||
if (!tagHasOpened) {
|
||||
let tag = vm.$store.state.app.tagsList.filter((item) => {
|
||||
let tag = appStore.tagsList.filter((item) => {
|
||||
if (item.children) {
|
||||
return name == item.children[0].name;
|
||||
}
|
||||
@@ -348,10 +348,10 @@ util.openNewPage = function (vm, name, argu, query) {
|
||||
tag = tag.children ? tag.children[0] : tag;
|
||||
if (argu) tag.argu = argu;
|
||||
if (query) tag.query = query;
|
||||
vm.$store.commit("increateTag", tag);
|
||||
appStore.increateTag(tag);
|
||||
}
|
||||
}
|
||||
vm.$store.commit("setCurrentPageName", name);
|
||||
appStore.setCurrentPageName(name);
|
||||
};
|
||||
|
||||
util.toDefaultPage = function (routers, name, route, next) {
|
||||
@@ -378,13 +378,14 @@ util.toDefaultPage = function (routers, name, route, next) {
|
||||
}
|
||||
};
|
||||
|
||||
util.initRouter = function (vm) {
|
||||
util.initRouter = function () {
|
||||
const appStore = useAppStore();
|
||||
const constRoutes = [];
|
||||
let userInfo = Cookies.get("userInfoSeller");
|
||||
if (!userInfo) {
|
||||
return;
|
||||
}
|
||||
if (!vm.$store.state.app.added) {
|
||||
if (!appStore.added) {
|
||||
getCurrentPermissionList().then((res) => {
|
||||
if (!res.success) return false;
|
||||
let menuData = res.result;
|
||||
@@ -405,24 +406,23 @@ util.initRouter = function (vm) {
|
||||
}
|
||||
util.initAllMenuData(constRoutes, menuData);
|
||||
util.registerDynamicRoutes(menuData);
|
||||
vm.$store.commit(
|
||||
"updateAppRouter",
|
||||
appStore.updateAppRouter(
|
||||
constRoutes.filter((item) => item.children && item.children.length > 0)
|
||||
);
|
||||
util.initMenuData(vm, menuData);
|
||||
util.initMenuData(menuData);
|
||||
window.localStorage.setItem("menuData", JSON.stringify(menuData));
|
||||
vm.$store.commit("setAdded", true);
|
||||
appStore.setAdded(true);
|
||||
});
|
||||
} else {
|
||||
let data = window.localStorage.getItem("menuData");
|
||||
if (!data) {
|
||||
vm.$store.commit("setAdded", false);
|
||||
util.initRouter(vm);
|
||||
appStore.setAdded(false);
|
||||
util.initRouter();
|
||||
return;
|
||||
}
|
||||
let menuData = JSON.parse(data);
|
||||
util.registerDynamicRoutes(menuData);
|
||||
util.initMenuData(vm, menuData);
|
||||
util.initMenuData(menuData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -438,7 +438,8 @@ util.initAllMenuData = function (constRoutes, data) {
|
||||
util.initRouterNode(constRoutes, allMenuData);
|
||||
};
|
||||
|
||||
util.initMenuData = function (vm, data) {
|
||||
util.initMenuData = function (data) {
|
||||
const appStore = useAppStore();
|
||||
const menuRoutes = [];
|
||||
let menuData = data;
|
||||
let navList = [HOME_NAV_ITEM];
|
||||
@@ -448,23 +449,23 @@ util.initMenuData = function (vm, data) {
|
||||
if (navList.length < 1) {
|
||||
return;
|
||||
}
|
||||
vm.$store.commit("setNavList", navList);
|
||||
appStore.setNavList(navList);
|
||||
let currNav = window.localStorage.getItem("currNav");
|
||||
if (currNav) {
|
||||
for (let item of navList) {
|
||||
if (item.name == currNav) {
|
||||
vm.$store.commit("setCurrNavTitle", item.title);
|
||||
appStore.setCurrNavTitle(item.title);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currNav = HOME_NAV_NAME;
|
||||
vm.$store.commit("setCurrNavTitle", HOME_NAV_ITEM.title);
|
||||
appStore.setCurrNavTitle(HOME_NAV_ITEM.title);
|
||||
}
|
||||
vm.$store.commit("setCurrNav", currNav);
|
||||
appStore.setCurrNav(currNav);
|
||||
|
||||
if (currNav === HOME_NAV_NAME) {
|
||||
vm.$store.commit("updateMenulist", []);
|
||||
appStore.updateMenulist([]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -475,20 +476,19 @@ util.initMenuData = function (vm, data) {
|
||||
}
|
||||
}
|
||||
util.initRouterNode(menuRoutes, menuData);
|
||||
vm.$store.commit(
|
||||
"updateMenulist",
|
||||
appStore.updateMenulist(
|
||||
menuRoutes.filter((item) => item.children.length > 0)
|
||||
);
|
||||
|
||||
let tagsList = [];
|
||||
vm.$store.state.app.routers.map((item) => {
|
||||
appStore.routers.map((item) => {
|
||||
if (item.children.length <= 1) {
|
||||
tagsList.push(item.children[0]);
|
||||
} else {
|
||||
tagsList.push(...item.children);
|
||||
}
|
||||
});
|
||||
vm.$store.commit("setTagsList", tagsList);
|
||||
appStore.setTagsList(tagsList);
|
||||
};
|
||||
|
||||
util.initRouterNode = function (routers, data) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import "./styles/theme.less";
|
||||
import App from "./App.vue";
|
||||
import { router } from "./router/index";
|
||||
import store from "./store";
|
||||
import { setupElementPlus } from "@/plugins/element";
|
||||
import { setupLegacyMessage } from "@/utils/message";
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
@@ -50,7 +50,7 @@ app.use(VueLazyload, {
|
||||
});
|
||||
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
app.use(createPinia());
|
||||
|
||||
app.component("liliDialog", liliDialog);
|
||||
app.component("priceColorScheme", PriceColorScheme);
|
||||
|
||||
@@ -3,7 +3,6 @@ import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
import Util from "../libs/util";
|
||||
import Cookies from "js-cookie";
|
||||
import store from "@/store";
|
||||
import { routers } from "./router";
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
@@ -44,7 +43,7 @@ router.beforeEach((to, from, next) => {
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
Util.openNewPage({ $store: store }, to.name, to.params, to.query);
|
||||
Util.openNewPage(to.name, to.params, to.query);
|
||||
NProgress.done();
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { createStore } from "vuex";
|
||||
import app from "./modules/app";
|
||||
import setting from "./modules/setting";
|
||||
import user from "./modules/user";
|
||||
import dict from "./modules/dict";
|
||||
|
||||
const store = createStore({
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
modules: {
|
||||
app,
|
||||
user,
|
||||
setting,
|
||||
dict,
|
||||
},
|
||||
});
|
||||
|
||||
export default store;
|
||||
@@ -1,162 +0,0 @@
|
||||
import { otherRouter } from "@/router/router";
|
||||
import Util from "@/libs/util";
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
shipTemplates: "",
|
||||
regions: [],
|
||||
styleStore: "",
|
||||
loading: false,
|
||||
added: false,
|
||||
navList: [],
|
||||
currNav: "",
|
||||
currNavTitle: "",
|
||||
cachePage: [],
|
||||
lang: "",
|
||||
isFullScreen: false,
|
||||
openedSubmenuArr: [],
|
||||
menuTheme: "dark",
|
||||
themeColor: "",
|
||||
storeOpenedList: [
|
||||
{
|
||||
title: "首页",
|
||||
path: "",
|
||||
name: "home_index",
|
||||
},
|
||||
],
|
||||
currentPageName: "",
|
||||
currentPath: [
|
||||
{
|
||||
title: "首页",
|
||||
path: "",
|
||||
name: "home_index",
|
||||
},
|
||||
],
|
||||
menuList: [],
|
||||
routers: [otherRouter],
|
||||
tagsList: [...otherRouter.children],
|
||||
messageCount: 0,
|
||||
dontCache: ["test", "test"],
|
||||
refMenu: "",
|
||||
},
|
||||
mutations: {
|
||||
childrenMenu(state, v) {
|
||||
state.refMenu = v;
|
||||
},
|
||||
updateAppRouter(state, routes) {
|
||||
state.routers.push(...routes);
|
||||
},
|
||||
updateDefaultRouter() {},
|
||||
setLoading(state, v) {
|
||||
state.loading = v;
|
||||
},
|
||||
setAdded(state, v) {
|
||||
state.added = v;
|
||||
},
|
||||
setNavList(state, list) {
|
||||
state.navList = list;
|
||||
},
|
||||
setCurrNav(state, v) {
|
||||
state.currNav = v;
|
||||
},
|
||||
setCurrNavTitle(state, v) {
|
||||
state.currNavTitle = v;
|
||||
},
|
||||
setTagsList(state, list) {
|
||||
state.tagsList.push(...list);
|
||||
},
|
||||
updateMenulist(state, routes) {
|
||||
state.menuList = routes;
|
||||
},
|
||||
addOpenSubmenu(state, name) {
|
||||
if (name.length && state.openedSubmenuArr.indexOf(name) === -1) {
|
||||
state.openedSubmenuArr.push(name);
|
||||
}
|
||||
},
|
||||
closePage(state, name) {
|
||||
state.cachePage.forEach((item, index) => {
|
||||
if (item == name) {
|
||||
state.cachePage.splice(index, 1);
|
||||
}
|
||||
});
|
||||
localStorage.cachePage = JSON.stringify(state.cachePage);
|
||||
},
|
||||
initCachepage(state) {
|
||||
if (localStorage.cachePage) {
|
||||
state.cachePage = JSON.parse(localStorage.cachePage);
|
||||
}
|
||||
},
|
||||
removeTag(state, name) {
|
||||
state.storeOpenedList.map((item, index) => {
|
||||
if (item.name == name) {
|
||||
state.storeOpenedList.splice(index, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
storeOpenedList(state, get) {
|
||||
let openedPage = state.storeOpenedList[get.index];
|
||||
if (get.argu) openedPage.argu = get.argu;
|
||||
if (get.query) openedPage.query = get.query;
|
||||
state.storeOpenedList.splice(get.index, 1, openedPage);
|
||||
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
|
||||
},
|
||||
clearAllTags(state) {
|
||||
state.storeOpenedList.splice(1);
|
||||
state.cachePage.length = 0;
|
||||
localStorage.cachePage = "";
|
||||
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
|
||||
},
|
||||
clearOtherTags(state, vm) {
|
||||
let currentName = vm.$route.name;
|
||||
let currentIndex = 0;
|
||||
state.storeOpenedList.forEach((item, index) => {
|
||||
if (item.name == currentName) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
if (currentIndex == 0) {
|
||||
state.storeOpenedList.splice(1);
|
||||
} else {
|
||||
state.storeOpenedList.splice(currentIndex + 1);
|
||||
state.storeOpenedList.splice(1, currentIndex - 1);
|
||||
}
|
||||
state.cachePage = state.cachePage.filter((item) => item == currentName);
|
||||
localStorage.cachePage = JSON.stringify(state.cachePage);
|
||||
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
|
||||
},
|
||||
setOpenedList(state) {
|
||||
state.storeOpenedList = localStorage.storeOpenedList
|
||||
? JSON.parse(localStorage.storeOpenedList)
|
||||
: [otherRouter.children[0]];
|
||||
},
|
||||
setCurrentPath(state, pathArr) {
|
||||
state.currentPath = pathArr;
|
||||
},
|
||||
setCurrentPageName(state, name) {
|
||||
state.currentPageName = name;
|
||||
},
|
||||
setAvatarPath(state, path) {
|
||||
localStorage.avatorImgPath = path;
|
||||
},
|
||||
switchLang(state, lang) {
|
||||
state.lang = lang;
|
||||
localStorage.lang = lang;
|
||||
},
|
||||
clearOpenedSubmenu(state) {
|
||||
state.openedSubmenuArr.length = 0;
|
||||
},
|
||||
setMessageCount(state, count) {
|
||||
state.messageCount = count;
|
||||
},
|
||||
increateTag(state, tagObj) {
|
||||
if (!Util.oneOf(tagObj.name, state.dontCache)) {
|
||||
state.cachePage.push(tagObj.name);
|
||||
localStorage.cachePage = JSON.stringify(state.cachePage);
|
||||
}
|
||||
state.storeOpenedList.push(tagObj);
|
||||
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default app;
|
||||
@@ -1,26 +0,0 @@
|
||||
const dict = {
|
||||
state: {
|
||||
// 经常需要读取的数据字典
|
||||
sex: [],
|
||||
messageType: [],
|
||||
priority: [],
|
||||
leaveType: []
|
||||
},
|
||||
mutations: {
|
||||
// 设置值的改变方法
|
||||
setSex(state, list) {
|
||||
state.sex = list;
|
||||
},
|
||||
setMessageType(state, list) {
|
||||
state.messageType = list;
|
||||
},
|
||||
setPriority(state, list) {
|
||||
state.priority = list;
|
||||
},
|
||||
setLeaveType(state, list) {
|
||||
state.leaveType = list;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default dict;
|
||||
@@ -1,17 +0,0 @@
|
||||
import config from "@/config/index";
|
||||
|
||||
const { setting } = config;
|
||||
|
||||
const localSetting = window.localStorage.getItem('setting')
|
||||
const settingData = {
|
||||
state: {
|
||||
setting:localSetting ? JSON.parse(localSetting) : setting,
|
||||
},
|
||||
mutations: {
|
||||
updateSetting(state, data) {
|
||||
state.setting = data;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default settingData;
|
||||
@@ -1,27 +0,0 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { removeStore } from '@/libs/storage';
|
||||
|
||||
// 退出登录时需要清除的会话/店铺数据
|
||||
// 保留 uuid(设备标识)、setting(主题)、currNav、category(缓存)、saveLogin(记住登录状态)等无关键
|
||||
const LOGOUT_KEYS = [
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
'menuData',
|
||||
'seller_expiration_time',
|
||||
'sellersiteName',
|
||||
'sellerlogoImg',
|
||||
'sellerIconImg'
|
||||
];
|
||||
|
||||
const user = {
|
||||
state: {},
|
||||
mutations: {
|
||||
logout () {
|
||||
Cookies.remove('userInfoSeller');
|
||||
// 精确清除会话相关键,不再 localStorage.clear() 误删无关数据
|
||||
LOGOUT_KEYS.forEach(key => removeStore(key));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
||||
162
seller/src/stores/app.js
Normal file
162
seller/src/stores/app.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { otherRouter } from "@/router/router";
|
||||
|
||||
// 原 vuex app 模块的 pinia 版本;mutation 全部转为同名 action
|
||||
// 例外:storeOpenedList mutation 与 state 同名,改名 updateOpenedList;
|
||||
// clearOtherTags 原接收组件实例 vm,改为直接接收当前路由名 currentName
|
||||
export const useAppStore = defineStore("app", {
|
||||
state: () => ({
|
||||
shipTemplates: "",
|
||||
regions: [],
|
||||
styleStore: "",
|
||||
loading: false,
|
||||
added: false,
|
||||
navList: [],
|
||||
currNav: "",
|
||||
currNavTitle: "",
|
||||
cachePage: [],
|
||||
lang: "",
|
||||
isFullScreen: false,
|
||||
openedSubmenuArr: [],
|
||||
menuTheme: "dark",
|
||||
themeColor: "",
|
||||
storeOpenedList: [
|
||||
{
|
||||
title: "首页",
|
||||
path: "",
|
||||
name: "home_index",
|
||||
},
|
||||
],
|
||||
currentPageName: "",
|
||||
currentPath: [
|
||||
{
|
||||
title: "首页",
|
||||
path: "",
|
||||
name: "home_index",
|
||||
},
|
||||
],
|
||||
menuList: [],
|
||||
routers: [otherRouter],
|
||||
tagsList: [...otherRouter.children],
|
||||
messageCount: 0,
|
||||
dontCache: ["test", "test"],
|
||||
refMenu: "",
|
||||
}),
|
||||
actions: {
|
||||
childrenMenu(v) {
|
||||
this.refMenu = v;
|
||||
},
|
||||
updateAppRouter(routes) {
|
||||
this.routers.push(...routes);
|
||||
},
|
||||
updateDefaultRouter() {},
|
||||
setLoading(v) {
|
||||
this.loading = v;
|
||||
},
|
||||
setAdded(v) {
|
||||
this.added = v;
|
||||
},
|
||||
setNavList(list) {
|
||||
this.navList = list;
|
||||
},
|
||||
setCurrNav(v) {
|
||||
this.currNav = v;
|
||||
},
|
||||
setCurrNavTitle(v) {
|
||||
this.currNavTitle = v;
|
||||
},
|
||||
setTagsList(list) {
|
||||
this.tagsList.push(...list);
|
||||
},
|
||||
updateMenulist(routes) {
|
||||
this.menuList = routes;
|
||||
},
|
||||
addOpenSubmenu(name) {
|
||||
if (name.length && this.openedSubmenuArr.indexOf(name) === -1) {
|
||||
this.openedSubmenuArr.push(name);
|
||||
}
|
||||
},
|
||||
closePage(name) {
|
||||
this.cachePage.forEach((item, index) => {
|
||||
if (item == name) {
|
||||
this.cachePage.splice(index, 1);
|
||||
}
|
||||
});
|
||||
localStorage.cachePage = JSON.stringify(this.cachePage);
|
||||
},
|
||||
initCachepage() {
|
||||
if (localStorage.cachePage) {
|
||||
this.cachePage = JSON.parse(localStorage.cachePage);
|
||||
}
|
||||
},
|
||||
removeTag(name) {
|
||||
this.storeOpenedList.map((item, index) => {
|
||||
if (item.name == name) {
|
||||
this.storeOpenedList.splice(index, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateOpenedList(get) {
|
||||
let openedPage = this.storeOpenedList[get.index];
|
||||
if (get.argu) openedPage.argu = get.argu;
|
||||
if (get.query) openedPage.query = get.query;
|
||||
this.storeOpenedList.splice(get.index, 1, openedPage);
|
||||
localStorage.storeOpenedList = JSON.stringify(this.storeOpenedList);
|
||||
},
|
||||
clearAllTags() {
|
||||
this.storeOpenedList.splice(1);
|
||||
this.cachePage.length = 0;
|
||||
localStorage.cachePage = "";
|
||||
localStorage.storeOpenedList = JSON.stringify(this.storeOpenedList);
|
||||
},
|
||||
clearOtherTags(currentName) {
|
||||
let currentIndex = 0;
|
||||
this.storeOpenedList.forEach((item, index) => {
|
||||
if (item.name == currentName) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
if (currentIndex == 0) {
|
||||
this.storeOpenedList.splice(1);
|
||||
} else {
|
||||
this.storeOpenedList.splice(currentIndex + 1);
|
||||
this.storeOpenedList.splice(1, currentIndex - 1);
|
||||
}
|
||||
this.cachePage = this.cachePage.filter((item) => item == currentName);
|
||||
localStorage.cachePage = JSON.stringify(this.cachePage);
|
||||
localStorage.storeOpenedList = JSON.stringify(this.storeOpenedList);
|
||||
},
|
||||
setOpenedList() {
|
||||
this.storeOpenedList = localStorage.storeOpenedList
|
||||
? JSON.parse(localStorage.storeOpenedList)
|
||||
: [otherRouter.children[0]];
|
||||
},
|
||||
setCurrentPath(pathArr) {
|
||||
this.currentPath = pathArr;
|
||||
},
|
||||
setCurrentPageName(name) {
|
||||
this.currentPageName = name;
|
||||
},
|
||||
setAvatarPath(path) {
|
||||
localStorage.avatorImgPath = path;
|
||||
},
|
||||
switchLang(lang) {
|
||||
this.lang = lang;
|
||||
localStorage.lang = lang;
|
||||
},
|
||||
clearOpenedSubmenu() {
|
||||
this.openedSubmenuArr.length = 0;
|
||||
},
|
||||
setMessageCount(count) {
|
||||
this.messageCount = count;
|
||||
},
|
||||
increateTag(tagObj) {
|
||||
if (this.dontCache.indexOf(tagObj.name) < 0) {
|
||||
this.cachePage.push(tagObj.name);
|
||||
localStorage.cachePage = JSON.stringify(this.cachePage);
|
||||
}
|
||||
this.storeOpenedList.push(tagObj);
|
||||
localStorage.storeOpenedList = JSON.stringify(this.storeOpenedList);
|
||||
},
|
||||
},
|
||||
});
|
||||
26
seller/src/stores/dict.js
Normal file
26
seller/src/stores/dict.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useDictStore = defineStore("dict", {
|
||||
state: () => ({
|
||||
// 经常需要读取的数据字典
|
||||
sex: [],
|
||||
messageType: [],
|
||||
priority: [],
|
||||
leaveType: [],
|
||||
}),
|
||||
actions: {
|
||||
// 设置值的改变方法
|
||||
setSex(list) {
|
||||
this.sex = list;
|
||||
},
|
||||
setMessageType(list) {
|
||||
this.messageType = list;
|
||||
},
|
||||
setPriority(list) {
|
||||
this.priority = list;
|
||||
},
|
||||
setLeaveType(list) {
|
||||
this.leaveType = list;
|
||||
},
|
||||
},
|
||||
});
|
||||
17
seller/src/stores/setting.js
Normal file
17
seller/src/stores/setting.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineStore } from "pinia";
|
||||
import config from "@/config/index";
|
||||
|
||||
const { setting } = config;
|
||||
|
||||
const localSetting = window.localStorage.getItem("setting");
|
||||
|
||||
export const useSettingStore = defineStore("setting", {
|
||||
state: () => ({
|
||||
setting: localSetting ? JSON.parse(localSetting) : setting,
|
||||
}),
|
||||
actions: {
|
||||
updateSetting(data) {
|
||||
this.setting = data;
|
||||
},
|
||||
},
|
||||
});
|
||||
25
seller/src/stores/user.js
Normal file
25
seller/src/stores/user.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineStore } from "pinia";
|
||||
import Cookies from "js-cookie";
|
||||
import { removeStore } from "@/libs/storage";
|
||||
|
||||
// 退出登录时需要清除的会话/店铺数据
|
||||
// 保留 uuid(设备标识)、setting(主题)、currNav、category(缓存)、saveLogin(记住登录状态)等无关键
|
||||
const LOGOUT_KEYS = [
|
||||
"accessToken",
|
||||
"refreshToken",
|
||||
"menuData",
|
||||
"seller_expiration_time",
|
||||
"sellersiteName",
|
||||
"sellerlogoImg",
|
||||
"sellerIconImg",
|
||||
];
|
||||
|
||||
export const useUserStore = defineStore("user", {
|
||||
actions: {
|
||||
logout() {
|
||||
Cookies.remove("userInfoSeller");
|
||||
// 精确清除会话相关键,不再 localStorage.clear() 误删无关数据
|
||||
LOGOUT_KEYS.forEach((key) => removeStore(key));
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="forget-password" @click="$refs.verify.show = false">
|
||||
<div class="forget-password" @click="hideVerify">
|
||||
<div style="height: 50px"></div>
|
||||
<div class="logo-box">
|
||||
<img :src="$store.state.logoImg" width="150" @click="$router.push('/')" />
|
||||
<img :src="logoImg" width="150" @click="$router.push('/')" />
|
||||
<div>修改密码</div>
|
||||
</div>
|
||||
<div class="login-container">
|
||||
<el-form
|
||||
ref="formFirst"
|
||||
ref="formFirstRef"
|
||||
:model="formFirst"
|
||||
:rules="ruleInline"
|
||||
style="width: 300px"
|
||||
@@ -46,7 +46,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form
|
||||
ref="form"
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="ruleInline"
|
||||
style="width: 300px"
|
||||
@@ -91,7 +91,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<verify
|
||||
ref="verify"
|
||||
ref="verifyRef"
|
||||
class="verify-con"
|
||||
:verifyType="verifyType"
|
||||
@change="verifyChange"
|
||||
@@ -115,157 +115,171 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Iphone, Message, Lock } from "@element-plus/icons-vue";
|
||||
import { Message as $Message } from "@/utils/message";
|
||||
import { md5 } from "@/utils/md5.js";
|
||||
import * as RegExp from "@/utils/regular.js";
|
||||
import * as apiLogin from "@/api/index.js";
|
||||
import { sendSms } from "@/api/common.js";
|
||||
import config from "@/config/index";
|
||||
import verify from "@/views/my-components/verify";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
export default {
|
||||
name: "ForgetPassword",
|
||||
components: { verify, Iphone, Message, Lock },
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
loading: false,
|
||||
loading1: false,
|
||||
formFirst: {
|
||||
mobile: "",
|
||||
code: "",
|
||||
},
|
||||
form: {
|
||||
password: "",
|
||||
oncePasd: "",
|
||||
},
|
||||
year: new Date().getFullYear(),
|
||||
step: 0,
|
||||
ruleInline: {
|
||||
mobile: [
|
||||
{ required: true, message: "请输入手机号码" },
|
||||
{
|
||||
pattern: RegExp.mobile,
|
||||
trigger: "blur",
|
||||
message: "请输入正确的手机号",
|
||||
},
|
||||
],
|
||||
code: [{ required: true, message: "请输入手机验证码" }],
|
||||
password: [
|
||||
{ required: true, message: "密码不能为空" },
|
||||
{ pattern: RegExp.password, message: "密码不能少于6位" },
|
||||
],
|
||||
},
|
||||
verifyStatus: false,
|
||||
verifyType: "FIND_USER",
|
||||
codeMsg: "发送验证码",
|
||||
interval: "",
|
||||
time: 60,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
next() {
|
||||
this.$refs.formFirst.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
const data = JSON.parse(JSON.stringify(this.formFirst));
|
||||
apiLogin
|
||||
.validateCode(data)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.step = 1;
|
||||
} else {
|
||||
this.$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
const params = JSON.parse(JSON.stringify(this.form));
|
||||
if (params.password !== params.oncePasd) {
|
||||
this.$Message.warning("两次输入密码不一致");
|
||||
return;
|
||||
}
|
||||
params.mobile = this.formFirst.mobile;
|
||||
params.password = this.md5(params.password);
|
||||
delete params.oncePasd;
|
||||
this.loading1 = true;
|
||||
defineOptions({ name: "ForgetPassword" });
|
||||
|
||||
apiLogin
|
||||
.forgetAndModify(params)
|
||||
.then((res) => {
|
||||
this.loading1 = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("修改密码成功");
|
||||
this.$router.push("login");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading1 = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const logoImg = computed(() => appStore.logoImg);
|
||||
|
||||
const loading = ref(false);
|
||||
const loading1 = ref(false);
|
||||
const formFirst = reactive({
|
||||
mobile: "",
|
||||
code: "",
|
||||
});
|
||||
const form = reactive({
|
||||
password: "",
|
||||
oncePasd: "",
|
||||
});
|
||||
const year = new Date().getFullYear();
|
||||
const step = ref(0);
|
||||
const ruleInline = {
|
||||
mobile: [
|
||||
{ required: true, message: "请输入手机号码" },
|
||||
{
|
||||
pattern: RegExp.mobile,
|
||||
trigger: "blur",
|
||||
message: "请输入正确的手机号",
|
||||
},
|
||||
sendCode() {
|
||||
if (this.time === 60) {
|
||||
if (this.formFirst.mobile === "") {
|
||||
this.$Message.warning("请先填写手机号");
|
||||
return;
|
||||
}
|
||||
if (!this.verifyStatus) {
|
||||
this.$Message.warning("请先完成安全验证");
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
mobile: this.formFirst.mobile,
|
||||
verificationEnums: "FIND_USER",
|
||||
};
|
||||
sendSms(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("验证码发送成功");
|
||||
const that = this;
|
||||
this.interval = setInterval(() => {
|
||||
that.time--;
|
||||
if (that.time === 0) {
|
||||
that.time = 60;
|
||||
that.codeMsg = "重新发送";
|
||||
that.verifyStatus = false;
|
||||
clearInterval(that.interval);
|
||||
} else {
|
||||
that.codeMsg = that.time;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
this.$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
verifyChange(con) {
|
||||
if (!con.status) return;
|
||||
this.$refs.verify.show = false;
|
||||
this.verifyStatus = true;
|
||||
},
|
||||
verifyBtnClick() {
|
||||
if (!this.verifyStatus) {
|
||||
this.$refs.verify.init();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.querySelector(".forget-password").style.height = window.innerHeight + "px";
|
||||
this.$refs.formFirst.resetFields();
|
||||
},
|
||||
],
|
||||
code: [{ required: true, message: "请输入手机验证码" }],
|
||||
password: [
|
||||
{ required: true, message: "密码不能为空" },
|
||||
{ pattern: RegExp.password, message: "密码不能少于6位" },
|
||||
],
|
||||
};
|
||||
const verifyStatus = ref(false);
|
||||
const verifyType = ref("FIND_USER");
|
||||
const codeMsg = ref("发送验证码");
|
||||
const interval = ref("");
|
||||
const time = ref(60);
|
||||
|
||||
const formFirstRef = ref(null);
|
||||
const formRef = ref(null);
|
||||
const verifyRef = ref(null);
|
||||
|
||||
function hideVerify() {
|
||||
verifyRef.value.show = false;
|
||||
}
|
||||
|
||||
function next() {
|
||||
formFirstRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
const data = JSON.parse(JSON.stringify(formFirst));
|
||||
apiLogin
|
||||
.validateCode(data)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
step.value = 1;
|
||||
} else {
|
||||
$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const params = JSON.parse(JSON.stringify(form));
|
||||
if (params.password !== params.oncePasd) {
|
||||
$Message.warning("两次输入密码不一致");
|
||||
return;
|
||||
}
|
||||
params.mobile = formFirst.mobile;
|
||||
params.password = md5(params.password);
|
||||
delete params.oncePasd;
|
||||
loading1.value = true;
|
||||
|
||||
apiLogin
|
||||
.forgetAndModify(params)
|
||||
.then((res) => {
|
||||
loading1.value = false;
|
||||
if (res.success) {
|
||||
$Message.success("修改密码成功");
|
||||
router.push("login");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading1.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendCode() {
|
||||
if (time.value === 60) {
|
||||
if (formFirst.mobile === "") {
|
||||
$Message.warning("请先填写手机号");
|
||||
return;
|
||||
}
|
||||
if (!verifyStatus.value) {
|
||||
$Message.warning("请先完成安全验证");
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
mobile: formFirst.mobile,
|
||||
verificationEnums: "FIND_USER",
|
||||
};
|
||||
sendSms(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
$Message.success("验证码发送成功");
|
||||
interval.value = setInterval(() => {
|
||||
time.value--;
|
||||
if (time.value === 0) {
|
||||
time.value = 60;
|
||||
codeMsg.value = "重新发送";
|
||||
verifyStatus.value = false;
|
||||
clearInterval(interval.value);
|
||||
} else {
|
||||
codeMsg.value = time.value;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
function verifyChange(con) {
|
||||
if (!con.status) return;
|
||||
hideVerify();
|
||||
verifyStatus.value = true;
|
||||
}
|
||||
|
||||
function verifyBtnClick() {
|
||||
if (!verifyStatus.value) {
|
||||
verifyRef.value.init();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.querySelector(".forget-password").style.height = window.innerHeight + "px";
|
||||
formFirstRef.value.resetFields();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -75,11 +75,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<circleLoading class="loading-position" v-show="loading" />
|
||||
<configDrawer ref="config" />
|
||||
<configDrawer ref="configRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ChatDotRound, Setting, UserFilled } from "@element-plus/icons-vue";
|
||||
import shrinkableMenu from "./main-components/shrinkable-menu/shrinkable-menu.vue";
|
||||
import tagsPageOpened from "./main-components/tags-page-opened.vue";
|
||||
@@ -92,147 +94,134 @@ import { logout } from "@/api/index";
|
||||
import { getIMDetail } from "@/api/common";
|
||||
import { userMsg } from "@/api/index";
|
||||
import config from "@/config/index.js";
|
||||
import { Message } from "@/utils/message";
|
||||
import { getStore, setStore } from "@/libs/storage";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useSettingStore } from "@/stores/setting";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChatDotRound,
|
||||
Setting,
|
||||
UserFilled,
|
||||
shrinkableMenu,
|
||||
tagsPageOpened,
|
||||
circleLoading,
|
||||
configDrawer,
|
||||
storeQrcode,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
sliceNum: 5,
|
||||
userInfo: {},
|
||||
storeSideLogo: "",
|
||||
IMLink: "",
|
||||
load: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
setting() {
|
||||
return this.$store.state.setting.setting;
|
||||
},
|
||||
loading() {
|
||||
return this.$store.state.app.loading;
|
||||
},
|
||||
pageTagsList() {
|
||||
return this.$store.state.app.storeOpenedList;
|
||||
},
|
||||
showSubMenu() {
|
||||
return this.$store.state.app.menuList.length > 0;
|
||||
},
|
||||
lang() {
|
||||
return this.$store.state.app.lang;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClickSetting() {
|
||||
this.$refs.config.open();
|
||||
},
|
||||
async im() {
|
||||
const accessToken = this.getStore("accessToken");
|
||||
this.load = true;
|
||||
await this.getIMDetailMethods();
|
||||
const userInfo = await userMsg();
|
||||
this.load = false;
|
||||
if (userInfo.success && this.IMLink) {
|
||||
window.open(`${this.IMLink}?token=` + accessToken);
|
||||
} else {
|
||||
this.$Message.error("请登录后再联系客服");
|
||||
}
|
||||
},
|
||||
async getIMDetailMethods() {
|
||||
const res = await getIMDetail();
|
||||
if (res.success) {
|
||||
this.IMLink = res.result;
|
||||
}
|
||||
},
|
||||
init() {
|
||||
const pathArr = util.setCurrentPath(this, this.$route.name);
|
||||
if (pathArr.length >= 2) {
|
||||
this.$store.commit("addOpenSubmenu", pathArr[1].name);
|
||||
}
|
||||
this.storeSideLogo = localStorage.getItem("sellerlogoImg");
|
||||
window.document.title = localStorage.getItem("sellersiteName");
|
||||
const link =
|
||||
document.querySelector("link[rel*='icon']") ||
|
||||
document.createElement("link");
|
||||
link.type = "image/x-icon";
|
||||
link.href = localStorage.getItem("sellerIconImg");
|
||||
link.rel = "shortcut icon";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
const userInfo = JSON.parse(Cookies.get("userInfoSeller"));
|
||||
this.userInfo = userInfo;
|
||||
this.checkTag(this.$route.name);
|
||||
const configRef = ref(null);
|
||||
const sliceNum = ref(5);
|
||||
const userInfo = ref({});
|
||||
const storeSideLogo = ref("");
|
||||
const IMLink = ref("");
|
||||
const load = ref(false);
|
||||
|
||||
const currWidth = document.body.clientWidth;
|
||||
if (currWidth <= 1200) {
|
||||
this.sliceNum = 2;
|
||||
}
|
||||
},
|
||||
handleClickUserDropdown(name) {
|
||||
if (name === "changePass") {
|
||||
util.openNewPage(this, "change_pass");
|
||||
this.$router.push({ name: "change_pass" });
|
||||
} else if (name === "loginOut") {
|
||||
logout().then(() => {
|
||||
Cookies.set("accessToken", "");
|
||||
this.$store.commit("logout", this);
|
||||
this.$store.commit("clearOpenedSubmenu");
|
||||
this.setStore("accessToken", "");
|
||||
this.setStore("refreshToken", "");
|
||||
this.$router.push({ path: "/login" });
|
||||
});
|
||||
}
|
||||
},
|
||||
checkTag(name) {
|
||||
const openpageHasTag = this.pageTagsList.some((item) => item.name === name);
|
||||
if (!openpageHasTag) {
|
||||
util.openNewPage(
|
||||
this,
|
||||
name,
|
||||
this.$route.params || {},
|
||||
this.$route.query || {}
|
||||
);
|
||||
}
|
||||
},
|
||||
resize() {
|
||||
const currWidth = document.body.clientWidth;
|
||||
const count = currWidth / 300;
|
||||
this.sliceNum = count > 6 ? 6 : count;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to) {
|
||||
this.$store.commit("setCurrentPageName", to.name);
|
||||
const pathArr = util.setCurrentPath(this, to.name);
|
||||
if (pathArr.length > 2) {
|
||||
this.$store.commit("addOpenSubmenu", pathArr[1].name);
|
||||
}
|
||||
this.checkTag(to.name);
|
||||
localStorage.currentPageName = to.name;
|
||||
},
|
||||
lang() {
|
||||
util.setCurrentPath(this, this.$route.name);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$store.commit("setOpenedList");
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
this.resize();
|
||||
window.addEventListener("resize", this.resize);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.resize);
|
||||
},
|
||||
};
|
||||
const setting = computed(() => settingStore.setting);
|
||||
const loading = computed(() => appStore.loading);
|
||||
const pageTagsList = computed(() => appStore.storeOpenedList);
|
||||
const showSubMenu = computed(() => appStore.menuList.length > 0);
|
||||
const lang = computed(() => appStore.lang);
|
||||
|
||||
function handleClickSetting() {
|
||||
configRef.value.open();
|
||||
}
|
||||
|
||||
async function im() {
|
||||
const accessToken = getStore("accessToken");
|
||||
load.value = true;
|
||||
await getIMDetailMethods();
|
||||
const userInfo = await userMsg();
|
||||
load.value = false;
|
||||
if (userInfo.success && IMLink.value) {
|
||||
window.open(`${IMLink.value}?token=` + accessToken);
|
||||
} else {
|
||||
Message.error("请登录后再联系客服");
|
||||
}
|
||||
}
|
||||
|
||||
async function getIMDetailMethods() {
|
||||
const res = await getIMDetail();
|
||||
if (res.success) {
|
||||
IMLink.value = res.result;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
const pathArr = util.setCurrentPath(route.name);
|
||||
if (pathArr.length >= 2) {
|
||||
appStore.addOpenSubmenu(pathArr[1].name);
|
||||
}
|
||||
storeSideLogo.value = localStorage.getItem("sellerlogoImg");
|
||||
window.document.title = localStorage.getItem("sellersiteName");
|
||||
const link =
|
||||
document.querySelector("link[rel*='icon']") ||
|
||||
document.createElement("link");
|
||||
link.type = "image/x-icon";
|
||||
link.href = localStorage.getItem("sellerIconImg");
|
||||
link.rel = "shortcut icon";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
|
||||
userInfo.value = JSON.parse(Cookies.get("userInfoSeller"));
|
||||
checkTag(route.name);
|
||||
|
||||
const currWidth = document.body.clientWidth;
|
||||
if (currWidth <= 1200) {
|
||||
sliceNum.value = 2;
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickUserDropdown(name) {
|
||||
if (name === "changePass") {
|
||||
util.openNewPage("change_pass");
|
||||
router.push({ name: "change_pass" });
|
||||
} else if (name === "loginOut") {
|
||||
logout().then(() => {
|
||||
Cookies.set("accessToken", "");
|
||||
userStore.logout();
|
||||
appStore.clearOpenedSubmenu();
|
||||
setStore("accessToken", "");
|
||||
setStore("refreshToken", "");
|
||||
router.push({ path: "/login" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkTag(name) {
|
||||
const openpageHasTag = pageTagsList.value.some((item) => item.name === name);
|
||||
if (!openpageHasTag) {
|
||||
util.openNewPage(name, route.params || {}, route.query || {});
|
||||
}
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const currWidth = document.body.clientWidth;
|
||||
const count = currWidth / 300;
|
||||
sliceNum.value = count > 6 ? 6 : count;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(name) => {
|
||||
appStore.setCurrentPageName(name);
|
||||
const pathArr = util.setCurrentPath(name);
|
||||
if (pathArr.length > 2) {
|
||||
appStore.addOpenSubmenu(pathArr[1].name);
|
||||
}
|
||||
checkTag(name);
|
||||
localStorage.currentPageName = name;
|
||||
}
|
||||
);
|
||||
|
||||
watch(lang, () => {
|
||||
util.setCurrentPath(route.name);
|
||||
});
|
||||
|
||||
appStore.setOpenedList();
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -88,7 +88,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
defineOptions({ name: "SystemFormFieldPreview" });
|
||||
|
||||
const props = defineProps({
|
||||
field: { type: Object, required: true },
|
||||
});
|
||||
|
||||
function pad(n) {
|
||||
return String(n).padStart(2, "0");
|
||||
}
|
||||
@@ -103,88 +111,80 @@ function nowTimeText() {
|
||||
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "SystemFormFieldPreview",
|
||||
props: {
|
||||
field: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
isInlineOptions() {
|
||||
return [
|
||||
"CHECKBOX",
|
||||
"RADIO",
|
||||
"CITY",
|
||||
"DATE",
|
||||
"DATE_RANGE",
|
||||
"SELECT",
|
||||
"TEXT",
|
||||
"TIME",
|
||||
"TIME_RANGE",
|
||||
].includes(this.field.field_type);
|
||||
},
|
||||
imageSlotCount() {
|
||||
const n = Number(this.field.max_upload_count);
|
||||
return Math.min(10, Math.max(1, Number.isFinite(n) ? n : 1));
|
||||
},
|
||||
optionListStacked() {
|
||||
// 一行放不下两个选项时改为纵向堆叠
|
||||
const labels = (this.field.options || []).map((item) => item.label || "");
|
||||
if (labels.length < 2) return false;
|
||||
const itemWidth = (text) => 24 + Array.from(text).length * 14;
|
||||
const available = 230;
|
||||
for (let i = 0; i < labels.length; i += 2) {
|
||||
const next = labels[i + 1];
|
||||
if (next == null) break;
|
||||
if (itemWidth(labels[i]) + 12 + itemWidth(next) > available) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
showDefault() {
|
||||
return this.field.default_visibility === "SHOW";
|
||||
},
|
||||
singleDefault() {
|
||||
if (!this.showDefault) return "";
|
||||
if (this.field.field_type === "DATE") {
|
||||
return this.field.default_date_mode === "SPECIFIED_DATE"
|
||||
? this.field.default_date
|
||||
: todayText();
|
||||
}
|
||||
return this.field.default_time_mode === "SPECIFIED_TIME"
|
||||
? this.field.default_time
|
||||
: nowTimeText();
|
||||
},
|
||||
dateRangeText() {
|
||||
if (this.rangeStart && this.rangeEnd) {
|
||||
return `${this.rangeStart} - ${this.rangeEnd}`;
|
||||
}
|
||||
return this.field.hint_text || "请选择";
|
||||
},
|
||||
rangeStart() {
|
||||
if (!this.showDefault) return "";
|
||||
if (this.field.field_type === "DATE_RANGE") {
|
||||
return this.field.default_date_mode === "SPECIFIED_DATE"
|
||||
? this.field.default_date_start
|
||||
: todayText();
|
||||
}
|
||||
return this.field.default_time_mode === "SPECIFIED_TIME"
|
||||
? this.field.default_time_start
|
||||
: nowTimeText();
|
||||
},
|
||||
rangeEnd() {
|
||||
if (!this.showDefault) return "";
|
||||
if (this.field.field_type === "DATE_RANGE") {
|
||||
return this.field.default_date_mode === "SPECIFIED_DATE"
|
||||
? this.field.default_date_end
|
||||
: todayText();
|
||||
}
|
||||
return this.field.default_time_mode === "SPECIFIED_TIME"
|
||||
? this.field.default_time_end
|
||||
: nowTimeText();
|
||||
},
|
||||
},
|
||||
};
|
||||
const isInlineOptions = computed(() => {
|
||||
return [
|
||||
"CHECKBOX",
|
||||
"RADIO",
|
||||
"CITY",
|
||||
"DATE",
|
||||
"DATE_RANGE",
|
||||
"SELECT",
|
||||
"TEXT",
|
||||
"TIME",
|
||||
"TIME_RANGE",
|
||||
].includes(props.field.field_type);
|
||||
});
|
||||
const imageSlotCount = computed(() => {
|
||||
const n = Number(props.field.max_upload_count);
|
||||
return Math.min(10, Math.max(1, Number.isFinite(n) ? n : 1));
|
||||
});
|
||||
const optionListStacked = computed(() => {
|
||||
// 一行放不下两个选项时改为纵向堆叠
|
||||
const labels = (props.field.options || []).map((item) => item.label || "");
|
||||
if (labels.length < 2) return false;
|
||||
const itemWidth = (text) => 24 + Array.from(text).length * 14;
|
||||
const available = 230;
|
||||
for (let i = 0; i < labels.length; i += 2) {
|
||||
const next = labels[i + 1];
|
||||
if (next == null) break;
|
||||
if (itemWidth(labels[i]) + 12 + itemWidth(next) > available) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const showDefault = computed(() => {
|
||||
return props.field.default_visibility === "SHOW";
|
||||
});
|
||||
const singleDefault = computed(() => {
|
||||
if (!showDefault.value) return "";
|
||||
if (props.field.field_type === "DATE") {
|
||||
return props.field.default_date_mode === "SPECIFIED_DATE"
|
||||
? props.field.default_date
|
||||
: todayText();
|
||||
}
|
||||
return props.field.default_time_mode === "SPECIFIED_TIME"
|
||||
? props.field.default_time
|
||||
: nowTimeText();
|
||||
});
|
||||
const dateRangeText = computed(() => {
|
||||
if (rangeStart.value && rangeEnd.value) {
|
||||
return `${rangeStart.value} - ${rangeEnd.value}`;
|
||||
}
|
||||
return props.field.hint_text || "请选择";
|
||||
});
|
||||
const rangeStart = computed(() => {
|
||||
if (!showDefault.value) return "";
|
||||
if (props.field.field_type === "DATE_RANGE") {
|
||||
return props.field.default_date_mode === "SPECIFIED_DATE"
|
||||
? props.field.default_date_start
|
||||
: todayText();
|
||||
}
|
||||
return props.field.default_time_mode === "SPECIFIED_TIME"
|
||||
? props.field.default_time_start
|
||||
: nowTimeText();
|
||||
});
|
||||
const rangeEnd = computed(() => {
|
||||
if (!showDefault.value) return "";
|
||||
if (props.field.field_type === "DATE_RANGE") {
|
||||
return props.field.default_date_mode === "SPECIFIED_DATE"
|
||||
? props.field.default_date_end
|
||||
: todayText();
|
||||
}
|
||||
return props.field.default_time_mode === "SPECIFIED_TIME"
|
||||
? props.field.default_time_end
|
||||
: nowTimeText();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -23,52 +23,50 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getInStoreSettings, saveInStoreSettings } from "@/api/appointment";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "appointmentInStoreSettings",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
form: {
|
||||
userBooking: true,
|
||||
workOrder: true,
|
||||
endService: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
getInStoreSettings()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.form = { ...this.form, ...res.result };
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleSave() {
|
||||
this.saving = true;
|
||||
saveInStoreSettings(this.form)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success("保存成功");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "appointmentInStoreSettings" });
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const form = ref({
|
||||
userBooking: true,
|
||||
workOrder: true,
|
||||
endService: true,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
getInStoreSettings()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
form.value = { ...form.value, ...res.result };
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
saving.value = true;
|
||||
saveInStoreSettings(form.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("保存成功");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
saving.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -50,59 +50,57 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getOnSiteSettings, saveOnSiteSettings } from "@/api/appointment";
|
||||
import { Message } from "@/utils/message";
|
||||
import ServiceAreaFence from "./components/ServiceAreaFence.vue";
|
||||
|
||||
export default {
|
||||
name: "appointmentOnSiteSettings",
|
||||
components: { ServiceAreaFence },
|
||||
data() {
|
||||
return {
|
||||
activeTab: "flow",
|
||||
loading: false,
|
||||
saving: false,
|
||||
form: {
|
||||
userBooking: true,
|
||||
acceptOrder: true,
|
||||
onSiteCheckin: true,
|
||||
serviceEvidence: false,
|
||||
endService: true,
|
||||
serviceAreaEnabled: false,
|
||||
serviceAreaGeo: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
getOnSiteSettings()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.form = { ...this.form, ...res.result };
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleSave() {
|
||||
this.saving = true;
|
||||
saveOnSiteSettings(this.form)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success("保存成功");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "appointmentOnSiteSettings" });
|
||||
|
||||
const activeTab = ref("flow");
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const form = ref({
|
||||
userBooking: true,
|
||||
acceptOrder: true,
|
||||
onSiteCheckin: true,
|
||||
serviceEvidence: false,
|
||||
endService: true,
|
||||
serviceAreaEnabled: false,
|
||||
serviceAreaGeo: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
getOnSiteSettings()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
form.value = { ...form.value, ...res.result };
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
saving.value = true;
|
||||
saveOnSiteSettings(form.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
// 原代码为 this.$message(小写),全局只注册了 $Message,按作者意图修正
|
||||
Message.success("保存成功");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
saving.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -35,81 +35,74 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { getServiceCalendarMonth, getServiceCalendarDay } from "@/api/appointment";
|
||||
|
||||
defineOptions({ name: "appointmentServiceCalendar" });
|
||||
|
||||
function formatMonth(date) {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||
return `${y}-${m}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "appointmentServiceCalendar",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
currentMonth: formatMonth(new Date()),
|
||||
monthDays: [],
|
||||
dayVisible: false,
|
||||
dayLoading: false,
|
||||
activeDate: "",
|
||||
dayStatus: "ALL",
|
||||
dayRows: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
monthLabel() {
|
||||
return this.currentMonth;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadMonth();
|
||||
},
|
||||
methods: {
|
||||
goToday() {
|
||||
this.currentMonth = formatMonth(new Date());
|
||||
this.loadMonth();
|
||||
},
|
||||
changeMonth(delta) {
|
||||
const [y, m] = this.currentMonth.split("-").map(Number);
|
||||
const date = new Date(y, m - 1 + delta, 1);
|
||||
this.currentMonth = formatMonth(date);
|
||||
this.loadMonth();
|
||||
},
|
||||
loadMonth() {
|
||||
this.loading = true;
|
||||
getServiceCalendarMonth(this.currentMonth)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.monthDays = res.result?.days || res.result || [];
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
openDay(date) {
|
||||
this.activeDate = date;
|
||||
this.dayStatus = "ALL";
|
||||
this.dayVisible = true;
|
||||
this.loadDay();
|
||||
},
|
||||
loadDay() {
|
||||
if (!this.activeDate) return;
|
||||
this.dayLoading = true;
|
||||
getServiceCalendarDay({ date: this.activeDate, status: this.dayStatus })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.dayRows = res.result?.records || res.result || [];
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.dayLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const loading = ref(false);
|
||||
const currentMonth = ref(formatMonth(new Date()));
|
||||
const monthDays = ref([]);
|
||||
const dayVisible = ref(false);
|
||||
const dayLoading = ref(false);
|
||||
const activeDate = ref("");
|
||||
const dayStatus = ref("ALL");
|
||||
const dayRows = ref([]);
|
||||
|
||||
const monthLabel = computed(() => currentMonth.value);
|
||||
|
||||
onMounted(() => {
|
||||
loadMonth();
|
||||
});
|
||||
|
||||
function goToday() {
|
||||
currentMonth.value = formatMonth(new Date());
|
||||
loadMonth();
|
||||
}
|
||||
function changeMonth(delta) {
|
||||
const [y, m] = currentMonth.value.split("-").map(Number);
|
||||
const date = new Date(y, m - 1 + delta, 1);
|
||||
currentMonth.value = formatMonth(date);
|
||||
loadMonth();
|
||||
}
|
||||
function loadMonth() {
|
||||
loading.value = true;
|
||||
getServiceCalendarMonth(currentMonth.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
monthDays.value = res.result?.days || res.result || [];
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function openDay(date) {
|
||||
activeDate.value = date;
|
||||
dayStatus.value = "ALL";
|
||||
dayVisible.value = true;
|
||||
loadDay();
|
||||
}
|
||||
function loadDay() {
|
||||
if (!activeDate.value) return;
|
||||
dayLoading.value = true;
|
||||
getServiceCalendarDay({ date: activeDate.value, status: dayStatus.value })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
dayRows.value = res.result?.records || res.result || [];
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
dayLoading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px" @keyup.enter="handleSearch">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="80px" @keyup.enter="handleSearch">
|
||||
<el-form-item label="员工姓名" prop="staffName">
|
||||
<el-input v-model="searchForm.staffName" clearable placeholder="模糊搜索" style="width: 220px" />
|
||||
</el-form-item>
|
||||
@@ -90,8 +90,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { Message } from "@/utils/message";
|
||||
import { getUserListData } from "@/api/index";
|
||||
import {
|
||||
getServiceStaffPage,
|
||||
@@ -101,163 +103,172 @@ import {
|
||||
deleteServiceStaff,
|
||||
} from "@/api/appointment";
|
||||
|
||||
export default {
|
||||
name: "appointmentServiceStaffList",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
staffName: "",
|
||||
status: "",
|
||||
},
|
||||
createVisible: false,
|
||||
createLoading: false,
|
||||
clerkLoading: false,
|
||||
clerkOptions: [],
|
||||
createForm: {
|
||||
id: "",
|
||||
clerkUserId: "",
|
||||
staffName: "",
|
||||
contactPhone: "",
|
||||
sortOrder: 0,
|
||||
status: "ENABLED",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.loadData();
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
getServiceStaffPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result?.records || [];
|
||||
this.total = res.result?.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
emptyForm() {
|
||||
return {
|
||||
id: "",
|
||||
clerkUserId: "",
|
||||
staffName: "",
|
||||
contactPhone: "",
|
||||
sortOrder: 0,
|
||||
status: "ENABLED",
|
||||
};
|
||||
},
|
||||
openCreate() {
|
||||
this.createForm = this.emptyForm();
|
||||
this.createVisible = true;
|
||||
this.loadClerks();
|
||||
},
|
||||
openEdit(row) {
|
||||
this.createForm = {
|
||||
id: row.id,
|
||||
clerkUserId: row.clerkUserId || "",
|
||||
staffName: row.staffName || "",
|
||||
contactPhone: row.contactPhone || "",
|
||||
sortOrder: row.sortOrder == null ? 0 : row.sortOrder,
|
||||
status: row.status || "ENABLED",
|
||||
};
|
||||
this.createVisible = true;
|
||||
this.loadClerks();
|
||||
},
|
||||
loadClerks() {
|
||||
this.clerkLoading = true;
|
||||
getUserListData({ pageNumber: 1, pageSize: 200 })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
const records = res.result?.records || [];
|
||||
const usedIds = new Set(
|
||||
(this.data || [])
|
||||
.filter((staff) => staff.id !== this.createForm.id)
|
||||
.map((staff) => staff.clerkUserId)
|
||||
.filter(Boolean)
|
||||
);
|
||||
const options = records.filter(
|
||||
(clerk) => clerk.memberId && clerk.status !== false && !usedIds.has(clerk.memberId)
|
||||
);
|
||||
const currentId = this.createForm.clerkUserId;
|
||||
if (currentId && !options.some((clerk) => clerk.memberId === currentId)) {
|
||||
options.unshift({
|
||||
memberId: currentId,
|
||||
clerkName: this.createForm.staffName || "当前店员",
|
||||
});
|
||||
}
|
||||
this.clerkOptions = options;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.clerkLoading = false;
|
||||
});
|
||||
},
|
||||
clerkOptionLabel(clerk) {
|
||||
return clerk.clerkName || "未命名店员";
|
||||
},
|
||||
onClerkChange(memberId) {
|
||||
const clerk = this.clerkOptions.find((item) => item.memberId === memberId);
|
||||
if (!clerk) {
|
||||
return;
|
||||
defineOptions({ name: "appointmentServiceStaffList" });
|
||||
|
||||
const loading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
staffName: "",
|
||||
status: "",
|
||||
});
|
||||
const createVisible = ref(false);
|
||||
const createLoading = ref(false);
|
||||
const clerkLoading = ref(false);
|
||||
const clerkOptions = ref([]);
|
||||
const createForm = ref({
|
||||
id: "",
|
||||
clerkUserId: "",
|
||||
staffName: "",
|
||||
contactPhone: "",
|
||||
sortOrder: 0,
|
||||
status: "ENABLED",
|
||||
});
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖(原 ref="searchForm" 在 script 中未被使用)
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
getServiceStaffPage(searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result?.records || [];
|
||||
total.value = res.result?.total || 0;
|
||||
}
|
||||
this.createForm.staffName = clerk.clerkName || "";
|
||||
this.createForm.contactPhone = clerk.mobile || "";
|
||||
},
|
||||
submitCreate() {
|
||||
if (!this.createForm.clerkUserId) {
|
||||
this.$message.warning("请选择店员");
|
||||
return;
|
||||
}
|
||||
if (!this.createForm.staffName) {
|
||||
this.$message.warning("请填写员工姓名");
|
||||
return;
|
||||
}
|
||||
const editing = !!this.createForm.id;
|
||||
const request = editing ? updateServiceStaff(this.createForm.id, this.createForm) : createServiceStaff(this.createForm);
|
||||
this.createLoading = true;
|
||||
request
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success(editing ? "已保存" : "创建成功");
|
||||
this.createVisible = false;
|
||||
this.loadData();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.createLoading = false;
|
||||
});
|
||||
},
|
||||
handleDelete(row) {
|
||||
ElMessageBox.confirm(`确认删除服务人员「${row.staffName || ""}」?`, "提示", { type: "warning" })
|
||||
.then(() => deleteServiceStaff(row.id))
|
||||
.then((res) => {
|
||||
if (res && res.success) {
|
||||
this.$message.success("已删除");
|
||||
this.loadData();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
toggleStatus(row, enabled) {
|
||||
updateServiceStaffStatus(row.id, enabled ? "ENABLED" : "DISABLED").then((res) => {
|
||||
if (res.success) {
|
||||
row.status = enabled ? "ENABLED" : "DISABLED";
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
id: "",
|
||||
clerkUserId: "",
|
||||
staffName: "",
|
||||
contactPhone: "",
|
||||
sortOrder: 0,
|
||||
status: "ENABLED",
|
||||
};
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
createForm.value = emptyForm();
|
||||
createVisible.value = true;
|
||||
loadClerks();
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
createForm.value = {
|
||||
id: row.id,
|
||||
clerkUserId: row.clerkUserId || "",
|
||||
staffName: row.staffName || "",
|
||||
contactPhone: row.contactPhone || "",
|
||||
sortOrder: row.sortOrder == null ? 0 : row.sortOrder,
|
||||
status: row.status || "ENABLED",
|
||||
};
|
||||
createVisible.value = true;
|
||||
loadClerks();
|
||||
}
|
||||
|
||||
function loadClerks() {
|
||||
clerkLoading.value = true;
|
||||
getUserListData({ pageNumber: 1, pageSize: 200 })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
const records = res.result?.records || [];
|
||||
const usedIds = new Set(
|
||||
(data.value || [])
|
||||
.filter((staff) => staff.id !== createForm.value.id)
|
||||
.map((staff) => staff.clerkUserId)
|
||||
.filter(Boolean)
|
||||
);
|
||||
const options = records.filter(
|
||||
(clerk) => clerk.memberId && clerk.status !== false && !usedIds.has(clerk.memberId)
|
||||
);
|
||||
const currentId = createForm.value.clerkUserId;
|
||||
if (currentId && !options.some((clerk) => clerk.memberId === currentId)) {
|
||||
options.unshift({
|
||||
memberId: currentId,
|
||||
clerkName: createForm.value.staffName || "当前店员",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
clerkOptions.value = options;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
clerkLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function clerkOptionLabel(clerk) {
|
||||
return clerk.clerkName || "未命名店员";
|
||||
}
|
||||
|
||||
function onClerkChange(memberId) {
|
||||
const clerk = clerkOptions.value.find((item) => item.memberId === memberId);
|
||||
if (!clerk) {
|
||||
return;
|
||||
}
|
||||
createForm.value.staffName = clerk.clerkName || "";
|
||||
createForm.value.contactPhone = clerk.mobile || "";
|
||||
}
|
||||
|
||||
function submitCreate() {
|
||||
if (!createForm.value.clerkUserId) {
|
||||
// 原代码 this.$message 小写,全局仅注册了 $Message,此处按作者意图修正
|
||||
Message.warning("请选择店员");
|
||||
return;
|
||||
}
|
||||
if (!createForm.value.staffName) {
|
||||
Message.warning("请填写员工姓名");
|
||||
return;
|
||||
}
|
||||
const editing = !!createForm.value.id;
|
||||
const request = editing ? updateServiceStaff(createForm.value.id, createForm.value) : createServiceStaff(createForm.value);
|
||||
createLoading.value = true;
|
||||
request
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
Message.success(editing ? "已保存" : "创建成功");
|
||||
createVisible.value = false;
|
||||
loadData();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
createLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
ElMessageBox.confirm(`确认删除服务人员「${row.staffName || ""}」?`, "提示", { type: "warning" })
|
||||
.then(() => deleteServiceStaff(row.id))
|
||||
.then((res) => {
|
||||
if (res && res.success) {
|
||||
Message.success("已删除");
|
||||
loadData();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function toggleStatus(row, enabled) {
|
||||
updateServiceStaffStatus(row.id, enabled ? "ENABLED" : "DISABLED").then((res) => {
|
||||
if (res.success) {
|
||||
row.status = enabled ? "ENABLED" : "DISABLED";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -260,7 +260,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
Delete,
|
||||
@@ -299,282 +301,268 @@ import {
|
||||
hasOptions,
|
||||
} from "@/constants/systemForm";
|
||||
|
||||
export default {
|
||||
name: "appointmentSystemFormDesigner",
|
||||
components: { editor, SystemFormFieldPreview, Delete, CopyDocument, ArrowUp, ArrowDown },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
formId: "",
|
||||
formName: "",
|
||||
fields: [],
|
||||
selectedKey: "",
|
||||
baseline: "",
|
||||
batchVisible: false,
|
||||
batchText: "",
|
||||
palette: FORM_FIELD_PALETTE,
|
||||
paletteIcons: {
|
||||
CHECKBOX: Checked,
|
||||
CITY: Location,
|
||||
RADIO: CircleCheck,
|
||||
DATE: Calendar,
|
||||
DATE_RANGE: Tickets,
|
||||
SELECT: ArrowDown,
|
||||
TEXT: EditPen,
|
||||
TIME: Clock,
|
||||
TIME_RANGE: Timer,
|
||||
RICH_TEXT: Document,
|
||||
IMAGE: Picture,
|
||||
},
|
||||
OPTION_MAX,
|
||||
FIELD_LABEL_MAX,
|
||||
OPTION_LABEL_MAX,
|
||||
HINT_TEXT_MAX,
|
||||
Delete,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return this.fields.find((item) => item.field_key === this.selectedKey) || null;
|
||||
},
|
||||
dateRangeValue: {
|
||||
get() {
|
||||
const field = this.selected;
|
||||
if (!field) return null;
|
||||
return field.default_date_start && field.default_date_end
|
||||
? [field.default_date_start, field.default_date_end]
|
||||
: null;
|
||||
},
|
||||
set(val) {
|
||||
if (!this.selected) return;
|
||||
this.selected.default_date_start = val?.[0] || null;
|
||||
this.selected.default_date_end = val?.[1] || null;
|
||||
},
|
||||
},
|
||||
timeRangeValue: {
|
||||
get() {
|
||||
const field = this.selected;
|
||||
if (!field) return null;
|
||||
return field.default_time_start && field.default_time_end
|
||||
? [field.default_time_start, field.default_time_end]
|
||||
: null;
|
||||
},
|
||||
set(val) {
|
||||
if (!this.selected) return;
|
||||
this.selected.default_time_start = val?.[0] || null;
|
||||
this.selected.default_time_end = val?.[1] || null;
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedKey() {
|
||||
this.closeBatch();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.formId = this.$route.query.id || "";
|
||||
if (this.formId) {
|
||||
this.loadForm();
|
||||
} else {
|
||||
this.captureBaseline();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addOption,
|
||||
removeOption,
|
||||
hasRequiredSwitch,
|
||||
hasOptions,
|
||||
fieldTypeLabel(type) {
|
||||
return FORM_FIELD_LABEL[type] || type;
|
||||
},
|
||||
captureBaseline() {
|
||||
this.baseline = JSON.stringify({
|
||||
formName: this.formName,
|
||||
fields: this.fields,
|
||||
});
|
||||
},
|
||||
loadForm() {
|
||||
this.loading = true;
|
||||
getRequest(`/appointment/system-forms/${this.formId}`)
|
||||
.then((res) => {
|
||||
if (!res.success || !res.result) {
|
||||
ElMessage.error("表单不存在");
|
||||
this.$router.push({ name: "appointment-system-forms" });
|
||||
return;
|
||||
}
|
||||
this.formName = res.result.formName || "";
|
||||
this.fields = parseFormSchema(res.result.formSchema);
|
||||
this.selectedKey = this.fields[0]?.field_key || "";
|
||||
this.captureBaseline();
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("加载表单失败");
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
addField(type) {
|
||||
const field = createField(type);
|
||||
this.fields.push(field);
|
||||
this.selectedKey = field.field_key;
|
||||
},
|
||||
removeField(index) {
|
||||
this.fields.splice(index, 1);
|
||||
this.selectedKey = this.fields[index]?.field_key || this.fields[index - 1]?.field_key || "";
|
||||
},
|
||||
copyField(index) {
|
||||
const source = this.fields[index];
|
||||
if (!source) return;
|
||||
const cloned = JSON.parse(JSON.stringify(source));
|
||||
cloned.field_key = createFieldKey();
|
||||
if (Array.isArray(cloned.options)) {
|
||||
cloned.options = cloned.options.map((opt) => ({
|
||||
...opt,
|
||||
value: createFieldKey(), // 避免与原字段共用 option value
|
||||
}));
|
||||
}
|
||||
this.fields.splice(index + 1, 0, cloned);
|
||||
this.selectedKey = cloned.field_key;
|
||||
},
|
||||
moveField(index, step) {
|
||||
const target = index + step;
|
||||
if (target < 0 || target >= this.fields.length) return;
|
||||
const list = [...this.fields];
|
||||
const [item] = list.splice(index, 1);
|
||||
list.splice(target, 0, item);
|
||||
this.fields = list;
|
||||
},
|
||||
closeBatch() {
|
||||
this.batchVisible = false;
|
||||
this.batchText = "";
|
||||
},
|
||||
confirmBatch() {
|
||||
if (!this.selected) return;
|
||||
this.selected.options = batchAddOptions(this.selected.options, this.batchText, {
|
||||
radio: this.selected.field_type === "RADIO",
|
||||
});
|
||||
this.closeBatch();
|
||||
},
|
||||
handleReset() {
|
||||
ElMessageBox.confirm("将撤销本次未保存的编辑,恢复进入页面时的内容?", "重置", {
|
||||
type: "warning",
|
||||
}).then(() => {
|
||||
const snap = JSON.parse(this.baseline || "{}");
|
||||
this.formName = snap.formName || "";
|
||||
this.fields = JSON.parse(JSON.stringify(snap.fields || []));
|
||||
this.selectedKey = this.fields[0]?.field_key || "";
|
||||
}).catch(() => {});
|
||||
},
|
||||
validate() {
|
||||
if (!this.formName.trim()) {
|
||||
ElMessage.error("请填写表单名称");
|
||||
return false;
|
||||
}
|
||||
if (!this.fields.length) {
|
||||
ElMessage.error("请至少添加一个表单组件");
|
||||
return false;
|
||||
}
|
||||
for (const field of this.fields) {
|
||||
if (field.field_type !== "RICH_TEXT") {
|
||||
const label = (field.field_label || "").trim();
|
||||
if (!label) {
|
||||
ElMessage.error("请填写组件标题");
|
||||
return false;
|
||||
}
|
||||
if (label.length > FIELD_LABEL_MAX) {
|
||||
ElMessage.error(`组件标题最多 ${FIELD_LABEL_MAX} 个字`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasOptions(field.field_type)) {
|
||||
const labels = (field.options || []).map((item) => item.label?.trim()).filter(Boolean);
|
||||
if (labels.length < 2) {
|
||||
ElMessage.error(`${field.field_label || "选项组件"}至少需要 2 个选项`);
|
||||
return false;
|
||||
}
|
||||
if ((field.options || []).some((item) => (item.label || "").length > OPTION_LABEL_MAX)) {
|
||||
ElMessage.error("选项最多 10 个字");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (field.hint_text && field.hint_text.length > HINT_TEXT_MAX) {
|
||||
ElMessage.error("提示语最多 10 个字");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_date_mode === "SPECIFIED_DATE" &&
|
||||
field.field_type === "DATE" &&
|
||||
!field.default_date
|
||||
) {
|
||||
ElMessage.error("请为日期组件指定默认日期");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_date_mode === "SPECIFIED_DATE" &&
|
||||
field.field_type === "DATE_RANGE"
|
||||
) {
|
||||
if (!field.default_date_start || !field.default_date_end) {
|
||||
ElMessage.error("请为日期范围指定起止日期");
|
||||
return false;
|
||||
}
|
||||
if (field.default_date_start > field.default_date_end) {
|
||||
ElMessage.error("日期范围的结束日期不能早于开始日期");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_time_mode === "SPECIFIED_TIME" &&
|
||||
field.field_type === "TIME" &&
|
||||
!field.default_time
|
||||
) {
|
||||
ElMessage.error("请为时间组件指定默认时间");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_time_mode === "SPECIFIED_TIME" &&
|
||||
field.field_type === "TIME_RANGE"
|
||||
) {
|
||||
if (!field.default_time_start || !field.default_time_end) {
|
||||
ElMessage.error("请为时间范围指定起止时间");
|
||||
return false;
|
||||
}
|
||||
if (field.default_time_start > field.default_time_end) {
|
||||
ElMessage.error("时间范围的结束时间不能早于开始时间");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSave() {
|
||||
if (!this.validate()) return;
|
||||
this.saving = true;
|
||||
postRequestWithNoForm("/appointment/system-forms", {
|
||||
id: this.formId || undefined,
|
||||
formName: this.formName.trim(),
|
||||
formSchema: serializeFormSchema(this.fields),
|
||||
status: "ENABLED",
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("已保存并发布");
|
||||
this.formId = res.result?.id || this.formId;
|
||||
this.captureBaseline();
|
||||
this.$router.push({ name: "appointment-system-forms" });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
defineOptions({ name: "appointmentSystemFormDesigner" });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const formId = ref("");
|
||||
const formName = ref("");
|
||||
const fields = ref([]);
|
||||
const selectedKey = ref("");
|
||||
const baseline = ref("");
|
||||
const batchVisible = ref(false);
|
||||
const batchText = ref("");
|
||||
const palette = FORM_FIELD_PALETTE;
|
||||
const paletteIcons = {
|
||||
CHECKBOX: Checked,
|
||||
CITY: Location,
|
||||
RADIO: CircleCheck,
|
||||
DATE: Calendar,
|
||||
DATE_RANGE: Tickets,
|
||||
SELECT: ArrowDown,
|
||||
TEXT: EditPen,
|
||||
TIME: Clock,
|
||||
TIME_RANGE: Timer,
|
||||
RICH_TEXT: Document,
|
||||
IMAGE: Picture,
|
||||
};
|
||||
|
||||
const selected = computed(
|
||||
() => fields.value.find((item) => item.field_key === selectedKey.value) || null
|
||||
);
|
||||
const dateRangeValue = computed({
|
||||
get() {
|
||||
const field = selected.value;
|
||||
if (!field) return null;
|
||||
return field.default_date_start && field.default_date_end
|
||||
? [field.default_date_start, field.default_date_end]
|
||||
: null;
|
||||
},
|
||||
set(val) {
|
||||
if (!selected.value) return;
|
||||
selected.value.default_date_start = val?.[0] || null;
|
||||
selected.value.default_date_end = val?.[1] || null;
|
||||
},
|
||||
});
|
||||
const timeRangeValue = computed({
|
||||
get() {
|
||||
const field = selected.value;
|
||||
if (!field) return null;
|
||||
return field.default_time_start && field.default_time_end
|
||||
? [field.default_time_start, field.default_time_end]
|
||||
: null;
|
||||
},
|
||||
set(val) {
|
||||
if (!selected.value) return;
|
||||
selected.value.default_time_start = val?.[0] || null;
|
||||
selected.value.default_time_end = val?.[1] || null;
|
||||
},
|
||||
});
|
||||
|
||||
watch(selectedKey, () => {
|
||||
closeBatch();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
formId.value = route.query.id || "";
|
||||
if (formId.value) {
|
||||
loadForm();
|
||||
} else {
|
||||
captureBaseline();
|
||||
}
|
||||
});
|
||||
|
||||
function fieldTypeLabel(type) {
|
||||
return FORM_FIELD_LABEL[type] || type;
|
||||
}
|
||||
function captureBaseline() {
|
||||
baseline.value = JSON.stringify({
|
||||
formName: formName.value,
|
||||
fields: fields.value,
|
||||
});
|
||||
}
|
||||
function loadForm() {
|
||||
loading.value = true;
|
||||
getRequest(`/appointment/system-forms/${formId.value}`)
|
||||
.then((res) => {
|
||||
if (!res.success || !res.result) {
|
||||
ElMessage.error("表单不存在");
|
||||
router.push({ name: "appointment-system-forms" });
|
||||
return;
|
||||
}
|
||||
formName.value = res.result.formName || "";
|
||||
fields.value = parseFormSchema(res.result.formSchema);
|
||||
selectedKey.value = fields.value[0]?.field_key || "";
|
||||
captureBaseline();
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("加载表单失败");
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function addField(type) {
|
||||
const field = createField(type);
|
||||
fields.value.push(field);
|
||||
selectedKey.value = field.field_key;
|
||||
}
|
||||
function removeField(index) {
|
||||
fields.value.splice(index, 1);
|
||||
selectedKey.value = fields.value[index]?.field_key || fields.value[index - 1]?.field_key || "";
|
||||
}
|
||||
function copyField(index) {
|
||||
const source = fields.value[index];
|
||||
if (!source) return;
|
||||
const cloned = JSON.parse(JSON.stringify(source));
|
||||
cloned.field_key = createFieldKey();
|
||||
if (Array.isArray(cloned.options)) {
|
||||
cloned.options = cloned.options.map((opt) => ({
|
||||
...opt,
|
||||
value: createFieldKey(), // 避免与原字段共用 option value
|
||||
}));
|
||||
}
|
||||
fields.value.splice(index + 1, 0, cloned);
|
||||
selectedKey.value = cloned.field_key;
|
||||
}
|
||||
function moveField(index, step) {
|
||||
const target = index + step;
|
||||
if (target < 0 || target >= fields.value.length) return;
|
||||
const list = [...fields.value];
|
||||
const [item] = list.splice(index, 1);
|
||||
list.splice(target, 0, item);
|
||||
fields.value = list;
|
||||
}
|
||||
function closeBatch() {
|
||||
batchVisible.value = false;
|
||||
batchText.value = "";
|
||||
}
|
||||
function confirmBatch() {
|
||||
if (!selected.value) return;
|
||||
selected.value.options = batchAddOptions(selected.value.options, batchText.value, {
|
||||
radio: selected.value.field_type === "RADIO",
|
||||
});
|
||||
closeBatch();
|
||||
}
|
||||
function handleReset() {
|
||||
ElMessageBox.confirm("将撤销本次未保存的编辑,恢复进入页面时的内容?", "重置", {
|
||||
type: "warning",
|
||||
}).then(() => {
|
||||
const snap = JSON.parse(baseline.value || "{}");
|
||||
formName.value = snap.formName || "";
|
||||
fields.value = JSON.parse(JSON.stringify(snap.fields || []));
|
||||
selectedKey.value = fields.value[0]?.field_key || "";
|
||||
}).catch(() => {});
|
||||
}
|
||||
function validate() {
|
||||
if (!formName.value.trim()) {
|
||||
ElMessage.error("请填写表单名称");
|
||||
return false;
|
||||
}
|
||||
if (!fields.value.length) {
|
||||
ElMessage.error("请至少添加一个表单组件");
|
||||
return false;
|
||||
}
|
||||
for (const field of fields.value) {
|
||||
if (field.field_type !== "RICH_TEXT") {
|
||||
const label = (field.field_label || "").trim();
|
||||
if (!label) {
|
||||
ElMessage.error("请填写组件标题");
|
||||
return false;
|
||||
}
|
||||
if (label.length > FIELD_LABEL_MAX) {
|
||||
ElMessage.error(`组件标题最多 ${FIELD_LABEL_MAX} 个字`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasOptions(field.field_type)) {
|
||||
const labels = (field.options || []).map((item) => item.label?.trim()).filter(Boolean);
|
||||
if (labels.length < 2) {
|
||||
ElMessage.error(`${field.field_label || "选项组件"}至少需要 2 个选项`);
|
||||
return false;
|
||||
}
|
||||
if ((field.options || []).some((item) => (item.label || "").length > OPTION_LABEL_MAX)) {
|
||||
ElMessage.error("选项最多 10 个字");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (field.hint_text && field.hint_text.length > HINT_TEXT_MAX) {
|
||||
ElMessage.error("提示语最多 10 个字");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_date_mode === "SPECIFIED_DATE" &&
|
||||
field.field_type === "DATE" &&
|
||||
!field.default_date
|
||||
) {
|
||||
ElMessage.error("请为日期组件指定默认日期");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_date_mode === "SPECIFIED_DATE" &&
|
||||
field.field_type === "DATE_RANGE"
|
||||
) {
|
||||
if (!field.default_date_start || !field.default_date_end) {
|
||||
ElMessage.error("请为日期范围指定起止日期");
|
||||
return false;
|
||||
}
|
||||
if (field.default_date_start > field.default_date_end) {
|
||||
ElMessage.error("日期范围的结束日期不能早于开始日期");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_time_mode === "SPECIFIED_TIME" &&
|
||||
field.field_type === "TIME" &&
|
||||
!field.default_time
|
||||
) {
|
||||
ElMessage.error("请为时间组件指定默认时间");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
field.default_visibility === "SHOW" &&
|
||||
field.default_time_mode === "SPECIFIED_TIME" &&
|
||||
field.field_type === "TIME_RANGE"
|
||||
) {
|
||||
if (!field.default_time_start || !field.default_time_end) {
|
||||
ElMessage.error("请为时间范围指定起止时间");
|
||||
return false;
|
||||
}
|
||||
if (field.default_time_start > field.default_time_end) {
|
||||
ElMessage.error("时间范围的结束时间不能早于开始时间");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleSave() {
|
||||
if (!validate()) return;
|
||||
saving.value = true;
|
||||
postRequestWithNoForm("/appointment/system-forms", {
|
||||
id: formId.value || undefined,
|
||||
formName: formName.value.trim(),
|
||||
formSchema: serializeFormSchema(fields.value),
|
||||
status: "ENABLED",
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("已保存并发布");
|
||||
formId.value = res.result?.id || formId.value;
|
||||
captureBaseline();
|
||||
router.push({ name: "appointment-system-forms" });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
saving.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -54,84 +54,89 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getRequest, postRequestWithNoForm, deleteRequest } from "@/libs/axios";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { countFormFields } from "@/constants/systemForm";
|
||||
|
||||
export default {
|
||||
name: "appointmentSystemFormList",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
formName: "",
|
||||
status: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
fieldCount: countFormFields,
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.loadData();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm.formName = "";
|
||||
this.searchForm.status = "";
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.loadData();
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
getRequest("/appointment/system-forms", this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result?.records || [];
|
||||
this.total = res.result?.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
goDesigner(id) {
|
||||
this.$router.push({
|
||||
name: "appointment-system-form-designer",
|
||||
query: id ? { id } : {},
|
||||
});
|
||||
},
|
||||
toggleStatus(row, enabled) {
|
||||
const status = enabled ? "ENABLED" : "DISABLED";
|
||||
postRequestWithNoForm("/appointment/system-forms", {
|
||||
id: row.id,
|
||||
formName: row.formName,
|
||||
formSchema: row.formSchema || "[]",
|
||||
status,
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
row.status = status;
|
||||
ElMessage.success(enabled ? "已启用" : "已停用");
|
||||
}
|
||||
});
|
||||
},
|
||||
handleDelete(row) {
|
||||
ElMessageBox.confirm("确认删除该表单?", "提示", { type: "warning" }).then(() => {
|
||||
deleteRequest(`/appointment/system-forms/${row.id}`).then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("已删除");
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "appointmentSystemFormList" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const searchForm = reactive({
|
||||
formName: "",
|
||||
status: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const fieldCount = countFormFields;
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.formName = "";
|
||||
searchForm.status = "";
|
||||
searchForm.pageNumber = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
getRequest("/appointment/system-forms", searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result?.records || [];
|
||||
total.value = res.result?.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function goDesigner(id) {
|
||||
router.push({
|
||||
name: "appointment-system-form-designer",
|
||||
query: id ? { id } : {},
|
||||
});
|
||||
}
|
||||
|
||||
function toggleStatus(row, enabled) {
|
||||
const status = enabled ? "ENABLED" : "DISABLED";
|
||||
postRequestWithNoForm("/appointment/system-forms", {
|
||||
id: row.id,
|
||||
formName: row.formName,
|
||||
formSchema: row.formSchema || "[]",
|
||||
status,
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
row.status = status;
|
||||
ElMessage.success(enabled ? "已启用" : "已停用");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
ElMessageBox.confirm("确认删除该表单?", "提示", { type: "warning" }).then(() => {
|
||||
deleteRequest(`/appointment/system-forms/${row.id}`).then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("已删除");
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px" @keyup.enter="handleSearch">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="80px" @keyup.enter="handleSearch">
|
||||
<el-form-item label="工单编号" prop="workOrderSn">
|
||||
<el-input v-model="searchForm.workOrderSn" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
@@ -75,7 +75,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
getWorkOrderPage,
|
||||
getWorkOrderStatusCounts,
|
||||
@@ -83,102 +85,103 @@ import {
|
||||
endWorkOrderService,
|
||||
} from "@/api/appointment";
|
||||
import { SERVICE_MODE_LABEL, WORK_ORDER_STATUS_LABEL } from "@/constants/appointment";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "appointmentWorkOrderList",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
statusTab: "ALL",
|
||||
statusCounts: {},
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
serviceStatus: "",
|
||||
workOrderSn: "",
|
||||
orderSn: "",
|
||||
serviceMode: "",
|
||||
},
|
||||
statusTabs: [
|
||||
{ value: "ALL", label: "全部" },
|
||||
{ value: "PENDING_CLAIM", label: "待领取" },
|
||||
{ value: "WAIT_VERIFY", label: "待核销" },
|
||||
{ value: "IN_SERVICE", label: "服务中" },
|
||||
{ value: "SERVICE_ENDED", label: "服务结束" },
|
||||
{ value: "REFUNDED", label: "已退款" },
|
||||
],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.tab) {
|
||||
this.statusTab = this.$route.query.tab;
|
||||
this.searchForm.serviceStatus = this.$route.query.tab === "ALL" ? "" : this.$route.query.tab;
|
||||
defineOptions({ name: "appointmentWorkOrderList" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const statusTab = ref("ALL");
|
||||
const statusCounts = ref({});
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
serviceStatus: "",
|
||||
workOrderSn: "",
|
||||
orderSn: "",
|
||||
serviceMode: "",
|
||||
});
|
||||
const statusTabs = ref([
|
||||
{ value: "ALL", label: "全部" },
|
||||
{ value: "PENDING_CLAIM", label: "待领取" },
|
||||
{ value: "WAIT_VERIFY", label: "待核销" },
|
||||
{ value: "IN_SERVICE", label: "服务中" },
|
||||
{ value: "SERVICE_ENDED", label: "服务结束" },
|
||||
{ value: "REFUNDED", label: "已退款" },
|
||||
]);
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.tab) {
|
||||
statusTab.value = route.query.tab;
|
||||
searchForm.serviceStatus = route.query.tab === "ALL" ? "" : route.query.tab;
|
||||
}
|
||||
loadCounts();
|
||||
loadData();
|
||||
});
|
||||
|
||||
function serviceModeText(v) {
|
||||
return SERVICE_MODE_LABEL[v] || v || "—";
|
||||
}
|
||||
function statusText(v) {
|
||||
return WORK_ORDER_STATUS_LABEL[v] || v || "—";
|
||||
}
|
||||
function tabLabel(item) {
|
||||
const count = statusCounts.value[item.value];
|
||||
return count != null ? `${item.label}(${count})` : item.label;
|
||||
}
|
||||
function onTabClick(tab) {
|
||||
searchForm.serviceStatus = tab.paneName === "ALL" ? "" : tab.paneName;
|
||||
searchForm.pageNumber = 1;
|
||||
loadData();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
loadData();
|
||||
}
|
||||
function loadCounts() {
|
||||
getWorkOrderStatusCounts()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
statusCounts.value = res.result;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
getWorkOrderPage(searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result?.records || [];
|
||||
total.value = res.result?.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function handleClaim(row) {
|
||||
claimWorkOrder(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("领取成功");
|
||||
loadCounts();
|
||||
loadData();
|
||||
}
|
||||
this.loadCounts();
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
serviceModeText(v) {
|
||||
return SERVICE_MODE_LABEL[v] || v || "—";
|
||||
},
|
||||
statusText(v) {
|
||||
return WORK_ORDER_STATUS_LABEL[v] || v || "—";
|
||||
},
|
||||
tabLabel(item) {
|
||||
const count = this.statusCounts[item.value];
|
||||
return count != null ? `${item.label}(${count})` : item.label;
|
||||
},
|
||||
onTabClick(tab) {
|
||||
this.searchForm.serviceStatus = tab.paneName === "ALL" ? "" : tab.paneName;
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.loadData();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.loadData();
|
||||
},
|
||||
loadCounts() {
|
||||
getWorkOrderStatusCounts()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.statusCounts = res.result;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
getWorkOrderPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result?.records || [];
|
||||
this.total = res.result?.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleClaim(row) {
|
||||
claimWorkOrder(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success("领取成功");
|
||||
this.loadCounts();
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleEnd(row) {
|
||||
endWorkOrderService(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success("已结束服务");
|
||||
this.loadCounts();
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
function handleEnd(row) {
|
||||
endWorkOrderService(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("已结束服务");
|
||||
loadCounts();
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-card class="change-pass">
|
||||
<template #header>修改密码</template>
|
||||
<el-form
|
||||
ref="editPasswordForm"
|
||||
ref="editPasswordFormRef"
|
||||
:model="editPasswordForm"
|
||||
label-width="100px"
|
||||
label-position="right"
|
||||
@@ -41,84 +41,83 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import SetPassword from "@/components/lili/set-password";
|
||||
import { changePass } from "@/api/index";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { md5 } from "@/utils/md5.js";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
export default {
|
||||
name: "change_pass",
|
||||
components: {
|
||||
SetPassword,
|
||||
},
|
||||
data() {
|
||||
const valideRePassword = (rule, value, callback) => {
|
||||
if (value !== this.editPasswordForm.newPassword) {
|
||||
callback(new Error("两次输入密码不一致"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
savePassLoading: false,
|
||||
editPasswordForm: {
|
||||
oldPass: "",
|
||||
newPassword: "",
|
||||
rePass: "",
|
||||
},
|
||||
strength: "",
|
||||
passwordValidate: {
|
||||
oldPass: [{ required: true, message: "请输入原密码", trigger: "blur" }],
|
||||
newPassword: [
|
||||
{ required: true, message: "请输入新密码", trigger: "blur" },
|
||||
{ min: 6, message: "请至少输入6个字符", trigger: "blur" },
|
||||
{ max: 32, message: "最多输入32个字符", trigger: "blur" },
|
||||
],
|
||||
rePass: [
|
||||
{ required: true, message: "请再次输入新密码", trigger: "blur" },
|
||||
{ validator: valideRePassword, trigger: "blur" },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeInputPass(v, grade, strength) {
|
||||
this.strength = strength;
|
||||
},
|
||||
editPassword() {
|
||||
const params = {
|
||||
password: this.md5(this.editPasswordForm.oldPass),
|
||||
newPassword: this.md5(this.editPasswordForm.newPassword),
|
||||
};
|
||||
this.$refs.editPasswordForm.validate((valid) => {
|
||||
if (!valid) return;
|
||||
this.savePassLoading = true;
|
||||
changePass(params)
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
ElMessageBox.alert("修改密码成功,需重新登录", "修改密码成功", {
|
||||
confirmButtonText: "确定",
|
||||
}).then(() => {
|
||||
this.$store.commit("logout", this);
|
||||
this.$store.commit("clearOpenedSubmenu");
|
||||
this.$router.push({ name: "login" });
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.savePassLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
cancelEditPass() {
|
||||
this.$store.commit("removeTag", "change_pass");
|
||||
localStorage.storeOpenedList = JSON.stringify(
|
||||
this.$store.state.app.storeOpenedList
|
||||
);
|
||||
const list = this.$store.state.app.storeOpenedList;
|
||||
const lastPageName = list.length > 1 ? list[list.length - 1].name : list[0].name;
|
||||
this.$router.push({ name: lastPageName });
|
||||
},
|
||||
},
|
||||
defineOptions({ name: "change_pass" });
|
||||
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const editPasswordFormRef = ref(null);
|
||||
const savePassLoading = ref(false);
|
||||
const editPasswordForm = reactive({
|
||||
oldPass: "",
|
||||
newPassword: "",
|
||||
rePass: "",
|
||||
});
|
||||
const strength = ref("");
|
||||
const valideRePassword = (rule, value, callback) => {
|
||||
if (value !== editPasswordForm.newPassword) {
|
||||
callback(new Error("两次输入密码不一致"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const passwordValidate = {
|
||||
oldPass: [{ required: true, message: "请输入原密码", trigger: "blur" }],
|
||||
newPassword: [
|
||||
{ required: true, message: "请输入新密码", trigger: "blur" },
|
||||
{ min: 6, message: "请至少输入6个字符", trigger: "blur" },
|
||||
{ max: 32, message: "最多输入32个字符", trigger: "blur" },
|
||||
],
|
||||
rePass: [
|
||||
{ required: true, message: "请再次输入新密码", trigger: "blur" },
|
||||
{ validator: valideRePassword, trigger: "blur" },
|
||||
],
|
||||
};
|
||||
|
||||
function changeInputPass(v, grade, strengthVal) {
|
||||
strength.value = strengthVal;
|
||||
}
|
||||
function editPassword() {
|
||||
const params = {
|
||||
password: md5(editPasswordForm.oldPass),
|
||||
newPassword: md5(editPasswordForm.newPassword),
|
||||
};
|
||||
editPasswordFormRef.value.validate((valid) => {
|
||||
if (!valid) return;
|
||||
savePassLoading.value = true;
|
||||
changePass(params)
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
ElMessageBox.alert("修改密码成功,需重新登录", "修改密码成功", {
|
||||
confirmButtonText: "确定",
|
||||
}).then(() => {
|
||||
userStore.logout();
|
||||
appStore.clearOpenedSubmenu();
|
||||
router.push({ name: "login" });
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
savePassLoading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
function cancelEditPass() {
|
||||
appStore.removeTag("change_pass");
|
||||
localStorage.storeOpenedList = JSON.stringify(appStore.storeOpenedList);
|
||||
const list = appStore.storeOpenedList;
|
||||
const lastPageName = list.length > 1 ? list[list.length - 1].name : list[0].name;
|
||||
router.push({ name: lastPageName });
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -132,7 +132,7 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoodsData" />
|
||||
<liliDialog ref="liliDialogRef" @selectedGoodsData="selectedGoodsData" />
|
||||
|
||||
<el-dialog
|
||||
v-model="modalVisible"
|
||||
@@ -222,7 +222,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import {
|
||||
getDistributionGoods,
|
||||
distributionGoodsCancel,
|
||||
@@ -230,242 +231,259 @@ import {
|
||||
updateDistributionGoodsCommission,
|
||||
} from "@/api/distribution";
|
||||
import { getShopGoodsLabelListSeller } from "@/api/goods";
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "distributionGoods",
|
||||
components: { liliDialog },
|
||||
data() {
|
||||
return {
|
||||
modalVisible: false,
|
||||
modalTitle: "添加分销商品",
|
||||
editVisible: false,
|
||||
editLoading: false,
|
||||
submitLoading: false,
|
||||
loading: true,
|
||||
currentAuthStatus: "ALL",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
goodsName: "",
|
||||
storeCategoryPath: "",
|
||||
goodsType: "",
|
||||
authStatus: "",
|
||||
},
|
||||
shopCategoryList: [],
|
||||
shopCategoryProps: {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
},
|
||||
selectedGoodsList: [],
|
||||
editForm: {
|
||||
id: "",
|
||||
goodsName: "",
|
||||
commission: "",
|
||||
commissionError: false,
|
||||
},
|
||||
commissionPattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
|
||||
data: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.loadFilterOptions();
|
||||
this.getDataList();
|
||||
},
|
||||
loadFilterOptions() {
|
||||
getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.labelName,
|
||||
children:
|
||||
item.children && item.children.length
|
||||
? this.formatShopCategory(item.children)
|
||||
: undefined,
|
||||
}));
|
||||
},
|
||||
goodsTypeText(value) {
|
||||
if (value === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (value === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (value === "E_COUPON") return "电子卡券";
|
||||
return "-";
|
||||
},
|
||||
authStatusText(value) {
|
||||
if (value === "TOBEAUDITED") return "待审核";
|
||||
if (value === "REFUSE") return "审核未通过";
|
||||
return "已审核";
|
||||
},
|
||||
onAuthStatusChange(name) {
|
||||
this.searchForm.authStatus = name === "ALL" ? "" : name;
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
add() {
|
||||
this.$refs.liliDialog.goodsData = [];
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
selectedGoodsData(selected) {
|
||||
if (!selected?.length) return;
|
||||
this.selectedGoodsList = selected.map((item) => ({
|
||||
skuId: item.id,
|
||||
goodsId: item.goodsId,
|
||||
thumbnail: item.thumbnail,
|
||||
goodsName: item.goodsName,
|
||||
specs: item.simpleSpecs || "-",
|
||||
price: item.price,
|
||||
commission: "1",
|
||||
commissionError: false,
|
||||
}));
|
||||
this.modalVisible = true;
|
||||
this.modalTitle = `添加分销商品(已选${this.selectedGoodsList.length}项)`;
|
||||
},
|
||||
validateRowCommission(row) {
|
||||
const valid = this.commissionPattern.test(String(row.commission || "").trim());
|
||||
row.commissionError = !valid;
|
||||
return valid;
|
||||
},
|
||||
validateAllCommission() {
|
||||
let valid = true;
|
||||
this.selectedGoodsList.forEach((row) => {
|
||||
if (!this.validateRowCommission(row)) {
|
||||
valid = false;
|
||||
defineOptions({ name: "distributionGoods" });
|
||||
|
||||
const modalVisible = ref(false);
|
||||
const modalTitle = ref("添加分销商品");
|
||||
const editVisible = ref(false);
|
||||
const editLoading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const currentAuthStatus = ref("ALL");
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
goodsName: "",
|
||||
storeCategoryPath: "",
|
||||
goodsType: "",
|
||||
authStatus: "",
|
||||
});
|
||||
const shopCategoryList = ref([]);
|
||||
const shopCategoryProps = {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
};
|
||||
const selectedGoodsList = ref([]);
|
||||
const editForm = ref({
|
||||
id: "",
|
||||
goodsName: "",
|
||||
commission: "",
|
||||
commissionError: false,
|
||||
});
|
||||
const commissionPattern = /^[1-9]\d{0,3}(\.\d{1,2})?$/;
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
const liliDialogRef = ref(null);
|
||||
const editFormRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
loadFilterOptions();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function loadFilterOptions() {
|
||||
getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
shopCategoryList.value = formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.labelName,
|
||||
children:
|
||||
item.children && item.children.length
|
||||
? formatShopCategory(item.children)
|
||||
: undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
function goodsTypeText(value) {
|
||||
if (value === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (value === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (value === "E_COUPON") return "电子卡券";
|
||||
return "-";
|
||||
}
|
||||
|
||||
function authStatusText(value) {
|
||||
if (value === "TOBEAUDITED") return "待审核";
|
||||
if (value === "REFUSE") return "审核未通过";
|
||||
return "已审核";
|
||||
}
|
||||
|
||||
function onAuthStatusChange(name) {
|
||||
searchForm.authStatus = name === "ALL" ? "" : name;
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function add() {
|
||||
liliDialogRef.value.goodsData = [];
|
||||
liliDialogRef.value.open("goods");
|
||||
}
|
||||
|
||||
function selectedGoodsData(selected) {
|
||||
if (!selected?.length) return;
|
||||
selectedGoodsList.value = selected.map((item) => ({
|
||||
skuId: item.id,
|
||||
goodsId: item.goodsId,
|
||||
thumbnail: item.thumbnail,
|
||||
goodsName: item.goodsName,
|
||||
specs: item.simpleSpecs || "-",
|
||||
price: item.price,
|
||||
commission: "1",
|
||||
commissionError: false,
|
||||
}));
|
||||
modalVisible.value = true;
|
||||
modalTitle.value = `添加分销商品(已选${selectedGoodsList.value.length}项)`;
|
||||
}
|
||||
|
||||
function validateRowCommission(row) {
|
||||
const valid = commissionPattern.test(String(row.commission || "").trim());
|
||||
row.commissionError = !valid;
|
||||
return valid;
|
||||
}
|
||||
|
||||
function validateAllCommission() {
|
||||
let valid = true;
|
||||
selectedGoodsList.value.forEach((row) => {
|
||||
if (!validateRowCommission(row)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
|
||||
function removeSelectedGoods(index) {
|
||||
selectedGoodsList.value.splice(index, 1);
|
||||
if (!selectedGoodsList.value.length) {
|
||||
modalVisible.value = false;
|
||||
return;
|
||||
}
|
||||
modalTitle.value = `添加分销商品(已选${selectedGoodsList.value.length}项)`;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!selectedGoodsList.value.length) return;
|
||||
if (!validateAllCommission()) {
|
||||
Message.error("请为所有商品填写大于1且小于9999的合法佣金金额");
|
||||
return;
|
||||
}
|
||||
submitLoading.value = true;
|
||||
const items = selectedGoodsList.value.map((item) => ({
|
||||
skuId: item.skuId,
|
||||
commission: Number(item.commission),
|
||||
}));
|
||||
distributionGoodsBatchCheck(items)
|
||||
.then((res) => {
|
||||
if (res?.success || res?.message === "success") {
|
||||
const addedCount = Array.isArray(res?.result) ? res.result.length : 0;
|
||||
if (addedCount > 0) {
|
||||
Message.success(`成功添加${addedCount}个分销商品`);
|
||||
} else {
|
||||
Message.info("所选商品均已是分销商品,已跳过");
|
||||
}
|
||||
}
|
||||
modalVisible.value = false;
|
||||
selectedGoodsList.value = [];
|
||||
getDataList();
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function changePage(v) {
|
||||
searchForm.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePageSize(v) {
|
||||
searchForm.pageSize = v;
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getDistributionGoods(searchForm)
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
const page = res.result || {};
|
||||
data.value = page.records || [];
|
||||
total.value = page.total || 0;
|
||||
} else {
|
||||
data.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function remove(row) {
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除此分销商品吗?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
distributionGoodsCancel(row.id).then((res) => {
|
||||
Modal.remove();
|
||||
if (res?.success) {
|
||||
Message.success("删除成功");
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
return valid;
|
||||
},
|
||||
removeSelectedGoods(index) {
|
||||
this.selectedGoodsList.splice(index, 1);
|
||||
if (!this.selectedGoodsList.length) {
|
||||
this.modalVisible = false;
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
function edit(row) {
|
||||
editForm.value = {
|
||||
id: row.id,
|
||||
goodsName: row.goodsName,
|
||||
commission: row.commission != null ? String(row.commission) : "1",
|
||||
commissionError: false,
|
||||
};
|
||||
editVisible.value = true;
|
||||
}
|
||||
|
||||
function handleEditSubmit() {
|
||||
const valid = commissionPattern.test(String(editForm.value.commission || "").trim());
|
||||
editForm.value.commissionError = !valid;
|
||||
if (!valid) {
|
||||
Message.error("请输入大于1且小于9999的合法佣金金额");
|
||||
return;
|
||||
}
|
||||
editLoading.value = true;
|
||||
updateDistributionGoodsCommission(editForm.value.id, {
|
||||
commission: Number(editForm.value.commission),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res?.success || res?.message === "success") {
|
||||
Message.success("修改成功");
|
||||
editVisible.value = false;
|
||||
getDataList();
|
||||
}
|
||||
this.modalTitle = `添加分销商品(已选${this.selectedGoodsList.length}项)`;
|
||||
},
|
||||
handleSubmit() {
|
||||
if (!this.selectedGoodsList.length) return;
|
||||
if (!this.validateAllCommission()) {
|
||||
this.$Message.error("请为所有商品填写大于1且小于9999的合法佣金金额");
|
||||
return;
|
||||
}
|
||||
this.submitLoading = true;
|
||||
const items = this.selectedGoodsList.map((item) => ({
|
||||
skuId: item.skuId,
|
||||
commission: Number(item.commission),
|
||||
}));
|
||||
distributionGoodsBatchCheck(items)
|
||||
.then((res) => {
|
||||
if (res?.success || res?.message === "success") {
|
||||
const addedCount = Array.isArray(res?.result) ? res.result.length : 0;
|
||||
if (addedCount > 0) {
|
||||
this.$Message.success(`成功添加${addedCount}个分销商品`);
|
||||
} else {
|
||||
this.$Message.info("所选商品均已是分销商品,已跳过");
|
||||
}
|
||||
}
|
||||
this.modalVisible = false;
|
||||
this.selectedGoodsList = [];
|
||||
this.getDataList();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getDistributionGoods(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
const page = res.result || {};
|
||||
this.data = page.records || [];
|
||||
this.total = page.total || 0;
|
||||
} else {
|
||||
this.data = [];
|
||||
this.total = 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
remove(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除此分销商品吗?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
distributionGoodsCancel(row.id).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res?.success) {
|
||||
this.$Message.success("删除成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
edit(row) {
|
||||
this.editForm = {
|
||||
id: row.id,
|
||||
goodsName: row.goodsName,
|
||||
commission: row.commission != null ? String(row.commission) : "1",
|
||||
commissionError: false,
|
||||
};
|
||||
this.editVisible = true;
|
||||
},
|
||||
handleEditSubmit() {
|
||||
const valid = this.commissionPattern.test(String(this.editForm.commission || "").trim());
|
||||
this.editForm.commissionError = !valid;
|
||||
if (!valid) {
|
||||
this.$Message.error("请输入大于1且小于9999的合法佣金金额");
|
||||
return;
|
||||
}
|
||||
this.editLoading = true;
|
||||
updateDistributionGoodsCommission(this.editForm.id, {
|
||||
commission: Number(this.editForm.commission),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res?.success || res?.message === "success") {
|
||||
this.$Message.success("修改成功");
|
||||
this.editVisible = false;
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.editLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
})
|
||||
.finally(() => {
|
||||
editLoading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -122,110 +122,119 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getDistributionOrder } from "@/api/distribution";
|
||||
import { orderStatusList } from "./dataJson";
|
||||
import vueQr from "vue-qr";
|
||||
import * as filters from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "distributionOrder",
|
||||
components: { vueQr },
|
||||
data() {
|
||||
return {
|
||||
timeRange: [],
|
||||
orderStatusList,
|
||||
distributionId: this.$route.query.id,
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
},
|
||||
data: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route(e) {
|
||||
this.distributionId = e.query.id ? e.query.id : undefined;
|
||||
this.getDataList();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.searchForm.distributionId = this.distributionId;
|
||||
this.loading = true;
|
||||
if (this.timeRange && this.timeRange[0] && this.timeRange[1]) {
|
||||
const startTime = new Date(this.timeRange[0]).getTime();
|
||||
const endTime = new Date(this.timeRange[1]).getTime();
|
||||
this.searchForm.startTime = this.$filters.unixToDate(startTime / 1000);
|
||||
this.searchForm.endTime = this.$filters.unixToDate(endTime / 1000);
|
||||
defineOptions({ name: "distributionOrder" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const timeRange = ref([]);
|
||||
const distributionId = ref(route.query.id);
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
});
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与响应式变量 searchForm 互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
const table = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(e) => {
|
||||
distributionId.value = e.id ? e.id : undefined;
|
||||
getDataList();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePageSize() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
searchForm.distributionId = distributionId.value;
|
||||
loading.value = true;
|
||||
if (timeRange.value && timeRange.value[0] && timeRange.value[1]) {
|
||||
const startTime = new Date(timeRange.value[0]).getTime();
|
||||
const endTime = new Date(timeRange.value[1]).getTime();
|
||||
searchForm.startTime = filters.unixToDate(startTime / 1000);
|
||||
searchForm.endTime = filters.unixToDate(endTime / 1000);
|
||||
} else {
|
||||
searchForm.startTime = null;
|
||||
searchForm.endTime = null;
|
||||
}
|
||||
getDistributionOrder(searchForm)
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
const page = res.result || {};
|
||||
data.value = page.records || [];
|
||||
total.value = page.total || 0;
|
||||
} else {
|
||||
this.searchForm.startTime = null;
|
||||
this.searchForm.endTime = null;
|
||||
data.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
getDistributionOrder(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
const page = res.result || {};
|
||||
this.data = page.records || [];
|
||||
this.total = page.total || 0;
|
||||
} else {
|
||||
this.data = [];
|
||||
this.total = 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
filterStatus(status) {
|
||||
const arr = [
|
||||
{ status: "NO_COMPLETED", title: "未完成" },
|
||||
{ status: "COMPLETE", title: "完成" },
|
||||
{ status: "REFUND", title: "退款" },
|
||||
];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].status === status) {
|
||||
return arr[i].title;
|
||||
}
|
||||
}
|
||||
return "未完成";
|
||||
},
|
||||
filterStatusTagType(status) {
|
||||
const arr = [
|
||||
{ status: "NO_COMPLETED", type: "warning" },
|
||||
{ status: "COMPLETE", type: "success" },
|
||||
{ status: "REFUND", type: "danger" },
|
||||
];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].status === status) {
|
||||
return arr[i].type;
|
||||
}
|
||||
}
|
||||
return "warning";
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function filterStatus(status) {
|
||||
const arr = [
|
||||
{ status: "NO_COMPLETED", title: "未完成" },
|
||||
{ status: "COMPLETE", title: "完成" },
|
||||
{ status: "REFUND", title: "退款" },
|
||||
];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].status === status) {
|
||||
return arr[i].title;
|
||||
}
|
||||
}
|
||||
return "未完成";
|
||||
}
|
||||
|
||||
function filterStatusTagType(status) {
|
||||
const arr = [
|
||||
{ status: "NO_COMPLETED", type: "warning" },
|
||||
{ status: "COMPLETE", type: "success" },
|
||||
{ status: "REFUND", type: "danger" },
|
||||
];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].status === status) {
|
||||
return arr[i].type;
|
||||
}
|
||||
}
|
||||
return "warning";
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -17,20 +17,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Error403",
|
||||
methods: {
|
||||
backPage() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
goHome() {
|
||||
this.$router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({ name: "Error403" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backPage() {
|
||||
router.go(-1);
|
||||
}
|
||||
function goHome() {
|
||||
router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -17,20 +17,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Error404",
|
||||
methods: {
|
||||
backPage() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
goHome() {
|
||||
this.$router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({ name: "Error404" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backPage() {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -17,20 +17,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Error500",
|
||||
methods: {
|
||||
backPage() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
goHome() {
|
||||
this.$router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({ name: "Error500" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backPage() {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -220,7 +220,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {
|
||||
importCardKey,
|
||||
addCardKey,
|
||||
@@ -235,6 +237,7 @@ import {
|
||||
formatCardKeyStatus,
|
||||
CARD_KEY_STATUS_TAG,
|
||||
} from "@/constants/cardKey";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
/**
|
||||
* 商家卡池管理页:导入/新增/作废/导出,列表展示明文卡密(仅商家授权上下文)。
|
||||
@@ -243,332 +246,355 @@ import {
|
||||
* @author Mike
|
||||
* @date 2026-07-31
|
||||
*/
|
||||
export default {
|
||||
name: "cardKeyPool",
|
||||
data() {
|
||||
return {
|
||||
skuId: "",
|
||||
goodsId: "",
|
||||
goodsName: "",
|
||||
skuOptions: [],
|
||||
skuLoading: false,
|
||||
statusTab: "ALL",
|
||||
stats: null,
|
||||
poolLocked: false,
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
templateLoading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
importRange: [],
|
||||
searchForm: {
|
||||
cardNo: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
importModal: false,
|
||||
importResultVisible: false,
|
||||
importResult: {},
|
||||
addVisible: false,
|
||||
addLoading: false,
|
||||
addForm: {
|
||||
cardNo: "",
|
||||
cardSecret: "",
|
||||
},
|
||||
addRules: {
|
||||
cardNo: [{ required: true, message: "卡号不能为空", trigger: "blur" }],
|
||||
cardSecret: [{ required: true, message: "卡密不能为空", trigger: "blur" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusTabsWithCount() {
|
||||
const s = this.stats || {};
|
||||
const withCount = (label, count) =>
|
||||
count != null && this.stats ? `${label}(${count})` : label;
|
||||
const total =
|
||||
(s.unusedCount || 0) +
|
||||
(s.reservedCount || 0) +
|
||||
(s.allocatedCount || 0) +
|
||||
(s.voidedCount || 0);
|
||||
return [
|
||||
{ title: withCount("全部", total), value: "ALL" },
|
||||
{ title: withCount("未使用", s.unusedCount || 0), value: "UNUSED" },
|
||||
{ title: withCount("已预占", s.reservedCount || 0), value: "RESERVED" },
|
||||
{ title: withCount("已分配", s.allocatedCount || 0), value: "ALLOCATED" },
|
||||
{ title: withCount("已作废", s.voidedCount || 0), value: "VOIDED" },
|
||||
];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"$route.query": {
|
||||
handler() {
|
||||
const prevSkuId = this.skuId;
|
||||
this.initFromRoute();
|
||||
if (this.skuId && this.skuId !== prevSkuId) {
|
||||
this.resetPoolViewState();
|
||||
this.loadStats();
|
||||
this.getList();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatStatus: formatCardKeyStatus,
|
||||
statusTag(status) {
|
||||
return CARD_KEY_STATUS_TAG[status] || "info";
|
||||
},
|
||||
goBack() {
|
||||
this.$router.back();
|
||||
},
|
||||
initFromRoute() {
|
||||
const q = this.$route.query;
|
||||
this.skuId = q.skuId || "";
|
||||
this.goodsId = q.goodsId || "";
|
||||
this.goodsName = q.goodsName || "";
|
||||
},
|
||||
formatSkuOptionLabel(sku) {
|
||||
if (!sku) return "";
|
||||
const spec = sku.simpleSpecs || sku.sn || sku.id;
|
||||
const qty =
|
||||
sku.poolStock != null && sku.poolStock !== ""
|
||||
? sku.poolStock
|
||||
: sku.quantity != null
|
||||
? sku.quantity
|
||||
: "—";
|
||||
return `${spec}(库存 ${qty})`;
|
||||
},
|
||||
resetPoolViewState() {
|
||||
this.statusTab = "ALL";
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.cardNo = "";
|
||||
this.importRange = [];
|
||||
this.poolLocked = false;
|
||||
this.stats = null;
|
||||
this.data = [];
|
||||
this.total = 0;
|
||||
},
|
||||
/** 卡池变更后刷新 SKU 选择器中的可售库存(后端 syncSkuStock → quantity) */
|
||||
refreshSkuOptionStock() {
|
||||
if (!this.goodsId) return Promise.resolve();
|
||||
return this.loadSkuOptions();
|
||||
},
|
||||
async loadSkuOptions() {
|
||||
if (!this.goodsId) {
|
||||
this.skuOptions = [];
|
||||
return;
|
||||
}
|
||||
this.skuLoading = true;
|
||||
try {
|
||||
const res = await getQueryGoodsIdGoodsList(this.goodsId);
|
||||
if (res.success) {
|
||||
this.skuOptions = (res.result || []).filter((s) => !s.deleteFlag);
|
||||
}
|
||||
} catch {
|
||||
this.skuOptions = [];
|
||||
} finally {
|
||||
this.skuLoading = false;
|
||||
}
|
||||
},
|
||||
onSkuChange(newSkuId) {
|
||||
if (!newSkuId) return;
|
||||
this.skuId = newSkuId;
|
||||
this.resetPoolViewState();
|
||||
this.$router.replace({
|
||||
path: this.$route.path,
|
||||
query: {
|
||||
...this.$route.query,
|
||||
skuId: newSkuId,
|
||||
goodsId: this.goodsId,
|
||||
goodsName: this.goodsName,
|
||||
},
|
||||
});
|
||||
this.loadStats();
|
||||
this.getList();
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.statusTab = tab.paneName;
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getList();
|
||||
},
|
||||
buildListParams() {
|
||||
const params = {
|
||||
skuId: this.skuId,
|
||||
pageNumber: this.searchForm.pageNumber,
|
||||
pageSize: this.searchForm.pageSize,
|
||||
};
|
||||
if (this.goodsId) params.goodsId = this.goodsId;
|
||||
if (this.searchForm.cardNo) params.cardNo = this.searchForm.cardNo;
|
||||
if (this.statusTab && this.statusTab !== "ALL") {
|
||||
params.status = this.statusTab;
|
||||
}
|
||||
if (this.importRange && this.importRange.length === 2) {
|
||||
params.createTimeStart = this.importRange[0];
|
||||
params.createTimeEnd = this.importRange[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
loadStats() {
|
||||
if (!this.skuId) return;
|
||||
getCardKeyStats(this.skuId).then((res) => {
|
||||
if (res.success) {
|
||||
this.stats = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
getList() {
|
||||
if (!this.skuId) return;
|
||||
this.loading = true;
|
||||
getCardKeyList(this.buildListParams())
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
this.poolLocked = false;
|
||||
} else {
|
||||
this.checkPoolLocked(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 审核拒绝 / SKU 删除 / 关店时禁用卡池操作(S-06 / S-07 / EC-21~23) */
|
||||
checkPoolLocked(res) {
|
||||
const lockedCodes = [
|
||||
"CARD_KEY_GOODS_AUTH_REFUSE",
|
||||
"CARD_KEY_SKU_DELETED",
|
||||
"CARD_KEY_STORE_CLOSED",
|
||||
];
|
||||
if (res.code && lockedCodes.includes(String(res.code))) {
|
||||
this.poolLocked = true;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm.cardNo = "";
|
||||
this.importRange = [];
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getList();
|
||||
},
|
||||
onPageSizeChange() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getList();
|
||||
},
|
||||
handleImportUpload(file) {
|
||||
if (!/\.xlsx$/i.test(file.name)) {
|
||||
this.$Message.error("请上传 .xlsx 文件");
|
||||
return false;
|
||||
}
|
||||
importCardKey(this.skuId, file).then((res) => {
|
||||
if (res.success) {
|
||||
this.importModal = false;
|
||||
this.importResult = res.result || {};
|
||||
this.importResultVisible = true;
|
||||
this.loadStats();
|
||||
this.getList();
|
||||
this.refreshSkuOptionStock();
|
||||
} else {
|
||||
this.checkPoolLocked(res);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
},
|
||||
handleDownloadTemplate() {
|
||||
this.templateLoading = true;
|
||||
downloadImportTemplate()
|
||||
.then(() => {
|
||||
this.$Message.success("模板下载成功");
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.templateLoading = false;
|
||||
});
|
||||
},
|
||||
openAddDialog() {
|
||||
this.addForm = { cardNo: "", cardSecret: "" };
|
||||
this.addVisible = true;
|
||||
},
|
||||
submitAdd() {
|
||||
this.$refs.addFormRef.validate((valid) => {
|
||||
if (!valid) return;
|
||||
this.addLoading = true;
|
||||
addCardKey({
|
||||
skuId: this.skuId,
|
||||
cardNo: this.addForm.cardNo.trim(),
|
||||
cardSecret: this.addForm.cardSecret.trim(),
|
||||
})
|
||||
.then((res) => {
|
||||
this.addLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("新增成功");
|
||||
this.addVisible = false;
|
||||
this.loadStats();
|
||||
this.getList();
|
||||
this.refreshSkuOptionStock();
|
||||
} else {
|
||||
this.checkPoolLocked(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.addLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
handleVoid(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认作废",
|
||||
content: `确定作废卡号「${row.cardNo}」?作废后不可恢复。`,
|
||||
onOk: () => {
|
||||
voidCardKey(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("作废成功");
|
||||
this.loadStats();
|
||||
this.getList();
|
||||
this.refreshSkuOptionStock();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleExport() {
|
||||
this.exportLoading = true;
|
||||
const params = { ...this.buildListParams() };
|
||||
delete params.pageNumber;
|
||||
delete params.pageSize;
|
||||
exportCardKey(params, this.skuId)
|
||||
.then(() => {
|
||||
this.$Message.success("导出成功");
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.exportLoading = false;
|
||||
});
|
||||
},
|
||||
async init() {
|
||||
this.initFromRoute();
|
||||
if (this.goodsId) {
|
||||
await this.loadSkuOptions();
|
||||
}
|
||||
if (!this.skuId && this.skuOptions.length) {
|
||||
this.onSkuChange(this.skuOptions[0].id);
|
||||
return;
|
||||
}
|
||||
if (this.skuId) {
|
||||
this.loadStats();
|
||||
this.getList();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
defineOptions({ name: "cardKeyPool" });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const skuId = ref("");
|
||||
const goodsId = ref("");
|
||||
const goodsName = ref("");
|
||||
const skuOptions = ref([]);
|
||||
const skuLoading = ref(false);
|
||||
const statusTab = ref("ALL");
|
||||
const stats = ref(null);
|
||||
const poolLocked = ref(false);
|
||||
const loading = ref(false);
|
||||
const exportLoading = ref(false);
|
||||
const templateLoading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const importRange = ref([]);
|
||||
const searchForm = reactive({
|
||||
cardNo: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
const importModal = ref(false);
|
||||
const importResultVisible = ref(false);
|
||||
const importResult = ref({});
|
||||
const addVisible = ref(false);
|
||||
const addLoading = ref(false);
|
||||
const addForm = ref({
|
||||
cardNo: "",
|
||||
cardSecret: "",
|
||||
});
|
||||
const addRules = {
|
||||
cardNo: [{ required: true, message: "卡号不能为空", trigger: "blur" }],
|
||||
cardSecret: [{ required: true, message: "卡密不能为空", trigger: "blur" }],
|
||||
};
|
||||
|
||||
const searchFormRef = ref(null);
|
||||
const addFormRef = ref(null);
|
||||
|
||||
const statusTabsWithCount = computed(() => {
|
||||
const s = stats.value || {};
|
||||
const withCount = (label, count) =>
|
||||
count != null && stats.value ? `${label}(${count})` : label;
|
||||
const totalCount =
|
||||
(s.unusedCount || 0) +
|
||||
(s.reservedCount || 0) +
|
||||
(s.allocatedCount || 0) +
|
||||
(s.voidedCount || 0);
|
||||
return [
|
||||
{ title: withCount("全部", totalCount), value: "ALL" },
|
||||
{ title: withCount("未使用", s.unusedCount || 0), value: "UNUSED" },
|
||||
{ title: withCount("已预占", s.reservedCount || 0), value: "RESERVED" },
|
||||
{ title: withCount("已分配", s.allocatedCount || 0), value: "ALLOCATED" },
|
||||
{ title: withCount("已作废", s.voidedCount || 0), value: "VOIDED" },
|
||||
];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
() => {
|
||||
const prevSkuId = skuId.value;
|
||||
initFromRoute();
|
||||
if (skuId.value && skuId.value !== prevSkuId) {
|
||||
resetPoolViewState();
|
||||
loadStats();
|
||||
getList();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const formatStatus = formatCardKeyStatus;
|
||||
|
||||
function statusTag(status) {
|
||||
return CARD_KEY_STATUS_TAG[status] || "info";
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
function initFromRoute() {
|
||||
const q = route.query;
|
||||
skuId.value = q.skuId || "";
|
||||
goodsId.value = q.goodsId || "";
|
||||
goodsName.value = q.goodsName || "";
|
||||
}
|
||||
|
||||
function formatSkuOptionLabel(sku) {
|
||||
if (!sku) return "";
|
||||
const spec = sku.simpleSpecs || sku.sn || sku.id;
|
||||
const qty =
|
||||
sku.poolStock != null && sku.poolStock !== ""
|
||||
? sku.poolStock
|
||||
: sku.quantity != null
|
||||
? sku.quantity
|
||||
: "—";
|
||||
return `${spec}(库存 ${qty})`;
|
||||
}
|
||||
|
||||
function resetPoolViewState() {
|
||||
statusTab.value = "ALL";
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.cardNo = "";
|
||||
importRange.value = [];
|
||||
poolLocked.value = false;
|
||||
stats.value = null;
|
||||
data.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
|
||||
/** 卡池变更后刷新 SKU 选择器中的可售库存(后端 syncSkuStock → quantity) */
|
||||
function refreshSkuOptionStock() {
|
||||
if (!goodsId.value) return Promise.resolve();
|
||||
return loadSkuOptions();
|
||||
}
|
||||
|
||||
async function loadSkuOptions() {
|
||||
if (!goodsId.value) {
|
||||
skuOptions.value = [];
|
||||
return;
|
||||
}
|
||||
skuLoading.value = true;
|
||||
try {
|
||||
const res = await getQueryGoodsIdGoodsList(goodsId.value);
|
||||
if (res.success) {
|
||||
skuOptions.value = (res.result || []).filter((s) => !s.deleteFlag);
|
||||
}
|
||||
} catch {
|
||||
skuOptions.value = [];
|
||||
} finally {
|
||||
skuLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onSkuChange(newSkuId) {
|
||||
if (!newSkuId) return;
|
||||
skuId.value = newSkuId;
|
||||
resetPoolViewState();
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
skuId: newSkuId,
|
||||
goodsId: goodsId.value,
|
||||
goodsName: goodsName.value,
|
||||
},
|
||||
});
|
||||
loadStats();
|
||||
getList();
|
||||
}
|
||||
|
||||
function onStatusTabClick(tab) {
|
||||
statusTab.value = tab.paneName;
|
||||
searchForm.pageNumber = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
function buildListParams() {
|
||||
const params = {
|
||||
skuId: skuId.value,
|
||||
pageNumber: searchForm.pageNumber,
|
||||
pageSize: searchForm.pageSize,
|
||||
};
|
||||
if (goodsId.value) params.goodsId = goodsId.value;
|
||||
if (searchForm.cardNo) params.cardNo = searchForm.cardNo;
|
||||
if (statusTab.value && statusTab.value !== "ALL") {
|
||||
params.status = statusTab.value;
|
||||
}
|
||||
if (importRange.value && importRange.value.length === 2) {
|
||||
params.createTimeStart = importRange.value[0];
|
||||
params.createTimeEnd = importRange.value[1];
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function loadStats() {
|
||||
if (!skuId.value) return;
|
||||
getCardKeyStats(skuId.value).then((res) => {
|
||||
if (res.success) {
|
||||
stats.value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getList() {
|
||||
if (!skuId.value) return;
|
||||
loading.value = true;
|
||||
getCardKeyList(buildListParams())
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
poolLocked.value = false;
|
||||
} else {
|
||||
checkPoolLocked(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 审核拒绝 / SKU 删除 / 关店时禁用卡池操作(S-06 / S-07 / EC-21~23) */
|
||||
function checkPoolLocked(res) {
|
||||
const lockedCodes = [
|
||||
"CARD_KEY_GOODS_AUTH_REFUSE",
|
||||
"CARD_KEY_SKU_DELETED",
|
||||
"CARD_KEY_STORE_CLOSED",
|
||||
];
|
||||
if (res.code && lockedCodes.includes(String(res.code))) {
|
||||
poolLocked.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.cardNo = "";
|
||||
importRange.value = [];
|
||||
searchForm.pageNumber = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
function onPageSizeChange() {
|
||||
searchForm.pageNumber = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
function handleImportUpload(file) {
|
||||
if (!/\.xlsx$/i.test(file.name)) {
|
||||
Message.error("请上传 .xlsx 文件");
|
||||
return false;
|
||||
}
|
||||
importCardKey(skuId.value, file).then((res) => {
|
||||
if (res.success) {
|
||||
importModal.value = false;
|
||||
importResult.value = res.result || {};
|
||||
importResultVisible.value = true;
|
||||
loadStats();
|
||||
getList();
|
||||
refreshSkuOptionStock();
|
||||
} else {
|
||||
checkPoolLocked(res);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleDownloadTemplate() {
|
||||
templateLoading.value = true;
|
||||
downloadImportTemplate()
|
||||
.then(() => {
|
||||
Message.success("模板下载成功");
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
templateLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function openAddDialog() {
|
||||
addForm.value = { cardNo: "", cardSecret: "" };
|
||||
addVisible.value = true;
|
||||
}
|
||||
|
||||
function submitAdd() {
|
||||
addFormRef.value.validate((valid) => {
|
||||
if (!valid) return;
|
||||
addLoading.value = true;
|
||||
addCardKey({
|
||||
skuId: skuId.value,
|
||||
cardNo: addForm.value.cardNo.trim(),
|
||||
cardSecret: addForm.value.cardSecret.trim(),
|
||||
})
|
||||
.then((res) => {
|
||||
addLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("新增成功");
|
||||
addVisible.value = false;
|
||||
loadStats();
|
||||
getList();
|
||||
refreshSkuOptionStock();
|
||||
} else {
|
||||
checkPoolLocked(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
addLoading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleVoid(row) {
|
||||
Modal.confirm({
|
||||
title: "确认作废",
|
||||
content: `确定作废卡号「${row.cardNo}」?作废后不可恢复。`,
|
||||
onOk: () => {
|
||||
voidCardKey(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("作废成功");
|
||||
loadStats();
|
||||
getList();
|
||||
refreshSkuOptionStock();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
exportLoading.value = true;
|
||||
const params = { ...buildListParams() };
|
||||
delete params.pageNumber;
|
||||
delete params.pageSize;
|
||||
exportCardKey(params, skuId.value)
|
||||
.then(() => {
|
||||
Message.success("导出成功");
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
exportLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
initFromRoute();
|
||||
if (goodsId.value) {
|
||||
await loadSkuOptions();
|
||||
}
|
||||
if (!skuId.value && skuOptions.value.length) {
|
||||
onSkuChange(skuOptions.value[0].id);
|
||||
return;
|
||||
}
|
||||
if (skuId.value) {
|
||||
loadStats();
|
||||
getList();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form ref="form" :model="formAdd" label-width="120px" :rules="formValidate">
|
||||
<el-form ref="formRef" :model="formAdd" label-width="120px" :rules="formValidate">
|
||||
<el-form-item v-if="showParent" label="上级分类" prop="parentId">
|
||||
{{ parentTitle }}
|
||||
<el-input v-model="formAdd.parentId" style="display: none" />
|
||||
@@ -65,7 +65,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import {
|
||||
addShopGoodsLabel,
|
||||
delCategdelShopGoodsLabel,
|
||||
@@ -74,170 +75,166 @@ import {
|
||||
} from "@/api/goods";
|
||||
import uploadPicInput from "@/views/my-components/lili/upload-pic-input";
|
||||
import { regular } from "@/utils";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "goods-category",
|
||||
components: {
|
||||
uploadPicInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
submitLoading: false,
|
||||
categoryList: [],
|
||||
loading: false,
|
||||
modalType: 0,
|
||||
modalVisible: false,
|
||||
modalTitle: "",
|
||||
showParent: false,
|
||||
parentTitle: "",
|
||||
formAdd: this.createDefaultForm(),
|
||||
formValidate: {
|
||||
name: [regular.REQUIRED, regular.VARCHAR20],
|
||||
sortOrder: [regular.REQUIRED, regular.INTEGER],
|
||||
},
|
||||
tableData: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
createDefaultForm() {
|
||||
return {
|
||||
parentId: "",
|
||||
name: "",
|
||||
image: "",
|
||||
sortOrder: 0,
|
||||
level: 0,
|
||||
};
|
||||
},
|
||||
normalizeCategoryTree(list, parentId = 0, level = 0) {
|
||||
if (!Array.isArray(list) || list.length === 0) return;
|
||||
list.forEach((item) => {
|
||||
if (!item || typeof item !== "object") return;
|
||||
item.name = item.name || item.labelName;
|
||||
item.parentId = item.parentId ?? parentId;
|
||||
item.level = item.level ?? level;
|
||||
if (Array.isArray(item.children) && item.children.length) {
|
||||
this.normalizeCategoryTree(item.children, item.id, Number(item.level) + 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.getAllList();
|
||||
},
|
||||
addChildren(v) {
|
||||
this.modalType = 0;
|
||||
this.modalTitle = "添加子分类";
|
||||
this.parentTitle = v.name;
|
||||
this.formAdd = this.createDefaultForm();
|
||||
this.formAdd.level = Number(v.level) + 1;
|
||||
this.showParent = true;
|
||||
delete this.formAdd.id;
|
||||
this.formAdd.parentId = v.id;
|
||||
this.modalVisible = true;
|
||||
},
|
||||
edit(v) {
|
||||
this.modalType = 1;
|
||||
this.modalTitle = "编辑";
|
||||
this.formAdd.id = v.id;
|
||||
this.formAdd.name = v.name;
|
||||
this.formAdd.level = v.level;
|
||||
this.formAdd.parentId = v.parentId ?? 0;
|
||||
this.formAdd.sortOrder = v.sortOrder;
|
||||
this.formAdd.image = v.image;
|
||||
this.showParent = false;
|
||||
this.modalVisible = true;
|
||||
},
|
||||
addParent() {
|
||||
this.modalType = 0;
|
||||
this.modalTitle = "添加一级分类";
|
||||
this.parentTitle = "顶级分类";
|
||||
this.showParent = true;
|
||||
this.formAdd = this.createDefaultForm();
|
||||
delete this.formAdd.id;
|
||||
this.formAdd.parentId = 0;
|
||||
this.formAdd.level = 0;
|
||||
this.modalVisible = true;
|
||||
},
|
||||
Submit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return;
|
||||
this.submitLoading = true;
|
||||
const params = this.buildLabelParams(this.formAdd);
|
||||
if (this.modalType === 0) {
|
||||
delete params.id;
|
||||
addShopGoodsLabel(params).then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("添加成功");
|
||||
this.getAllList();
|
||||
this.modalVisible = false;
|
||||
this.$refs.form.resetFields();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
editShopGoodsLabel(params).then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("修改成功");
|
||||
this.getAllList();
|
||||
this.modalVisible = false;
|
||||
this.$refs.form.resetFields();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
remove(v) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除 " + v.name + " ?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
delCategdelShopGoodsLabel(v.id).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.getAllList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
getAllList() {
|
||||
this.loading = true;
|
||||
getShopGoodsLabelList()
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.normalizeCategoryTree(res.result);
|
||||
this.categoryList = JSON.parse(JSON.stringify(res.result));
|
||||
this.tableData = JSON.parse(JSON.stringify(res.result));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
buildLabelParams(form) {
|
||||
const params = {
|
||||
id: form.id,
|
||||
parentId: form.parentId,
|
||||
labelName: form.name,
|
||||
image: form.image,
|
||||
sortOrder: form.sortOrder,
|
||||
level: form.level,
|
||||
};
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === undefined || params[key] === "") {
|
||||
delete params[key];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
defineOptions({ name: "goods-category" });
|
||||
|
||||
const submitLoading = ref(false);
|
||||
const categoryList = ref([]);
|
||||
const loading = ref(false);
|
||||
const modalType = ref(0);
|
||||
const modalVisible = ref(false);
|
||||
const modalTitle = ref("");
|
||||
const showParent = ref(false);
|
||||
const parentTitle = ref("");
|
||||
const formAdd = ref(createDefaultForm());
|
||||
const formValidate = {
|
||||
name: [regular.REQUIRED, regular.VARCHAR20],
|
||||
sortOrder: [regular.REQUIRED, regular.INTEGER],
|
||||
};
|
||||
const tableData = ref([]);
|
||||
|
||||
// formRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const formRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function createDefaultForm() {
|
||||
return {
|
||||
parentId: "",
|
||||
name: "",
|
||||
image: "",
|
||||
sortOrder: 0,
|
||||
level: 0,
|
||||
};
|
||||
}
|
||||
function normalizeCategoryTree(list, parentId = 0, level = 0) {
|
||||
if (!Array.isArray(list) || list.length === 0) return;
|
||||
list.forEach((item) => {
|
||||
if (!item || typeof item !== "object") return;
|
||||
item.name = item.name || item.labelName;
|
||||
item.parentId = item.parentId ?? parentId;
|
||||
item.level = item.level ?? level;
|
||||
if (Array.isArray(item.children) && item.children.length) {
|
||||
normalizeCategoryTree(item.children, item.id, Number(item.level) + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
getAllList();
|
||||
}
|
||||
function addChildren(v) {
|
||||
modalType.value = 0;
|
||||
modalTitle.value = "添加子分类";
|
||||
parentTitle.value = v.name;
|
||||
formAdd.value = createDefaultForm();
|
||||
formAdd.value.level = Number(v.level) + 1;
|
||||
showParent.value = true;
|
||||
delete formAdd.value.id;
|
||||
formAdd.value.parentId = v.id;
|
||||
modalVisible.value = true;
|
||||
}
|
||||
function edit(v) {
|
||||
modalType.value = 1;
|
||||
modalTitle.value = "编辑";
|
||||
formAdd.value.id = v.id;
|
||||
formAdd.value.name = v.name;
|
||||
formAdd.value.level = v.level;
|
||||
formAdd.value.parentId = v.parentId ?? 0;
|
||||
formAdd.value.sortOrder = v.sortOrder;
|
||||
formAdd.value.image = v.image;
|
||||
showParent.value = false;
|
||||
modalVisible.value = true;
|
||||
}
|
||||
function addParent() {
|
||||
modalType.value = 0;
|
||||
modalTitle.value = "添加一级分类";
|
||||
parentTitle.value = "顶级分类";
|
||||
showParent.value = true;
|
||||
formAdd.value = createDefaultForm();
|
||||
delete formAdd.value.id;
|
||||
formAdd.value.parentId = 0;
|
||||
formAdd.value.level = 0;
|
||||
modalVisible.value = true;
|
||||
}
|
||||
function Submit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (!valid) return;
|
||||
submitLoading.value = true;
|
||||
const params = buildLabelParams(formAdd.value);
|
||||
if (modalType.value === 0) {
|
||||
delete params.id;
|
||||
addShopGoodsLabel(params).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("添加成功");
|
||||
getAllList();
|
||||
modalVisible.value = false;
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
editShopGoodsLabel(params).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("修改成功");
|
||||
getAllList();
|
||||
modalVisible.value = false;
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function remove(v) {
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除 " + v.name + " ?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
delCategdelShopGoodsLabel(v.id).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("操作成功");
|
||||
getAllList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
function getAllList() {
|
||||
loading.value = true;
|
||||
getShopGoodsLabelList()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
normalizeCategoryTree(res.result);
|
||||
categoryList.value = JSON.parse(JSON.stringify(res.result));
|
||||
tableData.value = JSON.parse(JSON.stringify(res.result));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function buildLabelParams(form) {
|
||||
const params = {
|
||||
id: form.id,
|
||||
parentId: form.parentId,
|
||||
labelName: form.name,
|
||||
image: form.image,
|
||||
sortOrder: form.sortOrder,
|
||||
level: form.level,
|
||||
};
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === undefined || params[key] === "") {
|
||||
delete params[key];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<div @keyup.enter="handleSearch">
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
@@ -39,7 +39,7 @@
|
||||
<el-button type="primary" class="export" @click="exportStock">批量导出</el-button>
|
||||
<el-button class="export" @click="openImportStock">批量导入</el-button>
|
||||
</div>
|
||||
<el-table ref="table" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
|
||||
<el-table ref="tableRef" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
|
||||
<el-table-column label="商品信息" min-width="400">
|
||||
<template #default="{ row }">
|
||||
<div style="margin-top: 5px; height: 90px; display: flex">
|
||||
@@ -115,139 +115,139 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import {
|
||||
getGoodsCategoryAll,
|
||||
getGoodsSkuListDataSeller,
|
||||
importStockExcel,
|
||||
queryExportStock,
|
||||
} from "@/api/goods";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "alertQuantity",
|
||||
data() {
|
||||
return {
|
||||
importModal: false,
|
||||
spinShow: false,
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
},
|
||||
stockAllData: [],
|
||||
total: 0,
|
||||
categoryList: [],
|
||||
category: [],
|
||||
file: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
this.deepGroup();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.categoryPath = this.category?.length ? this.category.join(",") : null;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
};
|
||||
this.category = [];
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.stockAllData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
deepGroup() {
|
||||
getGoodsCategoryAll().then((res) => {
|
||||
if (res.success) {
|
||||
this.categoryList = res.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: (item.children || []).map((child) => ({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: (child.children || []).map((grandson) => ({
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
async exportStock() {
|
||||
let randomNumber = "";
|
||||
for (let i = 0; i < 10; i++) {
|
||||
randomNumber += Math.floor(Math.random() * 10);
|
||||
defineOptions({ name: "alertQuantity" });
|
||||
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
|
||||
const importModal = ref(false);
|
||||
const spinShow = ref(false);
|
||||
const loading = ref(true);
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
});
|
||||
const stockAllData = ref([]);
|
||||
const total = ref(0);
|
||||
const categoryList = ref([]);
|
||||
const category = ref([]);
|
||||
const file = ref(null);
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
deepGroup();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
searchForm.value.pageSize = v;
|
||||
getDataList();
|
||||
}
|
||||
function changePage(v) {
|
||||
searchForm.value.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.categoryPath = category.value?.length ? category.value.join(",") : null;
|
||||
getDataList();
|
||||
}
|
||||
function handleReset() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
};
|
||||
category.value = [];
|
||||
getDataList();
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getGoodsSkuListDataSeller(searchForm.value).then((res) => {
|
||||
if (res.success) {
|
||||
stockAllData.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function deepGroup() {
|
||||
getGoodsCategoryAll().then((res) => {
|
||||
if (res.success) {
|
||||
categoryList.value = res.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: (item.children || []).map((child) => ({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: (child.children || []).map((grandson) => ({
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
async function exportStock() {
|
||||
let randomNumber = "";
|
||||
for (let i = 0; i < 10; i++) {
|
||||
randomNumber += Math.floor(Math.random() * 10);
|
||||
}
|
||||
queryExportStock(searchForm.value)
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], { type: "application/vnd.ms-excel;charset=utf-8" });
|
||||
if ("download" in document.createElement("a")) {
|
||||
const link = document.createElement("a");
|
||||
link.download = randomNumber + ".xlsx";
|
||||
link.style.display = "none";
|
||||
link.href = URL.createObjectURL(blob);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, randomNumber + ".xlsx");
|
||||
}
|
||||
queryExportStock(this.searchForm)
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], { type: "application/vnd.ms-excel;charset=utf-8" });
|
||||
if ("download" in document.createElement("a")) {
|
||||
const link = document.createElement("a");
|
||||
link.download = randomNumber + ".xlsx";
|
||||
link.style.display = "none";
|
||||
link.href = URL.createObjectURL(blob);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, randomNumber + ".xlsx");
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
},
|
||||
openImportStock() {
|
||||
this.importModal = true;
|
||||
},
|
||||
handleUpload(file) {
|
||||
this.file = file;
|
||||
this.upload();
|
||||
return false;
|
||||
},
|
||||
upload() {
|
||||
const fd = new FormData();
|
||||
fd.append("files", this.file);
|
||||
this.spinShow = true;
|
||||
importStockExcel(fd).then((res) => {
|
||||
this.spinShow = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("导入成功");
|
||||
this.importModal = false;
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
}
|
||||
function openImportStock() {
|
||||
importModal.value = true;
|
||||
}
|
||||
function handleUpload(f) {
|
||||
file.value = f;
|
||||
upload();
|
||||
return false;
|
||||
}
|
||||
function upload() {
|
||||
const fd = new FormData();
|
||||
fd.append("files", file.value);
|
||||
spinShow.value = true;
|
||||
importStockExcel(fd).then((res) => {
|
||||
spinShow.value = false;
|
||||
if (res.success) {
|
||||
Message.success("导入成功");
|
||||
importModal.value = false;
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<div @keyup.enter="handleSearch">
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
@@ -120,7 +120,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
getGoodsCategoryAll,
|
||||
getGoodsListDataByStockSeller,
|
||||
@@ -128,6 +130,9 @@ import {
|
||||
updateGoodsAlertStocks,
|
||||
updateGoodsSkuStocks,
|
||||
} from "@/api/goods";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
defineOptions({ name: "alertQuantityWarn" });
|
||||
|
||||
const ROUTE_TAB_MAP = {
|
||||
alertQuantityWarn: "warnList",
|
||||
@@ -135,154 +140,165 @@ const ROUTE_TAB_MAP = {
|
||||
alertQuantityWarnSetting: "warnSetting",
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "alertQuantityWarn",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
updateStockModalVisible: false,
|
||||
activeTab: "warnList",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
},
|
||||
checkFlag: false,
|
||||
stockList: [],
|
||||
warnData: [],
|
||||
skuAllData: [],
|
||||
total: 0,
|
||||
categoryList: [],
|
||||
category: [],
|
||||
selectedSku: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
"$route.name"(name) {
|
||||
this.applyRouteTab(name);
|
||||
this.handleReset();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.applyRouteTab();
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
applyRouteTab(name = this.$route.name) {
|
||||
this.activeTab = ROUTE_TAB_MAP[name] || "warnList";
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
this.deepGroup();
|
||||
},
|
||||
openUpdataStockModal(row) {
|
||||
this.stockList = [];
|
||||
this.selectedSku = JSON.parse(JSON.stringify(row));
|
||||
this.stockList.push(this.selectedSku);
|
||||
this.updateStockModalVisible = true;
|
||||
},
|
||||
updateStock() {
|
||||
const updateStockList = this.stockList.map((i) => ({
|
||||
skuId: i.id,
|
||||
quantity: i.quantity,
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(true);
|
||||
const updateStockModalVisible = ref(false);
|
||||
const activeTab = ref("warnList");
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
});
|
||||
const checkFlag = ref(false);
|
||||
const stockList = ref([]);
|
||||
const warnData = ref([]);
|
||||
const skuAllData = ref([]);
|
||||
const total = ref(0);
|
||||
const categoryList = ref([]);
|
||||
const category = ref([]);
|
||||
const selectedSku = ref({});
|
||||
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(name) => {
|
||||
applyRouteTab(name);
|
||||
handleReset();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
applyRouteTab();
|
||||
init();
|
||||
});
|
||||
|
||||
function applyRouteTab(name = route.name) {
|
||||
activeTab.value = ROUTE_TAB_MAP[name] || "warnList";
|
||||
}
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
deepGroup();
|
||||
}
|
||||
|
||||
function openUpdataStockModal(row) {
|
||||
stockList.value = [];
|
||||
selectedSku.value = JSON.parse(JSON.stringify(row));
|
||||
stockList.value.push(selectedSku.value);
|
||||
updateStockModalVisible.value = true;
|
||||
}
|
||||
|
||||
function updateStock() {
|
||||
const updateStockList = stockList.value.map((i) => ({
|
||||
skuId: i.id,
|
||||
quantity: i.quantity,
|
||||
}));
|
||||
updateGoodsSkuStocks(updateStockList).then((res) => {
|
||||
if (res.success) {
|
||||
updateStockModalVisible.value = false;
|
||||
Message.success("更新库存成功");
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateWarnStock(row) {
|
||||
if (checkFlag.value) {
|
||||
updateGoodsAlertStocks({ skuId: row.id, alertQuantity: row.alertQuantity }).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("更新成功");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkVal(row) {
|
||||
if (
|
||||
!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row.alertQuantity) ||
|
||||
parseInt(row.alertQuantity) < 0 ||
|
||||
parseInt(row.alertQuantity) > 99999999
|
||||
) {
|
||||
Message.error("请输入0~99999999之间的数字值");
|
||||
row.alertQuantity = 0;
|
||||
checkFlag.value = false;
|
||||
return;
|
||||
}
|
||||
checkFlag.value = true;
|
||||
}
|
||||
|
||||
function switchTabs() {
|
||||
handleReset();
|
||||
}
|
||||
|
||||
function changePageSize(v) {
|
||||
searchForm.value.pageSize = v;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePage(v) {
|
||||
searchForm.value.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.categoryPath = category.value?.length ? category.value.join(",") : null;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
};
|
||||
category.value = [];
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
if (activeTab.value === "warnList") {
|
||||
getGoodsListDataByStockSeller(searchForm.value).then((res) => {
|
||||
if (res.success) {
|
||||
warnData.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
getGoodsSkuListDataSeller(searchForm.value).then((res) => {
|
||||
if (res.success) {
|
||||
skuAllData.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deepGroup() {
|
||||
getGoodsCategoryAll().then((res) => {
|
||||
if (res.success) {
|
||||
categoryList.value = res.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: (item.children || []).map((child) => ({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: (child.children || []).map((grandson) => ({
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
updateGoodsSkuStocks(updateStockList).then((res) => {
|
||||
if (res.success) {
|
||||
this.updateStockModalVisible = false;
|
||||
this.$Message.success("更新库存成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
updateWarnStock(row) {
|
||||
if (this.checkFlag) {
|
||||
updateGoodsAlertStocks({ skuId: row.id, alertQuantity: row.alertQuantity }).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("更新成功");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
checkVal(row) {
|
||||
if (
|
||||
!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row.alertQuantity) ||
|
||||
parseInt(row.alertQuantity) < 0 ||
|
||||
parseInt(row.alertQuantity) > 99999999
|
||||
) {
|
||||
this.$Message.error("请输入0~99999999之间的数字值");
|
||||
row.alertQuantity = 0;
|
||||
this.checkFlag = false;
|
||||
return;
|
||||
}
|
||||
this.checkFlag = true;
|
||||
},
|
||||
switchTabs() {
|
||||
this.handleReset();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.categoryPath = this.category?.length ? this.category.join(",") : null;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
};
|
||||
this.category = [];
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
if (this.activeTab === "warnList") {
|
||||
getGoodsListDataByStockSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.warnData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.skuAllData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
deepGroup() {
|
||||
getGoodsCategoryAll().then((res) => {
|
||||
if (res.success) {
|
||||
this.categoryList = res.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: (item.children || []).map((child) => ({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: (child.children || []).map((grandson) => ({
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -292,8 +292,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getRequest } from "@/libs/axios";
|
||||
import { Message } from "@/utils/message";
|
||||
import {
|
||||
SERVICE_MODE,
|
||||
BOOKABLE_DATE_MODE,
|
||||
@@ -307,6 +310,35 @@ import {
|
||||
slotKey,
|
||||
} from "@/constants/appointment";
|
||||
|
||||
defineOptions({ name: "AppointmentPublishPanel" });
|
||||
|
||||
const props = defineProps({
|
||||
settings: { type: Object, default: () => defaultAppointmentSettings() },
|
||||
division: { type: Object, default: () => defaultSlotDivision() },
|
||||
skuCapacities: { type: Object, default: () => ({}) },
|
||||
skuInfo: { type: Array, default: () => [] },
|
||||
divisionTarget: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["update:settings", "update:division", "update:skuCapacities"]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const localSettings = ref(defaultAppointmentSettings());
|
||||
const localDivision = ref(defaultSlotDivision());
|
||||
const enabledSlotKeys = ref([]);
|
||||
const systemFormOptions = ref([]);
|
||||
const capacityDialogVisible = ref(false);
|
||||
const capacityDraft = ref({});
|
||||
const activeSkuRowKey = ref("");
|
||||
const syncing = ref(false);
|
||||
const divisionReady = ref(false);
|
||||
|
||||
// 历史遗留:原代码读取未声明的 this.part,恒为 undefined,行为原样保留
|
||||
const part = undefined;
|
||||
|
||||
function parseMinutes(timeStr) {
|
||||
if (!timeStr) return 0;
|
||||
const [h, m] = timeStr.split(":").map(Number);
|
||||
@@ -319,402 +351,391 @@ function formatMinutes(total) {
|
||||
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "AppointmentPublishPanel",
|
||||
props: {
|
||||
settings: { type: Object, default: () => defaultAppointmentSettings() },
|
||||
division: { type: Object, default: () => defaultSlotDivision() },
|
||||
skuCapacities: { type: Object, default: () => ({}) },
|
||||
skuInfo: { type: Array, default: () => [] },
|
||||
divisionTarget: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
const bookableDateHint = computed(() => {
|
||||
return "设置预约服务的可预约日期。例如:周六、周天不选中,则在可预约日期中的周一到周五可用。";
|
||||
});
|
||||
const customDateRange = computed({
|
||||
get() {
|
||||
const { bookableCustomStartDate, bookableCustomEndDate } = localSettings.value;
|
||||
if (bookableCustomStartDate && bookableCustomEndDate) {
|
||||
return [bookableCustomStartDate, bookableCustomEndDate];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
emits: ["update:settings", "update:division", "update:skuCapacities"],
|
||||
data() {
|
||||
return {
|
||||
SERVICE_MODE,
|
||||
BOOKABLE_DATE_MODE,
|
||||
ADVANCE_MODE,
|
||||
CANCEL_MODE,
|
||||
FORM_SUBMIT_MODE,
|
||||
SLOT_DIVISION_MODE,
|
||||
WEEKDAY_OPTIONS,
|
||||
localSettings: defaultAppointmentSettings(),
|
||||
localDivision: defaultSlotDivision(),
|
||||
enabledSlotKeys: [],
|
||||
systemFormOptions: [],
|
||||
capacityDialogVisible: false,
|
||||
capacityDraft: {},
|
||||
activeSkuRowKey: "",
|
||||
syncing: false,
|
||||
divisionReady: false,
|
||||
set(val) {
|
||||
if (val && val.length === 2) {
|
||||
localSettings.value.bookableCustomStartDate = val[0];
|
||||
localSettings.value.bookableCustomEndDate = val[1];
|
||||
} else {
|
||||
localSettings.value.bookableCustomStartDate = "";
|
||||
localSettings.value.bookableCustomEndDate = "";
|
||||
}
|
||||
},
|
||||
});
|
||||
const autoTimeRange = computed({
|
||||
get() {
|
||||
if (localDivision.value.autoStartTime && localDivision.value.autoEndTime) {
|
||||
return [localDivision.value.autoStartTime, localDivision.value.autoEndTime];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
set(val) {
|
||||
if (val && val.length === 2) {
|
||||
localDivision.value.autoStartTime = val[0];
|
||||
localDivision.value.autoEndTime = val[1];
|
||||
}
|
||||
},
|
||||
});
|
||||
const activeCapacityRows = computed(() => {
|
||||
return localDivision.value.confirmedSlots
|
||||
.filter((slot) => isSlotEnabled(slot))
|
||||
.map((slot) => ({
|
||||
key: slotKey(slot),
|
||||
label: `${slot.startTime}-${slot.endTime}`,
|
||||
}));
|
||||
});
|
||||
const allSlotsChecked = computed(() => {
|
||||
const enabled = localDivision.value.confirmedSlots.filter((s) => isSlotEnabled(s));
|
||||
return enabled.length > 0 && enabled.length === enabledSlotKeys.value.length;
|
||||
});
|
||||
const slotIndeterminate = computed(() => {
|
||||
const enabled = localDivision.value.confirmedSlots.filter((s) => isSlotEnabled(s));
|
||||
return enabledSlotKeys.value.length > 0 && enabledSlotKeys.value.length < enabled.length;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.settings,
|
||||
(val) => {
|
||||
if (syncing.value) return;
|
||||
localSettings.value = { ...defaultAppointmentSettings(), ...(val || {}) };
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
watch(
|
||||
() => props.division,
|
||||
(val) => {
|
||||
if (syncing.value) return;
|
||||
localDivision.value = { ...defaultSlotDivision(), ...(val || {}) };
|
||||
enabledSlotKeys.value = (localDivision.value.confirmedSlots || [])
|
||||
.filter((s) => s.slotEnabled !== false)
|
||||
.map((s) => slotKey(s));
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
watch(
|
||||
localSettings,
|
||||
(val) => {
|
||||
syncing.value = true;
|
||||
emit("update:settings", { ...val });
|
||||
nextTick(() => {
|
||||
syncing.value = false;
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(
|
||||
localDivision,
|
||||
(val) => {
|
||||
syncing.value = true;
|
||||
emit("update:division", { ...val });
|
||||
nextTick(() => {
|
||||
syncing.value = false;
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(enabledSlotKeys, (val) => {
|
||||
localDivision.value.confirmedSlots = (localDivision.value.confirmedSlots || []).map((slot) => ({
|
||||
...slot,
|
||||
slotEnabled: val.includes(slotKey(slot)),
|
||||
}));
|
||||
});
|
||||
watch(
|
||||
() => props.divisionTarget,
|
||||
() => {
|
||||
prepareDivisionTarget();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function prepareDivisionTarget() {
|
||||
divisionReady.value = false;
|
||||
if (!props.divisionTarget) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => {
|
||||
divisionReady.value = !!document.querySelector(props.divisionTarget);
|
||||
});
|
||||
}
|
||||
function isSlotEnabled(slot) {
|
||||
return slot.slotEnabled !== false;
|
||||
}
|
||||
function toggleAllSlots(checked) {
|
||||
if (checked) {
|
||||
enabledSlotKeys.value = localDivision.value.confirmedSlots.map((s) => slotKey(s));
|
||||
} else {
|
||||
enabledSlotKeys.value = [];
|
||||
}
|
||||
}
|
||||
function loadSystemForms() {
|
||||
getRequest("/appointment/system-forms/enabled")
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
systemFormOptions.value = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
function goAddSystemForm() {
|
||||
// 主布局无 keep-alive,新开页避免发品表单被销毁
|
||||
const route = router.resolve({ name: "appointment-system-form-designer" });
|
||||
window.open(route.href, "_blank");
|
||||
}
|
||||
function addCustomDraft() {
|
||||
const last = localDivision.value.customDrafts[localDivision.value.customDrafts.length - 1];
|
||||
const start = last?.endTime || "08:00";
|
||||
const endMinutes = parseMinutes(start) + 60;
|
||||
const end = endMinutes >= 24 * 60 ? "23:59" : formatMinutes(endMinutes);
|
||||
localDivision.value.customDrafts.push({ startTime: start, endTime: end });
|
||||
}
|
||||
function removeCustomDraft(index) {
|
||||
localDivision.value.customDrafts.splice(index, 1);
|
||||
}
|
||||
function confirmSlots() {
|
||||
let slots = [];
|
||||
if (localDivision.value.slotDivisionMode === SLOT_DIVISION_MODE.AUTO) {
|
||||
const duration = Number(localDivision.value.serviceDurationMinutes);
|
||||
if (!duration || duration < 15) {
|
||||
Message.error("请填写有效的服务时长(15-480 分钟)");
|
||||
return false;
|
||||
}
|
||||
const start = parseMinutes(localDivision.value.autoStartTime);
|
||||
const end = parseMinutes(localDivision.value.autoEndTime);
|
||||
if (!localDivision.value.autoStartTime || !localDivision.value.autoEndTime || end <= start) {
|
||||
Message.error("请选择有效的起止时间(同一天内,结束大于开始)");
|
||||
return false;
|
||||
}
|
||||
for (let t = start; t + duration <= end; t += duration) {
|
||||
slots.push({
|
||||
startTime: formatMinutes(t),
|
||||
endTime: formatMinutes(t + duration),
|
||||
slotEnabled: true,
|
||||
});
|
||||
}
|
||||
if (!slots.length) {
|
||||
Message.error("未生成有效时段,请检查起止时间与服务时长");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const drafts = localDivision.value.customDrafts || [];
|
||||
if (!drafts.length) {
|
||||
Message.error("请至少添加一条自定义时段");
|
||||
return false;
|
||||
}
|
||||
let prevEnd = -1;
|
||||
for (const row of drafts) {
|
||||
if (!row.startTime || !row.endTime) {
|
||||
Message.error("请完整填写每条时段的开始和结束时间");
|
||||
return false;
|
||||
}
|
||||
const s = parseMinutes(row.startTime);
|
||||
const e = parseMinutes(row.endTime);
|
||||
if (e <= s) {
|
||||
Message.error("结束时间须大于开始时间,且不支持跨天");
|
||||
return false;
|
||||
}
|
||||
if (s < prevEnd) {
|
||||
Message.error("自定义时段不得重叠,须按时间顺序添加");
|
||||
return false;
|
||||
}
|
||||
prevEnd = e;
|
||||
slots.push({ startTime: row.startTime, endTime: row.endTime, slotEnabled: true });
|
||||
}
|
||||
}
|
||||
localDivision.value.confirmedSlots = slots;
|
||||
enabledSlotKeys.value = slots.map((s) => slotKey(s));
|
||||
Message.success(`已确认 ${slots.length} 个时段`);
|
||||
return true;
|
||||
}
|
||||
function openSkuCapacityDialog(skuRow) {
|
||||
activeSkuRowKey.value = getSkuRowKey(skuRow);
|
||||
const draft = {};
|
||||
activeCapacityRows.value.forEach((row) => {
|
||||
draft[row.key] = props.skuCapacities?.[activeSkuRowKey.value]?.[row.key] ?? 0;
|
||||
});
|
||||
capacityDraft.value = draft;
|
||||
capacityDialogVisible.value = true;
|
||||
}
|
||||
function saveSkuCapacities() {
|
||||
const next = { ...(props.skuCapacities || {}) };
|
||||
next[activeSkuRowKey.value] = { ...capacityDraft.value };
|
||||
emit("update:skuCapacities", next);
|
||||
capacityDialogVisible.value = false;
|
||||
}
|
||||
function validateRules() {
|
||||
const s = localSettings.value;
|
||||
if (!s.serviceModes?.length) {
|
||||
Message.error("预约规则:请至少选择一种服务模式");
|
||||
return false;
|
||||
}
|
||||
if (s.bookableDateMode === BOOKABLE_DATE_MODE.CUSTOM) {
|
||||
if (!s.bookableCustomStartDate || !s.bookableCustomEndDate) {
|
||||
Message.error("预约规则:请选择自定义可预约日期范围");
|
||||
return false;
|
||||
}
|
||||
if (!s.bookableWeekdays?.length) {
|
||||
Message.error("预约规则:请至少勾选一天可约星期");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!s.bookableDays || s.bookableDays < 1) {
|
||||
Message.error("预约规则:预约日期范围须为正整数");
|
||||
return false;
|
||||
}
|
||||
if (s.advanceMode === ADVANCE_MODE.REQUIRE_HOURS && (!s.advanceHours || s.advanceHours < 1)) {
|
||||
Message.error("预约规则:请填写提前预约小时数");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
s.cancelMode === CANCEL_MODE.ALLOW_BEFORE_HOURS &&
|
||||
(!s.cancelAheadHours || s.cancelAheadHours < 1)
|
||||
) {
|
||||
Message.error("预约规则:请填写取消政策小时数");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function validateDivision() {
|
||||
const enabledSlots = (localDivision.value.confirmedSlots || []).filter((slot) =>
|
||||
isSlotEnabled(slot)
|
||||
);
|
||||
if (!enabledSlots.length) {
|
||||
Message.error("时段划分:请确认时间段并至少启用 1 个时段");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function validate() {
|
||||
if (part === "rules") {
|
||||
return validateRules();
|
||||
}
|
||||
if (part === "division") {
|
||||
return validateDivision();
|
||||
}
|
||||
return validateRules() && validateDivision();
|
||||
}
|
||||
function buildSettingsPayload() {
|
||||
return {
|
||||
appointmentSettings: { ...localSettings.value },
|
||||
};
|
||||
}
|
||||
function buildDivisionPayload() {
|
||||
const enabledSlots = (localDivision.value.confirmedSlots || []).filter((slot) =>
|
||||
isSlotEnabled(slot)
|
||||
);
|
||||
return {
|
||||
appointmentSlotTemplate: {
|
||||
slotDivisionMode: localDivision.value.slotDivisionMode,
|
||||
serviceDurationMinutes: localDivision.value.serviceDurationMinutes,
|
||||
autoStartTime: localDivision.value.autoStartTime,
|
||||
autoEndTime: localDivision.value.autoEndTime,
|
||||
slots: enabledSlots.map((slot) => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
slotEnabled: true,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
function buildSubmitPayload() {
|
||||
return {
|
||||
...buildSettingsPayload(),
|
||||
...buildDivisionPayload(),
|
||||
};
|
||||
}
|
||||
function buildSkuSlotCapacities(skuRow) {
|
||||
const rowKey = getSkuRowKey(skuRow);
|
||||
const map = props.skuCapacities?.[rowKey] || {};
|
||||
return (localDivision.value.confirmedSlots || [])
|
||||
.filter((slot) => isSlotEnabled(slot))
|
||||
.map((slot) => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
slotCapacity: map[slotKey(slot)] ?? 0,
|
||||
}));
|
||||
}
|
||||
function getSkuRowKey(row) {
|
||||
return props.skuInfo
|
||||
.map((info) => row[info.name])
|
||||
.filter(Boolean)
|
||||
.join("::");
|
||||
}
|
||||
function hydrateFromGoods(result) {
|
||||
if (result.appointmentSettings) {
|
||||
localSettings.value = { ...defaultAppointmentSettings(), ...result.appointmentSettings };
|
||||
}
|
||||
if (result.appointmentSlotTemplate) {
|
||||
const tpl = result.appointmentSlotTemplate;
|
||||
localDivision.value = {
|
||||
...defaultSlotDivision(),
|
||||
slotDivisionMode: tpl.slotDivisionMode || SLOT_DIVISION_MODE.AUTO,
|
||||
serviceDurationMinutes: tpl.serviceDurationMinutes || 60,
|
||||
autoStartTime: tpl.autoStartTime || "08:00",
|
||||
autoEndTime: tpl.autoEndTime || "18:00",
|
||||
confirmedSlots: tpl.slots || [],
|
||||
customDrafts: tpl.slots?.length
|
||||
? tpl.slots.map((s) => ({ startTime: s.startTime, endTime: s.endTime }))
|
||||
: defaultSlotDivision().customDrafts,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bookableDateHint() {
|
||||
return "设置预约服务的可预约日期。例如:周六、周天不选中,则在可预约日期中的周一到周五可用。";
|
||||
},
|
||||
customDateRange: {
|
||||
get() {
|
||||
const { bookableCustomStartDate, bookableCustomEndDate } = this.localSettings;
|
||||
if (bookableCustomStartDate && bookableCustomEndDate) {
|
||||
return [bookableCustomStartDate, bookableCustomEndDate];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
set(val) {
|
||||
if (val && val.length === 2) {
|
||||
this.localSettings.bookableCustomStartDate = val[0];
|
||||
this.localSettings.bookableCustomEndDate = val[1];
|
||||
} else {
|
||||
this.localSettings.bookableCustomStartDate = "";
|
||||
this.localSettings.bookableCustomEndDate = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
autoTimeRange: {
|
||||
get() {
|
||||
if (this.localDivision.autoStartTime && this.localDivision.autoEndTime) {
|
||||
return [this.localDivision.autoStartTime, this.localDivision.autoEndTime];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
set(val) {
|
||||
if (val && val.length === 2) {
|
||||
this.localDivision.autoStartTime = val[0];
|
||||
this.localDivision.autoEndTime = val[1];
|
||||
}
|
||||
},
|
||||
},
|
||||
activeCapacityRows() {
|
||||
return this.localDivision.confirmedSlots
|
||||
.filter((slot) => this.isSlotEnabled(slot))
|
||||
.map((slot) => ({
|
||||
key: slotKey(slot),
|
||||
label: `${slot.startTime}-${slot.endTime}`,
|
||||
}));
|
||||
},
|
||||
allSlotsChecked() {
|
||||
const enabled = this.localDivision.confirmedSlots.filter((s) => this.isSlotEnabled(s));
|
||||
return enabled.length > 0 && enabled.length === this.enabledSlotKeys.length;
|
||||
},
|
||||
slotIndeterminate() {
|
||||
const enabled = this.localDivision.confirmedSlots.filter((s) => this.isSlotEnabled(s));
|
||||
return this.enabledSlotKeys.length > 0 && this.enabledSlotKeys.length < enabled.length;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
settings: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(val) {
|
||||
if (this.syncing) return;
|
||||
this.localSettings = { ...defaultAppointmentSettings(), ...(val || {}) };
|
||||
},
|
||||
},
|
||||
division: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(val) {
|
||||
if (this.syncing) return;
|
||||
this.localDivision = { ...defaultSlotDivision(), ...(val || {}) };
|
||||
this.enabledSlotKeys = (this.localDivision.confirmedSlots || [])
|
||||
.filter((s) => s.slotEnabled !== false)
|
||||
.map((s) => slotKey(s));
|
||||
},
|
||||
},
|
||||
localSettings: {
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.syncing = true;
|
||||
this.$emit("update:settings", { ...val });
|
||||
this.$nextTick(() => {
|
||||
this.syncing = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
localDivision: {
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.syncing = true;
|
||||
this.$emit("update:division", { ...val });
|
||||
this.$nextTick(() => {
|
||||
this.syncing = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
enabledSlotKeys(val) {
|
||||
this.localDivision.confirmedSlots = (this.localDivision.confirmedSlots || []).map((slot) => ({
|
||||
...slot,
|
||||
slotEnabled: val.includes(slotKey(slot)),
|
||||
}));
|
||||
},
|
||||
divisionTarget: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.prepareDivisionTarget();
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadSystemForms();
|
||||
this.prepareDivisionTarget();
|
||||
},
|
||||
methods: {
|
||||
prepareDivisionTarget() {
|
||||
this.divisionReady = false;
|
||||
if (!this.divisionTarget) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.divisionReady = !!document.querySelector(this.divisionTarget);
|
||||
enabledSlotKeys.value = (tpl.slots || [])
|
||||
.filter((s) => s.slotEnabled !== false)
|
||||
.map((s) => slotKey(s));
|
||||
}
|
||||
if (result.skuList?.length) {
|
||||
const cap = {};
|
||||
result.skuList.forEach((sku) => {
|
||||
const specKey = (sku.specList || []).map((s) => s.specValue).filter(Boolean).join("::");
|
||||
const keys = [specKey, sku.id, sku.sn].filter(Boolean);
|
||||
const slotMap = {};
|
||||
(sku.slotCapacities || []).forEach((item) => {
|
||||
slotMap[slotKey(item)] = item.slotCapacity ?? 0;
|
||||
});
|
||||
},
|
||||
slotKey,
|
||||
isSlotEnabled(slot) {
|
||||
return slot.slotEnabled !== false;
|
||||
},
|
||||
toggleAllSlots(checked) {
|
||||
if (checked) {
|
||||
this.enabledSlotKeys = this.localDivision.confirmedSlots.map((s) => slotKey(s));
|
||||
} else {
|
||||
this.enabledSlotKeys = [];
|
||||
}
|
||||
},
|
||||
loadSystemForms() {
|
||||
getRequest("/appointment/system-forms/enabled")
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.systemFormOptions = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
goAddSystemForm() {
|
||||
// 主布局无 keep-alive,新开页避免发品表单被销毁
|
||||
const route = this.$router.resolve({ name: "appointment-system-form-designer" });
|
||||
window.open(route.href, "_blank");
|
||||
},
|
||||
addCustomDraft() {
|
||||
const last = this.localDivision.customDrafts[this.localDivision.customDrafts.length - 1];
|
||||
const start = last?.endTime || "08:00";
|
||||
const endMinutes = parseMinutes(start) + 60;
|
||||
const end = endMinutes >= 24 * 60 ? "23:59" : formatMinutes(endMinutes);
|
||||
this.localDivision.customDrafts.push({ startTime: start, endTime: end });
|
||||
},
|
||||
removeCustomDraft(index) {
|
||||
this.localDivision.customDrafts.splice(index, 1);
|
||||
},
|
||||
confirmSlots() {
|
||||
let slots = [];
|
||||
if (this.localDivision.slotDivisionMode === SLOT_DIVISION_MODE.AUTO) {
|
||||
const duration = Number(this.localDivision.serviceDurationMinutes);
|
||||
if (!duration || duration < 15) {
|
||||
this.$Message.error("请填写有效的服务时长(15-480 分钟)");
|
||||
return false;
|
||||
}
|
||||
const start = parseMinutes(this.localDivision.autoStartTime);
|
||||
const end = parseMinutes(this.localDivision.autoEndTime);
|
||||
if (!this.localDivision.autoStartTime || !this.localDivision.autoEndTime || end <= start) {
|
||||
this.$Message.error("请选择有效的起止时间(同一天内,结束大于开始)");
|
||||
return false;
|
||||
}
|
||||
for (let t = start; t + duration <= end; t += duration) {
|
||||
slots.push({
|
||||
startTime: formatMinutes(t),
|
||||
endTime: formatMinutes(t + duration),
|
||||
slotEnabled: true,
|
||||
});
|
||||
}
|
||||
if (!slots.length) {
|
||||
this.$Message.error("未生成有效时段,请检查起止时间与服务时长");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const drafts = this.localDivision.customDrafts || [];
|
||||
if (!drafts.length) {
|
||||
this.$Message.error("请至少添加一条自定义时段");
|
||||
return false;
|
||||
}
|
||||
let prevEnd = -1;
|
||||
for (const row of drafts) {
|
||||
if (!row.startTime || !row.endTime) {
|
||||
this.$Message.error("请完整填写每条时段的开始和结束时间");
|
||||
return false;
|
||||
}
|
||||
const s = parseMinutes(row.startTime);
|
||||
const e = parseMinutes(row.endTime);
|
||||
if (e <= s) {
|
||||
this.$Message.error("结束时间须大于开始时间,且不支持跨天");
|
||||
return false;
|
||||
}
|
||||
if (s < prevEnd) {
|
||||
this.$Message.error("自定义时段不得重叠,须按时间顺序添加");
|
||||
return false;
|
||||
}
|
||||
prevEnd = e;
|
||||
slots.push({ startTime: row.startTime, endTime: row.endTime, slotEnabled: true });
|
||||
}
|
||||
}
|
||||
this.localDivision.confirmedSlots = slots;
|
||||
this.enabledSlotKeys = slots.map((s) => slotKey(s));
|
||||
this.$Message.success(`已确认 ${slots.length} 个时段`);
|
||||
return true;
|
||||
},
|
||||
openSkuCapacityDialog(skuRow) {
|
||||
this.activeSkuRowKey = this.getSkuRowKey(skuRow);
|
||||
const draft = {};
|
||||
this.activeCapacityRows.forEach((row) => {
|
||||
draft[row.key] = this.skuCapacities?.[this.activeSkuRowKey]?.[row.key] ?? 0;
|
||||
keys.forEach((key) => {
|
||||
cap[key] = { ...slotMap };
|
||||
});
|
||||
this.capacityDraft = draft;
|
||||
this.capacityDialogVisible = true;
|
||||
},
|
||||
saveSkuCapacities() {
|
||||
const next = { ...(this.skuCapacities || {}) };
|
||||
next[this.activeSkuRowKey] = { ...this.capacityDraft };
|
||||
this.$emit("update:skuCapacities", next);
|
||||
this.capacityDialogVisible = false;
|
||||
},
|
||||
validateRules() {
|
||||
const s = this.localSettings;
|
||||
if (!s.serviceModes?.length) {
|
||||
this.$Message.error("预约规则:请至少选择一种服务模式");
|
||||
return false;
|
||||
}
|
||||
if (s.bookableDateMode === BOOKABLE_DATE_MODE.CUSTOM) {
|
||||
if (!s.bookableCustomStartDate || !s.bookableCustomEndDate) {
|
||||
this.$Message.error("预约规则:请选择自定义可预约日期范围");
|
||||
return false;
|
||||
}
|
||||
if (!s.bookableWeekdays?.length) {
|
||||
this.$Message.error("预约规则:请至少勾选一天可约星期");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!s.bookableDays || s.bookableDays < 1) {
|
||||
this.$Message.error("预约规则:预约日期范围须为正整数");
|
||||
return false;
|
||||
}
|
||||
if (s.advanceMode === ADVANCE_MODE.REQUIRE_HOURS && (!s.advanceHours || s.advanceHours < 1)) {
|
||||
this.$Message.error("预约规则:请填写提前预约小时数");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
s.cancelMode === CANCEL_MODE.ALLOW_BEFORE_HOURS &&
|
||||
(!s.cancelAheadHours || s.cancelAheadHours < 1)
|
||||
) {
|
||||
this.$Message.error("预约规则:请填写取消政策小时数");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateDivision() {
|
||||
const enabledSlots = (this.localDivision.confirmedSlots || []).filter((slot) =>
|
||||
this.isSlotEnabled(slot)
|
||||
);
|
||||
if (!enabledSlots.length) {
|
||||
this.$Message.error("时段划分:请确认时间段并至少启用 1 个时段");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validate() {
|
||||
if (this.part === "rules") {
|
||||
return this.validateRules();
|
||||
}
|
||||
if (this.part === "division") {
|
||||
return this.validateDivision();
|
||||
}
|
||||
return this.validateRules() && this.validateDivision();
|
||||
},
|
||||
buildSettingsPayload() {
|
||||
return {
|
||||
appointmentSettings: { ...this.localSettings },
|
||||
};
|
||||
},
|
||||
buildDivisionPayload() {
|
||||
const enabledSlots = (this.localDivision.confirmedSlots || []).filter((slot) =>
|
||||
this.isSlotEnabled(slot)
|
||||
);
|
||||
return {
|
||||
appointmentSlotTemplate: {
|
||||
slotDivisionMode: this.localDivision.slotDivisionMode,
|
||||
serviceDurationMinutes: this.localDivision.serviceDurationMinutes,
|
||||
autoStartTime: this.localDivision.autoStartTime,
|
||||
autoEndTime: this.localDivision.autoEndTime,
|
||||
slots: enabledSlots.map((slot) => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
slotEnabled: true,
|
||||
})),
|
||||
},
|
||||
};
|
||||
},
|
||||
buildSubmitPayload() {
|
||||
return {
|
||||
...this.buildSettingsPayload(),
|
||||
...this.buildDivisionPayload(),
|
||||
};
|
||||
},
|
||||
buildSkuSlotCapacities(skuRow) {
|
||||
const rowKey = this.getSkuRowKey(skuRow);
|
||||
const map = this.skuCapacities?.[rowKey] || {};
|
||||
return (this.localDivision.confirmedSlots || [])
|
||||
.filter((slot) => this.isSlotEnabled(slot))
|
||||
.map((slot) => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
slotCapacity: map[slotKey(slot)] ?? 0,
|
||||
}));
|
||||
},
|
||||
getSkuRowKey(row) {
|
||||
return this.skuInfo
|
||||
.map((info) => row[info.name])
|
||||
.filter(Boolean)
|
||||
.join("::");
|
||||
},
|
||||
hydrateFromGoods(result) {
|
||||
if (result.appointmentSettings) {
|
||||
this.localSettings = { ...defaultAppointmentSettings(), ...result.appointmentSettings };
|
||||
}
|
||||
if (result.appointmentSlotTemplate) {
|
||||
const tpl = result.appointmentSlotTemplate;
|
||||
this.localDivision = {
|
||||
...defaultSlotDivision(),
|
||||
slotDivisionMode: tpl.slotDivisionMode || SLOT_DIVISION_MODE.AUTO,
|
||||
serviceDurationMinutes: tpl.serviceDurationMinutes || 60,
|
||||
autoStartTime: tpl.autoStartTime || "08:00",
|
||||
autoEndTime: tpl.autoEndTime || "18:00",
|
||||
confirmedSlots: tpl.slots || [],
|
||||
customDrafts: tpl.slots?.length
|
||||
? tpl.slots.map((s) => ({ startTime: s.startTime, endTime: s.endTime }))
|
||||
: defaultSlotDivision().customDrafts,
|
||||
};
|
||||
this.enabledSlotKeys = (tpl.slots || [])
|
||||
.filter((s) => s.slotEnabled !== false)
|
||||
.map((s) => slotKey(s));
|
||||
}
|
||||
if (result.skuList?.length) {
|
||||
const cap = {};
|
||||
result.skuList.forEach((sku) => {
|
||||
const specKey = (sku.specList || []).map((s) => s.specValue).filter(Boolean).join("::");
|
||||
const keys = [specKey, sku.id, sku.sn].filter(Boolean);
|
||||
const slotMap = {};
|
||||
(sku.slotCapacities || []).forEach((item) => {
|
||||
slotMap[slotKey(item)] = item.slotCapacity ?? 0;
|
||||
});
|
||||
keys.forEach((key) => {
|
||||
cap[key] = { ...slotMap };
|
||||
});
|
||||
});
|
||||
this.$emit("update:skuCapacities", cap);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
emit("update:skuCapacities", cap);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSystemForms();
|
||||
prepareDivisionTarget();
|
||||
});
|
||||
|
||||
// 父组件 goodsOperationSec.vue 通过 ref 调用以下方法,保持公开接口不变
|
||||
defineExpose({
|
||||
prepareDivisionTarget,
|
||||
slotKey,
|
||||
isSlotEnabled,
|
||||
toggleAllSlots,
|
||||
loadSystemForms,
|
||||
goAddSystemForm,
|
||||
addCustomDraft,
|
||||
removeCustomDraft,
|
||||
confirmSlots,
|
||||
openSkuCapacityDialog,
|
||||
saveSkuCapacities,
|
||||
validateRules,
|
||||
validateDivision,
|
||||
validate,
|
||||
buildSettingsPayload,
|
||||
buildDivisionPayload,
|
||||
buildSubmitPayload,
|
||||
buildSkuSlotCapacities,
|
||||
getSkuRowKey,
|
||||
hydrateFromGoods,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -23,7 +23,7 @@
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<el-table v-loading="loading" border :data="data" ref="table" class="mt_10" style="width: 100%">
|
||||
<el-table v-loading="loading" border :data="data" ref="tableRef" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="id" label="编号" min-width="120" />
|
||||
<el-table-column label="商品原图" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -66,88 +66,94 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { getDraftGoodsListData, deleteDraftGoods } from "@/api/goods";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "goods",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
saveType: "TEMPLATE",
|
||||
},
|
||||
data: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
editGoods(v) {
|
||||
this.$router.push({
|
||||
name: "goods-template-operation-edit",
|
||||
query: { draftId: v.id },
|
||||
});
|
||||
},
|
||||
removeDraft(id) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认审核",
|
||||
content: "您确认要删除id为 " + id + " 的模版吗?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
deleteDraftGoods(id).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("删除成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize() {
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 10;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.$refs.searchForm.resetFields();
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 10;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getDraftGoodsListData(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
defineOptions({ name: "goods" });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
|
||||
const loading = ref(true);
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
saveType: "TEMPLATE",
|
||||
});
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
function editGoods(v) {
|
||||
router.push({
|
||||
name: "goods-template-operation-edit",
|
||||
query: { draftId: v.id },
|
||||
});
|
||||
}
|
||||
function removeDraft(id) {
|
||||
Modal.confirm({
|
||||
title: "确认审核",
|
||||
content: "您确认要删除id为 " + id + " 的模版吗?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
deleteDraftGoods(id).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
Message.success("删除成功");
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.init();
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize() {
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.pageSize = 10;
|
||||
getDataList();
|
||||
}
|
||||
function handleReset() {
|
||||
searchFormRef.value.resetFields();
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.pageSize = 10;
|
||||
getDataList();
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getDraftGoodsListData(searchForm.value).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
() => {
|
||||
init();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -164,7 +164,7 @@
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
ref="table"
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
class="mt_10"
|
||||
@@ -257,7 +257,7 @@
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="modalVisible" title="下架操作" width="500px" :close-on-click-modal="false">
|
||||
<el-form ref="underForm" :model="underForm" label-width="100px">
|
||||
<el-form ref="underFormRef" :model="underForm" label-width="100px">
|
||||
<el-form-item label="下架原因" prop="reason">
|
||||
<el-input v-model="underForm.reason" clearable />
|
||||
</el-form-item>
|
||||
@@ -270,7 +270,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {
|
||||
getCategoryTree,
|
||||
getGoodsListData,
|
||||
@@ -280,411 +282,415 @@ import {
|
||||
upGoods,
|
||||
lowGoods,
|
||||
} from "@/api/goods";
|
||||
export default {
|
||||
name: "goods",
|
||||
data() {
|
||||
return {
|
||||
id: "", //要操作的id
|
||||
loading: true, // 表单加载状态
|
||||
modalVisible: false, // 添加或编辑显示
|
||||
createRange: [],
|
||||
searchForm: {
|
||||
// 搜索框初始化对象
|
||||
pageNumber: 1, // 当前页数
|
||||
pageSize: 20, // 页面大小
|
||||
sort: "create_time", // 默认排序字段
|
||||
order: "desc", // 默认排序方式
|
||||
storeCategoryPath: "",
|
||||
categoryPath: "",
|
||||
skuName: "",
|
||||
skuSn: "",
|
||||
stockStatus: "",
|
||||
stockMin: "",
|
||||
stockMax: "",
|
||||
priceMin: "",
|
||||
priceMax: "",
|
||||
},
|
||||
platformCategoryList: [],
|
||||
shopCategoryList: [],
|
||||
platformCategoryProps: {
|
||||
value: "id",
|
||||
label: "name",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
},
|
||||
shopCategoryProps: {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
},
|
||||
underForm: {
|
||||
// 下架原因
|
||||
reason: "",
|
||||
},
|
||||
submitLoading: false, // 添加或编辑提交状态
|
||||
data: [], // 表单数据
|
||||
total: 0, // 表单数据总数
|
||||
currentStatus: "ALL",
|
||||
goodsNumerData: {},
|
||||
selectedRows: [], // 选中的行数据
|
||||
selectAll: false, // 全选状态
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
goodsStatusWithCount() {
|
||||
return [
|
||||
{title: '全部', value: 'ALL'},
|
||||
{title: `出售中${this.goodsNumerData.upperGoodsNum ? '(' + this.goodsNumerData.upperGoodsNum + ')' : ''}`, value: 'UPPER'},
|
||||
{title: `仓库中${this.goodsNumerData.downGoodsNum ? '(' + this.goodsNumerData.downGoodsNum + ')' : ''}`, value: 'DOWN'},
|
||||
{title: `待审核${this.goodsNumerData.auditGoodsNum ? '(' + this.goodsNumerData.auditGoodsNum + ')' : ''}`, value: 'TOBEAUDITED'},
|
||||
{title: `审核未通过${this.goodsNumerData.refuseGoodsNum ? '(' + this.goodsNumerData.refuseGoodsNum + ')' : ''}`, value: 'REFUSE'}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearTableSelection() {
|
||||
this.$refs.table?.clearSelection?.();
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.goodsStatusClick(tab.paneName);
|
||||
},
|
||||
goodsTypeText(v) {
|
||||
if (v === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (v === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (v === "E_COUPON") return "电子卡券"; // 卡密商品
|
||||
if (v === "APPOINTMENT_GOODS") return "预约商品";
|
||||
return v || "—";
|
||||
},
|
||||
findTreeNodeLabel(tree, targetId, valueKey, labelKey) {
|
||||
for (const node of tree || []) {
|
||||
if (String(node[valueKey]) === String(targetId)) {
|
||||
return node[labelKey];
|
||||
}
|
||||
const childLabel = this.findTreeNodeLabel(node.children, targetId, valueKey, labelKey);
|
||||
if (childLabel) return childLabel;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
platformCategoryText(path) {
|
||||
if (!path) return "—";
|
||||
const ids = String(path).split(",").filter(Boolean);
|
||||
if (!ids.length) return "—";
|
||||
const labels = ids
|
||||
.map((id) => this.findTreeNodeLabel(this.platformCategoryList, id, "id", "name"))
|
||||
.filter(Boolean);
|
||||
return labels.length ? labels.join(" / ") : "—";
|
||||
},
|
||||
/** 跳转卡池管理;单 SKU 商品默认取第一个 SKU(card-key-pool 需 skuId) */
|
||||
async goCardKeyPool(row) {
|
||||
let skuId = row.skuId;
|
||||
if (!skuId) {
|
||||
try {
|
||||
const res = await getQueryGoodsIdGoodsList(row.id);
|
||||
if (res.success && res.result?.length) {
|
||||
skuId = res.result[0].id;
|
||||
} else {
|
||||
this.$message.warning("该商品暂无 SKU,请先完善商品规格");
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
this.$message.error("获取商品规格失败");
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.$router.push({
|
||||
path: "/card-key-pool",
|
||||
query: {
|
||||
skuId,
|
||||
goodsId: row.id,
|
||||
goodsName: row.goodsName,
|
||||
},
|
||||
});
|
||||
},
|
||||
marketEnableText(v) {
|
||||
if (v === "DOWN") return "下架";
|
||||
if (v === "UPPER") return "上架";
|
||||
return "";
|
||||
},
|
||||
authFlagText(v) {
|
||||
if (v === "TOBEAUDITED") return "待审核";
|
||||
if (v === "PASS") return "通过";
|
||||
if (v === "REFUSE") return "拒绝";
|
||||
return "";
|
||||
},
|
||||
// 初始化数据
|
||||
init() {
|
||||
this.loadFilterOptions();
|
||||
this.getDataList();
|
||||
this.getNumberData();
|
||||
},
|
||||
loadFilterOptions() {
|
||||
getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.platformCategoryList = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
onStockStatusChange(value) {
|
||||
if (value !== "IN_STOCK") {
|
||||
this.searchForm.stockMin = "";
|
||||
this.searchForm.stockMax = "";
|
||||
}
|
||||
},
|
||||
buildSearchParams(includeGoodsStatus = true) {
|
||||
const { stockStatus, stockMin, stockMax, priceMin, priceMax, ...rest } = this.searchForm;
|
||||
const params = { ...rest };
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
if (!includeGoodsStatus) {
|
||||
delete params.goodsStatus;
|
||||
}
|
||||
defineOptions({ name: "goods" });
|
||||
|
||||
if (this.createRange && this.createRange.length === 2) {
|
||||
params.startDate = this.createRange[0];
|
||||
params.endDate = this.createRange[1];
|
||||
}
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
if (stockStatus === "IN_STOCK") {
|
||||
params.geQuantity = stockMin !== "" && stockMin != null ? Number(stockMin) : 1;
|
||||
if (stockMax !== "" && stockMax != null) {
|
||||
params.leQuantity = Number(stockMax);
|
||||
}
|
||||
} else if (stockStatus === "OUT_OF_STOCK") {
|
||||
params.geQuantity = 0;
|
||||
params.leQuantity = 0;
|
||||
} else {
|
||||
if (stockMin !== "" && stockMin != null) params.geQuantity = Number(stockMin);
|
||||
if (stockMax !== "" && stockMax != null) params.leQuantity = Number(stockMax);
|
||||
}
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
const underFormRef = ref(null);
|
||||
|
||||
if (priceMin !== "" && priceMax !== "") {
|
||||
params.price = `${priceMin}_${priceMax}`;
|
||||
} else if (priceMin !== "") {
|
||||
params.price = String(priceMin);
|
||||
} else if (priceMax !== "") {
|
||||
params.price = `0_${priceMax}`;
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.labelName,
|
||||
children:
|
||||
item.children && item.children.length
|
||||
? this.formatShopCategory(item.children)
|
||||
: undefined,
|
||||
}));
|
||||
},
|
||||
// 分页 改变页码
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
// 分页 改变页数
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
this.getNumberData();
|
||||
},
|
||||
// 获取数据
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getGoodsListData(this.buildSearchParams()).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
},
|
||||
getNumberData() {
|
||||
getGoodsNumerData(this.buildSearchParams(false)).then((res) => {
|
||||
if (res.success) {
|
||||
this.goodsNumerData = res.result;
|
||||
}
|
||||
})
|
||||
},
|
||||
// 编辑
|
||||
edit(v) {
|
||||
this.id = v.id;
|
||||
if (v.underMessage) {
|
||||
this.underForm.reason = v.underMessage;
|
||||
} else {
|
||||
this.underForm.reason = "";
|
||||
}
|
||||
this.modalVisible = true;
|
||||
},
|
||||
// 下架
|
||||
lower() {
|
||||
let params = {
|
||||
goodsId: this.id,
|
||||
reason:this.underForm.reason
|
||||
};
|
||||
lowGoods(params).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.modalVisible = false;
|
||||
this.getDataList();
|
||||
this.getNumberData(); // 添加这行
|
||||
}
|
||||
});
|
||||
},
|
||||
// 上架
|
||||
upper(v) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认上架",
|
||||
content: "您确认要上架 " + v.goodsName + " ?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
let params = {
|
||||
goodsId: v.id
|
||||
};
|
||||
upGoods(params).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("上架成功");
|
||||
this.getDataList();
|
||||
this.getNumberData(); // 添加这行
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑商品
|
||||
editGoods(v) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "goods-operation-edit",
|
||||
query: { id: v.id },
|
||||
});
|
||||
},
|
||||
|
||||
// 复制商品
|
||||
copyGoods(v) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "goods-operation",
|
||||
query: { copyId: v.id },
|
||||
});
|
||||
},
|
||||
|
||||
// 商品状态筛选
|
||||
goodsStatusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined || name === null) {
|
||||
delete this.searchForm.goodsStatus;
|
||||
this.currentStatus = "ALL";
|
||||
} else {
|
||||
this.searchForm.goodsStatus = name;
|
||||
this.currentStatus = name;
|
||||
}
|
||||
this.selectedRows = [];
|
||||
this.clearTableSelection();
|
||||
this.getDataList();
|
||||
},
|
||||
|
||||
onSelectionChange(selection) {
|
||||
this.selectedRows = selection;
|
||||
},
|
||||
|
||||
// 批量上架
|
||||
batchUpper() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$Message.warning('请先选择要上架的商品');
|
||||
return;
|
||||
}
|
||||
|
||||
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
|
||||
this.$Modal.confirm({
|
||||
title: '确认批量上架',
|
||||
content: `您确认要上架以下商品吗?\n${goodsNames}`,
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
// 提取所有选中商品的ID
|
||||
const goodsIds = this.selectedRows.map(item => item.id);
|
||||
const params = {
|
||||
goodsId: goodsIds // 传递ID数组
|
||||
};
|
||||
|
||||
upGoods(params).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success('批量上架成功');
|
||||
this.selectedRows = [];
|
||||
this.selectAll = false;
|
||||
this.getDataList();
|
||||
this.getNumberData();
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$Modal.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 批量下架
|
||||
batchLower() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$Message.warning('请先选择要下架的商品');
|
||||
return;
|
||||
}
|
||||
|
||||
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
|
||||
this.$Modal.confirm({
|
||||
title: '确认批量下架',
|
||||
content: `您确认要下架以下商品吗?\n${goodsNames}`,
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
// 提取所有选中商品的ID
|
||||
const goodsIds = this.selectedRows.map(item => item.id);
|
||||
const params = {
|
||||
goodsId: goodsIds, // 传递ID数组
|
||||
reason: '批量下架操作' // 可以设置默认下架原因
|
||||
};
|
||||
|
||||
lowGoods(params).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success('批量下架成功');
|
||||
this.selectedRows = [];
|
||||
this.selectAll = false;
|
||||
this.getDataList();
|
||||
this.getNumberData();
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$Modal.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const goodsStatus = this.$route.query.goodsStatus;
|
||||
if (goodsStatus) {
|
||||
this.currentStatus = goodsStatus;
|
||||
this.searchForm.goodsStatus = goodsStatus;
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
const id = ref(""); //要操作的id
|
||||
const loading = ref(true); // 表单加载状态
|
||||
const modalVisible = ref(false); // 添加或编辑显示
|
||||
const createRange = ref([]);
|
||||
const searchForm = ref({
|
||||
// 搜索框初始化对象
|
||||
pageNumber: 1, // 当前页数
|
||||
pageSize: 20, // 页面大小
|
||||
sort: "create_time", // 默认排序字段
|
||||
order: "desc", // 默认排序方式
|
||||
storeCategoryPath: "",
|
||||
categoryPath: "",
|
||||
skuName: "",
|
||||
skuSn: "",
|
||||
stockStatus: "",
|
||||
stockMin: "",
|
||||
stockMax: "",
|
||||
priceMin: "",
|
||||
priceMax: "",
|
||||
});
|
||||
const platformCategoryList = ref([]);
|
||||
const shopCategoryList = ref([]);
|
||||
const platformCategoryProps = {
|
||||
value: "id",
|
||||
label: "name",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
};
|
||||
const shopCategoryProps = {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
};
|
||||
const underForm = ref({
|
||||
// 下架原因
|
||||
reason: "",
|
||||
});
|
||||
const submitLoading = ref(false); // 添加或编辑提交状态
|
||||
const data = ref([]); // 表单数据
|
||||
const total = ref(0); // 表单数据总数
|
||||
const currentStatus = ref("ALL");
|
||||
const goodsNumerData = ref({});
|
||||
const selectedRows = ref([]); // 选中的行数据
|
||||
const selectAll = ref(false); // 全选状态
|
||||
|
||||
const goodsStatusWithCount = computed(() => {
|
||||
return [
|
||||
{title: '全部', value: 'ALL'},
|
||||
{title: `出售中${goodsNumerData.value.upperGoodsNum ? '(' + goodsNumerData.value.upperGoodsNum + ')' : ''}`, value: 'UPPER'},
|
||||
{title: `仓库中${goodsNumerData.value.downGoodsNum ? '(' + goodsNumerData.value.downGoodsNum + ')' : ''}`, value: 'DOWN'},
|
||||
{title: `待审核${goodsNumerData.value.auditGoodsNum ? '(' + goodsNumerData.value.auditGoodsNum + ')' : ''}`, value: 'TOBEAUDITED'},
|
||||
{title: `审核未通过${goodsNumerData.value.refuseGoodsNum ? '(' + goodsNumerData.value.refuseGoodsNum + ')' : ''}`, value: 'REFUSE'}
|
||||
];
|
||||
});
|
||||
|
||||
function clearTableSelection() {
|
||||
tableRef.value?.clearSelection?.();
|
||||
}
|
||||
function onStatusTabClick(tab) {
|
||||
goodsStatusClick(tab.paneName);
|
||||
}
|
||||
function goodsTypeText(v) {
|
||||
if (v === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (v === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (v === "E_COUPON") return "电子卡券"; // 卡密商品
|
||||
if (v === "APPOINTMENT_GOODS") return "预约商品";
|
||||
return v || "—";
|
||||
}
|
||||
function findTreeNodeLabel(tree, targetId, valueKey, labelKey) {
|
||||
for (const node of tree || []) {
|
||||
if (String(node[valueKey]) === String(targetId)) {
|
||||
return node[labelKey];
|
||||
}
|
||||
const childLabel = findTreeNodeLabel(node.children, targetId, valueKey, labelKey);
|
||||
if (childLabel) return childLabel;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function platformCategoryText(path) {
|
||||
if (!path) return "—";
|
||||
const ids = String(path).split(",").filter(Boolean);
|
||||
if (!ids.length) return "—";
|
||||
const labels = ids
|
||||
.map((id) => findTreeNodeLabel(platformCategoryList.value, id, "id", "name"))
|
||||
.filter(Boolean);
|
||||
return labels.length ? labels.join(" / ") : "—";
|
||||
}
|
||||
/** 跳转卡池管理;单 SKU 商品默认取第一个 SKU(card-key-pool 需 skuId) */
|
||||
async function goCardKeyPool(row) {
|
||||
let skuId = row.skuId;
|
||||
if (!skuId) {
|
||||
try {
|
||||
const res = await getQueryGoodsIdGoodsList(row.id);
|
||||
if (res.success && res.result?.length) {
|
||||
skuId = res.result[0].id;
|
||||
} else {
|
||||
Message.warning("该商品暂无 SKU,请先完善商品规格");
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
Message.error("获取商品规格失败");
|
||||
return;
|
||||
}
|
||||
}
|
||||
router.push({
|
||||
path: "/card-key-pool",
|
||||
query: {
|
||||
skuId,
|
||||
goodsId: row.id,
|
||||
goodsName: row.goodsName,
|
||||
},
|
||||
});
|
||||
}
|
||||
function marketEnableText(v) {
|
||||
if (v === "DOWN") return "下架";
|
||||
if (v === "UPPER") return "上架";
|
||||
return "";
|
||||
}
|
||||
function authFlagText(v) {
|
||||
if (v === "TOBEAUDITED") return "待审核";
|
||||
if (v === "PASS") return "通过";
|
||||
if (v === "REFUSE") return "拒绝";
|
||||
return "";
|
||||
}
|
||||
// 初始化数据
|
||||
function init() {
|
||||
loadFilterOptions();
|
||||
getDataList();
|
||||
getNumberData();
|
||||
}
|
||||
function loadFilterOptions() {
|
||||
getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
shopCategoryList.value = formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
platformCategoryList.value = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
function onStockStatusChange(value) {
|
||||
if (value !== "IN_STOCK") {
|
||||
searchForm.value.stockMin = "";
|
||||
searchForm.value.stockMax = "";
|
||||
}
|
||||
}
|
||||
function buildSearchParams(includeGoodsStatus = true) {
|
||||
const { stockStatus, stockMin, stockMax, priceMin, priceMax, ...rest } = searchForm.value;
|
||||
const params = { ...rest };
|
||||
|
||||
if (!includeGoodsStatus) {
|
||||
delete params.goodsStatus;
|
||||
}
|
||||
|
||||
if (createRange.value && createRange.value.length === 2) {
|
||||
params.startDate = createRange.value[0];
|
||||
params.endDate = createRange.value[1];
|
||||
}
|
||||
|
||||
if (stockStatus === "IN_STOCK") {
|
||||
params.geQuantity = stockMin !== "" && stockMin != null ? Number(stockMin) : 1;
|
||||
if (stockMax !== "" && stockMax != null) {
|
||||
params.leQuantity = Number(stockMax);
|
||||
}
|
||||
} else if (stockStatus === "OUT_OF_STOCK") {
|
||||
params.geQuantity = 0;
|
||||
params.leQuantity = 0;
|
||||
} else {
|
||||
if (stockMin !== "" && stockMin != null) params.geQuantity = Number(stockMin);
|
||||
if (stockMax !== "" && stockMax != null) params.leQuantity = Number(stockMax);
|
||||
}
|
||||
|
||||
if (priceMin !== "" && priceMax !== "") {
|
||||
params.price = `${priceMin}_${priceMax}`;
|
||||
} else if (priceMin !== "") {
|
||||
params.price = String(priceMin);
|
||||
} else if (priceMax !== "") {
|
||||
params.price = `0_${priceMax}`;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
function formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.labelName,
|
||||
children:
|
||||
item.children && item.children.length
|
||||
? formatShopCategory(item.children)
|
||||
: undefined,
|
||||
}));
|
||||
}
|
||||
// 分页 改变页码
|
||||
function changePage(v) {
|
||||
searchForm.value.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
// 分页 改变页数
|
||||
function changePageSize(v) {
|
||||
searchForm.value.pageSize = v;
|
||||
getDataList();
|
||||
}
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.pageSize = 20;
|
||||
getDataList();
|
||||
getNumberData();
|
||||
}
|
||||
// 获取数据
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getGoodsListData(buildSearchParams()).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
function getNumberData() {
|
||||
getGoodsNumerData(buildSearchParams(false)).then((res) => {
|
||||
if (res.success) {
|
||||
goodsNumerData.value = res.result;
|
||||
}
|
||||
})
|
||||
}
|
||||
// 编辑
|
||||
function edit(v) {
|
||||
id.value = v.id;
|
||||
if (v.underMessage) {
|
||||
underForm.value.reason = v.underMessage;
|
||||
} else {
|
||||
underForm.value.reason = "";
|
||||
}
|
||||
modalVisible.value = true;
|
||||
}
|
||||
// 下架
|
||||
function lower() {
|
||||
let params = {
|
||||
goodsId: id.value,
|
||||
reason: underForm.value.reason
|
||||
};
|
||||
lowGoods(params).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("操作成功");
|
||||
modalVisible.value = false;
|
||||
getDataList();
|
||||
getNumberData(); // 添加这行
|
||||
}
|
||||
});
|
||||
}
|
||||
// 上架
|
||||
function upper(v) {
|
||||
Modal.confirm({
|
||||
title: "确认上架",
|
||||
content: "您确认要上架 " + v.goodsName + " ?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
let params = {
|
||||
goodsId: v.id
|
||||
};
|
||||
upGoods(params).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("上架成功");
|
||||
getDataList();
|
||||
getNumberData(); // 添加这行
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑商品
|
||||
function editGoods(v) {
|
||||
customRouterPush({
|
||||
name: "goods-operation-edit",
|
||||
query: { id: v.id },
|
||||
});
|
||||
}
|
||||
|
||||
// 复制商品
|
||||
function copyGoods(v) {
|
||||
customRouterPush({
|
||||
name: "goods-operation",
|
||||
query: { copyId: v.id },
|
||||
});
|
||||
}
|
||||
|
||||
// 商品状态筛选
|
||||
function goodsStatusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined || name === null) {
|
||||
delete searchForm.value.goodsStatus;
|
||||
currentStatus.value = "ALL";
|
||||
} else {
|
||||
searchForm.value.goodsStatus = name;
|
||||
currentStatus.value = name;
|
||||
}
|
||||
selectedRows.value = [];
|
||||
clearTableSelection();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function onSelectionChange(selection) {
|
||||
selectedRows.value = selection;
|
||||
}
|
||||
|
||||
// 批量上架
|
||||
function batchUpper() {
|
||||
if (selectedRows.value.length === 0) {
|
||||
Message.warning('请先选择要上架的商品');
|
||||
return;
|
||||
}
|
||||
|
||||
const goodsNames = selectedRows.value.map(item => item.goodsName).join('、');
|
||||
Modal.confirm({
|
||||
title: '确认批量上架',
|
||||
content: `您确认要上架以下商品吗?\n${goodsNames}`,
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
// 提取所有选中商品的ID
|
||||
const goodsIds = selectedRows.value.map(item => item.id);
|
||||
const params = {
|
||||
goodsId: goodsIds // 传递ID数组
|
||||
};
|
||||
|
||||
upGoods(params).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success('批量上架成功');
|
||||
selectedRows.value = [];
|
||||
selectAll.value = false;
|
||||
getDataList();
|
||||
getNumberData();
|
||||
}
|
||||
}).catch(() => {
|
||||
Modal.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 批量下架
|
||||
function batchLower() {
|
||||
if (selectedRows.value.length === 0) {
|
||||
Message.warning('请先选择要下架的商品');
|
||||
return;
|
||||
}
|
||||
|
||||
const goodsNames = selectedRows.value.map(item => item.goodsName).join('、');
|
||||
Modal.confirm({
|
||||
title: '确认批量下架',
|
||||
content: `您确认要下架以下商品吗?\n${goodsNames}`,
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
// 提取所有选中商品的ID
|
||||
const goodsIds = selectedRows.value.map(item => item.id);
|
||||
const params = {
|
||||
goodsId: goodsIds, // 传递ID数组
|
||||
reason: '批量下架操作' // 可以设置默认下架原因
|
||||
};
|
||||
|
||||
lowGoods(params).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success('批量下架成功');
|
||||
selectedRows.value = [];
|
||||
selectAll.value = false;
|
||||
getDataList();
|
||||
getNumberData();
|
||||
}
|
||||
}).catch(() => {
|
||||
Modal.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const goodsStatus = route.query.goodsStatus;
|
||||
if (goodsStatus) {
|
||||
currentStatus.value = goodsStatus;
|
||||
searchForm.value.goodsStatus = goodsStatus;
|
||||
}
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search-form-row {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</el-steps>
|
||||
</div>
|
||||
<!-- 第一步 选择分类 -->
|
||||
<first-step ref='first' v-show="activestep === 0" @change="getFirstData"></first-step>
|
||||
<first-step ref='firstRef' v-show="activestep === 0" @change="getFirstData"></first-step>
|
||||
<!-- 第二步 商品详细信息 -->
|
||||
<second-step ref='second' :firstData="firstData" v-if="activestep === 1"></second-step>
|
||||
<!-- 第三步 发布完成 -->
|
||||
@@ -18,43 +18,41 @@
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import firstStep from './goodsOperationFirst'
|
||||
import secondStep from './goodsOperationSec'
|
||||
import thirdStep from './goodsOperationThird'
|
||||
export default {
|
||||
name: "addGoods",
|
||||
components: {
|
||||
firstStep,
|
||||
secondStep,
|
||||
thirdStep
|
||||
},
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import firstStep from './goodsOperationFirst'
|
||||
import secondStep from './goodsOperationSec'
|
||||
import thirdStep from './goodsOperationThird'
|
||||
|
||||
data() {
|
||||
return {
|
||||
/** 当前激活步骤*/
|
||||
activestep: 0,
|
||||
firstData: {}, // 第一步传递的数据
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 选择商品分类回调
|
||||
getFirstData (item) {
|
||||
this.firstData = item;
|
||||
this.activestep = 1;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 编辑商品、模板、复制商品
|
||||
if (this.$route.query.id || this.$route.query.draftId || this.$route.query.copyId) {
|
||||
this.activestep = 1;
|
||||
} else {
|
||||
this.activestep = 0
|
||||
this.$refs.first.selectGoodsType = true;
|
||||
}
|
||||
|
||||
defineOptions({ name: "addGoods" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const firstRef = ref(null);
|
||||
|
||||
/** 当前激活步骤*/
|
||||
const activestep = ref(0);
|
||||
const firstData = ref({}); // 第一步传递的数据
|
||||
|
||||
// 子组件 goodsOperationSec 通过 $parent.activestep-- 回退一步
|
||||
defineExpose({ activestep });
|
||||
|
||||
// 选择商品分类回调
|
||||
function getFirstData (item) {
|
||||
firstData.value = item;
|
||||
activestep.value = 1;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 编辑商品、模板、复制商品
|
||||
if (route.query.id || route.query.draftId || route.query.copyId) {
|
||||
activestep.value = 1;
|
||||
} else {
|
||||
activestep.value = 0
|
||||
firstRef.value.selectGoodsType = true;
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./addGoods.scss";
|
||||
|
||||
@@ -85,143 +85,142 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, markRaw, onMounted, ref, watch } from "vue";
|
||||
import * as API_GOODS from "@/api/goods";
|
||||
import { Box, Calendar, CreditCard, Ticket } from "@element-plus/icons-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
components: { Box, Calendar, CreditCard, Ticket },
|
||||
data() {
|
||||
return {
|
||||
selectGoodsType: false,
|
||||
goodsTypeWay: [
|
||||
{
|
||||
title: "实物商品",
|
||||
desc: "零售批发,物流配送",
|
||||
type: "PHYSICAL_GOODS",
|
||||
icon: "Box",
|
||||
tone: "tone-physical",
|
||||
},
|
||||
{
|
||||
title: "虚拟商品",
|
||||
desc: "虚拟核验,无需物流",
|
||||
type: "VIRTUAL_GOODS",
|
||||
icon: "Ticket",
|
||||
tone: "tone-virtual",
|
||||
},
|
||||
{
|
||||
title: "电子卡券",
|
||||
desc: "卡密自动发卡,无需物流",
|
||||
type: "E_COUPON",
|
||||
icon: "CreditCard",
|
||||
tone: "tone-coupon",
|
||||
},
|
||||
{
|
||||
title: "预约商品",
|
||||
desc: "时段预约,到店或上门",
|
||||
type: "APPOINTMENT_GOODS",
|
||||
icon: "Calendar",
|
||||
tone: "tone-appointment",
|
||||
},
|
||||
],
|
||||
category: [
|
||||
{ name: "", id: "" },
|
||||
{ name: "", id: "" },
|
||||
{ name: "", id: "" },
|
||||
],
|
||||
goodsType: "",
|
||||
pendingGoodsType: "",
|
||||
categoryListLevel1: [],
|
||||
categoryListLevel2: [],
|
||||
categoryListLevel3: [],
|
||||
};
|
||||
const emit = defineEmits(["change"]);
|
||||
|
||||
const selectGoodsType = ref(false);
|
||||
// icon 直接存组件对象:<script setup> 下无 components 选项,字符串名无法在 <component :is> 中解析
|
||||
const goodsTypeWay = ref([
|
||||
{
|
||||
title: "实物商品",
|
||||
desc: "零售批发,物流配送",
|
||||
type: "PHYSICAL_GOODS",
|
||||
icon: markRaw(Box),
|
||||
tone: "tone-physical",
|
||||
},
|
||||
computed: {
|
||||
selectedTypeTitle() {
|
||||
const hit = this.goodsTypeWay.find((item) => item.type === this.goodsType);
|
||||
return hit ? hit.title : "";
|
||||
},
|
||||
{
|
||||
title: "虚拟商品",
|
||||
desc: "虚拟核验,无需物流",
|
||||
type: "VIRTUAL_GOODS",
|
||||
icon: markRaw(Ticket),
|
||||
tone: "tone-virtual",
|
||||
},
|
||||
watch: {
|
||||
selectGoodsType(val) {
|
||||
if (val) {
|
||||
this.syncGoodsTypeSelection();
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "电子卡券",
|
||||
desc: "卡密自动发卡,无需物流",
|
||||
type: "E_COUPON",
|
||||
icon: markRaw(CreditCard),
|
||||
tone: "tone-coupon",
|
||||
},
|
||||
methods: {
|
||||
syncGoodsTypeSelection() {
|
||||
this.pendingGoodsType = this.goodsType;
|
||||
},
|
||||
openGoodsTypeDialog() {
|
||||
this.selectGoodsType = true;
|
||||
},
|
||||
handleClickGoodsType(item) {
|
||||
this.pendingGoodsType = item.type;
|
||||
},
|
||||
cancelGoodsType() {
|
||||
this.syncGoodsTypeSelection();
|
||||
this.selectGoodsType = false;
|
||||
},
|
||||
confirmGoodsType() {
|
||||
if (!this.pendingGoodsType) {
|
||||
this.$Message.error("请选择商品类型");
|
||||
return;
|
||||
}
|
||||
this.goodsType = this.pendingGoodsType;
|
||||
this.selectGoodsType = false;
|
||||
},
|
||||
handleSelectCategory(row, index, level) {
|
||||
if (level === 1) {
|
||||
this.category.forEach((cate) => {
|
||||
(cate.name = ""), (cate.id = "");
|
||||
});
|
||||
this.category[0].name = row.name;
|
||||
this.category[0].id = row.id;
|
||||
this.categoryListLevel2 = this.categoryListLevel1[index].children;
|
||||
this.categoryListLevel3 = [];
|
||||
} else if (level === 2) {
|
||||
this.category[1].name = row.name;
|
||||
this.category[1].id = row.id;
|
||||
this.category[2].name = "";
|
||||
this.category[2].id = "";
|
||||
this.categoryListLevel3 = this.categoryListLevel2[index].children;
|
||||
} else {
|
||||
this.category[2].name = row.name;
|
||||
this.category[2].id = row.id;
|
||||
}
|
||||
},
|
||||
GET_NextLevelCategory() {
|
||||
API_GOODS.getGoodsCategoryAll().then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.categoryListLevel1 = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
next() {
|
||||
window.scrollTo(0, 0);
|
||||
if (!this.goodsType) {
|
||||
this.$Message.error("请选择商品类型");
|
||||
this.openGoodsTypeDialog();
|
||||
return;
|
||||
}
|
||||
if (!this.category[0].name) {
|
||||
this.$Message.error("请选择商品分类");
|
||||
return;
|
||||
} else if (!this.category[2].name) {
|
||||
this.$Message.error("必须选择到三级分类");
|
||||
return;
|
||||
}
|
||||
this.$emit("change", {
|
||||
category: this.category,
|
||||
goodsType: this.goodsType,
|
||||
});
|
||||
},
|
||||
{
|
||||
title: "预约商品",
|
||||
desc: "时段预约,到店或上门",
|
||||
type: "APPOINTMENT_GOODS",
|
||||
icon: markRaw(Calendar),
|
||||
tone: "tone-appointment",
|
||||
},
|
||||
mounted() {
|
||||
this.GET_NextLevelCategory();
|
||||
},
|
||||
};
|
||||
]);
|
||||
const category = ref([
|
||||
{ name: "", id: "" },
|
||||
{ name: "", id: "" },
|
||||
{ name: "", id: "" },
|
||||
]);
|
||||
const goodsType = ref("");
|
||||
const pendingGoodsType = ref("");
|
||||
const categoryListLevel1 = ref([]);
|
||||
const categoryListLevel2 = ref([]);
|
||||
const categoryListLevel3 = ref([]);
|
||||
|
||||
const selectedTypeTitle = computed(() => {
|
||||
const hit = goodsTypeWay.value.find((item) => item.type === goodsType.value);
|
||||
return hit ? hit.title : "";
|
||||
});
|
||||
|
||||
watch(selectGoodsType, (val) => {
|
||||
if (val) {
|
||||
syncGoodsTypeSelection();
|
||||
}
|
||||
});
|
||||
|
||||
function syncGoodsTypeSelection() {
|
||||
pendingGoodsType.value = goodsType.value;
|
||||
}
|
||||
function openGoodsTypeDialog() {
|
||||
selectGoodsType.value = true;
|
||||
}
|
||||
function handleClickGoodsType(item) {
|
||||
pendingGoodsType.value = item.type;
|
||||
}
|
||||
function cancelGoodsType() {
|
||||
syncGoodsTypeSelection();
|
||||
selectGoodsType.value = false;
|
||||
}
|
||||
function confirmGoodsType() {
|
||||
if (!pendingGoodsType.value) {
|
||||
Message.error("请选择商品类型");
|
||||
return;
|
||||
}
|
||||
goodsType.value = pendingGoodsType.value;
|
||||
selectGoodsType.value = false;
|
||||
}
|
||||
function handleSelectCategory(row, index, level) {
|
||||
if (level === 1) {
|
||||
category.value.forEach((cate) => {
|
||||
(cate.name = ""), (cate.id = "");
|
||||
});
|
||||
category.value[0].name = row.name;
|
||||
category.value[0].id = row.id;
|
||||
categoryListLevel2.value = categoryListLevel1.value[index].children;
|
||||
categoryListLevel3.value = [];
|
||||
} else if (level === 2) {
|
||||
category.value[1].name = row.name;
|
||||
category.value[1].id = row.id;
|
||||
category.value[2].name = "";
|
||||
category.value[2].id = "";
|
||||
categoryListLevel3.value = categoryListLevel2.value[index].children;
|
||||
} else {
|
||||
category.value[2].name = row.name;
|
||||
category.value[2].id = row.id;
|
||||
}
|
||||
}
|
||||
function GET_NextLevelCategory() {
|
||||
API_GOODS.getGoodsCategoryAll().then((res) => {
|
||||
if (res.success && res.result) {
|
||||
categoryListLevel1.value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
function next() {
|
||||
window.scrollTo(0, 0);
|
||||
if (!goodsType.value) {
|
||||
Message.error("请选择商品类型");
|
||||
openGoodsTypeDialog();
|
||||
return;
|
||||
}
|
||||
if (!category.value[0].name) {
|
||||
Message.error("请选择商品分类");
|
||||
return;
|
||||
} else if (!category.value[2].name) {
|
||||
Message.error("必须选择到三级分类");
|
||||
return;
|
||||
}
|
||||
emit("change", {
|
||||
category: category.value,
|
||||
goodsType: goodsType.value,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
GET_NextLevelCategory();
|
||||
});
|
||||
|
||||
// 父组件 goodsOperation.vue 通过 ref 直接赋值 selectGoodsType 打开弹窗
|
||||
defineExpose({ selectGoodsType });
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./addGoods.scss";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,18 +16,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
// 跳转商品列表
|
||||
gotoGoodsList() {
|
||||
this.$router.push({name: "goods"});
|
||||
},
|
||||
// 刷新页面
|
||||
gotoBack() {
|
||||
this.$router.go();
|
||||
},
|
||||
}
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 跳转商品列表
|
||||
function gotoGoodsList() {
|
||||
router.push({name: "goods"});
|
||||
}
|
||||
|
||||
// 刷新页面
|
||||
function gotoBack() {
|
||||
router.go();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -207,7 +207,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import defaultLogo from "@/assets/logo1.png";
|
||||
import { getSellerHomeData, getHomeNotice } from "@/api/index";
|
||||
import { getOrderOverView, goodsStatistics } from "@/api/goods";
|
||||
@@ -217,6 +219,12 @@ import { getIMDetail } from "@/api/common";
|
||||
import { seeArticle } from "@/api/pages";
|
||||
import Cookies from "js-cookie";
|
||||
import { userMsg } from "@/api/index";
|
||||
import { Message } from "@/utils/message";
|
||||
import { getStore } from "@/libs/storage";
|
||||
|
||||
defineOptions({ name: "home" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const HOME_ROUTE_TARGETS = {
|
||||
orderList: { name: "orderList", path: "/order/orderList" },
|
||||
@@ -237,161 +245,153 @@ const HOME_ROUTE_TARGETS = {
|
||||
|
||||
const AFTER_SALE_RETURN_GOODS_STATUSES = ["PASS", "BUYER_RETURN"];
|
||||
|
||||
export default {
|
||||
name: "home",
|
||||
data() {
|
||||
return {
|
||||
defaultLogo,
|
||||
noticeFlage: false,
|
||||
homeData: {},
|
||||
overviewToday: {},
|
||||
overviewYesterday: {},
|
||||
topGoodsData: [],
|
||||
afterSaleNumData: {},
|
||||
procurementStatusCount: {},
|
||||
userData: {},
|
||||
notices: "",
|
||||
noticesDetail: {
|
||||
title: "",
|
||||
},
|
||||
IMLink: "",
|
||||
load: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
navigateAfterSale(serviceStatus) {
|
||||
const routeKey = AFTER_SALE_RETURN_GOODS_STATUSES.includes(serviceStatus)
|
||||
? "returnGoodsOrder"
|
||||
: "returnMoneyOrder";
|
||||
this.navigateTo(routeKey, { serviceStatus });
|
||||
},
|
||||
navigateToProcurementAudit() {
|
||||
this.navigateTo("procurementPurchaseOrderList", { status: "SUBMITTED" });
|
||||
},
|
||||
navigateTo(key, query) {
|
||||
const target = HOME_ROUTE_TARGETS[key] || { name: key };
|
||||
if (target.name && this.$router.hasRoute(target.name)) {
|
||||
this.$router.push({ name: target.name, query });
|
||||
return;
|
||||
}
|
||||
if (target.path) {
|
||||
const resolved = this.$router.resolve({ path: target.path, query });
|
||||
if (resolved.matched.length > 0) {
|
||||
this.$router.push({ path: target.path, query });
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.$Message.warning("暂无权限访问该页面");
|
||||
},
|
||||
compareText(today, yesterday) {
|
||||
const t = Number(today) || 0;
|
||||
const y = Number(yesterday) || 0;
|
||||
if (y === 0) {
|
||||
return t > 0 ? "↑ --" : "—";
|
||||
}
|
||||
const rate = (((t - y) / y) * 100).toFixed(1);
|
||||
if (t > y) return `↑ ${rate}%`;
|
||||
if (t < y) return `↓ ${Math.abs(rate)}%`;
|
||||
return "—";
|
||||
},
|
||||
compareClass(today, yesterday) {
|
||||
const t = Number(today) || 0;
|
||||
const y = Number(yesterday) || 0;
|
||||
if (t > y) return "compare-up";
|
||||
if (t < y) return "compare-down";
|
||||
return "compare-flat";
|
||||
},
|
||||
// ElProgress 的 percentage 必须是 0-100 的数字,评分缺失/异常时兜底为 0
|
||||
scorePercent(score) {
|
||||
const n = Number(score) * 20;
|
||||
return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : 0;
|
||||
},
|
||||
async init() {
|
||||
const userInfo = JSON.parse(Cookies.get("userInfoSeller"));
|
||||
this.userData = userInfo;
|
||||
const noticeFlage = ref(false);
|
||||
const homeData = ref({});
|
||||
const overviewToday = ref({});
|
||||
const overviewYesterday = ref({});
|
||||
const topGoodsData = ref([]);
|
||||
const afterSaleNumData = ref({});
|
||||
const procurementStatusCount = ref({});
|
||||
const userData = ref({});
|
||||
const notices = ref("");
|
||||
const noticesDetail = ref({
|
||||
title: "",
|
||||
});
|
||||
const IMLink = ref("");
|
||||
const load = ref(false);
|
||||
|
||||
const res = await getHomeNotice();
|
||||
if (res.success) {
|
||||
this.notices = res.result.records;
|
||||
}
|
||||
},
|
||||
async clickLinkNotices(val) {
|
||||
const res = await seeArticle(val.id);
|
||||
if (res.success) {
|
||||
this.noticesDetail = res.result;
|
||||
this.noticeFlage = true;
|
||||
}
|
||||
},
|
||||
async im() {
|
||||
const accessToken = this.getStore("accessToken");
|
||||
this.load = true;
|
||||
await this.getIMDetailMethods();
|
||||
const userInfo = await userMsg();
|
||||
this.load = false;
|
||||
if (userInfo.success && this.IMLink) {
|
||||
window.open(`${this.IMLink}?token=` + accessToken);
|
||||
} else {
|
||||
this.$Message.error("请登录后再联系客服");
|
||||
}
|
||||
},
|
||||
async getIMDetailMethods() {
|
||||
const res = await getIMDetail();
|
||||
if (res.success) {
|
||||
this.IMLink = res.result;
|
||||
}
|
||||
},
|
||||
async getHomeData() {
|
||||
const res = await getSellerHomeData();
|
||||
if (res.success) {
|
||||
this.homeData = res.result;
|
||||
}
|
||||
},
|
||||
async loadOverview() {
|
||||
const year = new Date().getFullYear();
|
||||
const [todayRes, yesterdayRes] = await Promise.all([
|
||||
getOrderOverView({ searchType: "TODAY", year }),
|
||||
getOrderOverView({ searchType: "YESTERDAY", year }),
|
||||
]);
|
||||
if (todayRes.success) {
|
||||
this.overviewToday = todayRes.result || {};
|
||||
}
|
||||
if (yesterdayRes.success) {
|
||||
this.overviewYesterday = yesterdayRes.result || {};
|
||||
}
|
||||
},
|
||||
async loadSalesRank() {
|
||||
const year = new Date().getFullYear();
|
||||
const res = await goodsStatistics({
|
||||
searchType: "LAST_SEVEN",
|
||||
year,
|
||||
type: "NUM",
|
||||
});
|
||||
if (res.success && res.result) {
|
||||
this.topGoodsData = res.result.slice(0, 10);
|
||||
}
|
||||
},
|
||||
async loadAfterSaleNum() {
|
||||
const res = await getAfterSaleNumVO({});
|
||||
if (res.success) {
|
||||
this.afterSaleNumData = res.result || {};
|
||||
}
|
||||
},
|
||||
async loadProcurementStatusCount() {
|
||||
const res = await getProcurementOrderStatusCount({});
|
||||
if (res.success) {
|
||||
this.procurementStatusCount = res.result || {};
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
this.getHomeData();
|
||||
this.loadOverview();
|
||||
this.loadSalesRank();
|
||||
this.loadAfterSaleNum();
|
||||
this.loadProcurementStatusCount();
|
||||
},
|
||||
};
|
||||
function navigateAfterSale(serviceStatus) {
|
||||
const routeKey = AFTER_SALE_RETURN_GOODS_STATUSES.includes(serviceStatus)
|
||||
? "returnGoodsOrder"
|
||||
: "returnMoneyOrder";
|
||||
navigateTo(routeKey, { serviceStatus });
|
||||
}
|
||||
function navigateToProcurementAudit() {
|
||||
navigateTo("procurementPurchaseOrderList", { status: "SUBMITTED" });
|
||||
}
|
||||
function navigateTo(key, query) {
|
||||
const target = HOME_ROUTE_TARGETS[key] || { name: key };
|
||||
if (target.name && router.hasRoute(target.name)) {
|
||||
router.push({ name: target.name, query });
|
||||
return;
|
||||
}
|
||||
if (target.path) {
|
||||
const resolved = router.resolve({ path: target.path, query });
|
||||
if (resolved.matched.length > 0) {
|
||||
router.push({ path: target.path, query });
|
||||
return;
|
||||
}
|
||||
}
|
||||
Message.warning("暂无权限访问该页面");
|
||||
}
|
||||
function compareText(today, yesterday) {
|
||||
const t = Number(today) || 0;
|
||||
const y = Number(yesterday) || 0;
|
||||
if (y === 0) {
|
||||
return t > 0 ? "↑ --" : "—";
|
||||
}
|
||||
const rate = (((t - y) / y) * 100).toFixed(1);
|
||||
if (t > y) return `↑ ${rate}%`;
|
||||
if (t < y) return `↓ ${Math.abs(rate)}%`;
|
||||
return "—";
|
||||
}
|
||||
function compareClass(today, yesterday) {
|
||||
const t = Number(today) || 0;
|
||||
const y = Number(yesterday) || 0;
|
||||
if (t > y) return "compare-up";
|
||||
if (t < y) return "compare-down";
|
||||
return "compare-flat";
|
||||
}
|
||||
// ElProgress 的 percentage 必须是 0-100 的数字,评分缺失/异常时兜底为 0
|
||||
function scorePercent(score) {
|
||||
const n = Number(score) * 20;
|
||||
return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : 0;
|
||||
}
|
||||
async function init() {
|
||||
const userInfo = JSON.parse(Cookies.get("userInfoSeller"));
|
||||
userData.value = userInfo;
|
||||
|
||||
const res = await getHomeNotice();
|
||||
if (res.success) {
|
||||
notices.value = res.result.records;
|
||||
}
|
||||
}
|
||||
async function clickLinkNotices(val) {
|
||||
const res = await seeArticle(val.id);
|
||||
if (res.success) {
|
||||
noticesDetail.value = res.result;
|
||||
noticeFlage.value = true;
|
||||
}
|
||||
}
|
||||
async function im() {
|
||||
const accessToken = getStore("accessToken");
|
||||
load.value = true;
|
||||
await getIMDetailMethods();
|
||||
const userInfo = await userMsg();
|
||||
load.value = false;
|
||||
if (userInfo.success && IMLink.value) {
|
||||
window.open(`${IMLink.value}?token=` + accessToken);
|
||||
} else {
|
||||
Message.error("请登录后再联系客服");
|
||||
}
|
||||
}
|
||||
async function getIMDetailMethods() {
|
||||
const res = await getIMDetail();
|
||||
if (res.success) {
|
||||
IMLink.value = res.result;
|
||||
}
|
||||
}
|
||||
async function getHomeData() {
|
||||
const res = await getSellerHomeData();
|
||||
if (res.success) {
|
||||
homeData.value = res.result;
|
||||
}
|
||||
}
|
||||
async function loadOverview() {
|
||||
const year = new Date().getFullYear();
|
||||
const [todayRes, yesterdayRes] = await Promise.all([
|
||||
getOrderOverView({ searchType: "TODAY", year }),
|
||||
getOrderOverView({ searchType: "YESTERDAY", year }),
|
||||
]);
|
||||
if (todayRes.success) {
|
||||
overviewToday.value = todayRes.result || {};
|
||||
}
|
||||
if (yesterdayRes.success) {
|
||||
overviewYesterday.value = yesterdayRes.result || {};
|
||||
}
|
||||
}
|
||||
async function loadSalesRank() {
|
||||
const year = new Date().getFullYear();
|
||||
const res = await goodsStatistics({
|
||||
searchType: "LAST_SEVEN",
|
||||
year,
|
||||
type: "NUM",
|
||||
});
|
||||
if (res.success && res.result) {
|
||||
topGoodsData.value = res.result.slice(0, 10);
|
||||
}
|
||||
}
|
||||
async function loadAfterSaleNum() {
|
||||
const res = await getAfterSaleNumVO({});
|
||||
if (res.success) {
|
||||
afterSaleNumData.value = res.result || {};
|
||||
}
|
||||
}
|
||||
async function loadProcurementStatusCount() {
|
||||
const res = await getProcurementOrderStatusCount({});
|
||||
if (res.success) {
|
||||
procurementStatusCount.value = res.result || {};
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
getHomeData();
|
||||
loadOverview();
|
||||
loadSalesRank();
|
||||
loadAfterSaleNum();
|
||||
loadProcurementStatusCount();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -46,120 +46,117 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
|
||||
export default {
|
||||
props: ["closeShop"],
|
||||
data() {
|
||||
return {
|
||||
month: "",
|
||||
selectedWay: {
|
||||
title: "过去7天",
|
||||
selected: true,
|
||||
searchType: "LAST_SEVEN",
|
||||
},
|
||||
storeId: "",
|
||||
dates: [],
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
storeName: "",
|
||||
},
|
||||
dateList: [
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
],
|
||||
originDateList: [
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
],
|
||||
shopTotal: 0,
|
||||
shopsData: [],
|
||||
const props = defineProps(["closeShop"]);
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const month = ref("");
|
||||
const selectedWay = ref({
|
||||
title: "过去7天",
|
||||
selected: true,
|
||||
searchType: "LAST_SEVEN",
|
||||
});
|
||||
const storeId = ref("");
|
||||
const dates = ref([]);
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
storeName: "",
|
||||
});
|
||||
const dateList = ref([
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
]);
|
||||
const originDateList = ref([
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
]);
|
||||
const shopTotal = ref(0);
|
||||
const shopsData = ref([]);
|
||||
|
||||
function getShopList() {
|
||||
getShopListData(params).then((res) => {
|
||||
if (res.success) {
|
||||
shopTotal.value = res.result.total;
|
||||
shopsData.value = res.result.records || [];
|
||||
}
|
||||
});
|
||||
}
|
||||
function changeshop() {
|
||||
selectedWay.value.storeId = storeId.value;
|
||||
emit("selected", selectedWay.value);
|
||||
}
|
||||
function getFiveYears() {
|
||||
const getYear = new Date().getFullYear();
|
||||
const lastFiveYear = getYear - 5;
|
||||
const maxMonth = new Date().getMonth() + 1;
|
||||
const dateArr = [];
|
||||
for (let year = lastFiveYear; year <= getYear; year++) {
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
if (year === getYear && month > maxMonth) {
|
||||
continue;
|
||||
}
|
||||
dateArr.push({ year, month });
|
||||
}
|
||||
}
|
||||
dates.value = dateArr.reverse();
|
||||
}
|
||||
function changeSelect(e) {
|
||||
month.value = e;
|
||||
if (month.value) {
|
||||
dateList.value.forEach((res) => {
|
||||
res.selected = false;
|
||||
});
|
||||
const parts = String(month.value).split("-");
|
||||
selectedWay.value = {
|
||||
...selectedWay.value,
|
||||
year: parts[0],
|
||||
month: parts[1],
|
||||
searchType: "",
|
||||
storeId: storeId.value,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getFiveYears();
|
||||
if (!this.closeShop) this.getShopList();
|
||||
},
|
||||
methods: {
|
||||
getShopList() {
|
||||
getShopListData(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
this.shopTotal = res.result.total;
|
||||
this.shopsData = res.result.records || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
changeshop() {
|
||||
this.selectedWay.storeId = this.storeId;
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
getFiveYears() {
|
||||
const getYear = new Date().getFullYear();
|
||||
const lastFiveYear = getYear - 5;
|
||||
const maxMonth = new Date().getMonth() + 1;
|
||||
const dates = [];
|
||||
for (let year = lastFiveYear; year <= getYear; year++) {
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
if (year === getYear && month > maxMonth) {
|
||||
continue;
|
||||
}
|
||||
dates.push({ year, month });
|
||||
}
|
||||
}
|
||||
this.dates = dates.reverse();
|
||||
},
|
||||
changeSelect(e) {
|
||||
this.month = e;
|
||||
if (this.month) {
|
||||
this.dateList.forEach((res) => {
|
||||
res.selected = false;
|
||||
});
|
||||
const parts = String(this.month).split("-");
|
||||
this.selectedWay = {
|
||||
...this.selectedWay,
|
||||
year: parts[0],
|
||||
month: parts[1],
|
||||
searchType: "",
|
||||
storeId: this.storeId,
|
||||
};
|
||||
this.$emit("selected", this.selectedWay);
|
||||
} else {
|
||||
const current =
|
||||
this.dateList.find((item) => item.selected) ||
|
||||
this.dateList.find((item) => item.title === this.selectedWay.title) ||
|
||||
this.dateList.find((item) => item.searchType === "LAST_SEVEN");
|
||||
this.clickBreadcrumb(current);
|
||||
}
|
||||
},
|
||||
clickBreadcrumb(item) {
|
||||
if (!item) return;
|
||||
let currentIndex;
|
||||
this.dateList.forEach((res, index) => {
|
||||
res.selected = false;
|
||||
if (res.title === item.title) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
item.selected = true;
|
||||
item.storeId = this.storeId;
|
||||
this.month = "";
|
||||
if (item.searchType === "") {
|
||||
const origin = this.originDateList[currentIndex];
|
||||
item.searchType = (origin && origin.searchType) || "LAST_SEVEN";
|
||||
}
|
||||
this.selectedWay = item;
|
||||
this.selectedWay.year = new Date().getFullYear();
|
||||
this.selectedWay.month = "";
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
},
|
||||
};
|
||||
emit("selected", selectedWay.value);
|
||||
} else {
|
||||
const current =
|
||||
dateList.value.find((item) => item.selected) ||
|
||||
dateList.value.find((item) => item.title === selectedWay.value.title) ||
|
||||
dateList.value.find((item) => item.searchType === "LAST_SEVEN");
|
||||
clickBreadcrumb(current);
|
||||
}
|
||||
}
|
||||
function clickBreadcrumb(item) {
|
||||
if (!item) return;
|
||||
let currentIndex;
|
||||
dateList.value.forEach((res, index) => {
|
||||
res.selected = false;
|
||||
if (res.title === item.title) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
item.selected = true;
|
||||
item.storeId = storeId.value;
|
||||
month.value = "";
|
||||
if (item.searchType === "") {
|
||||
const origin = originDateList.value[currentIndex];
|
||||
item.searchType = (origin && origin.searchType) || "LAST_SEVEN";
|
||||
}
|
||||
selectedWay.value = item;
|
||||
selectedWay.value.year = new Date().getFullYear();
|
||||
selectedWay.value.month = "";
|
||||
emit("selected", selectedWay.value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getFiveYears();
|
||||
if (!props.closeShop) getShopList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -8,172 +8,174 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { initEditor } from "@/views/lili-components/editor/config";
|
||||
import { loadTinymce } from "@/views/lili-components/editor/load-tinymce";
|
||||
|
||||
import uploadImage from "@/views/lili-components/editor/upload-image.vue";
|
||||
export default {
|
||||
components:{uploadImage},
|
||||
name: "Tinymce",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
height:{
|
||||
type:String,
|
||||
default:'500px'
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue", "input"],
|
||||
data() {
|
||||
return {
|
||||
// 引入编辑器的配置
|
||||
initEditor,
|
||||
hasChange: false, // 标记内容是否有更改
|
||||
hasInit: false, // 标记编辑器是否已初始化
|
||||
pendingContent: null, // 编辑器未就绪时暂存待回填内容
|
||||
tinymceId:
|
||||
"tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""), // 生成唯一的编辑器 ID
|
||||
fullscreen: false, // 标记编辑器是否处于全屏模式
|
||||
toolbar: [], // 工具栏配置
|
||||
content: "", // 编辑器内容
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bindValue() {
|
||||
return this.modelValue ?? this.value ?? "";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
bindValue: {
|
||||
handler(val) {
|
||||
if (!this.hasChange) {
|
||||
this.setContent(val || "");
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitContent(content) {
|
||||
this.$emit("update:modelValue", content);
|
||||
this.$emit("input", content);
|
||||
},
|
||||
getEditor() {
|
||||
return window.tinymce?.get(this.tinymceId);
|
||||
},
|
||||
isEditorReady() {
|
||||
const editor = this.getEditor();
|
||||
return !!(editor && editor.initialized && editor.dom);
|
||||
},
|
||||
// 数据返回并给富文本框插入图片
|
||||
insertImage(arr){
|
||||
const editor = this.getEditor();
|
||||
if (!editor || !this.isEditorReady()) {
|
||||
return;
|
||||
}
|
||||
arr.forEach(v => editor.insertContent(`<img src="${v}" >`))
|
||||
},
|
||||
init() {
|
||||
// 初始化编辑器
|
||||
this.initTinymce();
|
||||
},
|
||||
initTinymce() {
|
||||
if (this.getEditor()) {
|
||||
return;
|
||||
}
|
||||
const _this = this;
|
||||
window.tinymce.init({
|
||||
selector: `#${this.tinymceId}`,
|
||||
convert_urls: false,
|
||||
init_instance_callback: (editor) => {
|
||||
_this.hasInit = true;
|
||||
const initial = _this.pendingContent ?? _this.bindValue;
|
||||
_this.pendingContent = null;
|
||||
if (initial) {
|
||||
_this.applyContent(editor, initial);
|
||||
}
|
||||
// 不监听 NodeChange/SetContent:初始化或回填时会把空内容写回 v-model,覆盖已加载的商品描述
|
||||
editor.on("input change undo redo KeyUp", () => {
|
||||
_this.hasChange = true;
|
||||
_this.emitContent(editor.getContent());
|
||||
});
|
||||
},
|
||||
setup(editor) {
|
||||
// 监听全屏状态变化
|
||||
editor.on("FullscreenStateChanged", (e) => {
|
||||
_this.fullscreen = e.state;
|
||||
});
|
||||
},
|
||||
..._this.initEditor,
|
||||
|
||||
height:this.height
|
||||
defineOptions({ name: "Tinymce" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "500px",
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input"]);
|
||||
|
||||
// 引入编辑器的配置(data 中的 initEditor 与 import 同名,直接使用 import)
|
||||
const hasChange = ref(false); // 标记内容是否有更改
|
||||
const hasInit = ref(false); // 标记编辑器是否已初始化
|
||||
const pendingContent = ref(null); // 编辑器未就绪时暂存待回填内容
|
||||
const tinymceId =
|
||||
"tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""); // 生成唯一的编辑器 ID
|
||||
const fullscreen = ref(false); // 标记编辑器是否处于全屏模式
|
||||
const toolbar = ref([]); // 工具栏配置
|
||||
const content = ref(""); // 编辑器内容
|
||||
|
||||
const bindValue = computed(() => {
|
||||
return props.modelValue ?? props.value ?? "";
|
||||
});
|
||||
|
||||
watch(bindValue, (val) => {
|
||||
if (!hasChange.value) {
|
||||
setContent(val || "");
|
||||
}
|
||||
});
|
||||
|
||||
function emitContent(val) {
|
||||
emit("update:modelValue", val);
|
||||
emit("input", val);
|
||||
}
|
||||
function getEditor() {
|
||||
return window.tinymce?.get(tinymceId);
|
||||
}
|
||||
function isEditorReady() {
|
||||
const editor = getEditor();
|
||||
return !!(editor && editor.initialized && editor.dom);
|
||||
}
|
||||
// 数据返回并给富文本框插入图片
|
||||
function insertImage(arr) {
|
||||
const editor = getEditor();
|
||||
if (!editor || !isEditorReady()) {
|
||||
return;
|
||||
}
|
||||
arr.forEach((v) => editor.insertContent(`<img src="${v}" >`));
|
||||
}
|
||||
function init() {
|
||||
// 初始化编辑器
|
||||
initTinymce();
|
||||
}
|
||||
function initTinymce() {
|
||||
if (getEditor()) {
|
||||
return;
|
||||
}
|
||||
window.tinymce.init({
|
||||
selector: `#${tinymceId}`,
|
||||
convert_urls: false,
|
||||
init_instance_callback: (editor) => {
|
||||
hasInit.value = true;
|
||||
const initial = pendingContent.value ?? bindValue.value;
|
||||
pendingContent.value = null;
|
||||
if (initial) {
|
||||
applyContent(editor, initial);
|
||||
}
|
||||
// 不监听 NodeChange/SetContent:初始化或回填时会把空内容写回 v-model,覆盖已加载的商品描述
|
||||
editor.on("input change undo redo KeyUp", () => {
|
||||
hasChange.value = true;
|
||||
emitContent(editor.getContent());
|
||||
});
|
||||
},
|
||||
applyContent(editor, content) {
|
||||
if (!editor || !editor.initialized || !editor.dom) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
editor.setContent(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn("tinymce setContent failed", e);
|
||||
return false;
|
||||
}
|
||||
setup(editor) {
|
||||
// 监听全屏状态变化
|
||||
editor.on("FullscreenStateChanged", (e) => {
|
||||
fullscreen.value = e.state;
|
||||
});
|
||||
},
|
||||
setContent(value) {
|
||||
const content = value || "";
|
||||
this.hasChange = false;
|
||||
if (!this.isEditorReady()) {
|
||||
this.pendingContent = content;
|
||||
return;
|
||||
}
|
||||
this.pendingContent = null;
|
||||
this.applyContent(this.getEditor(), content);
|
||||
this.emitContent(content);
|
||||
},
|
||||
getContent() {
|
||||
const editor = this.getEditor();
|
||||
if (!editor || !editor.initialized) {
|
||||
return this.pendingContent ?? this.bindValue ?? "";
|
||||
}
|
||||
return editor.getContent();
|
||||
},
|
||||
destroyTinymce() {
|
||||
const tinymce = this.getEditor();
|
||||
...initEditor,
|
||||
|
||||
if (tinymce) {
|
||||
// 销毁编辑器实例
|
||||
tinymce.destroy();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// 编辑器脚本改为按需加载,加载完成后再初始化
|
||||
height: props.height,
|
||||
});
|
||||
}
|
||||
function applyContent(editor, val) {
|
||||
if (!editor || !editor.initialized || !editor.dom) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
editor.setContent(val);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn("tinymce setContent failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function setContent(value) {
|
||||
const val = value || "";
|
||||
hasChange.value = false;
|
||||
if (!isEditorReady()) {
|
||||
pendingContent.value = val;
|
||||
return;
|
||||
}
|
||||
pendingContent.value = null;
|
||||
applyContent(getEditor(), val);
|
||||
emitContent(val);
|
||||
}
|
||||
function getContent() {
|
||||
const editor = getEditor();
|
||||
if (!editor || !editor.initialized) {
|
||||
return pendingContent.value ?? bindValue.value ?? "";
|
||||
}
|
||||
return editor.getContent();
|
||||
}
|
||||
function destroyTinymce() {
|
||||
const tinymce = getEditor();
|
||||
|
||||
if (tinymce) {
|
||||
// 销毁编辑器实例
|
||||
tinymce.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 编辑器脚本改为按需加载,加载完成后再初始化
|
||||
loadTinymce().then(() => {
|
||||
init();
|
||||
});
|
||||
});
|
||||
onActivated(() => {
|
||||
if (window.tinymce) {
|
||||
initTinymce();
|
||||
} else {
|
||||
loadTinymce().then(() => {
|
||||
this.init();
|
||||
initTinymce();
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
if (window.tinymce) {
|
||||
this.initTinymce();
|
||||
} else {
|
||||
loadTinymce().then(() => {
|
||||
this.initTinymce();
|
||||
});
|
||||
}
|
||||
},
|
||||
deactivated() {
|
||||
this.destroyTinymce();
|
||||
},
|
||||
unmounted() {
|
||||
this.destroyTinymce();
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
onDeactivated(() => {
|
||||
destroyTinymce();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
destroyTinymce();
|
||||
});
|
||||
|
||||
// 父组件通过 ref 调用:goodsOperationSec 使用 getEditor/setContent/getContent
|
||||
defineExpose({ getEditor, setContent, getContent });
|
||||
</script>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</vuedraggable>
|
||||
<div class="upload-box">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
ref="uploadRef"
|
||||
:action="uploadFileUrl"
|
||||
:headers="accessToken"
|
||||
:show-file-list="false"
|
||||
@@ -64,7 +64,7 @@
|
||||
@closed="resetOssSelection"
|
||||
>
|
||||
<OssManage
|
||||
ref="ossManage"
|
||||
ref="ossManageRef"
|
||||
:is-component="true"
|
||||
:initialize="showOssManager"
|
||||
:hide-select-footer="true"
|
||||
@@ -84,123 +84,120 @@
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { Delete, Plus, ZoomIn } from "@element-plus/icons-vue";
|
||||
import vuedraggable from "vuedraggable";
|
||||
import { uploadFile } from "@/libs/axios";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { Message } from "@/utils/message";
|
||||
import OssManage from "@/views/sys/oss-manage/ossManage";
|
||||
|
||||
export default {
|
||||
name: "upload-image",
|
||||
components: {
|
||||
OssManage,
|
||||
vuedraggable,
|
||||
Delete,
|
||||
Plus,
|
||||
ZoomIn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
uploadFileUrl: uploadFile,
|
||||
accessToken: {},
|
||||
showOssManager: false,
|
||||
images: [],
|
||||
selectedImage: [],
|
||||
viewImage: false,
|
||||
previewUrl: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
draggableItemKey(item) {
|
||||
return item?.url || item;
|
||||
},
|
||||
handleClickUploadImage() {
|
||||
this.show = true;
|
||||
},
|
||||
handleView(url) {
|
||||
this.previewUrl = url;
|
||||
this.viewImage = true;
|
||||
},
|
||||
callback() {
|
||||
const formatImages = this.images.map((item) => item.url);
|
||||
this.$emit("callback", formatImages);
|
||||
this.show = false;
|
||||
},
|
||||
handleRemoveGoodsPicture(__index) {
|
||||
this.images.splice(__index, 1);
|
||||
},
|
||||
handleBeforeUpload(file) {
|
||||
const okType = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
|
||||
if (!okType) {
|
||||
this.$Message.warning("文件 " + file.name + " 的格式不正确,请选择 jpg/jpeg/png");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
this.$Message.warning("图片大小不能超过10MB");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSuccessGoodsPicture(res) {
|
||||
const url = res?.result ?? res?.data?.result;
|
||||
if (url) {
|
||||
this.images.push({ url });
|
||||
} else {
|
||||
this.$Message.error(res?.message || "上传失败");
|
||||
}
|
||||
},
|
||||
handleUploadError(err) {
|
||||
this.$Message.error(err?.message || String(err));
|
||||
},
|
||||
confirmUrls() {
|
||||
this.applySelectedImages();
|
||||
this.showOssManager = false;
|
||||
},
|
||||
cancelOssImport() {
|
||||
this.selectedImage = [];
|
||||
this.showOssManager = false;
|
||||
},
|
||||
resetOssSelection() {
|
||||
this.selectedImage = [];
|
||||
},
|
||||
handleOssSelected(list) {
|
||||
this.selectedImage = Array.isArray(list) ? list : [];
|
||||
},
|
||||
parseOssSelectionUrl(item) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
if (typeof item === "string") {
|
||||
const index = item.indexOf(",");
|
||||
return index >= 0 ? item.slice(index + 1) : item;
|
||||
}
|
||||
return item.url || "";
|
||||
},
|
||||
applySelectedImages() {
|
||||
(this.selectedImage || []).forEach((item) => {
|
||||
const url = this.parseOssSelectionUrl(item);
|
||||
if (url) {
|
||||
this.images.push({ url });
|
||||
}
|
||||
});
|
||||
},
|
||||
importOSS() {
|
||||
this.selectedImage = [];
|
||||
this.showOssManager = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ossManage) {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "upload-image" });
|
||||
|
||||
const emit = defineEmits(["callback"]);
|
||||
|
||||
const show = ref(false);
|
||||
const uploadFileUrl = uploadFile;
|
||||
const accessToken = ref({});
|
||||
const showOssManager = ref(false);
|
||||
const images = ref([]);
|
||||
const selectedImage = ref([]);
|
||||
const viewImage = ref(false);
|
||||
const previewUrl = ref("");
|
||||
|
||||
// 模板 ref:upload/ossManage 原名与顶层变量无冲突,但统一改名避免与后续变量互相覆盖
|
||||
const uploadRef = ref(null);
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
accessToken.value = {
|
||||
accessToken: getStore("accessToken"),
|
||||
};
|
||||
});
|
||||
|
||||
function draggableItemKey(item) {
|
||||
return item?.url || item;
|
||||
}
|
||||
function handleClickUploadImage() {
|
||||
show.value = true;
|
||||
}
|
||||
function handleView(url) {
|
||||
previewUrl.value = url;
|
||||
viewImage.value = true;
|
||||
}
|
||||
function callback() {
|
||||
const formatImages = images.value.map((item) => item.url);
|
||||
emit("callback", formatImages);
|
||||
show.value = false;
|
||||
}
|
||||
function handleRemoveGoodsPicture(__index) {
|
||||
images.value.splice(__index, 1);
|
||||
}
|
||||
function handleBeforeUpload(file) {
|
||||
const okType = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
|
||||
if (!okType) {
|
||||
Message.warning("文件 " + file.name + " 的格式不正确,请选择 jpg/jpeg/png");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
Message.warning("图片大小不能超过10MB");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleSuccessGoodsPicture(res) {
|
||||
const url = res?.result ?? res?.data?.result;
|
||||
if (url) {
|
||||
images.value.push({ url });
|
||||
} else {
|
||||
Message.error(res?.message || "上传失败");
|
||||
}
|
||||
}
|
||||
function handleUploadError(err) {
|
||||
Message.error(err?.message || String(err));
|
||||
}
|
||||
function confirmUrls() {
|
||||
applySelectedImages();
|
||||
showOssManager.value = false;
|
||||
}
|
||||
function cancelOssImport() {
|
||||
selectedImage.value = [];
|
||||
showOssManager.value = false;
|
||||
}
|
||||
function resetOssSelection() {
|
||||
selectedImage.value = [];
|
||||
}
|
||||
function handleOssSelected(list) {
|
||||
selectedImage.value = Array.isArray(list) ? list : [];
|
||||
}
|
||||
function parseOssSelectionUrl(item) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
if (typeof item === "string") {
|
||||
const index = item.indexOf(",");
|
||||
return index >= 0 ? item.slice(index + 1) : item;
|
||||
}
|
||||
return item.url || "";
|
||||
}
|
||||
function applySelectedImages() {
|
||||
(selectedImage.value || []).forEach((item) => {
|
||||
const url = parseOssSelectionUrl(item);
|
||||
if (url) {
|
||||
images.value.push({ url });
|
||||
}
|
||||
});
|
||||
}
|
||||
function importOSS() {
|
||||
selectedImage.value = [];
|
||||
showOssManager.value = true;
|
||||
nextTick(() => {
|
||||
if (ossManageRef.value) {
|
||||
ossManageRef.value.selectImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
<div v-loading="spinShow" class="flex">
|
||||
<el-tree
|
||||
ref="tree"
|
||||
ref="treeRef"
|
||||
class="tree"
|
||||
:data="data"
|
||||
:props="treeProps"
|
||||
@@ -23,175 +23,184 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, onMounted } from "vue";
|
||||
import { getAllCity } from "@/api/index";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
switched: false,
|
||||
spinShow: false,
|
||||
data: [],
|
||||
selectedWay: [],
|
||||
callBackData: "",
|
||||
treeProps: {
|
||||
label: "title",
|
||||
children: "children",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
this.data = [];
|
||||
this.selectedWay = [];
|
||||
this.init();
|
||||
},
|
||||
cancel() {
|
||||
this.switched = false;
|
||||
this.data.forEach((item) => {
|
||||
item.disabled = false;
|
||||
item.children.forEach((child) => {
|
||||
child.disabled = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
open(val, index) {
|
||||
if (val) {
|
||||
let checkedData = this.$store.state.shipTemplate;
|
||||
let checkData = [];
|
||||
let disabledData = checkedData.filter((item, i) => {
|
||||
if (i != index) {
|
||||
return i != index;
|
||||
} else {
|
||||
checkData.push(item);
|
||||
}
|
||||
});
|
||||
checkData.forEach((check) => {
|
||||
check.areaId.split(",").forEach((ids) => {
|
||||
this.data.forEach((item) => {
|
||||
if (check.selectedAll) {
|
||||
check.area.split(",").forEach((area) => {
|
||||
if (area == item.name) {
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
item.children.forEach((child) => {
|
||||
if (item.checked) {
|
||||
child.checked = true;
|
||||
}
|
||||
if (child.id == ids) {
|
||||
child.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
disabledData.forEach((dis) => {
|
||||
dis.areaId.split(",").forEach((ids) => {
|
||||
this.data.forEach((item) => {
|
||||
if (dis.selectedAll) {
|
||||
dis.area.split(",").forEach((area) => {
|
||||
if (area == item.name) {
|
||||
item.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
item.children.forEach((child) => {
|
||||
if (item.disabled) {
|
||||
child.disabled = true;
|
||||
} else if (child.id == ids) {
|
||||
child.disabled = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
this.syncTreeCheckedKeys();
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const treeRef = ref(null);
|
||||
const switched = ref(false);
|
||||
const spinShow = ref(false);
|
||||
const data = ref([]);
|
||||
const selectedWay = ref([]);
|
||||
const callBackData = ref("");
|
||||
const treeProps = reactive({
|
||||
label: "title",
|
||||
children: "children",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function clear() {
|
||||
data.value = [];
|
||||
selectedWay.value = [];
|
||||
init();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
switched.value = false;
|
||||
data.value.forEach((item) => {
|
||||
item.disabled = false;
|
||||
item.children.forEach((child) => {
|
||||
child.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function open(val, index) {
|
||||
if (val) {
|
||||
let checkedData = appStore.shipTemplates;
|
||||
let checkData = [];
|
||||
let disabledData = checkedData.filter((item, i) => {
|
||||
if (i != index) {
|
||||
return i != index;
|
||||
} else {
|
||||
checkData.push(item);
|
||||
}
|
||||
this.switched = true;
|
||||
},
|
||||
syncTreeCheckedKeys() {
|
||||
this.$nextTick(() => {
|
||||
const keys = [];
|
||||
this.data.forEach((item) => {
|
||||
if (item.checked) {
|
||||
keys.push(item.id);
|
||||
});
|
||||
checkData.forEach((check) => {
|
||||
check.areaId.split(",").forEach((ids) => {
|
||||
data.value.forEach((item) => {
|
||||
if (check.selectedAll) {
|
||||
check.area.split(",").forEach((area) => {
|
||||
if (area == item.name) {
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
item.children.forEach((child) => {
|
||||
if (child.checked) {
|
||||
keys.push(child.id);
|
||||
if (item.checked) {
|
||||
child.checked = true;
|
||||
}
|
||||
if (child.id == ids) {
|
||||
child.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (this.$refs.tree) {
|
||||
this.$refs.tree.setCheckedKeys(keys);
|
||||
});
|
||||
});
|
||||
disabledData.forEach((dis) => {
|
||||
dis.areaId.split(",").forEach((ids) => {
|
||||
data.value.forEach((item) => {
|
||||
if (dis.selectedAll) {
|
||||
dis.area.split(",").forEach((area) => {
|
||||
if (area == item.name) {
|
||||
item.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
item.children.forEach((child) => {
|
||||
if (item.disabled) {
|
||||
child.disabled = true;
|
||||
} else if (child.id == ids) {
|
||||
child.disabled = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
syncTreeCheckedKeys();
|
||||
}
|
||||
switched.value = true;
|
||||
}
|
||||
|
||||
function syncTreeCheckedKeys() {
|
||||
nextTick(() => {
|
||||
const keys = [];
|
||||
data.value.forEach((item) => {
|
||||
if (item.checked) {
|
||||
keys.push(item.id);
|
||||
}
|
||||
item.children.forEach((child) => {
|
||||
if (child.checked) {
|
||||
keys.push(child.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
const checkedNodes = this.$refs.tree.getCheckedNodes(false, true);
|
||||
const halfCheckedNodes = this.$refs.tree.getHalfCheckedNodes();
|
||||
let list = [...checkedNodes, ...halfCheckedNodes];
|
||||
});
|
||||
if (treeRef.value) {
|
||||
treeRef.value.setCheckedKeys(keys);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let sort = [];
|
||||
list.forEach((item) => {
|
||||
item.selectedList = [];
|
||||
item.selectedAll = false;
|
||||
if (item.level == "province" && !item.disabled) {
|
||||
sort.push({ ...item });
|
||||
}
|
||||
sort.forEach((sortItem) => {
|
||||
if (item.level != "province" && sortItem.id == item.parentId && !item.disabled) {
|
||||
sortItem.selectedList.push({ ...item });
|
||||
}
|
||||
});
|
||||
});
|
||||
function submit() {
|
||||
const checkedNodes = treeRef.value.getCheckedNodes(false, true);
|
||||
const halfCheckedNodes = treeRef.value.getHalfCheckedNodes();
|
||||
let list = [...checkedNodes, ...halfCheckedNodes];
|
||||
|
||||
this.data.forEach((whether) => {
|
||||
sort.forEach((item) => {
|
||||
if (
|
||||
item.id == whether.id &&
|
||||
item.selectedList.length == whether.children.length
|
||||
) {
|
||||
item.selectedList.forEach((child) => {
|
||||
child.selectedAll = true;
|
||||
});
|
||||
item.selectedAll = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
let sort = [];
|
||||
list.forEach((item) => {
|
||||
item.selectedList = [];
|
||||
item.selectedAll = false;
|
||||
if (item.level == "province" && !item.disabled) {
|
||||
sort.push({ ...item });
|
||||
}
|
||||
sort.forEach((sortItem) => {
|
||||
if (item.level != "province" && sortItem.id == item.parentId && !item.disabled) {
|
||||
sortItem.selectedList.push({ ...item });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.$emit("selected", sort);
|
||||
this.cancel();
|
||||
},
|
||||
init() {
|
||||
this.spinShow = true;
|
||||
getAllCity()
|
||||
.then((res) => {
|
||||
if (res.result) {
|
||||
this.data = [];
|
||||
res.result.forEach((item) => {
|
||||
item.children.forEach((child) => {
|
||||
child.title = child.name;
|
||||
});
|
||||
this.data.push({
|
||||
title: item.name,
|
||||
...item,
|
||||
});
|
||||
this.selectedWay.push({ name: item.name, id: item.id });
|
||||
});
|
||||
this.$store.state.regions = this.data;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.spinShow = false;
|
||||
data.value.forEach((whether) => {
|
||||
sort.forEach((item) => {
|
||||
if (
|
||||
item.id == whether.id &&
|
||||
item.selectedList.length == whether.children.length
|
||||
) {
|
||||
item.selectedList.forEach((child) => {
|
||||
child.selectedAll = true;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
item.selectedAll = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
emit("selected", sort);
|
||||
cancel();
|
||||
}
|
||||
|
||||
function init() {
|
||||
spinShow.value = true;
|
||||
getAllCity()
|
||||
.then((res) => {
|
||||
if (res.result) {
|
||||
data.value = [];
|
||||
res.result.forEach((item) => {
|
||||
item.children.forEach((child) => {
|
||||
child.title = child.name;
|
||||
});
|
||||
data.value.push({
|
||||
title: item.name,
|
||||
...item,
|
||||
});
|
||||
selectedWay.value.push({ name: item.name, id: item.id });
|
||||
});
|
||||
appStore.regions = data.value;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
spinShow.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({ clear, open });
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.flex {
|
||||
|
||||
@@ -109,9 +109,9 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div ref="tableWrap" class="goods-table-wrap">
|
||||
<div ref="tableWrapRef" class="goods-table-wrap">
|
||||
<el-table
|
||||
ref="goodsTable"
|
||||
ref="goodsTableRef"
|
||||
v-loading="loading"
|
||||
:data="goodsData"
|
||||
border
|
||||
@@ -194,7 +194,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
||||
import * as API_Goods from "@/api/goods";
|
||||
|
||||
const defaultFilterForm = () => ({
|
||||
@@ -212,270 +213,273 @@ const defaultFilterForm = () => ({
|
||||
stockMax: "",
|
||||
});
|
||||
|
||||
export default {
|
||||
emits: ["selected"],
|
||||
props: {
|
||||
selectedWay: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
const props = defineProps({
|
||||
selectedWay: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: "multiple",
|
||||
selectedList: [],
|
||||
platformCategoryList: [],
|
||||
shopCategoryList: [],
|
||||
brandList: [],
|
||||
platformCategoryProps: {
|
||||
value: "id",
|
||||
label: "name",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
},
|
||||
shopCategoryProps: {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
},
|
||||
filterForm: defaultFilterForm(),
|
||||
total: 0,
|
||||
goodsParams: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
order: "desc",
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
sort: "createTime",
|
||||
},
|
||||
goodsData: [],
|
||||
loading: false,
|
||||
tableHeight: 400,
|
||||
syncingSelection: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedWay: {
|
||||
handler(val) {
|
||||
this.selectedList = Array.isArray(val) ? val.slice() : [];
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
this.$nextTick(this.calcTableHeight);
|
||||
window.addEventListener("resize", this.calcTableHeight);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.calcTableHeight);
|
||||
},
|
||||
methods: {
|
||||
calcTableHeight() {
|
||||
this.$nextTick(() => {
|
||||
const wrap = this.$refs.tableWrap;
|
||||
if (wrap) {
|
||||
this.tableHeight = Math.max(wrap.clientHeight, 320);
|
||||
}
|
||||
});
|
||||
},
|
||||
onSearchGoods() {
|
||||
this.goodsParams.pageNumber = 1;
|
||||
this.getQueryGoodsList();
|
||||
},
|
||||
resetFilter() {
|
||||
this.filterForm = defaultFilterForm();
|
||||
this.onSearchGoods();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.goodsParams.pageNumber = v;
|
||||
this.getQueryGoodsList();
|
||||
},
|
||||
buildQueryParams() {
|
||||
const params = {
|
||||
...this.goodsParams,
|
||||
goodsName: this.filterForm.goodsName || undefined,
|
||||
goodsId: this.filterForm.goodsId || undefined,
|
||||
goodsType: this.filterForm.goodsType || undefined,
|
||||
brandId: this.filterForm.brandId || undefined,
|
||||
storeCategoryPath: this.filterForm.storeCategoryPath || undefined,
|
||||
categoryPath: this.filterForm.categoryPath || undefined,
|
||||
geQuantity: undefined,
|
||||
leQuantity: undefined,
|
||||
geBuyCount: undefined,
|
||||
leBuyCount: undefined,
|
||||
price: undefined,
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const { priceMin, priceMax, salesMin, salesMax, stockMin, stockMax } = this.filterForm;
|
||||
if (priceMin !== "" && priceMax !== "") {
|
||||
params.price = `${priceMin}_${priceMax}`;
|
||||
} else if (priceMin !== "") {
|
||||
params.price = String(priceMin);
|
||||
} else if (priceMax !== "") {
|
||||
params.price = `0_${priceMax}`;
|
||||
}
|
||||
|
||||
if (stockMin !== "" && stockMin != null) params.geQuantity = Number(stockMin);
|
||||
if (stockMax !== "" && stockMax != null) params.leQuantity = Number(stockMax);
|
||||
if (salesMin !== "" && salesMin != null) params.geBuyCount = Number(salesMin);
|
||||
if (salesMax !== "" && salesMax != null) params.leBuyCount = Number(salesMax);
|
||||
|
||||
return params;
|
||||
},
|
||||
getQueryGoodsList() {
|
||||
this.loading = true;
|
||||
API_Goods.getGoodsSkuData(this.buildQueryParams())
|
||||
.then((res) => {
|
||||
this.initGoods(res);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
this.calcTableHeight();
|
||||
});
|
||||
},
|
||||
initGoods(res) {
|
||||
const records = res?.result?.records || [];
|
||||
this.total = res?.result?.total || 0;
|
||||
const selectedIds = new Set(
|
||||
(this.selectedList || [])
|
||||
.map((e) => e.id || e.skuId)
|
||||
.filter((id) => id != null)
|
||||
.map(String)
|
||||
);
|
||||
records.forEach((item) => {
|
||||
item.___type = "goods";
|
||||
item.selected = selectedIds.has(String(item.id));
|
||||
});
|
||||
this.goodsData = records;
|
||||
this.syncTableSelection();
|
||||
},
|
||||
syncTableSelection() {
|
||||
this.$nextTick(() => {
|
||||
const table = this.$refs.goodsTable;
|
||||
if (!table) return;
|
||||
if (this.type === "multiple") {
|
||||
// clearSelection/toggleRowSelection 都会触发 selection-change,
|
||||
// 同步期间忽略,避免把已选状态清掉
|
||||
this.syncingSelection = true;
|
||||
table.clearSelection();
|
||||
this.goodsData.forEach((row) => {
|
||||
if (row.selected) {
|
||||
table.toggleRowSelection(row, true);
|
||||
}
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
this.syncingSelection = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const selected = this.goodsData.find((item) => item.selected);
|
||||
table.setCurrentRow(selected || null);
|
||||
});
|
||||
},
|
||||
emitSelected(list) {
|
||||
this.selectedList = list;
|
||||
this.$emit("selected", this.selectedList);
|
||||
},
|
||||
handleRowClick(row) {
|
||||
if (this.type === "multiple") return;
|
||||
this.goodsData.forEach((item) => {
|
||||
item.selected = false;
|
||||
});
|
||||
row.selected = true;
|
||||
this.emitSelected([row]);
|
||||
this.$refs.goodsTable?.setCurrentRow(row);
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
if (this.type !== "multiple" || this.syncingSelection) return;
|
||||
const selectedIds = new Set(selection.map((item) => String(item.id)));
|
||||
this.goodsData.forEach((item) => {
|
||||
item.selected = selectedIds.has(String(item.id));
|
||||
});
|
||||
const currentPageIds = new Set(this.goodsData.map((item) => String(item.id)));
|
||||
const otherPages = this.selectedList.filter((item) => {
|
||||
const id = item.id || item.skuId;
|
||||
return id != null && !currentPageIds.has(String(id));
|
||||
});
|
||||
this.emitSelected([...otherPages, ...selection]);
|
||||
},
|
||||
formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.labelName,
|
||||
children:
|
||||
item.children && item.children.length ? this.formatShopCategory(item.children) : undefined,
|
||||
}));
|
||||
},
|
||||
goodsTypeText(value) {
|
||||
if (value === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (value === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (value === "E_COUPON") return "电子卡券";
|
||||
return "-";
|
||||
},
|
||||
brandText(brandId) {
|
||||
if (!brandId) return "-";
|
||||
const brand = this.brandList.find((item) => String(item.id) === String(brandId));
|
||||
return brand?.name || "-";
|
||||
},
|
||||
findTreeNodeLabel(tree, targetId, valueKey, labelKey) {
|
||||
for (const node of tree || []) {
|
||||
if (String(node[valueKey]) === String(targetId)) {
|
||||
return node[labelKey];
|
||||
}
|
||||
const childLabel = this.findTreeNodeLabel(node.children, targetId, valueKey, labelKey);
|
||||
if (childLabel) return childLabel;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
categoryPathText(path, tree, valueKey, labelKey) {
|
||||
if (!path) return "-";
|
||||
const ids = String(path).split(",").filter(Boolean);
|
||||
if (!ids.length) return "-";
|
||||
const labels = ids
|
||||
.map((id) => this.findTreeNodeLabel(tree, id, valueKey, labelKey))
|
||||
.filter(Boolean);
|
||||
return labels.length ? labels.join(" / ") : "-";
|
||||
},
|
||||
storeCategoryText(path) {
|
||||
return this.categoryPathText(path, this.shopCategoryList, "value", "label");
|
||||
},
|
||||
platformCategoryText(path) {
|
||||
if (!path) return "-";
|
||||
const ids = String(path).split(",").filter(Boolean);
|
||||
if (!ids.length) return "-";
|
||||
const targetId = ids.length >= 3 ? ids[2] : ids[ids.length - 1];
|
||||
const label = this.findTreeNodeLabel(this.platformCategoryList, targetId, "id", "name");
|
||||
return label || "-";
|
||||
},
|
||||
init() {
|
||||
this.getQueryGoodsList();
|
||||
API_Goods.getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.platformCategoryList = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
API_Goods.getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
API_Goods.getBrandListData()
|
||||
.then((res) => {
|
||||
this.brandList = Array.isArray(res) ? res : res?.result || [];
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
const type = ref("multiple");
|
||||
const selectedList = ref([]);
|
||||
const platformCategoryList = ref([]);
|
||||
const shopCategoryList = ref([]);
|
||||
const brandList = ref([]);
|
||||
// 静态配置对象,仅模板读取、从不修改,无需响应式
|
||||
const platformCategoryProps = {
|
||||
value: "id",
|
||||
label: "name",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
};
|
||||
const shopCategoryProps = {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
};
|
||||
const filterForm = ref(defaultFilterForm());
|
||||
const total = ref(0);
|
||||
const goodsParams = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
order: "desc",
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
sort: "createTime",
|
||||
});
|
||||
const goodsData = ref([]);
|
||||
const loading = ref(false);
|
||||
const tableHeight = ref(400);
|
||||
const syncingSelection = ref(false);
|
||||
|
||||
// 模板 ref:原名 tableWrap/goodsTable,统一改名 xxxRef 避免与变量同名互相覆盖
|
||||
const tableWrapRef = ref(null);
|
||||
const goodsTableRef = ref(null);
|
||||
|
||||
watch(
|
||||
() => props.selectedWay,
|
||||
(val) => {
|
||||
selectedList.value = Array.isArray(val) ? val.slice() : [];
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
function calcTableHeight() {
|
||||
nextTick(() => {
|
||||
const wrap = tableWrapRef.value;
|
||||
if (wrap) {
|
||||
tableHeight.value = Math.max(wrap.clientHeight, 320);
|
||||
}
|
||||
});
|
||||
}
|
||||
function onSearchGoods() {
|
||||
goodsParams.pageNumber = 1;
|
||||
getQueryGoodsList();
|
||||
}
|
||||
function resetFilter() {
|
||||
filterForm.value = defaultFilterForm();
|
||||
onSearchGoods();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
goodsParams.pageNumber = v;
|
||||
getQueryGoodsList();
|
||||
}
|
||||
function buildQueryParams() {
|
||||
const params = {
|
||||
...goodsParams,
|
||||
goodsName: filterForm.value.goodsName || undefined,
|
||||
goodsId: filterForm.value.goodsId || undefined,
|
||||
goodsType: filterForm.value.goodsType || undefined,
|
||||
brandId: filterForm.value.brandId || undefined,
|
||||
storeCategoryPath: filterForm.value.storeCategoryPath || undefined,
|
||||
categoryPath: filterForm.value.categoryPath || undefined,
|
||||
geQuantity: undefined,
|
||||
leQuantity: undefined,
|
||||
geBuyCount: undefined,
|
||||
leBuyCount: undefined,
|
||||
price: undefined,
|
||||
};
|
||||
|
||||
const { priceMin, priceMax, salesMin, salesMax, stockMin, stockMax } = filterForm.value;
|
||||
if (priceMin !== "" && priceMax !== "") {
|
||||
params.price = `${priceMin}_${priceMax}`;
|
||||
} else if (priceMin !== "") {
|
||||
params.price = String(priceMin);
|
||||
} else if (priceMax !== "") {
|
||||
params.price = `0_${priceMax}`;
|
||||
}
|
||||
|
||||
if (stockMin !== "" && stockMin != null) params.geQuantity = Number(stockMin);
|
||||
if (stockMax !== "" && stockMax != null) params.leQuantity = Number(stockMax);
|
||||
if (salesMin !== "" && salesMin != null) params.geBuyCount = Number(salesMin);
|
||||
if (salesMax !== "" && salesMax != null) params.leBuyCount = Number(salesMax);
|
||||
|
||||
return params;
|
||||
}
|
||||
function getQueryGoodsList() {
|
||||
loading.value = true;
|
||||
API_Goods.getGoodsSkuData(buildQueryParams())
|
||||
.then((res) => {
|
||||
initGoods(res);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
calcTableHeight();
|
||||
});
|
||||
}
|
||||
function initGoods(res) {
|
||||
const records = res?.result?.records || [];
|
||||
total.value = res?.result?.total || 0;
|
||||
const selectedIds = new Set(
|
||||
(selectedList.value || [])
|
||||
.map((e) => e.id || e.skuId)
|
||||
.filter((id) => id != null)
|
||||
.map(String)
|
||||
);
|
||||
records.forEach((item) => {
|
||||
item.___type = "goods";
|
||||
item.selected = selectedIds.has(String(item.id));
|
||||
});
|
||||
goodsData.value = records;
|
||||
syncTableSelection();
|
||||
}
|
||||
function syncTableSelection() {
|
||||
nextTick(() => {
|
||||
const table = goodsTableRef.value;
|
||||
if (!table) return;
|
||||
if (type.value === "multiple") {
|
||||
// clearSelection/toggleRowSelection 都会触发 selection-change,
|
||||
// 同步期间忽略,避免把已选状态清掉
|
||||
syncingSelection.value = true;
|
||||
table.clearSelection();
|
||||
goodsData.value.forEach((row) => {
|
||||
if (row.selected) {
|
||||
table.toggleRowSelection(row, true);
|
||||
}
|
||||
});
|
||||
nextTick(() => {
|
||||
syncingSelection.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const selected = goodsData.value.find((item) => item.selected);
|
||||
table.setCurrentRow(selected || null);
|
||||
});
|
||||
}
|
||||
function emitSelected(list) {
|
||||
selectedList.value = list;
|
||||
emit("selected", selectedList.value);
|
||||
}
|
||||
function handleRowClick(row) {
|
||||
if (type.value === "multiple") return;
|
||||
goodsData.value.forEach((item) => {
|
||||
item.selected = false;
|
||||
});
|
||||
row.selected = true;
|
||||
emitSelected([row]);
|
||||
goodsTableRef.value?.setCurrentRow(row);
|
||||
}
|
||||
function handleSelectionChange(selection) {
|
||||
if (type.value !== "multiple" || syncingSelection.value) return;
|
||||
const selectedIds = new Set(selection.map((item) => String(item.id)));
|
||||
goodsData.value.forEach((item) => {
|
||||
item.selected = selectedIds.has(String(item.id));
|
||||
});
|
||||
const currentPageIds = new Set(goodsData.value.map((item) => String(item.id)));
|
||||
const otherPages = selectedList.value.filter((item) => {
|
||||
const id = item.id || item.skuId;
|
||||
return id != null && !currentPageIds.has(String(id));
|
||||
});
|
||||
emitSelected([...otherPages, ...selection]);
|
||||
}
|
||||
function formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.labelName,
|
||||
children:
|
||||
item.children && item.children.length ? formatShopCategory(item.children) : undefined,
|
||||
}));
|
||||
}
|
||||
function goodsTypeText(value) {
|
||||
if (value === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (value === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (value === "E_COUPON") return "电子卡券";
|
||||
return "-";
|
||||
}
|
||||
function brandText(brandId) {
|
||||
if (!brandId) return "-";
|
||||
const brand = brandList.value.find((item) => String(item.id) === String(brandId));
|
||||
return brand?.name || "-";
|
||||
}
|
||||
function findTreeNodeLabel(tree, targetId, valueKey, labelKey) {
|
||||
for (const node of tree || []) {
|
||||
if (String(node[valueKey]) === String(targetId)) {
|
||||
return node[labelKey];
|
||||
}
|
||||
const childLabel = findTreeNodeLabel(node.children, targetId, valueKey, labelKey);
|
||||
if (childLabel) return childLabel;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function categoryPathText(path, tree, valueKey, labelKey) {
|
||||
if (!path) return "-";
|
||||
const ids = String(path).split(",").filter(Boolean);
|
||||
if (!ids.length) return "-";
|
||||
const labels = ids
|
||||
.map((id) => findTreeNodeLabel(tree, id, valueKey, labelKey))
|
||||
.filter(Boolean);
|
||||
return labels.length ? labels.join(" / ") : "-";
|
||||
}
|
||||
function storeCategoryText(path) {
|
||||
return categoryPathText(path, shopCategoryList.value, "value", "label");
|
||||
}
|
||||
function platformCategoryText(path) {
|
||||
if (!path) return "-";
|
||||
const ids = String(path).split(",").filter(Boolean);
|
||||
if (!ids.length) return "-";
|
||||
const targetId = ids.length >= 3 ? ids[2] : ids[ids.length - 1];
|
||||
const label = findTreeNodeLabel(platformCategoryList.value, targetId, "id", "name");
|
||||
return label || "-";
|
||||
}
|
||||
function init() {
|
||||
getQueryGoodsList();
|
||||
API_Goods.getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
platformCategoryList.value = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
API_Goods.getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
shopCategoryList.value = formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
API_Goods.getBrandListData()
|
||||
.then((res) => {
|
||||
brandList.value = Array.isArray(res) ? res : res?.result || [];
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
nextTick(calcTableHeight);
|
||||
window.addEventListener("resize", calcTableHeight);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", calcTableHeight);
|
||||
});
|
||||
|
||||
// 父组件(lili-dialog/index.vue 的 $refs.goodsDialog、link-dialog.vue 的 $refs["lili-component"])
|
||||
// 通过实例写入 type 切换单选/多选;expose 的 ref 外部读写等价于原 data 字段
|
||||
defineExpose({ type });
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
<goodsDialog
|
||||
v-if="goodsVisible"
|
||||
ref="goodsDialog"
|
||||
ref="goodsDialogRef"
|
||||
:selectedWay="goodsData"
|
||||
@selected="
|
||||
(val) => {
|
||||
@@ -51,86 +51,96 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import linkDialog from "./link-dialog";
|
||||
export default {
|
||||
components: {
|
||||
goodsDialog,
|
||||
linkDialog,
|
||||
|
||||
const emit = defineEmits(["closeFlag", "selectedGoodsData", "selectedLink"]);
|
||||
|
||||
const goodsVisible = ref(false);
|
||||
const linkVisible = ref(false);
|
||||
const goodsFlag = ref(false);
|
||||
const goodsData = ref([]);
|
||||
const linkData = ref("");
|
||||
|
||||
// 模板 ref:原 ref="goodsDialog" 与 import 的组件同名,改名避免互相覆盖
|
||||
const goodsDialogRef = ref(null);
|
||||
|
||||
const flag = computed({
|
||||
get() {
|
||||
return goodsVisible.value || linkVisible.value;
|
||||
},
|
||||
emits: ["closeFlag", "selectedGoodsData", "selectedLink"],
|
||||
data() {
|
||||
return {
|
||||
goodsVisible: false,
|
||||
linkVisible: false,
|
||||
goodsFlag: false,
|
||||
goodsData: [],
|
||||
linkData: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
flag: {
|
||||
get() {
|
||||
return this.goodsVisible || this.linkVisible;
|
||||
},
|
||||
set(val) {
|
||||
if (val) {
|
||||
if (this.goodsFlag) {
|
||||
this.goodsVisible = true;
|
||||
} else {
|
||||
this.linkVisible = true;
|
||||
}
|
||||
} else {
|
||||
this.goodsVisible = false;
|
||||
this.linkVisible = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearGoodsSelected() {
|
||||
this.goodsData = [];
|
||||
},
|
||||
clickClose() {
|
||||
this.goodsVisible = false;
|
||||
this.linkVisible = false;
|
||||
this.goodsFlag = false;
|
||||
this.$emit("closeFlag", false);
|
||||
},
|
||||
singleGoods() {
|
||||
const timer = setInterval(() => {
|
||||
if (this.$refs.goodsDialog) {
|
||||
this.$refs.goodsDialog.type = "single";
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
clickOK() {
|
||||
if (this.goodsVisible) {
|
||||
this.$emit("selectedGoodsData", this.goodsData);
|
||||
} else if (this.linkVisible) {
|
||||
this.$emit("selectedLink", this.linkData);
|
||||
}
|
||||
this.clickClose();
|
||||
},
|
||||
open(type, mutiple) {
|
||||
if (type == "goods") {
|
||||
this.goodsFlag = true;
|
||||
this.goodsVisible = true;
|
||||
if (mutiple) {
|
||||
this.singleGoods();
|
||||
}
|
||||
set(val) {
|
||||
if (val) {
|
||||
if (goodsFlag.value) {
|
||||
goodsVisible.value = true;
|
||||
} else {
|
||||
this.goodsFlag = false;
|
||||
this.linkVisible = true;
|
||||
linkVisible.value = true;
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.clickClose();
|
||||
},
|
||||
} else {
|
||||
goodsVisible.value = false;
|
||||
linkVisible.value = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function clearGoodsSelected() {
|
||||
goodsData.value = [];
|
||||
}
|
||||
function clickClose() {
|
||||
goodsVisible.value = false;
|
||||
linkVisible.value = false;
|
||||
goodsFlag.value = false;
|
||||
emit("closeFlag", false);
|
||||
}
|
||||
function singleGoods() {
|
||||
const timer = setInterval(() => {
|
||||
if (goodsDialogRef.value) {
|
||||
goodsDialogRef.value.type = "single";
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
function clickOK() {
|
||||
if (goodsVisible.value) {
|
||||
emit("selectedGoodsData", goodsData.value);
|
||||
} else if (linkVisible.value) {
|
||||
emit("selectedLink", linkData.value);
|
||||
}
|
||||
clickClose();
|
||||
}
|
||||
function open(type, mutiple) {
|
||||
if (type == "goods") {
|
||||
goodsFlag.value = true;
|
||||
goodsVisible.value = true;
|
||||
if (mutiple) {
|
||||
singleGoods();
|
||||
}
|
||||
} else {
|
||||
goodsFlag.value = false;
|
||||
linkVisible.value = true;
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
clickClose();
|
||||
}
|
||||
|
||||
// 外部通过 ref 访问的成员(grep seller/src 全量确认):
|
||||
// - open:所有调用方打开弹窗(goods/link)
|
||||
// - singleGoods:shop/modelList/forYour.vue、mixModel.vue 直接调用
|
||||
// - clearGoodsSelected:shop/wap/decorate.vue、modelList/goodsAndType.vue 调用
|
||||
// - goodsData:促销/采购/装修多处回写已选商品
|
||||
// - goodsFlag:shop/wap/decorate.vue 直接写入
|
||||
// - flag(可写 computed):shop/wap/decorate.vue 写入触发弹层开关
|
||||
defineExpose({
|
||||
open,
|
||||
singleGoods,
|
||||
clearGoodsSelected,
|
||||
goodsData,
|
||||
goodsFlag,
|
||||
flag,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.lili-goods-drawer {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:key="i"
|
||||
>
|
||||
<component
|
||||
ref="lili-component"
|
||||
ref="liliComponentRef"
|
||||
:is="templateWay[item.name]"
|
||||
@selected="
|
||||
(val) => {
|
||||
@@ -20,48 +20,43 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import wap from "./wap.js";
|
||||
<script setup>
|
||||
import { nextTick, onMounted, ref, watch } from "vue";
|
||||
import wapList from "./wap.js";
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import templateWay from "./template/index";
|
||||
export default {
|
||||
emits: ["selectedLink"],
|
||||
components: {
|
||||
goodsDialog,
|
||||
|
||||
const emit = defineEmits(["selectedLink"]);
|
||||
|
||||
const changed = ref("");
|
||||
const selected = ref(0);
|
||||
const selectedLink = ref("");
|
||||
// wap 为共享的模块级数组,保持原 data 引用同一数组的行为
|
||||
const wap = ref(wapList);
|
||||
const activeTab = ref(wapList[0]?.title || "");
|
||||
// v-for 内模板 ref 收集为数组
|
||||
const liliComponentRef = ref([]);
|
||||
|
||||
watch(
|
||||
changed,
|
||||
(val) => {
|
||||
emit("selectedLink", val[0]);
|
||||
},
|
||||
setup() {
|
||||
return { templateWay };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
changed: "",
|
||||
selected: 0,
|
||||
selectedLink: "",
|
||||
wap,
|
||||
activeTab: wap[0]?.title || "",
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
changed: {
|
||||
handler(val) {
|
||||
this.$emit("selectedLink", val[0]);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["lili-component"]?.[0]) {
|
||||
this.$refs["lili-component"][0].type = "single";
|
||||
}
|
||||
});
|
||||
this.wap.forEach((item) => {
|
||||
if (item) {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (liliComponentRef.value?.[0]) {
|
||||
liliComponentRef.value[0].type = "single";
|
||||
}
|
||||
});
|
||||
wap.value.forEach((item) => {
|
||||
if (item) {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
|
||||
@@ -26,91 +26,91 @@
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getCategoryTree } from "@/api/goods.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
parentIndex: '', // 分类父级下标
|
||||
secondIndex: '', // 分类二级下标
|
||||
thirdIndex: '', // 分类三级下标
|
||||
categoryList: [], // 分类列表一级数据
|
||||
secondLevel: [], // 分类列表二级数据
|
||||
thirdLevel: [], // 分类列表三级数据
|
||||
selectedData: "", // 已选分类数据
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
loadCategoryFromCache() {
|
||||
try {
|
||||
const raw = localStorage.getItem("category");
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
applyCategoryList(category) {
|
||||
if (!Array.isArray(category)) {
|
||||
this.categoryList = [];
|
||||
return;
|
||||
}
|
||||
category.forEach((item) => {
|
||||
item.___type = "category";
|
||||
});
|
||||
this.categoryList = category;
|
||||
},
|
||||
// 点击一级
|
||||
handleClickChild(item, index) {
|
||||
this.parentIndex = index;
|
||||
this.secondLevel = item.children || [];
|
||||
item.___type = "category";
|
||||
item.allId = item.id;
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
this.secondIndex = '';
|
||||
this.thirdIndex = '';
|
||||
this.thirdLevel = []
|
||||
this.$emit("selected", [item]);
|
||||
},
|
||||
// 点击二级
|
||||
handleClickSecondChild(second, index) {
|
||||
second.___type = "category";
|
||||
second.allId = `${second.parentId},${second.id}`
|
||||
// 无 defineExpose:grep seller/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关;
|
||||
// shop/wap/decorate.vue 仅通过 @selected 事件交互)
|
||||
const parentIndex = ref(''); // 分类父级下标
|
||||
const secondIndex = ref(''); // 分类二级下标
|
||||
const thirdIndex = ref(''); // 分类三级下标
|
||||
const categoryList = ref([]); // 分类列表一级数据
|
||||
const secondLevel = ref([]); // 分类列表二级数据
|
||||
const thirdLevel = ref([]); // 分类列表三级数据
|
||||
const selectedData = ref(""); // 已选分类数据
|
||||
|
||||
this.secondIndex = index;
|
||||
this.thirdLevel = second.children || [];
|
||||
this.thirdIndex = '';
|
||||
this.$emit("selected", [second]);
|
||||
},
|
||||
// 点击三级
|
||||
handleClickthirdChild(item, index) {
|
||||
item.___type = "category";
|
||||
item.allId = `${this.categoryList[this.parentIndex].id},${item.parentId},${item.id}`
|
||||
this.$emit("selected", [item]);
|
||||
this.thirdIndex = index;
|
||||
},
|
||||
init() {
|
||||
const cached = this.loadCategoryFromCache();
|
||||
if (cached) {
|
||||
this.applyCategoryList(cached);
|
||||
return;
|
||||
}
|
||||
getCategoryTree().then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
this.applyCategoryList(res.result);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
function loadCategoryFromCache() {
|
||||
try {
|
||||
const raw = localStorage.getItem("category");
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function applyCategoryList(category) {
|
||||
if (!Array.isArray(category)) {
|
||||
categoryList.value = [];
|
||||
return;
|
||||
}
|
||||
category.forEach((item) => {
|
||||
item.___type = "category";
|
||||
});
|
||||
categoryList.value = category;
|
||||
}
|
||||
// 点击一级
|
||||
function handleClickChild(item, index) {
|
||||
parentIndex.value = index;
|
||||
secondLevel.value = item.children || [];
|
||||
item.___type = "category";
|
||||
item.allId = item.id;
|
||||
|
||||
secondIndex.value = '';
|
||||
thirdIndex.value = '';
|
||||
thirdLevel.value = []
|
||||
emit("selected", [item]);
|
||||
}
|
||||
// 点击二级
|
||||
function handleClickSecondChild(second, index) {
|
||||
second.___type = "category";
|
||||
second.allId = `${second.parentId},${second.id}`
|
||||
|
||||
secondIndex.value = index;
|
||||
thirdLevel.value = second.children || [];
|
||||
thirdIndex.value = '';
|
||||
emit("selected", [second]);
|
||||
}
|
||||
// 点击三级
|
||||
function handleClickthirdChild(item, index) {
|
||||
item.___type = "category";
|
||||
item.allId = `${categoryList.value[parentIndex.value].id},${item.parentId},${item.id}`
|
||||
emit("selected", [item]);
|
||||
thirdIndex.value = index;
|
||||
}
|
||||
function init() {
|
||||
const cached = loadCategoryFromCache();
|
||||
if (cached) {
|
||||
applyCategoryList(cached);
|
||||
return;
|
||||
}
|
||||
getCategoryTree().then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
applyCategoryList(res.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
|
||||
@@ -44,8 +44,9 @@
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { markRaw } from "vue";
|
||||
<script setup>
|
||||
import { markRaw, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
House,
|
||||
ShoppingCart,
|
||||
@@ -60,52 +61,52 @@ import {
|
||||
Link,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
linkList: [
|
||||
{ title: "首页", icon: markRaw(House), ___type: "home" },
|
||||
{ title: "购物车", icon: markRaw(ShoppingCart), ___type: "cart" },
|
||||
{ title: "收藏商品", icon: markRaw(Star), ___type: "collection" },
|
||||
{ title: "我的订单", icon: markRaw(Document), ___type: "order" },
|
||||
{ title: "个人中心", icon: markRaw(User), ___type: "user" },
|
||||
{ title: "拼团频道", icon: markRaw(Promotion), ___type: "group" },
|
||||
{ title: "秒杀频道", icon: markRaw(Promotion), ___type: "seckill" },
|
||||
{ title: "领券中心", icon: markRaw(PriceTag), ___type: "coupon" },
|
||||
{ title: "签到", icon: markRaw(Sunny), ___type: "sign" },
|
||||
{ title: "砍价", icon: markRaw(Share), ___type: "kanjia" },
|
||||
{ title: "积分商城", icon: markRaw(ShoppingBag), ___type: "point" },
|
||||
],
|
||||
linkItem: {
|
||||
title: "外部链接",
|
||||
icon: markRaw(Link),
|
||||
___type: "link",
|
||||
url: "",
|
||||
},
|
||||
linkVisible: false,
|
||||
linkPopoverVisible: false,
|
||||
selectedIndex: 9999999,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showLinkItem(item) {
|
||||
return (
|
||||
(item.title !== "拼团频道" && item.title !== "签到") ||
|
||||
this.$route.name !== "renovation"
|
||||
);
|
||||
},
|
||||
handleLink(val, index) {
|
||||
val = { ...val, ___type: "other" };
|
||||
this.selectedIndex = index;
|
||||
if (index === this.linkList.length) {
|
||||
this.linkPopoverVisible = true;
|
||||
} else {
|
||||
this.linkPopoverVisible = false;
|
||||
}
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const linkList = ref([
|
||||
{ title: "首页", icon: markRaw(House), ___type: "home" },
|
||||
{ title: "购物车", icon: markRaw(ShoppingCart), ___type: "cart" },
|
||||
{ title: "收藏商品", icon: markRaw(Star), ___type: "collection" },
|
||||
{ title: "我的订单", icon: markRaw(Document), ___type: "order" },
|
||||
{ title: "个人中心", icon: markRaw(User), ___type: "user" },
|
||||
{ title: "拼团频道", icon: markRaw(Promotion), ___type: "group" },
|
||||
{ title: "秒杀频道", icon: markRaw(Promotion), ___type: "seckill" },
|
||||
{ title: "领券中心", icon: markRaw(PriceTag), ___type: "coupon" },
|
||||
{ title: "签到", icon: markRaw(Sunny), ___type: "sign" },
|
||||
{ title: "砍价", icon: markRaw(Share), ___type: "kanjia" },
|
||||
{ title: "积分商城", icon: markRaw(ShoppingBag), ___type: "point" },
|
||||
]);
|
||||
const linkItem = ref({
|
||||
title: "外部链接",
|
||||
icon: markRaw(Link),
|
||||
___type: "link",
|
||||
url: "",
|
||||
});
|
||||
const linkVisible = ref(false);
|
||||
const linkPopoverVisible = ref(false);
|
||||
const selectedIndex = ref(9999999);
|
||||
|
||||
function showLinkItem(item) {
|
||||
return (
|
||||
(item.title !== "拼团频道" && item.title !== "签到") ||
|
||||
route.name !== "renovation"
|
||||
);
|
||||
}
|
||||
function handleLink(val, index) {
|
||||
val = { ...val, ___type: "other" };
|
||||
selectedIndex.value = index;
|
||||
if (index === linkList.value.length) {
|
||||
linkPopoverVisible.value = true;
|
||||
} else {
|
||||
linkPopoverVisible.value = false;
|
||||
}
|
||||
emit("selected", [val]);
|
||||
}
|
||||
|
||||
// 无 defineExpose:grep seller/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
|
||||
@@ -12,25 +12,20 @@
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
// import Article from "@/views/page/article-manage/articleList.vue";
|
||||
export default {
|
||||
components: {
|
||||
// Article,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
callbackArticle(val) {
|
||||
val.___type = "pages";
|
||||
|
||||
val.___path = "/pages/passport/article";
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
// 无 defineExpose:grep seller/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
function callbackArticle(val) {
|
||||
val.___type = "pages";
|
||||
|
||||
val.___path = "/pages/passport/article";
|
||||
|
||||
emit("selected", [val]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-card__body) {
|
||||
|
||||
@@ -56,53 +56,50 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
total: 0,
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 12,
|
||||
storeDisable: "OPEN",
|
||||
storeName: "",
|
||||
},
|
||||
shopsData: [],
|
||||
selected: 9999999999,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.shopsData = [];
|
||||
this.params.pageNumber = 1;
|
||||
this.init();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.params.pageNumber = v;
|
||||
this.init();
|
||||
},
|
||||
init() {
|
||||
this.loading = true;
|
||||
getShopListData(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
this.total = res.result.total;
|
||||
this.shopsData = res.result.records;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
clickShop(val, i) {
|
||||
this.selected = i;
|
||||
val = { ...val, ___type: "shops" };
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 12,
|
||||
storeDisable: "OPEN",
|
||||
storeName: "",
|
||||
});
|
||||
const shopsData = ref([]);
|
||||
const selected = ref(9999999999);
|
||||
|
||||
// created
|
||||
init();
|
||||
|
||||
function resetSearch() {
|
||||
shopsData.value = [];
|
||||
params.pageNumber = 1;
|
||||
init();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
params.pageNumber = v;
|
||||
init();
|
||||
}
|
||||
function init() {
|
||||
loading.value = true;
|
||||
getShopListData(params).then((res) => {
|
||||
if (res.success) {
|
||||
total.value = res.result.total;
|
||||
shopsData.value = res.result.records;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function clickShop(val, i) {
|
||||
selected.value = i;
|
||||
val = { ...val, ___type: "shops" };
|
||||
emit("selected", [val]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="login" @click="$refs.verify.show = false">
|
||||
<div class="login" @click="hideVerify">
|
||||
<div class="login-hero">
|
||||
<img class="login-bg" :src="loginBg" alt="" />
|
||||
<div class="login-panel" @keyup.enter="submitLogin">
|
||||
@@ -10,7 +10,7 @@
|
||||
<el-tabs v-model="loginType" class="login-tabs">
|
||||
<el-tab-pane label="账号登录" name="passwordLogin">
|
||||
<el-form
|
||||
ref="usernameLoginForm"
|
||||
ref="usernameLoginFormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
class="form"
|
||||
@@ -46,11 +46,11 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="手机登录" name="mobileLogin">
|
||||
<el-form
|
||||
ref="formSms"
|
||||
ref="formSmsRef"
|
||||
:model="formSms"
|
||||
:rules="ruleInline"
|
||||
class="form"
|
||||
@click.self="$refs.verify.show = false"
|
||||
@click.self="hideVerify"
|
||||
>
|
||||
<el-form-item prop="mobile">
|
||||
<el-input
|
||||
@@ -105,7 +105,7 @@
|
||||
</el-button>
|
||||
|
||||
<verify
|
||||
ref="verify"
|
||||
ref="verifyRef"
|
||||
class="verify-con"
|
||||
verifyType="LOGIN"
|
||||
@change="verifyChange"
|
||||
@@ -117,215 +117,220 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { User, Lock, Iphone, Message } from "@element-plus/icons-vue";
|
||||
import * as RegExp from "@/utils/regular.js";
|
||||
import { sendSms } from "@/api/common.js";
|
||||
import { login, storeSmsLogin, userMsg } from "@/api/index";
|
||||
import util from "@/libs/util.js";
|
||||
import { setStore } from "@/libs/storage";
|
||||
import { Message as $Message } from "@/utils/message";
|
||||
import { md5 } from "@/utils/md5.js";
|
||||
import Footer from "@/views/main-components/footer";
|
||||
import Header from "@/views/main-components/header";
|
||||
import verify from "@/views/my-components/verify";
|
||||
import Cookies from "js-cookie";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header,
|
||||
Footer,
|
||||
verify,
|
||||
User,
|
||||
Lock,
|
||||
Iphone,
|
||||
Message,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loginBg: require("@/assets/login-bg.jpg"),
|
||||
loginLogo: require("@/assets/logo-lilishop.png"),
|
||||
saveLogin: true,
|
||||
sendCodeLoading: false,
|
||||
loading: false,
|
||||
verifyStatus: false,
|
||||
smsCodeSent: false,
|
||||
time: 60,
|
||||
loginType: "passwordLogin",
|
||||
form: {
|
||||
username: "",
|
||||
password: "",
|
||||
mobile: "",
|
||||
code: "",
|
||||
},
|
||||
formSms: {
|
||||
mobile: "",
|
||||
code: "",
|
||||
},
|
||||
rules: {
|
||||
username: [{ required: true, message: "账号不能为空", trigger: "blur" }],
|
||||
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
|
||||
},
|
||||
ruleInline: {
|
||||
mobile: [
|
||||
{ required: true, message: "请输入手机号码" },
|
||||
{
|
||||
pattern: RegExp.mobile,
|
||||
message: "请输入正确的手机号",
|
||||
},
|
||||
],
|
||||
code: [{ required: true, message: "请输入手机验证码" }],
|
||||
},
|
||||
codeMsg: "发送验证码",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMobileValid() {
|
||||
return RegExp.mobile.test(this.formSms.mobile);
|
||||
},
|
||||
canSubmitLogin() {
|
||||
if (this.loginType === "passwordLogin") {
|
||||
return !!(this.form.username.trim() && this.form.password.trim());
|
||||
}
|
||||
return this.smsCodeSent;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"formSms.mobile"() {
|
||||
this.smsCodeSent = false;
|
||||
this.verifyStatus = false;
|
||||
this.formSms.code = "";
|
||||
},
|
||||
},
|
||||
created() {
|
||||
window.localStorage.setItem("menuData", "");
|
||||
},
|
||||
methods: {
|
||||
afterLogin(res) {
|
||||
const accessToken = res.result.accessToken;
|
||||
this.setStore("accessToken", accessToken);
|
||||
this.setStore("refreshToken", res.result.refreshToken);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
||||
userMsg()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.setStore("saveLogin", this.saveLogin);
|
||||
if (this.saveLogin) {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result), {
|
||||
expires: 7,
|
||||
});
|
||||
} else {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result));
|
||||
}
|
||||
|
||||
util.initRouter(this);
|
||||
this.$store.commit("setAvatarPath", res.result.storeLogo);
|
||||
|
||||
const redirectRouter = this.$route.query.redirect;
|
||||
this.$router.push({
|
||||
path: redirectRouter || "/home",
|
||||
});
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
sendCode() {
|
||||
if (!this.isMobileValid) {
|
||||
this.$Message.warning("请输入正确的手机号");
|
||||
return;
|
||||
}
|
||||
if (!this.verifyStatus) {
|
||||
this.$refs.verify.init();
|
||||
return;
|
||||
}
|
||||
if (this.time === 60) {
|
||||
this.sendCodeLoading = true;
|
||||
const params = {
|
||||
mobile: this.formSms.mobile,
|
||||
verificationEnums: "LOGIN",
|
||||
};
|
||||
sendSms(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.smsCodeSent = true;
|
||||
this.$Message.success("验证码发送成功");
|
||||
const that = this;
|
||||
this.interval = setInterval(() => {
|
||||
that.time--;
|
||||
if (that.time === 0) {
|
||||
this.sendCodeLoading = false;
|
||||
that.time = 60;
|
||||
that.codeMsg = "重新发送";
|
||||
that.verifyStatus = false;
|
||||
clearInterval(that.interval);
|
||||
} else {
|
||||
that.codeMsg = that.time;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
this.$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.sendCodeLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
submitLogin() {
|
||||
if (!this.canSubmitLogin) return;
|
||||
|
||||
if (this.loginType === "passwordLogin") {
|
||||
this.$refs.usernameLoginForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$refs.verify.init();
|
||||
}
|
||||
});
|
||||
} else if (this.loginType === "mobileLogin") {
|
||||
this.$refs.formSms.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
storeSmsLogin(this.formSms)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.afterLogin(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
verifyChange(con) {
|
||||
if (!con.status) return;
|
||||
|
||||
if (this.loginType === "passwordLogin") {
|
||||
this.loading = true;
|
||||
const fd = new FormData();
|
||||
fd.append("username", this.form.username);
|
||||
fd.append("password", this.md5(this.form.password));
|
||||
login(fd)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res && res.success) {
|
||||
this.afterLogin(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
this.verifyStatus = true;
|
||||
this.sendCode();
|
||||
}
|
||||
|
||||
this.$refs.verify.show = false;
|
||||
},
|
||||
},
|
||||
const loginBg = require("@/assets/login-bg.jpg");
|
||||
const loginLogo = require("@/assets/logo-lilishop.png");
|
||||
const saveLogin = ref(true);
|
||||
const sendCodeLoading = ref(false);
|
||||
const loading = ref(false);
|
||||
const verifyStatus = ref(false);
|
||||
const smsCodeSent = ref(false);
|
||||
const time = ref(60);
|
||||
const loginType = ref("passwordLogin");
|
||||
const form = reactive({
|
||||
username: "",
|
||||
password: "",
|
||||
mobile: "",
|
||||
code: "",
|
||||
});
|
||||
const formSms = reactive({
|
||||
mobile: "",
|
||||
code: "",
|
||||
});
|
||||
const rules = {
|
||||
username: [{ required: true, message: "账号不能为空", trigger: "blur" }],
|
||||
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
|
||||
};
|
||||
const ruleInline = {
|
||||
mobile: [
|
||||
{ required: true, message: "请输入手机号码" },
|
||||
{
|
||||
pattern: RegExp.mobile,
|
||||
message: "请输入正确的手机号",
|
||||
},
|
||||
],
|
||||
code: [{ required: true, message: "请输入手机验证码" }],
|
||||
};
|
||||
const codeMsg = ref("发送验证码");
|
||||
let interval = null;
|
||||
|
||||
const usernameLoginFormRef = ref(null);
|
||||
const formSmsRef = ref(null);
|
||||
const verifyRef = ref(null);
|
||||
|
||||
const isMobileValid = computed(() => {
|
||||
return RegExp.mobile.test(formSms.mobile);
|
||||
});
|
||||
const canSubmitLogin = computed(() => {
|
||||
if (loginType.value === "passwordLogin") {
|
||||
return !!(form.username.trim() && form.password.trim());
|
||||
}
|
||||
return smsCodeSent.value;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => formSms.mobile,
|
||||
() => {
|
||||
smsCodeSent.value = false;
|
||||
verifyStatus.value = false;
|
||||
formSms.code = "";
|
||||
}
|
||||
);
|
||||
|
||||
window.localStorage.setItem("menuData", "");
|
||||
|
||||
function hideVerify() {
|
||||
verifyRef.value.show = false;
|
||||
}
|
||||
|
||||
function afterLogin(res) {
|
||||
const accessToken = res.result.accessToken;
|
||||
setStore("accessToken", accessToken);
|
||||
setStore("refreshToken", res.result.refreshToken);
|
||||
|
||||
userMsg()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
setStore("saveLogin", saveLogin.value);
|
||||
if (saveLogin.value) {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result), {
|
||||
expires: 7,
|
||||
});
|
||||
} else {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result));
|
||||
}
|
||||
|
||||
util.initRouter();
|
||||
appStore.setAvatarPath(res.result.storeLogo);
|
||||
|
||||
const redirectRouter = route.query.redirect;
|
||||
router.push({
|
||||
path: redirectRouter || "/home",
|
||||
});
|
||||
} else {
|
||||
loading.value = false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function sendCode() {
|
||||
if (!isMobileValid.value) {
|
||||
$Message.warning("请输入正确的手机号");
|
||||
return;
|
||||
}
|
||||
if (!verifyStatus.value) {
|
||||
verifyRef.value.init();
|
||||
return;
|
||||
}
|
||||
if (time.value === 60) {
|
||||
sendCodeLoading.value = true;
|
||||
const params = {
|
||||
mobile: formSms.mobile,
|
||||
verificationEnums: "LOGIN",
|
||||
};
|
||||
sendSms(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
smsCodeSent.value = true;
|
||||
$Message.success("验证码发送成功");
|
||||
interval = setInterval(() => {
|
||||
time.value--;
|
||||
if (time.value === 0) {
|
||||
sendCodeLoading.value = false;
|
||||
time.value = 60;
|
||||
codeMsg.value = "重新发送";
|
||||
verifyStatus.value = false;
|
||||
clearInterval(interval);
|
||||
} else {
|
||||
codeMsg.value = time.value;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
sendCodeLoading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function submitLogin() {
|
||||
if (!canSubmitLogin.value) return;
|
||||
|
||||
if (loginType.value === "passwordLogin") {
|
||||
usernameLoginFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
verifyRef.value.init();
|
||||
}
|
||||
});
|
||||
} else if (loginType.value === "mobileLogin") {
|
||||
formSmsRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
storeSmsLogin(formSms)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
afterLogin(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function verifyChange(con) {
|
||||
if (!con.status) return;
|
||||
|
||||
if (loginType.value === "passwordLogin") {
|
||||
loading.value = true;
|
||||
const fd = new FormData();
|
||||
fd.append("username", form.username);
|
||||
fd.append("password", md5(form.password));
|
||||
login(fd)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res && res.success) {
|
||||
afterLogin(res);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
verifyStatus.value = true;
|
||||
sendCode();
|
||||
}
|
||||
|
||||
hideVerify();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -17,39 +17,37 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useSettingStore } from "@/stores/setting";
|
||||
import { setStore } from "@/libs/storage";
|
||||
|
||||
export default {
|
||||
name: "configDrawer",
|
||||
data() {
|
||||
return {
|
||||
drawer: false,
|
||||
};
|
||||
defineOptions({ name: "configDrawer" });
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
const drawer = ref(false);
|
||||
|
||||
const setting = computed(() => settingStore.setting);
|
||||
|
||||
watch(
|
||||
setting,
|
||||
(val) => {
|
||||
setStore("setting", val);
|
||||
settingStore.updateSetting(val);
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
setting: (state) => state.setting.setting,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
setting: {
|
||||
handler(val) {
|
||||
this.setStore("setting", val);
|
||||
this.$store.commit("updateSetting", val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.drawer = true;
|
||||
},
|
||||
close() {
|
||||
this.drawer = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function open() {
|
||||
drawer.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
drawer.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -32,18 +32,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const config = require("@/config/index");
|
||||
<script setup>
|
||||
import config from "@/config/index";
|
||||
import zhizhaoImg from "@/assets/images/zhizhao.jpg";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
zhizhaoImg: require("@/assets/images/zhizhao.jpg"),
|
||||
year: new Date().getFullYear(),
|
||||
};
|
||||
},
|
||||
};
|
||||
const year = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -9,93 +9,90 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount } from "vue";
|
||||
import { FullScreen, ScaleToOriginal } from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
name: "fullScreen",
|
||||
components: { FullScreen, ScaleToOriginal },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
defineOptions({ name: "fullScreen" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change"],
|
||||
computed: {
|
||||
showFullScreenBtn() {
|
||||
return window.navigator.userAgent.indexOf("MSIE") < 0;
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
methods: {
|
||||
handleFullscreen() {
|
||||
const main = document.body;
|
||||
if (this.modelValue) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (main.requestFullscreen) {
|
||||
main.requestFullscreen();
|
||||
} else if (main.mozRequestFullScreen) {
|
||||
main.mozRequestFullScreen();
|
||||
} else if (main.webkitRequestFullScreen) {
|
||||
main.webkitRequestFullScreen();
|
||||
} else if (main.msRequestFullscreen) {
|
||||
main.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
},
|
||||
handleChange() {
|
||||
this.handleFullscreen();
|
||||
},
|
||||
emitFullscreenState(isFullscreen) {
|
||||
this.$emit("update:modelValue", isFullscreen);
|
||||
this.$emit("input", isFullscreen);
|
||||
this.$emit("on-change", isFullscreen);
|
||||
},
|
||||
onFullscreenChange() {
|
||||
const isFullscreen = !!(
|
||||
document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen
|
||||
);
|
||||
this.emitFullscreenState(isFullscreen);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const isFullscreen = !!(
|
||||
document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen
|
||||
);
|
||||
this.emitFullscreenState(isFullscreen);
|
||||
document.addEventListener("fullscreenchange", this.onFullscreenChange);
|
||||
document.addEventListener("mozfullscreenchange", this.onFullscreenChange);
|
||||
document.addEventListener("webkitfullscreenchange", this.onFullscreenChange);
|
||||
document.addEventListener("msfullscreenchange", this.onFullscreenChange);
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener("fullscreenchange", this.onFullscreenChange);
|
||||
document.removeEventListener("mozfullscreenchange", this.onFullscreenChange);
|
||||
document.removeEventListener("webkitfullscreenchange", this.onFullscreenChange);
|
||||
document.removeEventListener("msfullscreenchange", this.onFullscreenChange);
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
|
||||
const showFullScreenBtn = computed(() => {
|
||||
return window.navigator.userAgent.indexOf("MSIE") < 0;
|
||||
});
|
||||
|
||||
function handleFullscreen() {
|
||||
const main = document.body;
|
||||
if (props.modelValue) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (main.requestFullscreen) {
|
||||
main.requestFullscreen();
|
||||
} else if (main.mozRequestFullScreen) {
|
||||
main.mozRequestFullScreen();
|
||||
} else if (main.webkitRequestFullScreen) {
|
||||
main.webkitRequestFullScreen();
|
||||
} else if (main.msRequestFullscreen) {
|
||||
main.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleChange() {
|
||||
handleFullscreen();
|
||||
}
|
||||
function emitFullscreenState(isFullscreen) {
|
||||
emit("update:modelValue", isFullscreen);
|
||||
emit("input", isFullscreen);
|
||||
emit("on-change", isFullscreen);
|
||||
}
|
||||
function onFullscreenChange() {
|
||||
const isFullscreen = !!(
|
||||
document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen
|
||||
);
|
||||
emitFullscreenState(isFullscreen);
|
||||
}
|
||||
|
||||
const isFullscreen = !!(
|
||||
document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen
|
||||
);
|
||||
emitFullscreenState(isFullscreen);
|
||||
document.addEventListener("fullscreenchange", onFullscreenChange);
|
||||
document.addEventListener("mozfullscreenchange", onFullscreenChange);
|
||||
document.addEventListener("webkitfullscreenchange", onFullscreenChange);
|
||||
document.addEventListener("msfullscreenchange", onFullscreenChange);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("fullscreenchange", onFullscreenChange);
|
||||
document.removeEventListener("mozfullscreenchange", onFullscreenChange);
|
||||
document.removeEventListener("webkitfullscreenchange", onFullscreenChange);
|
||||
document.removeEventListener("msfullscreenchange", onFullscreenChange);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -6,63 +6,58 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getBaseSite } from "@/api/common.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
storeSideLogo: require("@/assets/logo-lilishop.png"),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (
|
||||
!localStorage.getItem("sellersiteName") ||
|
||||
!localStorage.getItem("sellerlogoImg") ||
|
||||
!localStorage.getItem("sellerIconImg") ||
|
||||
!localStorage.getItem("seller_expiration_time")
|
||||
) {
|
||||
this.getSite();
|
||||
} else if (new Date() > localStorage.getItem("seller_expiration_time")) {
|
||||
this.getSite();
|
||||
} else {
|
||||
this.storeSideLogo = localStorage.getItem("sellerlogoImg");
|
||||
window.document.title = localStorage.getItem("sellersiteName");
|
||||
this.applyFavicon(localStorage.getItem("sellerIconImg"));
|
||||
const storeSideLogo = ref(require("@/assets/logo-lilishop.png"));
|
||||
|
||||
function init() {
|
||||
if (
|
||||
!localStorage.getItem("sellersiteName") ||
|
||||
!localStorage.getItem("sellerlogoImg") ||
|
||||
!localStorage.getItem("sellerIconImg") ||
|
||||
!localStorage.getItem("seller_expiration_time")
|
||||
) {
|
||||
getSite();
|
||||
} else if (new Date() > localStorage.getItem("seller_expiration_time")) {
|
||||
getSite();
|
||||
} else {
|
||||
storeSideLogo.value = localStorage.getItem("sellerlogoImg");
|
||||
window.document.title = localStorage.getItem("sellersiteName");
|
||||
applyFavicon(localStorage.getItem("sellerIconImg"));
|
||||
}
|
||||
}
|
||||
function applyFavicon(href) {
|
||||
const link =
|
||||
document.querySelector("link[rel*='icon']") ||
|
||||
document.createElement("link");
|
||||
link.type = "image/x-icon";
|
||||
link.href = href;
|
||||
link.rel = "shortcut icon";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
}
|
||||
function getSite() {
|
||||
getBaseSite()
|
||||
.then((res) => {
|
||||
if (res.success && res.result.settingValue) {
|
||||
const data = JSON.parse(res.result.settingValue);
|
||||
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
||||
localStorage.setItem("seller_expiration_time", expirationTime);
|
||||
localStorage.setItem("sellersiteName", data.siteName);
|
||||
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
|
||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
||||
storeSideLogo.value = data.storeSideLogo;
|
||||
window.document.title = data.siteName;
|
||||
applyFavicon(data.storeSideIcon);
|
||||
}
|
||||
},
|
||||
applyFavicon(href) {
|
||||
const link =
|
||||
document.querySelector("link[rel*='icon']") ||
|
||||
document.createElement("link");
|
||||
link.type = "image/x-icon";
|
||||
link.href = href;
|
||||
link.rel = "shortcut icon";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
},
|
||||
getSite() {
|
||||
getBaseSite()
|
||||
.then((res) => {
|
||||
if (res.success && res.result.settingValue) {
|
||||
const data = JSON.parse(res.result.settingValue);
|
||||
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
||||
localStorage.setItem("seller_expiration_time", expirationTime);
|
||||
localStorage.setItem("sellersiteName", data.siteName);
|
||||
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
|
||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
||||
this.storeSideLogo = data.storeSideLogo;
|
||||
window.document.title = data.siteName;
|
||||
this.applyFavicon(data.storeSideIcon);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -12,30 +12,30 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
import { Bell } from "@element-plus/icons-vue";
|
||||
import util from "@/libs/util.js";
|
||||
|
||||
export default {
|
||||
name: "messageTip",
|
||||
components: { Bell },
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
defineOptions({ name: "messageTip" });
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
methods: {
|
||||
showMessage() {
|
||||
util.openNewPage(this, "message_index");
|
||||
this.$router.push({
|
||||
name: "message_index",
|
||||
});
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function showMessage() {
|
||||
util.openNewPage("message_index");
|
||||
router.push({
|
||||
name: "message_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,71 +33,77 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import util, { HOME_NAV_NAME, HOME_NAV_ITEM } from "@/libs/util.js";
|
||||
import { setStore } from "@/libs/storage";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
export default {
|
||||
name: "shrinkableMenu",
|
||||
computed: {
|
||||
menuList() {
|
||||
return this.$store.state.app.menuList;
|
||||
},
|
||||
navList() {
|
||||
return this.$store.state.app.navList;
|
||||
},
|
||||
currNav() {
|
||||
return this.$store.state.app.currNav;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(val) {
|
||||
this.syncRouteMenu(val);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
syncRouteMenu(route) {
|
||||
if (!route?.name) {
|
||||
return;
|
||||
}
|
||||
if (route.name === "home_index") {
|
||||
this.activateHomeNav();
|
||||
return;
|
||||
}
|
||||
const firstRouterName =
|
||||
route.meta?.firstRouterName || util.getFirstRouterName(route.name);
|
||||
if (firstRouterName && firstRouterName !== this.currNav) {
|
||||
this.selectNav(firstRouterName);
|
||||
}
|
||||
},
|
||||
activateHomeNav() {
|
||||
this.$store.commit("setCurrNav", HOME_NAV_NAME);
|
||||
this.$store.commit("setCurrNavTitle", HOME_NAV_ITEM.title);
|
||||
this.setStore("currNav", HOME_NAV_NAME);
|
||||
this.$store.commit("updateMenulist", []);
|
||||
},
|
||||
changeMenu(name) {
|
||||
if (!name) return;
|
||||
this.$router.push({ name });
|
||||
},
|
||||
selectNav(name) {
|
||||
this.$store.commit("childrenMenu", this.$refs.childrenMenu);
|
||||
this.$store.commit("setCurrNav", name);
|
||||
this.setStore("currNav", name);
|
||||
if (name === HOME_NAV_NAME) {
|
||||
this.$store.commit("updateMenulist", []);
|
||||
this.$store.commit("setCurrNavTitle", HOME_NAV_ITEM.title);
|
||||
if (this.$route.name !== "home_index") {
|
||||
this.$router.push({ name: "home_index" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
util.initRouter(this);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.syncRouteMenu(this.$route);
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "shrinkableMenu" });
|
||||
|
||||
const appStore = useAppStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const childrenMenu = ref(null);
|
||||
|
||||
const menuList = computed(() => appStore.menuList);
|
||||
const navList = computed(() => appStore.navList);
|
||||
const currNav = computed(() => appStore.currNav);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
syncRouteMenu(route);
|
||||
}
|
||||
);
|
||||
|
||||
function syncRouteMenu(route) {
|
||||
if (!route?.name) {
|
||||
return;
|
||||
}
|
||||
if (route.name === "home_index") {
|
||||
activateHomeNav();
|
||||
return;
|
||||
}
|
||||
const firstRouterName =
|
||||
route.meta?.firstRouterName || util.getFirstRouterName(route.name);
|
||||
if (firstRouterName && firstRouterName !== currNav.value) {
|
||||
selectNav(firstRouterName);
|
||||
}
|
||||
}
|
||||
|
||||
function activateHomeNav() {
|
||||
appStore.setCurrNav(HOME_NAV_NAME);
|
||||
appStore.setCurrNavTitle(HOME_NAV_ITEM.title);
|
||||
setStore("currNav", HOME_NAV_NAME);
|
||||
appStore.updateMenulist([]);
|
||||
}
|
||||
|
||||
function changeMenu(name) {
|
||||
if (!name) return;
|
||||
router.push({ name });
|
||||
}
|
||||
|
||||
function selectNav(name) {
|
||||
appStore.childrenMenu(childrenMenu.value);
|
||||
appStore.setCurrNav(name);
|
||||
setStore("currNav", name);
|
||||
if (name === HOME_NAV_NAME) {
|
||||
appStore.updateMenulist([]);
|
||||
appStore.setCurrNavTitle(HOME_NAV_ITEM.title);
|
||||
if (route.name !== "home_index") {
|
||||
router.push({ name: "home_index" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
util.initRouter();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
syncRouteMenu(route);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -36,129 +36,126 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import Cookies from "js-cookie";
|
||||
import vueQr from "vue-qr";
|
||||
import { getStoreMpQrcode } from "@/api/index";
|
||||
import { getBaseSite } from "@/api/common.js";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "store-qrcode",
|
||||
components: { "vue-qr": vueQr },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
storeId: "",
|
||||
wapUrl: "",
|
||||
pcUrlBase: "",
|
||||
mpQrcode: "",
|
||||
mpError: "",
|
||||
mpLoaded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
h5Url() {
|
||||
if (!this.storeId || !this.wapUrl) return "";
|
||||
return this.joinUrl(this.wapUrl, `/pages/product/shopPage?id=${this.storeId}`);
|
||||
},
|
||||
pcUrl() {
|
||||
if (!this.storeId || !this.pcUrlBase) return "";
|
||||
return this.joinUrl(this.pcUrlBase, `/merchant?id=${this.storeId}`);
|
||||
},
|
||||
mpQrSrc() {
|
||||
if (!this.mpQrcode) return "";
|
||||
const mime = String(this.mpQrcode).startsWith("/9j/")
|
||||
? "image/jpeg"
|
||||
: "image/png";
|
||||
return `data:${mime};base64,${this.mpQrcode}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
joinUrl(base, path) {
|
||||
return String(base || "").replace(/\/$/, "") + path;
|
||||
},
|
||||
parseSettingValue(raw) {
|
||||
if (!raw) return {};
|
||||
if (typeof raw === "object") return raw;
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return {};
|
||||
defineOptions({ name: "store-qrcode" });
|
||||
|
||||
const visible = ref(false);
|
||||
const storeId = ref("");
|
||||
const wapUrl = ref("");
|
||||
const pcUrlBase = ref("");
|
||||
const mpQrcode = ref("");
|
||||
const mpError = ref("");
|
||||
const mpLoaded = ref(false);
|
||||
|
||||
const h5Url = computed(() => {
|
||||
if (!storeId.value || !wapUrl.value) return "";
|
||||
return joinUrl(wapUrl.value, `/pages/product/shopPage?id=${storeId.value}`);
|
||||
});
|
||||
const pcUrl = computed(() => {
|
||||
if (!storeId.value || !pcUrlBase.value) return "";
|
||||
return joinUrl(pcUrlBase.value, `/merchant?id=${storeId.value}`);
|
||||
});
|
||||
const mpQrSrc = computed(() => {
|
||||
if (!mpQrcode.value) return "";
|
||||
const mime = String(mpQrcode.value).startsWith("/9j/")
|
||||
? "image/jpeg"
|
||||
: "image/png";
|
||||
return `data:${mime};base64,${mpQrcode.value}`;
|
||||
});
|
||||
|
||||
function joinUrl(base, path) {
|
||||
return String(base || "").replace(/\/$/, "") + path;
|
||||
}
|
||||
function parseSettingValue(raw) {
|
||||
if (!raw) return {};
|
||||
if (typeof raw === "object") return raw;
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
function initStore() {
|
||||
try {
|
||||
const userInfo = JSON.parse(Cookies.get("userInfoSeller") || "{}") || {};
|
||||
storeId.value = userInfo.id || userInfo.storeId || "";
|
||||
} catch (e) {
|
||||
storeId.value = "";
|
||||
}
|
||||
}
|
||||
function open() {
|
||||
initStore();
|
||||
visible.value = true;
|
||||
}
|
||||
function loadCodes() {
|
||||
initStore();
|
||||
getBaseSite()
|
||||
.then((res) => {
|
||||
const data = parseSettingValue(res && res.result && res.result.settingValue);
|
||||
const fallback = typeof window !== "undefined" ? window.BASE || {} : {};
|
||||
// wap/站点地址常配官网,H5、PC 店铺页用买家端域名
|
||||
wapUrl.value = fallback.WAP_URL || data.staticPageWapAddress || "";
|
||||
pcUrlBase.value = fallback.PC_URL || data.staticPageAddress || "";
|
||||
})
|
||||
.catch(() => {
|
||||
const fallback = typeof window !== "undefined" ? window.BASE || {} : {};
|
||||
wapUrl.value = fallback.WAP_URL || "";
|
||||
pcUrlBase.value = fallback.PC_URL || "";
|
||||
});
|
||||
loadMpQrcode();
|
||||
}
|
||||
function loadMpQrcode() {
|
||||
if (!storeId.value || mpLoaded.value) return;
|
||||
mpError.value = "";
|
||||
getStoreMpQrcode()
|
||||
.then((res) => {
|
||||
if (res && res.success && res.result) {
|
||||
if (String(res.result).startsWith("eyJ")) {
|
||||
mpError.value = "请先配置微信小程序";
|
||||
return;
|
||||
}
|
||||
mpQrcode.value = res.result;
|
||||
mpLoaded.value = true;
|
||||
mpError.value = "";
|
||||
} else {
|
||||
mpError.value = "请先配置微信小程序";
|
||||
}
|
||||
},
|
||||
initStore() {
|
||||
try {
|
||||
const userInfo = JSON.parse(Cookies.get("userInfoSeller") || "{}") || {};
|
||||
this.storeId = userInfo.id || userInfo.storeId || "";
|
||||
} catch (e) {
|
||||
this.storeId = "";
|
||||
}
|
||||
},
|
||||
open() {
|
||||
this.initStore();
|
||||
this.visible = true;
|
||||
},
|
||||
loadCodes() {
|
||||
this.initStore();
|
||||
getBaseSite()
|
||||
.then((res) => {
|
||||
const data = this.parseSettingValue(res && res.result && res.result.settingValue);
|
||||
const fallback = typeof window !== "undefined" ? window.BASE || {} : {};
|
||||
// wap/站点地址常配官网,H5、PC 店铺页用买家端域名
|
||||
this.wapUrl = fallback.WAP_URL || data.staticPageWapAddress || "";
|
||||
this.pcUrlBase = fallback.PC_URL || data.staticPageAddress || "";
|
||||
})
|
||||
.catch(() => {
|
||||
const fallback = typeof window !== "undefined" ? window.BASE || {} : {};
|
||||
this.wapUrl = fallback.WAP_URL || "";
|
||||
this.pcUrlBase = fallback.PC_URL || "";
|
||||
});
|
||||
this.loadMpQrcode();
|
||||
},
|
||||
loadMpQrcode() {
|
||||
if (!this.storeId || this.mpLoaded) return;
|
||||
this.mpError = "";
|
||||
getStoreMpQrcode()
|
||||
.then((res) => {
|
||||
if (res && res.success && res.result) {
|
||||
if (String(res.result).startsWith("eyJ")) {
|
||||
this.mpError = "请先配置微信小程序";
|
||||
return;
|
||||
}
|
||||
this.mpQrcode = res.result;
|
||||
this.mpLoaded = true;
|
||||
this.mpError = "";
|
||||
} else {
|
||||
this.mpError = "请先配置微信小程序";
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.mpError = "请先配置微信小程序";
|
||||
});
|
||||
},
|
||||
copyText(text) {
|
||||
if (!text) return;
|
||||
const ok = () => this.$Message.success("复制成功");
|
||||
const fail = () => this.$Message.error("复制失败");
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(ok).catch(fail);
|
||||
return;
|
||||
}
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
ok();
|
||||
} catch (e) {
|
||||
fail();
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
mpError.value = "请先配置微信小程序";
|
||||
});
|
||||
}
|
||||
function copyText(text) {
|
||||
if (!text) return;
|
||||
const ok = () => Message.success("复制成功");
|
||||
const fail = () => Message.error("复制失败");
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(ok).catch(fail);
|
||||
return;
|
||||
}
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
ok();
|
||||
} catch (e) {
|
||||
fail();
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
// 外部无 ref/$refs/$parent 访问(仅 Main.vue 以 <store-qrcode /> 使用),无需暴露成员
|
||||
defineExpose({});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -41,148 +41,163 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tagsPageOpened",
|
||||
props: {
|
||||
pageTagsList: Array,
|
||||
beforePush: {
|
||||
type: Function,
|
||||
default: () => true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPageName: this.$route.name,
|
||||
tagBodyLeft: 0,
|
||||
visible: false,
|
||||
contextMenuLeft: 0,
|
||||
contextMenuTop: 0,
|
||||
actionList: {
|
||||
others: "关闭其他",
|
||||
clearAll: "关闭所有",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tagsList() {
|
||||
return this.$store.state.app.storeOpenedList;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(item) {
|
||||
return item.children
|
||||
? item.children[0].name === this.currentPageName
|
||||
: item.name === this.currentPageName;
|
||||
},
|
||||
tagType(item) {
|
||||
return this.isActive(item) ? "primary" : "info";
|
||||
},
|
||||
itemTitle(item) {
|
||||
if (typeof item.title === "object") {
|
||||
return item.title;
|
||||
}
|
||||
return item.title;
|
||||
},
|
||||
closePage(event, name) {
|
||||
let storeOpenedList = this.$store.state.app.storeOpenedList;
|
||||
let lastPageObj = storeOpenedList[0];
|
||||
if (this.currentPageName === name) {
|
||||
const len = storeOpenedList.length;
|
||||
for (let i = 1; i < len; i++) {
|
||||
if (storeOpenedList[i].name === name) {
|
||||
lastPageObj =
|
||||
i < len - 1 ? storeOpenedList[i + 1] : storeOpenedList[i - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (event && event.target) {
|
||||
const tagWidth = event.target.parentNode?.offsetWidth || 0;
|
||||
this.tagBodyLeft = Math.min(this.tagBodyLeft + tagWidth, 0);
|
||||
}
|
||||
this.$store.commit("removeTag", name);
|
||||
this.$store.commit("closePage", name);
|
||||
storeOpenedList = this.$store.state.app.storeOpenedList;
|
||||
localStorage.storeOpenedList = JSON.stringify(storeOpenedList);
|
||||
if (this.currentPageName === name) {
|
||||
this.linkTo(lastPageObj);
|
||||
}
|
||||
},
|
||||
linkTo(item) {
|
||||
if (this.$route.name === item.name) return;
|
||||
const routerObj = { name: item.name };
|
||||
if (item.argu) routerObj.params = item.argu;
|
||||
if (item.query) routerObj.query = item.query;
|
||||
if (this.beforePush(item)) {
|
||||
this.$router.push(routerObj);
|
||||
}
|
||||
},
|
||||
handlescroll(e) {
|
||||
const type = e.type;
|
||||
let delta = 0;
|
||||
if (type === "DOMMouseScroll" || type === "mousewheel") {
|
||||
delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40;
|
||||
}
|
||||
let left = 0;
|
||||
if (delta > 0) {
|
||||
left = Math.min(0, this.tagBodyLeft + delta);
|
||||
} else if (
|
||||
this.$refs.scrollCon.offsetWidth - 100 <
|
||||
this.$refs.scrollBody.offsetWidth
|
||||
) {
|
||||
if (
|
||||
this.tagBodyLeft <
|
||||
-(this.$refs.scrollBody.offsetWidth - this.$refs.scrollCon.offsetWidth + 100)
|
||||
) {
|
||||
left = this.tagBodyLeft;
|
||||
} else {
|
||||
left = Math.max(
|
||||
this.tagBodyLeft + delta,
|
||||
this.$refs.scrollCon.offsetWidth -
|
||||
this.$refs.scrollBody.offsetWidth -
|
||||
100
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.tagBodyLeft = 0;
|
||||
}
|
||||
this.tagBodyLeft = left;
|
||||
},
|
||||
handleTagsOption(type) {
|
||||
if (type === "clearAll") {
|
||||
this.$store.commit("clearAllTags");
|
||||
this.$router.push({ name: "home_index" });
|
||||
} else {
|
||||
this.$store.commit("clearOtherTags", this);
|
||||
}
|
||||
this.tagBodyLeft = 0;
|
||||
},
|
||||
contextMenu(item, e) {
|
||||
this.visible = true;
|
||||
const offsetLeft = this.$el.getBoundingClientRect().left;
|
||||
this.contextMenuLeft = e.clientX - offsetLeft + 10;
|
||||
this.contextMenuTop = e.clientY - 64;
|
||||
},
|
||||
closeMenu() {
|
||||
this.visible = false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to) {
|
||||
this.currentPageName = to.name;
|
||||
},
|
||||
visible(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener("click", this.closeMenu);
|
||||
} else {
|
||||
document.body.removeEventListener("click", this.closeMenu);
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.body.removeEventListener("click", this.closeMenu);
|
||||
<script setup>
|
||||
import { ref, computed, watch, onBeforeUnmount } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
defineOptions({ name: "tagsPageOpened" });
|
||||
|
||||
const props = defineProps({
|
||||
pageTagsList: Array,
|
||||
beforePush: {
|
||||
type: Function,
|
||||
default: () => true,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const currentPageName = ref(route.name);
|
||||
const tagBodyLeft = ref(0);
|
||||
const visible = ref(false);
|
||||
const contextMenuLeft = ref(0);
|
||||
const contextMenuTop = ref(0);
|
||||
const actionList = {
|
||||
others: "关闭其他",
|
||||
clearAll: "关闭所有",
|
||||
};
|
||||
|
||||
const scrollCon = ref(null);
|
||||
const scrollBody = ref(null);
|
||||
|
||||
const tagsList = computed(() => appStore.storeOpenedList);
|
||||
|
||||
const isActive = (item) => {
|
||||
return item.children
|
||||
? item.children[0].name === currentPageName.value
|
||||
: item.name === currentPageName.value;
|
||||
};
|
||||
|
||||
const tagType = (item) => {
|
||||
return isActive(item) ? "primary" : "info";
|
||||
};
|
||||
|
||||
const itemTitle = (item) => {
|
||||
if (typeof item.title === "object") {
|
||||
return item.title;
|
||||
}
|
||||
return item.title;
|
||||
};
|
||||
|
||||
const closePage = (event, name) => {
|
||||
let storeOpenedList = appStore.storeOpenedList;
|
||||
let lastPageObj = storeOpenedList[0];
|
||||
if (currentPageName.value === name) {
|
||||
const len = storeOpenedList.length;
|
||||
for (let i = 1; i < len; i++) {
|
||||
if (storeOpenedList[i].name === name) {
|
||||
lastPageObj =
|
||||
i < len - 1 ? storeOpenedList[i + 1] : storeOpenedList[i - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (event && event.target) {
|
||||
const tagWidth = event.target.parentNode?.offsetWidth || 0;
|
||||
tagBodyLeft.value = Math.min(tagBodyLeft.value + tagWidth, 0);
|
||||
}
|
||||
appStore.removeTag(name);
|
||||
appStore.closePage(name);
|
||||
storeOpenedList = appStore.storeOpenedList;
|
||||
localStorage.storeOpenedList = JSON.stringify(storeOpenedList);
|
||||
if (currentPageName.value === name) {
|
||||
linkTo(lastPageObj);
|
||||
}
|
||||
};
|
||||
|
||||
const linkTo = (item) => {
|
||||
if (route.name === item.name) return;
|
||||
const routerObj = { name: item.name };
|
||||
if (item.argu) routerObj.params = item.argu;
|
||||
if (item.query) routerObj.query = item.query;
|
||||
if (props.beforePush(item)) {
|
||||
router.push(routerObj);
|
||||
}
|
||||
};
|
||||
|
||||
const handlescroll = (e) => {
|
||||
const type = e.type;
|
||||
let delta = 0;
|
||||
if (type === "DOMMouseScroll" || type === "mousewheel") {
|
||||
delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40;
|
||||
}
|
||||
let left = 0;
|
||||
if (delta > 0) {
|
||||
left = Math.min(0, tagBodyLeft.value + delta);
|
||||
} else if (
|
||||
scrollCon.value.offsetWidth - 100 <
|
||||
scrollBody.value.offsetWidth
|
||||
) {
|
||||
if (
|
||||
tagBodyLeft.value <
|
||||
-(scrollBody.value.offsetWidth - scrollCon.value.offsetWidth + 100)
|
||||
) {
|
||||
left = tagBodyLeft.value;
|
||||
} else {
|
||||
left = Math.max(
|
||||
tagBodyLeft.value + delta,
|
||||
scrollCon.value.offsetWidth -
|
||||
scrollBody.value.offsetWidth -
|
||||
100
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tagBodyLeft.value = 0;
|
||||
}
|
||||
tagBodyLeft.value = left;
|
||||
};
|
||||
|
||||
const handleTagsOption = (type) => {
|
||||
if (type === "clearAll") {
|
||||
appStore.clearAllTags();
|
||||
router.push({ name: "home_index" });
|
||||
} else {
|
||||
appStore.clearOtherTags(route.name);
|
||||
}
|
||||
tagBodyLeft.value = 0;
|
||||
};
|
||||
|
||||
const contextMenu = (item, e) => {
|
||||
visible.value = true;
|
||||
const offsetLeft = scrollCon.value.getBoundingClientRect().left;
|
||||
contextMenuLeft.value = e.clientX - offsetLeft + 10;
|
||||
contextMenuTop.value = e.clientY - 64;
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(name) => {
|
||||
currentPageName.value = name;
|
||||
}
|
||||
);
|
||||
|
||||
watch(visible, (value) => {
|
||||
if (value) {
|
||||
document.body.addEventListener("click", closeMenu);
|
||||
} else {
|
||||
document.body.removeEventListener("click", closeMenu);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.body.removeEventListener("click", closeMenu);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -42,7 +42,7 @@
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<el-table v-loading="loading" border :data="data" ref="table" class="mt_10" style="width: 100%">
|
||||
<el-table v-loading="loading" border :data="data" ref="tableRef" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="memberName" label="会员名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="content" label="评价内容" min-width="300" show-overflow-tooltip />
|
||||
@@ -88,7 +88,7 @@
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="modalVisible" :title="modalTitle" width="500px" :close-on-click-modal="false">
|
||||
<el-form ref="form" :model="form" label-width="100px" :rules="formValidate">
|
||||
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
|
||||
<el-form-item label="评价内容">
|
||||
<span v-if="!content">暂无评价</span>
|
||||
<el-input v-else v-model="content" type="textarea" maxlength="200" disabled :rows="4" style="width: 90%" />
|
||||
@@ -126,127 +126,129 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import * as API_Member from "@/api/member";
|
||||
import { Message } from "@/utils/message";
|
||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||
|
||||
export default {
|
||||
name: "memberComment",
|
||||
components: { uploadPicThumb },
|
||||
data() {
|
||||
return {
|
||||
detailInfo: {},
|
||||
image: [],
|
||||
replyStatus: false,
|
||||
modalVisible: false,
|
||||
modalTitle: "",
|
||||
loading: true,
|
||||
content: "",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
selectDate: null,
|
||||
form: {
|
||||
replyImage: [],
|
||||
reply: "",
|
||||
},
|
||||
formValidate: {
|
||||
reply: [{ required: true, message: "请输入回复内容", trigger: "blur" }],
|
||||
},
|
||||
submitLoading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 10;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
};
|
||||
this.selectDate = null;
|
||||
this.getDataList();
|
||||
},
|
||||
selectDateRange(v) {
|
||||
if (v && v.length === 2) {
|
||||
this.searchForm.startTime = v[0];
|
||||
this.searchForm.endTime = v[1];
|
||||
} else {
|
||||
this.searchForm.startTime = "";
|
||||
this.searchForm.endTime = "";
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
API_Member.getMemberReview(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
API_Member.replyMemberReview(this.form.id, this.form).then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("回复成功");
|
||||
this.getDataList();
|
||||
this.modalVisible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
detail(v) {
|
||||
this.form.replyImage = [];
|
||||
this.loading = true;
|
||||
API_Member.getMemberInfoReview(v.id).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.form.id = res.result.id;
|
||||
this.content = res.result.content;
|
||||
this.form.reply = res.result.reply;
|
||||
this.replyStatus = res.result.replyStatus;
|
||||
this.image = res.result.images ? (res.result.images || "").split(",") : [];
|
||||
this.form.replyImage = res.result.replyImage ? (res.result.replyImage || "").split(",") : [];
|
||||
this.detailInfo = res.result;
|
||||
this.modalVisible = true;
|
||||
this.modalTitle = "详细";
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
defineOptions({ name: "memberComment" });
|
||||
|
||||
const detailInfo = ref({});
|
||||
const image = ref([]);
|
||||
const replyStatus = ref(false);
|
||||
const modalVisible = ref(false);
|
||||
const modalTitle = ref("");
|
||||
const loading = ref(true);
|
||||
const content = ref("");
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
});
|
||||
const selectDate = ref(null);
|
||||
const form = reactive({
|
||||
replyImage: [],
|
||||
reply: "",
|
||||
});
|
||||
const formValidate = {
|
||||
reply: [{ required: true, message: "请输入回复内容", trigger: "blur" }],
|
||||
};
|
||||
const submitLoading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// searchFormRef/tableRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
// formRef:模板 ref 改名避免与顶层变量 form 互相覆盖
|
||||
const formRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.pageSize = 10;
|
||||
getDataList();
|
||||
}
|
||||
function handleReset() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
};
|
||||
selectDate.value = null;
|
||||
getDataList();
|
||||
}
|
||||
function selectDateRange(v) {
|
||||
if (v && v.length === 2) {
|
||||
searchForm.value.startTime = v[0];
|
||||
searchForm.value.endTime = v[1];
|
||||
} else {
|
||||
searchForm.value.startTime = "";
|
||||
searchForm.value.endTime = "";
|
||||
}
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Member.getMemberReview(searchForm.value).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleSubmit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
API_Member.replyMemberReview(form.id, form).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("回复成功");
|
||||
getDataList();
|
||||
modalVisible.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function detail(v) {
|
||||
form.replyImage = [];
|
||||
loading.value = true;
|
||||
API_Member.getMemberInfoReview(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
form.id = res.result.id;
|
||||
content.value = res.result.content;
|
||||
form.reply = res.result.reply;
|
||||
replyStatus.value = res.result.replyStatus;
|
||||
image.value = res.result.images ? (res.result.images || "").split(",") : [];
|
||||
form.replyImage = res.result.replyImage ? (res.result.replyImage || "").split(",") : [];
|
||||
detailInfo.value = res.result;
|
||||
modalVisible.value = true;
|
||||
modalTitle.value = "详细";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -84,137 +84,137 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { Clock, ArrowLeft } from "@element-plus/icons-vue";
|
||||
import * as API_Index from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "message_index",
|
||||
components: { Clock, ArrowLeft },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
mesTypeList: [
|
||||
{ type: "unread", title: "未读消息" },
|
||||
{ type: "read", title: "已读消息" },
|
||||
{ type: "recycleBin", title: "回收站" },
|
||||
],
|
||||
params: {
|
||||
status: "UN_READY",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
},
|
||||
total: 0,
|
||||
currentMesList: [],
|
||||
currentMessageType: "unread",
|
||||
showMesTitleList: true,
|
||||
unReadCount: 0,
|
||||
noDataText: "暂无未读消息",
|
||||
mes: {
|
||||
title: "",
|
||||
time: "",
|
||||
content: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changePage() {
|
||||
this.refreshMessage();
|
||||
},
|
||||
changePageSize() {
|
||||
this.refreshMessage();
|
||||
},
|
||||
refreshMessage() {
|
||||
let status;
|
||||
const type = this.currentMessageType;
|
||||
if (type == "unread") status = "UN_READY";
|
||||
else if (type == "read") status = "ALREADY_READY";
|
||||
else status = "ALREADY_REMOVE";
|
||||
this.params.status = status;
|
||||
this.loading = true;
|
||||
API_Index.getMessageSendData(this.params).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.currentMesList = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
},
|
||||
getAll() {
|
||||
API_Index.getAllMessage(this.params).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.unReadCount = res.result.UN_READY.total;
|
||||
this.currentMesList = res.result.UN_READY.records;
|
||||
}
|
||||
});
|
||||
},
|
||||
backMesTitleList() {
|
||||
this.showMesTitleList = true;
|
||||
},
|
||||
setCurrentMesType(type) {
|
||||
if (this.currentMessageType !== type) {
|
||||
this.showMesTitleList = true;
|
||||
}
|
||||
this.currentMessageType = type;
|
||||
if (type == "unread") this.noDataText = "暂无未读消息";
|
||||
else if (type == "read") this.noDataText = "暂无已读消息";
|
||||
else this.noDataText = "回收站无消息";
|
||||
this.params.pageNumber = 1;
|
||||
this.refreshMessage();
|
||||
},
|
||||
openMessage(row) {
|
||||
this.showMesTitleList = false;
|
||||
this.mes.title = row.title;
|
||||
this.mes.time = row.createTime;
|
||||
this.getContent(row);
|
||||
},
|
||||
markAsRead(v) {
|
||||
this.loading = true;
|
||||
API_Index.read(v.id).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) this.getAll();
|
||||
});
|
||||
},
|
||||
deleteMes(v) {
|
||||
this.loading = true;
|
||||
API_Index.deleteMessage(v.id).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) this.refreshMessage();
|
||||
});
|
||||
},
|
||||
restoreMes(v) {
|
||||
API_Index.reductionMessage(v.id).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) this.refreshMessage();
|
||||
});
|
||||
},
|
||||
deleteReal(v) {
|
||||
this.loading = true;
|
||||
API_Index.clearMessage(v.id).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) this.refreshMessage();
|
||||
});
|
||||
},
|
||||
getContent(v) {
|
||||
this.mes.content = v.content;
|
||||
API_Index.read(v.id).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) this.getAll();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getAll();
|
||||
},
|
||||
watch: {
|
||||
$route(to) {
|
||||
if (to.name == "message_index") this.getAll();
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "message_index" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(true);
|
||||
const mesTypeList = ref([
|
||||
{ type: "unread", title: "未读消息" },
|
||||
{ type: "read", title: "已读消息" },
|
||||
{ type: "recycleBin", title: "回收站" },
|
||||
]);
|
||||
const params = reactive({
|
||||
status: "UN_READY",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
});
|
||||
const total = ref(0);
|
||||
const currentMesList = ref([]);
|
||||
const currentMessageType = ref("unread");
|
||||
const showMesTitleList = ref(true);
|
||||
const unReadCount = ref(0);
|
||||
const noDataText = ref("暂无未读消息");
|
||||
const mes = reactive({
|
||||
title: "",
|
||||
time: "",
|
||||
content: "",
|
||||
});
|
||||
|
||||
// messageList:模板 ref 对应声明(原 Options API 中未在 script 内使用,仅保留绑定)
|
||||
const messageList = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
getAll();
|
||||
});
|
||||
|
||||
watch(() => route, (to) => {
|
||||
if (to.name == "message_index") getAll();
|
||||
});
|
||||
|
||||
function changePage() {
|
||||
refreshMessage();
|
||||
}
|
||||
function changePageSize() {
|
||||
refreshMessage();
|
||||
}
|
||||
function refreshMessage() {
|
||||
let status;
|
||||
const type = currentMessageType.value;
|
||||
if (type == "unread") status = "UN_READY";
|
||||
else if (type == "read") status = "ALREADY_READY";
|
||||
else status = "ALREADY_REMOVE";
|
||||
params.status = status;
|
||||
loading.value = true;
|
||||
API_Index.getMessageSendData(params).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
currentMesList.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
function getAll() {
|
||||
API_Index.getAllMessage(params).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
unReadCount.value = res.result.UN_READY.total;
|
||||
currentMesList.value = res.result.UN_READY.records;
|
||||
}
|
||||
});
|
||||
}
|
||||
function backMesTitleList() {
|
||||
showMesTitleList.value = true;
|
||||
}
|
||||
function setCurrentMesType(type) {
|
||||
if (currentMessageType.value !== type) {
|
||||
showMesTitleList.value = true;
|
||||
}
|
||||
currentMessageType.value = type;
|
||||
if (type == "unread") noDataText.value = "暂无未读消息";
|
||||
else if (type == "read") noDataText.value = "暂无已读消息";
|
||||
else noDataText.value = "回收站无消息";
|
||||
params.pageNumber = 1;
|
||||
refreshMessage();
|
||||
}
|
||||
function openMessage(row) {
|
||||
showMesTitleList.value = false;
|
||||
mes.title = row.title;
|
||||
mes.time = row.createTime;
|
||||
getContent(row);
|
||||
}
|
||||
function markAsRead(v) {
|
||||
loading.value = true;
|
||||
API_Index.read(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) getAll();
|
||||
});
|
||||
}
|
||||
function deleteMes(v) {
|
||||
loading.value = true;
|
||||
API_Index.deleteMessage(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) refreshMessage();
|
||||
});
|
||||
}
|
||||
function restoreMes(v) {
|
||||
API_Index.reductionMessage(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) refreshMessage();
|
||||
});
|
||||
}
|
||||
function deleteReal(v) {
|
||||
loading.value = true;
|
||||
API_Index.clearMessage(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) refreshMessage();
|
||||
});
|
||||
}
|
||||
function getContent(v) {
|
||||
mes.content = v.content;
|
||||
API_Index.read(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) getAll();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -6,13 +6,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
name: "circleLoading",
|
||||
components: { Loading },
|
||||
};
|
||||
defineOptions({ name: "circleLoading" });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -13,56 +13,55 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { initDepartment } from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "departmentChoose",
|
||||
data() {
|
||||
return {
|
||||
selectDep: [],
|
||||
department: [],
|
||||
cascaderProps: {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initDepartmentData() {
|
||||
initDepartment().then((res) => {
|
||||
if (res.success) {
|
||||
const arr = Array.isArray(res.result) ? res.result : [];
|
||||
this.filterData(arr);
|
||||
this.department = arr;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleChangeDep(value) {
|
||||
let departmentId = "";
|
||||
if (value && value.length > 0) {
|
||||
departmentId = value[value.length - 1];
|
||||
}
|
||||
this.$emit("on-change", departmentId);
|
||||
},
|
||||
clearSelect() {
|
||||
this.selectDep = [];
|
||||
},
|
||||
filterData(data) {
|
||||
data.forEach((e) => {
|
||||
e.value = e.id;
|
||||
e.label = e.title;
|
||||
if (e.children) {
|
||||
this.filterData(e.children);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDepartmentData();
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "departmentChoose" });
|
||||
|
||||
const emit = defineEmits(["on-change"]);
|
||||
|
||||
const selectDep = ref([]);
|
||||
const department = ref([]);
|
||||
const cascaderProps = ref({
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: true,
|
||||
});
|
||||
|
||||
function initDepartmentData() {
|
||||
initDepartment().then((res) => {
|
||||
if (res.success) {
|
||||
const arr = Array.isArray(res.result) ? res.result : [];
|
||||
filterData(arr);
|
||||
department.value = arr;
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleChangeDep(value) {
|
||||
let departmentId = "";
|
||||
if (value && value.length > 0) {
|
||||
departmentId = value[value.length - 1];
|
||||
}
|
||||
emit("on-change", departmentId);
|
||||
}
|
||||
function clearSelect() {
|
||||
selectDep.value = [];
|
||||
}
|
||||
function filterData(data) {
|
||||
data.forEach((e) => {
|
||||
e.value = e.id;
|
||||
e.label = e.title;
|
||||
if (e.children) {
|
||||
filterData(e.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initDepartmentData();
|
||||
|
||||
// 供父组件通过 ref 清空选择/读取选中项(当前引用方 clerkManage 未使用,作为公共组件 API 保留)
|
||||
defineExpose({ clearSelect, selectDep });
|
||||
</script>
|
||||
|
||||
@@ -35,114 +35,113 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { initDepartment, searchDepartment } from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "departmentTreeChoose",
|
||||
props: {
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "点击选择部门",
|
||||
},
|
||||
defineOptions({ name: "departmentTreeChoose" });
|
||||
|
||||
const props = defineProps({
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
depLoading: false,
|
||||
departmentTitle: "",
|
||||
searchKey: "",
|
||||
dataDep: [],
|
||||
cloneDep: [],
|
||||
departmentId: [],
|
||||
treeProps: {
|
||||
label: "title",
|
||||
children: "children",
|
||||
},
|
||||
};
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
methods: {
|
||||
initDepartmentData() {
|
||||
this.depLoading = true;
|
||||
initDepartment()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.dataDep = res.result;
|
||||
this.cloneDep = JSON.parse(JSON.stringify(this.dataDep));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.depLoading = false;
|
||||
});
|
||||
},
|
||||
searchDep() {
|
||||
if (this.searchKey) {
|
||||
this.depLoading = true;
|
||||
searchDepartment({ title: this.searchKey })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
res.result.forEach((e) => {
|
||||
if (e.status == -1) {
|
||||
e.title = "[已禁用] " + e.title;
|
||||
e.disabled = true;
|
||||
}
|
||||
});
|
||||
this.dataDep = res.result;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.depLoading = false;
|
||||
});
|
||||
} else {
|
||||
this.dataDep = JSON.parse(JSON.stringify(this.cloneDep));
|
||||
}
|
||||
},
|
||||
selectTree(node) {
|
||||
if (!node) {
|
||||
this.$emit("on-change", null);
|
||||
this.departmentId = "";
|
||||
this.departmentTitle = "";
|
||||
return;
|
||||
}
|
||||
this.departmentId = node.id;
|
||||
this.departmentTitle = node.title;
|
||||
this.$emit("on-change", {
|
||||
departmentId: this.departmentId,
|
||||
departmentTitle: this.departmentTitle,
|
||||
});
|
||||
},
|
||||
clearSelect() {
|
||||
this.departmentId = [];
|
||||
this.departmentTitle = "";
|
||||
this.initDepartmentData();
|
||||
if (this.multiple) {
|
||||
this.$emit("on-change", []);
|
||||
} else {
|
||||
this.$emit("on-change", "");
|
||||
}
|
||||
this.$emit("on-clear");
|
||||
},
|
||||
setData(ids, title) {
|
||||
this.departmentTitle = title;
|
||||
if (this.multiple) {
|
||||
this.departmentId = ids;
|
||||
} else {
|
||||
this.departmentId = [];
|
||||
this.departmentId.push(ids);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDepartmentData();
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "点击选择部门",
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["on-change", "on-clear"]);
|
||||
|
||||
const depLoading = ref(false);
|
||||
const departmentTitle = ref("");
|
||||
const searchKey = ref("");
|
||||
const dataDep = ref([]);
|
||||
const cloneDep = ref([]);
|
||||
const departmentId = ref([]);
|
||||
const treeProps = {
|
||||
label: "title",
|
||||
children: "children",
|
||||
};
|
||||
|
||||
function initDepartmentData() {
|
||||
depLoading.value = true;
|
||||
initDepartment()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
dataDep.value = res.result;
|
||||
cloneDep.value = JSON.parse(JSON.stringify(dataDep.value));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
depLoading.value = false;
|
||||
});
|
||||
}
|
||||
function searchDep() {
|
||||
if (searchKey.value) {
|
||||
depLoading.value = true;
|
||||
searchDepartment({ title: searchKey.value })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
res.result.forEach((e) => {
|
||||
if (e.status == -1) {
|
||||
e.title = "[已禁用] " + e.title;
|
||||
e.disabled = true;
|
||||
}
|
||||
});
|
||||
dataDep.value = res.result;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
depLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
dataDep.value = JSON.parse(JSON.stringify(cloneDep.value));
|
||||
}
|
||||
}
|
||||
function selectTree(node) {
|
||||
if (!node) {
|
||||
emit("on-change", null);
|
||||
departmentId.value = "";
|
||||
departmentTitle.value = "";
|
||||
return;
|
||||
}
|
||||
departmentId.value = node.id;
|
||||
departmentTitle.value = node.title;
|
||||
emit("on-change", {
|
||||
departmentId: departmentId.value,
|
||||
departmentTitle: departmentTitle.value,
|
||||
});
|
||||
}
|
||||
function clearSelect() {
|
||||
departmentId.value = [];
|
||||
departmentTitle.value = "";
|
||||
initDepartmentData();
|
||||
if (props.multiple) {
|
||||
emit("on-change", []);
|
||||
} else {
|
||||
emit("on-change", "");
|
||||
}
|
||||
emit("on-clear");
|
||||
}
|
||||
function setData(ids, title) {
|
||||
departmentTitle.value = title;
|
||||
if (props.multiple) {
|
||||
departmentId.value = ids;
|
||||
} else {
|
||||
departmentId.value = [];
|
||||
departmentId.value.push(ids);
|
||||
}
|
||||
}
|
||||
|
||||
initDepartmentData();
|
||||
|
||||
// 父组件(clerkManage)通过 ref 调用 setData / clearSelect
|
||||
defineExpose({ setData, clearSelect });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -29,99 +29,101 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "setPassword",
|
||||
props: {
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
defineOptions({ name: "setPassword" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符",
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change"],
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.modelValue ?? this.value ?? "",
|
||||
tipStyle: "password-tip-none",
|
||||
strengthValue: 0,
|
||||
progressStatus: "",
|
||||
strength: "无",
|
||||
grade: 0,
|
||||
};
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
methods: {
|
||||
checkStrengthValue(v) {
|
||||
let grade = 0;
|
||||
if (/\d/.test(v)) grade++;
|
||||
if (/[a-z]/.test(v)) grade++;
|
||||
if (/[A-Z]/.test(v)) grade++;
|
||||
if (/\W/.test(v)) grade++;
|
||||
if (v.length >= 10) grade++;
|
||||
this.grade = grade;
|
||||
return grade;
|
||||
},
|
||||
strengthChange() {
|
||||
if (!this.currentValue) {
|
||||
this.tipStyle = "password-tip-none";
|
||||
this.strength = "无";
|
||||
this.strengthValue = 0;
|
||||
return;
|
||||
}
|
||||
const grade = this.checkStrengthValue(this.currentValue);
|
||||
if (grade <= 1) {
|
||||
this.progressStatus = "exception";
|
||||
this.tipStyle = "password-tip-weak";
|
||||
this.strength = "弱";
|
||||
this.strengthValue = 33;
|
||||
} else if (grade >= 2 && grade <= 4) {
|
||||
this.progressStatus = "";
|
||||
this.tipStyle = "password-tip-middle";
|
||||
this.strength = "中";
|
||||
this.strengthValue = 66;
|
||||
} else {
|
||||
this.progressStatus = "success";
|
||||
this.tipStyle = "password-tip-strong";
|
||||
this.strength = "强";
|
||||
this.strengthValue = 100;
|
||||
}
|
||||
},
|
||||
handleChange() {
|
||||
this.strengthChange();
|
||||
this.$emit("update:modelValue", this.currentValue);
|
||||
this.$emit("input", this.currentValue);
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) return;
|
||||
this.currentValue = value ?? "";
|
||||
this.strengthChange();
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
|
||||
const currentValue = ref(props.modelValue ?? props.value ?? "");
|
||||
const tipStyle = ref("password-tip-none");
|
||||
const strengthValue = ref(0);
|
||||
const progressStatus = ref("");
|
||||
const strength = ref("无");
|
||||
const grade = ref(0);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
|
||||
function checkStrengthValue(v) {
|
||||
let g = 0;
|
||||
if (/\d/.test(v)) g++;
|
||||
if (/[a-z]/.test(v)) g++;
|
||||
if (/[A-Z]/.test(v)) g++;
|
||||
if (/\W/.test(v)) g++;
|
||||
if (v.length >= 10) g++;
|
||||
grade.value = g;
|
||||
return g;
|
||||
}
|
||||
function strengthChange() {
|
||||
if (!currentValue.value) {
|
||||
tipStyle.value = "password-tip-none";
|
||||
strength.value = "无";
|
||||
strengthValue.value = 0;
|
||||
return;
|
||||
}
|
||||
const g = checkStrengthValue(currentValue.value);
|
||||
if (g <= 1) {
|
||||
progressStatus.value = "exception";
|
||||
tipStyle.value = "password-tip-weak";
|
||||
strength.value = "弱";
|
||||
strengthValue.value = 33;
|
||||
} else if (g >= 2 && g <= 4) {
|
||||
progressStatus.value = "";
|
||||
tipStyle.value = "password-tip-middle";
|
||||
strength.value = "中";
|
||||
strengthValue.value = 66;
|
||||
} else {
|
||||
progressStatus.value = "success";
|
||||
tipStyle.value = "password-tip-strong";
|
||||
strength.value = "强";
|
||||
strengthValue.value = 100;
|
||||
}
|
||||
}
|
||||
function handleChange() {
|
||||
strengthChange();
|
||||
emit("update:modelValue", currentValue.value);
|
||||
emit("input", currentValue.value);
|
||||
emit("on-change", currentValue.value, grade.value, strength.value);
|
||||
}
|
||||
function setCurrentValue(value) {
|
||||
if (value === currentValue.value) return;
|
||||
currentValue.value = value ?? "";
|
||||
strengthChange();
|
||||
emit("on-change", currentValue.value, grade.value, strength.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<el-dialog v-model="picModalFlag" width="1200px" append-to-body :z-index="3500" destroy-on-close>
|
||||
<ossManage
|
||||
ref="ossManage"
|
||||
ref="ossManageRef"
|
||||
:is-component="true"
|
||||
:initialize="picModalFlag"
|
||||
:max-select="1"
|
||||
@@ -69,119 +69,122 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { nextTick, ref, useAttrs, watch } from "vue";
|
||||
import { View } from "@element-plus/icons-vue";
|
||||
import { uploadFile } from "@/libs/axios";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { Message } from "@/utils/message";
|
||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
||||
|
||||
export default {
|
||||
name: "uploadPicInput",
|
||||
components: {
|
||||
ossManage,
|
||||
View,
|
||||
defineOptions({ name: "uploadPicInput" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: {
|
||||
default: "default",
|
||||
type: String,
|
||||
},
|
||||
props: {
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: {
|
||||
default: "default",
|
||||
type: String,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "图片链接",
|
||||
},
|
||||
showInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxlength: Number,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "图片链接",
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change"],
|
||||
data() {
|
||||
return {
|
||||
accessToken: {},
|
||||
currentValue: this.modelValue ?? this.value ?? "",
|
||||
viewImage: false,
|
||||
uploadFileUrl: uploadFile,
|
||||
picModalFlag: false,
|
||||
selectedFormBtnName: "",
|
||||
picIndex: "",
|
||||
selectedImage: [],
|
||||
};
|
||||
showInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
methods: {
|
||||
handleCLickImg(val, index) {
|
||||
this.picModalFlag = true;
|
||||
this.selectedFormBtnName = val;
|
||||
this.picIndex = index;
|
||||
this.selectedImage = [];
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ossManage) {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
callbackSelected(val) {
|
||||
if (val?.url) {
|
||||
this.selectedImage = [val.url];
|
||||
}
|
||||
},
|
||||
handleOssSelected(list) {
|
||||
this.selectedImage = (list || []).map((item) => item.split(",")[1]).filter(Boolean);
|
||||
},
|
||||
confirmSelectedImage() {
|
||||
if (!this.selectedImage.length) {
|
||||
this.$Message.warning("请选择图片");
|
||||
return;
|
||||
}
|
||||
this.currentValue = this.selectedImage[this.selectedImage.length - 1];
|
||||
this.picModalFlag = false;
|
||||
this.picIndex = "";
|
||||
this.emitValue(this.currentValue);
|
||||
},
|
||||
init() {
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
emitValue(val) {
|
||||
this.$emit("update:modelValue", val);
|
||||
this.$emit("input", val);
|
||||
this.$emit("on-change", val);
|
||||
},
|
||||
handleChange() {
|
||||
this.emitValue(this.currentValue);
|
||||
this.$attrs.rollback && this.$attrs.rollback();
|
||||
},
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) {
|
||||
return;
|
||||
}
|
||||
this.currentValue = value ?? "";
|
||||
this.emitValue(this.currentValue);
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
maxlength: Number,
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
const attrs = useAttrs();
|
||||
|
||||
const accessToken = ref({});
|
||||
const currentValue = ref(props.modelValue ?? props.value ?? "");
|
||||
const viewImage = ref(false);
|
||||
const uploadFileUrl = ref(uploadFile);
|
||||
const picModalFlag = ref(false);
|
||||
const selectedFormBtnName = ref("");
|
||||
const picIndex = ref("");
|
||||
const selectedImage = ref([]);
|
||||
|
||||
// 模板 ref:ossManage 与 import 组件同名,改名避免互相覆盖
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
function handleCLickImg(val, index) {
|
||||
picModalFlag.value = true;
|
||||
selectedFormBtnName.value = val;
|
||||
picIndex.value = index;
|
||||
selectedImage.value = [];
|
||||
nextTick(() => {
|
||||
if (ossManageRef.value) {
|
||||
ossManageRef.value.selectImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function callbackSelected(val) {
|
||||
if (val?.url) {
|
||||
selectedImage.value = [val.url];
|
||||
}
|
||||
}
|
||||
function handleOssSelected(list) {
|
||||
selectedImage.value = (list || []).map((item) => item.split(",")[1]).filter(Boolean);
|
||||
}
|
||||
function confirmSelectedImage() {
|
||||
if (!selectedImage.value.length) {
|
||||
Message.warning("请选择图片");
|
||||
return;
|
||||
}
|
||||
currentValue.value = selectedImage.value[selectedImage.value.length - 1];
|
||||
picModalFlag.value = false;
|
||||
picIndex.value = "";
|
||||
emitValue(currentValue.value);
|
||||
}
|
||||
function init() {
|
||||
accessToken.value = {
|
||||
accessToken: getStore("accessToken"),
|
||||
};
|
||||
}
|
||||
function emitValue(val) {
|
||||
emit("update:modelValue", val);
|
||||
emit("input", val);
|
||||
emit("on-change", val);
|
||||
}
|
||||
function handleChange() {
|
||||
emitValue(currentValue.value);
|
||||
attrs.rollback && attrs.rollback();
|
||||
}
|
||||
function setCurrentValue(value) {
|
||||
if (value === currentValue.value) {
|
||||
return;
|
||||
}
|
||||
currentValue.value = value ?? "";
|
||||
emitValue(currentValue.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
|
||||
// 原 created 钩子
|
||||
init();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
|
||||
<ossManage
|
||||
ref="ossManage"
|
||||
ref="ossManageRef"
|
||||
:is-component="true"
|
||||
:initialize="picModelFlag"
|
||||
:hide-select-footer="true"
|
||||
@@ -81,204 +81,203 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import { Camera, Delete, View } from "@element-plus/icons-vue";
|
||||
import { uploadFile } from "@/libs/axios";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { Message } from "@/utils/message";
|
||||
import vuedraggable from "vuedraggable";
|
||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
||||
|
||||
export default {
|
||||
name: "uploadPicThumb",
|
||||
components: {
|
||||
vuedraggable,
|
||||
ossManage,
|
||||
Camera,
|
||||
Delete,
|
||||
View,
|
||||
defineOptions({ name: "uploadPicThumb" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: null },
|
||||
value: { type: null },
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
props: {
|
||||
modelValue: { type: null },
|
||||
value: { type: null },
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
remove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
isView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change", "uploadchange"],
|
||||
data() {
|
||||
return {
|
||||
accessToken: {},
|
||||
uploadFileUrl: uploadFile,
|
||||
uploadList: [],
|
||||
viewImage: false,
|
||||
imgUrl: "",
|
||||
picModelFlag: false,
|
||||
selectedFormBtnName: "",
|
||||
selectedImage: [],
|
||||
};
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
computed: {
|
||||
bindValue() {
|
||||
return this.modelValue !== undefined ? this.modelValue : this.value;
|
||||
},
|
||||
remove: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
methods: {
|
||||
handleCLickImg(val) {
|
||||
this.picModelFlag = true;
|
||||
this.selectedFormBtnName = val;
|
||||
this.selectedImage = [];
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ossManage) {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
}
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
isView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change", "uploadchange"]);
|
||||
|
||||
const accessToken = ref({});
|
||||
const uploadFileUrl = uploadFile;
|
||||
const uploadList = ref([]);
|
||||
const viewImage = ref(false);
|
||||
const imgUrl = ref("");
|
||||
const picModelFlag = ref(false);
|
||||
const selectedFormBtnName = ref("");
|
||||
const selectedImage = ref([]);
|
||||
|
||||
// 模板 ref:与 import 名 ossManage 重名,改名避免互相覆盖
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
const bindValue = computed(() => {
|
||||
return props.modelValue !== undefined ? props.modelValue : props.value;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setData(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setData(val);
|
||||
}
|
||||
);
|
||||
|
||||
function handleCLickImg(val) {
|
||||
picModelFlag.value = true;
|
||||
selectedFormBtnName.value = val;
|
||||
selectedImage.value = [];
|
||||
nextTick(() => {
|
||||
if (ossManageRef.value) {
|
||||
ossManageRef.value.selectImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function parseOssSelectionUrl(item) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
if (typeof item === "string") {
|
||||
const index = item.indexOf(",");
|
||||
return index >= 0 ? item.slice(index + 1) : item;
|
||||
}
|
||||
return item.url || "";
|
||||
}
|
||||
function callbackSelected(val) {
|
||||
if (props.multiple) {
|
||||
return;
|
||||
}
|
||||
if (!val?.url) {
|
||||
return;
|
||||
}
|
||||
picModelFlag.value = false;
|
||||
if (uploadList.value.length > 0) {
|
||||
uploadList.value.splice(0, 1);
|
||||
}
|
||||
uploadList.value.push({ ...val, status: "finished" });
|
||||
returnValue();
|
||||
}
|
||||
function confirmUrls() {
|
||||
if (selectedImage.value.length) {
|
||||
selectedImage.value.forEach((element) => {
|
||||
const url = parseOssSelectionUrl(element);
|
||||
if (url) {
|
||||
uploadList.value.push({ url, status: "finished" });
|
||||
}
|
||||
});
|
||||
}
|
||||
selectedImage.value = [];
|
||||
picModelFlag.value = false;
|
||||
returnValue();
|
||||
}
|
||||
function onEnd() {
|
||||
returnValue();
|
||||
}
|
||||
function init() {
|
||||
setData(bindValue.value, true);
|
||||
accessToken.value = {
|
||||
accessToken: getStore("accessToken"),
|
||||
};
|
||||
}
|
||||
function handleView(url) {
|
||||
imgUrl.value = url;
|
||||
viewImage.value = true;
|
||||
}
|
||||
function handleRemove(file) {
|
||||
uploadList.value = uploadList.value.filter((i) => i.url !== file.url);
|
||||
returnValue();
|
||||
}
|
||||
function emitValue(val) {
|
||||
emit("update:modelValue", val);
|
||||
emit("input", val);
|
||||
emit("on-change", val);
|
||||
}
|
||||
function returnValue() {
|
||||
if (!uploadList.value || uploadList.value.length < 1) {
|
||||
const empty = props.multiple ? [] : "";
|
||||
emitValue(empty);
|
||||
return;
|
||||
}
|
||||
if (!props.multiple) {
|
||||
emitValue(uploadList.value[0].url);
|
||||
} else {
|
||||
emitValue(uploadList.value.map((e) => e.url));
|
||||
}
|
||||
}
|
||||
function setData(v, init) {
|
||||
if (typeof v == "string") {
|
||||
if (props.multiple) {
|
||||
if (!v) {
|
||||
uploadList.value = [];
|
||||
return;
|
||||
}
|
||||
setData(
|
||||
v.split(",").map((s) => s.trim()).filter(Boolean),
|
||||
init
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
uploadList.value = [{ url: v, status: "finished" }];
|
||||
emit("uploadchange", v);
|
||||
emitValue(v);
|
||||
} else if (typeof v == "object" && v) {
|
||||
if (!props.multiple) {
|
||||
Message.warning("单张上传仅支持字符串数据类型");
|
||||
return;
|
||||
}
|
||||
uploadList.value = [];
|
||||
const list = v.length > props.limit ? v.slice(0, props.limit) : v;
|
||||
if (v.length > props.limit) {
|
||||
Message.warning("最多只能上传" + props.limit + "张图片");
|
||||
}
|
||||
list.forEach((e) => {
|
||||
uploadList.value.push({
|
||||
status: "finished",
|
||||
...(typeof e === "string" ? { url: e } : e),
|
||||
});
|
||||
},
|
||||
parseOssSelectionUrl(item) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
if (typeof item === "string") {
|
||||
const index = item.indexOf(",");
|
||||
return index >= 0 ? item.slice(index + 1) : item;
|
||||
}
|
||||
return item.url || "";
|
||||
},
|
||||
callbackSelected(val) {
|
||||
if (this.multiple) {
|
||||
return;
|
||||
}
|
||||
if (!val?.url) {
|
||||
return;
|
||||
}
|
||||
this.picModelFlag = false;
|
||||
if (this.uploadList.length > 0) {
|
||||
this.uploadList.splice(0, 1);
|
||||
}
|
||||
this.uploadList.push({ ...val, status: "finished" });
|
||||
this.returnValue();
|
||||
},
|
||||
confirmUrls() {
|
||||
if (this.selectedImage.length) {
|
||||
this.selectedImage.forEach((element) => {
|
||||
const url = this.parseOssSelectionUrl(element);
|
||||
if (url) {
|
||||
this.uploadList.push({ url, status: "finished" });
|
||||
}
|
||||
});
|
||||
}
|
||||
this.selectedImage = [];
|
||||
this.picModelFlag = false;
|
||||
this.returnValue();
|
||||
},
|
||||
onEnd() {
|
||||
this.returnValue();
|
||||
},
|
||||
init() {
|
||||
this.setData(this.bindValue, true);
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
handleView(imgUrl) {
|
||||
this.imgUrl = imgUrl;
|
||||
this.viewImage = true;
|
||||
},
|
||||
handleRemove(file) {
|
||||
this.uploadList = this.uploadList.filter((i) => i.url !== file.url);
|
||||
this.returnValue();
|
||||
},
|
||||
emitValue(val) {
|
||||
this.$emit("update:modelValue", val);
|
||||
this.$emit("input", val);
|
||||
this.$emit("on-change", val);
|
||||
},
|
||||
returnValue() {
|
||||
if (!this.uploadList || this.uploadList.length < 1) {
|
||||
const empty = this.multiple ? [] : "";
|
||||
this.emitValue(empty);
|
||||
return;
|
||||
}
|
||||
if (!this.multiple) {
|
||||
this.emitValue(this.uploadList[0].url);
|
||||
} else {
|
||||
this.emitValue(this.uploadList.map((e) => e.url));
|
||||
}
|
||||
},
|
||||
setData(v, init) {
|
||||
if (typeof v == "string") {
|
||||
if (this.multiple) {
|
||||
if (!v) {
|
||||
this.uploadList = [];
|
||||
return;
|
||||
}
|
||||
this.setData(
|
||||
v.split(",").map((s) => s.trim()).filter(Boolean),
|
||||
init
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.uploadList = [{ url: v, status: "finished" }];
|
||||
this.$emit("uploadchange", v);
|
||||
this.emitValue(v);
|
||||
} else if (typeof v == "object" && v) {
|
||||
if (!this.multiple) {
|
||||
this.$Message.warning("单张上传仅支持字符串数据类型");
|
||||
return;
|
||||
}
|
||||
this.uploadList = [];
|
||||
const list = v.length > this.limit ? v.slice(0, this.limit) : v;
|
||||
if (v.length > this.limit) {
|
||||
this.$Message.warning("最多只能上传" + this.limit + "张图片");
|
||||
}
|
||||
list.forEach((e) => {
|
||||
this.uploadList.push({
|
||||
status: "finished",
|
||||
...(typeof e === "string" ? { url: e } : e),
|
||||
});
|
||||
});
|
||||
if (init) {
|
||||
this.emitValue(list);
|
||||
} else {
|
||||
this.$emit("on-change", list);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setData(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setData(val);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
if (init) {
|
||||
emitValue(list);
|
||||
} else {
|
||||
emit("on-change", list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -17,116 +17,114 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import AMapLoader from "@amap/amap-jsapi-loader";
|
||||
import { getRegion } from "@/api/common.js";
|
||||
import config from "@/config/index";
|
||||
export default {
|
||||
name: "map",
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
showMap: false,
|
||||
mapSearch: "",
|
||||
map: null,
|
||||
autoComplete: null,
|
||||
geocoder: null,
|
||||
positionPicker: null,
|
||||
tips: [],
|
||||
addrContent: {},
|
||||
loading: false,
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
defineOptions({ name: "map" });
|
||||
|
||||
const emit = defineEmits(["getAddress"]);
|
||||
|
||||
const showMap = ref(false);
|
||||
const mapSearch = ref("");
|
||||
const map = ref(null);
|
||||
const autoComplete = ref(null);
|
||||
const geocoder = ref(null);
|
||||
const positionPicker = ref(null);
|
||||
const tips = ref([]);
|
||||
const addrContent = ref({});
|
||||
const loading = ref(false);
|
||||
|
||||
watch(mapSearch, (val) => {
|
||||
searchOfMap(val);
|
||||
});
|
||||
|
||||
function ok() {
|
||||
if (addrContent.value && addrContent.value.regeocode) {
|
||||
const params = {
|
||||
cityCode: addrContent.value.regeocode.addressComponent.citycode,
|
||||
townName: addrContent.value.regeocode.addressComponent.township,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mapSearch(val) {
|
||||
this.searchOfMap(val);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
ok() {
|
||||
if (this.addrContent && this.addrContent.regeocode) {
|
||||
const params = {
|
||||
cityCode: this.addrContent.regeocode.addressComponent.citycode,
|
||||
townName: this.addrContent.regeocode.addressComponent.township,
|
||||
};
|
||||
getRegion(params).then((res) => {
|
||||
if (res.success) {
|
||||
this.addrContent.addr = res.result.name.replace(/,/g, " ");
|
||||
this.addrContent.addrId = res.result.id;
|
||||
this.loading = false;
|
||||
this.$emit("getAddress", this.addrContent);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$Message.error("未获取到坐标信息!请查看高德API配置是否正确");
|
||||
getRegion(params).then((res) => {
|
||||
if (res.success) {
|
||||
addrContent.value.addr = res.result.name.replace(/,/g, " ");
|
||||
addrContent.value.addrId = res.result.id;
|
||||
loading.value = false;
|
||||
emit("getAddress", addrContent.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Message.error("未获取到坐标信息!请查看高德API配置是否正确");
|
||||
}
|
||||
}
|
||||
function init() {
|
||||
AMapLoader.load({
|
||||
key: config.aMapKey,
|
||||
version: "",
|
||||
plugins: [
|
||||
"AMap.ToolBar",
|
||||
"AMap.Autocomplete",
|
||||
"AMap.PlaceSearch",
|
||||
"AMap.Geolocation",
|
||||
"AMap.Geocoder",
|
||||
],
|
||||
AMapUI: {
|
||||
version: "1.1",
|
||||
plugins: ["misc/PositionPicker"],
|
||||
},
|
||||
init() {
|
||||
AMapLoader.load({
|
||||
key: this.config.aMapKey,
|
||||
version: "",
|
||||
plugins: [
|
||||
"AMap.ToolBar",
|
||||
"AMap.Autocomplete",
|
||||
"AMap.PlaceSearch",
|
||||
"AMap.Geolocation",
|
||||
"AMap.Geocoder",
|
||||
],
|
||||
AMapUI: {
|
||||
version: "1.1",
|
||||
plugins: ["misc/PositionPicker"],
|
||||
},
|
||||
})
|
||||
.then((AMap) => {
|
||||
let that = this;
|
||||
this.map = new AMap.Map("map-container", {
|
||||
zoom: 12,
|
||||
});
|
||||
that.map.addControl(new AMap.ToolBar());
|
||||
that.map.addControl(new AMap.Autocomplete());
|
||||
that.map.addControl(new AMap.PlaceSearch());
|
||||
that.map.addControl(new AMap.Geocoder());
|
||||
|
||||
let autoOptions = {
|
||||
city: "全国",
|
||||
};
|
||||
that.autoComplete = new AMap.Autocomplete(autoOptions);
|
||||
that.geocoder = new AMap.Geocoder(autoOptions);
|
||||
|
||||
that.positionPicker = new AMapUI.PositionPicker({
|
||||
mode: "dragMap",
|
||||
map: that.map,
|
||||
});
|
||||
that.positionPicker.start();
|
||||
that.positionPicker.on("success", function (positionResult) {
|
||||
that.addrContent = positionResult;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
searchOfMap(val) {
|
||||
let that = this;
|
||||
this.autoComplete.search(val, function (status, result) {
|
||||
if (status == "complete" && result.info == "OK") {
|
||||
that.tips = result.tips;
|
||||
} else {
|
||||
that.tips = [];
|
||||
}
|
||||
})
|
||||
.then((AMap) => {
|
||||
map.value = new AMap.Map("map-container", {
|
||||
zoom: 12,
|
||||
});
|
||||
},
|
||||
selectAddr(location) {
|
||||
if (!location) {
|
||||
this.$Message.warning("请选择正确点位");
|
||||
return false;
|
||||
}
|
||||
const lnglat = [location.lng, location.lat];
|
||||
this.positionPicker.start(lnglat);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
map.value.addControl(new AMap.ToolBar());
|
||||
map.value.addControl(new AMap.Autocomplete());
|
||||
map.value.addControl(new AMap.PlaceSearch());
|
||||
map.value.addControl(new AMap.Geocoder());
|
||||
|
||||
let autoOptions = {
|
||||
city: "全国",
|
||||
};
|
||||
autoComplete.value = new AMap.Autocomplete(autoOptions);
|
||||
geocoder.value = new AMap.Geocoder(autoOptions);
|
||||
|
||||
positionPicker.value = new AMapUI.PositionPicker({
|
||||
mode: "dragMap",
|
||||
map: map.value,
|
||||
});
|
||||
positionPicker.value.start();
|
||||
positionPicker.value.on("success", function (positionResult) {
|
||||
addrContent.value = positionResult;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
function searchOfMap(val) {
|
||||
autoComplete.value.search(val, function (status, result) {
|
||||
if (status == "complete" && result.info == "OK") {
|
||||
tips.value = result.tips;
|
||||
} else {
|
||||
tips.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
function selectAddr(location) {
|
||||
if (!location) {
|
||||
Message.warning("请选择正确点位");
|
||||
return false;
|
||||
}
|
||||
const lnglat = [location.lng, location.lat];
|
||||
positionPicker.value.start(lnglat);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
// 唯一引用方 multiple-map.vue 通过 ref 引用本组件但从不访问任何成员(仅监听 getAddress 事件),无需暴露成员
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
#map-container {
|
||||
|
||||
@@ -40,85 +40,87 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { aMapSwitch } from "@/config/index";
|
||||
import mapping from "@/views/my-components/map/index.vue";
|
||||
import * as API_Setup from "@/api/common.js";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
components: { mapping },
|
||||
data() {
|
||||
return {
|
||||
aMapSwitch,
|
||||
enableMap: false,
|
||||
mapDefault: "select",
|
||||
data: {
|
||||
province: [],
|
||||
city: [],
|
||||
area: [],
|
||||
street: [],
|
||||
},
|
||||
chiosend: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chiosend = new Array(4).fill("");
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.enableMap = true;
|
||||
this.init({ id: 0 }, "province");
|
||||
},
|
||||
changeMap(val) {
|
||||
this.mapDefault = val;
|
||||
},
|
||||
init(val, level = "province", index) {
|
||||
if (level == 0) {
|
||||
this.chiosend.splice(3, 1, val);
|
||||
} else {
|
||||
API_Setup.getChildRegion(val.id).then((res) => {
|
||||
if (res.result.length && val.id !== 0) {
|
||||
this.chiosend[index] = val;
|
||||
} else if (!res.result.length) {
|
||||
this.chiosend[index] = val;
|
||||
}
|
||||
this.data[level] = res.result;
|
||||
if (level == "city") {
|
||||
this.data.area = [];
|
||||
this.data.street = [];
|
||||
this.chiosend.splice(1, 3, "", "", "");
|
||||
}
|
||||
if (level == "area") {
|
||||
this.data.street = [];
|
||||
this.chiosend.splice(2, 2, "", "");
|
||||
}
|
||||
if (level == "street") {
|
||||
this.chiosend.splice(3, 1, "");
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["callback"]);
|
||||
|
||||
const enableMap = ref(false);
|
||||
const mapDefault = ref("select");
|
||||
const data = reactive({
|
||||
province: [],
|
||||
city: [],
|
||||
area: [],
|
||||
street: [],
|
||||
});
|
||||
const chiosend = ref([]);
|
||||
|
||||
// 模板 ref,对应高德地图子组件
|
||||
const map = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
chiosend.value = new Array(4).fill("");
|
||||
});
|
||||
|
||||
function open() {
|
||||
enableMap.value = true;
|
||||
init({ id: 0 }, "province");
|
||||
}
|
||||
function changeMap(val) {
|
||||
mapDefault.value = val;
|
||||
}
|
||||
function init(val, level = "province", index) {
|
||||
if (level == 0) {
|
||||
chiosend.value.splice(3, 1, val);
|
||||
} else {
|
||||
API_Setup.getChildRegion(val.id).then((res) => {
|
||||
if (res.result.length && val.id !== 0) {
|
||||
chiosend.value[index] = val;
|
||||
} else if (!res.result.length) {
|
||||
chiosend.value[index] = val;
|
||||
}
|
||||
},
|
||||
getAddress(center) {
|
||||
this.$emit("callback", {
|
||||
type: this.mapDefault,
|
||||
data: center,
|
||||
});
|
||||
this.enableMap = false;
|
||||
},
|
||||
finished() {
|
||||
if (!this.chiosend[0]) {
|
||||
this.$Message.error("请选择地址");
|
||||
return;
|
||||
data[level] = res.result;
|
||||
if (level == "city") {
|
||||
data.area = [];
|
||||
data.street = [];
|
||||
chiosend.value.splice(1, 3, "", "", "");
|
||||
}
|
||||
const params = this.chiosend.filter((item) => item !== "" && item.value !== "");
|
||||
this.enableMap = false;
|
||||
this.$emit("callback", {
|
||||
type: this.mapDefault,
|
||||
data: params,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
if (level == "area") {
|
||||
data.street = [];
|
||||
chiosend.value.splice(2, 2, "", "");
|
||||
}
|
||||
if (level == "street") {
|
||||
chiosend.value.splice(3, 1, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function getAddress(center) {
|
||||
emit("callback", {
|
||||
type: mapDefault.value,
|
||||
data: center,
|
||||
});
|
||||
enableMap.value = false;
|
||||
}
|
||||
function finished() {
|
||||
if (!chiosend.value[0]) {
|
||||
Message.error("请选择地址");
|
||||
return;
|
||||
}
|
||||
const params = chiosend.value.filter((item) => item !== "" && item.value !== "");
|
||||
enableMap.value = false;
|
||||
emit("callback", {
|
||||
type: mapDefault.value,
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
// 父组件(shopAddress/shopSetting/orderDetail)通过 ref 调用 open() 打开弹窗
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -37,111 +37,110 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { Refresh, DArrowRight } from "@element-plus/icons-vue";
|
||||
import { getVerifyImg, postVerifyImg } from "./verify.js";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
components: { Refresh, DArrowRight },
|
||||
props: {
|
||||
verifyType: {
|
||||
default: "LOGIN",
|
||||
type: String,
|
||||
},
|
||||
const props = defineProps({
|
||||
verifyType: {
|
||||
default: "LOGIN",
|
||||
type: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
type: "LOGIN",
|
||||
data: {
|
||||
backImage: "",
|
||||
slidingImage: "",
|
||||
originalHeight: 150,
|
||||
originalWidth: 300,
|
||||
sliderWidth: 60,
|
||||
sliderHeight: 60,
|
||||
},
|
||||
distance: 0,
|
||||
flag: false,
|
||||
downX: 0,
|
||||
bgColor: "#04ad11",
|
||||
verifyText: "拖动滑块解锁",
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["change"]);
|
||||
|
||||
const show = ref(false);
|
||||
const type = ref("LOGIN");
|
||||
const data = ref({
|
||||
backImage: "",
|
||||
slidingImage: "",
|
||||
originalHeight: 150,
|
||||
originalWidth: 300,
|
||||
sliderWidth: 60,
|
||||
sliderHeight: 60,
|
||||
});
|
||||
const distance = ref(0);
|
||||
const flag = ref(false);
|
||||
const downX = ref(0);
|
||||
const bgColor = ref("#04ad11");
|
||||
const verifyText = ref("拖动滑块解锁");
|
||||
|
||||
watch(
|
||||
() => props.verifyType,
|
||||
(v) => {
|
||||
type.value = v;
|
||||
},
|
||||
methods: {
|
||||
mouseDown(e) {
|
||||
this.downX = e.clientX;
|
||||
this.flag = true;
|
||||
},
|
||||
mouseMove(e) {
|
||||
if (this.flag) {
|
||||
const offset = e.clientX - this.downX;
|
||||
if (offset > this.data.originalWidth - 43) {
|
||||
this.distance = this.data.originalWidth - 43;
|
||||
} else if (offset < 0) {
|
||||
this.distance = 0;
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function mouseDown(e) {
|
||||
downX.value = e.clientX;
|
||||
flag.value = true;
|
||||
}
|
||||
function mouseMove(e) {
|
||||
if (flag.value) {
|
||||
const offset = e.clientX - downX.value;
|
||||
if (offset > data.value.originalWidth - 43) {
|
||||
distance.value = data.value.originalWidth - 43;
|
||||
} else if (offset < 0) {
|
||||
distance.value = 0;
|
||||
} else {
|
||||
distance.value = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
function mouseUp() {
|
||||
if (!flag.value) return false;
|
||||
flag.value = false;
|
||||
const params = {
|
||||
verificationEnums: type.value,
|
||||
xPos: distance.value,
|
||||
};
|
||||
postVerifyImg(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
if (res.result) {
|
||||
bgColor.value = "green";
|
||||
verifyText.value = "解锁成功";
|
||||
emit("change", { status: true, distance: distance.value });
|
||||
} else {
|
||||
this.distance = offset;
|
||||
bgColor.value = "red";
|
||||
verifyText.value = "解锁失败";
|
||||
setTimeout(() => init(), 1000);
|
||||
emit("change", { status: false, distance: distance.value });
|
||||
}
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
},
|
||||
mouseUp() {
|
||||
if (!this.flag) return false;
|
||||
this.flag = false;
|
||||
const params = {
|
||||
verificationEnums: this.type,
|
||||
xPos: this.distance,
|
||||
};
|
||||
postVerifyImg(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
if (res.result) {
|
||||
this.bgColor = "green";
|
||||
this.verifyText = "解锁成功";
|
||||
this.$emit("change", { status: true, distance: this.distance });
|
||||
} else {
|
||||
this.bgColor = "red";
|
||||
this.verifyText = "解锁失败";
|
||||
setTimeout(() => this.init(), 1000);
|
||||
this.$emit("change", { status: false, distance: this.distance });
|
||||
}
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.init();
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.flag = false;
|
||||
this.downX = 0;
|
||||
this.distance = 0;
|
||||
this.bgColor = "#04ad11";
|
||||
this.verifyText = "拖动滑块解锁";
|
||||
getVerifyImg(this.type)
|
||||
.then((res) => {
|
||||
if (res.result) {
|
||||
this.data = res.result;
|
||||
this.show = true;
|
||||
} else {
|
||||
this.$Message.warning("请求失败请重试!");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$Message.warning("验证码加载失败,请点击重试");
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
verifyType: {
|
||||
immediate: true,
|
||||
handler(v) {
|
||||
this.type = v;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
init();
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
flag.value = false;
|
||||
downX.value = 0;
|
||||
distance.value = 0;
|
||||
bgColor.value = "#04ad11";
|
||||
verifyText.value = "拖动滑块解锁";
|
||||
getVerifyImg(type.value)
|
||||
.then((res) => {
|
||||
if (res.result) {
|
||||
data.value = res.result;
|
||||
show.value = true;
|
||||
} else {
|
||||
Message.warning("请求失败请重试!");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
Message.warning("验证码加载失败,请点击重试");
|
||||
});
|
||||
}
|
||||
|
||||
// login.vue / ForgetPassword.vue 通过 verifyRef.value.show = false 关闭、verifyRef.value.init() 打开
|
||||
defineExpose({ show, init });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -104,96 +104,100 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import * as API_Order from "@/api/order";
|
||||
import vueQr from "vue-qr";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "orderComplaint",
|
||||
components: { vueQr },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
},
|
||||
data: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getDataList();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
complainStatusText(v) {
|
||||
const map = {
|
||||
NEW: "新投诉",
|
||||
CANCEL: "已撤销",
|
||||
WAIT_APPEAL: "待申诉",
|
||||
COMMUNICATION: "对话中",
|
||||
WAIT_ARBITRATION: "等待仲裁",
|
||||
COMPLETE: "已完成",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
},
|
||||
complainStatusTagType(v) {
|
||||
const map = {
|
||||
NEW: "primary",
|
||||
CANCEL: "info",
|
||||
WAIT_APPEAL: "warning",
|
||||
COMMUNICATION: "warning",
|
||||
WAIT_ARBITRATION: "",
|
||||
COMPLETE: "success",
|
||||
};
|
||||
return map[v] || "info";
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
API_Order.getOrderComplain(this.searchForm)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
detail(v) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "order-complaint-detail",
|
||||
query: { id: v.id },
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "orderComplaint" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
});
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与 searchForm 数据互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
() => {
|
||||
getDataList();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function complainStatusText(v) {
|
||||
const map = {
|
||||
NEW: "新投诉",
|
||||
CANCEL: "已撤销",
|
||||
WAIT_APPEAL: "待申诉",
|
||||
COMMUNICATION: "对话中",
|
||||
WAIT_ARBITRATION: "等待仲裁",
|
||||
COMPLETE: "已完成",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
}
|
||||
function complainStatusTagType(v) {
|
||||
const map = {
|
||||
NEW: "primary",
|
||||
CANCEL: "info",
|
||||
WAIT_APPEAL: "warning",
|
||||
COMMUNICATION: "warning",
|
||||
WAIT_ARBITRATION: "",
|
||||
COMPLETE: "success",
|
||||
};
|
||||
return map[v] || "info";
|
||||
}
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
function changePage(v) {
|
||||
searchForm.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = v;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Order.getOrderComplain(searchForm)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function detail(v) {
|
||||
customRouterPush({
|
||||
name: "order-complaint-detail",
|
||||
query: { id: v.id },
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -188,134 +188,135 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import * as API_Order from "@/api/order";
|
||||
import { Message } from "@/utils/message";
|
||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||
|
||||
defineOptions({ name: "orderComplaintDetail" });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const emptyComplaint = () => ({
|
||||
id: "",
|
||||
orderComplaintCommunications: [],
|
||||
});
|
||||
|
||||
export default {
|
||||
name: "orderComplaintDetail",
|
||||
components: {
|
||||
uploadPicThumb,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
id: "",
|
||||
complaintInfo: emptyComplaint(),
|
||||
images: [],
|
||||
appealImages: [],
|
||||
submitLoading: false,
|
||||
params: {
|
||||
content: "",
|
||||
complainId: "",
|
||||
},
|
||||
appeal: {
|
||||
orderComplaintId: "",
|
||||
appealContent: "",
|
||||
appealImages: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
complainStatusText(v) {
|
||||
const map = {
|
||||
NEW: "新投诉",
|
||||
CANCEL: "已撤销",
|
||||
WAIT_APPEAL: "待申诉",
|
||||
COMMUNICATION: "对话中",
|
||||
WAIT_ARBITRATION: "等待仲裁",
|
||||
COMPLETE: "已完成",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
},
|
||||
getDetail() {
|
||||
if (!this.id) return;
|
||||
this.loading = true;
|
||||
API_Order.getOrderComplainDetail(this.id)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.complaintInfo = res.result || emptyComplaint();
|
||||
this.images = (res.result.images || "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
this.appealImages = (res.result.appealImages || "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
returnDataList() {
|
||||
this.$router.push({ name: "orderComplaint" });
|
||||
},
|
||||
appealSubmit() {
|
||||
if (!this.appeal.appealContent) {
|
||||
this.$Message.error("请填写申诉内容");
|
||||
return;
|
||||
const loading = ref(false);
|
||||
const id = ref("");
|
||||
const complaintInfo = ref(emptyComplaint());
|
||||
const images = ref([]);
|
||||
const appealImages = ref([]);
|
||||
const submitLoading = ref(false);
|
||||
const params = reactive({
|
||||
content: "",
|
||||
complainId: "",
|
||||
});
|
||||
const appeal = reactive({
|
||||
orderComplaintId: "",
|
||||
appealContent: "",
|
||||
appealImages: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
id.value = route.query.id;
|
||||
getDetail();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query.id,
|
||||
(val) => {
|
||||
id.value = val;
|
||||
getDetail();
|
||||
}
|
||||
);
|
||||
|
||||
function complainStatusText(v) {
|
||||
const map = {
|
||||
NEW: "新投诉",
|
||||
CANCEL: "已撤销",
|
||||
WAIT_APPEAL: "待申诉",
|
||||
COMMUNICATION: "对话中",
|
||||
WAIT_ARBITRATION: "等待仲裁",
|
||||
COMPLETE: "已完成",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
}
|
||||
function getDetail() {
|
||||
if (!id.value) return;
|
||||
loading.value = true;
|
||||
API_Order.getOrderComplainDetail(id.value)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
complaintInfo.value = res.result || emptyComplaint();
|
||||
images.value = (res.result.images || "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
appealImages.value = (res.result.appealImages || "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
this.submitLoading = true;
|
||||
const appealImages = Array.isArray(this.appeal.appealImages)
|
||||
? this.appeal.appealImages
|
||||
: [];
|
||||
API_Order.appeal({
|
||||
orderComplaintId: this.id,
|
||||
appealContent: this.appeal.appealContent,
|
||||
appealImages,
|
||||
})
|
||||
.then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("申诉成功");
|
||||
this.appeal.appealContent = "";
|
||||
this.appeal.appealImages = [];
|
||||
this.getDetail();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
handleSubmit() {
|
||||
if (!this.params.content) {
|
||||
this.$Message.error("请填写对话内容");
|
||||
return;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function returnDataList() {
|
||||
router.push({ name: "orderComplaint" });
|
||||
}
|
||||
function appealSubmit() {
|
||||
if (!appeal.appealContent) {
|
||||
Message.error("请填写申诉内容");
|
||||
return;
|
||||
}
|
||||
submitLoading.value = true;
|
||||
const appealImages = Array.isArray(appeal.appealImages)
|
||||
? appeal.appealImages
|
||||
: [];
|
||||
API_Order.appeal({
|
||||
orderComplaintId: id.value,
|
||||
appealContent: appeal.appealContent,
|
||||
appealImages,
|
||||
})
|
||||
.then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("申诉成功");
|
||||
appeal.appealContent = "";
|
||||
appeal.appealImages = [];
|
||||
getDetail();
|
||||
}
|
||||
this.submitLoading = true;
|
||||
this.params.complainId = this.id;
|
||||
API_Order.addOrderCommunication(this.params)
|
||||
.then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("对话成功");
|
||||
this.params.content = "";
|
||||
this.getDetail();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.id = this.$route.query.id;
|
||||
this.getDetail();
|
||||
},
|
||||
watch: {
|
||||
"$route.query.id"(val) {
|
||||
this.id = val;
|
||||
this.getDetail();
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
function handleSubmit() {
|
||||
if (!params.content) {
|
||||
Message.error("请填写对话内容");
|
||||
return;
|
||||
}
|
||||
submitLoading.value = true;
|
||||
params.complainId = id.value;
|
||||
API_Order.addOrderCommunication(params)
|
||||
.then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("对话成功");
|
||||
params.content = "";
|
||||
getDetail();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -153,182 +153,184 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import * as API_Order from "@/api/order";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "after-sale-order",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
serviceType: "RETURN_GOODS",
|
||||
orderSn: "",
|
||||
memberName: "",
|
||||
serviceStatus: "",
|
||||
storeName: "",
|
||||
sn: "",
|
||||
keywords: "",
|
||||
},
|
||||
selectDate: null,
|
||||
data: [],
|
||||
total: 0,
|
||||
currentStatus: "",
|
||||
afterSaleNumData: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
serviceStatusWithCount() {
|
||||
return [
|
||||
{ title: "全部", value: "" },
|
||||
{
|
||||
title: `申请售后${this.afterSaleNumData.applyNum ? "(" + this.afterSaleNumData.applyNum + ")" : ""}`,
|
||||
value: "APPLY",
|
||||
},
|
||||
{
|
||||
title: `通过售后${this.afterSaleNumData.passNum ? "(" + this.afterSaleNumData.passNum + ")" : ""}`,
|
||||
value: "PASS",
|
||||
},
|
||||
{
|
||||
title: `拒绝售后${this.afterSaleNumData.refuseNum ? "(" + this.afterSaleNumData.refuseNum + ")" : ""}`,
|
||||
value: "REFUSE",
|
||||
},
|
||||
{
|
||||
title: `待收货${this.afterSaleNumData.buyerReturnNum ? "(" + this.afterSaleNumData.buyerReturnNum + ")" : ""}`,
|
||||
value: "BUYER_RETURN",
|
||||
},
|
||||
{
|
||||
title: `确认收货${this.afterSaleNumData.sellerConfirmNum ? "(" + this.afterSaleNumData.sellerConfirmNum + ")" : ""}`,
|
||||
value: "SELLER_CONFIRM",
|
||||
},
|
||||
{
|
||||
title: `完成售后${this.afterSaleNumData.completeNum ? "(" + this.afterSaleNumData.completeNum + ")" : ""}`,
|
||||
value: "COMPLETE",
|
||||
},
|
||||
{
|
||||
title: `卖家终止售后${this.afterSaleNumData.sellerTerminationNum ? "(" + this.afterSaleNumData.sellerTerminationNum + ")" : ""}`,
|
||||
value: "SELLER_TERMINATION",
|
||||
},
|
||||
{
|
||||
title: `买家取消售后${this.afterSaleNumData.buyerCancelNum ? "(" + this.afterSaleNumData.buyerCancelNum + ")" : ""}`,
|
||||
value: "BUYER_CANCEL",
|
||||
},
|
||||
{
|
||||
title: `等待平台退款${this.afterSaleNumData.waitRefundNum ? "(" + this.afterSaleNumData.waitRefundNum + ")" : ""}`,
|
||||
value: "WAIT_REFUND",
|
||||
},
|
||||
];
|
||||
defineOptions({ name: "after-sale-order" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
serviceType: "RETURN_GOODS",
|
||||
orderSn: "",
|
||||
memberName: "",
|
||||
serviceStatus: "",
|
||||
storeName: "",
|
||||
sn: "",
|
||||
keywords: "",
|
||||
});
|
||||
const selectDate = ref(null);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const currentStatus = ref("");
|
||||
const afterSaleNumData = ref({});
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖(原 ref="searchForm" 未被使用)
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
const serviceStatusWithCount = computed(() => {
|
||||
return [
|
||||
{ title: "全部", value: "" },
|
||||
{
|
||||
title: `申请售后${afterSaleNumData.value.applyNum ? "(" + afterSaleNumData.value.applyNum + ")" : ""}`,
|
||||
value: "APPLY",
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
serviceStatusText(status) {
|
||||
const map = {
|
||||
APPLY: "申请中",
|
||||
PASS: "通过售后",
|
||||
REFUSE: "拒绝售后",
|
||||
BUYER_RETURN: "买家退货,待卖家收货",
|
||||
SELLER_CONFIRM: "卖家确认收货",
|
||||
SELLER_TERMINATION: "卖家终止售后",
|
||||
BUYER_CANCEL: "买家取消售后",
|
||||
COMPLETE: "完成售后",
|
||||
WAIT_REFUND: "待平台退款",
|
||||
};
|
||||
return map[status] || status || "-";
|
||||
{
|
||||
title: `通过售后${afterSaleNumData.value.passNum ? "(" + afterSaleNumData.value.passNum + ")" : ""}`,
|
||||
value: "PASS",
|
||||
},
|
||||
serviceStatusTagType(status) {
|
||||
const map = {
|
||||
APPLY: "primary",
|
||||
PASS: "info",
|
||||
REFUSE: "warning",
|
||||
BUYER_RETURN: "warning",
|
||||
SELLER_CONFIRM: "",
|
||||
SELLER_TERMINATION: "success",
|
||||
BUYER_CANCEL: "danger",
|
||||
COMPLETE: "success",
|
||||
WAIT_REFUND: "primary",
|
||||
};
|
||||
return map[status] || "info";
|
||||
{
|
||||
title: `拒绝售后${afterSaleNumData.value.refuseNum ? "(" + afterSaleNumData.value.refuseNum + ")" : ""}`,
|
||||
value: "REFUSE",
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
{
|
||||
title: `待收货${afterSaleNumData.value.buyerReturnNum ? "(" + afterSaleNumData.value.buyerReturnNum + ")" : ""}`,
|
||||
value: "BUYER_RETURN",
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
{
|
||||
title: `确认收货${afterSaleNumData.value.sellerConfirmNum ? "(" + afterSaleNumData.value.sellerConfirmNum + ")" : ""}`,
|
||||
value: "SELLER_CONFIRM",
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
{
|
||||
title: `完成售后${afterSaleNumData.value.completeNum ? "(" + afterSaleNumData.value.completeNum + ")" : ""}`,
|
||||
value: "COMPLETE",
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
{
|
||||
title: `卖家终止售后${afterSaleNumData.value.sellerTerminationNum ? "(" + afterSaleNumData.value.sellerTerminationNum + ")" : ""}`,
|
||||
value: "SELLER_TERMINATION",
|
||||
},
|
||||
selectDateRange(v) {
|
||||
if (v) {
|
||||
this.searchForm.startDate = v[0];
|
||||
this.searchForm.endDate = v[1];
|
||||
} else {
|
||||
this.searchForm.startDate = "";
|
||||
this.searchForm.endDate = "";
|
||||
}
|
||||
{
|
||||
title: `买家取消售后${afterSaleNumData.value.buyerCancelNum ? "(" + afterSaleNumData.value.buyerCancelNum + ")" : ""}`,
|
||||
value: "BUYER_CANCEL",
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
API_Order.getAfterSaleOrderPage(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
{
|
||||
title: `等待平台退款${afterSaleNumData.value.waitRefundNum ? "(" + afterSaleNumData.value.waitRefundNum + ")" : ""}`,
|
||||
value: "WAIT_REFUND",
|
||||
},
|
||||
getAfterSaleNumData() {
|
||||
const { serviceStatus, ...searchParams } = this.searchForm;
|
||||
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
|
||||
if (res.success) {
|
||||
this.afterSaleNumData = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
detail(v) {
|
||||
const sn = v.sn;
|
||||
this.$filters.customRouterPush({
|
||||
name: "return-goods-order-detail",
|
||||
query: { sn: sn },
|
||||
});
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.serviceStatusClick(tab.paneName);
|
||||
},
|
||||
serviceStatusClick(item) {
|
||||
this.currentStatus = item;
|
||||
if (item === "" || item === undefined) {
|
||||
delete this.searchForm.serviceStatus;
|
||||
} else {
|
||||
this.searchForm.serviceStatus = item;
|
||||
}
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const status = this.$route.query.serviceStatus;
|
||||
if (status) {
|
||||
this.currentStatus = status;
|
||||
this.searchForm.serviceStatus = status;
|
||||
];
|
||||
});
|
||||
|
||||
function serviceStatusText(status) {
|
||||
const map = {
|
||||
APPLY: "申请中",
|
||||
PASS: "通过售后",
|
||||
REFUSE: "拒绝售后",
|
||||
BUYER_RETURN: "买家退货,待卖家收货",
|
||||
SELLER_CONFIRM: "卖家确认收货",
|
||||
SELLER_TERMINATION: "卖家终止售后",
|
||||
BUYER_CANCEL: "买家取消售后",
|
||||
COMPLETE: "完成售后",
|
||||
WAIT_REFUND: "待平台退款",
|
||||
};
|
||||
return map[status] || status || "-";
|
||||
}
|
||||
function serviceStatusTagType(status) {
|
||||
const map = {
|
||||
APPLY: "primary",
|
||||
PASS: "info",
|
||||
REFUSE: "warning",
|
||||
BUYER_RETURN: "warning",
|
||||
SELLER_CONFIRM: "",
|
||||
SELLER_TERMINATION: "success",
|
||||
BUYER_CANCEL: "danger",
|
||||
COMPLETE: "success",
|
||||
WAIT_REFUND: "primary",
|
||||
};
|
||||
return map[status] || "info";
|
||||
}
|
||||
function init() {
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
function selectDateRange(v) {
|
||||
if (v) {
|
||||
searchForm.startDate = v[0];
|
||||
searchForm.endDate = v[1];
|
||||
} else {
|
||||
searchForm.startDate = "";
|
||||
searchForm.endDate = "";
|
||||
}
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Order.getAfterSaleOrderPage(searchForm).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
function getAfterSaleNumData() {
|
||||
const { serviceStatus, ...searchParams } = searchForm;
|
||||
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
|
||||
if (res.success) {
|
||||
afterSaleNumData.value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
function detail(v) {
|
||||
const sn = v.sn;
|
||||
customRouterPush({
|
||||
name: "return-goods-order-detail",
|
||||
query: { sn: sn },
|
||||
});
|
||||
}
|
||||
function onStatusTabClick(tab) {
|
||||
serviceStatusClick(tab.paneName);
|
||||
}
|
||||
function serviceStatusClick(item) {
|
||||
currentStatus.value = item;
|
||||
if (item === "" || item === undefined) {
|
||||
delete searchForm.serviceStatus;
|
||||
} else {
|
||||
searchForm.serviceStatus = item;
|
||||
}
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const status = route.query.serviceStatus;
|
||||
if (status) {
|
||||
currentStatus.value = status;
|
||||
searchForm.serviceStatus = status;
|
||||
}
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -73,7 +73,7 @@
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
ref="table"
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
class="mt_10"
|
||||
@@ -145,8 +145,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import * as API_Order from "@/api/order";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
defineOptions({ name: "returnMoneyOrder" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const createDefaultSearchForm = () => ({
|
||||
pageNumber: 1,
|
||||
@@ -163,166 +170,163 @@ const createDefaultSearchForm = () => ({
|
||||
keywords: "",
|
||||
});
|
||||
|
||||
export default {
|
||||
name: "returnMoneyOrder",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: createDefaultSearchForm(),
|
||||
selectDate: null,
|
||||
data: [],
|
||||
total: 0,
|
||||
currentStatus: "",
|
||||
afterSaleNumData: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
serviceStatusWithCount() {
|
||||
return [
|
||||
{ title: "全部", value: "" },
|
||||
{
|
||||
title: `申请售后${this.afterSaleNumData.applyNum ? "(" + this.afterSaleNumData.applyNum + ")" : ""}`,
|
||||
value: "APPLY",
|
||||
},
|
||||
{
|
||||
title: `通过售后${this.afterSaleNumData.passNum ? "(" + this.afterSaleNumData.passNum + ")" : ""}`,
|
||||
value: "PASS",
|
||||
},
|
||||
{
|
||||
title: `拒绝售后${this.afterSaleNumData.refuseNum ? "(" + this.afterSaleNumData.refuseNum + ")" : ""}`,
|
||||
value: "REFUSE",
|
||||
},
|
||||
{
|
||||
title: `完成售后${this.afterSaleNumData.completeNum ? "(" + this.afterSaleNumData.completeNum + ")" : ""}`,
|
||||
value: "COMPLETE",
|
||||
},
|
||||
{
|
||||
title: `卖家终止售后${this.afterSaleNumData.sellerTerminationNum ? "(" + this.afterSaleNumData.sellerTerminationNum + ")" : ""}`,
|
||||
value: "SELLER_TERMINATION",
|
||||
},
|
||||
{
|
||||
title: `买家取消售后${this.afterSaleNumData.buyerCancelNum ? "(" + this.afterSaleNumData.buyerCancelNum + ")" : ""}`,
|
||||
value: "BUYER_CANCEL",
|
||||
},
|
||||
{
|
||||
title: `等待平台退款${this.afterSaleNumData.waitRefundNum ? "(" + this.afterSaleNumData.waitRefundNum + ")" : ""}`,
|
||||
value: "WAIT_REFUND",
|
||||
},
|
||||
];
|
||||
const loading = ref(true);
|
||||
// searchForm 会被整体替换(handleReset),使用 ref
|
||||
const searchForm = ref(createDefaultSearchForm());
|
||||
const selectDate = ref(null);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const currentStatus = ref("");
|
||||
const afterSaleNumData = ref({});
|
||||
|
||||
// searchFormRef / tableRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
|
||||
const serviceStatusWithCount = computed(() => {
|
||||
return [
|
||||
{ title: "全部", value: "" },
|
||||
{
|
||||
title: `申请售后${afterSaleNumData.value.applyNum ? "(" + afterSaleNumData.value.applyNum + ")" : ""}`,
|
||||
value: "APPLY",
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
serviceStatusText(status) {
|
||||
const map = {
|
||||
APPLY: "申请中",
|
||||
PASS: "通过售后",
|
||||
REFUSE: "拒绝售后",
|
||||
BUYER_RETURN: "买家退货,待卖家收货",
|
||||
SELLER_CONFIRM: "卖家确认收货",
|
||||
SELLER_TERMINATION: "卖家终止售后",
|
||||
BUYER_CANCEL: "买家取消售后",
|
||||
COMPLETE: "完成售后",
|
||||
WAIT_REFUND: "待平台退款",
|
||||
};
|
||||
return map[status] || status || "-";
|
||||
{
|
||||
title: `通过售后${afterSaleNumData.value.passNum ? "(" + afterSaleNumData.value.passNum + ")" : ""}`,
|
||||
value: "PASS",
|
||||
},
|
||||
serviceStatusTagType(status) {
|
||||
const map = {
|
||||
APPLY: "primary",
|
||||
PASS: "info",
|
||||
REFUSE: "warning",
|
||||
BUYER_RETURN: "warning",
|
||||
SELLER_CONFIRM: "",
|
||||
SELLER_TERMINATION: "success",
|
||||
BUYER_CANCEL: "danger",
|
||||
COMPLETE: "success",
|
||||
WAIT_REFUND: "primary",
|
||||
};
|
||||
return map[status] || "info";
|
||||
{
|
||||
title: `拒绝售后${afterSaleNumData.value.refuseNum ? "(" + afterSaleNumData.value.refuseNum + ")" : ""}`,
|
||||
value: "REFUSE",
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
{
|
||||
title: `完成售后${afterSaleNumData.value.completeNum ? "(" + afterSaleNumData.value.completeNum + ")" : ""}`,
|
||||
value: "COMPLETE",
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
{
|
||||
title: `卖家终止售后${afterSaleNumData.value.sellerTerminationNum ? "(" + afterSaleNumData.value.sellerTerminationNum + ")" : ""}`,
|
||||
value: "SELLER_TERMINATION",
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
{
|
||||
title: `买家取消售后${afterSaleNumData.value.buyerCancelNum ? "(" + afterSaleNumData.value.buyerCancelNum + ")" : ""}`,
|
||||
value: "BUYER_CANCEL",
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
{
|
||||
title: `等待平台退款${afterSaleNumData.value.waitRefundNum ? "(" + afterSaleNumData.value.waitRefundNum + ")" : ""}`,
|
||||
value: "WAIT_REFUND",
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = createDefaultSearchForm();
|
||||
this.selectDate = null;
|
||||
this.currentStatus = "";
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
},
|
||||
selectDateRange(v) {
|
||||
if (v) {
|
||||
this.searchForm.startDate = v[0];
|
||||
this.searchForm.endDate = v[1];
|
||||
} else {
|
||||
this.searchForm.startDate = "";
|
||||
this.searchForm.endDate = "";
|
||||
];
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const status = route.query.serviceStatus;
|
||||
if (status) {
|
||||
currentStatus.value = status;
|
||||
searchForm.value.serviceStatus = status;
|
||||
}
|
||||
init();
|
||||
});
|
||||
|
||||
function serviceStatusText(status) {
|
||||
const map = {
|
||||
APPLY: "申请中",
|
||||
PASS: "通过售后",
|
||||
REFUSE: "拒绝售后",
|
||||
BUYER_RETURN: "买家退货,待卖家收货",
|
||||
SELLER_CONFIRM: "卖家确认收货",
|
||||
SELLER_TERMINATION: "卖家终止售后",
|
||||
BUYER_CANCEL: "买家取消售后",
|
||||
COMPLETE: "完成售后",
|
||||
WAIT_REFUND: "待平台退款",
|
||||
};
|
||||
return map[status] || status || "-";
|
||||
}
|
||||
function serviceStatusTagType(status) {
|
||||
const map = {
|
||||
APPLY: "primary",
|
||||
PASS: "info",
|
||||
REFUSE: "warning",
|
||||
BUYER_RETURN: "warning",
|
||||
SELLER_CONFIRM: "",
|
||||
SELLER_TERMINATION: "success",
|
||||
BUYER_CANCEL: "danger",
|
||||
COMPLETE: "success",
|
||||
WAIT_REFUND: "primary",
|
||||
};
|
||||
return map[status] || "info";
|
||||
}
|
||||
function init() {
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
searchForm.value.pageSize = 20;
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
function handleReset() {
|
||||
searchForm.value = createDefaultSearchForm();
|
||||
selectDate.value = null;
|
||||
currentStatus.value = "";
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
function selectDateRange(v) {
|
||||
if (v) {
|
||||
searchForm.value.startDate = v[0];
|
||||
searchForm.value.endDate = v[1];
|
||||
} else {
|
||||
searchForm.value.startDate = "";
|
||||
searchForm.value.endDate = "";
|
||||
}
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Order.getAfterSaleOrderPage(searchForm.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
API_Order.getAfterSaleOrderPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getAfterSaleNumData() {
|
||||
const { serviceStatus, ...searchParams } = this.searchForm;
|
||||
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
|
||||
if (res.success) {
|
||||
this.afterSaleNumData = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
detail(v) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "return-goods-order-detail",
|
||||
query: { sn: v.sn },
|
||||
});
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.serviceStatusClick(tab.paneName);
|
||||
},
|
||||
serviceStatusClick(item) {
|
||||
this.currentStatus = item;
|
||||
if (item === "" || item === undefined) {
|
||||
delete this.searchForm.serviceStatus;
|
||||
} else {
|
||||
this.searchForm.serviceStatus = item;
|
||||
}
|
||||
this.getDataList();
|
||||
this.getAfterSaleNumData();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const status = this.$route.query.serviceStatus;
|
||||
if (status) {
|
||||
this.currentStatus = status;
|
||||
this.searchForm.serviceStatus = status;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function getAfterSaleNumData() {
|
||||
const { serviceStatus, ...searchParams } = searchForm.value;
|
||||
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
|
||||
if (res.success) {
|
||||
afterSaleNumData.value = res.result;
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
function detail(v) {
|
||||
customRouterPush({
|
||||
name: "return-goods-order-detail",
|
||||
query: { sn: v.sn },
|
||||
});
|
||||
}
|
||||
function onStatusTabClick(tab) {
|
||||
serviceStatusClick(tab.paneName);
|
||||
}
|
||||
function serviceStatusClick(item) {
|
||||
currentStatus.value = item;
|
||||
if (item === "" || item === undefined) {
|
||||
delete searchForm.value.serviceStatus;
|
||||
} else {
|
||||
searchForm.value.serviceStatus = item;
|
||||
}
|
||||
getDataList();
|
||||
getAfterSaleNumData();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -348,276 +348,278 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import * as API_Order from "@/api/order";
|
||||
export default {
|
||||
name: "returnGoodsOrderDetail",
|
||||
data() {
|
||||
return {
|
||||
sn: "", // 订单号
|
||||
logisticsModal: false, //查询物流模态框
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
logisticsInfo: {}, //物流信息
|
||||
afterSaleInfo: {}, // 售后信息
|
||||
afterSaleImage: [], //会员申诉图片
|
||||
submitLoading: false, // 添加或编辑提交状态
|
||||
confirmLoading: false, // 确认收货提交状态
|
||||
storeMsg: {}, // 商家地址信息
|
||||
//商家处理意见
|
||||
params: {
|
||||
serviceStatus: "PASS",
|
||||
remark: "",
|
||||
},
|
||||
//平台退款
|
||||
refundPriceForm: {
|
||||
remark: "",
|
||||
},
|
||||
afterSaleRefundStatusList: [
|
||||
{
|
||||
name: "退款申请",
|
||||
status: "APPLY",
|
||||
},
|
||||
{
|
||||
name: "通过售后",
|
||||
status: "PASS",
|
||||
},
|
||||
{
|
||||
name: "拒绝售后",
|
||||
status: "REFUSE",
|
||||
},
|
||||
{
|
||||
name: "卖家终止售后",
|
||||
status: "SELLER_TERMINATION",
|
||||
},
|
||||
{
|
||||
name: "买家取消售后",
|
||||
status: "BUYER_CANCEL",
|
||||
},
|
||||
{
|
||||
name: "完成售后",
|
||||
status: "COMPLETE",
|
||||
},
|
||||
{
|
||||
name: "待平台退款",
|
||||
status: "WAIT_REFUND",
|
||||
},
|
||||
],
|
||||
afterSaleStatusList: [
|
||||
// 售后状态列表
|
||||
{
|
||||
name: "申请中",
|
||||
status: "APPLY",
|
||||
},
|
||||
{
|
||||
name: "通过售后",
|
||||
status: "PASS",
|
||||
},
|
||||
{
|
||||
name: "拒绝售后",
|
||||
status: "REFUSE",
|
||||
},
|
||||
{
|
||||
name: "买家退货,待卖家收货",
|
||||
status: "BUYER_RETURN",
|
||||
},
|
||||
{
|
||||
name: "卖家确认收货",
|
||||
status: "SELLER_CONFIRM",
|
||||
},
|
||||
{
|
||||
name: "卖家终止售后",
|
||||
status: "SELLER_TERMINATION",
|
||||
},
|
||||
{
|
||||
name: "买家取消售后",
|
||||
status: "BUYER_CANCEL",
|
||||
},
|
||||
{
|
||||
name: "完成售后",
|
||||
status: "COMPLETE",
|
||||
},
|
||||
{
|
||||
name: "待平台退款",
|
||||
status: "WAIT_REFUND",
|
||||
},
|
||||
],
|
||||
};
|
||||
defineOptions({ name: "returnGoodsOrderDetail" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const sn = ref(""); // 订单号
|
||||
const logisticsModal = ref(false); //查询物流模态框
|
||||
|
||||
const logisticsInfo = ref({}); //物流信息
|
||||
const afterSaleInfo = ref({}); // 售后信息
|
||||
const afterSaleImage = ref([]); //会员申诉图片
|
||||
const submitLoading = ref(false); // 添加或编辑提交状态
|
||||
const confirmLoading = ref(false); // 确认收货提交状态
|
||||
const storeMsg = ref({}); // 商家地址信息
|
||||
// 原代码未在 data 声明、直接挂 this 上的字段,补声明避免 ReferenceError
|
||||
let loading = false;
|
||||
//商家处理意见
|
||||
const params = reactive({
|
||||
serviceStatus: "PASS",
|
||||
remark: "",
|
||||
});
|
||||
//平台退款
|
||||
const refundPriceForm = reactive({
|
||||
remark: "",
|
||||
});
|
||||
const afterSaleRefundStatusList = ref([
|
||||
{
|
||||
name: "退款申请",
|
||||
status: "APPLY",
|
||||
},
|
||||
computed: {
|
||||
isRefundOnly() {
|
||||
return this.afterSaleInfo.serviceType === "RETURN_MONEY";
|
||||
},
|
||||
sectionTitle() {
|
||||
return this.isRefundOnly ? "退款申请" : "退货申请";
|
||||
},
|
||||
buyerLogisticsName() {
|
||||
const info = this.afterSaleInfo;
|
||||
return (
|
||||
info.mLogisticsName ||
|
||||
info.mlogisticsName ||
|
||||
this.logisticsInfo.shipper ||
|
||||
"-"
|
||||
);
|
||||
},
|
||||
buyerLogisticsNo() {
|
||||
const info = this.afterSaleInfo;
|
||||
return (
|
||||
info.mLogisticsNo ||
|
||||
info.mlogisticsNo ||
|
||||
this.logisticsInfo.logisticCode ||
|
||||
"-"
|
||||
);
|
||||
},
|
||||
canConfirmReceipt() {
|
||||
const vo = this.afterSaleInfo.afterSaleAllowOperationVO;
|
||||
if (vo && vo.rog) return true;
|
||||
return this.afterSaleInfo.serviceStatus === "BUYER_RETURN";
|
||||
},
|
||||
{
|
||||
name: "通过售后",
|
||||
status: "PASS",
|
||||
},
|
||||
methods: {
|
||||
// 获取售后详情
|
||||
getDetail() {
|
||||
this.loading = true;
|
||||
API_Order.getAfterSaleOrderDetail(this.sn).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.afterSaleInfo = res.result;
|
||||
this.afterSaleInfo.showDelivery = this.showDelivery(
|
||||
this.afterSaleInfo.serviceStatus
|
||||
);
|
||||
this.afterSaleInfo.serviceName = this.filterOrderStatus(
|
||||
this.afterSaleInfo.serviceStatus
|
||||
);
|
||||
this.afterSaleImage = (res.result.afterSaleImage || "").split(",");
|
||||
//退货地址去掉逗号
|
||||
if (this.afterSaleInfo.mconsigneeAddressPath)
|
||||
this.afterSaleInfo.mconsigneeAddressPath = this.afterSaleInfo.mconsigneeAddressPath.replaceAll(
|
||||
{
|
||||
name: "拒绝售后",
|
||||
status: "REFUSE",
|
||||
},
|
||||
{
|
||||
name: "卖家终止售后",
|
||||
status: "SELLER_TERMINATION",
|
||||
},
|
||||
{
|
||||
name: "买家取消售后",
|
||||
status: "BUYER_CANCEL",
|
||||
},
|
||||
{
|
||||
name: "完成售后",
|
||||
status: "COMPLETE",
|
||||
},
|
||||
{
|
||||
name: "待平台退款",
|
||||
status: "WAIT_REFUND",
|
||||
},
|
||||
]);
|
||||
const afterSaleStatusList = ref([
|
||||
// 售后状态列表
|
||||
{
|
||||
name: "申请中",
|
||||
status: "APPLY",
|
||||
},
|
||||
{
|
||||
name: "通过售后",
|
||||
status: "PASS",
|
||||
},
|
||||
{
|
||||
name: "拒绝售后",
|
||||
status: "REFUSE",
|
||||
},
|
||||
{
|
||||
name: "买家退货,待卖家收货",
|
||||
status: "BUYER_RETURN",
|
||||
},
|
||||
{
|
||||
name: "卖家确认收货",
|
||||
status: "SELLER_CONFIRM",
|
||||
},
|
||||
{
|
||||
name: "卖家终止售后",
|
||||
status: "SELLER_TERMINATION",
|
||||
},
|
||||
{
|
||||
name: "买家取消售后",
|
||||
status: "BUYER_CANCEL",
|
||||
},
|
||||
{
|
||||
name: "完成售后",
|
||||
status: "COMPLETE",
|
||||
},
|
||||
{
|
||||
name: "待平台退款",
|
||||
status: "WAIT_REFUND",
|
||||
},
|
||||
]);
|
||||
|
||||
const isRefundOnly = computed(() => {
|
||||
return afterSaleInfo.value.serviceType === "RETURN_MONEY";
|
||||
});
|
||||
const sectionTitle = computed(() => {
|
||||
return isRefundOnly.value ? "退款申请" : "退货申请";
|
||||
});
|
||||
const buyerLogisticsName = computed(() => {
|
||||
const info = afterSaleInfo.value;
|
||||
return (
|
||||
info.mLogisticsName ||
|
||||
info.mlogisticsName ||
|
||||
logisticsInfo.value.shipper ||
|
||||
"-"
|
||||
);
|
||||
});
|
||||
const buyerLogisticsNo = computed(() => {
|
||||
const info = afterSaleInfo.value;
|
||||
return (
|
||||
info.mLogisticsNo ||
|
||||
info.mlogisticsNo ||
|
||||
logisticsInfo.value.logisticCode ||
|
||||
"-"
|
||||
);
|
||||
});
|
||||
const canConfirmReceipt = computed(() => {
|
||||
const vo = afterSaleInfo.value.afterSaleAllowOperationVO;
|
||||
if (vo && vo.rog) return true;
|
||||
return afterSaleInfo.value.serviceStatus === "BUYER_RETURN";
|
||||
});
|
||||
|
||||
// 获取售后详情
|
||||
function getDetail() {
|
||||
loading = true;
|
||||
API_Order.getAfterSaleOrderDetail(sn.value).then((res) => {
|
||||
loading = false;
|
||||
if (res.success) {
|
||||
afterSaleInfo.value = res.result;
|
||||
afterSaleInfo.value.showDelivery = showDelivery(
|
||||
afterSaleInfo.value.serviceStatus
|
||||
);
|
||||
afterSaleInfo.value.serviceName = filterOrderStatus(
|
||||
afterSaleInfo.value.serviceStatus
|
||||
);
|
||||
afterSaleImage.value = (res.result.afterSaleImage || "").split(",");
|
||||
//退货地址去掉逗号
|
||||
if (afterSaleInfo.value.mconsigneeAddressPath)
|
||||
afterSaleInfo.value.mconsigneeAddressPath = afterSaleInfo.value.mconsigneeAddressPath.replaceAll(
|
||||
",",
|
||||
" "
|
||||
);
|
||||
|
||||
params.actualRefundPrice = afterSaleInfo.value.applyRefundPrice;
|
||||
// 如果显示物流信息,展示商家地址
|
||||
if (afterSaleInfo.value.showDelivery) {
|
||||
API_Order.storeAddress(sn.value).then((resu) => {
|
||||
if (resu.success) {
|
||||
const obj = resu.result;
|
||||
obj.salesConsigneeAddressPath = obj.salesConsigneeAddressPath.replaceAll(
|
||||
",",
|
||||
" "
|
||||
""
|
||||
);
|
||||
|
||||
this.params.actualRefundPrice = this.afterSaleInfo.applyRefundPrice;
|
||||
// 如果显示物流信息,展示商家地址
|
||||
if (this.afterSaleInfo.showDelivery) {
|
||||
API_Order.storeAddress(this.sn).then((resu) => {
|
||||
if (resu.success) {
|
||||
const obj = resu.result;
|
||||
obj.salesConsigneeAddressPath = obj.salesConsigneeAddressPath.replaceAll(
|
||||
",",
|
||||
""
|
||||
);
|
||||
this.storeMsg = obj;
|
||||
}
|
||||
});
|
||||
storeMsg.value = obj;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
//平台退款
|
||||
refundPriceSubmit() {
|
||||
if (this.refundPriceForm.remark == "") {
|
||||
this.$Message.error("请输入退款备注");
|
||||
return;
|
||||
});
|
||||
}
|
||||
this.$Modal.confirm({
|
||||
title: "确认退款",
|
||||
content: "请确认退款?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
API_Order.refundPrice(this.sn, this.refundPriceForm).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("收款成功");
|
||||
this.getDetail();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
//查询物流
|
||||
logisticsSeller() {
|
||||
this.logisticsModal = true;
|
||||
this.logisticsInfo = {};
|
||||
API_Order.getAfterSaleTraces(this.sn).then((res) => {
|
||||
if (res.success && res.result != null) {
|
||||
this.logisticsInfo = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
// 商家确认收货 / 拒收
|
||||
sellerConfirmSubmit(type) {
|
||||
const isPass = type === "PASS";
|
||||
this.$Modal.confirm({
|
||||
title: isPass ? "确认收货" : "确认拒收",
|
||||
content: isPass ? "请确认已经收到退货货物?" : "确认拒收此货物?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
this.confirmLoading = true;
|
||||
API_Order.afterSaleSellerConfirm(this.sn, {
|
||||
serviceStatus: type,
|
||||
remark: this.params.remark || "",
|
||||
}).then((res) => {
|
||||
this.$Modal.remove();
|
||||
this.confirmLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success(isPass ? "收货成功" : "拒收成功");
|
||||
this.getDetail();
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$Modal.remove();
|
||||
this.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
//关闭物流弹出框
|
||||
logisticsClose() {
|
||||
this.logisticsModal = false;
|
||||
},
|
||||
//回复
|
||||
handleSubmit() {
|
||||
if (this.params.remark == "") {
|
||||
this.$Message.error("请输入备注信息");
|
||||
return;
|
||||
}
|
||||
if (this.params.serviceStatus === "PASS" && this.params.actualRefundPrice === "") {
|
||||
this.$Message.error("请输入退款金额");
|
||||
return;
|
||||
}
|
||||
API_Order.afterSaleSellerReview(this.sn, this.params).then((res) => {
|
||||
this.submitLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
//平台退款
|
||||
function refundPriceSubmit() {
|
||||
if (refundPriceForm.remark == "") {
|
||||
Message.error("请输入退款备注");
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "确认退款",
|
||||
content: "请确认退款?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
API_Order.refundPrice(sn.value, refundPriceForm).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("审核成功");
|
||||
this.params.remark = "";
|
||||
this.getDetail();
|
||||
Message.success("收款成功");
|
||||
getDetail();
|
||||
}
|
||||
});
|
||||
},
|
||||
filterOrderStatus(status) {
|
||||
const list =
|
||||
this.afterSaleInfo.serviceType === "RETURN_MONEY"
|
||||
? this.afterSaleRefundStatusList
|
||||
: this.afterSaleStatusList;
|
||||
const ob = list.filter((e) => e.status === status);
|
||||
return ob.length > 0 ? ob[0].name : status;
|
||||
},
|
||||
// 根据订单状态判断是否显示物流信息
|
||||
showDelivery(status) {
|
||||
let flag = false;
|
||||
this.afterSaleStatusList.forEach((e, index) => {
|
||||
// 订单为买家退货,待卖家收货之后的状态,并且不是买家取消售后,展示物流信息
|
||||
if (e.status === status && index >= 3 && index !== 6) {
|
||||
flag = true;
|
||||
});
|
||||
}
|
||||
//查询物流
|
||||
function logisticsSeller() {
|
||||
logisticsModal.value = true;
|
||||
logisticsInfo.value = {};
|
||||
API_Order.getAfterSaleTraces(sn.value).then((res) => {
|
||||
if (res.success && res.result != null) {
|
||||
logisticsInfo.value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 商家确认收货 / 拒收
|
||||
function sellerConfirmSubmit(type) {
|
||||
const isPass = type === "PASS";
|
||||
Modal.confirm({
|
||||
title: isPass ? "确认收货" : "确认拒收",
|
||||
content: isPass ? "请确认已经收到退货货物?" : "确认拒收此货物?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
confirmLoading.value = true;
|
||||
API_Order.afterSaleSellerConfirm(sn.value, {
|
||||
serviceStatus: type,
|
||||
remark: params.remark || "",
|
||||
}).then((res) => {
|
||||
Modal.remove();
|
||||
confirmLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success(isPass ? "收货成功" : "拒收成功");
|
||||
getDetail();
|
||||
}
|
||||
}).catch(() => {
|
||||
Modal.remove();
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
return flag;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.sn = this.$route.query.sn;
|
||||
this.getDetail();
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
//关闭物流弹出框
|
||||
function logisticsClose() {
|
||||
logisticsModal.value = false;
|
||||
}
|
||||
//回复
|
||||
function handleSubmit() {
|
||||
if (params.remark == "") {
|
||||
Message.error("请输入备注信息");
|
||||
return;
|
||||
}
|
||||
if (params.serviceStatus === "PASS" && params.actualRefundPrice === "") {
|
||||
Message.error("请输入退款金额");
|
||||
return;
|
||||
}
|
||||
API_Order.afterSaleSellerReview(sn.value, params).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("审核成功");
|
||||
params.remark = "";
|
||||
getDetail();
|
||||
}
|
||||
});
|
||||
}
|
||||
function filterOrderStatus(status) {
|
||||
const list =
|
||||
afterSaleInfo.value.serviceType === "RETURN_MONEY"
|
||||
? afterSaleRefundStatusList.value
|
||||
: afterSaleStatusList.value;
|
||||
const ob = list.filter((e) => e.status === status);
|
||||
return ob.length > 0 ? ob[0].name : status;
|
||||
}
|
||||
// 根据订单状态判断是否显示物流信息
|
||||
function showDelivery(status) {
|
||||
let flag = false;
|
||||
afterSaleStatusList.value.forEach((e, index) => {
|
||||
// 订单为买家退货,待卖家收货之后的状态,并且不是买家取消售后,展示物流信息
|
||||
if (e.status === status && index >= 3 && index !== 6) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
sn.value = route.query.sn;
|
||||
getDetail();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.main-content {
|
||||
|
||||
@@ -45,101 +45,106 @@
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { UploadFilled } from "@element-plus/icons-vue";
|
||||
import { downLoadDeliverExcel, uploadDeliverExcel } from "@/api/order.js";
|
||||
import { baseUrl } from "@/libs/axios.js";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import downloadImg from "@/assets/download.png";
|
||||
import uploadImg from "@/assets/upload.png";
|
||||
import successImg from "@/assets/success.png";
|
||||
|
||||
export default {
|
||||
components: { UploadFilled },
|
||||
data() {
|
||||
return {
|
||||
file: "",
|
||||
action: baseUrl + "/order/order/batchDeliver",
|
||||
accessToken: {},
|
||||
stepList: [
|
||||
{
|
||||
img: downloadImg,
|
||||
title: "1.下载批量发货导入模板",
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
img: uploadImg,
|
||||
title: "2.上传数据",
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
img: successImg,
|
||||
title: "3.完成",
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const appStore = useAppStore();
|
||||
const router = useRouter();
|
||||
|
||||
const file = ref("");
|
||||
const action = baseUrl + "/order/order/batchDeliver";
|
||||
const accessToken = ref({});
|
||||
const stepList = ref([
|
||||
{
|
||||
img: downloadImg,
|
||||
title: "1.下载批量发货导入模板",
|
||||
checked: true,
|
||||
},
|
||||
mounted() {
|
||||
this.accessToken.accessToken = this.getStore("accessToken");
|
||||
{
|
||||
img: uploadImg,
|
||||
title: "2.上传数据",
|
||||
checked: false,
|
||||
},
|
||||
methods: {
|
||||
handleCheckStep(val) {
|
||||
if (val.title.search("3") === -1) {
|
||||
this.stepList.forEach((item) => {
|
||||
item.checked = false;
|
||||
});
|
||||
val.checked = true;
|
||||
{
|
||||
img: successImg,
|
||||
title: "3.完成",
|
||||
checked: false,
|
||||
},
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
accessToken.value.accessToken = getStore("accessToken");
|
||||
});
|
||||
|
||||
function handleCheckStep(val) {
|
||||
if (val.title.search("3") === -1) {
|
||||
stepList.value.forEach((item) => {
|
||||
item.checked = false;
|
||||
});
|
||||
val.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpload(uploadFile) {
|
||||
file.value = uploadFile;
|
||||
upload();
|
||||
return false;
|
||||
}
|
||||
|
||||
function navigationToGoodsOrder() {
|
||||
router.push({ path: "/order/orderList" });
|
||||
}
|
||||
|
||||
function close() {
|
||||
appStore.removeTag("export-order-deliver");
|
||||
localStorage.storeOpenedList = JSON.stringify(appStore.storeOpenedList);
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
async function upload() {
|
||||
const fd = new FormData();
|
||||
fd.append("files", file.value);
|
||||
const res = await uploadDeliverExcel(fd);
|
||||
if (res.success) {
|
||||
stepList.value.forEach((item) => {
|
||||
item.checked = false;
|
||||
});
|
||||
stepList.value[2].checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function downLoad() {
|
||||
downLoadDeliverExcel()
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], {
|
||||
type: "application/vnd.ms-excel;charset=utf-8",
|
||||
});
|
||||
if ("download" in document.createElement("a")) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "批量发货导入模板.xls";
|
||||
link.style.display = "none";
|
||||
link.href = URL.createObjectURL(blob);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, "批量发货导入模板.xls");
|
||||
}
|
||||
},
|
||||
handleUpload(file) {
|
||||
this.file = file;
|
||||
this.upload();
|
||||
return false;
|
||||
},
|
||||
navigationToGoodsOrder() {
|
||||
this.$router.push({ path: "/order/orderList" });
|
||||
},
|
||||
close() {
|
||||
this.$store.commit("removeTag", "export-order-deliver");
|
||||
localStorage.storeOpenedList = JSON.stringify(this.$store.state.app.storeOpenedList);
|
||||
this.$router.go(-1);
|
||||
},
|
||||
async upload() {
|
||||
const fd = new FormData();
|
||||
fd.append("files", this.file);
|
||||
const res = await uploadDeliverExcel(fd);
|
||||
if (res.success) {
|
||||
this.stepList.forEach((item) => {
|
||||
item.checked = false;
|
||||
});
|
||||
this.stepList[2].checked = true;
|
||||
}
|
||||
},
|
||||
downLoad() {
|
||||
downLoadDeliverExcel()
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], {
|
||||
type: "application/vnd.ms-excel;charset=utf-8",
|
||||
});
|
||||
if ("download" in document.createElement("a")) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "批量发货导入模板.xls";
|
||||
link.style.display = "none";
|
||||
link.href = URL.createObjectURL(blob);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, "批量发货导入模板.xls");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
label-width="70px"
|
||||
class="search-form order-search-form"
|
||||
@@ -156,322 +156,325 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { CopyDocument } from "@element-plus/icons-vue";
|
||||
import * as API_Order from "@/api/order";
|
||||
import { canShowShipButton, isNonShippableOrder } from "@/utils/orderFulfill";
|
||||
export default {
|
||||
name: "orderList",
|
||||
components: { CopyDocument },
|
||||
data() {
|
||||
return {
|
||||
fields: {
|
||||
订单编号: "sn",
|
||||
下单时间: "createTime",
|
||||
客户名称: "memberName",
|
||||
支付方式: {
|
||||
field: "clientType",
|
||||
callback: (value) => {
|
||||
if (value == "H5") return "移动端";
|
||||
if (value == "PC") return "PC端";
|
||||
if (value == "WECHAT_MP") return "小程序端";
|
||||
if (value == "APP") return "移动应用端";
|
||||
return value;
|
||||
},
|
||||
},
|
||||
商品数量: "groupNum",
|
||||
付款状态: {
|
||||
field: "payStatus",
|
||||
callback: (value) =>
|
||||
value == "UNPAID" ? "未付款" : value == "PAID" ? "已付款" : "",
|
||||
},
|
||||
店铺: "storeName",
|
||||
},
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
orderType: "",
|
||||
orderSn: "",
|
||||
keywords: "",
|
||||
goodsType: "",
|
||||
orderStatus: "",
|
||||
paymentMethod: "",
|
||||
orderPromotionType: "",
|
||||
},
|
||||
selectDate: null,
|
||||
data: [],
|
||||
total: 0,
|
||||
orderNumData: {},
|
||||
currentStatus: "ALL",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
orderStatusWithCount() {
|
||||
return [
|
||||
{ title: "全部", value: "ALL" },
|
||||
{
|
||||
title: `未付款${this.orderNumData.waitPayNum ? "(" + this.orderNumData.waitPayNum + ")" : ""}`,
|
||||
value: "UNPAID",
|
||||
},
|
||||
{
|
||||
title: `已付款${this.orderNumData.waitDeliveryNum ? "(" + this.orderNumData.waitDeliveryNum + ")" : ""}`,
|
||||
value: "PAID",
|
||||
},
|
||||
{
|
||||
title: `待发货${this.orderNumData.waitShipNum ? "(" + this.orderNumData.waitShipNum + ")" : ""}`,
|
||||
value: "UNDELIVERED",
|
||||
},
|
||||
{
|
||||
title: `部分发货${this.orderNumData.partsDeliveredNumNum ? "(" + this.orderNumData.partsDeliveredNumNum + ")" : ""}`,
|
||||
value: "PARTS_DELIVERED",
|
||||
},
|
||||
{
|
||||
title: `待收货${this.orderNumData.deliveredNum ? "(" + this.orderNumData.deliveredNum + ")" : ""}`,
|
||||
value: "DELIVERED",
|
||||
},
|
||||
{
|
||||
title: `待核验${this.orderNumData.waitCheckNum ? "(" + this.orderNumData.waitCheckNum + ")" : ""}`,
|
||||
value: "TAKE",
|
||||
},
|
||||
{
|
||||
title: `待自提${this.orderNumData.waitSelfPickNum ? "(" + this.orderNumData.waitSelfPickNum + ")" : ""}`,
|
||||
value: "STAY_PICKED_UP",
|
||||
},
|
||||
{
|
||||
title: `已完成${this.orderNumData.finishNum ? "(" + this.orderNumData.finishNum + ")" : ""}`,
|
||||
value: "COMPLETED",
|
||||
},
|
||||
{
|
||||
title: `已关闭${this.orderNumData.closeNum ? "(" + this.orderNumData.closeNum + ")" : ""}`,
|
||||
value: "CANCELLED",
|
||||
},
|
||||
];
|
||||
import { Message } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
defineOptions({ name: "orderList" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const fields = ref({
|
||||
订单编号: "sn",
|
||||
下单时间: "createTime",
|
||||
客户名称: "memberName",
|
||||
支付方式: {
|
||||
field: "clientType",
|
||||
callback: (value) => {
|
||||
if (value == "H5") return "移动端";
|
||||
if (value == "PC") return "PC端";
|
||||
if (value == "WECHAT_MP") return "小程序端";
|
||||
if (value == "APP") return "移动应用端";
|
||||
return value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onStatusTabClick(tab) {
|
||||
this.orderStatusClick(tab.paneName);
|
||||
商品数量: "groupNum",
|
||||
付款状态: {
|
||||
field: "payStatus",
|
||||
callback: (value) =>
|
||||
value == "UNPAID" ? "未付款" : value == "PAID" ? "已付款" : "",
|
||||
},
|
||||
店铺: "storeName",
|
||||
});
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
orderType: "",
|
||||
orderSn: "",
|
||||
keywords: "",
|
||||
goodsType: "",
|
||||
orderStatus: "",
|
||||
paymentMethod: "",
|
||||
orderPromotionType: "",
|
||||
});
|
||||
const selectDate = ref(null);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const orderNumData = ref({});
|
||||
const currentStatus = ref("ALL");
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
const orderStatusWithCount = computed(() => {
|
||||
return [
|
||||
{ title: "全部", value: "ALL" },
|
||||
{
|
||||
title: `未付款${orderNumData.value.waitPayNum ? "(" + orderNumData.value.waitPayNum + ")" : ""}`,
|
||||
value: "UNPAID",
|
||||
},
|
||||
orderGoods(row) {
|
||||
const items = row && row.orderItems;
|
||||
return items && items.length ? items : [{}];
|
||||
{
|
||||
title: `已付款${orderNumData.value.waitDeliveryNum ? "(" + orderNumData.value.waitDeliveryNum + ")" : ""}`,
|
||||
value: "PAID",
|
||||
},
|
||||
afterSaleText(status) {
|
||||
const map = {
|
||||
NOT_APPLIED: "—",
|
||||
NEW: "—",
|
||||
PART_AFTER_SALE: "部分售后",
|
||||
ALREADY_APPLIED: "售后中",
|
||||
EXPIRED: "不可售后",
|
||||
};
|
||||
return map[status] || "—";
|
||||
{
|
||||
title: `待发货${orderNumData.value.waitShipNum ? "(" + orderNumData.value.waitShipNum + ")" : ""}`,
|
||||
value: "UNDELIVERED",
|
||||
},
|
||||
copyText(text) {
|
||||
if (!text) {
|
||||
return;
|
||||
{
|
||||
title: `部分发货${orderNumData.value.partsDeliveredNumNum ? "(" + orderNumData.value.partsDeliveredNumNum + ")" : ""}`,
|
||||
value: "PARTS_DELIVERED",
|
||||
},
|
||||
{
|
||||
title: `待收货${orderNumData.value.deliveredNum ? "(" + orderNumData.value.deliveredNum + ")" : ""}`,
|
||||
value: "DELIVERED",
|
||||
},
|
||||
{
|
||||
title: `待核验${orderNumData.value.waitCheckNum ? "(" + orderNumData.value.waitCheckNum + ")" : ""}`,
|
||||
value: "TAKE",
|
||||
},
|
||||
{
|
||||
title: `待自提${orderNumData.value.waitSelfPickNum ? "(" + orderNumData.value.waitSelfPickNum + ")" : ""}`,
|
||||
value: "STAY_PICKED_UP",
|
||||
},
|
||||
{
|
||||
title: `已完成${orderNumData.value.finishNum ? "(" + orderNumData.value.finishNum + ")" : ""}`,
|
||||
value: "COMPLETED",
|
||||
},
|
||||
{
|
||||
title: `已关闭${orderNumData.value.closeNum ? "(" + orderNumData.value.closeNum + ")" : ""}`,
|
||||
value: "CANCELLED",
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const orderStatus = route.query.orderStatus;
|
||||
if (orderStatus) {
|
||||
currentStatus.value = orderStatus;
|
||||
searchForm.orderStatus = orderStatus;
|
||||
}
|
||||
init();
|
||||
});
|
||||
|
||||
function onStatusTabClick(tab) {
|
||||
orderStatusClick(tab.paneName);
|
||||
}
|
||||
function orderGoods(row) {
|
||||
const items = row && row.orderItems;
|
||||
return items && items.length ? items : [{}];
|
||||
}
|
||||
function afterSaleText(status) {
|
||||
const map = {
|
||||
NOT_APPLIED: "—",
|
||||
NEW: "—",
|
||||
PART_AFTER_SALE: "部分售后",
|
||||
ALREADY_APPLIED: "售后中",
|
||||
EXPIRED: "不可售后",
|
||||
};
|
||||
return map[status] || "—";
|
||||
}
|
||||
function copyText(text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const done = () => Message.success("已复制订单编号");
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(done).catch(() => copyTextFallback(text, done));
|
||||
return;
|
||||
}
|
||||
copyTextFallback(text, done);
|
||||
}
|
||||
function copyTextFallback(text, done) {
|
||||
const input = document.createElement("textarea");
|
||||
input.value = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(input);
|
||||
done();
|
||||
}
|
||||
function clientTypeText(v) {
|
||||
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
|
||||
return map[v] || v || "-";
|
||||
}
|
||||
function orderPromotionText(v) {
|
||||
const map = {
|
||||
NORMAL: "普通订单",
|
||||
PINTUAN: "拼团订单",
|
||||
GIFT: "赠品订单",
|
||||
POINTS: "积分订单",
|
||||
KANJIA: "砍价订单",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
}
|
||||
function orderPromotionTagType(v) {
|
||||
const map = {
|
||||
NORMAL: "primary",
|
||||
PINTUAN: "danger",
|
||||
GIFT: "success",
|
||||
POINTS: "info",
|
||||
KANJIA: "warning",
|
||||
};
|
||||
return map[v] || "info";
|
||||
}
|
||||
function paymentMethodText(v) {
|
||||
const map = {
|
||||
NOT_ACTUALLY_PAID: "-",
|
||||
WECHAT: "微信支付",
|
||||
ALIPAY: "支付宝",
|
||||
WALLET: "余额支付",
|
||||
BANK_TRANSFER: "线下转账",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
}
|
||||
function orderStatusText(v) {
|
||||
const map = {
|
||||
UNPAID: "未付款",
|
||||
PAID: "已付款",
|
||||
UNDELIVERED: "待发货",
|
||||
STAY_PICKED_UP: "待自提",
|
||||
PARTS_DELIVERED: "部分发货",
|
||||
DELIVERED: "已发货",
|
||||
COMPLETED: "已完成",
|
||||
TAKE: "待核验",
|
||||
CANCELLED: "已关闭",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
}
|
||||
function orderStatusTagType(v) {
|
||||
const map = {
|
||||
UNPAID: "danger",
|
||||
PAID: "primary",
|
||||
UNDELIVERED: "info",
|
||||
STAY_PICKED_UP: "info",
|
||||
PARTS_DELIVERED: "warning",
|
||||
DELIVERED: "warning",
|
||||
COMPLETED: "success",
|
||||
TAKE: "warning",
|
||||
CANCELLED: "danger",
|
||||
};
|
||||
return map[v] || "info";
|
||||
}
|
||||
function init() {
|
||||
getDataList();
|
||||
getOrderNumData();
|
||||
}
|
||||
function changePage(v) {
|
||||
searchForm.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = v;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
getOrderNumData();
|
||||
}
|
||||
function selectDateRange(v) {
|
||||
if (v && v.length === 2) {
|
||||
searchForm.startDate = v[0];
|
||||
searchForm.endDate = v[1];
|
||||
} else {
|
||||
searchForm.startDate = "";
|
||||
searchForm.endDate = "";
|
||||
}
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Order.getOrderList(searchForm)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
const done = () => this.$Message.success("已复制订单编号");
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(done).catch(() => this.copyTextFallback(text, done));
|
||||
return;
|
||||
}
|
||||
this.copyTextFallback(text, done);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function detail(v, openFulfill = false) {
|
||||
customRouterPush({
|
||||
name: "order-detail",
|
||||
query: {
|
||||
sn: v.sn,
|
||||
...(openFulfill ? { openFulfill: "1" } : {}),
|
||||
...(openFulfill && v.orderStatus === "PARTS_DELIVERED" ? { fulfillMode: "SPLIT" } : {}),
|
||||
},
|
||||
copyTextFallback(text, done) {
|
||||
const input = document.createElement("textarea");
|
||||
input.value = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(input);
|
||||
done();
|
||||
},
|
||||
clientTypeText(v) {
|
||||
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
|
||||
return map[v] || v || "-";
|
||||
},
|
||||
orderPromotionText(v) {
|
||||
const map = {
|
||||
NORMAL: "普通订单",
|
||||
PINTUAN: "拼团订单",
|
||||
GIFT: "赠品订单",
|
||||
POINTS: "积分订单",
|
||||
KANJIA: "砍价订单",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
},
|
||||
orderPromotionTagType(v) {
|
||||
const map = {
|
||||
NORMAL: "primary",
|
||||
PINTUAN: "danger",
|
||||
GIFT: "success",
|
||||
POINTS: "info",
|
||||
KANJIA: "warning",
|
||||
};
|
||||
return map[v] || "info";
|
||||
},
|
||||
paymentMethodText(v) {
|
||||
const map = {
|
||||
NOT_ACTUALLY_PAID: "-",
|
||||
WECHAT: "微信支付",
|
||||
ALIPAY: "支付宝",
|
||||
WALLET: "余额支付",
|
||||
BANK_TRANSFER: "线下转账",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
},
|
||||
orderStatusText(v) {
|
||||
const map = {
|
||||
UNPAID: "未付款",
|
||||
PAID: "已付款",
|
||||
UNDELIVERED: "待发货",
|
||||
STAY_PICKED_UP: "待自提",
|
||||
PARTS_DELIVERED: "部分发货",
|
||||
DELIVERED: "已发货",
|
||||
COMPLETED: "已完成",
|
||||
TAKE: "待核验",
|
||||
CANCELLED: "已关闭",
|
||||
};
|
||||
return map[v] || v || "-";
|
||||
},
|
||||
orderStatusTagType(v) {
|
||||
const map = {
|
||||
UNPAID: "danger",
|
||||
PAID: "primary",
|
||||
UNDELIVERED: "info",
|
||||
STAY_PICKED_UP: "info",
|
||||
PARTS_DELIVERED: "warning",
|
||||
DELIVERED: "warning",
|
||||
COMPLETED: "success",
|
||||
TAKE: "warning",
|
||||
CANCELLED: "danger",
|
||||
};
|
||||
return map[v] || "info";
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
this.getOrderNumData();
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
this.getOrderNumData();
|
||||
},
|
||||
selectDateRange(v) {
|
||||
if (v && v.length === 2) {
|
||||
this.searchForm.startDate = v[0];
|
||||
this.searchForm.endDate = v[1];
|
||||
} else {
|
||||
this.searchForm.startDate = "";
|
||||
this.searchForm.endDate = "";
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
API_Order.getOrderList(this.searchForm)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
detail(v, openFulfill = false) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "order-detail",
|
||||
query: {
|
||||
sn: v.sn,
|
||||
...(openFulfill ? { openFulfill: "1" } : {}),
|
||||
...(openFulfill && v.orderStatus === "PARTS_DELIVERED" ? { fulfillMode: "SPLIT" } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
function canShip(row) {
|
||||
if (!row) return false;
|
||||
if (row.allowOperationVO) {
|
||||
return canShowShipButton(row.allowOperationVO, row);
|
||||
}
|
||||
if (isNonShippableOrder(row)) return false;
|
||||
return row.orderStatus === "UNDELIVERED" || row.orderStatus === "PARTS_DELIVERED";
|
||||
}
|
||||
async function exportOrder() {
|
||||
if (!searchForm.startDate || !searchForm.endDate) {
|
||||
Message.error("必须选择时间范围,搜索后进行导出!");
|
||||
return;
|
||||
}
|
||||
API_Order.exportOrder(searchForm)
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], {
|
||||
type: "application/vnd.ms-excel;charset=utf-8",
|
||||
});
|
||||
},
|
||||
canShip(row) {
|
||||
if (!row) return false;
|
||||
if (row.allowOperationVO) {
|
||||
return canShowShipButton(row.allowOperationVO, row);
|
||||
}
|
||||
if (isNonShippableOrder(row)) return false;
|
||||
return row.orderStatus === "UNDELIVERED" || row.orderStatus === "PARTS_DELIVERED";
|
||||
},
|
||||
async exportOrder() {
|
||||
if (!this.searchForm.startDate || !this.searchForm.endDate) {
|
||||
this.$Message.error("必须选择时间范围,搜索后进行导出!");
|
||||
return;
|
||||
}
|
||||
API_Order.exportOrder(this.searchForm)
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], {
|
||||
type: "application/vnd.ms-excel;charset=utf-8",
|
||||
});
|
||||
if ("download" in document.createElement("a")) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "订单列表.xlsx";
|
||||
link.style.display = "none";
|
||||
link.href = URL.createObjectURL(blob);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, "订单列表.xlsx");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
orderStatusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
this.searchForm.orderStatus = "";
|
||||
this.currentStatus = "ALL";
|
||||
if ("download" in document.createElement("a")) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "订单列表.xlsx";
|
||||
link.style.display = "none";
|
||||
link.href = URL.createObjectURL(blob);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
this.searchForm.orderStatus = name;
|
||||
this.currentStatus = name;
|
||||
navigator.msSaveBlob(blob, "订单列表.xlsx");
|
||||
}
|
||||
this.getDataList();
|
||||
},
|
||||
getOrderNumData() {
|
||||
const { orderStatus, pageNumber, pageSize, sort, order, ...searchParams } = this.searchForm;
|
||||
API_Order.getOrderNum(searchParams)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.orderNumData = res.result;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("获取订单数量统计失败:", err);
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const orderStatus = this.$route.query.orderStatus;
|
||||
if (orderStatus) {
|
||||
this.currentStatus = orderStatus;
|
||||
this.searchForm.orderStatus = orderStatus;
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
function orderStatusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
searchForm.orderStatus = "";
|
||||
currentStatus.value = "ALL";
|
||||
} else {
|
||||
searchForm.orderStatus = name;
|
||||
currentStatus.value = name;
|
||||
}
|
||||
getDataList();
|
||||
}
|
||||
function getOrderNumData() {
|
||||
const { orderStatus, pageNumber, pageSize, sort, order, ...searchParams } = searchForm;
|
||||
API_Order.getOrderNum(searchParams)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
orderNumData.value = res.result;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("获取订单数量统计失败:", err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -113,135 +113,131 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import { confirmVerification, getVerificationRecords, previewVerification } from "@/api/order";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "orderVerification",
|
||||
data() {
|
||||
return {
|
||||
activeTab: "verify",
|
||||
code: "",
|
||||
lookingUp: false,
|
||||
submitting: false,
|
||||
confirmVisible: false,
|
||||
preview: {
|
||||
code: "",
|
||||
orderSn: "",
|
||||
goods: [],
|
||||
},
|
||||
loading: false,
|
||||
records: [],
|
||||
total: 0,
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
verifiedRange: null,
|
||||
recordQuery: {
|
||||
goodsName: "",
|
||||
verifierName: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
specText(specs) {
|
||||
if (!specs) return "";
|
||||
try {
|
||||
const obj = typeof specs === "string" ? JSON.parse(specs) : specs;
|
||||
if (!obj || typeof obj !== "object") return "";
|
||||
return Object.keys(obj)
|
||||
.filter((key) => key !== "images")
|
||||
.map((key) => `${key}:${obj[key]}`)
|
||||
.join(" ");
|
||||
} catch (e) {
|
||||
return "";
|
||||
defineOptions({ name: "orderVerification" });
|
||||
|
||||
const activeTab = ref("verify");
|
||||
const code = ref("");
|
||||
const lookingUp = ref(false);
|
||||
const submitting = ref(false);
|
||||
const confirmVisible = ref(false);
|
||||
const preview = ref({
|
||||
code: "",
|
||||
orderSn: "",
|
||||
goods: [],
|
||||
});
|
||||
const loading = ref(false);
|
||||
const records = ref([]);
|
||||
const total = ref(0);
|
||||
const pageNumber = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const verifiedRange = ref(null);
|
||||
const recordQuery = reactive({
|
||||
goodsName: "",
|
||||
verifierName: "",
|
||||
});
|
||||
|
||||
function specText(specs) {
|
||||
if (!specs) return "";
|
||||
try {
|
||||
const obj = typeof specs === "string" ? JSON.parse(specs) : specs;
|
||||
if (!obj || typeof obj !== "object") return "";
|
||||
return Object.keys(obj)
|
||||
.filter((key) => key !== "images")
|
||||
.map((key) => `${key}:${obj[key]}`)
|
||||
.join(" ");
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function confirmVerify() {
|
||||
const inputCode = (code.value || "").trim();
|
||||
if (!inputCode) {
|
||||
Message.warning("请输入核销码");
|
||||
return;
|
||||
}
|
||||
lookingUp.value = true;
|
||||
previewVerification(inputCode)
|
||||
.then((res) => {
|
||||
lookingUp.value = false;
|
||||
if (res.success && res.result) {
|
||||
preview.value = {
|
||||
code: res.result.code || inputCode,
|
||||
orderSn: res.result.orderSn || "",
|
||||
goods: res.result.goods || [],
|
||||
};
|
||||
confirmVisible.value = true;
|
||||
}
|
||||
},
|
||||
confirmVerify() {
|
||||
const code = (this.code || "").trim();
|
||||
if (!code) {
|
||||
this.$Message.warning("请输入核销码");
|
||||
return;
|
||||
})
|
||||
.catch(() => {
|
||||
lookingUp.value = false;
|
||||
});
|
||||
}
|
||||
function submitVerify() {
|
||||
const submitCode = (preview.value.code || "").trim();
|
||||
if (!submitCode) {
|
||||
Message.warning("请输入核销码");
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
confirmVerification(submitCode)
|
||||
.then((res) => {
|
||||
submitting.value = false;
|
||||
if (res.success) {
|
||||
Message.success(res.result || "核销成功");
|
||||
confirmVisible.value = false;
|
||||
code.value = "";
|
||||
preview.value = { code: "", orderSn: "", goods: [] };
|
||||
pageNumber.value = 1;
|
||||
loadRecords();
|
||||
}
|
||||
this.lookingUp = true;
|
||||
previewVerification(code)
|
||||
.then((res) => {
|
||||
this.lookingUp = false;
|
||||
if (res.success && res.result) {
|
||||
this.preview = {
|
||||
code: res.result.code || code,
|
||||
orderSn: res.result.orderSn || "",
|
||||
goods: res.result.goods || [],
|
||||
};
|
||||
this.confirmVisible = true;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.lookingUp = false;
|
||||
});
|
||||
},
|
||||
submitVerify() {
|
||||
const code = (this.preview.code || "").trim();
|
||||
if (!code) {
|
||||
this.$Message.warning("请输入核销码");
|
||||
return;
|
||||
})
|
||||
.catch(() => {
|
||||
submitting.value = false;
|
||||
});
|
||||
}
|
||||
function onTabChange(name) {
|
||||
if (name === "records") {
|
||||
loadRecords();
|
||||
}
|
||||
}
|
||||
function onSizeChange() {
|
||||
pageNumber.value = 1;
|
||||
loadRecords();
|
||||
}
|
||||
function searchRecords() {
|
||||
pageNumber.value = 1;
|
||||
loadRecords();
|
||||
}
|
||||
function loadRecords() {
|
||||
loading.value = true;
|
||||
const range = verifiedRange.value;
|
||||
const params = {
|
||||
pageNumber: pageNumber.value,
|
||||
pageSize: pageSize.value,
|
||||
goodsName: (recordQuery.goodsName || "").trim(),
|
||||
verifierName: (recordQuery.verifierName || "").trim(),
|
||||
};
|
||||
if (range && range.length === 2) {
|
||||
params.startTime = range[0];
|
||||
params.endTime = range[1];
|
||||
}
|
||||
getVerificationRecords(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
records.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
}
|
||||
this.submitting = true;
|
||||
confirmVerification(code)
|
||||
.then((res) => {
|
||||
this.submitting = false;
|
||||
if (res.success) {
|
||||
this.$Message.success(res.result || "核销成功");
|
||||
this.confirmVisible = false;
|
||||
this.code = "";
|
||||
this.preview = { code: "", orderSn: "", goods: [] };
|
||||
this.pageNumber = 1;
|
||||
this.loadRecords();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.submitting = false;
|
||||
});
|
||||
},
|
||||
onTabChange(name) {
|
||||
if (name === "records") {
|
||||
this.loadRecords();
|
||||
}
|
||||
},
|
||||
onSizeChange() {
|
||||
this.pageNumber = 1;
|
||||
this.loadRecords();
|
||||
},
|
||||
searchRecords() {
|
||||
this.pageNumber = 1;
|
||||
this.loadRecords();
|
||||
},
|
||||
loadRecords() {
|
||||
this.loading = true;
|
||||
const range = this.verifiedRange;
|
||||
const params = {
|
||||
pageNumber: this.pageNumber,
|
||||
pageSize: this.pageSize,
|
||||
goodsName: (this.recordQuery.goodsName || "").trim(),
|
||||
verifierName: (this.recordQuery.verifierName || "").trim(),
|
||||
};
|
||||
if (range && range.length === 2) {
|
||||
params.startTime = range[0];
|
||||
params.endTime = range[1];
|
||||
}
|
||||
getVerificationRecords(params)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.records = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
ref="table"
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="data"
|
||||
@@ -126,110 +126,113 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import * as API_Order from "@/api/order";
|
||||
export default {
|
||||
name: "fictitiousOrderList",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "",
|
||||
order: "",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
orderType: "VIRTUAL",
|
||||
orderSn: "",
|
||||
buyerName: "",
|
||||
orderStatus: "",
|
||||
},
|
||||
selectDate: null,
|
||||
data: [],
|
||||
total: 0,
|
||||
orderStatus: [
|
||||
{ title: "全部", value: "" },
|
||||
{ title: "未付款", value: "UNPAID" },
|
||||
{ title: "已付款", value: "PAID" },
|
||||
{ title: "待核验", value: "TAKE" },
|
||||
{ title: "已完成", value: "COMPLETED" },
|
||||
{ title: "已关闭", value: "CANCELLED" },
|
||||
],
|
||||
currentStatus: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
},
|
||||
changeSort(e) {
|
||||
this.searchForm.sort = e.prop;
|
||||
this.searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
|
||||
this.getDataList();
|
||||
},
|
||||
selectDateRange(v) {
|
||||
if (v) {
|
||||
this.searchForm.startDate = v[0];
|
||||
this.searchForm.endDate = v[1];
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
API_Order.getOrderList(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
defineOptions({ name: "fictitiousOrderList" });
|
||||
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "",
|
||||
order: "",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
orderType: "VIRTUAL",
|
||||
orderSn: "",
|
||||
buyerName: "",
|
||||
orderStatus: "",
|
||||
});
|
||||
const selectDate = ref(null);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const orderStatus = ref([
|
||||
{ title: "全部", value: "" },
|
||||
{ title: "未付款", value: "UNPAID" },
|
||||
{ title: "已付款", value: "PAID" },
|
||||
{ title: "待核验", value: "TAKE" },
|
||||
{ title: "已完成", value: "COMPLETED" },
|
||||
{ title: "已关闭", value: "CANCELLED" },
|
||||
]);
|
||||
const currentStatus = ref("");
|
||||
|
||||
// searchFormRef/tableRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
}
|
||||
function changeSort(e) {
|
||||
searchForm.sort = e.prop;
|
||||
searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
|
||||
getDataList();
|
||||
}
|
||||
function selectDateRange(v) {
|
||||
if (v) {
|
||||
searchForm.startDate = v[0];
|
||||
searchForm.endDate = v[1];
|
||||
}
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Order.getOrderList(searchForm).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
function confirmPrice(v) {
|
||||
Modal.confirm({
|
||||
title: "确认收款",
|
||||
content: "您确定要收款吗?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
API_Order.orderPay(v.sn).then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
Message.success("收款成功");
|
||||
getDataList();
|
||||
}
|
||||
Modal.remove();
|
||||
});
|
||||
},
|
||||
confirmPrice(v) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认收款",
|
||||
content: "您确定要收款吗?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
API_Order.orderPay(v.sn).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("收款成功");
|
||||
this.getDataList();
|
||||
}
|
||||
this.$Modal.remove();
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
detail(v) {
|
||||
let sn = v.sn;
|
||||
this.$filters.customRouterPush({
|
||||
name: "order-detail",
|
||||
query: { sn: sn, orderType: v.orderType },
|
||||
});
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
const item = tab.paneName;
|
||||
this.currentStatus = item;
|
||||
this.searchForm.orderStatus = item;
|
||||
this.getDataList();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
function detail(v) {
|
||||
let sn = v.sn;
|
||||
customRouterPush({
|
||||
name: "order-detail",
|
||||
query: { sn: sn, orderType: v.orderType },
|
||||
});
|
||||
}
|
||||
function onStatusTabClick(tab) {
|
||||
const item = tab.paneName;
|
||||
currentStatus.value = item;
|
||||
searchForm.orderStatus = item;
|
||||
getDataList();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form-item label="订单编号" prop="orderSn">
|
||||
<el-input v-model="searchForm.orderSn" clearable placeholder="请输入订单编号" style="width: 240px" />
|
||||
</el-form-item>
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<el-card>
|
||||
<div class="receipt-tip">订单状态为已发货/已完成可开票</div>
|
||||
<el-table ref="table" v-loading="loading" border :data="data" class="mt_10" style="width: 100%">
|
||||
<el-table ref="tableRef" v-loading="loading" border :data="data" class="mt_10" style="width: 100%">
|
||||
<el-table-column label="订单号" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="$router.push({ name: 'order-detail', query: { sn: row.orderSn } })">
|
||||
@@ -181,217 +181,219 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import * as API_Order from "@/api/order";
|
||||
import { uploadFile } from "@/libs/axios";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "receipt",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
receiptModalVisible: false,
|
||||
receiptDetailLoading: false,
|
||||
invoiceSubmitting: false,
|
||||
receiptModalMode: "detail",
|
||||
uploadFileUrl: uploadFile,
|
||||
accessToken: {},
|
||||
receiptUploadData: { directoryPath: "default" }, // OSS 上传目录(与后端存储配置一致)
|
||||
currentReceipt: {},
|
||||
selectedReceiptRow: null,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
receiptStatus: "",
|
||||
},
|
||||
data: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
orderStatusText(status) {
|
||||
const map = {
|
||||
UNPAID: "未付款",
|
||||
PAID: "已付款",
|
||||
UNDELIVERED: "待发货",
|
||||
DELIVERED: "已发货",
|
||||
COMPLETED: "已完成",
|
||||
TAKE: "待核验",
|
||||
CANCELLED: "已取消",
|
||||
};
|
||||
return map[status] || status;
|
||||
},
|
||||
orderStatusTagType(status) {
|
||||
const map = {
|
||||
UNPAID: "danger",
|
||||
PAID: "primary",
|
||||
UNDELIVERED: "info",
|
||||
DELIVERED: "info",
|
||||
COMPLETED: "success",
|
||||
TAKE: "warning",
|
||||
CANCELLED: "danger",
|
||||
};
|
||||
return map[status] || "info";
|
||||
},
|
||||
canInvoicing(row) {
|
||||
if (!row) return false;
|
||||
return (
|
||||
(row.orderStatus === "COMPLETED" || row.orderStatus === "DELIVERED") &&
|
||||
Number(row.receiptStatus) === 0
|
||||
);
|
||||
},
|
||||
initUploadAccessToken() {
|
||||
this.accessToken = { accessToken: this.getStore("accessToken") };
|
||||
},
|
||||
hasValue(value) {
|
||||
if (value === null || value === undefined) return false;
|
||||
return String(value).trim() !== "";
|
||||
},
|
||||
hasPrice(value) {
|
||||
return value !== null && value !== undefined && value !== "";
|
||||
},
|
||||
formatPrice(value) {
|
||||
if (value === null || value === undefined || value === "") return "暂无";
|
||||
return `¥${value}`;
|
||||
},
|
||||
isVatSpecialReceipt(receipt) {
|
||||
if (!receipt) return false;
|
||||
const receiptType = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
|
||||
return receiptType === "2" || receiptType === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL";
|
||||
},
|
||||
formatReceiptType(receipt) {
|
||||
if (!receipt || (!receipt.receiptType && !receipt.invoiceKind)) return "电子普通发票";
|
||||
const receiptType = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
|
||||
if (receiptType === "电子普通发票" || receiptType === "增值税专用发票") return receiptType;
|
||||
return this.isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
|
||||
},
|
||||
getInvoiceAddress(receipt) {
|
||||
if (!receipt) return "";
|
||||
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
|
||||
},
|
||||
buildInvoicingPayload() {
|
||||
const invoiceAddress = this.getInvoiceAddress(this.currentReceipt);
|
||||
return invoiceAddress ? { invoiceAddress } : {};
|
||||
},
|
||||
beforeInvoiceUpload(file) {
|
||||
const allowed = ["image/jpeg", "image/jpg", "image/png", "application/pdf"];
|
||||
const okType = allowed.includes(file.type) || /\.(jpg|jpeg|png|pdf)$/i.test(file.name);
|
||||
if (!okType) {
|
||||
this.$Message.warning("请上传 jpg、jpeg、png 或 pdf 格式文件");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
this.$Message.warning("发票附件不能超过 10MB");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleInvoiceUploadSuccess(res) {
|
||||
if (res && res.success && res.result) {
|
||||
this.currentReceipt.invoiceAddress = res.result;
|
||||
if (this.selectedReceiptRow) {
|
||||
this.selectedReceiptRow.invoiceAddress = res.result;
|
||||
}
|
||||
this.$Message.success("发票上传成功");
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || "发票上传失败");
|
||||
}
|
||||
},
|
||||
handleInvoiceUploadError() {
|
||||
this.$Message.error("发票上传失败");
|
||||
},
|
||||
viewInvoiceFile(url) {
|
||||
if (!url) return;
|
||||
window.open(url, "_blank");
|
||||
},
|
||||
init() {
|
||||
this.getData();
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getData();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.getData();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
receiptStatus: "",
|
||||
};
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
API_Order.getReceiptPage(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
},
|
||||
async openReceiptModal(row, mode = "detail") {
|
||||
if (!row) return;
|
||||
this.receiptModalMode = mode;
|
||||
this.selectedReceiptRow = row;
|
||||
this.currentReceipt = { ...row };
|
||||
this.receiptModalVisible = true;
|
||||
this.receiptDetailLoading = true;
|
||||
try {
|
||||
const res = await API_Order.getReceiptDetail(row.id);
|
||||
if (res.success && res.result) {
|
||||
this.currentReceipt = { ...row, ...res.result };
|
||||
} else {
|
||||
this.$Message.warning("发票详情获取失败,已展示列表中的发票信息");
|
||||
}
|
||||
} catch (e) {
|
||||
this.$Message.error("发票详情获取失败");
|
||||
} finally {
|
||||
this.receiptDetailLoading = false;
|
||||
}
|
||||
},
|
||||
async submitInvoicing() {
|
||||
if (!this.selectedReceiptRow) return;
|
||||
if (!this.canInvoicing(this.selectedReceiptRow)) {
|
||||
this.$Message.warning("当前订单状态不支持开票");
|
||||
return;
|
||||
}
|
||||
const params = this.buildInvoicingPayload();
|
||||
if (!params.invoiceAddress) {
|
||||
this.$Message.warning("请先上传发票");
|
||||
return;
|
||||
}
|
||||
this.invoiceSubmitting = true;
|
||||
try {
|
||||
const res = await API_Order.invoicing(this.selectedReceiptRow.id, params);
|
||||
if (res.success) {
|
||||
this.$Message.success("开票成功");
|
||||
this.receiptModalVisible = false;
|
||||
this.getData();
|
||||
}
|
||||
} catch (e) {
|
||||
this.$Message.error("开票失败");
|
||||
} finally {
|
||||
this.invoiceSubmitting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initUploadAccessToken();
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "receipt" });
|
||||
|
||||
const loading = ref(true);
|
||||
const receiptModalVisible = ref(false);
|
||||
const receiptDetailLoading = ref(false);
|
||||
const invoiceSubmitting = ref(false);
|
||||
const receiptModalMode = ref("detail");
|
||||
const uploadFileUrl = ref(uploadFile);
|
||||
const accessToken = ref({});
|
||||
const receiptUploadData = ref({ directoryPath: "default" }); // OSS 上传目录(与后端存储配置一致)
|
||||
const currentReceipt = ref({});
|
||||
const selectedReceiptRow = ref(null);
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
receiptStatus: "",
|
||||
});
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// searchFormRef / tableRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
|
||||
function orderStatusText(status) {
|
||||
const map = {
|
||||
UNPAID: "未付款",
|
||||
PAID: "已付款",
|
||||
UNDELIVERED: "待发货",
|
||||
DELIVERED: "已发货",
|
||||
COMPLETED: "已完成",
|
||||
TAKE: "待核验",
|
||||
CANCELLED: "已取消",
|
||||
};
|
||||
return map[status] || status;
|
||||
}
|
||||
function orderStatusTagType(status) {
|
||||
const map = {
|
||||
UNPAID: "danger",
|
||||
PAID: "primary",
|
||||
UNDELIVERED: "info",
|
||||
DELIVERED: "info",
|
||||
COMPLETED: "success",
|
||||
TAKE: "warning",
|
||||
CANCELLED: "danger",
|
||||
};
|
||||
return map[status] || "info";
|
||||
}
|
||||
function canInvoicing(row) {
|
||||
if (!row) return false;
|
||||
return (
|
||||
(row.orderStatus === "COMPLETED" || row.orderStatus === "DELIVERED") &&
|
||||
Number(row.receiptStatus) === 0
|
||||
);
|
||||
}
|
||||
function initUploadAccessToken() {
|
||||
accessToken.value = { accessToken: getStore("accessToken") };
|
||||
}
|
||||
function hasValue(value) {
|
||||
if (value === null || value === undefined) return false;
|
||||
return String(value).trim() !== "";
|
||||
}
|
||||
function hasPrice(value) {
|
||||
return value !== null && value !== undefined && value !== "";
|
||||
}
|
||||
function formatPrice(value) {
|
||||
if (value === null || value === undefined || value === "") return "暂无";
|
||||
return `¥${value}`;
|
||||
}
|
||||
function isVatSpecialReceipt(receipt) {
|
||||
if (!receipt) return false;
|
||||
const receiptType = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
|
||||
return receiptType === "2" || receiptType === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL";
|
||||
}
|
||||
function formatReceiptType(receipt) {
|
||||
if (!receipt || (!receipt.receiptType && !receipt.invoiceKind)) return "电子普通发票";
|
||||
const receiptType = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
|
||||
if (receiptType === "电子普通发票" || receiptType === "增值税专用发票") return receiptType;
|
||||
return isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
|
||||
}
|
||||
function getInvoiceAddress(receipt) {
|
||||
if (!receipt) return "";
|
||||
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
|
||||
}
|
||||
function buildInvoicingPayload() {
|
||||
const invoiceAddress = getInvoiceAddress(currentReceipt.value);
|
||||
return invoiceAddress ? { invoiceAddress } : {};
|
||||
}
|
||||
function beforeInvoiceUpload(file) {
|
||||
const allowed = ["image/jpeg", "image/jpg", "image/png", "application/pdf"];
|
||||
const okType = allowed.includes(file.type) || /\.(jpg|jpeg|png|pdf)$/i.test(file.name);
|
||||
if (!okType) {
|
||||
Message.warning("请上传 jpg、jpeg、png 或 pdf 格式文件");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
Message.warning("发票附件不能超过 10MB");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleInvoiceUploadSuccess(res) {
|
||||
if (res && res.success && res.result) {
|
||||
currentReceipt.value.invoiceAddress = res.result;
|
||||
if (selectedReceiptRow.value) {
|
||||
selectedReceiptRow.value.invoiceAddress = res.result;
|
||||
}
|
||||
Message.success("发票上传成功");
|
||||
} else {
|
||||
Message.error((res && res.message) || "发票上传失败");
|
||||
}
|
||||
}
|
||||
function handleInvoiceUploadError() {
|
||||
Message.error("发票上传失败");
|
||||
}
|
||||
function viewInvoiceFile(url) {
|
||||
if (!url) return;
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
function init() {
|
||||
getData();
|
||||
}
|
||||
function changePage(v) {
|
||||
searchForm.value.pageNumber = v;
|
||||
getData();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
searchForm.value.pageSize = v;
|
||||
getData();
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getData();
|
||||
}
|
||||
function handleReset() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
receiptStatus: "",
|
||||
};
|
||||
getData();
|
||||
}
|
||||
function getData() {
|
||||
loading.value = true;
|
||||
API_Order.getReceiptPage(searchForm.value).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
async function openReceiptModal(row, mode = "detail") {
|
||||
if (!row) return;
|
||||
receiptModalMode.value = mode;
|
||||
selectedReceiptRow.value = row;
|
||||
currentReceipt.value = { ...row };
|
||||
receiptModalVisible.value = true;
|
||||
receiptDetailLoading.value = true;
|
||||
try {
|
||||
const res = await API_Order.getReceiptDetail(row.id);
|
||||
if (res.success && res.result) {
|
||||
currentReceipt.value = { ...row, ...res.result };
|
||||
} else {
|
||||
Message.warning("发票详情获取失败,已展示列表中的发票信息");
|
||||
}
|
||||
} catch (e) {
|
||||
Message.error("发票详情获取失败");
|
||||
} finally {
|
||||
receiptDetailLoading.value = false;
|
||||
}
|
||||
}
|
||||
async function submitInvoicing() {
|
||||
if (!selectedReceiptRow.value) return;
|
||||
if (!canInvoicing(selectedReceiptRow.value)) {
|
||||
Message.warning("当前订单状态不支持开票");
|
||||
return;
|
||||
}
|
||||
const params = buildInvoicingPayload();
|
||||
if (!params.invoiceAddress) {
|
||||
Message.warning("请先上传发票");
|
||||
return;
|
||||
}
|
||||
invoiceSubmitting.value = true;
|
||||
try {
|
||||
const res = await API_Order.invoicing(selectedReceiptRow.value.id, params);
|
||||
if (res.success) {
|
||||
Message.success("开票成功");
|
||||
receiptModalVisible.value = false;
|
||||
getData();
|
||||
}
|
||||
} catch (e) {
|
||||
Message.error("开票失败");
|
||||
} finally {
|
||||
invoiceSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initUploadAccessToken();
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "@/styles/table-common.scss";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="80px">
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
@@ -86,7 +86,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import {
|
||||
getDamageReportPage,
|
||||
@@ -103,193 +104,204 @@ import {
|
||||
damageStatusTag,
|
||||
formatMoney,
|
||||
} from "../constants";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-damage-report-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
dateRange: [],
|
||||
currentStatus: "ALL",
|
||||
statusCountData: {},
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusTabs() {
|
||||
const tabs = [{ title: "全部", value: "ALL" }];
|
||||
Object.keys(DAMAGE_STATUS).forEach((key) => {
|
||||
const count = this.statusCountData[key];
|
||||
const label = DAMAGE_STATUS[key];
|
||||
tabs.push({
|
||||
title: count ? `${label}(${count})` : label,
|
||||
value: key,
|
||||
});
|
||||
});
|
||||
return tabs;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
damageStatusText,
|
||||
damageStatusTag,
|
||||
formatMoney,
|
||||
init() {
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
buildSearchParams(includeStatus = true) {
|
||||
const params = { ...this.searchForm };
|
||||
if (!includeStatus) {
|
||||
delete params.status;
|
||||
defineOptions({ name: "procurement-damage-report-list" });
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
const loading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const dateRange = ref([]);
|
||||
const currentStatus = ref("ALL");
|
||||
const statusCountData = ref({});
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: "",
|
||||
});
|
||||
|
||||
const statusTabs = computed(() => {
|
||||
const tabs = [{ title: "全部", value: "ALL" }];
|
||||
Object.keys(DAMAGE_STATUS).forEach((key) => {
|
||||
const count = statusCountData.value[key];
|
||||
const label = DAMAGE_STATUS[key];
|
||||
tabs.push({
|
||||
title: count ? `${label}(${count})` : label,
|
||||
value: key,
|
||||
});
|
||||
});
|
||||
return tabs;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function buildSearchParams(includeStatus = true) {
|
||||
const params = { ...searchForm.value };
|
||||
if (!includeStatus) {
|
||||
delete params.status;
|
||||
}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
params.startDate = dateRange.value[0];
|
||||
params.endDate = dateRange.value[1];
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
searchForm.value = { pageNumber: 1, pageSize: 10, status: "" };
|
||||
dateRange.value = [];
|
||||
currentStatus.value = "ALL";
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function onStatusTabClick(tab) {
|
||||
statusClick(tab.paneName);
|
||||
}
|
||||
|
||||
function statusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
searchForm.value.status = "";
|
||||
currentStatus.value = "ALL";
|
||||
} else {
|
||||
searchForm.value.status = name;
|
||||
currentStatus.value = name;
|
||||
}
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getStatusCount() {
|
||||
getDamageReportStatusCount(buildSearchParams(false)).then((res) => {
|
||||
if (res.success) {
|
||||
statusCountData.value = res.result || {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getDamageReportPage(buildSearchParams(true))
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
}
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
params.startDate = this.dateRange[0];
|
||||
params.endDate = this.dateRange[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = { pageNumber: 1, pageSize: 10, status: "" };
|
||||
this.dateRange = [];
|
||||
this.currentStatus = "ALL";
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.statusClick(tab.paneName);
|
||||
},
|
||||
statusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
this.searchForm.status = "";
|
||||
this.currentStatus = "ALL";
|
||||
} else {
|
||||
this.searchForm.status = name;
|
||||
this.currentStatus = name;
|
||||
}
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getStatusCount() {
|
||||
getDamageReportStatusCount(this.buildSearchParams(false)).then((res) => {
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function refreshList() {
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function goCreate() {
|
||||
customRouterPush({ name: "procurement-damage-report-operation" });
|
||||
}
|
||||
|
||||
function goDetail(row) {
|
||||
customRouterPush({
|
||||
name: "procurement-damage-report-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(row) {
|
||||
Modal.confirm({
|
||||
title: "提交报损单",
|
||||
content: "确认提交该报损单?",
|
||||
onOk: () => {
|
||||
submitDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.statusCountData = res.result || {};
|
||||
Message.success("提交成功");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getDamageReportPage(this.buildSearchParams(true))
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
refreshList() {
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
goCreate() {
|
||||
this.$filters.customRouterPush({ name: "procurement-damage-report-operation" });
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-damage-report-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
function handleApprove(row) {
|
||||
Modal.confirm({
|
||||
title: "审核通过",
|
||||
content: "确认通过该报损单?",
|
||||
onOk: () => {
|
||||
approveDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("审核通过");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleSubmit(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "提交报损单",
|
||||
content: "确认提交该报损单?",
|
||||
onOk: () => {
|
||||
submitDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("提交成功");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleReject(row) {
|
||||
ElMessageBox.prompt("请输入驳回原因", "驳回报损单", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
inputPlaceholder: "驳回原因",
|
||||
})
|
||||
.then(({ value }) => {
|
||||
rejectDamageReport(row.id, value || "驳回").then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("已驳回");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function handleCancel(row) {
|
||||
Modal.confirm({
|
||||
title: "作废报损单",
|
||||
content: "确认作废该报损单?",
|
||||
onOk: () => {
|
||||
cancelDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("已作废");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleApprove(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "审核通过",
|
||||
content: "确认通过该报损单?",
|
||||
onOk: () => {
|
||||
approveDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("审核通过");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleComplete(row) {
|
||||
Modal.confirm({
|
||||
title: "完成报损",
|
||||
content: "完成后将扣减库存,是否继续?",
|
||||
onOk: () => {
|
||||
completeDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("报损完成,库存已扣减");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleReject(row) {
|
||||
ElMessageBox.prompt("请输入驳回原因", "驳回报损单", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
inputPlaceholder: "驳回原因",
|
||||
})
|
||||
.then(({ value }) => {
|
||||
rejectDamageReport(row.id, value || "驳回").then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("已驳回");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
handleCancel(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "作废报损单",
|
||||
content: "确认作废该报损单?",
|
||||
onOk: () => {
|
||||
cancelDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("已作废");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleComplete(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "完成报损",
|
||||
content: "完成后将扣减库存,是否继续?",
|
||||
onOk: () => {
|
||||
completeDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("报损完成,库存已扣减");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -149,11 +149,13 @@
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="onGoodsSelected" />
|
||||
<liliDialog ref="liliDialogRef" @selectedGoodsData="onGoodsSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||
import {
|
||||
@@ -164,179 +166,170 @@ import {
|
||||
submitDamageReport,
|
||||
} from "@/api/procurement";
|
||||
import { damageStatusText, damageStatusTag, formatMoney } from "../constants";
|
||||
import { Message } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-damage-report-operation",
|
||||
components: { liliDialog, uploadPicThumb },
|
||||
data() {
|
||||
return {
|
||||
readonly: false,
|
||||
submitLoading: false,
|
||||
form: {
|
||||
damageDate: "",
|
||||
damageReasonId: "",
|
||||
remark: "",
|
||||
evidence: [],
|
||||
},
|
||||
items: [],
|
||||
detail: {},
|
||||
reasonOptions: [],
|
||||
evidenceImageList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.readonly ? "报损单详情" : "新建报损单";
|
||||
},
|
||||
reasonText() {
|
||||
const reason = this.reasonOptions.find((item) => item.id === this.detail.damageReasonId);
|
||||
return reason ? reason.reason : "-";
|
||||
},
|
||||
totalQuantity() {
|
||||
return this.items.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0);
|
||||
},
|
||||
totalAmount() {
|
||||
return this.items.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.readonly = this.$route.query.readonly === "1" || !!this.$route.query.id;
|
||||
this.loadReasons();
|
||||
if (this.$route.query.id) {
|
||||
this.loadDetail(this.$route.query.id);
|
||||
defineOptions({ name: "procurement-damage-report-operation" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const readonly = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const form = reactive({
|
||||
damageDate: "",
|
||||
damageReasonId: "",
|
||||
remark: "",
|
||||
evidence: [],
|
||||
});
|
||||
const items = ref([]);
|
||||
const detail = ref({});
|
||||
const reasonOptions = ref([]);
|
||||
const evidenceImageList = ref([]);
|
||||
const liliDialogRef = ref(null);
|
||||
|
||||
const pageTitle = computed(() => (readonly.value ? "报损单详情" : "新建报损单"));
|
||||
const reasonText = computed(() => {
|
||||
const reason = reasonOptions.value.find((item) => item.id === detail.value.damageReasonId);
|
||||
return reason ? reason.reason : "-";
|
||||
});
|
||||
const totalQuantity = computed(() =>
|
||||
items.value.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0)
|
||||
);
|
||||
const totalAmount = computed(() =>
|
||||
items.value.reduce((sum, item) => sum + (Number(item.amount) || 0), 0)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
readonly.value = route.query.readonly === "1" || !!route.query.id;
|
||||
loadReasons();
|
||||
if (route.query.id) {
|
||||
loadDetail(route.query.id);
|
||||
}
|
||||
});
|
||||
|
||||
function loadReasons() {
|
||||
getStockReasonList({ category: "DAMAGE", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||
if (res.success) {
|
||||
reasonOptions.value = res.result.records || [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
damageStatusText,
|
||||
damageStatusTag,
|
||||
formatMoney,
|
||||
loadReasons() {
|
||||
getStockReasonList({ category: "DAMAGE", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||
if (res.success) {
|
||||
this.reasonOptions = res.result.records || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
recalcRow(row) {
|
||||
row.amount = (Number(row.unitPrice) || 0) * (Number(row.quantity) || 0);
|
||||
},
|
||||
onQuantityBlur(row) {
|
||||
const val = parseInt(row.quantity, 10);
|
||||
row.quantity = Number.isNaN(val) || val < 1 ? 1 : val;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
onUnitPriceBlur(row) {
|
||||
const val = parseFloat(row.unitPrice);
|
||||
row.unitPrice = Number.isNaN(val) || val < 0 ? 0 : Math.round(val * 100) / 100;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
loadDetail(id) {
|
||||
getDamageReportDetail(id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.detail = res.result;
|
||||
this.form.damageDate = res.result.damageDate;
|
||||
this.form.damageReasonId = res.result.damageReasonId;
|
||||
this.form.remark = res.result.remark;
|
||||
this.form.evidence = res.result.evidence;
|
||||
this.evidenceImageList = this.parseEvidenceImages(res.result.evidence);
|
||||
}
|
||||
});
|
||||
getDamageReportItems(id).then((res) => {
|
||||
if (res.success) {
|
||||
this.items = res.result || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
parseEvidenceImages(val) {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val.filter(Boolean);
|
||||
return String(val)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
openSkuPicker() {
|
||||
this.$refs.liliDialog.goodsData = this.items.map((item) => ({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
goodsName: item.goodsName,
|
||||
price: item.unitPrice,
|
||||
cost: item.unitPrice,
|
||||
}));
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
onGoodsSelected(selected) {
|
||||
const map = new Map(this.items.map((item) => [item.skuId, item]));
|
||||
this.items = selected.map((sku) => {
|
||||
const existed = map.get(sku.id);
|
||||
if (existed) return existed;
|
||||
const unitPrice = sku.cost || sku.price || 0;
|
||||
return {
|
||||
goodsId: sku.goodsId,
|
||||
skuId: sku.id,
|
||||
goodsName: sku.goodsName,
|
||||
quantity: 1,
|
||||
unitPrice,
|
||||
amount: unitPrice,
|
||||
};
|
||||
});
|
||||
},
|
||||
removeItem(index) {
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
validateForm() {
|
||||
if (!this.form.damageReasonId) {
|
||||
this.$Message.warning("请选择报损原因");
|
||||
return false;
|
||||
}
|
||||
if (!this.items.length) {
|
||||
this.$Message.warning("请添加报损商品");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSave(andSubmit) {
|
||||
if (!this.validateForm()) return;
|
||||
this.submitLoading = true;
|
||||
const payload = {
|
||||
damageDate: this.form.damageDate || undefined,
|
||||
damageReasonId: this.form.damageReasonId,
|
||||
remark: this.form.remark,
|
||||
evidence: Array.isArray(this.form.evidence)
|
||||
? this.form.evidence.join(",")
|
||||
: this.form.evidence || "",
|
||||
items: this.items.map((item) => ({
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitPrice,
|
||||
})),
|
||||
};
|
||||
createDamageReport(payload)
|
||||
.then((res) => {
|
||||
if (!res.success) return;
|
||||
const reportId = res.result.id;
|
||||
if (andSubmit) {
|
||||
return submitDamageReport(reportId).then((subRes) => {
|
||||
if (subRes.success) {
|
||||
this.$Message.success("保存并提交成功");
|
||||
this.goBack();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function recalcRow(row) {
|
||||
row.amount = (Number(row.unitPrice) || 0) * (Number(row.quantity) || 0);
|
||||
}
|
||||
function onQuantityBlur(row) {
|
||||
const val = parseInt(row.quantity, 10);
|
||||
row.quantity = Number.isNaN(val) || val < 1 ? 1 : val;
|
||||
recalcRow(row);
|
||||
}
|
||||
function onUnitPriceBlur(row) {
|
||||
const val = parseFloat(row.unitPrice);
|
||||
row.unitPrice = Number.isNaN(val) || val < 0 ? 0 : Math.round(val * 100) / 100;
|
||||
recalcRow(row);
|
||||
}
|
||||
function loadDetail(id) {
|
||||
getDamageReportDetail(id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
detail.value = res.result;
|
||||
form.damageDate = res.result.damageDate;
|
||||
form.damageReasonId = res.result.damageReasonId;
|
||||
form.remark = res.result.remark;
|
||||
form.evidence = res.result.evidence;
|
||||
evidenceImageList.value = parseEvidenceImages(res.result.evidence);
|
||||
}
|
||||
});
|
||||
getDamageReportItems(id).then((res) => {
|
||||
if (res.success) {
|
||||
items.value = res.result || [];
|
||||
}
|
||||
});
|
||||
}
|
||||
function parseEvidenceImages(val) {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val.filter(Boolean);
|
||||
return String(val)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
function openSkuPicker() {
|
||||
liliDialogRef.value.goodsData = items.value.map((item) => ({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
goodsName: item.goodsName,
|
||||
price: item.unitPrice,
|
||||
cost: item.unitPrice,
|
||||
}));
|
||||
liliDialogRef.value.open("goods");
|
||||
}
|
||||
function onGoodsSelected(selected) {
|
||||
const map = new Map(items.value.map((item) => [item.skuId, item]));
|
||||
items.value = selected.map((sku) => {
|
||||
const existed = map.get(sku.id);
|
||||
if (existed) return existed;
|
||||
const unitPrice = sku.cost || sku.price || 0;
|
||||
return {
|
||||
goodsId: sku.goodsId,
|
||||
skuId: sku.id,
|
||||
goodsName: sku.goodsName,
|
||||
quantity: 1,
|
||||
unitPrice,
|
||||
amount: unitPrice,
|
||||
};
|
||||
});
|
||||
}
|
||||
function removeItem(index) {
|
||||
items.value.splice(index, 1);
|
||||
}
|
||||
function validateForm() {
|
||||
if (!form.damageReasonId) {
|
||||
Message.warning("请选择报损原因");
|
||||
return false;
|
||||
}
|
||||
if (!items.value.length) {
|
||||
Message.warning("请添加报损商品");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleSave(andSubmit) {
|
||||
if (!validateForm()) return;
|
||||
submitLoading.value = true;
|
||||
const payload = {
|
||||
damageDate: form.damageDate || undefined,
|
||||
damageReasonId: form.damageReasonId,
|
||||
remark: form.remark,
|
||||
evidence: Array.isArray(form.evidence) ? form.evidence.join(",") : form.evidence || "",
|
||||
items: items.value.map((item) => ({
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitPrice,
|
||||
})),
|
||||
};
|
||||
createDamageReport(payload)
|
||||
.then((res) => {
|
||||
if (!res.success) return;
|
||||
const reportId = res.result.id;
|
||||
if (andSubmit) {
|
||||
return submitDamageReport(reportId).then((subRes) => {
|
||||
if (subRes.success) {
|
||||
Message.success("保存并提交成功");
|
||||
goBack();
|
||||
}
|
||||
this.$Message.success("保存成功");
|
||||
this.goBack();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementDamageReportList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Message.success("保存成功");
|
||||
goBack();
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
function goBack() {
|
||||
customRouterPush({ name: "procurementDamageReportList" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="80px">
|
||||
<el-form-item label="入库单号">
|
||||
<el-input v-model="searchForm.inboundSn" placeholder="入库单号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
@@ -48,62 +48,65 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { getProcurementInboundPage } from "@/api/procurement";
|
||||
import { formatMoney } from "../constants";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-inbound-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
inboundSn: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
formatMoney,
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm.inboundSn = "";
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getProcurementInboundPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
goCreate() {
|
||||
this.$filters.customRouterPush({ name: "procurement-inbound-operation" });
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-inbound-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "procurement-inbound-list" });
|
||||
|
||||
const loading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
inboundSn: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
});
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
searchForm.inboundSn = "";
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getProcurementInboundPage(searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function goCreate() {
|
||||
customRouterPush({ name: "procurement-inbound-operation" });
|
||||
}
|
||||
|
||||
function goDetail(row) {
|
||||
customRouterPush({
|
||||
name: "procurement-inbound-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -100,7 +100,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||
import {
|
||||
getProcurementOrderPage,
|
||||
@@ -110,153 +112,146 @@ import {
|
||||
getProcurementInboundItems,
|
||||
} from "@/api/procurement";
|
||||
import { procurementStatusText, formatMoney } from "../constants";
|
||||
import { Message } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-inbound-operation",
|
||||
components: { uploadPicThumb },
|
||||
data() {
|
||||
return {
|
||||
readonly: false,
|
||||
submitLoading: false,
|
||||
form: {
|
||||
procurementOrderId: "",
|
||||
certificateImages: [],
|
||||
},
|
||||
orderOptions: [],
|
||||
items: [],
|
||||
detail: {},
|
||||
inboundItems: [],
|
||||
certificateImageList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.readonly ? "入库单详情" : "新建入库单";
|
||||
},
|
||||
totalInboundQuantity() {
|
||||
return (this.inboundItems || []).reduce(
|
||||
(sum, item) => sum + (Number(item.inboundQuantity) || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.readonly = this.$route.query.readonly === "1";
|
||||
if (this.$route.query.id) {
|
||||
this.loadInboundDetail(this.$route.query.id);
|
||||
} else {
|
||||
this.loadPendingOrders();
|
||||
if (this.$route.query.procurementOrderId) {
|
||||
this.form.procurementOrderId = this.$route.query.procurementOrderId;
|
||||
this.onOrderChange(this.$route.query.procurementOrderId);
|
||||
}
|
||||
defineOptions({ name: "procurement-inbound-operation" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const readonly = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const form = reactive({
|
||||
procurementOrderId: "",
|
||||
certificateImages: [],
|
||||
});
|
||||
const orderOptions = ref([]);
|
||||
const items = ref([]);
|
||||
const detail = ref({});
|
||||
const inboundItems = ref([]);
|
||||
const certificateImageList = ref([]);
|
||||
|
||||
const pageTitle = computed(() => (readonly.value ? "入库单详情" : "新建入库单"));
|
||||
const totalInboundQuantity = computed(() =>
|
||||
(inboundItems.value || []).reduce(
|
||||
(sum, item) => sum + (Number(item.inboundQuantity) || 0),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
readonly.value = route.query.readonly === "1";
|
||||
if (route.query.id) {
|
||||
loadInboundDetail(route.query.id);
|
||||
} else {
|
||||
loadPendingOrders();
|
||||
if (route.query.procurementOrderId) {
|
||||
form.procurementOrderId = route.query.procurementOrderId;
|
||||
onOrderChange(route.query.procurementOrderId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
procurementStatusText,
|
||||
formatMoney,
|
||||
remainQty(row) {
|
||||
return (row.quantity || 0) - (row.receivedQuantity || 0);
|
||||
},
|
||||
onInboundQtyBlur(row) {
|
||||
const max = this.remainQty(row);
|
||||
const val = parseInt(row.inboundQuantity, 10);
|
||||
if (Number.isNaN(val) || val < 0) {
|
||||
row.inboundQuantity = 0;
|
||||
} else if (val > max) {
|
||||
row.inboundQuantity = max;
|
||||
} else {
|
||||
row.inboundQuantity = val;
|
||||
}
|
||||
});
|
||||
|
||||
function remainQty(row) {
|
||||
return (row.quantity || 0) - (row.receivedQuantity || 0);
|
||||
}
|
||||
function onInboundQtyBlur(row) {
|
||||
const max = remainQty(row);
|
||||
const val = parseInt(row.inboundQuantity, 10);
|
||||
if (Number.isNaN(val) || val < 0) {
|
||||
row.inboundQuantity = 0;
|
||||
} else if (val > max) {
|
||||
row.inboundQuantity = max;
|
||||
} else {
|
||||
row.inboundQuantity = val;
|
||||
}
|
||||
}
|
||||
function loadPendingOrders() {
|
||||
getProcurementOrderPage({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
status: "",
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
orderOptions.value = (res.result.records || []).filter((o) =>
|
||||
["PENDING_INBOUND", "PARTIAL_INBOUND"].includes(o.status)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
function onOrderChange(orderId) {
|
||||
if (!orderId) {
|
||||
items.value = [];
|
||||
return;
|
||||
}
|
||||
getProcurementOrderDetail(orderId).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
items.value = (res.result.items || [])
|
||||
.filter((item) => remainQty(item) > 0)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
procurementOrderItemId: item.id,
|
||||
inboundQuantity: remainQty(item),
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
function loadInboundDetail(id) {
|
||||
getProcurementInboundDetail(id).then((res) => {
|
||||
if (res.success) {
|
||||
detail.value = res.result || {};
|
||||
certificateImageList.value = parseCertificateImages(detail.value.certificateImages);
|
||||
}
|
||||
});
|
||||
getProcurementInboundItems(id).then((res) => {
|
||||
if (res.success) inboundItems.value = res.result || [];
|
||||
});
|
||||
}
|
||||
function parseCertificateImages(val) {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val.filter(Boolean);
|
||||
return String(val)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
function handleSubmit() {
|
||||
if (!form.procurementOrderId) {
|
||||
Message.warning("请选择采购单");
|
||||
return;
|
||||
}
|
||||
const validItems = items.value.filter((item) => item.inboundQuantity > 0);
|
||||
if (!validItems.length) {
|
||||
Message.warning("请填写本次入库数量");
|
||||
return;
|
||||
}
|
||||
submitLoading.value = true;
|
||||
createProcurementInbound({
|
||||
procurementOrderId: form.procurementOrderId,
|
||||
certificateImages: Array.isArray(form.certificateImages)
|
||||
? form.certificateImages.join(",")
|
||||
: form.certificateImages || "",
|
||||
items: validItems.map((item) => ({
|
||||
procurementOrderItemId: item.procurementOrderItemId || item.id,
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
inboundQuantity: item.inboundQuantity,
|
||||
})),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("入库成功");
|
||||
goBack();
|
||||
}
|
||||
},
|
||||
loadPendingOrders() {
|
||||
getProcurementOrderPage({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
status: "",
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
this.orderOptions = (res.result.records || []).filter((o) =>
|
||||
["PENDING_INBOUND", "PARTIAL_INBOUND"].includes(o.status)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
onOrderChange(orderId) {
|
||||
if (!orderId) {
|
||||
this.items = [];
|
||||
return;
|
||||
}
|
||||
getProcurementOrderDetail(orderId).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.items = (res.result.items || [])
|
||||
.filter((item) => this.remainQty(item) > 0)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
procurementOrderItemId: item.id,
|
||||
inboundQuantity: this.remainQty(item),
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
loadInboundDetail(id) {
|
||||
getProcurementInboundDetail(id).then((res) => {
|
||||
if (res.success) {
|
||||
this.detail = res.result || {};
|
||||
this.certificateImageList = this.parseCertificateImages(this.detail.certificateImages);
|
||||
}
|
||||
});
|
||||
getProcurementInboundItems(id).then((res) => {
|
||||
if (res.success) this.inboundItems = res.result || [];
|
||||
});
|
||||
},
|
||||
parseCertificateImages(val) {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val.filter(Boolean);
|
||||
return String(val)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
handleSubmit() {
|
||||
if (!this.form.procurementOrderId) {
|
||||
this.$Message.warning("请选择采购单");
|
||||
return;
|
||||
}
|
||||
const validItems = this.items.filter((item) => item.inboundQuantity > 0);
|
||||
if (!validItems.length) {
|
||||
this.$Message.warning("请填写本次入库数量");
|
||||
return;
|
||||
}
|
||||
this.submitLoading = true;
|
||||
createProcurementInbound({
|
||||
procurementOrderId: this.form.procurementOrderId,
|
||||
certificateImages: Array.isArray(this.form.certificateImages)
|
||||
? this.form.certificateImages.join(",")
|
||||
: this.form.certificateImages || "",
|
||||
items: validItems.map((item) => ({
|
||||
procurementOrderItemId: item.procurementOrderItemId || item.id,
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
inboundQuantity: item.inboundQuantity,
|
||||
})),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("入库成功");
|
||||
this.goBack();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementInboundList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
function goBack() {
|
||||
customRouterPush({ name: "procurementInboundList" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -68,7 +68,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
getInventoryCountDetail,
|
||||
getInventoryCountItemsPage,
|
||||
@@ -76,83 +78,80 @@ import {
|
||||
} from "@/api/procurement";
|
||||
import { MARKET_ENABLE } from "../constants";
|
||||
import { downloadBlob } from "@/utils/downloadBlob";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-inventory-count-detail",
|
||||
data() {
|
||||
return {
|
||||
MARKET_ENABLE,
|
||||
loading: false,
|
||||
exporting: false,
|
||||
detail: {},
|
||||
data: [],
|
||||
total: 0,
|
||||
sn: "",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
goodsName: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.sn = this.$route.query.sn || "";
|
||||
const id = this.$route.query.id;
|
||||
if (id) {
|
||||
getInventoryCountDetail(id).then((res) => {
|
||||
if (res.success) {
|
||||
this.detail = res.result || {};
|
||||
this.sn = this.detail.sn || this.sn;
|
||||
}
|
||||
});
|
||||
this.getDataList();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDataList() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
this.loading = true;
|
||||
getInventoryCountItemsPage(id, this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
goodsName: "",
|
||||
};
|
||||
this.getDataList();
|
||||
},
|
||||
handleExport() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
this.exporting = true;
|
||||
exportInventoryCountItems(id)
|
||||
.then((blob) => {
|
||||
downloadBlob(blob, `盘点单_${this.sn || id}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
this.exporting = false;
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementInventoryCountList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "procurement-inventory-count-detail" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(false);
|
||||
const exporting = ref(false);
|
||||
const detail = ref({});
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const sn = ref("");
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
goodsName: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
sn.value = route.query.sn || "";
|
||||
const id = route.query.id;
|
||||
if (id) {
|
||||
getInventoryCountDetail(id).then((res) => {
|
||||
if (res.success) {
|
||||
detail.value = res.result || {};
|
||||
sn.value = detail.value.sn || sn.value;
|
||||
}
|
||||
});
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
|
||||
function getDataList() {
|
||||
const id = route.query.id;
|
||||
if (!id) return;
|
||||
loading.value = true;
|
||||
getInventoryCountItemsPage(id, searchForm.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function handleReset() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
goodsName: "",
|
||||
};
|
||||
getDataList();
|
||||
}
|
||||
function handleExport() {
|
||||
const id = route.query.id;
|
||||
if (!id) return;
|
||||
exporting.value = true;
|
||||
exportInventoryCountItems(id)
|
||||
.then((blob) => {
|
||||
downloadBlob(blob, `盘点单_${sn.value || id}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
exporting.value = false;
|
||||
});
|
||||
}
|
||||
function goBack() {
|
||||
customRouterPush({ name: "procurementInventoryCountList" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -33,67 +33,67 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { createInventoryCount, getInventoryCountPage } from "@/api/procurement";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-inventory-count-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
creating: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getInventoryCountPage(this.searchForm)
|
||||
defineOptions({ name: "procurement-inventory-count-list" });
|
||||
|
||||
const loading = ref(false);
|
||||
const creating = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getInventoryCountPage(searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
Modal.confirm({
|
||||
title: "生成盘点单",
|
||||
content: "将快照当前店铺全部 SKU 库存,是否继续?",
|
||||
onOk: () => {
|
||||
creating.value = true;
|
||||
createInventoryCount()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
Message.success("盘点单已生成");
|
||||
getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
creating.value = false;
|
||||
});
|
||||
},
|
||||
handleCreate() {
|
||||
this.$Modal.confirm({
|
||||
title: "生成盘点单",
|
||||
content: "将快照当前店铺全部 SKU 库存,是否继续?",
|
||||
onOk: () => {
|
||||
this.creating = true;
|
||||
createInventoryCount()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("盘点单已生成");
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.creating = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-inventory-count-detail",
|
||||
query: { id: row.id, sn: row.sn },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function goDetail(row) {
|
||||
customRouterPush({
|
||||
name: "procurement-inventory-count-detail",
|
||||
query: { id: row.id, sn: row.sn },
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px" class="search-form">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="80px" class="search-form">
|
||||
<el-form-item label="单据编号">
|
||||
<el-input v-model="searchForm.orderSn" placeholder="采购单号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
@@ -94,7 +94,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
getProcurementOrderPage,
|
||||
getProcurementOrderStatusCount,
|
||||
@@ -108,187 +110,199 @@ import {
|
||||
procurementStatusTag,
|
||||
formatMoney,
|
||||
} from "../constants";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "procurement-purchase-order-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
createRange: [],
|
||||
currentStatus: "ALL",
|
||||
statusCountData: {},
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
orderSn: "",
|
||||
goodsName: "",
|
||||
status: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusTabs() {
|
||||
const tabs = [{ title: "全部", value: "ALL" }];
|
||||
Object.keys(PROCUREMENT_STATUS).forEach((key) => {
|
||||
const count = this.statusCountData[key];
|
||||
const label = PROCUREMENT_STATUS[key];
|
||||
tabs.push({
|
||||
title: count ? `${label}(${count})` : label,
|
||||
value: key,
|
||||
});
|
||||
});
|
||||
return tabs;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const status = this.$route.query.status;
|
||||
if (status) {
|
||||
this.currentStatus = status;
|
||||
this.searchForm.status = status;
|
||||
defineOptions({ name: "procurement-purchase-order-list" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(false);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const createRange = ref([]);
|
||||
const currentStatus = ref("ALL");
|
||||
const statusCountData = ref({});
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
orderSn: "",
|
||||
goodsName: "",
|
||||
status: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
});
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
const statusTabs = computed(() => {
|
||||
const tabs = [{ title: "全部", value: "ALL" }];
|
||||
Object.keys(PROCUREMENT_STATUS).forEach((key) => {
|
||||
const count = statusCountData.value[key];
|
||||
const label = PROCUREMENT_STATUS[key];
|
||||
tabs.push({
|
||||
title: count ? `${label}(${count})` : label,
|
||||
value: key,
|
||||
});
|
||||
});
|
||||
return tabs;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const status = route.query.status;
|
||||
if (status) {
|
||||
currentStatus.value = status;
|
||||
searchForm.value.status = status;
|
||||
}
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function buildSearchParams(includeStatus = true) {
|
||||
const params = { ...searchForm.value };
|
||||
if (!includeStatus) {
|
||||
delete params.status;
|
||||
}
|
||||
if (createRange.value && createRange.value.length === 2) {
|
||||
params.startCreateTime = createRange.value[0];
|
||||
params.endCreateTime = createRange.value[1];
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
orderSn: "",
|
||||
goodsName: "",
|
||||
status: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
};
|
||||
createRange.value = [];
|
||||
currentStatus.value = "ALL";
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function onStatusTabClick(tab) {
|
||||
statusClick(tab.paneName);
|
||||
}
|
||||
|
||||
function statusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
searchForm.value.status = "";
|
||||
currentStatus.value = "ALL";
|
||||
} else {
|
||||
searchForm.value.status = name;
|
||||
currentStatus.value = name;
|
||||
}
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getStatusCount() {
|
||||
getProcurementOrderStatusCount(buildSearchParams(false)).then((res) => {
|
||||
if (res.success) {
|
||||
statusCountData.value = res.result || {};
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
procurementStatusText,
|
||||
procurementStatusTag,
|
||||
formatMoney,
|
||||
init() {
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
buildSearchParams(includeStatus = true) {
|
||||
const params = { ...this.searchForm };
|
||||
if (!includeStatus) {
|
||||
delete params.status;
|
||||
});
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getProcurementOrderPage(buildSearchParams(true))
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
}
|
||||
if (this.createRange && this.createRange.length === 2) {
|
||||
params.startCreateTime = this.createRange[0];
|
||||
params.endCreateTime = this.createRange[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
orderSn: "",
|
||||
goodsName: "",
|
||||
status: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
};
|
||||
this.createRange = [];
|
||||
this.currentStatus = "ALL";
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.statusClick(tab.paneName);
|
||||
},
|
||||
statusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
this.searchForm.status = "";
|
||||
this.currentStatus = "ALL";
|
||||
} else {
|
||||
this.searchForm.status = name;
|
||||
this.currentStatus = name;
|
||||
}
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getStatusCount() {
|
||||
getProcurementOrderStatusCount(this.buildSearchParams(false)).then((res) => {
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function refreshList() {
|
||||
getStatusCount();
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function goCreate() {
|
||||
customRouterPush({ name: "procurement-purchase-order-operation" });
|
||||
}
|
||||
|
||||
function goDetail(row) {
|
||||
customRouterPush({
|
||||
name: "procurement-purchase-order-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
function goInbound(row) {
|
||||
customRouterPush({
|
||||
name: "procurement-inbound-operation",
|
||||
query: { procurementOrderId: row.id },
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(row) {
|
||||
Modal.confirm({
|
||||
title: "确认提交",
|
||||
content: "提交后采购单将进入审核流程,是否继续?",
|
||||
onOk: () => {
|
||||
submitProcurementOrder(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.statusCountData = res.result || {};
|
||||
Message.success("提交成功");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getProcurementOrderPage(this.buildSearchParams(true))
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
refreshList() {
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
goCreate() {
|
||||
this.$filters.customRouterPush({ name: "procurement-purchase-order-operation" });
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-purchase-order-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
function handleAudit(row, pass) {
|
||||
const title = pass ? "审核通过" : "审核驳回";
|
||||
Modal.confirm({
|
||||
title,
|
||||
content: pass ? "确认通过该采购单?" : "确认驳回该采购单?",
|
||||
onOk: () => {
|
||||
auditProcurementOrder(row.id, { pass, remark: "" }).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("操作成功");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
goInbound(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-inbound-operation",
|
||||
query: { procurementOrderId: row.id },
|
||||
});
|
||||
}
|
||||
|
||||
function handleClose(row) {
|
||||
Modal.confirm({
|
||||
title: "关闭采购单",
|
||||
content: "关闭后不可继续入库,是否继续?",
|
||||
onOk: () => {
|
||||
closeProcurementOrder(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("已关闭");
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleSubmit(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认提交",
|
||||
content: "提交后采购单将进入审核流程,是否继续?",
|
||||
onOk: () => {
|
||||
submitProcurementOrder(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("提交成功");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleAudit(row, pass) {
|
||||
const title = pass ? "审核通过" : "审核驳回";
|
||||
this.$Modal.confirm({
|
||||
title,
|
||||
content: pass ? "确认通过该采购单?" : "确认驳回该采购单?",
|
||||
onOk: () => {
|
||||
auditProcurementOrder(row.id, { pass, remark: "" }).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleClose(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "关闭采购单",
|
||||
content: "关闭后不可继续入库,是否继续?",
|
||||
onOk: () => {
|
||||
closeProcurementOrder(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("已关闭");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -122,11 +122,13 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="onGoodsSelected" />
|
||||
<liliDialog ref="liliDialogRef" @selectedGoodsData="onGoodsSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
import {
|
||||
createProcurementOrder,
|
||||
@@ -140,182 +142,179 @@ import {
|
||||
procurementStatusTag,
|
||||
formatMoney,
|
||||
} from "../constants";
|
||||
import { Message } from "@/utils/message";
|
||||
import { customRouterPush } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "procurement-purchase-order-operation",
|
||||
components: { liliDialog },
|
||||
data() {
|
||||
return {
|
||||
form: { remark: "", stockReasonId: "" },
|
||||
items: [],
|
||||
orderDetail: {},
|
||||
reasonOptions: [],
|
||||
submitLoading: false,
|
||||
readonly: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
if (this.readonly) return "采购单详情";
|
||||
return this.$route.query.id ? "编辑采购单" : "新建采购单";
|
||||
},
|
||||
totalQuantity() {
|
||||
return this.items.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0);
|
||||
},
|
||||
totalAmount() {
|
||||
return this.items.reduce((sum, item) => sum + this.calcSubtotal(item), 0);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadReasons();
|
||||
if (this.$route.query.id) {
|
||||
this.readonly = true;
|
||||
this.loadDetail(this.$route.query.id);
|
||||
defineOptions({ name: "procurement-purchase-order-operation" });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const form = reactive({ remark: "", stockReasonId: "" });
|
||||
const items = ref([]);
|
||||
const orderDetail = ref({});
|
||||
const reasonOptions = ref([]);
|
||||
const submitLoading = ref(false);
|
||||
const readonly = ref(false);
|
||||
|
||||
// liliDialogRef:模板 ref 改名避免与 import 的组件同名互相覆盖
|
||||
const liliDialogRef = ref(null);
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
if (readonly.value) return "采购单详情";
|
||||
return route.query.id ? "编辑采购单" : "新建采购单";
|
||||
});
|
||||
const totalQuantity = computed(() =>
|
||||
items.value.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0)
|
||||
);
|
||||
const totalAmount = computed(() =>
|
||||
items.value.reduce((sum, item) => sum + calcSubtotal(item), 0)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
loadReasons();
|
||||
if (route.query.id) {
|
||||
readonly.value = true;
|
||||
loadDetail(route.query.id);
|
||||
}
|
||||
});
|
||||
|
||||
function loadReasons() {
|
||||
getStockReasonList({ category: "INBOUND", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||
if (res.success) {
|
||||
reasonOptions.value = res.result.records || [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
procurementStatusText,
|
||||
procurementStatusTag,
|
||||
formatMoney,
|
||||
loadReasons() {
|
||||
getStockReasonList({ category: "INBOUND", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||
if (res.success) {
|
||||
this.reasonOptions = res.result.records || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
calcSubtotal(row) {
|
||||
return (Number(row.unitPriceWithTax) || 0) * (Number(row.quantity) || 0);
|
||||
},
|
||||
recalcRow() {},
|
||||
onQuantityBlur(row) {
|
||||
const val = parseInt(row.quantity, 10);
|
||||
row.quantity = Number.isNaN(val) || val < 1 ? 1 : val;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
onTaxRateBlur(row) {
|
||||
const val = parseInt(row.taxRate, 10);
|
||||
if (Number.isNaN(val) || val < 0) {
|
||||
row.taxRate = 0;
|
||||
} else if (val > 100) {
|
||||
row.taxRate = 100;
|
||||
} else {
|
||||
row.taxRate = val;
|
||||
}
|
||||
this.recalcRow(row);
|
||||
},
|
||||
onDecimalBlur(row, field) {
|
||||
const val = parseFloat(row[field]);
|
||||
row[field] = Number.isNaN(val) || val < 0 ? 0 : Math.round(val * 100) / 100;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
loadDetail(id) {
|
||||
getProcurementOrderDetail(id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.orderDetail = res.result;
|
||||
this.form.remark = res.result.remark || "";
|
||||
this.form.stockReasonId = res.result.stockReasonId || "";
|
||||
this.items = (res.result.items || []).map((item) => ({ ...item }));
|
||||
}
|
||||
});
|
||||
},
|
||||
openSkuPicker() {
|
||||
this.$refs.liliDialog.goodsData = this.items.map((item) => ({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
goodsName: item.goodsName,
|
||||
price: item.retailPrice,
|
||||
}));
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
onGoodsSelected(selected) {
|
||||
const map = new Map(this.items.map((item) => [item.skuId, item]));
|
||||
this.items = selected.map((sku) => {
|
||||
const existed = map.get(sku.id);
|
||||
if (existed) return existed;
|
||||
return {
|
||||
goodsId: sku.goodsId,
|
||||
skuId: sku.id,
|
||||
goodsName: sku.goodsName,
|
||||
retailPrice: sku.price || 0,
|
||||
quantity: 1,
|
||||
taxRate: 0,
|
||||
unitPriceWithTax: sku.cost || sku.price || 0,
|
||||
};
|
||||
});
|
||||
},
|
||||
removeItem(index) {
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
validateItems() {
|
||||
if (!this.form.stockReasonId) {
|
||||
this.$Message.warning("请选择出入库原因");
|
||||
return false;
|
||||
}
|
||||
if (!this.items.length) {
|
||||
this.$Message.warning("请至少添加一个商品");
|
||||
return false;
|
||||
}
|
||||
for (const item of this.items) {
|
||||
if (!item.quantity || item.quantity < 1) {
|
||||
this.$Message.warning("采购数量必须大于0");
|
||||
return false;
|
||||
}
|
||||
if (item.unitPriceWithTax == null || item.unitPriceWithTax < 0) {
|
||||
this.$Message.warning("请填写含税单价");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSave(andSubmit) {
|
||||
if (!this.validateItems()) return;
|
||||
this.submitLoading = true;
|
||||
const payload = {
|
||||
remark: this.form.remark,
|
||||
stockReasonId: this.form.stockReasonId,
|
||||
items: this.items.map((item) => ({
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
retailPrice: item.retailPrice,
|
||||
quantity: item.quantity,
|
||||
taxRate: item.taxRate || 0,
|
||||
unitPriceWithTax: item.unitPriceWithTax,
|
||||
})),
|
||||
};
|
||||
createProcurementOrder(payload)
|
||||
.then((res) => {
|
||||
if (!res.success) return;
|
||||
const orderId = res.result.id;
|
||||
if (andSubmit) {
|
||||
return submitProcurementOrder(orderId).then((subRes) => {
|
||||
if (subRes.success) {
|
||||
this.$Message.success("保存并提交成功");
|
||||
this.goBack();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function calcSubtotal(row) {
|
||||
return (Number(row.unitPriceWithTax) || 0) * (Number(row.quantity) || 0);
|
||||
}
|
||||
function recalcRow() {}
|
||||
function onQuantityBlur(row) {
|
||||
const val = parseInt(row.quantity, 10);
|
||||
row.quantity = Number.isNaN(val) || val < 1 ? 1 : val;
|
||||
recalcRow(row);
|
||||
}
|
||||
function onTaxRateBlur(row) {
|
||||
const val = parseInt(row.taxRate, 10);
|
||||
if (Number.isNaN(val) || val < 0) {
|
||||
row.taxRate = 0;
|
||||
} else if (val > 100) {
|
||||
row.taxRate = 100;
|
||||
} else {
|
||||
row.taxRate = val;
|
||||
}
|
||||
recalcRow(row);
|
||||
}
|
||||
function onDecimalBlur(row, field) {
|
||||
const val = parseFloat(row[field]);
|
||||
row[field] = Number.isNaN(val) || val < 0 ? 0 : Math.round(val * 100) / 100;
|
||||
recalcRow(row);
|
||||
}
|
||||
function loadDetail(id) {
|
||||
getProcurementOrderDetail(id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
orderDetail.value = res.result;
|
||||
form.remark = res.result.remark || "";
|
||||
form.stockReasonId = res.result.stockReasonId || "";
|
||||
items.value = (res.result.items || []).map((item) => ({ ...item }));
|
||||
}
|
||||
});
|
||||
}
|
||||
function openSkuPicker() {
|
||||
liliDialogRef.value.goodsData = items.value.map((item) => ({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
goodsName: item.goodsName,
|
||||
price: item.retailPrice,
|
||||
}));
|
||||
liliDialogRef.value.open("goods");
|
||||
}
|
||||
function onGoodsSelected(selected) {
|
||||
const map = new Map(items.value.map((item) => [item.skuId, item]));
|
||||
items.value = selected.map((sku) => {
|
||||
const existed = map.get(sku.id);
|
||||
if (existed) return existed;
|
||||
return {
|
||||
goodsId: sku.goodsId,
|
||||
skuId: sku.id,
|
||||
goodsName: sku.goodsName,
|
||||
retailPrice: sku.price || 0,
|
||||
quantity: 1,
|
||||
taxRate: 0,
|
||||
unitPriceWithTax: sku.cost || sku.price || 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
function removeItem(index) {
|
||||
items.value.splice(index, 1);
|
||||
}
|
||||
function validateItems() {
|
||||
if (!form.stockReasonId) {
|
||||
Message.warning("请选择出入库原因");
|
||||
return false;
|
||||
}
|
||||
if (!items.value.length) {
|
||||
Message.warning("请至少添加一个商品");
|
||||
return false;
|
||||
}
|
||||
for (const item of items.value) {
|
||||
if (!item.quantity || item.quantity < 1) {
|
||||
Message.warning("采购数量必须大于0");
|
||||
return false;
|
||||
}
|
||||
if (item.unitPriceWithTax == null || item.unitPriceWithTax < 0) {
|
||||
Message.warning("请填写含税单价");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleSave(andSubmit) {
|
||||
if (!validateItems()) return;
|
||||
submitLoading.value = true;
|
||||
const payload = {
|
||||
remark: form.remark,
|
||||
stockReasonId: form.stockReasonId,
|
||||
items: items.value.map((item) => ({
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
retailPrice: item.retailPrice,
|
||||
quantity: item.quantity,
|
||||
taxRate: item.taxRate || 0,
|
||||
unitPriceWithTax: item.unitPriceWithTax,
|
||||
})),
|
||||
};
|
||||
createProcurementOrder(payload)
|
||||
.then((res) => {
|
||||
if (!res.success) return;
|
||||
const orderId = res.result.id;
|
||||
if (andSubmit) {
|
||||
return submitProcurementOrder(orderId).then((subRes) => {
|
||||
if (subRes.success) {
|
||||
Message.success("保存并提交成功");
|
||||
goBack();
|
||||
}
|
||||
this.$Message.success("保存成功");
|
||||
this.goBack();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
handleAudit(pass) {
|
||||
auditProcurementOrder(this.orderDetail.id, { pass, remark: "" }).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.goBack();
|
||||
}
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementPurchaseOrderList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Message.success("保存成功");
|
||||
goBack();
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
function handleAudit(pass) {
|
||||
auditProcurementOrder(orderDetail.value.id, { pass, remark: "" }).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("操作成功");
|
||||
goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
function goBack() {
|
||||
customRouterPush({ name: "procurementPurchaseOrderList" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<el-form ref="form" :model="form" label-width="120px" :rules="formRule">
|
||||
<el-form ref="formRef" :model="form" label-width="120px" :rules="formRule">
|
||||
<div class="base-info-item">
|
||||
<h4>基本信息</h4>
|
||||
<div class="form-item-view">
|
||||
@@ -232,350 +232,358 @@
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<sku-select ref="skuSelect" @selectedGoodsData="selectedGoodsData"></sku-select>
|
||||
<sku-select ref="skuSelectRef" @selectedGoodsData="selectedGoodsData"></sku-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { saveShopCoupon, getShopCoupon, editShopCoupon } from "@/api/promotion";
|
||||
import { getGoodsCategoryAll } from "@/api/goods";
|
||||
import { regular } from "@/utils";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { unixToDate } from "@/utils/filters";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import skuSelect from "@/views/lili-dialog";
|
||||
|
||||
export default {
|
||||
name: "add-coupon",
|
||||
components: {
|
||||
skuSelect,
|
||||
},
|
||||
watch: {
|
||||
"form.getType": {
|
||||
handler(val) {
|
||||
if (val == "FREE") {
|
||||
this.rangeTimeType = 1;
|
||||
} else {
|
||||
this.rangeTimeType = 0;
|
||||
}
|
||||
if (this.rangeTimeType == 0) {
|
||||
delete this.formRule.rangeTime;
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
$route(e) {
|
||||
this.id = e.query.id;
|
||||
if (this.id) {
|
||||
this.getCoupon();
|
||||
} else {
|
||||
this.$refs.form.resetFields();
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const checkPrice = (rule, value, callback) => {
|
||||
if (!value && value !== 0) {
|
||||
return callback(new Error("面额不能为空"));
|
||||
} else if (!regular.money.test(value)) {
|
||||
callback(new Error("请输入正整数或者两位小数"));
|
||||
} else if (parseFloat(value) > 99999999) {
|
||||
callback(new Error("面额设置超过上限值"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const checkWeight = (rule, value, callback) => {
|
||||
if (!value && typeof value !== "number") {
|
||||
callback(new Error("消费门槛不能为空"));
|
||||
} else if (!regular.money.test(value)) {
|
||||
callback(new Error("请输入正整数或者两位小数"));
|
||||
} else if (parseFloat(value) > 99999999) {
|
||||
callback(new Error("消费门槛设置超过上限值"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
disabled: this.$route.query.onlyView,
|
||||
rangeTimeType: 1,
|
||||
modalType: 0,
|
||||
form: {
|
||||
storeCommission: 0,
|
||||
publishNum: 0,
|
||||
scopeType: "ALL",
|
||||
couponLimitNum: 1,
|
||||
couponType: "PRICE",
|
||||
couponName: "",
|
||||
promotionName: "",
|
||||
getType: "FREE",
|
||||
promotionGoodsList: [],
|
||||
scopeIdGoods: [],
|
||||
rangeDayType: "",
|
||||
effectiveDays: 1,
|
||||
},
|
||||
id: this.$route.query.id,
|
||||
submitLoading: false,
|
||||
selectedGoods: [],
|
||||
goodsCategoryList: [],
|
||||
formRule: {
|
||||
promotionName: [{ required: true, message: "活动名称不能为空" }],
|
||||
couponName: [{ required: true, message: "优惠券名称不能为空" }],
|
||||
price: [{ required: true, message: "请输入面额" }, { validator: checkPrice }],
|
||||
rangeTime: [{ required: true, message: "请选择优惠券有效期" }],
|
||||
consumeThreshold: [
|
||||
{ required: true, message: "请输入消费门槛" },
|
||||
{ validator: checkWeight },
|
||||
],
|
||||
couponDiscount: [
|
||||
{ required: true, message: "请输入折扣" },
|
||||
{
|
||||
pattern: regular.discount,
|
||||
message: "请输入0-10的数字,可有一位小数",
|
||||
},
|
||||
],
|
||||
storeCommission: [
|
||||
{ required: true, message: "请输入店铺承担比例" },
|
||||
{ pattern: regular.rate, message: "请输入0-100的正整数" },
|
||||
],
|
||||
publishNum: [
|
||||
{ required: true, message: "请输入发放数量" },
|
||||
{ pattern: regular.Integer, message: "请输入正整数" },
|
||||
],
|
||||
couponLimitNum: [
|
||||
{ required: true, message: "领取限制不能为空" },
|
||||
{ pattern: regular.Integer, message: "请输入正整数" },
|
||||
],
|
||||
description: [{ required: true, message: "请输入范围描述" }],
|
||||
},
|
||||
options: {
|
||||
disabledDate(date) {
|
||||
return date && date.valueOf() < Date.now() - 86400000;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.getCagetoryList();
|
||||
if (this.id) {
|
||||
this.getCoupon();
|
||||
this.modalType = 1;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCoupon() {
|
||||
getShopCoupon(this.id).then((res) => {
|
||||
let data = res.result;
|
||||
if (!data.promotionGoodsList) data.promotionGoodsList = [];
|
||||
this.rangeTimeType = data.rangeDayType === "DYNAMICTIME" ? 0 : 1;
|
||||
if (data.scopeType == "PORTION_GOODS_CATEGORY") {
|
||||
let prevCascader = data.scopeId.split(",");
|
||||
function next(params, prev) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const item = params[i];
|
||||
if (item.children) {
|
||||
next(item.children, [...prev, item]);
|
||||
} else {
|
||||
if (prevCascader.includes(item.id)) {
|
||||
prevCascader = prevCascader.map((key) => {
|
||||
if (key === item.id) {
|
||||
let result = prev.map((item) => item.id);
|
||||
return [...result, item.id];
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
i === params.length - 1 && (prev = []);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
defineOptions({ name: "add-coupon" });
|
||||
|
||||
next(this.goodsCategoryList, []);
|
||||
data.scopeIdGoods = prevCascader;
|
||||
}
|
||||
data.rangeTime = [];
|
||||
if (data.startTime && data.endTime) {
|
||||
data.rangeTime.push(new Date(data.startTime), new Date(data.endTime));
|
||||
}
|
||||
this.form = data;
|
||||
});
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
const params = JSON.parse(JSON.stringify(this.form));
|
||||
params.getType != "ACTIVITY" ? delete params.effectiveDays : "";
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
||||
if (this.rangeTimeType == 1) {
|
||||
params.rangeDayType = "FIXEDTIME";
|
||||
const rangeTime = this.form.rangeTime;
|
||||
const start = rangeTime[0] instanceof Date ? rangeTime[0] : new Date(rangeTime[0]);
|
||||
const end = rangeTime[1] instanceof Date ? rangeTime[1] : new Date(rangeTime[1]);
|
||||
params.startTime = this.$filters.unixToDate(start.getTime() / 1000);
|
||||
params.endTime = this.$filters.unixToDate(end.getTime() / 1000);
|
||||
delete params.effectiveDays;
|
||||
} else {
|
||||
params.rangeDayType = "DYNAMICTIME";
|
||||
delete params.rangeTime;
|
||||
}
|
||||
const formRef = ref(null);
|
||||
const skuSelectRef = ref(null);
|
||||
|
||||
let scopeId = [];
|
||||
const checkPrice = (rule, value, callback) => {
|
||||
if (!value && value !== 0) {
|
||||
return callback(new Error("面额不能为空"));
|
||||
} else if (!regular.money.test(value)) {
|
||||
callback(new Error("请输入正整数或者两位小数"));
|
||||
} else if (parseFloat(value) > 99999999) {
|
||||
callback(new Error("面额设置超过上限值"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const checkWeight = (rule, value, callback) => {
|
||||
if (!value && typeof value !== "number") {
|
||||
callback(new Error("消费门槛不能为空"));
|
||||
} else if (!regular.money.test(value)) {
|
||||
callback(new Error("请输入正整数或者两位小数"));
|
||||
} else if (parseFloat(value) > 99999999) {
|
||||
callback(new Error("消费门槛设置超过上限值"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
params.scopeType == "PORTION_GOODS" &&
|
||||
(!params.promotionGoodsList || params.promotionGoodsList.length == 0)
|
||||
) {
|
||||
this.$Modal.warning({ title: "提示", content: "请选择指定商品" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
params.scopeType == "PORTION_GOODS_CATEGORY" &&
|
||||
(!params.scopeIdGoods || params.scopeIdGoods.length == 0)
|
||||
) {
|
||||
this.$Modal.warning({ title: "提示", content: "请选择商品分类" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.scopeType == "PORTION_GOODS") {
|
||||
params.promotionGoodsList.forEach((item) => {
|
||||
scopeId.push(item.skuId);
|
||||
});
|
||||
params.scopeId = scopeId.toString();
|
||||
} else if (params.scopeType == "ALL") {
|
||||
delete params.promotionGoodsList;
|
||||
} else if (params.scopeType == "PORTION_GOODS_CATEGORY") {
|
||||
scopeId = this.filterCategoryId(params.scopeIdGoods, []);
|
||||
params.scopeId = scopeId.toString();
|
||||
delete params.promotionGoodsList;
|
||||
}
|
||||
delete params.scopeIdGoods;
|
||||
|
||||
this.submitLoading = true;
|
||||
if (this.modalType === 0) {
|
||||
delete params.id;
|
||||
|
||||
saveShopCoupon(params).then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("优惠券发送成功");
|
||||
this.closeCurrentPage();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
delete params.consumeLimit;
|
||||
delete params.updateTime;
|
||||
|
||||
editShopCoupon(params).then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success("优惠券修改成功");
|
||||
this.closeCurrentPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
closeCurrentPage() {
|
||||
this.$store.commit("removeTag", "add-coupon");
|
||||
localStorage.pageOpenedList = JSON.stringify(this.$store.state.app.pageOpenedList);
|
||||
this.$router.go(-1);
|
||||
},
|
||||
openSkuList() {
|
||||
this.$refs.skuSelect.open("goods");
|
||||
let data = JSON.parse(JSON.stringify(this.form.promotionGoodsList));
|
||||
data.forEach((e) => {
|
||||
e.id = e.skuId;
|
||||
});
|
||||
this.$refs.skuSelect.goodsData = data;
|
||||
},
|
||||
changeSelect(e) {
|
||||
this.selectedGoods = e;
|
||||
},
|
||||
delSelectGoods() {
|
||||
if (this.selectedGoods.length <= 0) {
|
||||
this.$Message.warning("您还未选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
this.$Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除所选商品吗?",
|
||||
onOk: () => {
|
||||
let ids = [];
|
||||
this.selectedGoods.forEach(function (e) {
|
||||
ids.push(e.skuId);
|
||||
});
|
||||
|
||||
this.form.promotionGoodsList = this.form.promotionGoodsList.filter((item) => {
|
||||
return !ids.includes(item.skuId);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
delGoods(index) {
|
||||
this.form.promotionGoodsList.splice(index, 1);
|
||||
},
|
||||
selectedGoodsData(item) {
|
||||
let list = [];
|
||||
item.forEach((e) => {
|
||||
list.push({
|
||||
goodsName: e.goodsName,
|
||||
price: e.price,
|
||||
originalPrice: e.price,
|
||||
quantity: e.quantity,
|
||||
storeId: e.storeId,
|
||||
storeName: e.storeName,
|
||||
skuId: e.id,
|
||||
categoryPath: e.categoryPath,
|
||||
thumbnail: e.small,
|
||||
goodsType: e.goodsType,
|
||||
goodsId: e.goodsId,
|
||||
originPrice: e.price,
|
||||
});
|
||||
});
|
||||
this.form.promotionGoodsList = list;
|
||||
},
|
||||
async getCagetoryList() {
|
||||
let data = await getGoodsCategoryAll();
|
||||
this.goodsCategoryList = data.result;
|
||||
this.goodsCategoryList = this.goodsCategoryList.map((item) => {
|
||||
if (item.children) {
|
||||
item.children = item.children.map((child) => {
|
||||
if (child.children) {
|
||||
child.children = child.children.map((son) => {
|
||||
return {
|
||||
value: son.id,
|
||||
label: son.name,
|
||||
};
|
||||
});
|
||||
return {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: child.children,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
return { value: item.id, label: item.name, children: item.children };
|
||||
});
|
||||
},
|
||||
filterCategoryId(list, idArr) {
|
||||
list.forEach((e) => {
|
||||
if (e instanceof Array) {
|
||||
this.filterCategoryId(e, idArr);
|
||||
} else {
|
||||
if (!idArr.includes(e)) idArr.push(e);
|
||||
}
|
||||
});
|
||||
return idArr;
|
||||
const disabled = ref(route.query.onlyView);
|
||||
const rangeTimeType = ref(1);
|
||||
const modalType = ref(0);
|
||||
const form = ref({
|
||||
storeCommission: 0,
|
||||
publishNum: 0,
|
||||
scopeType: "ALL",
|
||||
couponLimitNum: 1,
|
||||
couponType: "PRICE",
|
||||
couponName: "",
|
||||
promotionName: "",
|
||||
getType: "FREE",
|
||||
promotionGoodsList: [],
|
||||
scopeIdGoods: [],
|
||||
rangeDayType: "",
|
||||
effectiveDays: 1,
|
||||
});
|
||||
const id = ref(route.query.id);
|
||||
const submitLoading = ref(false);
|
||||
const selectedGoods = ref([]);
|
||||
const goodsCategoryList = ref([]);
|
||||
const formRule = reactive({
|
||||
promotionName: [{ required: true, message: "活动名称不能为空" }],
|
||||
couponName: [{ required: true, message: "优惠券名称不能为空" }],
|
||||
price: [{ required: true, message: "请输入面额" }, { validator: checkPrice }],
|
||||
rangeTime: [{ required: true, message: "请选择优惠券有效期" }],
|
||||
consumeThreshold: [
|
||||
{ required: true, message: "请输入消费门槛" },
|
||||
{ validator: checkWeight },
|
||||
],
|
||||
couponDiscount: [
|
||||
{ required: true, message: "请输入折扣" },
|
||||
{
|
||||
pattern: regular.discount,
|
||||
message: "请输入0-10的数字,可有一位小数",
|
||||
},
|
||||
],
|
||||
storeCommission: [
|
||||
{ required: true, message: "请输入店铺承担比例" },
|
||||
{ pattern: regular.rate, message: "请输入0-100的正整数" },
|
||||
],
|
||||
publishNum: [
|
||||
{ required: true, message: "请输入发放数量" },
|
||||
{ pattern: regular.Integer, message: "请输入正整数" },
|
||||
],
|
||||
couponLimitNum: [
|
||||
{ required: true, message: "领取限制不能为空" },
|
||||
{ pattern: regular.Integer, message: "请输入正整数" },
|
||||
],
|
||||
description: [{ required: true, message: "请输入范围描述" }],
|
||||
});
|
||||
const options = {
|
||||
disabledDate(date) {
|
||||
return date && date.valueOf() < Date.now() - 86400000;
|
||||
},
|
||||
};
|
||||
|
||||
watch(
|
||||
() => form.value.getType,
|
||||
(val) => {
|
||||
if (val == "FREE") {
|
||||
rangeTimeType.value = 1;
|
||||
} else {
|
||||
rangeTimeType.value = 0;
|
||||
}
|
||||
if (rangeTimeType.value == 0) {
|
||||
delete formRule.rangeTime;
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(
|
||||
() => route.query,
|
||||
(e) => {
|
||||
id.value = e.id;
|
||||
if (id.value) {
|
||||
getCoupon();
|
||||
} else {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await getCagetoryList();
|
||||
if (id.value) {
|
||||
getCoupon();
|
||||
modalType.value = 1;
|
||||
}
|
||||
});
|
||||
|
||||
function getCoupon() {
|
||||
getShopCoupon(id.value).then((res) => {
|
||||
let data = res.result;
|
||||
if (!data.promotionGoodsList) data.promotionGoodsList = [];
|
||||
rangeTimeType.value = data.rangeDayType === "DYNAMICTIME" ? 0 : 1;
|
||||
if (data.scopeType == "PORTION_GOODS_CATEGORY") {
|
||||
let prevCascader = data.scopeId.split(",");
|
||||
function next(params, prev) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const item = params[i];
|
||||
if (item.children) {
|
||||
next(item.children, [...prev, item]);
|
||||
} else {
|
||||
if (prevCascader.includes(item.id)) {
|
||||
prevCascader = prevCascader.map((key) => {
|
||||
if (key === item.id) {
|
||||
let result = prev.map((item) => item.id);
|
||||
return [...result, item.id];
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
i === params.length - 1 && (prev = []);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next(goodsCategoryList.value, []);
|
||||
data.scopeIdGoods = prevCascader;
|
||||
}
|
||||
data.rangeTime = [];
|
||||
if (data.startTime && data.endTime) {
|
||||
data.rangeTime.push(new Date(data.startTime), new Date(data.endTime));
|
||||
}
|
||||
form.value = data;
|
||||
});
|
||||
}
|
||||
function handleSubmit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const params = JSON.parse(JSON.stringify(form.value));
|
||||
params.getType != "ACTIVITY" ? delete params.effectiveDays : "";
|
||||
|
||||
if (rangeTimeType.value == 1) {
|
||||
params.rangeDayType = "FIXEDTIME";
|
||||
const rangeTime = form.value.rangeTime;
|
||||
const start = rangeTime[0] instanceof Date ? rangeTime[0] : new Date(rangeTime[0]);
|
||||
const end = rangeTime[1] instanceof Date ? rangeTime[1] : new Date(rangeTime[1]);
|
||||
params.startTime = unixToDate(start.getTime() / 1000);
|
||||
params.endTime = unixToDate(end.getTime() / 1000);
|
||||
delete params.effectiveDays;
|
||||
} else {
|
||||
params.rangeDayType = "DYNAMICTIME";
|
||||
delete params.rangeTime;
|
||||
}
|
||||
|
||||
let scopeId = [];
|
||||
|
||||
if (
|
||||
params.scopeType == "PORTION_GOODS" &&
|
||||
(!params.promotionGoodsList || params.promotionGoodsList.length == 0)
|
||||
) {
|
||||
Modal.warning({ title: "提示", content: "请选择指定商品" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
params.scopeType == "PORTION_GOODS_CATEGORY" &&
|
||||
(!params.scopeIdGoods || params.scopeIdGoods.length == 0)
|
||||
) {
|
||||
Modal.warning({ title: "提示", content: "请选择商品分类" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.scopeType == "PORTION_GOODS") {
|
||||
params.promotionGoodsList.forEach((item) => {
|
||||
scopeId.push(item.skuId);
|
||||
});
|
||||
params.scopeId = scopeId.toString();
|
||||
} else if (params.scopeType == "ALL") {
|
||||
delete params.promotionGoodsList;
|
||||
} else if (params.scopeType == "PORTION_GOODS_CATEGORY") {
|
||||
scopeId = filterCategoryId(params.scopeIdGoods, []);
|
||||
params.scopeId = scopeId.toString();
|
||||
delete params.promotionGoodsList;
|
||||
}
|
||||
delete params.scopeIdGoods;
|
||||
|
||||
submitLoading.value = true;
|
||||
if (modalType.value === 0) {
|
||||
delete params.id;
|
||||
|
||||
saveShopCoupon(params).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("优惠券发送成功");
|
||||
closeCurrentPage();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
delete params.consumeLimit;
|
||||
delete params.updateTime;
|
||||
|
||||
editShopCoupon(params).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("优惠券修改成功");
|
||||
closeCurrentPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function closeCurrentPage() {
|
||||
appStore.removeTag("add-coupon");
|
||||
localStorage.pageOpenedList = JSON.stringify(appStore.pageOpenedList);
|
||||
router.go(-1);
|
||||
}
|
||||
function openSkuList() {
|
||||
skuSelectRef.value.open("goods");
|
||||
let data = JSON.parse(JSON.stringify(form.value.promotionGoodsList));
|
||||
data.forEach((e) => {
|
||||
e.id = e.skuId;
|
||||
});
|
||||
skuSelectRef.value.goodsData = data;
|
||||
}
|
||||
function changeSelect(e) {
|
||||
selectedGoods.value = e;
|
||||
}
|
||||
function delSelectGoods() {
|
||||
if (selectedGoods.value.length <= 0) {
|
||||
Message.warning("您还未选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除所选商品吗?",
|
||||
onOk: () => {
|
||||
let ids = [];
|
||||
selectedGoods.value.forEach(function (e) {
|
||||
ids.push(e.skuId);
|
||||
});
|
||||
|
||||
form.value.promotionGoodsList = form.value.promotionGoodsList.filter((item) => {
|
||||
return !ids.includes(item.skuId);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
function delGoods(index) {
|
||||
form.value.promotionGoodsList.splice(index, 1);
|
||||
}
|
||||
function selectedGoodsData(item) {
|
||||
let list = [];
|
||||
item.forEach((e) => {
|
||||
list.push({
|
||||
goodsName: e.goodsName,
|
||||
price: e.price,
|
||||
originalPrice: e.price,
|
||||
quantity: e.quantity,
|
||||
storeId: e.storeId,
|
||||
storeName: e.storeName,
|
||||
skuId: e.id,
|
||||
categoryPath: e.categoryPath,
|
||||
thumbnail: e.small,
|
||||
goodsType: e.goodsType,
|
||||
goodsId: e.goodsId,
|
||||
originPrice: e.price,
|
||||
});
|
||||
});
|
||||
form.value.promotionGoodsList = list;
|
||||
}
|
||||
async function getCagetoryList() {
|
||||
let data = await getGoodsCategoryAll();
|
||||
goodsCategoryList.value = data.result;
|
||||
goodsCategoryList.value = goodsCategoryList.value.map((item) => {
|
||||
if (item.children) {
|
||||
item.children = item.children.map((child) => {
|
||||
if (child.children) {
|
||||
child.children = child.children.map((son) => {
|
||||
return {
|
||||
value: son.id,
|
||||
label: son.name,
|
||||
};
|
||||
});
|
||||
return {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: child.children,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
return { value: item.id, label: item.name, children: item.children };
|
||||
});
|
||||
}
|
||||
function filterCategoryId(list, idArr) {
|
||||
list.forEach((e) => {
|
||||
if (e instanceof Array) {
|
||||
filterCategoryId(e, idArr);
|
||||
} else {
|
||||
if (!idArr.includes(e)) idArr.push(e);
|
||||
}
|
||||
});
|
||||
return idArr;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-button style="margin-bottom: 10px" @click="back()">返回</el-button>
|
||||
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="90px" class="search-form mb_10">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="90px" class="search-form mb_10">
|
||||
<el-form-item label="优惠券名称" prop="couponName">
|
||||
<el-input
|
||||
v-model="searchForm.couponName"
|
||||
@@ -144,93 +144,111 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, reactive, watch, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { getCouponReceiveList } from "@/api/promotion";
|
||||
export default {
|
||||
name: "coupon-recevie",
|
||||
props: {
|
||||
promotionStatus: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
defineOptions({ name: "coupon-recevie" });
|
||||
|
||||
const props = defineProps({
|
||||
promotionStatus: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
getType: "",
|
||||
couponId: this.$route.query.couponId,
|
||||
},
|
||||
selectList: [],
|
||||
selectCount: 0,
|
||||
data: [],
|
||||
total: 0,
|
||||
refreshTable: true,
|
||||
selectDate: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route(e) {
|
||||
this.searchForm.couponId = e.query.couponId;
|
||||
if (this.couponId) {
|
||||
this.getDataList();
|
||||
} else {
|
||||
this.$refs.searchForm?.resetFields();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
this.$store.commit("removeTag", "coupon-receive");
|
||||
this.$router.go(-1);
|
||||
},
|
||||
check() {
|
||||
this.$emit("selected", this.selectList);
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePage() {
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
},
|
||||
changeSelect(e) {
|
||||
this.selectList = e;
|
||||
this.selectCount = e.length;
|
||||
if (this.getType === "ACTIVITY") this.check();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
if (this.selectDate && this.selectDate[0] && this.selectDate[1]) {
|
||||
this.searchForm.startTime = this.selectDate[0].getTime();
|
||||
this.searchForm.endTime = this.selectDate[1].getTime();
|
||||
} else {
|
||||
this.searchForm.startTime = null;
|
||||
this.searchForm.endTime = null;
|
||||
}
|
||||
getCouponReceiveList(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
getType: "",
|
||||
couponId: route.query.couponId,
|
||||
});
|
||||
const selectList = ref([]);
|
||||
const selectCount = ref(0);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const refreshTable = ref(true);
|
||||
const selectDate = ref([]);
|
||||
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(query) => {
|
||||
searchForm.couponId = query.couponId;
|
||||
// 原代码此处判断的 couponId 在 data 中并不存在,恒为 undefined;保持原行为
|
||||
if (props.couponId) {
|
||||
getDataList();
|
||||
} else {
|
||||
searchFormRef.value?.resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function back() {
|
||||
appStore.removeTag("coupon-receive");
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
function check() {
|
||||
emit("selected", selectList.value);
|
||||
}
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePage() {
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePageSize() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changeSelect(e) {
|
||||
selectList.value = e;
|
||||
selectCount.value = e.length;
|
||||
// 原代码此处判断的 getType 组件并未声明该 prop,恒为 undefined;保持原行为
|
||||
if (props.getType === "ACTIVITY") check();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
if (selectDate.value && selectDate.value[0] && selectDate.value[1]) {
|
||||
searchForm.startTime = selectDate.value[0].getTime();
|
||||
searchForm.endTime = selectDate.value[1].getTime();
|
||||
} else {
|
||||
searchForm.startTime = null;
|
||||
searchForm.endTime = null;
|
||||
}
|
||||
getCouponReceiveList(searchForm).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="90px"
|
||||
@@ -181,315 +181,350 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { nextTick, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {
|
||||
getShopCouponList,
|
||||
updateCouponStatus,
|
||||
deleteShopCoupon,
|
||||
} from "@/api/promotion";
|
||||
import { formatPromotionCouponValidityHtml } from "@/utils/promotions";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "coupon",
|
||||
props: {
|
||||
getType: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
promotionStatus: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
selectedList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
defineOptions({ name: "coupon" });
|
||||
|
||||
const props = defineProps({
|
||||
getType: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
getType: "",
|
||||
},
|
||||
selectList: [],
|
||||
selectCount: 0,
|
||||
data: [],
|
||||
total: 0,
|
||||
selectDate: [],
|
||||
showActionColumn: true,
|
||||
showStatusColumn: true,
|
||||
isSyncingSelection: false,
|
||||
};
|
||||
promotionStatus: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
watch: {
|
||||
$route(to) {
|
||||
if (to.fullPath == "/promotions/manager-coupon") {
|
||||
this.init();
|
||||
selectedList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(true);
|
||||
const searchForm = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
getType: "",
|
||||
});
|
||||
const selectList = ref([]);
|
||||
const selectCount = ref(0);
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const selectDate = ref([]);
|
||||
const showActionColumn = ref(true);
|
||||
const showStatusColumn = ref(true);
|
||||
const isSyncingSelection = ref(false);
|
||||
|
||||
// searchFormRef/table:模板 ref 绑定(searchFormRef 原名 searchForm,改名避免与顶层变量互相覆盖)
|
||||
const searchFormRef = ref(null);
|
||||
const table = ref(null);
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(fullPath) => {
|
||||
if (fullPath == "/promotions/manager-coupon") {
|
||||
init();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.selectedList,
|
||||
(val) => {
|
||||
if (isSyncingSelection.value) return;
|
||||
if (isSameSelection(val, selectList.value)) return;
|
||||
nextTick(() => {
|
||||
syncTableSelection(val);
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function formatActivityTime(row) {
|
||||
return formatPromotionCouponValidityHtml(row);
|
||||
}
|
||||
|
||||
function promotionStatusText(status) {
|
||||
const map = {
|
||||
NEW: "未开始",
|
||||
START: "已开始",
|
||||
END: "已结束",
|
||||
CLOSE: "已关闭",
|
||||
};
|
||||
return map[status] || "未知";
|
||||
}
|
||||
|
||||
function promotionStatusTagType(status) {
|
||||
const map = {
|
||||
NEW: "info",
|
||||
START: "success",
|
||||
END: "danger",
|
||||
CLOSE: "danger",
|
||||
};
|
||||
return map[status] || "danger";
|
||||
}
|
||||
|
||||
function scopeTypeText(type) {
|
||||
const map = {
|
||||
ALL: "全品类",
|
||||
PORTION_GOODS_CATEGORY: "商品分类",
|
||||
PORTION_SHOP_CATEGORY: "店铺分类",
|
||||
PORTION_GOODS: "指定商品",
|
||||
};
|
||||
return map[type] || "未知";
|
||||
}
|
||||
|
||||
function scopeTypeTagType(type) {
|
||||
const map = {
|
||||
ALL: "info",
|
||||
PORTION_GOODS_CATEGORY: "warning",
|
||||
PORTION_SHOP_CATEGORY: "warning",
|
||||
PORTION_GOODS: "primary",
|
||||
};
|
||||
return map[type] || "danger";
|
||||
}
|
||||
|
||||
function getTypeText(type) {
|
||||
const map = {
|
||||
FREE: "免费获取",
|
||||
ACTIVITY: "活动获取",
|
||||
INSIDE: "内购",
|
||||
IGAME: "游戏人生",
|
||||
};
|
||||
return map[type] || "未知";
|
||||
}
|
||||
|
||||
function getTypeTagType(type) {
|
||||
const map = {
|
||||
FREE: "danger",
|
||||
ACTIVITY: "warning",
|
||||
INSIDE: "success",
|
||||
IGAME: "success",
|
||||
};
|
||||
return map[type] || "info";
|
||||
}
|
||||
|
||||
function couponTypeText(type) {
|
||||
const map = {
|
||||
DISCOUNT: "打折",
|
||||
PRICE: "减免现金",
|
||||
};
|
||||
return map[type] || "未知";
|
||||
}
|
||||
|
||||
function couponTypeTagType(type) {
|
||||
const map = {
|
||||
DISCOUNT: "primary",
|
||||
PRICE: "info",
|
||||
};
|
||||
return map[type] || "info";
|
||||
}
|
||||
|
||||
function isSameSelection(next, current) {
|
||||
const a = next || [];
|
||||
const b = current || [];
|
||||
if (a.length !== b.length) return false;
|
||||
const nextIds = a.map((item) => item.id).sort().join(",");
|
||||
const currentIds = b.map((item) => item.id).sort().join(",");
|
||||
return nextIds === currentIds;
|
||||
}
|
||||
|
||||
function syncTableSelection(selected) {
|
||||
const tableEl = table.value;
|
||||
if (!tableEl) return;
|
||||
const nextList = selected ? [...selected] : [];
|
||||
isSyncingSelection.value = true;
|
||||
tableEl.clearSelection();
|
||||
if (nextList.length) {
|
||||
data.value.forEach((row) => {
|
||||
if (nextList.some((item) => item.id === row.id)) {
|
||||
tableEl.toggleRowSelection(row, true);
|
||||
}
|
||||
},
|
||||
selectedList: {
|
||||
handler(val) {
|
||||
if (this.isSyncingSelection) return;
|
||||
if (this.isSameSelection(val, this.selectList)) return;
|
||||
this.$nextTick(() => {
|
||||
this.syncTableSelection(val);
|
||||
});
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatActivityTime(row) {
|
||||
return formatPromotionCouponValidityHtml(row);
|
||||
},
|
||||
promotionStatusText(status) {
|
||||
const map = {
|
||||
NEW: "未开始",
|
||||
START: "已开始",
|
||||
END: "已结束",
|
||||
CLOSE: "已关闭",
|
||||
};
|
||||
return map[status] || "未知";
|
||||
},
|
||||
promotionStatusTagType(status) {
|
||||
const map = {
|
||||
NEW: "info",
|
||||
START: "success",
|
||||
END: "danger",
|
||||
CLOSE: "danger",
|
||||
};
|
||||
return map[status] || "danger";
|
||||
},
|
||||
scopeTypeText(type) {
|
||||
const map = {
|
||||
ALL: "全品类",
|
||||
PORTION_GOODS_CATEGORY: "商品分类",
|
||||
PORTION_SHOP_CATEGORY: "店铺分类",
|
||||
PORTION_GOODS: "指定商品",
|
||||
};
|
||||
return map[type] || "未知";
|
||||
},
|
||||
scopeTypeTagType(type) {
|
||||
const map = {
|
||||
ALL: "info",
|
||||
PORTION_GOODS_CATEGORY: "warning",
|
||||
PORTION_SHOP_CATEGORY: "warning",
|
||||
PORTION_GOODS: "primary",
|
||||
};
|
||||
return map[type] || "danger";
|
||||
},
|
||||
getTypeText(type) {
|
||||
const map = {
|
||||
FREE: "免费获取",
|
||||
ACTIVITY: "活动获取",
|
||||
INSIDE: "内购",
|
||||
IGAME: "游戏人生",
|
||||
};
|
||||
return map[type] || "未知";
|
||||
},
|
||||
getTypeTagType(type) {
|
||||
const map = {
|
||||
FREE: "danger",
|
||||
ACTIVITY: "warning",
|
||||
INSIDE: "success",
|
||||
IGAME: "success",
|
||||
};
|
||||
return map[type] || "info";
|
||||
},
|
||||
couponTypeText(type) {
|
||||
const map = {
|
||||
DISCOUNT: "打折",
|
||||
PRICE: "减免现金",
|
||||
};
|
||||
return map[type] || "未知";
|
||||
},
|
||||
couponTypeTagType(type) {
|
||||
const map = {
|
||||
DISCOUNT: "primary",
|
||||
PRICE: "info",
|
||||
};
|
||||
return map[type] || "info";
|
||||
},
|
||||
isSameSelection(next, current) {
|
||||
const a = next || [];
|
||||
const b = current || [];
|
||||
if (a.length !== b.length) return false;
|
||||
const nextIds = a.map((item) => item.id).sort().join(",");
|
||||
const currentIds = b.map((item) => item.id).sort().join(",");
|
||||
return nextIds === currentIds;
|
||||
},
|
||||
syncTableSelection(selected) {
|
||||
const table = this.$refs.table;
|
||||
if (!table) return;
|
||||
const nextList = selected ? [...selected] : [];
|
||||
this.isSyncingSelection = true;
|
||||
table.clearSelection();
|
||||
if (nextList.length) {
|
||||
this.data.forEach((row) => {
|
||||
if (nextList.some((item) => item.id === row.id)) {
|
||||
table.toggleRowSelection(row, true);
|
||||
});
|
||||
}
|
||||
selectList.value = nextList;
|
||||
selectCount.value = nextList.length;
|
||||
nextTick(() => {
|
||||
isSyncingSelection.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function check() {
|
||||
emit("selected", selectList.value);
|
||||
}
|
||||
|
||||
function receivePage(id) {
|
||||
if (id) {
|
||||
router.push({ name: "coupon-receive", query: { couponId: id } });
|
||||
} else {
|
||||
router.push({ name: "coupon-receive" });
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function add() {
|
||||
router.push({ name: "add-coupon" });
|
||||
}
|
||||
|
||||
function changePage(v) {
|
||||
searchForm.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePageSize(v) {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = v;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
searchForm.pageSize = 20;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function clearSelectAll() {
|
||||
table.value?.clearSelection();
|
||||
}
|
||||
|
||||
function changeSelect(e) {
|
||||
if (isSyncingSelection.value) return;
|
||||
selectList.value = e;
|
||||
selectCount.value = e.length;
|
||||
if (props.getType === "ACTIVITY") check();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
if (selectDate.value && selectDate.value[0] && selectDate.value[1]) {
|
||||
searchForm.startTime = new Date(selectDate.value[0]).getTime();
|
||||
searchForm.endTime = new Date(selectDate.value[1]).getTime();
|
||||
} else {
|
||||
searchForm.startTime = null;
|
||||
searchForm.endTime = null;
|
||||
}
|
||||
getShopCouponList(searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
nextTick(() => {
|
||||
if (props.selectedList.length) {
|
||||
syncTableSelection(props.selectedList);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.selectList = nextList;
|
||||
this.selectCount = nextList.length;
|
||||
this.$nextTick(() => {
|
||||
this.isSyncingSelection = false;
|
||||
});
|
||||
},
|
||||
check() {
|
||||
this.$emit("selected", this.selectList);
|
||||
},
|
||||
receivePage(id) {
|
||||
if (id) {
|
||||
this.$router.push({ name: "coupon-receive", query: { couponId: id } });
|
||||
} else {
|
||||
this.$router.push({ name: "coupon-receive" });
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
add() {
|
||||
this.$router.push({ name: "add-coupon" });
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = 20;
|
||||
this.getDataList();
|
||||
},
|
||||
clearSelectAll() {
|
||||
this.$refs.table?.clearSelection();
|
||||
},
|
||||
changeSelect(e) {
|
||||
if (this.isSyncingSelection) return;
|
||||
this.selectList = e;
|
||||
this.selectCount = e.length;
|
||||
if (this.getType === "ACTIVITY") this.check();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
if (this.selectDate && this.selectDate[0] && this.selectDate[1]) {
|
||||
this.searchForm.startTime = new Date(this.selectDate[0]).getTime();
|
||||
this.searchForm.endTime = new Date(this.selectDate[1]).getTime();
|
||||
} else {
|
||||
this.searchForm.startTime = null;
|
||||
this.searchForm.endTime = null;
|
||||
}
|
||||
getShopCouponList(this.searchForm)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function see(v, only) {
|
||||
const data = only ? { onlyView: true, id: v.id } : { id: v.id };
|
||||
router.push({ name: "add-coupon", query: data });
|
||||
}
|
||||
|
||||
function close(v) {
|
||||
Modal.confirm({
|
||||
title: "确认关闭",
|
||||
content: "确认要关闭此优惠券么?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
updateCouponStatus({
|
||||
couponIds: v.id,
|
||||
effectiveDays: 0,
|
||||
})
|
||||
.then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
this.$nextTick(() => {
|
||||
if (this.selectedList.length) {
|
||||
this.syncTableSelection(this.selectedList);
|
||||
}
|
||||
});
|
||||
Message.success("优惠券已关闭");
|
||||
getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
.catch(() => {
|
||||
Modal.remove();
|
||||
});
|
||||
},
|
||||
see(v, only) {
|
||||
const data = only ? { onlyView: true, id: v.id } : { id: v.id };
|
||||
this.$router.push({ name: "add-coupon", query: data });
|
||||
});
|
||||
}
|
||||
|
||||
function remove(v) {
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "确认要删除此优惠券么?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
deleteShopCoupon(v.id)
|
||||
.then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("优惠券已删除");
|
||||
getDataList();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
Modal.remove();
|
||||
});
|
||||
},
|
||||
close(v) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认关闭",
|
||||
content: "确认要关闭此优惠券么?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
updateCouponStatus({
|
||||
couponIds: v.id,
|
||||
effectiveDays: 0,
|
||||
})
|
||||
.then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("优惠券已关闭");
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$Modal.remove();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function delAll() {
|
||||
if (selectCount.value <= 0) {
|
||||
Message.warning("您还未选择要下架的优惠券");
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "确认下架",
|
||||
content: "您确认要下架所选的 " + selectCount.value + " 条数据?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
const ids = selectList.value.map((e) => e.id);
|
||||
updateCouponStatus({
|
||||
couponIds: ids.toString(),
|
||||
promotionStatus: "CLOSE",
|
||||
}).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("下架成功");
|
||||
clearSelectAll();
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
remove(v) {
|
||||
this.$Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "确认要删除此优惠券么?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
deleteShopCoupon(v.id)
|
||||
.then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("优惠券已删除");
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$Modal.remove();
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
delAll() {
|
||||
if (this.selectCount <= 0) {
|
||||
this.$Message.warning("您还未选择要下架的优惠券");
|
||||
return;
|
||||
}
|
||||
this.$Modal.confirm({
|
||||
title: "确认下架",
|
||||
content: "您确认要下架所选的 " + this.selectCount + " 条数据?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
const ids = this.selectList.map((e) => e.id);
|
||||
updateCouponStatus({
|
||||
couponIds: ids.toString(),
|
||||
promotionStatus: "CLOSE",
|
||||
}).then((res) => {
|
||||
this.$Modal.remove();
|
||||
if (res.success) {
|
||||
this.$Message.success("下架成功");
|
||||
this.clearSelectAll();
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.getType) {
|
||||
this.searchForm.getType = this.getType;
|
||||
this.showActionColumn = false;
|
||||
}
|
||||
if (this.promotionStatus) {
|
||||
this.searchForm.promotionStatus = this.promotionStatus;
|
||||
this.showStatusColumn = false;
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.getType) {
|
||||
searchForm.getType = props.getType;
|
||||
showActionColumn.value = false;
|
||||
}
|
||||
if (props.promotionStatus) {
|
||||
searchForm.promotionStatus = props.promotionStatus;
|
||||
showStatusColumn.value = false;
|
||||
}
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<el-form ref="form" :model="form" label-width="120px">
|
||||
<el-form ref="formRef" :model="form" label-width="120px">
|
||||
<el-form-item label="活动名称" required>
|
||||
<el-input v-model="form.promotionName" style="width: 320px" />
|
||||
</el-form-item>
|
||||
@@ -49,82 +49,88 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoods" />
|
||||
<liliDialog ref="liliDialogRef" @selectedGoodsData="selectedGoods" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { getFlashDiscountDetail, saveFlashDiscount, editFlashDiscount } from "@/api/promotion.js";
|
||||
import { promotionPriceMin } from "@/constants/goodsType";
|
||||
import { unixToDate } from "@/utils/filters";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
promotionName: "",
|
||||
rangeTime: [],
|
||||
limitNum: 0,
|
||||
description: "",
|
||||
scopeType: "PORTION_GOODS",
|
||||
promotionGoodsList: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.id) {
|
||||
getFlashDiscountDetail(this.$route.query.id).then((res) => {
|
||||
if (res.success) {
|
||||
Object.assign(this.form, res.result);
|
||||
this.form.rangeTime = [new Date(res.result.startTime), new Date(res.result.endTime)];
|
||||
}
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const form = reactive({
|
||||
promotionName: "",
|
||||
rangeTime: [],
|
||||
limitNum: 0,
|
||||
description: "",
|
||||
scopeType: "PORTION_GOODS",
|
||||
promotionGoodsList: [],
|
||||
});
|
||||
|
||||
// formRef / liliDialogRef:模板 ref 改名避免与 form 变量、liliDialog 全局组件互相覆盖
|
||||
const formRef = ref(null);
|
||||
const liliDialogRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.id) {
|
||||
getFlashDiscountDetail(route.query.id).then((res) => {
|
||||
if (res.success) {
|
||||
Object.assign(form, res.result);
|
||||
form.rangeTime = [new Date(res.result.startTime), new Date(res.result.endTime)];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function openGoods() {
|
||||
liliDialogRef.value.open("goods");
|
||||
}
|
||||
|
||||
function selectedGoods(list) {
|
||||
list.forEach((g) => {
|
||||
if (!form.promotionGoodsList.find((i) => i.skuId === g.id)) {
|
||||
form.promotionGoodsList.push({
|
||||
skuId: g.id,
|
||||
goodsId: g.goodsId,
|
||||
goodsName: g.goodsName,
|
||||
originalPrice: g.price,
|
||||
price: g.price,
|
||||
quantity: g.quantity,
|
||||
goodsType: g.goodsType,
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
promotionPriceMin,
|
||||
openGoods() {
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
selectedGoods(list) {
|
||||
list.forEach((g) => {
|
||||
if (!this.form.promotionGoodsList.find((i) => i.skuId === g.id)) {
|
||||
this.form.promotionGoodsList.push({
|
||||
skuId: g.id,
|
||||
goodsId: g.goodsId,
|
||||
goodsName: g.goodsName,
|
||||
originalPrice: g.price,
|
||||
price: g.price,
|
||||
quantity: g.quantity,
|
||||
goodsType: g.goodsType,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
const payload = { ...this.form };
|
||||
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
||||
const start =
|
||||
payload.rangeTime[0] instanceof Date
|
||||
? payload.rangeTime[0]
|
||||
: new Date(payload.rangeTime[0]);
|
||||
const end =
|
||||
payload.rangeTime[1] instanceof Date
|
||||
? payload.rangeTime[1]
|
||||
: new Date(payload.rangeTime[1]);
|
||||
payload.startTime = this.$filters.unixToDate(start.getTime() / 1000);
|
||||
payload.endTime = this.$filters.unixToDate(end.getTime() / 1000);
|
||||
}
|
||||
delete payload.rangeTime;
|
||||
const req = payload.id ? editFlashDiscount(payload) : saveFlashDiscount(payload);
|
||||
req.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("保存成功");
|
||||
this.$router.back();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const payload = { ...form };
|
||||
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
||||
const start =
|
||||
payload.rangeTime[0] instanceof Date
|
||||
? payload.rangeTime[0]
|
||||
: new Date(payload.rangeTime[0]);
|
||||
const end =
|
||||
payload.rangeTime[1] instanceof Date
|
||||
? payload.rangeTime[1]
|
||||
: new Date(payload.rangeTime[1]);
|
||||
payload.startTime = unixToDate(start.getTime() / 1000);
|
||||
payload.endTime = unixToDate(end.getTime() / 1000);
|
||||
}
|
||||
delete payload.rangeTime;
|
||||
const req = payload.id ? editFlashDiscount(payload) : saveFlashDiscount(payload);
|
||||
req.then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("保存成功");
|
||||
router.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form" @keyup.enter="handleSearch">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="70px" class="search-form" @keyup.enter="handleSearch">
|
||||
<el-form-item label="活动名称" prop="promotionName">
|
||||
<el-input v-model="searchForm.promotionName" placeholder="请输入活动名称" clearable style="width: 240px" />
|
||||
</el-form-item>
|
||||
@@ -50,58 +50,60 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getFlashDiscountList, updateFlashDiscountStatus } from "@/api/promotion.js";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
total: 0,
|
||||
data: [],
|
||||
searchForm: { pageNumber: 1, pageSize: 20, sort: "createTime", order: "desc" },
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
statusText(s) {
|
||||
return { NEW: "未开始", START: "进行中", END: "已结束", CLOSE: "已关闭" }[s] || s;
|
||||
},
|
||||
statusType(s) {
|
||||
return { NEW: "info", START: "success", END: "danger", CLOSE: "danger" }[s] || "info";
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getFlashDiscountList(this.searchForm).then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
});
|
||||
},
|
||||
goAdd() {
|
||||
this.$router.push({ name: "flash-discount-add" });
|
||||
},
|
||||
goEdit(row) {
|
||||
this.$router.push({ name: "flash-discount-add", query: { id: row.id } });
|
||||
},
|
||||
toggleStatus(row) {
|
||||
updateFlashDiscountStatus(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const data = ref([]);
|
||||
const searchForm = reactive({ pageNumber: 1, pageSize: 20, sort: "createTime", order: "desc" });
|
||||
|
||||
// searchFormRef:模板 ref 改名避免与顶层变量 searchForm 互相覆盖
|
||||
const searchFormRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
|
||||
function statusText(s) {
|
||||
return { NEW: "未开始", START: "进行中", END: "已结束", CLOSE: "已关闭" }[s] || s;
|
||||
}
|
||||
function statusType(s) {
|
||||
return { NEW: "info", START: "success", END: "danger", CLOSE: "danger" }[s] || "info";
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getFlashDiscountList(searchForm).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
function goAdd() {
|
||||
router.push({ name: "flash-discount-add" });
|
||||
}
|
||||
function goEdit(row) {
|
||||
router.push({ name: "flash-discount-add", query: { id: row.id } });
|
||||
}
|
||||
function toggleStatus(row) {
|
||||
updateFlashDiscountStatus(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("操作成功");
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<el-form ref="form" :model="form" label-width="120px" :rules="formRule">
|
||||
<el-form ref="formRef" :model="form" label-width="120px" :rules="formRule">
|
||||
<div class="base-info-item">
|
||||
<h4>基本信息</h4>
|
||||
<div class="form-item-view">
|
||||
@@ -230,11 +230,13 @@
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoodsData" />
|
||||
<liliDialog ref="liliDialogRef" @selectedGoodsData="selectedGoodsData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {
|
||||
getShopCouponList,
|
||||
getFullDiscountById,
|
||||
@@ -243,260 +245,260 @@ import {
|
||||
} from "@/api/promotion";
|
||||
import { getGoodsSkuListDataSeller } from "@/api/goods";
|
||||
import { regular } from "@/utils";
|
||||
import vueQr from "vue-qr";
|
||||
import { goodsTypeLabel } from "@/constants/goodsType";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { unixToDate } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "add-full-discount",
|
||||
components: {
|
||||
"vue-qr": vueQr,
|
||||
},
|
||||
data() {
|
||||
const checkWeight = (rule, value, callback) => {
|
||||
if (!value && typeof value !== "number") {
|
||||
callback(new Error("优惠门槛不能为空"));
|
||||
} else if (!regular.money.test(value)) {
|
||||
callback(new Error("请输入正整数或者两位小数"));
|
||||
} else if (parseFloat(value) > 99999999) {
|
||||
callback(new Error("优惠门槛设置超过上限值"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const checkDiscount = (rule, value, callback) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
callback(new Error("请填写优惠折扣"));
|
||||
} else if (value < 0.1 || value > 9.9) {
|
||||
callback(new Error("请输入0-10的数字,可有一位小数"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {
|
||||
discountType: "fullMinusFlag",
|
||||
scopeType: "ALL",
|
||||
promotionGoodsList: [],
|
||||
promotionStatus: "NEW",
|
||||
},
|
||||
id: this.$route.query.id,
|
||||
submitLoading: false,
|
||||
selectedGoods: [],
|
||||
couponList: [],
|
||||
giftList: [],
|
||||
giftLoading: false,
|
||||
couponLoading: false,
|
||||
formRule: {
|
||||
promotionName: [{ required: true, message: "活动名称不能为空" }],
|
||||
rangeTime: [{ required: true, message: "请选择活动时间" }],
|
||||
description: [{ required: true, message: "请填写活动描述" }],
|
||||
fullMoney: [{ required: true, validator: checkWeight }],
|
||||
fullMinus: [
|
||||
{ required: true, message: "请填写优惠金额" },
|
||||
{ pattern: regular.money, message: "请输入正确金额" },
|
||||
],
|
||||
fullRate: [{ required: true, validator: checkDiscount }],
|
||||
},
|
||||
options: {
|
||||
disabledDate(date) {
|
||||
return date && date.valueOf() < Date.now() - 86400000;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
fieldDisabled() {
|
||||
return !!this.id && this.form.promotionStatus !== "NEW";
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail();
|
||||
}
|
||||
this.getCouponList();
|
||||
this.getGiftList();
|
||||
},
|
||||
methods: {
|
||||
giftOptionLabel(item) {
|
||||
if (!item) return "";
|
||||
const typeLabel =
|
||||
item.goodsType === "E_COUPON" ? ` [${goodsTypeLabel(item.goodsType)}]` : "";
|
||||
const stock =
|
||||
item.goodsType === "E_COUPON" && item.quantity != null
|
||||
? ` · 库存${item.quantity}`
|
||||
: "";
|
||||
return `${item.goodsName || ""}${typeLabel}${stock}`;
|
||||
},
|
||||
closeCurrentPage() {
|
||||
this.$router.back();
|
||||
},
|
||||
openSkuList() {
|
||||
const data = JSON.parse(JSON.stringify(this.form.promotionGoodsList || []));
|
||||
data.forEach((e) => {
|
||||
e.id = e.skuId || e.id;
|
||||
});
|
||||
// 先写入已选商品,再打开选择器,保证回显时 selectedWay 已就绪
|
||||
this.$refs.liliDialog.goodsData = data;
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
getDetail() {
|
||||
getFullDiscountById(this.id).then((res) => {
|
||||
const data = res.result;
|
||||
if (data.scopeType === "ALL") {
|
||||
data.promotionGoodsList = [];
|
||||
}
|
||||
if (data.fullMinusFlag) {
|
||||
data.discountType = "fullMinusFlag";
|
||||
delete data.fullMinusFlag;
|
||||
} else {
|
||||
data.discountType = "fullRateFlag";
|
||||
delete data.fullRateFlag;
|
||||
}
|
||||
data.rangeTime = [new Date(data.startTime), new Date(data.endTime)];
|
||||
if (data.fullRate !== null && data.fullRate !== undefined && data.fullRate !== "") {
|
||||
data.fullRate = parseFloat(Number(data.fullRate).toFixed(1));
|
||||
}
|
||||
this.form = data;
|
||||
});
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return;
|
||||
const params = JSON.parse(JSON.stringify(this.form));
|
||||
params.startTime = this.$filters.unixToDate(this.form.rangeTime[0] / 1000);
|
||||
params.endTime = this.$filters.unixToDate(this.form.rangeTime[1] / 1000);
|
||||
defineOptions({ name: "add-full-discount" });
|
||||
|
||||
if (params.couponFlag && !params.couponId) {
|
||||
this.$Message.warning("请选择优惠券");
|
||||
return;
|
||||
}
|
||||
if (params.giftFlag && !params.giftId) {
|
||||
this.$Message.warning("请选择赠品");
|
||||
return;
|
||||
}
|
||||
if (params.pointFlag && !params.point) {
|
||||
this.$Message.warning("请填写积分");
|
||||
return;
|
||||
}
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
if (
|
||||
params.scopeType === "PORTION_GOODS" &&
|
||||
(!params.promotionGoodsList || params.promotionGoodsList.length === 0)
|
||||
) {
|
||||
this.$Message.warning("请选择指定商品");
|
||||
return;
|
||||
}
|
||||
if (params.scopeType === "ALL") {
|
||||
delete params.promotionGoodsList;
|
||||
params.number = -1;
|
||||
} else {
|
||||
const scopeId = [];
|
||||
params.number = 1;
|
||||
params.promotionGoodsList.forEach((e) => {
|
||||
e.startTime = params.startTime;
|
||||
e.endTime = params.endTime;
|
||||
scopeId.push(e.skuId);
|
||||
});
|
||||
params.scopeId = scopeId.toString();
|
||||
}
|
||||
if (params.discountType === "fullMinusFlag") {
|
||||
params.fullMinusFlag = true;
|
||||
} else {
|
||||
params.fullRateFlag = true;
|
||||
}
|
||||
delete params.discountType;
|
||||
delete params.rangeTime;
|
||||
if (!this.id) {
|
||||
delete params.id;
|
||||
} else {
|
||||
delete params.updateTime;
|
||||
}
|
||||
const checkWeight = (rule, value, callback) => {
|
||||
if (!value && typeof value !== "number") {
|
||||
callback(new Error("优惠门槛不能为空"));
|
||||
} else if (!regular.money.test(value)) {
|
||||
callback(new Error("请输入正整数或者两位小数"));
|
||||
} else if (parseFloat(value) > 99999999) {
|
||||
callback(new Error("优惠门槛设置超过上限值"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const checkDiscount = (rule, value, callback) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
callback(new Error("请填写优惠折扣"));
|
||||
} else if (value < 0.1 || value > 9.9) {
|
||||
callback(new Error("请输入0-10的数字,可有一位小数"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
this.submitLoading = true;
|
||||
const request = this.id ? editFullDiscount(params) : newFullDiscount(params);
|
||||
request.then((res) => {
|
||||
this.submitLoading = false;
|
||||
if (res.success) {
|
||||
this.$Message.success(this.id ? "编辑活动成功" : "添加活动成功");
|
||||
this.closeCurrentPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
changeSelect(e) {
|
||||
this.selectedGoods = e;
|
||||
},
|
||||
delSelectGoods() {
|
||||
if (this.selectedGoods.length <= 0) {
|
||||
this.$Message.warning("您还未选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
this.$Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除所选商品吗?",
|
||||
onOk: () => {
|
||||
const ids = this.selectedGoods.map((e) => e.skuId || e.id);
|
||||
this.form.promotionGoodsList = this.form.promotionGoodsList.filter(
|
||||
(item) => !ids.includes(item.skuId)
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
delGoods(index) {
|
||||
this.form.promotionGoodsList.splice(index, 1);
|
||||
},
|
||||
selectedGoodsData(item) {
|
||||
const list = [];
|
||||
item.forEach((e) => {
|
||||
list.push({
|
||||
goodsName: e.goodsName,
|
||||
price: e.price,
|
||||
quantity: e.quantity,
|
||||
storeId: e.storeId,
|
||||
storeName: e.storeName,
|
||||
thumbnail: e.thumbnail,
|
||||
skuId: e.id,
|
||||
goodsId: e.goodsId,
|
||||
goodsType: e.goodsType,
|
||||
});
|
||||
});
|
||||
this.form.promotionGoodsList = list;
|
||||
},
|
||||
getCouponList(query) {
|
||||
const params = {
|
||||
pageSize: 20,
|
||||
pageNumber: 1,
|
||||
getType: "ACTIVITY",
|
||||
couponName: query,
|
||||
promotionStatus: "START",
|
||||
};
|
||||
this.couponLoading = true;
|
||||
getShopCouponList(params).then((res) => {
|
||||
this.couponLoading = false;
|
||||
if (res.success) {
|
||||
this.couponList = res.result.records;
|
||||
}
|
||||
});
|
||||
},
|
||||
getGiftList(query) {
|
||||
const params = {
|
||||
pageSize: 20,
|
||||
pageNumber: 1,
|
||||
id: query === this.form.giftId ? this.form.giftId : null,
|
||||
goodsName: query === this.form.giftId ? null : query,
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
};
|
||||
this.giftLoading = true;
|
||||
getGoodsSkuListDataSeller(params).then((res) => {
|
||||
this.giftLoading = false;
|
||||
if (res.success) {
|
||||
this.giftList = res.result.records;
|
||||
}
|
||||
});
|
||||
},
|
||||
const form = ref({
|
||||
discountType: "fullMinusFlag",
|
||||
scopeType: "ALL",
|
||||
promotionGoodsList: [],
|
||||
promotionStatus: "NEW",
|
||||
});
|
||||
const id = ref(route.query.id);
|
||||
const submitLoading = ref(false);
|
||||
const selectedGoods = ref([]);
|
||||
const couponList = ref([]);
|
||||
const giftList = ref([]);
|
||||
const giftLoading = ref(false);
|
||||
const couponLoading = ref(false);
|
||||
const formRule = {
|
||||
promotionName: [{ required: true, message: "活动名称不能为空" }],
|
||||
rangeTime: [{ required: true, message: "请选择活动时间" }],
|
||||
description: [{ required: true, message: "请填写活动描述" }],
|
||||
fullMoney: [{ required: true, validator: checkWeight }],
|
||||
fullMinus: [
|
||||
{ required: true, message: "请填写优惠金额" },
|
||||
{ pattern: regular.money, message: "请输入正确金额" },
|
||||
],
|
||||
fullRate: [{ required: true, validator: checkDiscount }],
|
||||
};
|
||||
const options = {
|
||||
disabledDate(date) {
|
||||
return date && date.valueOf() < Date.now() - 86400000;
|
||||
},
|
||||
};
|
||||
|
||||
// formRef:模板 ref 改名避免与顶层变量互相覆盖
|
||||
const formRef = ref(null);
|
||||
const liliDialogRef = ref(null);
|
||||
|
||||
const fieldDisabled = computed(() => {
|
||||
return !!id.value && form.value.promotionStatus !== "NEW";
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (id.value) {
|
||||
getDetail();
|
||||
}
|
||||
getCouponList();
|
||||
getGiftList();
|
||||
});
|
||||
|
||||
function giftOptionLabel(item) {
|
||||
if (!item) return "";
|
||||
const typeLabel =
|
||||
item.goodsType === "E_COUPON" ? ` [${goodsTypeLabel(item.goodsType)}]` : "";
|
||||
const stock =
|
||||
item.goodsType === "E_COUPON" && item.quantity != null
|
||||
? ` · 库存${item.quantity}`
|
||||
: "";
|
||||
return `${item.goodsName || ""}${typeLabel}${stock}`;
|
||||
}
|
||||
function closeCurrentPage() {
|
||||
router.back();
|
||||
}
|
||||
function openSkuList() {
|
||||
const data = JSON.parse(JSON.stringify(form.value.promotionGoodsList || []));
|
||||
data.forEach((e) => {
|
||||
e.id = e.skuId || e.id;
|
||||
});
|
||||
// 先写入已选商品,再打开选择器,保证回显时 selectedWay 已就绪
|
||||
liliDialogRef.value.goodsData = data;
|
||||
liliDialogRef.value.open("goods");
|
||||
}
|
||||
function getDetail() {
|
||||
getFullDiscountById(id.value).then((res) => {
|
||||
const data = res.result;
|
||||
if (data.scopeType === "ALL") {
|
||||
data.promotionGoodsList = [];
|
||||
}
|
||||
if (data.fullMinusFlag) {
|
||||
data.discountType = "fullMinusFlag";
|
||||
delete data.fullMinusFlag;
|
||||
} else {
|
||||
data.discountType = "fullRateFlag";
|
||||
delete data.fullRateFlag;
|
||||
}
|
||||
data.rangeTime = [new Date(data.startTime), new Date(data.endTime)];
|
||||
if (data.fullRate !== null && data.fullRate !== undefined && data.fullRate !== "") {
|
||||
data.fullRate = parseFloat(Number(data.fullRate).toFixed(1));
|
||||
}
|
||||
form.value = data;
|
||||
});
|
||||
}
|
||||
function handleSubmit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (!valid) return;
|
||||
const params = JSON.parse(JSON.stringify(form.value));
|
||||
params.startTime = unixToDate(form.value.rangeTime[0] / 1000);
|
||||
params.endTime = unixToDate(form.value.rangeTime[1] / 1000);
|
||||
|
||||
if (params.couponFlag && !params.couponId) {
|
||||
Message.warning("请选择优惠券");
|
||||
return;
|
||||
}
|
||||
if (params.giftFlag && !params.giftId) {
|
||||
Message.warning("请选择赠品");
|
||||
return;
|
||||
}
|
||||
if (params.pointFlag && !params.point) {
|
||||
Message.warning("请填写积分");
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
params.scopeType === "PORTION_GOODS" &&
|
||||
(!params.promotionGoodsList || params.promotionGoodsList.length === 0)
|
||||
) {
|
||||
Message.warning("请选择指定商品");
|
||||
return;
|
||||
}
|
||||
if (params.scopeType === "ALL") {
|
||||
delete params.promotionGoodsList;
|
||||
params.number = -1;
|
||||
} else {
|
||||
const scopeId = [];
|
||||
params.number = 1;
|
||||
params.promotionGoodsList.forEach((e) => {
|
||||
e.startTime = params.startTime;
|
||||
e.endTime = params.endTime;
|
||||
scopeId.push(e.skuId);
|
||||
});
|
||||
params.scopeId = scopeId.toString();
|
||||
}
|
||||
if (params.discountType === "fullMinusFlag") {
|
||||
params.fullMinusFlag = true;
|
||||
} else {
|
||||
params.fullRateFlag = true;
|
||||
}
|
||||
delete params.discountType;
|
||||
delete params.rangeTime;
|
||||
if (!id.value) {
|
||||
delete params.id;
|
||||
} else {
|
||||
delete params.updateTime;
|
||||
}
|
||||
|
||||
submitLoading.value = true;
|
||||
const request = id.value ? editFullDiscount(params) : newFullDiscount(params);
|
||||
request.then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success(id.value ? "编辑活动成功" : "添加活动成功");
|
||||
closeCurrentPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function changeSelect(e) {
|
||||
selectedGoods.value = e;
|
||||
}
|
||||
function delSelectGoods() {
|
||||
if (selectedGoods.value.length <= 0) {
|
||||
Message.warning("您还未选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "您确认要删除所选商品吗?",
|
||||
onOk: () => {
|
||||
const ids = selectedGoods.value.map((e) => e.skuId || e.id);
|
||||
form.value.promotionGoodsList = form.value.promotionGoodsList.filter(
|
||||
(item) => !ids.includes(item.skuId)
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
function delGoods(index) {
|
||||
form.value.promotionGoodsList.splice(index, 1);
|
||||
}
|
||||
function selectedGoodsData(item) {
|
||||
const list = [];
|
||||
item.forEach((e) => {
|
||||
list.push({
|
||||
goodsName: e.goodsName,
|
||||
price: e.price,
|
||||
quantity: e.quantity,
|
||||
storeId: e.storeId,
|
||||
storeName: e.storeName,
|
||||
thumbnail: e.thumbnail,
|
||||
skuId: e.id,
|
||||
goodsId: e.goodsId,
|
||||
goodsType: e.goodsType,
|
||||
});
|
||||
});
|
||||
form.value.promotionGoodsList = list;
|
||||
}
|
||||
function getCouponList(query) {
|
||||
const params = {
|
||||
pageSize: 20,
|
||||
pageNumber: 1,
|
||||
getType: "ACTIVITY",
|
||||
couponName: query,
|
||||
promotionStatus: "START",
|
||||
};
|
||||
couponLoading.value = true;
|
||||
getShopCouponList(params).then((res) => {
|
||||
couponLoading.value = false;
|
||||
if (res.success) {
|
||||
couponList.value = res.result.records;
|
||||
}
|
||||
});
|
||||
}
|
||||
function getGiftList(query) {
|
||||
const params = {
|
||||
pageSize: 20,
|
||||
pageNumber: 1,
|
||||
id: query === form.value.giftId ? form.value.giftId : null,
|
||||
goodsName: query === form.value.giftId ? null : query,
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
};
|
||||
giftLoading.value = true;
|
||||
getGoodsSkuListDataSeller(params).then((res) => {
|
||||
giftLoading.value = false;
|
||||
if (res.success) {
|
||||
giftList.value = res.result.records;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user