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:
lifenlong
2026-10-13 07:06:59 +08:00
parent 870bacdd8c
commit 88fc1b0b2a
674 changed files with 71220 additions and 69943 deletions

View File

@@ -76,7 +76,9 @@
</div>
</template>
<script>
<script setup>
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { UserFilled } from "@element-plus/icons-vue";
import shrinkableMenu from "./main-parts/shrinkable-menu/shrinkable-menu.vue";
import tagsPageOpened from "./main-parts/tags-page-opened.vue";
@@ -87,123 +89,108 @@ import util from "@/libs/util.js";
import { getNoticePage, logout } from "@/api/index";
import config from "@/config/index.js";
import defaultLogo from "@/assets/logo.png";
import { setStore } from "@/libs/storage";
import { useAppStore } from "@/stores/app";
import { useUserStore } from "@/stores/user";
import { useSettingStore } from "@/stores/setting";
export default {
components: {
UserFilled,
shrinkableMenu,
tagsPageOpened,
messageTip,
circleLoading,
},
data() {
return {
config,
sliceNum: 5,
userInfo: "",
tipsMessage: "",
defaultLogo,
domainLogo: "",
};
},
computed: {
setting() {
return this.$store.state.setting.setting;
},
loading() {
return this.$store.state.app.loading;
},
menuList() {
return this.$store.state.app.menuList || [];
},
pageTagsList() {
return this.$store.state.app.pageOpenedList;
},
avatarPath() {
return localStorage.avatorImgPath;
},
},
methods: {
init() {
const userInfo = JSON.parse(Cookies.get("userInfoManager"));
this.userInfo = userInfo;
this.checkTag(this.$route.name);
this.domainLogo = localStorage.getItem("icon") || this.defaultLogo;
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = localStorage.getItem("domainIcon");
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
window.document.title = localStorage.getItem("title") + " - 运营后台";
getNoticePage({}).then((res) => {
if (res.success) {
this.tipsMessage = res.result;
this.$store.state.notices = res.result;
}
});
},
handleClickUserDropdown(name) {
if (name === "personalCenter") {
util.openNewPage(this, "personal-center");
this.$router.push({ name: "personal-center" });
} else if (name === "changePass") {
util.openNewPage(this, "change-password");
this.$router.push({ name: "change_password" });
} else if (name === "loginOut") {
logout().then(() => {
this.$store.commit("logout", this);
this.$store.commit("setAdded", false);
this.setStore("accessToken", "");
this.setStore("refreshToken", "");
this.$router.push({ path: "/login" });
});
}
},
checkTag(name) {
const openpageHasTag = util.findOpenedPageIndex(
this.pageTagsList,
name,
this.$route.path
) >= 0;
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;
},
onLogoError(e) {
if (e?.target && e.target.src !== this.defaultLogo) {
e.target.src = this.defaultLogo;
}
},
},
watch: {
$route(to) {
this.checkTag(to.name);
localStorage.currentPageName = to.name;
},
},
mounted() {
this.init();
this.resize();
window.addEventListener("resize", this.resize);
this.$store.commit("setOpenedList");
},
beforeUnmount() {
window.removeEventListener("resize", this.resize);
},
created() {
this.$store.commit("setOpenedList");
},
};
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const userStore = useUserStore();
const settingStore = useSettingStore();
const sliceNum = ref(5);
const userInfo = ref("");
const tipsMessage = ref("");
const domainLogo = ref("");
const setting = computed(() => settingStore.setting);
const loading = computed(() => appStore.loading);
const menuList = computed(() => appStore.menuList || []);
const pageTagsList = computed(() => appStore.pageOpenedList);
const avatarPath = computed(() => localStorage.avatorImgPath);
function init() {
const userInfoData = JSON.parse(Cookies.get("userInfoManager"));
userInfo.value = userInfoData;
checkTag(route.name);
domainLogo.value = localStorage.getItem("icon") || defaultLogo;
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = localStorage.getItem("domainIcon");
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
window.document.title = localStorage.getItem("title") + " - 运营后台";
getNoticePage({}).then((res) => {
if (res.success) {
tipsMessage.value = res.result;
appStore.notices = res.result;
}
});
}
function handleClickUserDropdown(name) {
if (name === "personalCenter") {
util.openNewPage("personal-center");
router.push({ name: "personal-center" });
} else if (name === "changePass") {
util.openNewPage("change-password");
router.push({ name: "change_password" });
} else if (name === "loginOut") {
logout().then(() => {
userStore.logout();
appStore.setAdded(false);
setStore("accessToken", "");
setStore("refreshToken", "");
router.push({ path: "/login" });
});
}
}
function checkTag(name) {
const openpageHasTag = util.findOpenedPageIndex(
pageTagsList.value,
name,
route.path
) >= 0;
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;
}
function onLogoError(e) {
if (e?.target && e.target.src !== defaultLogo) {
e.target.src = defaultLogo;
}
}
watch(
() => route.name,
(name) => {
checkTag(name);
localStorage.currentPageName = name;
}
);
appStore.setOpenedList();
onMounted(() => {
init();
resize();
window.addEventListener("resize", resize);
appStore.setOpenedList();
});
onBeforeUnmount(() => {
window.removeEventListener("resize", resize);
});
</script>

View File

@@ -19,52 +19,48 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import { getAppointmentOrderSettings, saveAppointmentOrderSettings } from "@/api/appointment";
import { ElMessage } from "element-plus";
export default {
name: "appointmentOrderSettings",
data() {
return {
loading: false,
saving: false,
form: {
serviceStartRemindMemberHours: 2,
inStoreNoShowAutoCompleteHours: 24,
},
};
},
mounted() {
this.loadData();
},
methods: {
loadData() {
this.loading = true;
getAppointmentOrderSettings()
.then((res) => {
if (res.success && res.result) {
this.form = { ...this.form, ...res.result };
}
})
.finally(() => {
this.loading = false;
});
},
handleSave() {
this.saving = true;
saveAppointmentOrderSettings(this.form)
.then((res) => {
if (res.success) {
ElMessage.success("保存成功");
}
})
.finally(() => {
this.saving = false;
});
},
},
};
defineOptions({ name: "appointmentOrderSettings" });
const loading = ref(false);
const saving = ref(false);
const form = ref({
serviceStartRemindMemberHours: 2,
inStoreNoShowAutoCompleteHours: 24,
});
onMounted(() => {
loadData();
});
function loadData() {
loading.value = true;
getAppointmentOrderSettings()
.then((res) => {
if (res.success && res.result) {
form.value = { ...form.value, ...res.result };
}
})
.finally(() => {
loading.value = false;
});
}
function handleSave() {
saving.value = true;
saveAppointmentOrderSettings(form.value)
.then((res) => {
if (res.success) {
ElMessage.success("保存成功");
}
})
.finally(() => {
saving.value = false;
});
}
</script>
<style scoped>

View File

@@ -43,9 +43,15 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { getManagerWorkOrderPage } from "@/api/appointment";
defineOptions({ name: "managerAppointmentWorkOrders" });
const route = useRoute();
const STATUS_OPTIONS = [
{ label: "未完成", value: "OPEN" },
{ label: "待领取", value: "PENDING_CLAIM" },
@@ -57,52 +63,45 @@ const STATUS_OPTIONS = [
{ label: "逾期未到店", value: "NO_SHOW" },
];
export default {
name: "managerAppointmentWorkOrders",
data() {
return {
loading: false,
data: [],
total: 0,
statusOptions: STATUS_OPTIONS,
searchForm: {
pageNumber: 1,
pageSize: 10,
orderSn: "",
workOrderSn: "",
serviceStatus: "",
},
};
},
mounted() {
const status = this.$route.query.serviceStatus;
if (status) {
this.searchForm.serviceStatus = status;
}
this.getData();
},
methods: {
statusText(value) {
const found = STATUS_OPTIONS.find((item) => item.value === value);
return found ? found.label : value || "—";
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getData();
},
getData() {
this.loading = true;
getManagerWorkOrderPage(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result?.records || [];
this.total = res.result?.total || 0;
}
})
.finally(() => {
this.loading = false;
});
},
},
};
const loading = ref(false);
const data = ref([]);
const total = ref(0);
const statusOptions = ref(STATUS_OPTIONS);
const searchForm = ref({
pageNumber: 1,
pageSize: 10,
orderSn: "",
workOrderSn: "",
serviceStatus: "",
});
onMounted(() => {
const status = route.query.serviceStatus;
if (status) {
searchForm.value.serviceStatus = status;
}
getData();
});
function statusText(value) {
const found = STATUS_OPTIONS.find((item) => item.value === value);
return found ? found.label : value || "—";
}
function handleSearch() {
searchForm.value.pageNumber = 1;
getData();
}
function getData() {
loading.value = true;
getManagerWorkOrderPage(searchForm.value)
.then((res) => {
if (res.success) {
data.value = res.result?.records || [];
total.value = res.result?.total || 0;
}
})
.finally(() => {
loading.value = false;
});
}
</script>

View File

@@ -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"
@@ -39,80 +39,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-password",
components: {
SetPassword,
},
data() {
const valideRePassword = (rule, value, callback) => {
if (value !== this.editPasswordForm.newPassword) {
callback(new Error("两次输入密码不一致"));
} else {
callback();
}
};
return {
savePassLoading: false,
editPasswordForm: {
oldPassword: "",
newPassword: "",
rePassword: "",
},
strength: "",
passwordValidate: {
oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
newPassword: [
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 6, message: "请至少输入6个字符", trigger: "blur" },
{ max: 32, message: "最多输入32个字符", trigger: "blur" },
],
rePassword: [
{ required: true, message: "请再次输入新密码", trigger: "blur" },
{ validator: valideRePassword, trigger: "blur" },
],
},
};
},
methods: {
changeInputPass(v, grade, strength) {
this.strength = strength;
},
saveEditPass() {
const params = {
password: this.md5(this.editPasswordForm.oldPassword),
newPassword: this.md5(this.editPasswordForm.newPassword),
passStrength: this.md5(this.strength),
};
this.$refs.editPasswordForm.validate((valid) => {
if (!valid) return;
this.savePassLoading = true;
changePass(params).then((res) => {
this.savePassLoading = false;
if (res.success) {
ElMessageBox.alert("修改密码成功,需重新登录", "修改密码成功", {
confirmButtonText: "确定",
callback: () => {
this.$store.commit("logout", this);
this.$router.push({ name: "login" });
},
});
}
});
});
},
cancelEditPass() {
this.$store.commit("removeTag", "change_password");
localStorage.pageOpenedList = JSON.stringify(this.$store.state.app.pageOpenedList);
const list = this.$store.state.app.pageOpenedList;
const lastPageName = list.length > 1 ? list[list.length - 1].name : list[0].name;
this.$router.push({ name: lastPageName });
},
},
defineOptions({ name: "change-password" });
const router = useRouter();
const appStore = useAppStore();
const userStore = useUserStore();
const editPasswordFormRef = ref(null);
const savePassLoading = ref(false);
const editPasswordForm = reactive({
oldPassword: "",
newPassword: "",
rePassword: "",
});
const strength = ref("");
const valideRePassword = (rule, value, callback) => {
if (value !== editPasswordForm.newPassword) {
callback(new Error("两次输入密码不一致"));
} else {
callback();
}
};
const passwordValidate = {
oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
newPassword: [
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 6, message: "请至少输入6个字符", trigger: "blur" },
{ max: 32, message: "最多输入32个字符", trigger: "blur" },
],
rePassword: [
{ required: true, message: "请再次输入新密码", trigger: "blur" },
{ validator: valideRePassword, trigger: "blur" },
],
};
function changeInputPass(v, grade, strengthVal) {
strength.value = strengthVal;
}
function saveEditPass() {
const params = {
password: md5(editPasswordForm.oldPassword),
newPassword: md5(editPasswordForm.newPassword),
passStrength: md5(strength.value),
};
editPasswordFormRef.value.validate((valid) => {
if (!valid) return;
savePassLoading.value = true;
changePass(params).then((res) => {
savePassLoading.value = false;
if (res.success) {
ElMessageBox.alert("修改密码成功,需重新登录", "修改密码成功", {
confirmButtonText: "确定",
callback: () => {
userStore.logout();
router.push({ name: "login" });
},
});
}
});
});
}
function cancelEditPass() {
appStore.removeTag("change_password");
localStorage.pageOpenedList = JSON.stringify(appStore.pageOpenedList);
const list = appStore.pageOpenedList;
const lastPageName = list.length > 1 ? list[list.length - 1].name : list[0].name;
router.push({ name: lastPageName });
}
</script>

View File

@@ -51,7 +51,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<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="自定义分词" prop="name">
<el-input v-model="form.name" clearable style="width: 100%" />
</el-form-item>
@@ -64,7 +64,8 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import {
getCustomWordsPage,
delCustom,
@@ -72,160 +73,161 @@ import {
updateCustomWords,
} from "@/api/index";
import { regular } from "@/utils";
import { Message, Modal } from "@/utils/message";
export default {
name: "custom-words",
data() {
return {
loading: true,
modalType: 0,
modalVisible: false,
modalTitle: "",
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
words: "",
},
form: {
name: "",
},
formValidate: {
name: [regular.REQUIRED, regular.VARCHAR20],
},
submitLoading: false,
selectList: [],
selectCount: 0,
data: [],
total: 0,
};
},
methods: {
init() {
this.getDataList();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
clearSelectAll() {
this.$refs.table?.clearSelection();
},
changeSelect(e) {
this.selectList = e;
this.selectCount = e.length;
},
getDataList() {
this.loading = true;
getCustomWordsPage(this.searchForm)
.then((res) => {
defineOptions({ name: "custom-words" });
const table = ref(null);
const formRef = ref(null);
const loading = ref(true);
const modalType = ref(0);
const modalVisible = ref(false);
const modalTitle = ref("");
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
words: "",
});
const form = ref({
name: "",
});
const formValidate = reactive({
name: [regular.REQUIRED, regular.VARCHAR20],
});
const submitLoading = ref(false);
const selectList = ref([]);
const selectCount = ref(0);
const data = ref([]);
const total = ref(0);
// 未在 data 声明、detail() 中直接挂到 this 上的字段
let id;
function init() {
getDataList();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
clearSelectAll();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
getDataList();
}
function clearSelectAll() {
table.value?.clearSelection();
}
function changeSelect(e) {
selectList.value = e;
selectCount.value = e.length;
}
function getDataList() {
loading.value = true;
getCustomWordsPage(searchForm)
.then((res) => {
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
})
.finally(() => {
loading.value = false;
});
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
if (modalType.value == 0) {
delete form.value.id;
insertCustomWords(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
})
.finally(() => {
this.loading = false;
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
if (this.modalType == 0) {
delete this.form.id;
insertCustomWords(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
this.form.id = this.id;
updateCustomWords(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
form.value.id = id;
updateCustomWords(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}
}
});
}
function add() {
modalType.value = 0;
modalTitle.value = "添加";
form.value = {};
formRef.value?.resetFields();
modalVisible.value = true;
}
function detail(v) {
modalType.value = 1;
id = v.id;
modalTitle.value = "修改";
modalVisible.value = true;
form.value.name = v.name;
}
function remove(v) {
Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.name + " ?",
loading: true,
onOk: () => {
delCustom(v.id).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
add() {
this.modalType = 0;
this.modalTitle = "添加";
this.form = {};
this.$refs.form?.resetFields();
this.modalVisible = true;
},
detail(v) {
this.modalType = 1;
this.id = v.id;
this.modalTitle = "修改";
this.modalVisible = true;
this.form.name = v.name;
},
remove(v) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.name + " ?",
loading: true,
onOk: () => {
delCustom(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
}
function delAll() {
if (selectCount.value <= 0) {
Message.warning("您还未选择要删除的数据");
return;
}
Modal.confirm({
title: "确认删除",
content: "您确认要删除所选的 " + selectCount.value + " 条数据?",
loading: true,
onOk: () => {
let ids = "";
selectList.value.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1);
delCustom(ids).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
clearSelectAll();
getDataList();
}
});
},
delAll() {
if (this.selectCount <= 0) {
this.$Message.warning("您还未选择要删除的数据");
return;
}
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除所选的 " + this.selectCount + " 条数据?",
loading: true,
onOk: () => {
let ids = "";
this.selectList.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1);
delCustom(ids).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.clearSelectAll();
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
};
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="100px"
@@ -420,7 +420,9 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, watch, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
auditDistribution,
batchUpdateDistributionGrade,
@@ -436,469 +438,478 @@ import {
updateDistributionMember,
} from "@/api/distribution";
import { downloadBlob } from "@/utils/downloadBlob";
import { Message, Modal } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
defineOptions({ name: "distribution" });
const MOBILE_PATTERN = /^1[3-9]\d{9}$/;
export default {
name: "distribution",
data() {
return {
loading: true,
currentDistributionStatus: "ALL",
joinTimeRange: [],
gradeOptions: [],
searchForm: {
pageNumber: 1,
pageSize: 20,
id: "",
mobile: "",
inviterMobile: "",
gradeId: "",
nickname: "",
},
data: [],
total: 0,
detailVisible: false,
detailLoading: false,
detail: null,
editVisible: false,
editLoading: false,
editForm: {
id: "",
mobile: "",
nickname: "",
inviterMobile: "",
gradeId: "",
},
editRules: {
nickname: [{ required: true, message: "请输入昵称", trigger: "blur" }],
gradeId: [{ required: true, message: "请选择分销等级", trigger: "change" }],
},
createVisible: false,
createLoading: false,
createForm: {
nickname: "",
mobile: "",
inviterMobile: "",
gradeId: "",
},
createRules: {
nickname: [{ required: true, message: "请输入昵称", trigger: "blur" }],
mobile: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ pattern: MOBILE_PATTERN, message: "手机号格式有误", trigger: "blur" },
],
gradeId: [{ required: true, message: "请选择分销等级", trigger: "change" }],
},
selectedRows: [],
batchGradeVisible: false,
batchGradeLoading: false,
exportLoading: false,
templateLoading: false,
importVisible: false,
importLoading: false,
importFile: null,
importFileName: "",
batchGradeForm: {
gradeId: "",
},
batchGradeRules: {
gradeId: [{ required: true, message: "请选择分销等级", trigger: "change" }],
},
};
},
mounted() {
this.loadGradeOptions();
this.applyRouteQuery();
this.getDataList();
},
watch: {
$route() {
this.applyRouteQuery();
this.getDataList();
const route = useRoute();
const router = useRouter();
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const editFormRef = ref(null);
const createFormRef = ref(null);
const batchGradeFormRef = ref(null);
const loading = ref(true);
const currentDistributionStatus = ref("ALL");
const joinTimeRange = ref([]);
const gradeOptions = ref([]);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
id: "",
mobile: "",
inviterMobile: "",
gradeId: "",
nickname: "",
});
const data = ref([]);
const total = ref(0);
const detailVisible = ref(false);
const detailLoading = ref(false);
const detail = ref(null);
const editVisible = ref(false);
const editLoading = ref(false);
const editForm = ref({
id: "",
mobile: "",
nickname: "",
inviterMobile: "",
gradeId: "",
});
const editRules = {
nickname: [{ required: true, message: "请输入昵称", trigger: "blur" }],
gradeId: [{ required: true, message: "请选择分销等级", trigger: "change" }],
};
const createVisible = ref(false);
const createLoading = ref(false);
const createForm = ref({
nickname: "",
mobile: "",
inviterMobile: "",
gradeId: "",
});
const createRules = {
nickname: [{ required: true, message: "请输入昵称", trigger: "blur" }],
mobile: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ pattern: MOBILE_PATTERN, message: "手机号格式有误", trigger: "blur" },
],
gradeId: [{ required: true, message: "请选择分销等级", trigger: "change" }],
};
const selectedRows = ref([]);
const batchGradeVisible = ref(false);
const batchGradeLoading = ref(false);
const exportLoading = ref(false);
const templateLoading = ref(false);
const importVisible = ref(false);
const importLoading = ref(false);
const importFile = ref(null);
const importFileName = ref("");
const batchGradeForm = reactive({
gradeId: "",
});
const batchGradeRules = {
gradeId: [{ required: true, message: "请选择分销等级", trigger: "change" }],
};
watch(
() => route.query,
() => {
applyRouteQuery();
getDataList();
}
);
function applyRouteQuery() {
const query = route.query || {};
searchForm.inviterMobile = query.inviterMobile ? String(query.inviterMobile) : "";
}
function openCustomerRelationPage() {
if (!detail.value) return;
const resolved = router.resolve({
path: "/distributionCustomerRelation",
query: {
distributionId: detail.value.id,
distributionMobile: detail.value.mobile || "",
},
},
methods: {
applyRouteQuery() {
const query = this.$route.query || {};
this.searchForm.inviterMobile = query.inviterMobile ? String(query.inviterMobile) : "";
});
window.open(resolved.href, "_blank");
}
function openInviteeDistributionPage() {
if (!detail.value) return;
const resolved = router.resolve({
path: "/distribution",
query: {
inviterMobile: detail.value.mobile || "",
},
openCustomerRelationPage() {
if (!this.detail) return;
const route = this.$router.resolve({
path: "/distributionCustomerRelation",
query: {
distributionId: this.detail.id,
distributionMobile: this.detail.mobile || "",
},
});
window.open(route.href, "_blank");
},
openInviteeDistributionPage() {
if (!this.detail) return;
const route = this.$router.resolve({
path: "/distribution",
query: {
inviterMobile: this.detail.mobile || "",
},
});
window.open(route.href, "_blank");
},
loadGradeOptions() {
getDistributionGrades().then((res) => {
});
window.open(resolved.href, "_blank");
}
function loadGradeOptions() {
getDistributionGrades().then((res) => {
if (res.success) {
gradeOptions.value = (res.result || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
}
});
}
function statusLabel(status) {
const map = {
PASS: "审核通过",
APPLY: "待审核",
RETREAT: "已清退",
REFUSE: "审核拒绝",
};
return map[status] || status || "-";
}
function formatMoney(value) {
const num = Number(value);
if (Number.isNaN(num)) return "0.00";
return num.toFixed(2);
}
function onStatusTabChange() {
searchForm.pageNumber = 1;
clearSelectAll();
getDataList();
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.pageNumber = 1;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
clearSelectAll();
getDataList();
}
function changeSelect(selection) {
selectedRows.value = selection || [];
}
function clearSelectAll() {
selectedRows.value = [];
table.value?.clearSelection();
}
function openBatchGrade() {
if (!selectedRows.value.length) {
Message.warning("请先选择分销员");
return;
}
batchGradeForm.gradeId = "";
batchGradeVisible.value = true;
}
function resetBatchGradeForm() {
batchGradeLoading.value = false;
batchGradeForm.gradeId = "";
batchGradeFormRef.value?.clearValidate();
}
function submitBatchGrade() {
batchGradeFormRef.value.validate((valid) => {
if (!valid) return;
batchGradeLoading.value = true;
batchUpdateDistributionGrade({
ids: selectedRows.value.map((item) => item.id),
gradeId: batchGradeForm.gradeId,
})
.then((res) => {
batchGradeLoading.value = false;
if (res.success) {
this.gradeOptions = (res.result || []).slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
Message.success("设置成功");
batchGradeVisible.value = false;
clearSelectAll();
getDataList();
}
})
.catch(() => {
batchGradeLoading.value = false;
});
});
}
function handleExport() {
exportLoading.value = true;
const params = buildParams();
delete params.pageNumber;
delete params.pageSize;
exportDistributionMembers(params)
.then((blob) => {
if (blob && blob.type && blob.type.includes("application/json")) {
const reader = new FileReader();
reader.onload = () => {
try {
const result = JSON.parse(reader.result);
Message.error(result.message || "导出失败");
} catch (e) {
Message.error("导出失败");
}
};
reader.readAsText(blob);
return;
}
downloadBlob(blob, `分销员列表_${Date.now()}.xlsx`);
Message.success("导出成功");
})
.catch(() => {
Message.error("导出失败");
})
.finally(() => {
exportLoading.value = false;
});
}
function downloadImportTemplate() {
templateLoading.value = true;
downloadDistributionImportTemplate()
.then((blob) => {
downloadBlob(blob, "分销员批量导入模板.xlsx");
Message.success("模板已下载");
})
.catch(() => {
Message.error("模板下载失败");
})
.finally(() => {
templateLoading.value = false;
});
}
function openImportDialog() {
importVisible.value = true;
}
function resetImportDialog() {
importLoading.value = false;
importFile.value = null;
importFileName.value = "";
}
function onImportBeforeUpload(file) {
importFile.value = file;
importFileName.value = file.name;
return false;
}
function submitImport() {
if (!importFile.value) {
Message.warning("请先选择文件");
return;
}
const fd = new FormData();
fd.append("file", importFile.value);
importLoading.value = true;
importDistributionMembers(fd)
.then((res) => {
importLoading.value = false;
if (res.success) {
const result = res.result || {};
Message.success(
`导入完成:成功 ${result.successCount || 0} 条,失败 ${result.failCount || 0} 条`
);
importVisible.value = false;
getDataList();
}
})
.catch(() => {
importLoading.value = false;
});
}
function goImportRecord() {
router.push({
path: "/distributionImportRecord",
});
}
function buildParams() {
const params = {
pageNumber: searchForm.pageNumber,
pageSize: searchForm.pageSize,
};
if (currentDistributionStatus.value && currentDistributionStatus.value !== "ALL") {
params.distributionStatus = currentDistributionStatus.value;
}
if (searchForm.id) params.id = searchForm.id.trim();
if (searchForm.mobile) params.mobile = searchForm.mobile;
if (searchForm.inviterMobile) params.inviterMobile = searchForm.inviterMobile;
if (searchForm.gradeId) params.gradeId = searchForm.gradeId;
if (searchForm.nickname) params.nickname = searchForm.nickname;
if (joinTimeRange.value && joinTimeRange.value[0] && joinTimeRange.value[1]) {
const startTime = new Date(joinTimeRange.value[0]).getTime();
const endTime = new Date(joinTimeRange.value[1]).getTime();
params.joinTimeStart = unixToDate(startTime / 1000);
params.joinTimeEnd = unixToDate(endTime / 1000);
}
return params;
}
function getDataList() {
loading.value = true;
getDistributionMemberPage(buildParams())
.then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
})
.catch(() => {
loading.value = false;
});
}
function openDetail(row) {
detailVisible.value = true;
detailLoading.value = true;
detail.value = null;
getDistributionMemberDetail(row.id)
.then((res) => {
detailLoading.value = false;
if (res.success) {
detail.value = res.result;
}
})
.catch(() => {
detailLoading.value = false;
});
}
function openCreate() {
createForm.value = {
nickname: "",
mobile: "",
inviterMobile: "",
gradeId: "",
};
createVisible.value = true;
}
function resetCreateForm() {
createLoading.value = false;
createForm.value = {
nickname: "",
mobile: "",
inviterMobile: "",
gradeId: "",
};
createFormRef.value?.clearValidate();
}
function submitCreate() {
createFormRef.value.validate((valid) => {
if (!valid) return;
createLoading.value = true;
createDistributionMember({
nickname: createForm.value.nickname,
mobile: createForm.value.mobile,
inviterMobile: createForm.value.inviterMobile || "",
gradeId: createForm.value.gradeId,
})
.then((res) => {
createLoading.value = false;
if (res.success) {
Message.success("新增成功");
createVisible.value = false;
getDataList();
}
})
.catch(() => {
createLoading.value = false;
});
});
}
function openEdit(row) {
editForm.value = {
id: row.id,
mobile: row.mobile || "",
nickname: row.nickname || "",
inviterMobile: row.inviterMobile || "",
gradeId: resolveGradeOptionId(row.gradeId),
};
editVisible.value = true;
}
function resolveGradeOptionId(gradeRef) {
if (!gradeRef || !gradeOptions.value.length) return gradeRef || "";
const direct = gradeOptions.value.find((item) => item.id === gradeRef);
if (direct) return direct.id;
const bySort = gradeOptions.value.find((item) => String(item.sortOrder) === String(gradeRef));
return bySort ? bySort.id : gradeRef;
}
function resetEditForm() {
editLoading.value = false;
editForm.value = {
id: "",
mobile: "",
nickname: "",
inviterMobile: "",
gradeId: "",
};
editFormRef.value?.clearValidate();
}
function submitEdit() {
editFormRef.value.validate((valid) => {
if (!valid) return;
editLoading.value = true;
updateDistributionMember(editForm.value.id, {
nickname: editForm.value.nickname,
inviterMobile: editForm.value.inviterMobile || "",
gradeId: editForm.value.gradeId,
})
.then((res) => {
editLoading.value = false;
if (res.success) {
Message.success("保存成功");
editVisible.value = false;
getDataList();
}
})
.catch(() => {
editLoading.value = false;
});
});
}
function audit(row, status) {
const text = status === "PASS" ? "通过" : "拒绝";
Modal.confirm({
title: "审核确认",
content: `确认${text}该分销员申请?`,
loading: true,
onOk: () => {
auditDistribution(row.id, { status }).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
statusLabel(status) {
const map = {
PASS: "审核通过",
APPLY: "待审核",
RETREAT: "已清退",
REFUSE: "审核拒绝",
};
return map[status] || status || "-";
},
formatMoney(value) {
const num = Number(value);
if (Number.isNaN(num)) return "0.00";
return num.toFixed(2);
},
onStatusTabChange() {
this.searchForm.pageNumber = 1;
this.clearSelectAll();
this.getDataList();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.clearSelectAll();
this.getDataList();
},
changeSelect(selection) {
this.selectedRows = selection || [];
},
clearSelectAll() {
this.selectedRows = [];
this.$refs.table?.clearSelection();
},
openBatchGrade() {
if (!this.selectedRows.length) {
this.$Message.warning("请先选择分销员");
return;
}
this.batchGradeForm.gradeId = "";
this.batchGradeVisible = true;
},
resetBatchGradeForm() {
this.batchGradeLoading = false;
this.batchGradeForm.gradeId = "";
this.$refs.batchGradeFormRef?.clearValidate();
},
submitBatchGrade() {
this.$refs.batchGradeFormRef.validate((valid) => {
if (!valid) return;
this.batchGradeLoading = true;
batchUpdateDistributionGrade({
ids: this.selectedRows.map((item) => item.id),
gradeId: this.batchGradeForm.gradeId,
})
.then((res) => {
this.batchGradeLoading = false;
if (res.success) {
this.$Message.success("设置成功");
this.batchGradeVisible = false;
this.clearSelectAll();
this.getDataList();
}
})
.catch(() => {
this.batchGradeLoading = false;
});
});
}
function retreat(row) {
Modal.confirm({
title: "清退确认",
content: "确认清退该分销员?清退后将停止后续计佣。",
onOk: () => {
retreatDistribution(row.id).then((res) => {
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
handleExport() {
this.exportLoading = true;
const params = this.buildParams();
delete params.pageNumber;
delete params.pageSize;
exportDistributionMembers(params)
.then((blob) => {
if (blob && blob.type && blob.type.includes("application/json")) {
const reader = new FileReader();
reader.onload = () => {
try {
const result = JSON.parse(reader.result);
this.$Message.error(result.message || "导出失败");
} catch (e) {
this.$Message.error("导出失败");
}
};
reader.readAsText(blob);
return;
}
downloadBlob(blob, `分销员列表_${Date.now()}.xlsx`);
this.$Message.success("导出成功");
})
.catch(() => {
this.$Message.error("导出失败");
})
.finally(() => {
this.exportLoading = false;
});
},
downloadImportTemplate() {
this.templateLoading = true;
downloadDistributionImportTemplate()
.then((blob) => {
downloadBlob(blob, "分销员批量导入模板.xlsx");
this.$Message.success("模板已下载");
})
.catch(() => {
this.$Message.error("模板下载失败");
})
.finally(() => {
this.templateLoading = false;
});
},
openImportDialog() {
this.importVisible = true;
},
resetImportDialog() {
this.importLoading = false;
this.importFile = null;
this.importFileName = "";
},
onImportBeforeUpload(file) {
this.importFile = file;
this.importFileName = file.name;
return false;
},
submitImport() {
if (!this.importFile) {
this.$Message.warning("请先选择文件");
return;
}
const fd = new FormData();
fd.append("file", this.importFile);
this.importLoading = true;
importDistributionMembers(fd)
.then((res) => {
this.importLoading = false;
if (res.success) {
const result = res.result || {};
this.$Message.success(
`导入完成:成功 ${result.successCount || 0} 条,失败 ${result.failCount || 0} 条`
);
this.importVisible = false;
this.getDataList();
}
})
.catch(() => {
this.importLoading = false;
});
},
goImportRecord() {
this.$router.push({
path: "/distributionImportRecord",
});
}
function resume(row) {
Modal.confirm({
title: "恢复确认",
content: "确认恢复该分销员?",
onOk: () => {
resumeDistribution(row.id).then((res) => {
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
buildParams() {
const params = {
pageNumber: this.searchForm.pageNumber,
pageSize: this.searchForm.pageSize,
};
if (this.currentDistributionStatus && this.currentDistributionStatus !== "ALL") {
params.distributionStatus = this.currentDistributionStatus;
}
if (this.searchForm.id) params.id = this.searchForm.id.trim();
if (this.searchForm.mobile) params.mobile = this.searchForm.mobile;
if (this.searchForm.inviterMobile) params.inviterMobile = this.searchForm.inviterMobile;
if (this.searchForm.gradeId) params.gradeId = this.searchForm.gradeId;
if (this.searchForm.nickname) params.nickname = this.searchForm.nickname;
if (this.joinTimeRange && this.joinTimeRange[0] && this.joinTimeRange[1]) {
const startTime = new Date(this.joinTimeRange[0]).getTime();
const endTime = new Date(this.joinTimeRange[1]).getTime();
params.joinTimeStart = this.$filters.unixToDate(startTime / 1000);
params.joinTimeEnd = this.$filters.unixToDate(endTime / 1000);
}
return params;
},
getDataList() {
this.loading = true;
getDistributionMemberPage(this.buildParams())
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
})
.catch(() => {
this.loading = false;
});
},
openDetail(row) {
this.detailVisible = true;
this.detailLoading = true;
this.detail = null;
getDistributionMemberDetail(row.id)
.then((res) => {
this.detailLoading = false;
if (res.success) {
this.detail = res.result;
}
})
.catch(() => {
this.detailLoading = false;
});
},
openCreate() {
this.createForm = {
nickname: "",
mobile: "",
inviterMobile: "",
gradeId: "",
};
this.createVisible = true;
},
resetCreateForm() {
this.createLoading = false;
this.createForm = {
nickname: "",
mobile: "",
inviterMobile: "",
gradeId: "",
};
this.$refs.createFormRef?.clearValidate();
},
submitCreate() {
this.$refs.createFormRef.validate((valid) => {
if (!valid) return;
this.createLoading = true;
createDistributionMember({
nickname: this.createForm.nickname,
mobile: this.createForm.mobile,
inviterMobile: this.createForm.inviterMobile || "",
gradeId: this.createForm.gradeId,
})
.then((res) => {
this.createLoading = false;
if (res.success) {
this.$Message.success("新增成功");
this.createVisible = false;
this.getDataList();
}
})
.catch(() => {
this.createLoading = false;
});
});
},
openEdit(row) {
this.editForm = {
id: row.id,
mobile: row.mobile || "",
nickname: row.nickname || "",
inviterMobile: row.inviterMobile || "",
gradeId: this.resolveGradeOptionId(row.gradeId),
};
this.editVisible = true;
},
resolveGradeOptionId(gradeRef) {
if (!gradeRef || !this.gradeOptions.length) return gradeRef || "";
const direct = this.gradeOptions.find((item) => item.id === gradeRef);
if (direct) return direct.id;
const bySort = this.gradeOptions.find((item) => String(item.sortOrder) === String(gradeRef));
return bySort ? bySort.id : gradeRef;
},
resetEditForm() {
this.editLoading = false;
this.editForm = {
id: "",
mobile: "",
nickname: "",
inviterMobile: "",
gradeId: "",
};
this.$refs.editFormRef?.clearValidate();
},
submitEdit() {
this.$refs.editFormRef.validate((valid) => {
if (!valid) return;
this.editLoading = true;
updateDistributionMember(this.editForm.id, {
nickname: this.editForm.nickname,
inviterMobile: this.editForm.inviterMobile || "",
gradeId: this.editForm.gradeId,
})
.then((res) => {
this.editLoading = false;
if (res.success) {
this.$Message.success("保存成功");
this.editVisible = false;
this.getDataList();
}
})
.catch(() => {
this.editLoading = false;
});
});
},
audit(row, status) {
const text = status === "PASS" ? "通过" : "拒绝";
this.$Modal.confirm({
title: "审核确认",
content: `确认${text}该分销员申请?`,
loading: true,
onOk: () => {
auditDistribution(row.id, { status }).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
retreat(row) {
this.$Modal.confirm({
title: "清退确认",
content: "确认清退该分销员?清退后将停止后续计佣。",
onOk: () => {
retreatDistribution(row.id).then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
resume(row) {
this.$Modal.confirm({
title: "恢复确认",
content: "确认恢复该分销员?",
onOk: () => {
resumeDistribution(row.id).then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
},
};
});
}
onMounted(() => {
loadGradeOptions();
applyRouteQuery();
getDataList();
});
</script>
<style scoped lang="scss">

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
class="search-form"
@keyup.enter="handleSearch"
@@ -173,144 +173,147 @@
</div>
</template>
<script>
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRoute } from "vue-router";
import { auditDistributionCash, getDistributionCash } from "@/api/distribution";
import { Message, Modal } from "@/utils/message";
export default {
name: "distributionCash",
data() {
return {
tabStatus: "",
applyCount: 0,
loading: true,
drawerVisible: false,
drawerTitle: "",
result: "VIA_AUDITING",
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
},
handleStatus: "edit",
form: {
sn: "",
distributionId: "",
distributionName: "",
price: "",
name: "",
settlementBankAccountName: "",
settlementBankAccountNum: "",
},
submitLoading: false,
data: [],
total: 0,
};
},
computed: {
applyCountLabel() {
return this.applyCount > 0 ? `待处理(${this.applyCount})` : "待处理";
},
},
methods: {
init() {
this.getDataList();
this.loadApplyCount();
},
loadApplyCount() {
getDistributionCash({
distributionCashStatus: "APPLY",
pageSize: 1,
pageNumber: 1,
}).then((res) => {
if (res.success) {
this.applyCount = res.result.total || 0;
}
});
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
getDataList() {
this.loading = true;
const params = { ...this.searchForm };
if (this.tabStatus) {
params.distributionCashStatus = this.tabStatus;
}
getDistributionCash(params).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
handleTabChange(name) {
this.tabStatus = name;
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSubmit() {
let result = this.result == "VIA_AUDITING" ? "通过" : "拒绝";
this.$Modal.confirm({
title: "确认审核",
content: "您确认要审核" + result + "么?",
loading: true,
onOk: () => {
auditDistributionCash(this.form.id, { result: this.result }).then((res) => {
if (res.success) {
this.$Modal.remove();
this.$Message.success("审核成功");
this.drawerVisible = false;
this.getDataList();
this.loadApplyCount();
} else {
this.$Modal.remove();
}
});
},
});
},
edit(v) {
this.drawerTitle = "审核";
this.handleStatus = "edit";
this.result = "VIA_AUDITING";
const data = {};
for (let attr in v) {
data[attr] = v[attr] === null ? "" : v[attr];
}
this.form = data;
this.drawerVisible = true;
},
view(v) {
this.drawerTitle = "查看";
this.handleStatus = "view";
const data = {};
for (let attr in v) {
data[attr] = v[attr] === null ? "" : v[attr];
}
this.form = data;
this.drawerVisible = true;
},
},
mounted() {
// 处理路由参数,自动跳转到指定标签页
const tabStatus = this.$route.query.tabStatus;
if (tabStatus) {
this.tabStatus = tabStatus;
defineOptions({ name: "distributionCash" });
const route = useRoute();
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const tabStatus = ref("");
const applyCount = ref(0);
const loading = ref(true);
const drawerVisible = ref(false);
const drawerTitle = ref("");
const result = ref("VIA_AUDITING");
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
});
const handleStatus = ref("edit");
const form = ref({
sn: "",
distributionId: "",
distributionName: "",
price: "",
name: "",
settlementBankAccountName: "",
settlementBankAccountNum: "",
});
const submitLoading = ref(false);
const data = ref([]);
const total = ref(0);
const applyCountLabel = computed(() => {
return applyCount.value > 0 ? `待处理(${applyCount.value})` : "待处理";
});
function init() {
getDataList();
loadApplyCount();
}
function loadApplyCount() {
getDistributionCash({
distributionCashStatus: "APPLY",
pageSize: 1,
pageNumber: 1,
}).then((res) => {
if (res.success) {
applyCount.value = res.result.total || 0;
}
this.init();
},
};
});
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.value.pageNumber = 1;
getDataList();
}
function handleSearch() {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = 20;
getDataList();
}
function getDataList() {
loading.value = true;
const params = { ...searchForm.value };
if (tabStatus.value) {
params.distributionCashStatus = tabStatus.value;
}
getDistributionCash(params).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function handleTabChange(name) {
tabStatus.value = name;
searchForm.value.pageNumber = 1;
getDataList();
}
function handleSubmit() {
let resultText = result.value == "VIA_AUDITING" ? "通过" : "拒绝";
Modal.confirm({
title: "确认审核",
content: "您确认要审核" + resultText + "么?",
loading: true,
onOk: () => {
auditDistributionCash(form.value.id, { result: result.value }).then((res) => {
if (res.success) {
Modal.remove();
Message.success("审核成功");
drawerVisible.value = false;
getDataList();
loadApplyCount();
} else {
Modal.remove();
}
});
},
});
}
function edit(v) {
drawerTitle.value = "审核";
handleStatus.value = "edit";
result.value = "VIA_AUDITING";
const formData = {};
for (let attr in v) {
formData[attr] = v[attr] === null ? "" : v[attr];
}
form.value = formData;
drawerVisible.value = true;
}
function view(v) {
drawerTitle.value = "查看";
handleStatus.value = "view";
const formData = {};
for (let attr in v) {
formData[attr] = v[attr] === null ? "" : v[attr];
}
form.value = formData;
drawerVisible.value = true;
}
onMounted(() => {
// 处理路由参数,自动跳转到指定标签页
const queryTabStatus = route.query.tabStatus;
if (queryTabStatus) {
tabStatus.value = queryTabStatus;
}
init();
});
</script>
<style lang="scss" scoped>
.ops a {

View File

@@ -0,0 +1,359 @@
<template>
<div class="search">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="客户绑定" name="customer">
<el-card>
<el-form ref="searchFormRef" :model="searchForm" inline class="search-form" @keyup.enter="handleSearch">
<el-form-item label="客户昵称">
<el-input v-model="searchForm.memberName" clearable placeholder="客户昵称" style="width: 180px" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="searchForm.memberMobile" clearable placeholder="手机号" style="width: 180px" />
</el-form-item>
<el-form-item label="分销员">
<el-input v-model="searchForm.distributionName" clearable placeholder="分销员名称" style="width: 180px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" clearable style="width: 120px">
<el-option v-for="item in bindStatusList" :key="item.value" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item label="来源">
<el-select v-model="searchForm.bindSource" clearable style="width: 140px">
<el-option v-for="item in bindSourceList" :key="item.value" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<el-table v-loading="loading" border :data="data" class="mt_10">
<el-table-column prop="memberName" label="客户昵称" min-width="120" />
<el-table-column prop="memberMobile" label="手机号" min-width="120" />
<el-table-column prop="distributionName" label="归属分销员" min-width="120" />
<el-table-column label="绑定来源" min-width="100">
<template #default="{ row }">{{ bindSourceLabel(row.bindSource) }}</template>
</el-table-column>
<el-table-column label="绑定类型" min-width="90">
<template #default="{ row }">
{{ row.bindType === 'PERMANENT' ? '永久' : '短期' }}
</template>
</el-table-column>
<el-table-column prop="bindStartTime" label="绑定时间" min-width="160" />
<el-table-column prop="bindExpireTime" label="到期时间" min-width="160" />
<el-table-column label="保护期剩余" min-width="100">
<template #default="{ row }">
<span v-if="row.protectRemainDays != null">{{ row.protectRemainDays }} 天</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag v-if="row.status === 'ACTIVE'" type="success">有效</el-tag>
<el-tag v-else type="info">已过期</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template #default="{ row }">
<a class="link-text" @click="openRebind(row)">换绑</a>
<span class="split">|</span>
<a class="link-text" @click="viewMemberLog(row)">日志</a>
</template>
</el-table-column>
</el-table>
<div class="mt_10 page padding-row" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@current-change="getDataList"
@size-change="handlePageSizeChange"
/>
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label="绑定日志" name="log">
<el-card>
<el-form ref="logSearchFormRef" :model="logSearchForm" inline class="search-form" @keyup.enter="handleLogSearch">
<el-form-item label="客户昵称">
<el-input v-model="logSearchForm.memberName" clearable placeholder="客户昵称" style="width: 180px" />
</el-form-item>
<el-form-item label="变更类型">
<el-select v-model="logSearchForm.changeType" clearable style="width: 140px">
<el-option v-for="item in bindChangeTypeList" :key="item.value" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item label="时间">
<el-date-picker
v-model="logDateRange"
type="daterange"
value-format="YYYY-MM-DD"
start-placeholder="开始"
end-placeholder="结束"
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<el-table v-loading="logLoading" border :data="logData" class="mt_10">
<el-table-column prop="createTime" label="时间" min-width="160" />
<el-table-column prop="memberName" label="客户" min-width="100" />
<el-table-column prop="fromDistributionName" label="原分销员" min-width="110" />
<el-table-column prop="toDistributionName" label="新分销员" min-width="110" />
<el-table-column label="变更类型" min-width="100">
<template #default="{ row }">{{ bindChangeTypeLabel(row.changeType) }}</template>
</el-table-column>
<el-table-column prop="operatorType" label="操作方" width="90" />
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
</el-table>
<div class="mt_10 page padding-row" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="logSearchForm.pageNumber"
v-model:page-size="logSearchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="logTotal"
layout="total, sizes, prev, pager, next, jumper"
@current-change="getLogList"
@size-change="handleLogPageSizeChange"
/>
</div>
</el-card>
</el-tab-pane>
</el-tabs>
<el-dialog v-model="rebindVisible" title="手动换绑" width="520px">
<el-form ref="rebindFormRef" :model="rebindForm" :rules="rebindRules" label-width="100px">
<el-form-item label="客户">
<el-input :model-value="rebindForm.memberLabel" disabled />
</el-form-item>
<el-form-item label="新分销员" prop="distributionId">
<el-select
v-model="rebindForm.distributionId"
filterable
remote
reserve-keyword
placeholder="输入分销员名称搜索"
:remote-method="searchDistributor"
:loading="distributorLoading"
style="width: 100%"
>
<el-option
v-for="item in distributorOptions"
:key="item.id"
:label="item.memberName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="rebindForm.remark" type="textarea" :rows="3" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="rebindVisible = false">取消</el-button>
<el-button type="primary" :loading="rebindSubmitting" @click="submitRebind">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import {
getDistributionCustomerPage,
getDistributionBindLogPage,
getDistributionListData,
rebindDistributionCustomer,
} from "@/api/distribution";
import { bindStatusList, bindSourceList, bindChangeTypeList } from "./dataJson";
import { Message } from "@/utils/message";
defineOptions({ name: "distributionCustomer" });
// 模板 el-form 的 ref(原 ref="searchForm"/"logSearchForm",与 data 同名会互相覆盖,改名为 searchFormRef/logSearchFormRef)
const searchFormRef = ref(null);
const logSearchFormRef = ref(null);
const rebindFormRef = ref(null);
const activeTab = ref("customer");
const loading = ref(false);
const data = ref([]);
const total = ref(0);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "bindStartTime",
order: "desc",
memberName: "",
memberMobile: "",
distributionName: "",
status: "",
bindSource: "",
});
const logLoading = ref(false);
const logData = ref([]);
const logTotal = ref(0);
const logDateRange = ref([]);
const logSearchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
memberName: "",
memberId: "",
changeType: "",
startDate: "",
endDate: "",
});
const rebindVisible = ref(false);
const rebindSubmitting = ref(false);
const distributorLoading = ref(false);
const distributorOptions = ref([]);
const rebindForm = ref({
memberId: "",
memberLabel: "",
distributionId: "",
remark: "",
});
const rebindRules = {
distributionId: [{ required: true, message: "请选择分销员", trigger: "change" }],
};
function bindSourceLabel(value) {
const item = bindSourceList.find((i) => i.value === value);
return item ? item.label : value;
}
function bindChangeTypeLabel(value) {
const item = bindChangeTypeList.find((i) => i.value === value);
return item ? item.label : value;
}
function handleTabChange(name) {
if (name === "log" && logData.value.length === 0) {
getLogList();
}
}
function handleSearch() {
searchForm.pageNumber = 1;
getDataList();
}
function handlePageSizeChange() {
searchForm.pageNumber = 1;
getDataList();
}
function getDataList() {
loading.value = true;
getDistributionCustomerPage(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function handleLogSearch() {
logSearchForm.pageNumber = 1;
if (logDateRange.value && logDateRange.value.length === 2) {
logSearchForm.startDate = logDateRange.value[0];
logSearchForm.endDate = logDateRange.value[1];
} else {
logSearchForm.startDate = "";
logSearchForm.endDate = "";
}
getLogList();
}
function handleLogPageSizeChange() {
logSearchForm.pageNumber = 1;
getLogList();
}
function getLogList() {
logLoading.value = true;
getDistributionBindLogPage(logSearchForm).then((res) => {
logLoading.value = false;
if (res.success) {
logData.value = res.result.records;
logTotal.value = res.result.total;
}
});
}
function openRebind(row) {
rebindForm.value = {
memberId: row.memberId,
memberLabel: `${row.memberName || ""}${row.memberMobile ? " / " + row.memberMobile : ""}`,
distributionId: "",
remark: "",
};
distributorOptions.value = [];
rebindVisible.value = true;
}
function searchDistributor(query) {
if (!query) {
distributorOptions.value = [];
return;
}
distributorLoading.value = true;
getDistributionListData({
memberName: query,
distributionStatus: "PASS",
pageNumber: 1,
pageSize: 20,
}).then((res) => {
distributorLoading.value = false;
if (res.success) {
distributorOptions.value = (res.result.records || []).filter((item) => item.id !== rebindForm.value.distributionId);
}
});
}
function submitRebind() {
rebindFormRef.value.validate((valid) => {
if (!valid) return;
rebindSubmitting.value = true;
rebindDistributionCustomer({
memberId: rebindForm.value.memberId,
distributionId: rebindForm.value.distributionId,
remark: rebindForm.value.remark,
}).then((res) => {
rebindSubmitting.value = false;
if (res.success) {
Message.success("换绑成功");
rebindVisible.value = false;
getDataList();
}
}).catch(() => {
rebindSubmitting.value = false;
});
});
}
function viewMemberLog(row) {
activeTab.value = "log";
logSearchForm.memberId = row.memberId;
logSearchForm.memberName = "";
logSearchForm.pageNumber = 1;
getLogList();
}
onMounted(() => {
getDataList();
});
</script>
<style lang="scss" scoped>
.link-text {
color: #2d8cf0;
cursor: pointer;
}
.split {
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="110px"
@@ -250,7 +250,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="rebindForm" :model="rebindForm" :rules="rebindRules" label-width="110px">
<el-form ref="rebindFormRef" :model="rebindForm" :rules="rebindRules" label-width="110px">
<el-form-item label="客户">
<span>{{ rebindForm.memberNickname || rebindForm.memberMobile || rebindForm.memberId }}</span>
</el-form-item>
@@ -288,7 +288,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="unbindForm" :model="unbindForm" label-width="110px">
<el-form ref="unbindFormRef" :model="unbindForm" label-width="110px">
<el-form-item label="客户">
<span>{{ unbindForm.memberNickname || unbindForm.memberMobile || unbindForm.memberId }}</span>
</el-form-item>
@@ -304,7 +304,9 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRoute } from "vue-router";
import {
getCustomerRelationDetail,
getCustomerRelationLogs,
@@ -315,259 +317,260 @@ import {
import { relationEffectiveList, hasDeadlineList } from "./dataJson.js";
import DistributionSelector from "@/components/distribution/distribution-selector.vue";
import { QuestionFilled } from "@element-plus/icons-vue";
import { Message } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
export default {
name: "distributionCustomerRelation",
components: { QuestionFilled, DistributionSelector },
data() {
return {
relationEffectiveList,
hasDeadlineList,
bindTimeRange: [],
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
memberMobile: "",
distributionMobile: "",
distributionId: "",
relationEffective: "",
hasDeadline: "",
},
data: [],
total: 0,
detailVisible: false,
detailLoading: false,
detail: null,
logVisible: false,
logLoading: false,
logList: [],
logTarget: null,
rebindVisible: false,
distributorSelectorVisible: false,
unbindVisible: false,
rebindLoading: false,
unbindLoading: false,
rebindForm: {
memberId: "",
memberNickname: "",
memberMobile: "",
currentDistributionId: "",
distributionId: "",
distributionNickname: "",
distributionMobile: "",
reason: "",
},
unbindForm: {
memberId: "",
memberNickname: "",
memberMobile: "",
reason: "",
},
rebindRules: {
distributionId: [{
required: true,
message: "请选择分销员",
trigger: "change",
}],
},
};
},
computed: {
selectedDistributorLabel() {
if (!this.rebindForm.distributionId) return "";
const name = this.rebindForm.distributionNickname || "-";
const mobile = this.rebindForm.distributionMobile || "-";
return `${name}(${mobile})`;
},
},
mounted() {
this.applyRouteQuery();
this.getDataList();
},
watch: {
$route() {
this.applyRouteQuery();
this.getDataList();
},
},
methods: {
applyRouteQuery() {
const query = this.$route.query || {};
this.searchForm.distributionId = query.distributionId ? String(query.distributionId) : "";
this.searchForm.distributionMobile = query.distributionMobile ? String(query.distributionMobile) : "";
},
formatInvalidTime(row) {
return row?.invalidTime || "-";
},
formatDetailInvalidTime(detail) {
if (!detail || this.isRelationEffective(detail)) return "-";
return detail.invalidTime || "-";
},
formatDetailInvalidReason(detail) {
if (!detail || this.isRelationEffective(detail)) return "-";
return detail.invalidReason || "客户关系失效";
},
isRelationEffective(row) {
if (!row) return false;
if (row.relationEffective === true) return true;
if (row.relationEffective === false) return false;
return !["EXPIRED", "UNBOUND"].includes(row.relationStatus);
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
const params = { ...this.searchForm };
if (!params.memberMobile) delete params.memberMobile;
if (!params.distributionMobile) delete params.distributionMobile;
if (!params.distributionId) delete params.distributionId;
if (params.relationEffective === "" || params.relationEffective === null || params.relationEffective === undefined) {
delete params.relationEffective;
} else {
params.relationEffective = params.relationEffective === true || params.relationEffective === "true";
defineOptions({ name: "distributionCustomerRelation" });
const route = useRoute();
const searchFormRef = ref(null);
const rebindFormRef = ref(null);
const unbindFormRef = ref(null);
const bindTimeRange = ref([]);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
memberMobile: "",
distributionMobile: "",
distributionId: "",
relationEffective: "",
hasDeadline: "",
});
const data = ref([]);
const total = ref(0);
const detailVisible = ref(false);
const detailLoading = ref(false);
const detail = ref(null);
const logVisible = ref(false);
const logLoading = ref(false);
const logList = ref([]);
const logTarget = ref(null);
const rebindVisible = ref(false);
const distributorSelectorVisible = ref(false);
const unbindVisible = ref(false);
const rebindLoading = ref(false);
const unbindLoading = ref(false);
const rebindForm = ref({
memberId: "",
memberNickname: "",
memberMobile: "",
currentDistributionId: "",
distributionId: "",
distributionNickname: "",
distributionMobile: "",
reason: "",
});
const unbindForm = ref({
memberId: "",
memberNickname: "",
memberMobile: "",
reason: "",
});
const rebindRules = reactive({
distributionId: [{
required: true,
message: "请选择分销员",
trigger: "change",
}],
});
const selectedDistributorLabel = computed(() => {
if (!rebindForm.value.distributionId) return "";
const name = rebindForm.value.distributionNickname || "-";
const mobile = rebindForm.value.distributionMobile || "-";
return `${name}(${mobile})`;
});
watch(
() => route.query,
() => {
applyRouteQuery();
getDataList();
}
);
onMounted(() => {
applyRouteQuery();
getDataList();
});
function applyRouteQuery() {
const query = route.query || {};
searchForm.distributionId = query.distributionId ? String(query.distributionId) : "";
searchForm.distributionMobile = query.distributionMobile ? String(query.distributionMobile) : "";
}
function formatInvalidTime(row) {
return row?.invalidTime || "-";
}
function formatDetailInvalidTime(detail) {
if (!detail || isRelationEffective(detail)) return "-";
return detail.invalidTime || "-";
}
function formatDetailInvalidReason(detail) {
if (!detail || isRelationEffective(detail)) return "-";
return detail.invalidReason || "客户关系失效";
}
function isRelationEffective(row) {
if (!row) return false;
if (row.relationEffective === true) return true;
if (row.relationEffective === false) return false;
return !["EXPIRED", "UNBOUND"].includes(row.relationStatus);
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.pageNumber = 1;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
getDataList();
}
function getDataList() {
loading.value = true;
const params = { ...searchForm };
if (!params.memberMobile) delete params.memberMobile;
if (!params.distributionMobile) delete params.distributionMobile;
if (!params.distributionId) delete params.distributionId;
if (params.relationEffective === "" || params.relationEffective === null || params.relationEffective === undefined) {
delete params.relationEffective;
} else {
params.relationEffective = params.relationEffective === true || params.relationEffective === "true";
}
if (params.hasDeadline === "" || params.hasDeadline === null || params.hasDeadline === undefined) {
delete params.hasDeadline;
} else {
params.hasDeadline = params.hasDeadline === true || params.hasDeadline === "true";
}
if (bindTimeRange.value && bindTimeRange.value[0] && bindTimeRange.value[1]) {
const startTime = new Date(bindTimeRange.value[0]).getTime();
const endTime = new Date(bindTimeRange.value[1]).getTime();
params.bindTimeStart = unixToDate(startTime / 1000);
params.bindTimeEnd = unixToDate(endTime / 1000);
}
getCustomerRelations(params).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function openDetail(row) {
detailVisible.value = true;
detailLoading.value = true;
detail.value = null;
getCustomerRelationDetail(row.id).then((res) => {
detailLoading.value = false;
if (res.success) {
detail.value = res.result;
}
}).catch(() => {
detailLoading.value = false;
});
}
function openLogs(row) {
logVisible.value = true;
logTarget.value = row;
logLoading.value = true;
logList.value = [];
getCustomerRelationLogs(row.id).then((res) => {
logLoading.value = false;
if (res.success) {
logList.value = res.result || [];
}
}).catch(() => {
logLoading.value = false;
});
}
function formatDistributorCell(nickname, mobile, id) {
if ((!nickname || nickname === "-") && !id) return "-";
const name = nickname && nickname !== "-" ? nickname : id;
if (mobile) return `${name}\n${mobile}`;
return name;
}
function openRebind(row) {
rebindForm.value = {
memberId: row.memberId,
memberNickname: row.memberNickname,
memberMobile: row.memberMobile,
currentDistributionId: row.distributionId || "",
distributionId: "",
distributionNickname: "",
distributionMobile: "",
reason: "",
};
rebindVisible.value = true;
}
function onDistributorSelected(row) {
rebindForm.value.distributionId = row.id;
rebindForm.value.distributionNickname = row.nickname;
rebindForm.value.distributionMobile = row.mobile;
nextTick(() => {
rebindFormRef.value?.validateField("distributionId");
});
}
function clearSelectedDistributor() {
rebindForm.value.distributionId = "";
rebindForm.value.distributionNickname = "";
rebindForm.value.distributionMobile = "";
nextTick(() => {
rebindFormRef.value?.validateField("distributionId");
});
}
function openUnbind(row) {
unbindForm.value = {
memberId: row.memberId,
memberNickname: row.memberNickname,
memberMobile: row.memberMobile,
reason: "",
};
unbindVisible.value = true;
}
function submitRebind() {
rebindFormRef.value.validate((valid) => {
if (!valid) return;
if (rebindForm.value.distributionId === rebindForm.value.currentDistributionId) {
Message.warning("新分销员不能与当前分销员相同");
return;
}
rebindLoading.value = true;
rebindCustomerRelation({
memberId: rebindForm.value.memberId,
distributionId: rebindForm.value.distributionId,
reason: rebindForm.value.reason,
}).then((res) => {
rebindLoading.value = false;
if (res.success) {
Message.success("操作成功");
rebindVisible.value = false;
getDataList();
}
if (params.hasDeadline === "" || params.hasDeadline === null || params.hasDeadline === undefined) {
delete params.hasDeadline;
} else {
params.hasDeadline = params.hasDeadline === true || params.hasDeadline === "true";
}
if (this.bindTimeRange && this.bindTimeRange[0] && this.bindTimeRange[1]) {
const startTime = new Date(this.bindTimeRange[0]).getTime();
const endTime = new Date(this.bindTimeRange[1]).getTime();
params.bindTimeStart = this.$filters.unixToDate(startTime / 1000);
params.bindTimeEnd = this.$filters.unixToDate(endTime / 1000);
}
getCustomerRelations(params).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
openDetail(row) {
this.detailVisible = true;
this.detailLoading = true;
this.detail = null;
getCustomerRelationDetail(row.id).then((res) => {
this.detailLoading = false;
if (res.success) {
this.detail = res.result;
}
}).catch(() => {
this.detailLoading = false;
});
},
openLogs(row) {
this.logVisible = true;
this.logTarget = row;
this.logLoading = true;
this.logList = [];
getCustomerRelationLogs(row.id).then((res) => {
this.logLoading = false;
if (res.success) {
this.logList = res.result || [];
}
}).catch(() => {
this.logLoading = false;
});
},
formatDistributorCell(nickname, mobile, id) {
if ((!nickname || nickname === "-") && !id) return "-";
const name = nickname && nickname !== "-" ? nickname : id;
if (mobile) return `${name}\n${mobile}`;
return name;
},
openRebind(row) {
this.rebindForm = {
memberId: row.memberId,
memberNickname: row.memberNickname,
memberMobile: row.memberMobile,
currentDistributionId: row.distributionId || "",
distributionId: "",
distributionNickname: "",
distributionMobile: "",
reason: "",
};
this.rebindVisible = true;
},
onDistributorSelected(row) {
this.rebindForm.distributionId = row.id;
this.rebindForm.distributionNickname = row.nickname;
this.rebindForm.distributionMobile = row.mobile;
this.$nextTick(() => {
this.$refs.rebindForm?.validateField("distributionId");
});
},
clearSelectedDistributor() {
this.rebindForm.distributionId = "";
this.rebindForm.distributionNickname = "";
this.rebindForm.distributionMobile = "";
this.$nextTick(() => {
this.$refs.rebindForm?.validateField("distributionId");
});
},
openUnbind(row) {
this.unbindForm = {
memberId: row.memberId,
memberNickname: row.memberNickname,
memberMobile: row.memberMobile,
reason: "",
};
this.unbindVisible = true;
},
submitRebind() {
this.$refs.rebindForm.validate((valid) => {
if (!valid) return;
if (this.rebindForm.distributionId === this.rebindForm.currentDistributionId) {
this.$Message.warning("新分销员不能与当前分销员相同");
return;
}
this.rebindLoading = true;
rebindCustomerRelation({
memberId: this.rebindForm.memberId,
distributionId: this.rebindForm.distributionId,
reason: this.rebindForm.reason,
}).then((res) => {
this.rebindLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.rebindVisible = false;
this.getDataList();
}
}).catch(() => {
this.rebindLoading = false;
});
});
},
submitUnbind() {
this.unbindLoading = true;
unbindCustomerRelation({
memberId: this.unbindForm.memberId,
reason: this.unbindForm.reason,
}).then((res) => {
this.unbindLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.unbindVisible = false;
this.getDataList();
}
}).catch(() => {
this.unbindLoading = false;
});
},
},
};
}).catch(() => {
rebindLoading.value = false;
});
});
}
function submitUnbind() {
unbindLoading.value = true;
unbindCustomerRelation({
memberId: unbindForm.value.memberId,
reason: unbindForm.value.reason,
}).then((res) => {
unbindLoading.value = false;
if (res.success) {
Message.success("操作成功");
unbindVisible.value = false;
getDataList();
}
}).catch(() => {
unbindLoading.value = false;
});
}
</script>
<style scoped lang="scss">

View File

@@ -2,7 +2,7 @@
<div>
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -189,206 +189,206 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { auditDistributionGoods, delDistributionGoods, getDistributionGoods } from "@/api/distribution";
import { getBrandListData, getCategoryTree } from "@/api/goods";
import { Message, Modal } from "@/utils/message";
export default {
name: "distributionGoods",
data() {
return {
loading: true,
currentAuthStatus: "ALL",
auditModalVisible: false,
auditLoading: false,
auditTargetIds: [],
auditForm: {
authStatus: "PASS",
authRemark: "",
},
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
goodsName: "",
storeName: "",
categoryId: "",
brandId: "",
authStatus: "",
},
categoryList: [],
categoryProps: {
value: "id",
label: "name",
children: "children",
checkStrictly: true,
emitPath: false,
},
brandList: [],
selectList: [],
selectCount: 0,
data: [],
total: 0,
};
},
computed: {
hasPendingAuditSelection() {
return this.selectList.some((item) => item.authStatus === "TOBEAUDITED");
},
},
methods: {
authStatusText(value) {
if (value === "TOBEAUDITED") return "待审核";
if (value === "REFUSE") return "审核未通过";
return "已审核";
},
authStatusTagType(value) {
if (value === "TOBEAUDITED") return "warning";
if (value === "REFUSE") return "danger";
return "success";
},
onAuthStatusChange(name) {
this.searchForm.authStatus = name === "ALL" ? "" : name;
this.searchForm.pageNumber = 1;
this.getDataList();
this.clearSelectAll();
},
openAudit(row) {
this.auditTargetIds = [row.id];
this.auditForm = { authStatus: "PASS", authRemark: "" };
this.auditModalVisible = true;
},
openBatchAudit() {
const pendingRows = this.selectList.filter((item) => item.authStatus === "TOBEAUDITED");
if (!pendingRows.length) {
this.$Message.warning("请选择待审核的分销商品");
return;
defineOptions({ name: "distributionGoods" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const loading = ref(true);
const currentAuthStatus = ref("ALL");
const auditModalVisible = ref(false);
const auditLoading = ref(false);
const auditTargetIds = ref([]);
const auditForm = ref({
authStatus: "PASS",
authRemark: "",
});
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
goodsName: "",
storeName: "",
categoryId: "",
brandId: "",
authStatus: "",
});
const categoryList = ref([]);
const categoryProps = reactive({
value: "id",
label: "name",
children: "children",
checkStrictly: true,
emitPath: false,
});
const brandList = ref([]);
const selectList = ref([]);
const selectCount = ref(0);
const data = ref([]);
const total = ref(0);
const hasPendingAuditSelection = computed(() =>
selectList.value.some((item) => item.authStatus === "TOBEAUDITED")
);
function authStatusText(value) {
if (value === "TOBEAUDITED") return "待审核";
if (value === "REFUSE") return "审核未通过";
return "已审核";
}
function authStatusTagType(value) {
if (value === "TOBEAUDITED") return "warning";
if (value === "REFUSE") return "danger";
return "success";
}
function onAuthStatusChange(name) {
searchForm.authStatus = name === "ALL" ? "" : name;
searchForm.pageNumber = 1;
getDataList();
clearSelectAll();
}
function openAudit(row) {
auditTargetIds.value = [row.id];
auditForm.value = { authStatus: "PASS", authRemark: "" };
auditModalVisible.value = true;
}
function openBatchAudit() {
const pendingRows = selectList.value.filter((item) => item.authStatus === "TOBEAUDITED");
if (!pendingRows.length) {
Message.warning("请选择待审核的分销商品");
return;
}
auditTargetIds.value = pendingRows.map((item) => item.id);
auditForm.value = { authStatus: "PASS", authRemark: "" };
auditModalVisible.value = true;
}
function submitAudit() {
if (auditForm.value.authStatus === "REFUSE" && !auditForm.value.authRemark.trim()) {
Message.warning("请填写驳回原因");
return;
}
auditLoading.value = true;
auditDistributionGoods({
ids: auditTargetIds.value,
authStatus: auditForm.value.authStatus,
authRemark: auditForm.value.authRemark.trim(),
})
.then((res) => {
if (res.success) {
Message.success("审核成功");
auditModalVisible.value = false;
clearSelectAll();
getDataList();
}
this.auditTargetIds = pendingRows.map((item) => item.id);
this.auditForm = { authStatus: "PASS", authRemark: "" };
this.auditModalVisible = true;
},
submitAudit() {
if (this.auditForm.authStatus === "REFUSE" && !this.auditForm.authRemark.trim()) {
this.$Message.warning("请填写驳回原因");
return;
})
.finally(() => {
auditLoading.value = false;
});
}
function goodsTypeText(value) {
if (value === "PHYSICAL_GOODS") return "实物商品";
if (value === "VIRTUAL_GOODS") return "虚拟商品";
if (value === "E_COUPON") return "电子卡券";
return "-";
}
function init() {
loadFilterOptions();
getDataList();
}
function loadFilterOptions() {
getCategoryTree().then((res) => {
if (res.success) {
categoryList.value = res.result || [];
}
});
getBrandListData().then((res) => {
brandList.value = Array.isArray(res) ? res : res.result || [];
});
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
clearSelectAll();
}
function changePageSize(v) {
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function clearSelectAll() {
table.value?.clearSelection();
}
function changeSelect(e) {
selectList.value = e;
selectCount.value = e.length;
}
function getDataList() {
loading.value = true;
getDistributionGoods(searchForm)
.then((res) => {
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
this.auditLoading = true;
auditDistributionGoods({
ids: this.auditTargetIds,
authStatus: this.auditForm.authStatus,
authRemark: this.auditForm.authRemark.trim(),
})
.then((res) => {
if (res.success) {
this.$Message.success("审核成功");
this.auditModalVisible = false;
this.clearSelectAll();
this.getDataList();
}
})
.finally(() => {
this.auditLoading = false;
});
},
goodsTypeText(value) {
if (value === "PHYSICAL_GOODS") return "实物商品";
if (value === "VIRTUAL_GOODS") return "虚拟商品";
if (value === "E_COUPON") return "电子卡券";
return "-";
},
init() {
this.loadFilterOptions();
this.getDataList();
},
loadFilterOptions() {
getCategoryTree().then((res) => {
})
.finally(() => {
loading.value = false;
});
}
function remove(v) {
Modal.confirm({
title: "确认下架",
content: "您确认要下架么?",
loading: true,
onOk: () => {
delDistributionGoods(v.id).then((res) => {
Modal.remove();
if (res.success) {
this.categoryList = res.result || [];
Message.success("下架成功");
getDataList();
}
});
getBrandListData().then((res) => {
this.brandList = Array.isArray(res) ? res : res.result || [];
},
});
}
function delAll() {
if (selectCount.value <= 0) {
Message.warning("您还未选择要下架的数据");
return;
}
Modal.confirm({
title: "确认下架",
content: "您确认要下架所选的 " + selectCount.value + " 条数据?",
loading: true,
onOk: () => {
const ids = selectList.value.map((item) => item.id);
delDistributionGoods(ids.toString()).then((res) => {
Modal.remove();
if (res.success) {
Message.success("下架成功");
clearSelectAll();
getDataList();
}
});
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
clearSelectAll() {
this.$refs.table?.clearSelection();
},
changeSelect(e) {
this.selectList = e;
this.selectCount = e.length;
},
getDataList() {
this.loading = true;
getDistributionGoods(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
})
.finally(() => {
this.loading = false;
});
},
remove(v) {
this.$Modal.confirm({
title: "确认下架",
content: "您确认要下架么?",
loading: true,
onOk: () => {
delDistributionGoods(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("下架成功");
this.getDataList();
}
});
},
});
},
delAll() {
if (this.selectCount <= 0) {
this.$Message.warning("您还未选择要下架的数据");
return;
}
this.$Modal.confirm({
title: "确认下架",
content: "您确认要下架所选的 " + this.selectCount + " 条数据?",
loading: true,
onOk: () => {
const ids = this.selectList.map((item) => item.id);
delDistributionGoods(ids.toString()).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("下架成功");
this.clearSelectAll();
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
};
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -108,12 +108,16 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import {
getDistributionGrades,
saveAllDistributionGrades,
deleteDistributionGrade,
} from "@/api/distribution";
import { Message, Modal } from "@/utils/message";
defineOptions({ name: "distributionGrade" });
const MAX_GRADE_COUNT = 5;
const GRADE_VALUE_OPTIONS = [1, 2, 3, 4, 5];
@@ -154,252 +158,244 @@ const buildDefaultRow = (sortOrder = null) => {
};
};
export default {
name: "distributionGrade",
data() {
return {
loading: true,
saveLoading: false,
data: [],
};
},
mounted() {
this.getDataList();
},
methods: {
normalizeRow(row, index) {
const normalized = {
...buildDefaultRow(row.sortOrder),
...row,
enableSalesCondition: row.enableSalesCondition !== false,
enableCommissionCondition: row.enableCommissionCondition !== false,
enableInviteCondition: row.enableInviteCondition !== false,
goodsCommissionRate: toPercent(row.goodsCommissionRate),
inviteCommissionRate: toPercent(row.inviteCommissionRate),
_key: row._key || row.id || `grade_${index}_${Date.now()}`,
};
if (this.isBaseGrade(normalized)) {
normalized.enableSalesCondition = false;
normalized.enableCommissionCondition = false;
normalized.enableInviteCondition = false;
if (!normalized.gradeName || !normalized.gradeName.trim()) {
normalized.gradeName = BASE_GRADE_NAME;
}
const loading = ref(true);
const saveLoading = ref(false);
const data = ref([]);
function normalizeRow(row, index) {
const normalized = {
...buildDefaultRow(row.sortOrder),
...row,
enableSalesCondition: row.enableSalesCondition !== false,
enableCommissionCondition: row.enableCommissionCondition !== false,
enableInviteCondition: row.enableInviteCondition !== false,
goodsCommissionRate: toPercent(row.goodsCommissionRate),
inviteCommissionRate: toPercent(row.inviteCommissionRate),
_key: row._key || row.id || `grade_${index}_${Date.now()}`,
};
if (isBaseGrade(normalized)) {
normalized.enableSalesCondition = false;
normalized.enableCommissionCondition = false;
normalized.enableInviteCondition = false;
if (!normalized.gradeName || !normalized.gradeName.trim()) {
normalized.gradeName = BASE_GRADE_NAME;
}
}
return normalized;
}
function isBaseGrade(row) {
return row && row.sortOrder === BASE_GRADE_VALUE;
}
function getSortedRows() {
return data.value.slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
}
function getGradeLabel(row) {
return row.gradeName && row.gradeName.trim() ? row.gradeName.trim() : `等级值${row.sortOrder}`;
}
function getNextGradeValue() {
const used = new Set(data.value.map((item) => item.sortOrder));
return GRADE_VALUE_OPTIONS.find((value) => value !== BASE_GRADE_VALUE && !used.has(value)) || null;
}
function getDataList() {
loading.value = true;
getDistributionGrades().then((res) => {
loading.value = false;
if (res.success) {
data.value = (res.result || [])
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((item, index) => normalizeRow(item, index));
}
});
}
function addRow() {
if (data.value.length >= MAX_GRADE_COUNT) {
Message.warning("最多添加5个分销等级");
return;
}
const nextGradeValue = getNextGradeValue();
if (!nextGradeValue) {
Message.warning("等级值1-5已全部使用");
return;
}
data.value.push(buildDefaultRow(nextGradeValue));
}
function validateGradeProgression() {
const rows = getSortedRows();
for (let i = 1; i < rows.length; i++) {
const lower = rows[i - 1];
const higher = rows[i];
const lowerLabel = getGradeLabel(lower);
const higherLabel = getGradeLabel(higher);
if (lower.enableSalesCondition) {
if (!higher.enableSalesCondition) {
Message.warning(`${higherLabel}必须启用${lowerLabel}已启用的销售额升级条件`);
return false;
}
return normalized;
},
isBaseGrade(row) {
return row && row.sortOrder === BASE_GRADE_VALUE;
},
getSortedRows() {
return this.data.slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
},
getGradeLabel(row) {
return row.gradeName && row.gradeName.trim() ? row.gradeName.trim() : `等级值${row.sortOrder}`;
},
getNextGradeValue() {
const used = new Set(this.data.map((item) => item.sortOrder));
return GRADE_VALUE_OPTIONS.find((value) => value !== BASE_GRADE_VALUE && !used.has(value)) || null;
},
getDataList() {
this.loading = true;
getDistributionGrades().then((res) => {
this.loading = false;
if (Number(higher.salesThreshold || 0) < Number(lower.salesThreshold || 0)) {
Message.warning(`${higherLabel}销售额阈值不能小于${lowerLabel}`);
return false;
}
}
if (lower.enableCommissionCondition) {
if (!higher.enableCommissionCondition) {
Message.warning(`${higherLabel}必须启用${lowerLabel}已启用的收益额升级条件`);
return false;
}
if (Number(higher.commissionThreshold || 0) < Number(lower.commissionThreshold || 0)) {
Message.warning(`${higherLabel}收益额阈值不能小于${lowerLabel}`);
return false;
}
}
if (lower.enableInviteCondition) {
if (!higher.enableInviteCondition) {
Message.warning(`${higherLabel}必须启用${lowerLabel}已启用的邀请人数升级条件`);
return false;
}
if (Number(higher.inviteThreshold || 0) < Number(lower.inviteThreshold || 0)) {
Message.warning(`${higherLabel}邀请人数阈值不能小于${lowerLabel}`);
return false;
}
}
}
return true;
}
function validateRows() {
if (!data.value.length) {
Message.warning("请至少添加一个分销等级");
return false;
}
const usedValues = [];
for (let i = 0; i < data.value.length; i++) {
const row = data.value[i];
if (!row.sortOrder) {
Message.warning(`请填写第${i + 1}行等级值`);
return false;
}
if (usedValues.includes(row.sortOrder)) {
Message.warning("等级值不能重复");
return false;
}
usedValues.push(row.sortOrder);
if (!row.gradeName || !row.gradeName.trim()) {
Message.warning(`请填写第${i + 1}行等级名`);
return false;
}
if (isBaseGrade(row)) {
continue;
}
if (!row.enableSalesCondition && !row.enableCommissionCondition && !row.enableInviteCondition) {
Message.warning(`第${i + 1}行请至少启用一项升级规则`);
return false;
}
const rowLabel = getGradeLabel(row);
if (row.enableSalesCondition && Number(row.salesThreshold || 0) <= 0) {
Message.warning(`${rowLabel}销售额必须大于0`);
return false;
}
if (row.enableCommissionCondition && Number(row.commissionThreshold || 0) <= 0) {
Message.warning(`${rowLabel}收益额必须大于0`);
return false;
}
if (row.enableInviteCondition && Number(row.inviteThreshold || 0) <= 0) {
Message.warning(`${rowLabel}邀请人数必须大于0`);
return false;
}
}
return validateGradeProgression() && validateCrossRates();
}
function validateCrossRates() {
let maxGoods = 0;
let maxInvite = 0;
for (const row of data.value) {
maxGoods = Math.max(maxGoods, Number(row.goodsCommissionRate || 0));
maxInvite = Math.max(maxInvite, Number(row.inviteCommissionRate || 0));
}
if (maxGoods + maxInvite > 100) {
Message.warning(
`所有等级中最高佣金比例(${maxGoods}%)与最高邀请奖励(${maxInvite}%)之和不能超过100%`
);
return false;
}
for (const row of data.value) {
const goods = Number(row.goodsCommissionRate || 0);
const invite = Number(row.inviteCommissionRate || 0);
if (goods + invite > 100) {
Message.warning(`${getGradeLabel(row)}的佣金比例与邀请奖励之和不能超过100%`);
return false;
}
}
return true;
}
function buildPayload(row) {
const base = isBaseGrade(row);
return {
id: row.id || undefined,
gradeName: row.gradeName.trim(),
sortOrder: row.sortOrder,
salesThreshold: row.salesThreshold,
commissionThreshold: row.commissionThreshold,
inviteThreshold: row.inviteThreshold,
enableSalesCondition: base ? false : row.enableSalesCondition,
enableCommissionCondition: base ? false : row.enableCommissionCondition,
enableInviteCondition: base ? false : row.enableInviteCondition,
goodsCommissionRate: toFraction(row.goodsCommissionRate),
inviteCommissionRate: toFraction(row.inviteCommissionRate),
};
}
async function saveAll() {
if (!validateRows()) {
return;
}
saveLoading.value = true;
try {
const payload = getSortedRows().map((row) => buildPayload(row));
const res = await saveAllDistributionGrades(payload);
if (res.success) {
Message.success("保存成功");
getDataList();
}
} catch (e) {
// request layer handles error message
} finally {
saveLoading.value = false;
}
}
function remove(row, index) {
if (isBaseGrade(row)) {
Message.warning("默认分销等级不能删除");
return;
}
const name = row.gradeName || `等级值${row.sortOrder || ""}`;
if (!row.id) {
Modal.confirm({
title: "确认删除",
content: `您确认要删除 ${name} 吗?`,
onOk: () => {
data.value.splice(index, 1);
},
});
return;
}
Modal.confirm({
title: "确认删除",
content: `您确认要删除 ${name} 吗?`,
loading: true,
onOk: () => {
deleteDistributionGrade(row.id).then((res) => {
Modal.remove();
if (res.success) {
this.data = (res.result || [])
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((item, index) => this.normalizeRow(item, index));
Message.success("删除成功");
getDataList();
}
});
},
addRow() {
if (this.data.length >= MAX_GRADE_COUNT) {
this.$Message.warning("最多添加5个分销等级");
return;
}
const nextGradeValue = this.getNextGradeValue();
if (!nextGradeValue) {
this.$Message.warning("等级值1-5已全部使用");
return;
}
this.data.push(buildDefaultRow(nextGradeValue));
},
validateGradeProgression() {
const rows = this.getSortedRows();
for (let i = 1; i < rows.length; i++) {
const lower = rows[i - 1];
const higher = rows[i];
const lowerLabel = this.getGradeLabel(lower);
const higherLabel = this.getGradeLabel(higher);
if (lower.enableSalesCondition) {
if (!higher.enableSalesCondition) {
this.$Message.warning(`${higherLabel}必须启用${lowerLabel}已启用的销售额升级条件`);
return false;
}
if (Number(higher.salesThreshold || 0) < Number(lower.salesThreshold || 0)) {
this.$Message.warning(`${higherLabel}销售额阈值不能小于${lowerLabel}`);
return false;
}
}
if (lower.enableCommissionCondition) {
if (!higher.enableCommissionCondition) {
this.$Message.warning(`${higherLabel}必须启用${lowerLabel}已启用的收益额升级条件`);
return false;
}
if (Number(higher.commissionThreshold || 0) < Number(lower.commissionThreshold || 0)) {
this.$Message.warning(`${higherLabel}收益额阈值不能小于${lowerLabel}`);
return false;
}
}
if (lower.enableInviteCondition) {
if (!higher.enableInviteCondition) {
this.$Message.warning(`${higherLabel}必须启用${lowerLabel}已启用的邀请人数升级条件`);
return false;
}
if (Number(higher.inviteThreshold || 0) < Number(lower.inviteThreshold || 0)) {
this.$Message.warning(`${higherLabel}邀请人数阈值不能小于${lowerLabel}`);
return false;
}
}
}
return true;
},
validateRows() {
if (!this.data.length) {
this.$Message.warning("请至少添加一个分销等级");
return false;
}
const usedValues = [];
for (let i = 0; i < this.data.length; i++) {
const row = this.data[i];
if (!row.sortOrder) {
this.$Message.warning(`请填写第${i + 1}行等级值`);
return false;
}
if (usedValues.includes(row.sortOrder)) {
this.$Message.warning("等级值不能重复");
return false;
}
usedValues.push(row.sortOrder);
if (!row.gradeName || !row.gradeName.trim()) {
this.$Message.warning(`请填写第${i + 1}行等级名`);
return false;
}
if (this.isBaseGrade(row)) {
continue;
}
if (!row.enableSalesCondition && !row.enableCommissionCondition && !row.enableInviteCondition) {
this.$Message.warning(`第${i + 1}行请至少启用一项升级规则`);
return false;
}
const rowLabel = this.getGradeLabel(row);
if (row.enableSalesCondition && Number(row.salesThreshold || 0) <= 0) {
this.$Message.warning(`${rowLabel}销售额必须大于0`);
return false;
}
if (row.enableCommissionCondition && Number(row.commissionThreshold || 0) <= 0) {
this.$Message.warning(`${rowLabel}收益额必须大于0`);
return false;
}
if (row.enableInviteCondition && Number(row.inviteThreshold || 0) <= 0) {
this.$Message.warning(`${rowLabel}邀请人数必须大于0`);
return false;
}
}
return this.validateGradeProgression() && this.validateCrossRates();
},
validateCrossRates() {
let maxGoods = 0;
let maxInvite = 0;
for (const row of this.data) {
maxGoods = Math.max(maxGoods, Number(row.goodsCommissionRate || 0));
maxInvite = Math.max(maxInvite, Number(row.inviteCommissionRate || 0));
}
if (maxGoods + maxInvite > 100) {
this.$Message.warning(
`所有等级中最高佣金比例(${maxGoods}%)与最高邀请奖励(${maxInvite}%)之和不能超过100%`
);
return false;
}
for (const row of this.data) {
const goods = Number(row.goodsCommissionRate || 0);
const invite = Number(row.inviteCommissionRate || 0);
if (goods + invite > 100) {
this.$Message.warning(`${this.getGradeLabel(row)}的佣金比例与邀请奖励之和不能超过100%`);
return false;
}
}
return true;
},
buildPayload(row) {
const isBaseGrade = this.isBaseGrade(row);
return {
id: row.id || undefined,
gradeName: row.gradeName.trim(),
sortOrder: row.sortOrder,
salesThreshold: row.salesThreshold,
commissionThreshold: row.commissionThreshold,
inviteThreshold: row.inviteThreshold,
enableSalesCondition: isBaseGrade ? false : row.enableSalesCondition,
enableCommissionCondition: isBaseGrade ? false : row.enableCommissionCondition,
enableInviteCondition: isBaseGrade ? false : row.enableInviteCondition,
goodsCommissionRate: toFraction(row.goodsCommissionRate),
inviteCommissionRate: toFraction(row.inviteCommissionRate),
};
},
async saveAll() {
if (!this.validateRows()) {
return;
}
this.saveLoading = true;
try {
const payload = this.getSortedRows().map((row) => this.buildPayload(row));
const res = await saveAllDistributionGrades(payload);
if (res.success) {
this.$Message.success("保存成功");
this.getDataList();
}
} catch (e) {
// request layer handles error message
} finally {
this.saveLoading = false;
}
},
remove(row, index) {
if (this.isBaseGrade(row)) {
this.$Message.warning("默认分销等级不能删除");
return;
}
const name = row.gradeName || `等级值${row.sortOrder || ""}`;
if (!row.id) {
this.$Modal.confirm({
title: "确认删除",
content: `您确认要删除 ${name} 吗?`,
onOk: () => {
this.data.splice(index, 1);
},
});
return;
}
this.$Modal.confirm({
title: "确认删除",
content: `您确认要删除 ${name} 吗?`,
loading: true,
onOk: () => {
deleteDistributionGrade(row.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("删除成功");
this.getDataList();
}
});
},
});
},
},
};
</script>
});
}
onMounted(() => {
getDataList();
});</script>
<style scoped lang="scss">
.grade-footer {

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="110px"
@@ -101,115 +101,114 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { getDistributionGradeHistoryPage, getDistributionGrades } from "@/api/distribution";
export default {
name: "distributionGradeHistory",
data() {
return {
loading: false,
searchForm: {
pageNumber: 1,
pageSize: 20,
memberName: "",
distributionMobile: "",
gradeId: "",
upgradeTimeStart: "",
upgradeTimeEnd: "",
},
upgradeTimeRange: [],
gradeOptions: [],
data: [],
total: 0,
};
},
mounted() {
this.loadGradeOptions();
this.getDataList();
},
methods: {
loadGradeOptions() {
getDistributionGrades()
.then((res) => {
if (!res.success) {
return;
}
this.gradeOptions = (res.result || [])
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((item) => ({
value: String(item.sortOrder),
label: item.gradeName || `等级值${item.sortOrder}`,
}));
});
},
formatGrade(name, gradeId) {
if (name) {
return name;
defineOptions({ name: "distributionGradeHistory" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const loading = ref(false);
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
memberName: "",
distributionMobile: "",
gradeId: "",
upgradeTimeStart: "",
upgradeTimeEnd: "",
});
const upgradeTimeRange = ref([]);
const gradeOptions = ref([]);
const data = ref([]);
const total = ref(0);
onMounted(() => {
loadGradeOptions();
getDataList();
});
function loadGradeOptions() {
getDistributionGrades()
.then((res) => {
if (!res.success) {
return;
}
if (gradeId) {
return `等级值${gradeId}`;
gradeOptions.value = (res.result || [])
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((item) => ({
value: String(item.sortOrder),
label: item.gradeName || `等级值${item.sortOrder}`,
}));
});
}
function formatGrade(name, gradeId) {
if (name) {
return name;
}
if (gradeId) {
return `等级值${gradeId}`;
}
return "-";
}
function formatRate(value) {
const num = Number(value);
if (!Number.isFinite(num)) {
return "-";
}
return `${(num * 100).toFixed(2).replace(/\.?0+$/, "") || "0"}%`;
}
function applyUpgradeTimeRange() {
if (upgradeTimeRange.value && upgradeTimeRange.value.length === 2) {
searchForm.value.upgradeTimeStart = `${upgradeTimeRange.value[0]} 00:00:00`;
searchForm.value.upgradeTimeEnd = `${upgradeTimeRange.value[1]} 23:59:59`;
} else {
searchForm.value.upgradeTimeStart = "";
searchForm.value.upgradeTimeEnd = "";
}
}
function handleSearch() {
searchForm.value.pageNumber = 1;
getDataList();
}
function handleReset() {
searchForm.value = {
pageNumber: 1,
pageSize: 20,
memberName: "",
distributionMobile: "",
gradeId: "",
upgradeTimeStart: "",
upgradeTimeEnd: "",
};
upgradeTimeRange.value = [];
getDataList();
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.value.pageNumber = 1;
getDataList();
}
function getDataList() {
applyUpgradeTimeRange();
loading.value = true;
getDistributionGradeHistoryPage(searchForm.value)
.then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records || [];
total.value = res.result.total || 0;
}
return "-";
},
formatRate(value) {
const num = Number(value);
if (!Number.isFinite(num)) {
return "-";
}
return `${(num * 100).toFixed(2).replace(/\.?0+$/, "") || "0"}%`;
},
applyUpgradeTimeRange() {
if (this.upgradeTimeRange && this.upgradeTimeRange.length === 2) {
this.searchForm.upgradeTimeStart = `${this.upgradeTimeRange[0]} 00:00:00`;
this.searchForm.upgradeTimeEnd = `${this.upgradeTimeRange[1]} 23:59:59`;
} else {
this.searchForm.upgradeTimeStart = "";
this.searchForm.upgradeTimeEnd = "";
}
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleReset() {
this.searchForm = {
pageNumber: 1,
pageSize: 20,
memberName: "",
distributionMobile: "",
gradeId: "",
upgradeTimeStart: "",
upgradeTimeEnd: "",
};
this.upgradeTimeRange = [];
this.getDataList();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.applyUpgradeTimeRange();
this.loading = true;
getDistributionGradeHistoryPage(this.searchForm)
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
}
})
.catch(() => {
this.loading = false;
});
},
},
};
})
.catch(() => {
loading.value = false;
});
}
</script>
<style scoped>

View File

@@ -43,85 +43,82 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import {
downloadDistributionImportFailItems,
getDistributionImportBatches,
} from "@/api/distribution";
import { downloadBlob } from "@/utils/downloadBlob";
import { Message } from "@/utils/message";
export default {
name: "distributionImportRecord",
data() {
return {
loading: false,
searchForm: {
pageNumber: 1,
pageSize: 20,
},
data: [],
total: 0,
};
},
mounted() {
this.getDataList();
},
methods: {
statusLabel(status) {
const map = {
PROCESSING: "处理中",
DONE: "已完成",
FAILED: "失败",
};
return map[status] || status || "-";
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
getDistributionImportBatches(this.searchForm)
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
defineOptions({ name: "distributionImportRecord" });
const loading = ref(false);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
});
const data = ref([]);
const total = ref(0);
onMounted(() => {
getDataList();
});
function statusLabel(status) {
const map = {
PROCESSING: "处理中",
DONE: "已完成",
FAILED: "失败",
};
return map[status] || status || "-";
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.pageNumber = 1;
getDataList();
}
function getDataList() {
loading.value = true;
getDistributionImportBatches(searchForm)
.then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records || [];
total.value = res.result.total || 0;
}
})
.catch(() => {
loading.value = false;
});
}
function downloadFailList(row) {
if (!row || !row.failCount) return;
downloadDistributionImportFailItems(row.id)
.then((blob) => {
if (blob && blob.type && blob.type.includes("application/json")) {
const reader = new FileReader();
reader.onload = () => {
try {
const result = JSON.parse(reader.result);
Message.error(result.message || "下载失败");
} catch (e) {
Message.error("下载失败");
}
})
.catch(() => {
this.loading = false;
});
},
downloadFailList(row) {
if (!row || !row.failCount) return;
downloadDistributionImportFailItems(row.id)
.then((blob) => {
if (blob && blob.type && blob.type.includes("application/json")) {
const reader = new FileReader();
reader.onload = () => {
try {
const result = JSON.parse(reader.result);
this.$Message.error(result.message || "下载失败");
} catch (e) {
this.$Message.error("下载失败");
}
};
reader.readAsText(blob);
return;
}
downloadBlob(blob, `分销员导入失败列表_${row.id}.xlsx`);
this.$Message.success("下载成功");
})
.catch(() => {
this.$Message.error("下载失败");
});
},
},
};
};
reader.readAsText(blob);
return;
}
downloadBlob(blob, `分销员导入失败列表_${row.id}.xlsx`);
Message.success("下载成功");
})
.catch(() => {
Message.error("下载失败");
});
}
</script>
<style scoped lang="scss">

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -159,135 +159,140 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, watch, onMounted } from "vue";
import { useRoute } from "vue-router";
import { getDistributionOrder } from "@/api/distribution";
import { orderStatusList } from "./dataJson";
import { getShopListData } from "@/api/shops";
import { unixToDate, unitPrice } from "@/utils/filters";
export default {
name: "distributionOrder",
data() {
return {
timeRange: [],
orderStatusList,
shopList: [],
distributionId: this.$route.query.id,
currentStatus: "ALL",
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
distributionOrderStatus: "",
},
data: [],
total: 0,
};
},
watch: {
$route(e) {
this.distributionId = e.query.id ? e.query.id : undefined;
this.getDataList();
},
},
methods: {
init() {
this.getDataList();
this.getShopList();
},
onStatusTabChange(name) {
this.currentStatus = name;
this.searchForm.distributionOrderStatus = name === "ALL" ? "" : name;
this.searchForm.pageNumber = 1;
this.getDataList();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
getDataList() {
if (this.distributionId) {
this.searchForm.distributionId = this.distributionId;
} else {
delete this.searchForm.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);
} else {
this.searchForm.startTime = null;
this.searchForm.endTime = null;
}
getDistributionOrder(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
getShopList(val) {
const params = {
pageNumber: 1,
pageSize: 20,
storeName: val || "",
};
getShopListData(params).then((res) => {
this.shopList = res.result.records;
});
},
searchChange(val) {
this.getShopList(val);
},
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";
},
formatCommission(amount) {
if (amount === null || amount === undefined || amount === "") {
return "-";
}
return this.$filters.unitPrice(amount, "¥");
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "distributionOrder" });
const route = useRoute();
// 模板 el-form / el-table 的 ref(原 ref="searchForm" 与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const timeRange = ref([]);
const shopList = ref([]);
const distributionId = ref(route.query.id);
const currentStatus = ref("ALL");
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
distributionOrderStatus: "",
});
const data = ref([]);
const total = ref(0);
watch(
() => route.query,
(query) => {
distributionId.value = query.id ? query.id : undefined;
getDataList();
}
);
function init() {
getDataList();
getShopList();
}
function onStatusTabChange(name) {
currentStatus.value = name;
searchForm.distributionOrderStatus = name === "ALL" ? "" : name;
searchForm.pageNumber = 1;
getDataList();
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.pageNumber = 1;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function getDataList() {
if (distributionId.value) {
searchForm.distributionId = distributionId.value;
} else {
delete searchForm.distributionId;
}
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 = unixToDate(startTime / 1000);
searchForm.endTime = unixToDate(endTime / 1000);
} else {
searchForm.startTime = null;
searchForm.endTime = null;
}
getDistributionOrder(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function getShopList(val) {
const params = {
pageNumber: 1,
pageSize: 20,
storeName: val || "",
};
getShopListData(params).then((res) => {
shopList.value = res.result.records;
});
}
function searchChange(val) {
getShopList(val);
}
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";
}
function formatCommission(amount) {
if (amount === null || amount === undefined || amount === "") {
return "-";
}
return unitPrice(amount, "¥");
}
onMounted(() => {
init();
});
</script>
<style scoped lang="scss">

View File

@@ -177,19 +177,23 @@
<el-button type="primary" :loading="planSaveLoading" @click="savePlan">保存</el-button>
</el-form-item>
</el-form>
<sku-select ref="skuSelect" @selectedGoodsData="selectedGoodsData" />
<sku-select ref="skuSelectRef" @selectedGoodsData="selectedGoodsData" />
</div>
</template>
<script>
<script setup>
import { onMounted, ref, watch } from "vue";
import {
getCurrentRecruitPlan,
saveRecruitPlan,
} from "@/api/distribution";
import { Message } from "@/utils/message";
import uploadPicInput from "@/components/lili/upload-pic-input";
import tinymec from "@/components/editor/index.vue";
import editor from "@/components/editor/index.vue";
import skuSelect from "@/components/lili-dialog";
defineOptions({ name: "distributionRecruit" });
const MAX_RECRUIT_FIELDS = 5;
const FIELD_TYPE_OPTIONS = [
@@ -231,228 +235,239 @@ const buildDefaultField = () => ({
placeholder: "",
});
export default {
name: "distributionRecruit",
components: { uploadPicInput, editor: tinymec, skuSelect },
data() {
return {
planSaveLoading: false,
editorReady: true,
planForm: buildDefaultPlan(),
recruitFields: [],
recruitGoodsList: [],
fieldTypeOptions: FIELD_TYPE_OPTIONS,
};
},
mounted() {
this.loadPlan();
},
watch: {
"planForm.applyMode"(value) {
if (value !== "MANUAL") {
this.planForm.requireApplyInfo = false;
this.recruitFields = [];
}
},
"planForm.requireApplyInfo"(value) {
if (!value) {
this.recruitFields = [];
}
},
"planForm.joinConditionType"(value) {
if (value !== "CONDITIONAL") {
this.planForm.requirePurchaseGoods = false;
this.planForm.requireSelfPurchaseAmount = false;
this.planForm.requireConsumeCount = false;
this.recruitGoodsList = [];
}
},
"planForm.requirePurchaseGoods"(value) {
if (!value) {
this.recruitGoodsList = [];
}
},
"planForm.requireSelfPurchaseAmount"(value) {
if (value && (!this.planForm.minSelfPurchaseAmount || this.planForm.minSelfPurchaseAmount < 0.01)) {
this.planForm.minSelfPurchaseAmount = 0.01;
}
},
"planForm.requireConsumeCount"(value) {
if (value && (!this.planForm.minConsumeCount || this.planForm.minConsumeCount < 1)) {
this.planForm.minConsumeCount = 1;
}
},
},
methods: {
normalizeFields(fields) {
if (!fields || !fields.length) {
return [];
}
return fields.map((item, index) => ({
...buildDefaultField(),
...item,
_key: item._key || item.id || `field_${index}_${Date.now()}`,
required: item.required !== false,
}));
},
addRecruitField() {
if (this.recruitFields.length >= MAX_RECRUIT_FIELDS) {
this.$Message.warning("最多添加5个字段");
return;
}
this.recruitFields.push(buildDefaultField());
},
removeRecruitField(index) {
this.recruitFields.splice(index, 1);
},
validateRecruitFields() {
if (!this.planForm.requireApplyInfo) {
return true;
}
if (!this.recruitFields.length) {
this.$Message.warning("请至少添加一个申请字段");
return false;
}
for (let i = 0; i < this.recruitFields.length; i++) {
const field = this.recruitFields[i];
if (!field.fieldName || !field.fieldName.trim()) {
this.$Message.warning(`请填写字段${i + 1}名称`);
return false;
const skuSelectRef = ref(null);
const planSaveLoading = ref(false);
const editorReady = ref(true);
const planForm = ref(buildDefaultPlan());
const recruitFields = ref([]);
const recruitGoodsList = ref([]);
const fieldTypeOptions = ref(FIELD_TYPE_OPTIONS);
onMounted(() => {
loadPlan();
});
watch(
() => planForm.value.applyMode,
(value) => {
if (value !== "MANUAL") {
planForm.value.requireApplyInfo = false;
recruitFields.value = [];
}
}
);
watch(
() => planForm.value.requireApplyInfo,
(value) => {
if (!value) {
recruitFields.value = [];
}
}
);
watch(
() => planForm.value.joinConditionType,
(value) => {
if (value !== "CONDITIONAL") {
planForm.value.requirePurchaseGoods = false;
planForm.value.requireSelfPurchaseAmount = false;
planForm.value.requireConsumeCount = false;
recruitGoodsList.value = [];
}
}
);
watch(
() => planForm.value.requirePurchaseGoods,
(value) => {
if (!value) {
recruitGoodsList.value = [];
}
}
);
watch(
() => planForm.value.requireSelfPurchaseAmount,
(value) => {
if (value && (!planForm.value.minSelfPurchaseAmount || planForm.value.minSelfPurchaseAmount < 0.01)) {
planForm.value.minSelfPurchaseAmount = 0.01;
}
}
);
watch(
() => planForm.value.requireConsumeCount,
(value) => {
if (value && (!planForm.value.minConsumeCount || planForm.value.minConsumeCount < 1)) {
planForm.value.minConsumeCount = 1;
}
}
);
function normalizeFields(fields) {
if (!fields || !fields.length) {
return [];
}
return fields.map((item, index) => ({
...buildDefaultField(),
...item,
_key: item._key || item.id || `field_${index}_${Date.now()}`,
required: item.required !== false,
}));
}
function addRecruitField() {
if (recruitFields.value.length >= MAX_RECRUIT_FIELDS) {
Message.warning("最多添加5个字段");
return;
}
recruitFields.value.push(buildDefaultField());
}
function removeRecruitField(index) {
recruitFields.value.splice(index, 1);
}
function validateRecruitFields() {
if (!planForm.value.requireApplyInfo) {
return true;
}
if (!recruitFields.value.length) {
Message.warning("请至少添加一个申请字段");
return false;
}
for (let i = 0; i < recruitFields.value.length; i++) {
const field = recruitFields.value[i];
if (!field.fieldName || !field.fieldName.trim()) {
Message.warning(`请填写字段${i + 1}名称`);
return false;
}
}
return true;
}
function validateJoinConditions() {
if (planForm.value.joinConditionType !== "CONDITIONAL") {
return true;
}
const {
requirePurchaseGoods,
requireSelfPurchaseAmount,
requireConsumeCount,
} = planForm.value;
if (!requirePurchaseGoods && !requireSelfPurchaseAmount && !requireConsumeCount) {
Message.warning("请至少选择一个加入条件");
return false;
}
if (requirePurchaseGoods && !recruitGoodsList.value.length) {
Message.warning("请选择指定商品");
return false;
}
if (requireSelfPurchaseAmount && planForm.value.minSelfPurchaseAmount < 0.01) {
Message.warning("自购金额最低设置为0.01元");
return false;
}
if (requireConsumeCount && planForm.value.minConsumeCount < 1) {
Message.warning("消费笔数最低设置为1笔");
return false;
}
return true;
}
function openGoodsSelect() {
skuSelectRef.value.open("goods");
const data = recruitGoodsList.value.map((item) => ({
id: item.skuId,
goodsId: item.goodsId,
goodsName: item.goodsName,
sn: item.sn,
simpleSpecs: item.simpleSpecs,
price: item.price,
quantity: item.quantity,
small: item.thumbnail,
}));
skuSelectRef.value.goodsData = data;
}
function selectedGoodsData(items) {
recruitGoodsList.value = (items || []).map((item) => ({
skuId: item.id,
goodsId: item.goodsId,
goodsName: item.goodsName,
sn: item.sn || "",
simpleSpecs: item.simpleSpecs || "",
price: item.price,
quantity: item.quantity,
thumbnail: item.small || item.thumbnail || "",
}));
}
function removeRecruitGoods(index) {
recruitGoodsList.value.splice(index, 1);
}
function buildSavePayload() {
const fields = planForm.value.requireApplyInfo
? recruitFields.value.map((field, index) => ({
id: field.id,
fieldName: field.fieldName.trim(),
fieldType: field.fieldType,
required: field.required !== false,
placeholder: field.placeholder || "",
sortOrder: index,
}))
: [];
return {
...planForm.value,
id: planForm.value.id || undefined,
publishStatus: true,
posterEnabled: !!(planForm.value.posterBackground && planForm.value.posterBackground.trim()),
recruitGoods: planForm.value.requirePurchaseGoods
? recruitGoodsList.value.map((item) => ({
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
sn: item.sn,
simpleSpecs: item.simpleSpecs,
thumbnail: item.thumbnail,
price: item.price,
}))
: [],
goodsIds: planForm.value.requirePurchaseGoods
? recruitGoodsList.value.map((item) => item.goodsId)
: [],
fields,
};
}
function applyPlanResult(result) {
planForm.value = { ...buildDefaultPlan(), ...result };
recruitFields.value = normalizeFields(result.fields);
recruitGoodsList.value = (result.recruitGoods || []).map((item) => ({
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
sn: item.sn || "",
simpleSpecs: item.simpleSpecs || "",
thumbnail: item.thumbnail,
price: item.price,
}));
}
function loadPlan() {
getCurrentRecruitPlan().then((res) => {
if (res.success && res.result) {
applyPlanResult(res.result);
}
});
}
function savePlan() {
if (!validateRecruitFields() || !validateJoinConditions()) {
return;
}
planSaveLoading.value = true;
saveRecruitPlan(buildSavePayload())
.then((res) => {
if (res.success) {
Message.success("保存成功");
if (res.result) {
applyPlanResult(res.result);
} else {
loadPlan();
}
}
return true;
},
validateJoinConditions() {
if (this.planForm.joinConditionType !== "CONDITIONAL") {
return true;
}
const {
requirePurchaseGoods,
requireSelfPurchaseAmount,
requireConsumeCount,
} = this.planForm;
if (!requirePurchaseGoods && !requireSelfPurchaseAmount && !requireConsumeCount) {
this.$Message.warning("请至少选择一个加入条件");
return false;
}
if (requirePurchaseGoods && !this.recruitGoodsList.length) {
this.$Message.warning("请选择指定商品");
return false;
}
if (requireSelfPurchaseAmount && this.planForm.minSelfPurchaseAmount < 0.01) {
this.$Message.warning("自购金额最低设置为0.01元");
return false;
}
if (requireConsumeCount && this.planForm.minConsumeCount < 1) {
this.$Message.warning("消费笔数最低设置为1笔");
return false;
}
return true;
},
openGoodsSelect() {
this.$refs.skuSelect.open("goods");
const data = this.recruitGoodsList.map((item) => ({
id: item.skuId,
goodsId: item.goodsId,
goodsName: item.goodsName,
sn: item.sn,
simpleSpecs: item.simpleSpecs,
price: item.price,
quantity: item.quantity,
small: item.thumbnail,
}));
this.$refs.skuSelect.goodsData = data;
},
selectedGoodsData(items) {
this.recruitGoodsList = (items || []).map((item) => ({
skuId: item.id,
goodsId: item.goodsId,
goodsName: item.goodsName,
sn: item.sn || "",
simpleSpecs: item.simpleSpecs || "",
price: item.price,
quantity: item.quantity,
thumbnail: item.small || item.thumbnail || "",
}));
},
removeRecruitGoods(index) {
this.recruitGoodsList.splice(index, 1);
},
buildSavePayload() {
const fields = this.planForm.requireApplyInfo
? this.recruitFields.map((field, index) => ({
id: field.id,
fieldName: field.fieldName.trim(),
fieldType: field.fieldType,
required: field.required !== false,
placeholder: field.placeholder || "",
sortOrder: index,
}))
: [];
return {
...this.planForm,
id: this.planForm.id || undefined,
publishStatus: true,
posterEnabled: !!(this.planForm.posterBackground && this.planForm.posterBackground.trim()),
recruitGoods: this.planForm.requirePurchaseGoods
? this.recruitGoodsList.map((item) => ({
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
sn: item.sn,
simpleSpecs: item.simpleSpecs,
thumbnail: item.thumbnail,
price: item.price,
}))
: [],
goodsIds: this.planForm.requirePurchaseGoods
? this.recruitGoodsList.map((item) => item.goodsId)
: [],
fields,
};
},
applyPlanResult(result) {
this.planForm = { ...buildDefaultPlan(), ...result };
this.recruitFields = this.normalizeFields(result.fields);
this.recruitGoodsList = (result.recruitGoods || []).map((item) => ({
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
sn: item.sn || "",
simpleSpecs: item.simpleSpecs || "",
thumbnail: item.thumbnail,
price: item.price,
}));
},
loadPlan() {
getCurrentRecruitPlan().then((res) => {
if (res.success && res.result) {
this.applyPlanResult(res.result);
}
});
},
savePlan() {
if (!this.validateRecruitFields() || !this.validateJoinConditions()) {
return;
}
this.planSaveLoading = true;
saveRecruitPlan(this.buildSavePayload())
.then((res) => {
if (res.success) {
this.$Message.success("保存成功");
if (res.result) {
this.applyPlanResult(res.result);
} else {
this.loadPlan();
}
}
})
.finally(() => {
this.planSaveLoading = false;
});
},
},
};
})
.finally(() => {
planSaveLoading.value = false;
});
}
</script>
<style scoped lang="scss">

View File

@@ -0,0 +1,91 @@
<template>
<div style="background-color: #fff">
<el-form ref="formRef" :model="form" label-width="160px" style="padding: 10px">
<el-divider content-position="left">招募计划</el-divider>
<el-form-item label="开启招募">
<el-switch v-model="form.recruitOpen" />
</el-form-item>
<el-form-item label="申请方式">
<el-radio-group v-model="form.applyMode">
<el-radio value="MANUAL_APPLY">手动申请</el-radio>
<el-radio value="AUTO_APPLY_WHEN_QUALIFIED">满足条件自动申请</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核方式">
<el-radio-group v-model="form.auditMode">
<el-radio value="MANUAL_AUDIT">人工审核</el-radio>
<el-radio value="AUTO_AUDIT">自动审核</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="开启加入门槛">
<el-switch v-model="form.thresholdEnabled" />
</el-form-item>
<template v-if="form.thresholdEnabled">
<el-form-item label="门槛组合">
<el-radio-group v-model="form.thresholdMatch">
<el-radio value="ALL">需同时满足</el-radio>
<el-radio value="ANY">满足其一</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="最低消费金额">
<el-input-number v-model="form.consumeAmountMin" :min="0" :precision="2" />
</el-form-item>
<el-form-item label="最低消费笔数">
<el-input-number v-model="form.consumeCountMin" :min="0" />
</el-form-item>
<el-form-item label="需购买指定商品">
<el-switch v-model="form.requireBuyGoods" />
</el-form-item>
</template>
<el-form-item label="招募页标题">
<el-input v-model="form.recruitPageTitle" />
</el-form-item>
<el-form-item label="招募页说明">
<el-input v-model="form.recruitPageDesc" type="textarea" />
</el-form-item>
<el-form-item label="展示招募入口">
<el-switch v-model="form.showRecruitEntry" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { setSetting, getSetting } from "@/api/index";
import { Message } from "@/utils/message";
defineOptions({ name: "distributionRecruitSetting" });
const formRef = ref(null);
const form = ref({
recruitOpen: true,
applyMode: "MANUAL_APPLY",
auditMode: "MANUAL_AUDIT",
thresholdEnabled: false,
thresholdMatch: "ALL",
consumeAmountMin: 0,
consumeCountMin: 0,
requireBuyGoods: false,
recruitPageTitle: "成为分销员",
recruitPageDesc: "分享商品赚取佣金",
showRecruitEntry: true,
});
onMounted(() => {
getSetting("DISTRIBUTION_RECRUIT_SETTING").then((res) => {
if (res.success && res.result) {
form.value = { ...form.value, ...res.result };
}
});
});
function submit() {
setSetting("DISTRIBUTION_RECRUIT_SETTING", form.value).then((res) => {
if (res.success) Message.success("保存成功");
});
}
</script>

View File

@@ -4,7 +4,7 @@
<el-tabs v-model="activeTab" class="setting-tabs">
<el-tab-pane label="基础设置" name="base">
<el-form
ref="form"
ref="formRef"
:model="form"
:rules="formRule"
label-width="180px"
@@ -69,7 +69,7 @@
<el-tab-pane label="客户关系" name="customer">
<el-form
ref="customerForm"
ref="customerFormRef"
:model="customerForm"
label-width="180px"
label-position="right"
@@ -116,7 +116,7 @@
<el-tab-pane label="结算方案" name="settlement">
<el-form
ref="settlementForm"
ref="settlementFormRef"
:model="settlementForm"
label-width="180px"
label-position="right"
@@ -234,7 +234,7 @@
<el-tab-pane label="商品分销设置" name="featured">
<el-form
ref="featuredForm"
ref="featuredFormRef"
:model="featuredForm"
label-width="220px"
label-position="right"
@@ -428,7 +428,8 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, computed, watch, onMounted, nextTick } from "vue";
import { setSetting, getSetting } from "@/api/index";
import {
getDistributionCustomerSetting,
@@ -445,6 +446,7 @@ import {
} from "@/api/distribution";
import uploadPicInput from "@/components/lili/upload-pic-input";
import { regular } from "@/utils";
import { Message } from "@/utils/message";
const DEFAULT_FORM = {
isOpen: true,
@@ -485,430 +487,426 @@ const DEFAULT_SORT_OPTIONS = [
{ value: "LATEST_LISTING", label: "商品上架越晚越靠前" },
];
export default {
name: "distributionSetting",
components: {
uploadPicInput,
},
data() {
return {
activeTab: "base",
saving: {
base: false,
customer: false,
settlement: false,
featured: false,
poster: false,
inviteCard: false,
},
form: { ...DEFAULT_FORM },
formRule: {
isOpen: [regular.REQUIRED],
distributorName: [regular.REQUIRED, regular.VARCHAR20],
},
customerForm: {
bindMode: "SHORT_TERM",
permanentBindMode: "PAID_WITHIN_DAYS",
permanentPaidDays: 1,
shortTermBindDays: 7,
},
settlementForm: {
selfPurchaseCommissionMode: "SELF",
},
posterForm: { ...DEFAULT_POSTER_FORM },
inviteCardForm: { ...DEFAULT_INVITE_CARD_FORM },
featuredForm: { ...DEFAULT_FEATURED_FORM },
defaultSortOptions: DEFAULT_SORT_OPTIONS,
featuredCustomGoods: [],
featuredPickerVisible: false,
featuredPickerLoading: false,
featuredPickerList: [],
featuredPickerTotal: 0,
featuredPickerDraft: [],
featuredPickerSelection: [],
syncingFeaturedPickerSelection: false,
featuredPickerSearch: {
goodsName: "",
authStatus: "PASS",
pageNumber: 1,
pageSize: 20,
},
defineOptions({ name: "distributionSetting" });
const formRef = ref(null);
const featuredPickerTable = ref(null);
const activeTab = ref("base");
const saving = reactive({
base: false,
customer: false,
settlement: false,
featured: false,
poster: false,
inviteCard: false,
});
const form = ref({ ...DEFAULT_FORM });
const formRule = reactive({
isOpen: [regular.REQUIRED],
distributorName: [regular.REQUIRED, regular.VARCHAR20],
});
const customerForm = ref({
bindMode: "SHORT_TERM",
permanentBindMode: "PAID_WITHIN_DAYS",
permanentPaidDays: 1,
shortTermBindDays: 7,
});
const settlementForm = ref({
selfPurchaseCommissionMode: "SELF",
});
const posterForm = ref({ ...DEFAULT_POSTER_FORM });
const inviteCardForm = ref({ ...DEFAULT_INVITE_CARD_FORM });
const featuredForm = ref({ ...DEFAULT_FEATURED_FORM });
const defaultSortOptions = ref(DEFAULT_SORT_OPTIONS);
const featuredCustomGoods = ref([]);
const featuredPickerVisible = ref(false);
const featuredPickerLoading = ref(false);
const featuredPickerList = ref([]);
const featuredPickerTotal = ref(0);
const featuredPickerDraft = ref([]);
const featuredPickerSelection = ref([]);
const syncingFeaturedPickerSelection = ref(false);
const featuredPickerSearch = reactive({
goodsName: "",
authStatus: "PASS",
pageNumber: 1,
pageSize: 20,
});
const featuredGoodsLimit = computed(() => {
return featuredForm.value.displayCount === 10 ? 10 : 5;
});
const featuredGoodsOverLimit = computed(() => {
return featuredForm.value.sourceMode === "CUSTOM" && featuredCustomGoods.value.length > featuredGoodsLimit.value;
});
const featuredDisplayCountDesc = computed(() => {
if (featuredForm.value.sourceMode === "CUSTOM") {
return `最多选择 ${featuredGoodsLimit.value} 个,分销员端按顺序展示。`;
}
return `分销员端展示前 ${featuredGoodsLimit.value} 个。`;
});
watch(
() => customerForm.value.permanentBindMode,
(value) => {
if (value === "PAID_WITHIN_DAYS") {
normalizePermanentPaidDays();
}
}
);
onMounted(() => {
init();
});
function init() {
loadBaseSetting();
loadFeaturedSetting();
loadPosterSetting();
loadInviteCardSetting();
loadCustomerSetting();
loadSettlementSetting();
}
function loadBaseSetting() {
getSetting("DISTRIBUTION_SETTING").then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
form.value = {
...DEFAULT_FORM,
...result,
distributorName: result.distributorName || "分销员",
parentAttributionMode: result.parentAttributionMode || DEFAULT_FORM.parentAttributionMode,
};
},
computed: {
featuredGoodsLimit() {
return this.featuredForm.displayCount === 10 ? 10 : 5;
},
featuredGoodsOverLimit() {
return this.featuredForm.sourceMode === "CUSTOM" && this.featuredCustomGoods.length > this.featuredGoodsLimit;
},
featuredDisplayCountDesc() {
if (this.featuredForm.sourceMode === "CUSTOM") {
return `最多选择 ${this.featuredGoodsLimit} 个,分销员端按顺序展示。`;
});
}
function loadPosterSetting() {
getDistributionPromotionPosterSetting().then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
posterForm.value = {
...DEFAULT_POSTER_FORM,
memberInfoVisible: result.memberInfoVisible !== false,
backgroundImage: result.backgroundImage || "",
slogan: result.slogan || DEFAULT_POSTER_FORM.slogan,
textColor: result.textColor || DEFAULT_POSTER_FORM.textColor,
};
});
}
function submitPoster() {
const slogan = (posterForm.value.slogan || "").trim();
if (!slogan) {
Message.warning("请填写推荐语");
return;
}
saving.poster = true;
putDistributionPromotionPosterSetting({
memberInfoVisible: posterForm.value.memberInfoVisible,
backgroundImage: (posterForm.value.backgroundImage || "").trim(),
slogan,
textColor: posterForm.value.textColor || DEFAULT_POSTER_FORM.textColor,
})
.then((res) => {
if (res.success) {
Message.success("操作成功");
loadPosterSetting();
}
return `分销员端展示前 ${this.featuredGoodsLimit} 个。`;
},
},
mounted() {
this.init();
},
watch: {
"customerForm.permanentBindMode"(value) {
if (value === "PAID_WITHIN_DAYS") {
this.normalizePermanentPaidDays();
})
.finally(() => {
saving.poster = false;
});
}
function loadInviteCardSetting() {
getDistributionInviteCardSetting().then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
inviteCardForm.value = {
...DEFAULT_INVITE_CARD_FORM,
memberInfoVisible: result.memberInfoVisible !== false,
backgroundImage: result.backgroundImage || "",
slogan: result.slogan || DEFAULT_INVITE_CARD_FORM.slogan,
textColor: result.textColor || DEFAULT_INVITE_CARD_FORM.textColor,
};
});
}
function submitInviteCard() {
const slogan = (inviteCardForm.value.slogan || "").trim();
if (!slogan) {
Message.warning("请填写推荐语");
return;
}
saving.inviteCard = true;
putDistributionInviteCardSetting({
memberInfoVisible: inviteCardForm.value.memberInfoVisible,
backgroundImage: (inviteCardForm.value.backgroundImage || "").trim(),
slogan,
textColor: inviteCardForm.value.textColor || DEFAULT_INVITE_CARD_FORM.textColor,
})
.then((res) => {
if (res.success) {
Message.success("操作成功");
loadInviteCardSetting();
}
},
},
methods: {
init() {
this.loadBaseSetting();
this.loadFeaturedSetting();
this.loadPosterSetting();
this.loadInviteCardSetting();
this.loadCustomerSetting();
this.loadSettlementSetting();
},
loadBaseSetting() {
getSetting("DISTRIBUTION_SETTING").then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
this.form = {
...DEFAULT_FORM,
...result,
distributorName: result.distributorName || "分销员",
parentAttributionMode: result.parentAttributionMode || DEFAULT_FORM.parentAttributionMode,
};
})
.finally(() => {
saving.inviteCard = false;
});
}
function loadFeaturedSetting() {
getDistributionFeaturedGoodsSetting().then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
featuredForm.value = {
...DEFAULT_FEATURED_FORM,
defaultSort: result.defaultSort || DEFAULT_FEATURED_FORM.defaultSort,
defaultShareText: result.defaultShareText || "",
centerVisible: !!result.centerVisible,
showSales: !!result.showSales,
sourceMode: result.sourceMode || DEFAULT_FEATURED_FORM.sourceMode,
matchRule: result.matchRule || DEFAULT_FEATURED_FORM.matchRule,
displayCount: result.displayCount === 10 ? 10 : 5,
};
featuredCustomGoods.value = Array.isArray(result.customGoods) ? [...result.customGoods] : [];
});
}
function loadCustomerSetting() {
getDistributionCustomerSetting().then((res) => {
if (res.success && res.result) {
customerForm.value = { ...customerForm.value, ...res.result };
normalizePermanentPaidDays();
}
});
}
function normalizePermanentPaidDays() {
const days = Number(customerForm.value.permanentPaidDays);
if (!Number.isFinite(days) || days < 1) {
customerForm.value.permanentPaidDays = 1;
} else {
customerForm.value.permanentPaidDays = Math.floor(days);
}
}
function onPermanentPaidDaysChange(value) {
if (value == null || value < 1) {
customerForm.value.permanentPaidDays = 1;
}
}
function loadSettlementSetting() {
getDistributionSettlementSetting().then((res) => {
if (res.success && res.result) {
settlementForm.value = { ...settlementForm.value, ...res.result };
}
});
}
function openFeaturedGoodsPicker() {
featuredPickerDraft.value = featuredCustomGoods.value.map((item) => ({ ...item }));
featuredPickerVisible.value = true;
}
function loadFeaturedPickerGoods() {
featuredPickerLoading.value = true;
syncingFeaturedPickerSelection.value = true;
getDistributionGoods({ ...featuredPickerSearch })
.then((res) => {
if (res.success) {
featuredPickerList.value = res.result?.records || [];
featuredPickerTotal.value = res.result?.total || 0;
}
})
.finally(() => {
featuredPickerLoading.value = false;
nextTick(() => {
syncFeaturedPickerSelection();
nextTick(() => {
syncingFeaturedPickerSelection.value = false;
});
});
},
loadPosterSetting() {
getDistributionPromotionPosterSetting().then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
this.posterForm = {
...DEFAULT_POSTER_FORM,
memberInfoVisible: result.memberInfoVisible !== false,
backgroundImage: result.backgroundImage || "",
slogan: result.slogan || DEFAULT_POSTER_FORM.slogan,
textColor: result.textColor || DEFAULT_POSTER_FORM.textColor,
};
});
},
submitPoster() {
const slogan = (this.posterForm.slogan || "").trim();
if (!slogan) {
this.$Message.warning("请填写推荐语");
return;
});
}
function syncFeaturedPickerSelection() {
const table = featuredPickerTable.value;
if (!table) {
return;
}
const selectedSkuIds = new Set(
featuredPickerDraft.value.map((item) => item.skuId).filter(Boolean)
);
table.clearSelection();
featuredPickerList.value.forEach((row) => {
if (row.skuId && selectedSkuIds.has(row.skuId)) {
table.toggleRowSelection(row, true);
}
});
}
function searchFeaturedPickerGoods() {
featuredPickerSearch.pageNumber = 1;
loadFeaturedPickerGoods();
}
function onFeaturedPickerPageSizeChange() {
featuredPickerSearch.pageNumber = 1;
loadFeaturedPickerGoods();
}
function onFeaturedPickerSelectionChange(selection) {
if (syncingFeaturedPickerSelection.value) {
return;
}
const selectedSkuIds = new Set((selection || []).map((item) => item.skuId).filter(Boolean));
const currentPageSkuIds = new Set(featuredPickerList.value.map((item) => item.skuId).filter(Boolean));
const remaining = featuredPickerDraft.value.filter((item) => {
if (!currentPageSkuIds.has(item.skuId)) {
return true;
}
return selectedSkuIds.has(item.skuId);
});
const remainingSkuIds = new Set(remaining.map((item) => item.skuId));
const added = [];
(selection || []).forEach((item) => {
if (!item.skuId || remainingSkuIds.has(item.skuId)) {
return;
}
remainingSkuIds.add(item.skuId);
added.push(toFeaturedGoodsItem(item));
});
featuredPickerDraft.value = [...remaining, ...added];
featuredPickerSelection.value = selection || [];
}
function toFeaturedGoodsItem(item) {
return {
id: item.id,
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
specs: item.specs,
thumbnail: item.thumbnail,
price: item.price,
commission: item.commission,
storeName: item.storeName,
};
}
function featuredGoodsLimitMessage() {
const limit = featuredGoodsLimit.value;
return `主推商品数量为${limit}时,最多选择${limit}个自定义商品`;
}
function confirmFeaturedPicker() {
const next = featuredPickerDraft.value.filter((item) => item.skuId);
if (!next.length) {
Message.warning("请至少选择一个分销商品");
return;
}
const limit = featuredGoodsLimit.value;
if (next.length > limit) {
Message.warning(featuredGoodsLimitMessage());
return;
}
featuredCustomGoods.value = next;
featuredPickerVisible.value = false;
featuredPickerSelection.value = [];
}
function removeFeaturedGoods(index) {
featuredCustomGoods.value.splice(index, 1);
}
function buildFeaturedPayload() {
return {
defaultSort: featuredForm.value.defaultSort,
defaultShareText: (featuredForm.value.defaultShareText || "").trim(),
centerVisible: featuredForm.value.centerVisible,
showSales: featuredForm.value.showSales,
sourceMode: featuredForm.value.sourceMode,
matchRule: featuredForm.value.matchRule,
displayCount: featuredForm.value.displayCount === 10 ? 10 : 5,
skuIds:
featuredForm.value.sourceMode === "CUSTOM"
? featuredCustomGoods.value.map((item) => item.skuId).filter(Boolean)
: undefined,
};
}
function submitFeatured() {
if (featuredForm.value.sourceMode === "CUSTOM" && !featuredCustomGoods.value.length) {
Message.warning("请至少选择一个自定义主推商品");
return;
}
if (featuredGoodsOverLimit.value) {
Message.warning(featuredGoodsLimitMessage());
return;
}
saving.featured = true;
putDistributionFeaturedGoodsSetting(buildFeaturedPayload())
.then((res) => {
if (res.success) {
Message.success("操作成功");
loadFeaturedSetting();
}
this.saving.poster = true;
putDistributionPromotionPosterSetting({
memberInfoVisible: this.posterForm.memberInfoVisible,
backgroundImage: (this.posterForm.backgroundImage || "").trim(),
slogan,
textColor: this.posterForm.textColor || DEFAULT_POSTER_FORM.textColor,
})
.finally(() => {
saving.featured = false;
});
}
function buildBasePayload() {
return {
isOpen: form.value.isOpen,
distributorName: (form.value.distributorName || "分销员").trim(),
upgradeConditionVisible: form.value.upgradeConditionVisible,
parentDistributorVisible: form.value.parentDistributorVisible,
parentAttributionMode: form.value.parentAttributionMode,
};
}
function submitBase() {
formRef.value.validate((valid) => {
if (!valid) return;
saving.base = true;
setSetting("DISTRIBUTION_SETTING", buildBasePayload())
.then((res) => {
if (res.success) {
Message.success("操作成功");
loadBaseSetting();
}
})
.then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadPosterSetting();
}
})
.finally(() => {
this.saving.poster = false;
});
},
loadInviteCardSetting() {
getDistributionInviteCardSetting().then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
this.inviteCardForm = {
...DEFAULT_INVITE_CARD_FORM,
memberInfoVisible: result.memberInfoVisible !== false,
backgroundImage: result.backgroundImage || "",
slogan: result.slogan || DEFAULT_INVITE_CARD_FORM.slogan,
textColor: result.textColor || DEFAULT_INVITE_CARD_FORM.textColor,
};
.finally(() => {
saving.base = false;
});
},
submitInviteCard() {
const slogan = (this.inviteCardForm.slogan || "").trim();
if (!slogan) {
this.$Message.warning("请填写推荐语");
return;
});
}
function submitCustomer() {
normalizePermanentPaidDays();
if (
customerForm.value.bindMode === "PERMANENT" &&
customerForm.value.permanentBindMode === "PAID_WITHIN_DAYS" &&
customerForm.value.permanentPaidDays < 1
) {
Message.warning("永久锁客支付天数不能低于1天");
return;
}
saving.customer = true;
putDistributionCustomerSetting(customerForm.value)
.then((res) => {
if (res.success) {
Message.success("操作成功");
loadCustomerSetting();
}
this.saving.inviteCard = true;
putDistributionInviteCardSetting({
memberInfoVisible: this.inviteCardForm.memberInfoVisible,
backgroundImage: (this.inviteCardForm.backgroundImage || "").trim(),
slogan,
textColor: this.inviteCardForm.textColor || DEFAULT_INVITE_CARD_FORM.textColor,
})
.then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadInviteCardSetting();
}
})
.finally(() => {
this.saving.inviteCard = false;
});
},
loadFeaturedSetting() {
getDistributionFeaturedGoodsSetting().then((res) => {
if (!res.success || !res.result) {
return;
}
const result = res.result;
this.featuredForm = {
...DEFAULT_FEATURED_FORM,
defaultSort: result.defaultSort || DEFAULT_FEATURED_FORM.defaultSort,
defaultShareText: result.defaultShareText || "",
centerVisible: !!result.centerVisible,
showSales: !!result.showSales,
sourceMode: result.sourceMode || DEFAULT_FEATURED_FORM.sourceMode,
matchRule: result.matchRule || DEFAULT_FEATURED_FORM.matchRule,
displayCount: result.displayCount === 10 ? 10 : 5,
};
this.featuredCustomGoods = Array.isArray(result.customGoods) ? [...result.customGoods] : [];
});
},
loadCustomerSetting() {
getDistributionCustomerSetting().then((res) => {
if (res.success && res.result) {
this.customerForm = { ...this.customerForm, ...res.result };
this.normalizePermanentPaidDays();
}
});
},
normalizePermanentPaidDays() {
const days = Number(this.customerForm.permanentPaidDays);
if (!Number.isFinite(days) || days < 1) {
this.customerForm.permanentPaidDays = 1;
} else {
this.customerForm.permanentPaidDays = Math.floor(days);
})
.finally(() => {
saving.customer = false;
});
}
function submitSettlement() {
saving.settlement = true;
putDistributionSettlementSetting(settlementForm.value)
.then((res) => {
if (res.success) {
Message.success("操作成功");
loadSettlementSetting();
}
},
onPermanentPaidDaysChange(value) {
if (value == null || value < 1) {
this.customerForm.permanentPaidDays = 1;
}
},
loadSettlementSetting() {
getDistributionSettlementSetting().then((res) => {
if (res.success && res.result) {
this.settlementForm = { ...this.settlementForm, ...res.result };
}
});
},
openFeaturedGoodsPicker() {
this.featuredPickerDraft = this.featuredCustomGoods.map((item) => ({ ...item }));
this.featuredPickerVisible = true;
},
loadFeaturedPickerGoods() {
this.featuredPickerLoading = true;
this.syncingFeaturedPickerSelection = true;
getDistributionGoods({ ...this.featuredPickerSearch })
.then((res) => {
if (res.success) {
this.featuredPickerList = res.result?.records || [];
this.featuredPickerTotal = res.result?.total || 0;
}
})
.finally(() => {
this.featuredPickerLoading = false;
this.$nextTick(() => {
this.syncFeaturedPickerSelection();
this.$nextTick(() => {
this.syncingFeaturedPickerSelection = false;
});
});
});
},
syncFeaturedPickerSelection() {
const table = this.$refs.featuredPickerTable;
if (!table) {
return;
}
const selectedSkuIds = new Set(
this.featuredPickerDraft.map((item) => item.skuId).filter(Boolean)
);
table.clearSelection();
this.featuredPickerList.forEach((row) => {
if (row.skuId && selectedSkuIds.has(row.skuId)) {
table.toggleRowSelection(row, true);
}
});
},
searchFeaturedPickerGoods() {
this.featuredPickerSearch.pageNumber = 1;
this.loadFeaturedPickerGoods();
},
onFeaturedPickerPageSizeChange() {
this.featuredPickerSearch.pageNumber = 1;
this.loadFeaturedPickerGoods();
},
onFeaturedPickerSelectionChange(selection) {
if (this.syncingFeaturedPickerSelection) {
return;
}
const selectedSkuIds = new Set((selection || []).map((item) => item.skuId).filter(Boolean));
const currentPageSkuIds = new Set(this.featuredPickerList.map((item) => item.skuId).filter(Boolean));
const remaining = this.featuredPickerDraft.filter((item) => {
if (!currentPageSkuIds.has(item.skuId)) {
return true;
}
return selectedSkuIds.has(item.skuId);
});
const remainingSkuIds = new Set(remaining.map((item) => item.skuId));
const added = [];
(selection || []).forEach((item) => {
if (!item.skuId || remainingSkuIds.has(item.skuId)) {
return;
}
remainingSkuIds.add(item.skuId);
added.push(this.toFeaturedGoodsItem(item));
});
this.featuredPickerDraft = [...remaining, ...added];
this.featuredPickerSelection = selection || [];
},
toFeaturedGoodsItem(item) {
return {
id: item.id,
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
specs: item.specs,
thumbnail: item.thumbnail,
price: item.price,
commission: item.commission,
storeName: item.storeName,
};
},
featuredGoodsLimitMessage() {
const limit = this.featuredGoodsLimit;
return `主推商品数量为${limit}时,最多选择${limit}个自定义商品`;
},
confirmFeaturedPicker() {
const next = this.featuredPickerDraft.filter((item) => item.skuId);
if (!next.length) {
this.$Message.warning("请至少选择一个分销商品");
return;
}
const limit = this.featuredGoodsLimit;
if (next.length > limit) {
this.$Message.warning(this.featuredGoodsLimitMessage());
return;
}
this.featuredCustomGoods = next;
this.featuredPickerVisible = false;
this.featuredPickerSelection = [];
},
removeFeaturedGoods(index) {
this.featuredCustomGoods.splice(index, 1);
},
buildFeaturedPayload() {
return {
defaultSort: this.featuredForm.defaultSort,
defaultShareText: (this.featuredForm.defaultShareText || "").trim(),
centerVisible: this.featuredForm.centerVisible,
showSales: this.featuredForm.showSales,
sourceMode: this.featuredForm.sourceMode,
matchRule: this.featuredForm.matchRule,
displayCount: this.featuredForm.displayCount === 10 ? 10 : 5,
skuIds:
this.featuredForm.sourceMode === "CUSTOM"
? this.featuredCustomGoods.map((item) => item.skuId).filter(Boolean)
: undefined,
};
},
submitFeatured() {
if (this.featuredForm.sourceMode === "CUSTOM" && !this.featuredCustomGoods.length) {
this.$Message.warning("请至少选择一个自定义主推商品");
return;
}
if (this.featuredGoodsOverLimit) {
this.$Message.warning(this.featuredGoodsLimitMessage());
return;
}
this.saving.featured = true;
putDistributionFeaturedGoodsSetting(this.buildFeaturedPayload())
.then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadFeaturedSetting();
}
})
.finally(() => {
this.saving.featured = false;
});
},
buildBasePayload() {
return {
isOpen: this.form.isOpen,
distributorName: (this.form.distributorName || "分销员").trim(),
upgradeConditionVisible: this.form.upgradeConditionVisible,
parentDistributorVisible: this.form.parentDistributorVisible,
parentAttributionMode: this.form.parentAttributionMode,
};
},
submitBase() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.saving.base = true;
setSetting("DISTRIBUTION_SETTING", this.buildBasePayload())
.then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadBaseSetting();
}
})
.finally(() => {
this.saving.base = false;
});
});
},
submitCustomer() {
this.normalizePermanentPaidDays();
if (
this.customerForm.bindMode === "PERMANENT" &&
this.customerForm.permanentBindMode === "PAID_WITHIN_DAYS" &&
this.customerForm.permanentPaidDays < 1
) {
this.$Message.warning("永久锁客支付天数不能低于1天");
return;
}
this.saving.customer = true;
putDistributionCustomerSetting(this.customerForm)
.then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadCustomerSetting();
}
})
.finally(() => {
this.saving.customer = false;
});
},
submitSettlement() {
this.saving.settlement = true;
putDistributionSettlementSetting(this.settlementForm)
.then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadSettlementSetting();
}
})
.finally(() => {
this.saving.settlement = false;
});
},
},
};
})
.finally(() => {
saving.settlement = false;
});
}
</script>
<style scoped lang="scss">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -4,22 +4,23 @@
</div>
</template>
<script>
export default {
name: "ExternalLink",
data() {
return {
link: this.$router.currentRoute.name,
};
},
mounted() {
let iframe = document.getElementById("ifram");
const deviceWidth = document.body.clientWidth;
const deviceHeight = document.body.clientHeight;
iframe.style.width = deviceWidth + "px";
iframe.style.height = deviceHeight + "px";
}
};
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
defineOptions({ name: "ExternalLink" });
const router = useRouter();
const link = ref(router.currentRoute.name);
onMounted(() => {
let iframe = document.getElementById("ifram");
const deviceWidth = document.body.clientWidth;
const deviceHeight = document.body.clientHeight;
iframe.style.width = deviceWidth + "px";
iframe.style.height = deviceHeight + "px";
});
</script>
<style lang="scss" scoped>

View File

@@ -24,39 +24,39 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import * as API_Finance from "@/api/finance";
import { downloadBlob } from "@/utils/downloadBlob";
export default {
name: "finance-payment-method",
data() {
return { loading: false, dateRange: [], data: [] };
},
mounted() {
this.loadData();
},
methods: {
buildParams() {
const p = {};
if (this.dateRange?.length === 2) {
p.startDate = this.dateRange[0];
p.endDate = this.dateRange[1];
}
return p;
},
loadData() {
this.loading = true;
API_Finance.getPaymentMethodReport(this.buildParams()).then((res) => {
this.loading = false;
if (res.success) this.data = res.result || [];
});
},
handleExport() {
API_Finance.exportPaymentMethodReport(this.buildParams()).then((blob) => {
downloadBlob(blob, "支付方式汇总.xlsx");
});
},
},
};
defineOptions({ name: "finance-payment-method" });
const loading = ref(false);
const dateRange = ref([]);
const data = ref([]);
onMounted(() => {
loadData();
});
function buildParams() {
const p = {};
if (dateRange.value?.length === 2) {
p.startDate = dateRange.value[0];
p.endDate = dateRange.value[1];
}
return p;
}
function loadData() {
loading.value = true;
API_Finance.getPaymentMethodReport(buildParams()).then((res) => {
loading.value = false;
if (res.success) data.value = res.result || [];
});
}
function handleExport() {
API_Finance.exportPaymentMethodReport(buildParams()).then((blob) => {
downloadBlob(blob, "支付方式汇总.xlsx");
});
}
</script>

View File

@@ -39,44 +39,40 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import * as API_Finance from "@/api/finance";
import { downloadBlob } from "@/utils/downloadBlob";
export default {
name: "finance-platform-report",
data() {
return {
loading: false,
dateRange: [],
params: { granularity: "DAY" },
data: [],
};
},
mounted() {
this.loadData();
},
methods: {
buildParams() {
const p = { ...this.params };
if (this.dateRange && this.dateRange.length === 2) {
p.startDate = this.dateRange[0];
p.endDate = this.dateRange[1];
}
return p;
},
loadData() {
this.loading = true;
API_Finance.getPlatformReport(this.buildParams()).then((res) => {
this.loading = false;
if (res.success) this.data = res.result || [];
});
},
handleExport() {
API_Finance.exportPlatformReport(this.buildParams()).then((blob) => {
downloadBlob(blob, "平台经营报表.xlsx");
});
},
},
};
defineOptions({ name: "finance-platform-report" });
const loading = ref(false);
const dateRange = ref([]);
const params = ref({ granularity: "DAY" });
const data = ref([]);
onMounted(() => {
loadData();
});
function buildParams() {
const p = { ...params.value };
if (dateRange.value && dateRange.value.length === 2) {
p.startDate = dateRange.value[0];
p.endDate = dateRange.value[1];
}
return p;
}
function loadData() {
loading.value = true;
API_Finance.getPlatformReport(buildParams()).then((res) => {
loading.value = false;
if (res.success) data.value = res.result || [];
});
}
function handleExport() {
API_Finance.exportPlatformReport(buildParams()).then((blob) => {
downloadBlob(blob, "平台经营报表.xlsx");
});
}
</script>

View File

@@ -34,44 +34,40 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Finance from "@/api/finance";
import { downloadBlob } from "@/utils/downloadBlob";
export default {
name: "finance-refund-log",
data() {
return {
loading: false,
searchForm: { pageNumber: 1, pageSize: 20, orderSn: "" },
data: [],
total: 0,
};
},
mounted() {
this.getDataList();
},
methods: {
handleSearch() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
API_Finance.refundLogPage(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
handleExport() {
const { pageNumber, pageSize, ...rest } = this.searchForm;
API_Finance.exportRefundFlow(rest).then((blob) => {
downloadBlob(blob, "退款流水.xlsx");
});
},
},
};
defineOptions({ name: "finance-refund-log" });
const loading = ref(false);
const searchForm = reactive({ pageNumber: 1, pageSize: 20, orderSn: "" });
const data = ref([]);
const total = ref(0);
onMounted(() => {
getDataList();
});
function handleSearch() {
searchForm.pageNumber = 1;
getDataList();
}
function getDataList() {
loading.value = true;
API_Finance.refundLogPage(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function handleExport() {
const { pageNumber, pageSize, ...rest } = searchForm;
API_Finance.exportRefundFlow(rest).then((blob) => {
downloadBlob(blob, "退款流水.xlsx");
});
}
</script>

View File

@@ -16,31 +16,30 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import * as API_Finance from "@/api/finance";
import { downloadBlob } from "@/utils/downloadBlob";
export default {
name: "finance-settlement-ledger",
data() {
return { loading: false, data: [] };
},
mounted() {
this.loadData();
},
methods: {
loadData() {
this.loading = true;
API_Finance.getSettlementLedger({}).then((res) => {
this.loading = false;
if (res.success) this.data = res.result || [];
});
},
handleExport() {
API_Finance.exportSettlementLedger({}).then((blob) => {
downloadBlob(blob, "结算台账.xlsx");
});
},
},
};
defineOptions({ name: "finance-settlement-ledger" });
const loading = ref(false);
const data = ref([]);
onMounted(() => {
loadData();
});
function loadData() {
loading.value = true;
API_Finance.getSettlementLedger({}).then((res) => {
loading.value = false;
if (res.success) data.value = res.result || [];
});
}
function handleExport() {
API_Finance.exportSettlementLedger({}).then((blob) => {
downloadBlob(blob, "结算台账.xlsx");
});
}
</script>

View File

@@ -26,39 +26,39 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import * as API_Finance from "@/api/finance";
import { downloadBlob } from "@/utils/downloadBlob";
export default {
name: "finance-store-settlement",
data() {
return { loading: false, dateRange: [], data: [] };
},
mounted() {
this.loadData();
},
methods: {
buildParams() {
const p = {};
if (this.dateRange?.length === 2) {
p.startDate = this.dateRange[0];
p.endDate = this.dateRange[1];
}
return p;
},
loadData() {
this.loading = true;
API_Finance.getStoreSettlementReport(this.buildParams()).then((res) => {
this.loading = false;
if (res.success) this.data = res.result || [];
});
},
handleExport() {
API_Finance.exportStoreSettlementReport(this.buildParams()).then((blob) => {
downloadBlob(blob, "店铺结算汇总.xlsx");
});
},
},
};
defineOptions({ name: "finance-store-settlement" });
const loading = ref(false);
const dateRange = ref([]);
const data = ref([]);
onMounted(() => {
loadData();
});
function buildParams() {
const p = {};
if (dateRange.value?.length === 2) {
p.startDate = dateRange.value[0];
p.endDate = dateRange.value[1];
}
return p;
}
function loadData() {
loading.value = true;
API_Finance.getStoreSettlementReport(buildParams()).then((res) => {
loading.value = false;
if (res.success) data.value = res.result || [];
});
}
function handleExport() {
API_Finance.exportStoreSettlementReport(buildParams()).then((blob) => {
downloadBlob(blob, "店铺结算汇总.xlsx");
});
}
</script>

View File

@@ -36,59 +36,55 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Finance from "@/api/finance";
import { getRequest } from "@/libs/axios";
import { downloadBlob } from "@/utils/downloadBlob";
export default {
name: "finance-wallet-log",
data() {
return {
loading: false,
searchForm: { memberName: "" },
dateRange: [],
pageNumber: 1,
pageSize: 20,
data: [],
total: 0,
};
},
mounted() {
this.getDataList();
},
methods: {
handleSearch() {
this.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
const params = {
pageNumber: this.pageNumber,
pageSize: this.pageSize,
memberName: this.searchForm.memberName,
};
getRequest("/wallet/log", params).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
}).catch(() => {
this.loading = false;
});
},
handleExport() {
const params = { memberName: this.searchForm.memberName };
if (this.dateRange?.length === 2) {
params.startDate = this.dateRange[0];
params.endDate = this.dateRange[1];
}
API_Finance.exportWalletLog(params).then((blob) => {
downloadBlob(blob, "钱包流水.xlsx");
});
},
},
};
defineOptions({ name: "finance-wallet-log" });
const loading = ref(false);
const searchForm = reactive({ memberName: "" });
const dateRange = ref([]);
const pageNumber = ref(1);
const pageSize = ref(20);
const data = ref([]);
const total = ref(0);
onMounted(() => {
getDataList();
});
function handleSearch() {
pageNumber.value = 1;
getDataList();
}
function getDataList() {
loading.value = true;
const params = {
pageNumber: pageNumber.value,
pageSize: pageSize.value,
memberName: searchForm.memberName,
};
getRequest("/wallet/log", params).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
}).catch(() => {
loading.value = false;
});
}
function handleExport() {
const params = { memberName: searchForm.memberName };
if (dateRange.value?.length === 2) {
params.startDate = dateRange.value[0];
params.endDate = dateRange.value[1];
}
API_Finance.exportWalletLog(params).then((blob) => {
downloadBlob(blob, "钱包流水.xlsx");
});
}
</script>

View File

@@ -52,7 +52,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<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="计量单位" prop="name">
<el-input v-model="form.name" clearable style="width: 100%" />
</el-form-item>
@@ -65,7 +65,8 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import {
addGoodsUnit,
getGoodsUnitPage,
@@ -73,161 +74,163 @@ import {
delGoodsUnit,
} from "@/api/index";
import { regular } from "@/utils";
import { Message, Modal } from "@/utils/message";
export default {
name: "goods-unit",
data() {
return {
loading: true,
modalVisible: false,
modalTitle: "",
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
name: "",
},
form: {
name: "",
},
formValidate: {
name: [regular.REQUIRED, regular.VARCHAR5],
},
submitLoading: false,
selectList: [],
selectCount: 0,
data: [],
total: 0,
};
},
methods: {
init() {
this.getDataList();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
clearSelectAll() {
this.$refs.table?.clearSelection();
},
changeSelect(e) {
this.selectList = e;
this.selectCount = e.length;
},
getDataList() {
this.loading = true;
getGoodsUnitPage(this.searchForm)
.then((res) => {
defineOptions({ name: "goods-unit" });
const loading = ref(true);
const modalVisible = ref(false);
const modalTitle = ref("");
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
name: "",
});
const form = ref({
name: "",
});
const formValidate = reactive({
name: [regular.REQUIRED, regular.VARCHAR5],
});
const submitLoading = ref(false);
const selectList = ref([]);
const selectCount = ref(0);
const data = ref([]);
const total = ref(0);
const table = ref(null);
const formRef = ref(null);
// 未在 data 中声明、原代码直接挂载到 this 上的字段
let id = null;
function init() {
getDataList();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
clearSelectAll();
}
function changePageSize(v) {
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function clearSelectAll() {
table.value?.clearSelection();
}
function changeSelect(e) {
selectList.value = e;
selectCount.value = e.length;
}
function getDataList() {
loading.value = true;
getGoodsUnitPage(searchForm)
.then((res) => {
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
})
.finally(() => {
loading.value = false;
});
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
if (modalTitle.value == "添加") {
if (data.value.find((item) => item.name == form.value.name)) {
Message.error("请勿添加重复计量单位!");
submitLoading.value = false;
return;
}
delete form.value.id;
addGoodsUnit(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
})
.finally(() => {
this.loading = false;
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
if (this.modalTitle == "添加") {
if (this.data.find((item) => item.name == this.form.name)) {
this.$Message.error("请勿添加重复计量单位!");
this.submitLoading = false;
return;
}
delete this.form.id;
addGoodsUnit(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
updateGoodsUnit(this.id, this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
updateGoodsUnit(id, form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}
}
});
}
function add() {
modalTitle.value = "添加";
form.value = {};
formRef.value?.resetFields();
modalVisible.value = true;
}
function edit(v) {
id = v.id;
modalTitle.value = "修改";
modalVisible.value = true;
form.value.name = v.name;
}
function remove(v) {
Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.name + " ?",
loading: true,
onOk: () => {
delGoodsUnit(v.id).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
add() {
this.modalTitle = "添加";
this.form = {};
this.$refs.form?.resetFields();
this.modalVisible = true;
},
edit(v) {
this.id = v.id;
this.modalTitle = "修改";
this.modalVisible = true;
this.form.name = v.name;
},
remove(v) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.name + " ?",
loading: true,
onOk: () => {
delGoodsUnit(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
}
function delAll() {
if (selectCount.value <= 0) {
Message.warning("您还未选择要删除的数据");
return;
}
Modal.confirm({
title: "确认删除",
content: "您确认要删除所选的 " + selectCount.value + " 条数据?",
loading: true,
onOk: () => {
let ids = "";
selectList.value.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1);
delGoodsUnit(ids).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
clearSelectAll();
getDataList();
}
});
},
delAll() {
if (this.selectCount <= 0) {
this.$Message.warning("您还未选择要删除的数据");
return;
}
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除所选的 " + this.selectCount + " 条数据?",
loading: true,
onOk: () => {
let ids = "";
this.selectList.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1);
delGoodsUnit(ids).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.clearSelectAll();
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
};
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -216,7 +216,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>
@@ -255,7 +255,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="batchAuditForm" :model="batchAuditForm" label-width="100px">
<el-form ref="batchAuditFormRef" :model="batchAuditForm" label-width="100px">
<el-form-item label="审核结果" prop="auth_flag">
<el-radio-group v-model="batchAuditForm.auth_flag">
<el-radio :value="1">审核通过</el-radio>
@@ -278,7 +278,7 @@
</el-dialog>
<el-dialog v-model="goodsGroupFlag" title="批量设置商品分组" width="420px">
<el-form ref="goodsGroupForm" :model="goodsGroupForm" :rules="goodsGroupRule" label-width="90px">
<el-form ref="goodsGroupFormRef" :model="goodsGroupForm" :rules="goodsGroupRule" label-width="90px">
<el-form-item label="商品分组" prop="groupId">
<el-select v-model="goodsGroupForm.groupId" clearable filterable style="width: 240px">
<el-option
@@ -298,7 +298,8 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref } from "vue";
import {
getGoodsListData,
getGoodsNumerData,
@@ -310,421 +311,423 @@ import {
} from "@/api/goods";
import { ElMessage, ElMessageBox } from "element-plus";
import { unitPrice, customRouterPush } from "@/utils/filters";
export default {
name: "goods",
data() {
return {
id: "", //要操作的id
loading: true, // 表单加载状态
modalVisible: false, // 添加或编辑显示
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
groupId: "",
},
underForm: {
// 下架原因
reason: "",
},
goodsAuditForm: {
// 商品审核表单
auth_flag: 1,
},
auditModalVisible: false, // 审核弹框显示状态
auditSubmitLoading: false,
currentAuditGoods: null, // 当前审核的商品
submitLoading: false, // 添加或编辑提交状态
data: [], // 表单数据
total: 0, // 表单数据总数
currentStatus: "ALL",
goodsNumerData: {},
selectedRows: [], // 选中的行数据
selectAll: false, // 全选状态
batchAuditModalVisible: false, // 批量审核弹框显示状态
batchAuditSubmitLoading: false,
batchAuditForm: {
auth_flag: 1,
reason: ''
},
goodsGroupFlag: false,
goodsGroupLoading: false,
goodsGroupList: [],
goodsGroupForm: {
groupId: ""
},
goodsGroupRule: {
groupId: [{ required: true, message: "请选择商品分组", trigger: "change" }]
}
defineOptions({ name: "goods" });
// 模板 ref(searchFormRef/underFormRef/batchAuditFormRef/goodsGroupFormRef 因与 data 同名改为 xxxRef)
const searchFormRef = ref(null);
const table = ref(null);
const underFormRef = ref(null);
const auditForm = ref(null);
const batchAuditFormRef = ref(null);
const goodsGroupFormRef = ref(null);
const id = ref(""); //要操作的id
const loading = ref(true); // 表单加载状态
const modalVisible = ref(false); // 添加或编辑显示
const searchForm = reactive({
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
groupId: "",
});
const underForm = reactive({
// 下架原因
reason: "",
});
const goodsAuditForm = reactive({
// 商品审核表单
auth_flag: 1,
});
const auditModalVisible = ref(false); // 审核弹框显示状态
const auditSubmitLoading = ref(false);
const currentAuditGoods = ref(null); // 当前审核的商品
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 batchAuditModalVisible = ref(false); // 批量审核弹框显示状态
const batchAuditSubmitLoading = ref(false);
const batchAuditForm = ref({
auth_flag: 1,
reason: ''
});
const goodsGroupFlag = ref(false);
const goodsGroupLoading = ref(false);
const goodsGroupList = ref([]);
const goodsGroupForm = ref({
groupId: ""
});
const goodsGroupRule = reactive({
groupId: [{ required: true, message: "请选择商品分组", trigger: "change" }]
});
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() {
table.value?.clearSelection?.();
}
function onStatusTabClick(tab) {
const status = tab.paneName ?? tab.props?.name;
goodsStatusClick(status);
}
function salesModelText(v) {
if (v === "RETAIL") return "零售";
if (v === "WHOLESALE") return "批发";
return "其他类型";
}
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 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() {
getDataList();
getNumberData();
loadGoodsGroupList();
}
// 分页 改变页码
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
}
// 分页 改变页数
function changePageSize(v) {
searchForm.pageSize = v;
getDataList();
}
// 搜索
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
getNumberData();
}
// 获取数据
function getDataList() {
loading.value = true;
getGoodsListData(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function getNumberData() {
// 创建一个不包含goodsStatus字段的搜索参数
const { goodsStatus, ...searchParams } = searchForm;
getGoodsNumerData(searchParams).then((res) => {
if (res.success) {
goodsNumerData.value = res.result;
}
})
}
// 编辑
function edit(v) {
id.value = v.id;
if (v.underMessage) {
underForm.reason = v.underMessage;
} else {
underForm.reason = "";
}
modalVisible.value = true;
}
// 下架
function lower() {
const reason = underForm.reason ? underForm.reason.trim() : "";
if (!reason) {
ElMessage.error("下架原因不能为空");
return;
}
let params = {
goodsId: id.value,
reason
};
lowGoods(params).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
modalVisible.value = false;
getDataList();
getNumberData(); // 添加这行
}
});
}
// 上架
function upper(v) {
ElMessageBox.confirm("您确认要上架 " + v.goodsName + " ?", "确认上架", { type: "warning" }).then(() => {
let params = {
goodsId: v.id
};
},
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'}
];
return upGoods(params).then((res) => {
if (res.success) {
ElMessage.success("上架成功");
getDataList();
getNumberData(); // 添加这行
}
});
}).catch(() => {});
}
//查看商品详情
function showDetail(v) {
customRouterPush({
name: "goods-detail",
query: { id: v.id },
});
}
// 商品状态筛选
function goodsStatusClick(name) {
if (name === "ALL" || name === "" || name === undefined || name === null) {
delete searchForm.goodsStatus;
currentStatus.value = "ALL";
} else {
searchForm.goodsStatus = name;
currentStatus.value = name;
}
selectedRows.value = [];
clearTableSelection();
getDataList();
}
function examine(v, authFlag) {
// 审核商品
let examine = "通过";
goodsAuditForm.authFlag = "PASS";
if (authFlag != 1) {
examine = "拒绝";
goodsAuditForm.authFlag = "REFUSE";
}
ElMessageBox.confirm("您确认要审核" + examine + " " + v.goodsName + " ?", "确认审核", { type: "warning" }).then(() => {
goodsAuditForm.goodsIds=v.id;
let formData = new FormData();
formData.append('goodsIds', v.id);
formData.append('authFlag', goodsAuditForm.authFlag);
return authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success("审核成功");
getDataList();
getNumberData();
}
});
}).catch(() => {});
}
// 打开审核弹框
function openAuditModal(goods) {
currentAuditGoods.value = goods;
goodsAuditForm.auth_flag = 1;
goodsAuditForm.reason = '';
auditSubmitLoading.value = false;
auditModalVisible.value = true;
}
// 提交审核
function submitAudit() {
if (!currentAuditGoods.value) {
return;
}
auditSubmitLoading.value = true;
const formData = new FormData();
formData.append('goodsIds', currentAuditGoods.value.id);
formData.append('authFlag', goodsAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success('审核成功');
auditModalVisible.value = false;
getDataList();
getNumberData();
}
},
methods: {
unitPrice,
clearTableSelection() {
this.$refs.table?.clearSelection?.();
},
onStatusTabClick(tab) {
const status = tab.paneName ?? tab.props?.name;
this.goodsStatusClick(status);
},
salesModelText(v) {
if (v === "RETAIL") return "零售";
if (v === "WHOLESALE") return "批发";
return "其他类型";
},
goodsTypeText(v) {
if (v === "PHYSICAL_GOODS") return "实物商品";
if (v === "VIRTUAL_GOODS") return "虚拟商品";
if (v === "E_COUPON") return "电子卡券"; // 卡密商品
if (v === "APPOINTMENT_GOODS") return "预约商品";
return v || "—";
},
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.getDataList();
this.getNumberData();
this.loadGoodsGroupList();
},
// 分页 改变页码
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.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
getNumberData() {
// 创建一个不包含goodsStatus字段的搜索参数
const { goodsStatus, ...searchParams } = this.searchForm;
getGoodsNumerData(searchParams).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 = "";
}).finally(() => {
auditSubmitLoading.value = false;
});
}
function onSelectionChange(selection) {
selectedRows.value = selection;
}
// 批量上架
function batchUpper() {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要上架的商品');
return;
}
const goodsNames = selectedRows.value.map(item => item.goodsName).join('、');
ElMessageBox.confirm(`您确认要上架以下商品吗?\n${goodsNames}`, "确认批量上架", { type: "warning" }).then(() => {
const goodsIds = selectedRows.value.map(item => item.id);
const params = {
goodsId: goodsIds
};
return upGoods(params).then((res) => {
if (res.success) {
ElMessage.success('批量上架成功');
selectedRows.value = [];
selectAll.value = false;
getDataList();
getNumberData();
}
this.modalVisible = true;
},
// 下架
lower() {
const reason = this.underForm.reason ? this.underForm.reason.trim() : "";
if (!reason) {
ElMessage.error("下架原因不能为空");
return;
});
}).catch(() => {});
}
// 批量下架
function batchLower() {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要下架的商品');
return;
}
const goodsNames = selectedRows.value.map(item => item.goodsName).join('、');
ElMessageBox.confirm(`您确认要下架以下商品吗?\n${goodsNames}`, "确认批量下架", { type: "warning" }).then(() => {
const goodsIds = selectedRows.value.map(item => item.id);
const params = {
goodsId: goodsIds,
reason: '批量下架操作'
};
return lowGoods(params).then((res) => {
if (res.success) {
ElMessage.success('批量下架成功');
selectedRows.value = [];
selectAll.value = false;
getDataList();
getNumberData();
}
let params = {
goodsId: this.id,
reason
};
lowGoods(params).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
this.modalVisible = false;
this.getDataList();
this.getNumberData(); // 添加这行
}
});
},
// 上架
upper(v) {
ElMessageBox.confirm("您确认要上架 " + v.goodsName + " ?", "确认上架", { type: "warning" }).then(() => {
let params = {
goodsId: v.id
};
return upGoods(params).then((res) => {
if (res.success) {
ElMessage.success("上架成功");
this.getDataList();
this.getNumberData(); // 添加这行
}
});
}).catch(() => {});
},
});
}).catch(() => {});
}
//查看商品详情
showDetail(v) {
customRouterPush({
name: "goods-detail",
query: { id: v.id },
});
},
// 批量审核
function batchAudit() {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要审核的商品');
return;
}
// 商品状态筛选
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();
},
examine(v, authFlag) {
// 审核商品
let examine = "通过";
this.goodsAuditForm.authFlag = "PASS";
if (authFlag != 1) {
examine = "拒绝";
this.goodsAuditForm.authFlag = "REFUSE";
}
// 重置批量审核表单
batchAuditForm.value = {
auth_flag: 1,
reason: ''
};
batchAuditModalVisible.value = true;
}
ElMessageBox.confirm("您确认要审核" + examine + " " + v.goodsName + " ?", "确认审核", { type: "warning" }).then(() => {
this.goodsAuditForm.goodsIds=v.id;
let formData = new FormData();
formData.append('goodsIds', v.id);
formData.append('authFlag', this.goodsAuditForm.authFlag);
// 提交批量审核
function submitBatchAudit() {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要审核的商品');
return;
}
return authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success("审核成功");
this.getDataList();
this.getNumberData();
}
});
}).catch(() => {});
},
// 如果是拒绝审核,必须填写原因
if (batchAuditForm.value.auth_flag === 2 && !batchAuditForm.value.reason.trim()) {
ElMessage.warning('审核拒绝时必须填写拒绝原因');
return;
}
// 打开审核弹框
openAuditModal(goods) {
this.currentAuditGoods = goods;
this.goodsAuditForm.auth_flag = 1;
this.goodsAuditForm.reason = '';
this.auditSubmitLoading = false;
this.auditModalVisible = true;
},
const actionText = batchAuditForm.value.auth_flag === 1 ? '通过' : '拒绝';
const goodsIds = selectedRows.value.map(item => item.id);
const formData = new FormData();
formData.append('goodsIds', goodsIds.join(','));
formData.append('authFlag', batchAuditForm.value.auth_flag === 1 ? 'PASS' : 'REFUSE');
if (batchAuditForm.value.reason) {
formData.append('reason', batchAuditForm.value.reason);
}
// 提交审核
submitAudit() {
if (!this.currentAuditGoods) {
return;
}
this.auditSubmitLoading = true;
const formData = new FormData();
formData.append('goodsIds', this.currentAuditGoods.id);
formData.append('authFlag', this.goodsAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success('审核成功');
this.auditModalVisible = false;
this.getDataList();
this.getNumberData();
}
}).finally(() => {
this.auditSubmitLoading = false;
});
},
onSelectionChange(selection) {
this.selectedRows = selection;
},
// 批量上架
batchUpper() {
if (this.selectedRows.length === 0) {
ElMessage.warning('请先选择要上架的商品');
return;
}
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
ElMessageBox.confirm(`您确认要上架以下商品吗?\n${goodsNames}`, "确认批量上架", { type: "warning" }).then(() => {
const goodsIds = this.selectedRows.map(item => item.id);
const params = {
goodsId: goodsIds
};
return upGoods(params).then((res) => {
if (res.success) {
ElMessage.success('批量上架成功');
this.selectedRows = [];
this.selectAll = false;
this.getDataList();
this.getNumberData();
}
});
}).catch(() => {});
},
// 批量下架
batchLower() {
if (this.selectedRows.length === 0) {
ElMessage.warning('请先选择要下架的商品');
return;
}
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
ElMessageBox.confirm(`您确认要下架以下商品吗?\n${goodsNames}`, "确认批量下架", { type: "warning" }).then(() => {
const goodsIds = this.selectedRows.map(item => item.id);
const params = {
goodsId: goodsIds,
reason: '批量下架操作'
};
return lowGoods(params).then((res) => {
if (res.success) {
ElMessage.success('批量下架成功');
this.selectedRows = [];
this.selectAll = false;
this.getDataList();
this.getNumberData();
}
});
}).catch(() => {});
},
// 批量审核
batchAudit() {
if (this.selectedRows.length === 0) {
ElMessage.warning('请先选择要审核的商品');
return;
}
// 重置批量审核表单
this.batchAuditForm = {
auth_flag: 1,
reason: ''
};
this.batchAuditModalVisible = true;
},
// 提交批量审核
submitBatchAudit() {
if (this.selectedRows.length === 0) {
ElMessage.warning('请先选择要审核的商品');
return;
}
// 如果是拒绝审核,必须填写原因
if (this.batchAuditForm.auth_flag === 2 && !this.batchAuditForm.reason.trim()) {
ElMessage.warning('审核拒绝时必须填写拒绝原因');
return;
}
const actionText = this.batchAuditForm.auth_flag === 1 ? '通过' : '拒绝';
const goodsIds = this.selectedRows.map(item => item.id);
const formData = new FormData();
formData.append('goodsIds', goodsIds.join(','));
formData.append('authFlag', this.batchAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
if (this.batchAuditForm.reason) {
formData.append('reason', this.batchAuditForm.reason);
}
this.batchAuditSubmitLoading = true;
authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success(`批量审核${actionText}成功`);
this.selectedRows = [];
this.selectAll = false;
this.batchAuditModalVisible = false;
this.clearTableSelection();
this.getDataList();
this.getNumberData();
}
}).finally(() => {
this.batchAuditSubmitLoading = false;
});
},
loadGoodsGroupList() {
getGoodsGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
this.goodsGroupList = res.result.records || [];
}
});
},
openSetGoodsGroup() {
if (this.selectedRows.length === 0) {
ElMessage.warning("请先选择商品");
return;
}
this.goodsGroupFlag = true;
this.goodsGroupLoading = false;
this.goodsGroupForm = { groupId: "" };
this.$nextTick(() => {
if (this.$refs.goodsGroupForm) this.$refs.goodsGroupForm.resetFields();
});
this.loadGoodsGroupList();
},
submitSetGoodsGroup() {
this.$refs.goodsGroupForm.validate((valid) => {
if (!valid) return;
const goodsIds = this.selectedRows.map((item) => item.id);
this.goodsGroupLoading = true;
addGoodsGroupItems(this.goodsGroupForm.groupId, goodsIds).then((res) => {
this.goodsGroupLoading = false;
if (res && res.success) {
ElMessage.success("设置成功");
this.goodsGroupFlag = false;
this.selectedRows = [];
this.selectAll = false;
this.clearTableSelection();
this.getDataList();
} else if (res && res.message) {
ElMessage.error(res.message);
}
}).catch(() => {
this.goodsGroupLoading = false;
ElMessage.error("设置失败,请检查权限或后端接口");
});
});
batchAuditSubmitLoading.value = true;
authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success(`批量审核${actionText}成功`);
selectedRows.value = [];
selectAll.value = false;
batchAuditModalVisible.value = false;
clearTableSelection();
getDataList();
getNumberData();
}
},
mounted() {
this.init();
},
};
}).finally(() => {
batchAuditSubmitLoading.value = false;
});
}
function loadGoodsGroupList() {
getGoodsGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
goodsGroupList.value = res.result.records || [];
}
});
}
function openSetGoodsGroup() {
if (selectedRows.value.length === 0) {
ElMessage.warning("请先选择商品");
return;
}
goodsGroupFlag.value = true;
goodsGroupLoading.value = false;
goodsGroupForm.value = { groupId: "" };
nextTick(() => {
if (goodsGroupFormRef.value) goodsGroupFormRef.value.resetFields();
});
loadGoodsGroupList();
}
function submitSetGoodsGroup() {
goodsGroupFormRef.value.validate((valid) => {
if (!valid) return;
const goodsIds = selectedRows.value.map((item) => item.id);
goodsGroupLoading.value = true;
addGoodsGroupItems(goodsGroupForm.value.groupId, goodsIds).then((res) => {
goodsGroupLoading.value = false;
if (res && res.success) {
ElMessage.success("设置成功");
goodsGroupFlag.value = false;
selectedRows.value = [];
selectAll.value = false;
clearTableSelection();
getDataList();
} else if (res && res.message) {
ElMessage.error(res.message);
}
}).catch(() => {
goodsGroupLoading.value = false;
ElMessage.error("设置失败,请检查权限或后端接口");
});
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
// Tab组件样式

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -87,104 +87,103 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import { authGoods, getAuthGoodsListData } from "@/api/goods";
import { ElMessage, ElMessageBox } from "element-plus";
import { unitPrice, customRouterPush } from "@/utils/filters";
export default {
name: "goodsApply",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
},
goodsAuditForm: {
auth_flag: 1,
},
data: [],
total: 0,
};
},
methods: {
unitPrice,
authFlagText(v) {
const map = {
TOBEAUDITED: "待审核",
PASS: "审核通过",
REFUSE: "审核拒绝",
};
return map[v] || v || "-";
},
authFlagTagType(v) {
const map = {
TOBEAUDITED: "warning",
PASS: "success",
REFUSE: "danger",
};
return map[v] || "info";
},
init() {
this.getDataList();
},
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();
},
getDataList() {
this.loading = true;
this.searchForm.authFlag = 0;
getAuthGoodsListData(this.searchForm)
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
})
.catch(() => {
this.loading = false;
});
},
examine(v, authFlag) {
const examine = authFlag === 1 ? "通过" : "拒绝";
this.goodsAuditForm.authFlag = authFlag === 1 ? "PASS" : "REFUSE";
ElMessageBox.confirm(`您确认要审核${examine} ${v.goodsName} ?`, "确认审核", { type: "warning" }).then(() => {
const formData = new FormData();
formData.append("goodsIds", v.id);
formData.append("authFlag", this.goodsAuditForm.authFlag);
return authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success("审核成功");
this.getDataList();
}
});
}).catch(() => {});
},
showDetail(v) {
customRouterPush({
name: "goods-detail",
query: { id: v.id },
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "goodsApply" });
// 模板中 el-form 的 ref 与数据 searchForm 同名,避免相互覆盖改名
const searchFormRef = ref(null);
const table = ref(null);
const loading = ref(true);
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
});
const goodsAuditForm = ref({
auth_flag: 1,
});
const data = ref([]);
const total = ref(0);
function authFlagText(v) {
const map = {
TOBEAUDITED: "待审核",
PASS: "审核通过",
REFUSE: "审核拒绝",
};
return map[v] || v || "-";
}
function authFlagTagType(v) {
const map = {
TOBEAUDITED: "warning",
PASS: "success",
REFUSE: "danger",
};
return map[v] || "info";
}
function init() {
getDataList();
}
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();
}
function getDataList() {
loading.value = true;
searchForm.value.authFlag = 0;
getAuthGoodsListData(searchForm.value)
.then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
})
.catch(() => {
loading.value = false;
});
}
function examine(v, authFlag) {
const examine = authFlag === 1 ? "通过" : "拒绝";
goodsAuditForm.value.authFlag = authFlag === 1 ? "PASS" : "REFUSE";
ElMessageBox.confirm(`您确认要审核${examine} ${v.goodsName} ?`, "确认审核", { type: "warning" }).then(() => {
const formData = new FormData();
formData.append("goodsIds", v.id);
formData.append("authFlag", goodsAuditForm.value.authFlag);
return authGoods(formData).then((res) => {
if (res.success) {
ElMessage.success("审核成功");
getDataList();
}
});
}).catch(() => {});
}
function showDetail(v) {
customRouterPush({
name: "goods-detail",
query: { id: v.id },
});
}
onMounted(() => {
init();
});
</script>
<style scoped>

View File

@@ -376,307 +376,304 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref } from "vue";
import { useRoute } from "vue-router";
import { View } from "@element-plus/icons-vue";
import { authGoods, getBrandListData, getGoodsDetail } from "@/api/goods";
import { ElMessage } from "element-plus";
export default {
name: "goodsDetail",
components: { View },
data() {
return {
loading: false,
goods: {},
brandList: [],
previewGoodsPicture: "",
goodsPictureVisible: false,
wholesaleData: [],
skuData: [],
activeTab: "basic",
slotCapacityVisible: false,
currentSlotCapacities: [],
goodsAuditForm: {
auth_flag: 1,
},
auditModalVisible: false,
auditSubmitLoading: false,
};
},
computed: {
isAppointmentGoods() {
return this.goods.goodsType === "APPOINTMENT_GOODS";
},
needsLogistics() {
return this.goods.goodsType === "PHYSICAL_GOODS";
},
salesModelDisplayText() {
const type = this.goods.goodsType;
const model = this.goods.salesModel;
if (type === "APPOINTMENT_GOODS") return "预约型";
if (type === "E_COUPON") return "电子卡券";
if (type === "VIRTUAL_GOODS") return "虚拟型";
if (model === "WHOLESALE") return "批发型";
if (model === "RETAIL") return "零售型";
return model || "—";
},
sectionTabs() {
const tabs = [
{ name: "basic", label: "基础信息" },
{ name: "media", label: "图文信息" },
];
if (this.isAppointmentGoods) {
tabs.push({ name: "appointment", label: "预约规则" });
}
tabs.push(
{ name: "price", label: "价格库存" },
{ name: "other", label: "其他信息" },
);
return tabs;
},
categoryText() {
const names = this.goods.categoryName;
if (!names || !names.length) return "—";
return names.join(" > ");
},
/** 兼容扁平列表与历史嵌套分组两种 params 结构 */
displayGoodsParams() {
let list = this.goods.goodsParamsDTOList;
if ((!Array.isArray(list) || !list.length) && this.goods.params) {
list = this.parseParamsJson(this.goods.params);
}
if (!Array.isArray(list) || !list.length) return [];
const flat = [];
list.forEach((item) => {
if (Array.isArray(item?.goodsParamsItemDTOList) && item.goodsParamsItemDTOList.length) {
item.goodsParamsItemDTOList.forEach((child) => {
if (child?.paramName) flat.push(child);
});
} else if (item?.paramName) {
flat.push(item);
}
defineOptions({ name: "goodsDetail" });
const route = useRoute();
const loading = ref(false);
const goods = ref({});
const brandList = ref([]);
const previewGoodsPicture = ref("");
const goodsPictureVisible = ref(false);
const wholesaleData = ref([]);
const skuData = ref([]);
const activeTab = ref("basic");
const slotCapacityVisible = ref(false);
const currentSlotCapacities = ref([]);
const goodsAuditForm = reactive({
auth_flag: 1,
});
const auditModalVisible = ref(false);
const auditSubmitLoading = ref(false);
const isAppointmentGoods = computed(() => {
return goods.value.goodsType === "APPOINTMENT_GOODS";
});
const needsLogistics = computed(() => {
return goods.value.goodsType === "PHYSICAL_GOODS";
});
const salesModelDisplayText = computed(() => {
const type = goods.value.goodsType;
const model = goods.value.salesModel;
if (type === "APPOINTMENT_GOODS") return "预约型";
if (type === "E_COUPON") return "电子卡券";
if (type === "VIRTUAL_GOODS") return "虚拟型";
if (model === "WHOLESALE") return "批发型";
if (model === "RETAIL") return "零售型";
return model || "—";
});
const sectionTabs = computed(() => {
const tabs = [
{ name: "basic", label: "基础信息" },
{ name: "media", label: "图文信息" },
];
if (isAppointmentGoods.value) {
tabs.push({ name: "appointment", label: "预约规则" });
}
tabs.push(
{ name: "price", label: "价格库存" },
{ name: "other", label: "其他信息" },
);
return tabs;
});
const categoryText = computed(() => {
const names = goods.value.categoryName;
if (!names || !names.length) return "—";
return names.join(" > ");
});
/** 兼容扁平列表与历史嵌套分组两种 params 结构 */
const displayGoodsParams = computed(() => {
let list = goods.value.goodsParamsDTOList;
if ((!Array.isArray(list) || !list.length) && goods.value.params) {
list = parseParamsJson(goods.value.params);
}
if (!Array.isArray(list) || !list.length) return [];
const flat = [];
list.forEach((item) => {
if (Array.isArray(item?.goodsParamsItemDTOList) && item.goodsParamsItemDTOList.length) {
item.goodsParamsItemDTOList.forEach((child) => {
if (child?.paramName) flat.push(child);
});
return flat;
},
appointmentSettings() {
return this.goods.appointmentSettings || null;
},
appointmentSlotTemplate() {
return this.goods.appointmentSlotTemplate || null;
},
appointmentSlots() {
return this.appointmentSlotTemplate?.slots || [];
},
bookableDateText() {
const s = this.appointmentSettings;
if (!s) return "—";
if (s.bookableDateMode === "CUSTOM") {
const parts = [];
if (s.bookableCustomStartDate && s.bookableCustomEndDate) {
parts.push(`${s.bookableCustomStartDate} ~ ${s.bookableCustomEndDate}`);
}
const weekdays = this.formatWeekdays(s.bookableWeekdays);
if (weekdays) parts.push(weekdays);
return parts.length ? parts.join(";") : "自定义";
} else if (item?.paramName) {
flat.push(item);
}
});
return flat;
});
const appointmentSettings = computed(() => {
return goods.value.appointmentSettings || null;
});
const appointmentSlotTemplate = computed(() => {
return goods.value.appointmentSlotTemplate || null;
});
const appointmentSlots = computed(() => {
return appointmentSlotTemplate.value?.slots || [];
});
const bookableDateText = computed(() => {
const s = appointmentSettings.value;
if (!s) return "—";
if (s.bookableDateMode === "CUSTOM") {
const parts = [];
if (s.bookableCustomStartDate && s.bookableCustomEndDate) {
parts.push(`${s.bookableCustomStartDate} ~ ${s.bookableCustomEndDate}`);
}
const weekdays = formatWeekdays(s.bookableWeekdays);
if (weekdays) parts.push(weekdays);
return parts.length ? parts.join(";") : "自定义";
}
if (s.bookableDateMode === "EVERY_DAY") return "每天";
return "—";
});
const bookableDaysText = computed(() => {
const s = appointmentSettings.value;
if (!s || s.bookableDays == null) return "—";
return `对用户展示 ${s.bookableDays} 天内的可预约日期`;
});
const advanceBookingText = computed(() => {
const s = appointmentSettings.value;
if (!s) return "—";
if (s.advanceMode === "NONE") return "无需提前预约";
if (s.advanceMode === "REQUIRE_HOURS" && s.advanceHours != null) {
return `需提前 ${s.advanceHours} 小时进行预约`;
}
return "—";
});
const cancelRuleText = computed(() => {
const s = appointmentSettings.value;
if (!s) return "—";
if (s.cancelMode === "NOT_ALLOWED") return "不允许取消";
if (s.cancelMode === "ALLOW_BEFORE_HOURS" && s.cancelAheadHours != null) {
return `服务开始 ${s.cancelAheadHours} 小时之前,允许取消并自动退款`;
}
return "—";
});
const formSubmitText = computed(() => {
const s = appointmentSettings.value;
if (!s) return "—";
if (s.formSubmitMode === "PER_APPOINTMENT") return "每个预约提交一次";
if (s.formSubmitMode === "PER_ORDER") return "每单提交一次";
return "—";
});
onMounted(() => {
loadBrandList();
initGoods(route.query.id);
});
function sectionNo(name) {
const index = sectionTabs.value.findIndex((tab) => tab.name === name);
return index >= 0 ? index + 1 : "";
}
function parseParamsJson(paramsJson) {
if (!paramsJson) return [];
try {
const array = JSON.parse(paramsJson);
if (!Array.isArray(array)) return [];
const items = [];
array.forEach((element) => {
if (Array.isArray(element?.goodsParamsItemDTOList)) {
items.push(...element.goodsParamsItemDTOList);
} else if (element?.paramName) {
items.push(element);
}
if (s.bookableDateMode === "EVERY_DAY") return "每天";
return "—";
},
bookableDaysText() {
const s = this.appointmentSettings;
if (!s || s.bookableDays == null) return "—";
return `对用户展示 ${s.bookableDays} 天内的可预约日期`;
},
advanceBookingText() {
const s = this.appointmentSettings;
if (!s) return "—";
if (s.advanceMode === "NONE") return "无需提前预约";
if (s.advanceMode === "REQUIRE_HOURS" && s.advanceHours != null) {
return `需提前 ${s.advanceHours} 小时进行预约`;
}
return "—";
},
cancelRuleText() {
const s = this.appointmentSettings;
if (!s) return "—";
if (s.cancelMode === "NOT_ALLOWED") return "不允许取消";
if (s.cancelMode === "ALLOW_BEFORE_HOURS" && s.cancelAheadHours != null) {
return `服务开始 ${s.cancelAheadHours} 小时之前,允许取消并自动退款`;
}
return "—";
},
formSubmitText() {
const s = this.appointmentSettings;
if (!s) return "—";
if (s.formSubmitMode === "PER_APPOINTMENT") return "每个预约提交一次";
if (s.formSubmitMode === "PER_ORDER") return "每单提交一次";
return "—";
},
},
mounted() {
this.loadBrandList();
this.initGoods(this.$route.query.id);
},
methods: {
sectionNo(name) {
const index = this.sectionTabs.findIndex((tab) => tab.name === name);
return index >= 0 ? index + 1 : "";
},
parseParamsJson(paramsJson) {
if (!paramsJson) return [];
try {
const array = JSON.parse(paramsJson);
if (!Array.isArray(array)) return [];
const items = [];
array.forEach((element) => {
if (Array.isArray(element?.goodsParamsItemDTOList)) {
items.push(...element.goodsParamsItemDTOList);
} else if (element?.paramName) {
items.push(element);
}
});
return items;
} catch {
return [];
}
},
jumpToTab(name) {
this.activeTab = name;
this.$nextTick(() => {
const el = document.getElementById(`goods-section-${name}`);
el?.scrollIntoView({ behavior: "smooth", block: "start" });
});
},
loadBrandList() {
getBrandListData().then((res) => {
this.brandList = Array.isArray(res) ? res : res?.result || [];
});
},
initGoods(id) {
if (!id) return;
this.loading = true;
getGoodsDetail(id)
.then((res) => {
if (!res?.success) return;
this.goods = res.result || {};
this.skuData = (res.result?.skuList || []).map((sku) => ({
id: sku.id,
specs: sku.simpleSpecs || sku.goodsName,
sn: sku.sn,
weight: sku.weight,
price: sku.price,
image: sku.goodsGalleryList,
quantity: sku.quantity,
slotCapacities: sku.slotCapacities,
}));
this.wholesaleData = res.result?.wholesaleList || [];
})
.finally(() => {
this.loading = false;
});
},
brandText(brandId) {
if (!brandId || brandId === "0") return "—";
const brand = this.brandList.find((item) => String(item.id) === String(brandId));
return brand?.name || brandId;
},
goodsTypeText(v) {
if (v === "PHYSICAL_GOODS") return "实物商品";
if (v === "VIRTUAL_GOODS") return "虚拟商品";
if (v === "E_COUPON") return "电子卡券";
if (v === "APPOINTMENT_GOODS") return "预约商品";
return v || "—";
},
normalizeSlotCapacities(caps) {
if (!Array.isArray(caps) || !caps.length) return [];
return caps.map((item) => ({
startTime: item.startTime || "",
endTime: item.endTime || "",
capacity: item.slotCapacity ?? item.capacity ?? 0,
});
return items;
} catch {
return [];
}
}
function jumpToTab(name) {
activeTab.value = name;
nextTick(() => {
const el = document.getElementById(`goods-section-${name}`);
el?.scrollIntoView({ behavior: "smooth", block: "start" });
});
}
function loadBrandList() {
getBrandListData().then((res) => {
brandList.value = Array.isArray(res) ? res : res?.result || [];
});
}
function initGoods(id) {
if (!id) return;
loading.value = true;
getGoodsDetail(id)
.then((res) => {
if (!res?.success) return;
goods.value = res.result || {};
skuData.value = (res.result?.skuList || []).map((sku) => ({
id: sku.id,
specs: sku.simpleSpecs || sku.goodsName,
sn: sku.sn,
weight: sku.weight,
price: sku.price,
image: sku.goodsGalleryList,
quantity: sku.quantity,
slotCapacities: sku.slotCapacities,
}));
},
hasSlotCapacities(row) {
return this.slotCapacityCount(row) > 0;
},
slotCapacityCount(row) {
return this.normalizeSlotCapacities(row?.slotCapacities).length;
},
openSlotCapacityDialog(row) {
this.currentSlotCapacities = this.normalizeSlotCapacities(row?.slotCapacities);
this.slotCapacityVisible = true;
},
formatSlotCapacity(capacity) {
const value = Number(capacity);
if (!value) return "不限制";
return String(value);
},
marketEnableText(v) {
if (v === "UPPER") return "上架";
if (v === "DOWN") return "下架";
return "—";
},
authFlagText(v) {
if (v === "TOBEAUDITED") return "待审核";
if (v === "PASS") return "通过";
if (v === "REFUSE") return "拒绝";
return v || "—";
},
authFlagTagType(v) {
if (v === "PASS") return "success";
if (v === "TOBEAUDITED") return "warning";
if (v === "REFUSE") return "danger";
return "info";
},
formatServiceModes(modes) {
if (!modes || !modes.length) return "—";
const map = {
IN_STORE: "到店服务",
ON_SITE: "上门服务",
};
return modes.map((m) => map[m] || m).join("、");
},
formatWeekdays(weekdays) {
if (!Array.isArray(weekdays) || !weekdays.length) return "";
const map = {
1: "周一",
2: "周二",
3: "周三",
4: "周四",
5: "周五",
6: "周六",
7: "周日",
};
return weekdays.map((d) => map[d] || d).join("、");
},
handleViewGoodsPicture(url) {
this.previewGoodsPicture = url;
this.goodsPictureVisible = true;
},
openAuditModal() {
this.goodsAuditForm.auth_flag = 1;
this.auditSubmitLoading = false;
this.auditModalVisible = true;
},
submitAudit() {
if (!this.goods.id) return;
this.auditSubmitLoading = true;
const formData = new FormData();
formData.append("goodsIds", this.goods.id);
formData.append("authFlag", this.goodsAuditForm.auth_flag === 1 ? "PASS" : "REFUSE");
authGoods(formData)
.then((res) => {
if (res.success) {
ElMessage.success("审核成功");
this.auditModalVisible = false;
this.initGoods(this.goods.id);
}
})
.finally(() => {
this.auditSubmitLoading = false;
});
},
},
};
wholesaleData.value = res.result?.wholesaleList || [];
})
.finally(() => {
loading.value = false;
});
}
function brandText(brandId) {
if (!brandId || brandId === "0") return "—";
const brand = brandList.value.find((item) => String(item.id) === String(brandId));
return brand?.name || brandId;
}
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 normalizeSlotCapacities(caps) {
if (!Array.isArray(caps) || !caps.length) return [];
return caps.map((item) => ({
startTime: item.startTime || "",
endTime: item.endTime || "",
capacity: item.slotCapacity ?? item.capacity ?? 0,
}));
}
function hasSlotCapacities(row) {
return slotCapacityCount(row) > 0;
}
function slotCapacityCount(row) {
return normalizeSlotCapacities(row?.slotCapacities).length;
}
function openSlotCapacityDialog(row) {
currentSlotCapacities.value = normalizeSlotCapacities(row?.slotCapacities);
slotCapacityVisible.value = true;
}
function formatSlotCapacity(capacity) {
const value = Number(capacity);
if (!value) return "不限制";
return String(value);
}
function marketEnableText(v) {
if (v === "UPPER") return "上架";
if (v === "DOWN") return "下架";
return "—";
}
function authFlagText(v) {
if (v === "TOBEAUDITED") return "待审核";
if (v === "PASS") return "通过";
if (v === "REFUSE") return "拒绝";
return v || "—";
}
function authFlagTagType(v) {
if (v === "PASS") return "success";
if (v === "TOBEAUDITED") return "warning";
if (v === "REFUSE") return "danger";
return "info";
}
function formatServiceModes(modes) {
if (!modes || !modes.length) return "—";
const map = {
IN_STORE: "到店服务",
ON_SITE: "上门服务",
};
return modes.map((m) => map[m] || m).join("、");
}
function formatWeekdays(weekdays) {
if (!Array.isArray(weekdays) || !weekdays.length) return "";
const map = {
1: "周一",
2: "周二",
3: "周三",
4: "周四",
5: "周五",
6: "周六",
7: "周日",
};
return weekdays.map((d) => map[d] || d).join("、");
}
function handleViewGoodsPicture(url) {
previewGoodsPicture.value = url;
goodsPictureVisible.value = true;
}
function openAuditModal() {
goodsAuditForm.auth_flag = 1;
auditSubmitLoading.value = false;
auditModalVisible.value = true;
}
function submitAudit() {
if (!goods.value.id) return;
auditSubmitLoading.value = true;
const formData = new FormData();
formData.append("goodsIds", goods.value.id);
formData.append("authFlag", goodsAuditForm.auth_flag === 1 ? "PASS" : "REFUSE");
authGoods(formData)
.then((res) => {
if (res.success) {
ElMessage.success("审核成功");
auditModalVisible.value = false;
initGoods(goods.value.id);
}
})
.finally(() => {
auditSubmitLoading.value = false;
});
}
</script>
<style lang="scss" scoped>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -87,7 +87,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<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="品牌名称" prop="name">
<el-input v-model="form.name" clearable style="width: 100%" />
</el-form-item>
@@ -110,7 +110,7 @@
<el-dialog v-model="picModelFlag" width="1200px" :show-close="true">
<ossManage
ref="ossManage"
ref="ossManageRef"
:is-component="true"
:initialize="picModelFlag"
@callback="callbackSelected"
@@ -146,13 +146,14 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, nextTick, onMounted, getCurrentInstance } from "vue";
import {
getManagerBrandPage,
addBrand,
updateBrand,
disableBrand,
delBrand,
delBrand as delBrandApi,
getCategoryTree,
getBrandCategoryListData,
saveBrandCategory,
@@ -162,222 +163,234 @@ import { regular } from "@/utils";
import { ElMessage, ElMessageBox } from "element-plus";
import defaultPic from "@/assets/default.png";
export default {
name: "brand",
components: {
ossManage,
},
data() {
return {
defaultPic,
loading: true,
modalType: 0,
modalVisible: false,
modalTitle: "",
picModelFlag: false,
categoryModalVisible: false,
categoryModalTitle: "关联分类",
categoryTreeLoading: false,
categoryTreeData: [],
categoryTreeKey: 0,
categorySubmitLoading: false,
currentBrandId: "",
selectedCategoryIds: [],
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
},
form: {
name: "",
logo: "",
deleteFlag: "",
},
formValidate: {
name: [regular.REQUIRED, regular.VARCHAR20],
logo: [regular.REQUIRED, regular.URL200],
},
submitLoading: false,
data: [],
total: 0,
};
},
methods: {
openLogoPicker() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
callbackSelected(val) {
this.picModelFlag = false;
this.form.logo = val.url;
},
buildCategoryTreeNodes(list) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => ({
id: item.id,
title: item.name,
children: this.buildCategoryTreeNodes(item.children || []),
}));
},
async openCategoryModal(row) {
this.currentBrandId = row.id;
this.categoryModalTitle = "关联分类 - " + (row.name || "");
this.categoryModalVisible = true;
this.categoryTreeLoading = true;
this.categoryTreeKey += 1;
this.categoryTreeData = [];
this.selectedCategoryIds = [];
try {
const [treeRes, bindRes] = await Promise.all([
getCategoryTree(),
getBrandCategoryListData(row.id),
]);
const selectedIds = Array.isArray(bindRes?.result)
? bindRes.result
.map((x) => (typeof x === "string" ? x : x && x.id))
.filter(Boolean)
: [];
this.selectedCategoryIds = selectedIds;
this.categoryTreeData =
treeRes && treeRes.success
? this.buildCategoryTreeNodes(treeRes.result || [])
: [];
} finally {
this.categoryTreeLoading = false;
defineOptions({ name: "brand" });
const { proxy } = getCurrentInstance();
const loading = ref(true);
const modalType = ref(0);
const modalVisible = ref(false);
const modalTitle = ref("");
const picModelFlag = ref(false);
const categoryModalVisible = ref(false);
const categoryModalTitle = ref("关联分类");
const categoryTreeLoading = ref(false);
const categoryTreeData = ref([]);
const categoryTreeKey = ref(0);
const categorySubmitLoading = ref(false);
const currentBrandId = ref("");
const selectedCategoryIds = ref([]);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
});
const form = ref({
name: "",
logo: "",
deleteFlag: "",
});
const formValidate = reactive({
name: [regular.REQUIRED, regular.VARCHAR20],
logo: [regular.REQUIRED, regular.URL200],
});
const submitLoading = ref(false);
const data = ref([]);
const total = ref(0);
const searchFormRef = ref(null);
const formRef = ref(null);
const ossManageRef = ref(null);
function openLogoPicker() {
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function callbackSelected(val) {
picModelFlag.value = false;
form.value.logo = val.url;
}
function buildCategoryTreeNodes(list) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => ({
id: item.id,
title: item.name,
children: buildCategoryTreeNodes(item.children || []),
}));
}
async function openCategoryModal(row) {
currentBrandId.value = row.id;
categoryModalTitle.value = "关联分类 - " + (row.name || "");
categoryModalVisible.value = true;
categoryTreeLoading.value = true;
categoryTreeKey.value += 1;
categoryTreeData.value = [];
selectedCategoryIds.value = [];
try {
const [treeRes, bindRes] = await Promise.all([
getCategoryTree(),
getBrandCategoryListData(row.id),
]);
const selectedIds = Array.isArray(bindRes?.result)
? bindRes.result
.map((x) => (typeof x === "string" ? x : x && x.id))
.filter(Boolean)
: [];
selectedCategoryIds.value = selectedIds;
categoryTreeData.value =
treeRes && treeRes.success
? buildCategoryTreeNodes(treeRes.result || [])
: [];
} finally {
categoryTreeLoading.value = false;
}
}
function onCategoryTreeCheckChange(_data, checkedInfo) {
selectedCategoryIds.value = checkedInfo?.checkedKeys || [];
}
function submitBrandCategory() {
if (!currentBrandId.value) return;
categorySubmitLoading.value = true;
saveBrandCategory(
currentBrandId.value,
(selectedCategoryIds.value || []).map((id) => String(id))
)
.then((res) => {
categorySubmitLoading.value = false;
if (res && res.success) {
ElMessage.success("操作成功");
categoryModalVisible.value = false;
}
},
onCategoryTreeCheckChange(_data, checkedInfo) {
this.selectedCategoryIds = checkedInfo?.checkedKeys || [];
},
submitBrandCategory() {
if (!this.currentBrandId) return;
this.categorySubmitLoading = true;
saveBrandCategory(
this.currentBrandId,
(this.selectedCategoryIds || []).map((id) => String(id))
)
.then((res) => {
this.categorySubmitLoading = false;
if (res && res.success) {
ElMessage.success("操作成功");
this.categoryModalVisible = false;
}
})
.catch(() => {
this.categorySubmitLoading = false;
});
},
handleStatusSwitchChange(row, checked) {
const disable = !checked;
ElMessageBox.confirm(
"您确认要" + (disable ? "禁用" : "启用") + "品牌 " + row.name + " ?",
disable ? "确认禁用" : "确认启用",
{ type: "warning" }
).then(() => {
return disableBrand(row.id, { disable }).then((res) => {
})
.catch(() => {
categorySubmitLoading.value = false;
});
}
function handleStatusSwitchChange(row, checked) {
const disable = !checked;
ElMessageBox.confirm(
"您确认要" + (disable ? "禁用" : "启用") + "品牌 " + row.name + " ?",
disable ? "确认禁用" : "确认启用",
{ type: "warning" }
).then(() => {
return disableBrand(row.id, { disable }).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
}
getDataList();
});
}).catch(() => {
nextTick(() => proxy.$forceUpdate());
});
}
function delBrand(row) {
ElMessageBox.confirm("您确认要删除 " + row.name + " ?", "确认删除", { type: "warning" }).then(() => {
return delBrandApi(row.id).then((res) => {
if (res.success) {
ElMessage.success("品牌删除成功!");
getDataList();
}
});
}).catch(() => {});
}
function init() {
getDataList();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function getDataList() {
loading.value = true;
getManagerBrandPage(searchForm).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) {
submitLoading.value = true;
if (modalType.value === 0) {
delete form.value.id;
addBrand(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("操作成功");
getDataList();
modalVisible.value = false;
}
this.getDataList();
});
}).catch(() => {
this.$nextTick(() => this.$forceUpdate());
});
},
delBrand(row) {
ElMessageBox.confirm("您确认要删除 " + row.name + " ?", "确认删除", { type: "warning" }).then(() => {
return delBrand(row.id).then((res) => {
} else {
updateBrand(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("品牌删除成功!");
this.getDataList();
ElMessage.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}).catch(() => {});
},
init() {
this.getDataList();
},
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();
},
getDataList() {
this.loading = true;
getManagerBrandPage(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) {
this.submitLoading = true;
if (this.modalType === 0) {
delete this.form.id;
addBrand(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
updateBrand(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
}
}
});
},
add() {
this.modalType = 0;
this.modalTitle = "添加";
this.$refs.form?.resetFields();
delete this.form.id;
this.modalVisible = true;
},
edit(v) {
this.modalType = 1;
this.modalTitle = "编辑";
this.$refs.form?.resetFields();
for (const attr in v) {
if (v[attr] === null) {
v[attr] = "";
}
}
const str = JSON.stringify(v);
const data = JSON.parse(str);
this.form = data;
this.modalVisible = true;
},
},
mounted() {
this.init();
},
};
}
});
}
function add() {
modalType.value = 0;
modalTitle.value = "添加";
formRef.value?.resetFields();
delete form.value.id;
modalVisible.value = true;
}
function edit(v) {
modalType.value = 1;
modalTitle.value = "编辑";
formRef.value?.resetFields();
for (const attr in v) {
if (v[attr] === null) {
v[attr] = "";
}
}
const str = JSON.stringify(v);
const data = JSON.parse(str);
form.value = data;
modalVisible.value = true;
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -62,7 +62,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="form" :model="formAdd" label-width="110px" :rules="formValidate">
<el-form ref="formRef" :model="formAdd" label-width="110px" :rules="formValidate">
<el-form-item v-if="showParent" label="上级分类" prop="parentId">
{{ parentTitle }}
<el-input v-model="formAdd.parentId" style="display: none" />
@@ -106,7 +106,7 @@
width="500px"
:close-on-click-modal="false"
>
<el-form ref="brandForm" :model="brandForm" label-width="100px">
<el-form ref="brandFormRef" :model="brandForm" label-width="100px">
<el-select v-model="brandForm.categoryBrands" filterable multiple style="width: 100%">
<el-option v-for="item in brandWay" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
@@ -120,7 +120,8 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import {
delCategory,
disableCategory,
@@ -128,241 +129,249 @@ import {
getCategoryBrandListData,
getCategoryTree,
insertCategory,
saveCategoryBrand,
saveCategoryBrand as apiSaveCategoryBrand,
updateCategory,
} from "@/api/goods";
import uploadPicInput from "@/components/lili/upload-pic-input";
import { regular } from "@/utils";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "goods-category",
components: {
uploadPicInput,
},
data() {
return {
submitLoading: false,
categoryList: [],
loading: false,
brands: [],
categoryId: "",
modalType: 0,
modalVisible: false,
modalBrandVisible: false,
modalTitle: "",
showParent: false,
parentTitle: "",
modalBrandTitle: "",
formAdd: {
parentId: "",
name: "",
image: "",
sortOrder: 0,
deleteFlag: false,
commissionRate: 0,
level: 0,
},
brandForm: {
categoryBrands: [],
},
brandWay: [],
formValidate: {
commissionRate: [regular.REQUIRED, regular.INTEGER],
name: [regular.REQUIRED, regular.VARCHAR20],
sortOrder: [regular.REQUIRED, regular.INTEGER],
},
tableData: [],
};
},
methods: {
normalizeCategoryTree(list) {
if (!Array.isArray(list) || list.length === 0) return;
list.forEach((item) => {
if (!item || typeof item !== "object") return;
if (item.deleteFlag === 0) item.deleteFlag = false;
else if (item.deleteFlag === 1) item.deleteFlag = true;
else item.deleteFlag = !!item.deleteFlag;
if (Array.isArray(item.children) && item.children.length) {
this.normalizeCategoryTree(item.children);
}
});
},
onStatusSwitchChange(row, nextDeleteFlag) {
const previousDeleteFlag = !nextDeleteFlag;
const isClosing = nextDeleteFlag === true;
ElMessageBox.confirm(
`您是否要${isClosing ? "关闭" : "开启"}当前分类 ${row.name} 及其子分类?`,
isClosing ? "确认关闭" : "确认开启",
{ type: "warning", confirmButtonText: "是", cancelButtonText: "否" }
).then(() => {
row._statusLoading = true;
return disableCategory(row.id, { enableOperations: isClosing ? true : 0 }).then((res) => {
row._statusLoading = false;
if (res && res.success) {
ElMessage.success("操作成功");
this.getAllList();
return;
}
row.deleteFlag = previousDeleteFlag;
});
}).catch(() => {
row.deleteFlag = previousDeleteFlag;
});
},
init() {
this.getAllList();
this.getBrandList();
},
getBrandList() {
getBrandListData().then((res) => {
this.brandWay = res;
});
},
brandOperation(v) {
getCategoryBrandListData(v.id).then((res) => {
this.categoryId = v.id;
this.modalBrandTitle = "品牌关联";
this.brandForm.categoryBrands = res.result.map((item) => item.id);
this.modalBrandVisible = true;
});
},
saveCategoryBrand() {
saveCategoryBrand(this.categoryId, this.brandForm).then((res) => {
this.submitLoading = false;
defineOptions({ name: "goods-category" });
const submitLoading = ref(false);
const categoryList = ref([]);
const loading = ref(false);
const brands = ref([]);
const categoryId = ref("");
const modalType = ref(0);
const modalVisible = ref(false);
const modalBrandVisible = ref(false);
const modalTitle = ref("");
const showParent = ref(false);
const parentTitle = ref("");
const modalBrandTitle = ref("");
const formAdd = reactive({
parentId: "",
name: "",
image: "",
sortOrder: 0,
deleteFlag: false,
commissionRate: 0,
level: 0,
});
const brandForm = reactive({
categoryBrands: [],
});
const brandWay = ref([]);
const formValidate = reactive({
commissionRate: [regular.REQUIRED, regular.INTEGER],
name: [regular.REQUIRED, regular.VARCHAR20],
sortOrder: [regular.REQUIRED, regular.INTEGER],
});
const tableData = ref([]);
const formRef = ref(null);
const brandFormRef = ref(null);
function normalizeCategoryTree(list) {
if (!Array.isArray(list) || list.length === 0) return;
list.forEach((item) => {
if (!item || typeof item !== "object") return;
if (item.deleteFlag === 0) item.deleteFlag = false;
else if (item.deleteFlag === 1) item.deleteFlag = true;
else item.deleteFlag = !!item.deleteFlag;
if (Array.isArray(item.children) && item.children.length) {
normalizeCategoryTree(item.children);
}
});
}
function onStatusSwitchChange(row, nextDeleteFlag) {
const previousDeleteFlag = !nextDeleteFlag;
const isClosing = nextDeleteFlag === true;
ElMessageBox.confirm(
`您是否要${isClosing ? "关闭" : "开启"}当前分类 ${row.name} 及其子分类?`,
isClosing ? "确认关闭" : "确认开启",
{ type: "warning", confirmButtonText: "是", cancelButtonText: "否" }
).then(() => {
row._statusLoading = true;
return disableCategory(row.id, { enableOperations: isClosing ? true : 0 }).then((res) => {
row._statusLoading = false;
if (res && res.success) {
ElMessage.success("操作成功");
getAllList();
return;
}
row.deleteFlag = previousDeleteFlag;
});
}).catch(() => {
row.deleteFlag = previousDeleteFlag;
});
}
function init() {
getAllList();
getBrandList();
}
function getBrandList() {
getBrandListData().then((res) => {
brandWay.value = res;
});
}
function brandOperation(v) {
getCategoryBrandListData(v.id).then((res) => {
categoryId.value = v.id;
modalBrandTitle.value = "品牌关联";
brandForm.categoryBrands = res.result.map((item) => item.id);
modalBrandVisible.value = true;
});
}
function saveCategoryBrand() {
apiSaveCategoryBrand(categoryId.value, brandForm).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("操作成功");
modalBrandVisible.value = false;
}
});
}
function addChildren(v) {
modalType.value = 0;
modalTitle.value = "添加子分类";
parentTitle.value = v.name;
formAdd.level = Number(v.level) + 1;
formAdd.commissionRate = v.commissionRate;
showParent.value = true;
delete formAdd.id;
formAdd.parentId = v.id;
modalVisible.value = true;
}
function edit(v) {
modalType.value = 1;
modalTitle.value = "编辑";
formAdd.id = v.id;
formAdd.name = v.name;
formAdd.level = v.level;
formAdd.parentId = v.parentId;
formAdd.sortOrder = v.sortOrder;
formAdd.commissionRate = v.commissionRate;
formAdd.deleteFlag = v.deleteFlag;
formAdd.image = v.image;
showParent.value = false;
modalVisible.value = true;
}
function addParent() {
modalType.value = 0;
modalTitle.value = "添加一级分类";
parentTitle.value = "顶级分类";
showParent.value = true;
formRef.value?.resetFields();
delete formAdd.id;
formAdd.parentId = 0;
formAdd.level = 0;
modalVisible.value = true;
}
function Submit() {
formRef.value.validate((valid) => {
if (!valid) return;
submitLoading.value = true;
if (modalType.value === 0) {
delete formAdd.id;
insertCategory(formAdd).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("操作成功");
this.modalBrandVisible = false;
ElMessage.success("添加成功");
getAllList();
modalVisible.value = false;
formRef.value.resetFields();
}
});
},
addChildren(v) {
this.modalType = 0;
this.modalTitle = "添加子分类";
this.parentTitle = v.name;
this.formAdd.level = Number(v.level) + 1;
this.formAdd.commissionRate = v.commissionRate;
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;
this.formAdd.sortOrder = v.sortOrder;
this.formAdd.commissionRate = v.commissionRate;
this.formAdd.deleteFlag = v.deleteFlag;
this.formAdd.image = v.image;
this.showParent = false;
this.modalVisible = true;
},
addParent() {
this.modalType = 0;
this.modalTitle = "添加一级分类";
this.parentTitle = "顶级分类";
this.showParent = true;
this.$refs.form?.resetFields();
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;
if (this.modalType === 0) {
delete this.formAdd.id;
insertCategory(this.formAdd).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("添加成功");
this.getAllList();
this.modalVisible = false;
this.$refs.form.resetFields();
}
});
} else {
updateCategory(this.formAdd).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("修改成功");
this.getAllList();
this.modalVisible = false;
this.$refs.form.resetFields();
}
});
} else {
updateCategory(formAdd).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("修改成功");
getAllList();
modalVisible.value = false;
formRef.value.resetFields();
}
});
},
remove(v) {
ElMessageBox.confirm("您确认要删除 " + v.name + " ?", "确认删除", { type: "warning" }).then(() => {
return delCategory(v.id).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
this.getAllList();
}
});
}).catch(() => {});
},
getAllList() {
this.loading = true;
getCategoryTree()
.then((res) => {
this.loading = false;
if (res.success) {
localStorage.setItem("category", JSON.stringify(res.result));
this.normalizeCategoryTree(res.result);
this.categoryList = JSON.parse(JSON.stringify(res.result));
this.tableData = JSON.parse(JSON.stringify(res.result));
}
})
.catch(() => {
this.loading = false;
});
},
enable(v) {
ElMessageBox.confirm(
"您是否要启用当前分类 " + v.name + " 及其子分类?",
"确认启用",
{ type: "warning", confirmButtonText: "是", cancelButtonText: "否" }
).then(() => {
return disableCategory(v.id, { enableOperations: 0 }).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
this.getAllList();
}
});
}).catch(() => {
this.getAllList();
});
},
disable(v) {
ElMessageBox.confirm(
"您是否要禁用当前分类 " + v.name + " 及其子分类?",
"确认禁用",
{ type: "warning", confirmButtonText: "是", cancelButtonText: "否" }
).then(() => {
return disableCategory(v.id, { enableOperations: true }).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
this.getAllList();
}
});
}).catch(() => {
this.getAllList();
});
},
},
mounted() {
this.init();
},
};
}
});
}
function remove(v) {
ElMessageBox.confirm("您确认要删除 " + v.name + " ?", "确认删除", { type: "warning" }).then(() => {
return delCategory(v.id).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
getAllList();
}
});
}).catch(() => {});
}
function getAllList() {
loading.value = true;
getCategoryTree()
.then((res) => {
loading.value = false;
if (res.success) {
localStorage.setItem("category", JSON.stringify(res.result));
normalizeCategoryTree(res.result);
categoryList.value = JSON.parse(JSON.stringify(res.result));
tableData.value = JSON.parse(JSON.stringify(res.result));
}
})
.catch(() => {
loading.value = false;
});
}
function enable(v) {
ElMessageBox.confirm(
"您是否要启用当前分类 " + v.name + " 及其子分类?",
"确认启用",
{ type: "warning", confirmButtonText: "是", cancelButtonText: "否" }
).then(() => {
return disableCategory(v.id, { enableOperations: 0 }).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
getAllList();
}
});
}).catch(() => {
getAllList();
});
}
function disable(v) {
ElMessageBox.confirm(
"您是否要禁用当前分类 " + v.name + " 及其子分类?",
"确认禁用",
{ type: "warning", confirmButtonText: "是", cancelButtonText: "否" }
).then(() => {
return disableCategory(v.id, { enableOperations: true }).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
getAllList();
}
});
}).catch(() => {
getAllList();
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -1,7 +1,7 @@
<template>
<div class="search">
<el-card>
<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="参数名称" prop="paramName">
<el-input v-model="form.paramName" clearable style="width: 520px" />
</el-form-item>
@@ -92,7 +92,9 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
getCategoryTree,
getGoodsParamsDetail,
@@ -102,6 +104,11 @@ import {
import { regular } from "@/utils";
import { ElMessage } from "element-plus";
defineOptions({ name: "parameterEdit" });
const route = useRoute();
const router = useRouter();
const getOptionText = (value) => {
if (value && typeof value === "object") return value.value;
return value;
@@ -213,228 +220,228 @@ const buildFormState = (source, parameterId) => {
return formState;
};
export default {
name: "parameterEdit",
data() {
return {
submitLoading: false,
modalType: 0,
categoryModalVisible: false,
categoryModalTitle: "关联分类",
categoryTreeLoading: false,
categoryTreeData: [],
categoryTreeSource: [],
categoryIdNameMap: {},
categoryTreeKey: 0,
selectedCategoryIds: [],
form: {
paramName: "",
options: [{ value: "" }],
required: 0,
isIndex: 0,
sort: 0,
categoryIds: [],
},
formValidate: {
paramName: [regular.REQUIRED, regular.VARCHAR5],
options: [{ required: true, validator: validateOptions, trigger: "change" }],
required: [{ required: true, validator: validateRadioRequired("请选择是否必填"), trigger: "change" }],
isIndex: [{ required: true, validator: validateRadioRequired("请选择是否索引"), trigger: "change" }],
sort: [regular.REQUIRED, regular.INTEGER],
categoryIds: [
{
type: "array",
required: true,
min: 1,
message: "请选择关联分类",
trigger: "change",
},
],
},
};
},
computed: {
id() {
return this.$route.query && this.$route.query.id ? String(this.$route.query.id) : "";
const formRef = ref(null);
const categoryTree = ref(null);
const submitLoading = ref(false);
const modalType = ref(0);
const categoryModalVisible = ref(false);
const categoryModalTitle = ref("关联分类");
const categoryTreeLoading = ref(false);
const categoryTreeData = ref([]);
const categoryTreeSource = ref([]);
const categoryIdNameMap = ref({});
const categoryTreeKey = ref(0);
const selectedCategoryIds = ref([]);
const form = ref({
paramName: "",
options: [{ value: "" }],
required: 0,
isIndex: 0,
sort: 0,
categoryIds: [],
});
const formValidate = reactive({
paramName: [regular.REQUIRED, regular.VARCHAR5],
options: [{ required: true, validator: validateOptions, trigger: "change" }],
required: [{ required: true, validator: validateRadioRequired("请选择是否必填"), trigger: "change" }],
isIndex: [{ required: true, validator: validateRadioRequired("请选择是否索引"), trigger: "change" }],
sort: [regular.REQUIRED, regular.INTEGER],
categoryIds: [
{
type: "array",
required: true,
min: 1,
message: "请选择关联分类",
trigger: "change",
},
selectedCategoryNamesText() {
if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) return "";
const selectedSet = new Set(toStringArray(this.selectedCategoryIds));
const isSelected = (node) => {
if (!node) return false;
const nodeId = node.id !== undefined && node.id !== null ? String(node.id) : "";
if (nodeId && selectedSet.has(nodeId)) return true;
const children = Array.isArray(node.children) ? node.children : [];
if (children.length === 0) return false;
return children.every(isSelected);
};
const collect = (node, out) => {
if (!node) return;
if (isSelected(node)) {
if (node.name) out.push(node.name);
return;
}
const children = Array.isArray(node.children) ? node.children : [];
children.forEach((c) => collect(c, out));
};
const names = [];
this.categoryTreeSource.forEach((n) => collect(n, names));
return names.join(",");
},
},
watch: {
"form.required"(val) {
const n = normalize01(val, 0);
if (val !== n) this.form.required = n;
},
"form.isIndex"(val) {
const n = normalize01(val, 0);
if (val !== n) this.form.isIndex = n;
},
},
methods: {
back() {
this.$router.push({ name: "goods-parameter" });
},
syncCategoryIds(ids) {
const categoryIds = toStringArray(ids);
this.selectedCategoryIds = categoryIds;
if (!this.form) this.form = {};
this.form.categoryIds = categoryIds;
},
touchOptionsValidate() {
this.$nextTick(() => {
const form = this.$refs.form;
if (form && typeof form.validateField === "function") {
// validateField rejects on failure; catch to avoid dev-server [object Object] overlay
form.validateField("options", () => {});
}
});
},
addOptionRow() {
if (!Array.isArray(this.form.options)) this.form.options = [];
this.form.options.push({ value: "" });
},
removeOptionRow(index) {
if (!Array.isArray(this.form.options)) this.form.options = [];
this.form.options.splice(index, 1);
this.touchOptionsValidate();
},
buildCategoryTreeNodes(list) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => ({
id: item.id,
title: item.name,
children: this.buildCategoryTreeNodes(item.children || []),
}));
},
async loadDetail(parameterId) {
if (!parameterId) return;
const res = await getGoodsParamsDetail(parameterId).catch(() => null);
if (!(res && res.success && res.result)) return;
const dto = res.result;
this.form = buildFormState(dto, parameterId);
this.selectedCategoryIds = [...this.form.categoryIds];
},
async openCategoryModal() {
this.categoryModalVisible = true;
this.categoryTreeLoading = true;
this.categoryTreeKey += 1;
],
});
const id = computed(() =>
route.query && route.query.id ? String(route.query.id) : ""
);
const selectedCategoryNamesText = computed(() => {
if (!Array.isArray(categoryTreeSource.value) || categoryTreeSource.value.length === 0) return "";
const selectedSet = new Set(toStringArray(selectedCategoryIds.value));
const isSelected = (node) => {
if (!node) return false;
const nodeId = node.id !== undefined && node.id !== null ? String(node.id) : "";
if (nodeId && selectedSet.has(nodeId)) return true;
const children = Array.isArray(node.children) ? node.children : [];
if (children.length === 0) return false;
return children.every(isSelected);
};
const collect = (node, out) => {
if (!node) return;
if (isSelected(node)) {
if (node.name) out.push(node.name);
return;
}
const children = Array.isArray(node.children) ? node.children : [];
children.forEach((c) => collect(c, out));
};
const names = [];
categoryTreeSource.value.forEach((n) => collect(n, names));
return names.join(",");
});
watch(
() => form.value.required,
(val) => {
const n = normalize01(val, 0);
if (val !== n) form.value.required = n;
}
);
watch(
() => form.value.isIndex,
(val) => {
const n = normalize01(val, 0);
if (val !== n) form.value.isIndex = n;
}
);
function back() {
router.push({ name: "goods-parameter" });
}
function syncCategoryIds(ids) {
const categoryIds = toStringArray(ids);
selectedCategoryIds.value = categoryIds;
if (!form.value) form.value = {};
form.value.categoryIds = categoryIds;
}
function touchOptionsValidate() {
nextTick(() => {
const formEl = formRef.value;
if (formEl && typeof formEl.validateField === "function") {
// validateField rejects on failure; catch to avoid dev-server [object Object] overlay
formEl.validateField("options", () => {});
}
});
}
function addOptionRow() {
if (!Array.isArray(form.value.options)) form.value.options = [];
form.value.options.push({ value: "" });
}
function removeOptionRow(index) {
if (!Array.isArray(form.value.options)) form.value.options = [];
form.value.options.splice(index, 1);
touchOptionsValidate();
}
function buildCategoryTreeNodes(list) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => ({
id: item.id,
title: item.name,
children: buildCategoryTreeNodes(item.children || []),
}));
}
async function loadDetail(parameterId) {
if (!parameterId) return;
const res = await getGoodsParamsDetail(parameterId).catch(() => null);
if (!(res && res.success && res.result)) return;
const dto = res.result;
form.value = buildFormState(dto, parameterId);
selectedCategoryIds.value = [...form.value.categoryIds];
}
async function openCategoryModal() {
categoryModalVisible.value = true;
categoryTreeLoading.value = true;
categoryTreeKey.value += 1;
try {
if (!Array.isArray(categoryTreeSource.value) || categoryTreeSource.value.length === 0) {
const treeRes = await getCategoryTree();
categoryTreeSource.value = treeRes && treeRes.success ? treeRes.result || [] : [];
const map = {};
buildCategoryIdNameMap(categoryTreeSource.value, map);
categoryIdNameMap.value = map;
}
categoryTreeData.value = buildCategoryTreeNodes(categoryTreeSource.value || []);
} finally {
categoryTreeLoading.value = false;
}
}
function onCategoryTreeCheckChange(_data, checkedInfo) {
syncCategoryIds(checkedInfo?.checkedKeys || []);
nextTick(() => {
const formEl = formRef.value;
if (formEl && typeof formEl.validateField === "function") {
formEl.validateField("categoryIds", () => {});
}
});
}
function initForm() {
if (id.value) {
modalType.value = 1;
const cached = window.sessionStorage.getItem(cacheKey(id.value));
if (cached) {
try {
if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) {
const treeRes = await getCategoryTree();
this.categoryTreeSource = treeRes && treeRes.success ? treeRes.result || [] : [];
const map = {};
buildCategoryIdNameMap(this.categoryTreeSource, map);
this.categoryIdNameMap = map;
}
this.categoryTreeData = this.buildCategoryTreeNodes(this.categoryTreeSource || []);
} finally {
this.categoryTreeLoading = false;
const row = JSON.parse(cached);
form.value = buildFormState(row, row.id);
selectedCategoryIds.value = [...form.value.categoryIds];
} catch (e) {
modalType.value = 0;
}
},
onCategoryTreeCheckChange(_data, checkedInfo) {
this.syncCategoryIds(checkedInfo?.checkedKeys || []);
this.$nextTick(() => {
const form = this.$refs.form;
if (form && typeof form.validateField === "function") {
form.validateField("categoryIds", () => {});
}
});
},
initForm() {
if (this.id) {
this.modalType = 1;
const cached = window.sessionStorage.getItem(cacheKey(this.id));
if (cached) {
try {
const row = JSON.parse(cached);
this.form = buildFormState(row, row.id);
this.selectedCategoryIds = [...this.form.categoryIds];
} catch (e) {
this.modalType = 0;
}
}
this.loadDetail(String(this.id));
} else {
this.modalType = 0;
if (!Array.isArray(this.form.options) || this.form.options.length === 0) {
this.form.options = [{ value: "" }];
}
this.syncCategoryIds([]);
}
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
const options = normalizeOptions(this.form.options);
const categoryIds = toStringArray(this.form.categoryIds);
const payload = {
...this.form,
options: options.join(","),
required: Number(this.form.required),
isIndex: Number(this.form.isIndex),
sort: Number(this.form.sort || 0),
categoryParameterList: categoryIds.map((categoryId) => ({ categoryId })),
};
if (this.modalType === 0) {
delete payload.id;
insertGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
ElMessage.success("操作成功");
this.back();
})
.finally(() => {
this.submitLoading = false;
});
} else {
updateGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
ElMessage.success("操作成功");
this.back();
})
.finally(() => {
this.submitLoading = false;
});
}
});
},
},
mounted() {
getCategoryTree().then((res) => {
if (res && res.success) {
this.categoryTreeSource = res.result || [];
const map = {};
buildCategoryIdNameMap(this.categoryTreeSource, map);
this.categoryIdNameMap = map;
}
});
this.initForm();
},
};
}
loadDetail(String(id.value));
} else {
modalType.value = 0;
if (!Array.isArray(form.value.options) || form.value.options.length === 0) {
form.value.options = [{ value: "" }];
}
syncCategoryIds([]);
}
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (!valid) return;
submitLoading.value = true;
const options = normalizeOptions(form.value.options);
const categoryIds = toStringArray(form.value.categoryIds);
const payload = {
...form.value,
options: options.join(","),
required: Number(form.value.required),
isIndex: Number(form.value.isIndex),
sort: Number(form.value.sort || 0),
categoryParameterList: categoryIds.map((categoryId) => ({ categoryId })),
};
if (modalType.value === 0) {
delete payload.id;
insertGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
ElMessage.success("操作成功");
back();
})
.finally(() => {
submitLoading.value = false;
});
} else {
updateGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
ElMessage.success("操作成功");
back();
})
.finally(() => {
submitLoading.value = false;
});
}
});
}
onMounted(() => {
getCategoryTree().then((res) => {
if (res && res.success) {
categoryTreeSource.value = res.result || [];
const map = {};
buildCategoryIdNameMap(categoryTreeSource.value, map);
categoryIdNameMap.value = map;
}
});
initForm();
});
</script>
<style lang="scss">

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -27,7 +27,7 @@
<div class="operation padding-row">
<el-button type="primary" @click="goAdd">添加</el-button>
</div>
<el-table v-loading="loading" border :data="data" ref="table" style="width: 100%">
<el-table v-loading="loading" border :data="data" ref="tableRef" style="width: 100%">
<el-table-column prop="paramName" label="参数名称" width="300" />
<el-table-column prop="options" label="参数值" min-width="260" show-overflow-tooltip />
<el-table-column label="必填" width="300" align="center">
@@ -65,83 +65,93 @@
</el-card>
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { useRouter } from "vue-router";
import { deleteParams, getGoodsParamsPage } from "@/api/goods";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "categoryParams",
data() {
return {
submitLoading: false,
loading: true,
total: 0,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
paramName: "",
},
data: [],
};
},
methods: {
isTruthyFlag(val) {
return val === 1 || val === "1" || val === true;
},
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;
getGoodsParamsPage(this.searchForm).then((res) => {
this.loading = false;
if (res && res.success) {
this.data = (res.result && res.result.records) || [];
this.total = (res.result && res.result.total) || 0;
}
});
},
goAdd() {
this.$router.push({ name: "goods-parameter-edit" });
},
goEdit(row) {
if (!row || !row.id) return;
try {
window.sessionStorage.setItem(`goods-parameter-edit:${row.id}`, JSON.stringify(row));
} catch (e) {}
this.$router.push({ name: "goods-parameter-edit", query: { id: row.id } });
},
remove(row) {
ElMessageBox.confirm("您确认要删除 " + (row.paramName || "") + " ?", "确认删除", { type: "warning" }).then(() => {
return deleteParams(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
this.getDataList();
}
});
}).catch(() => {});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "categoryParams" });
const router = useRouter();
const submitLoading = ref(false);
const loading = ref(true);
const total = ref(0);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
paramName: "",
});
const data = ref([]);
const searchFormRef = ref(null);
const tableRef = ref(null);
function isTruthyFlag(val) {
return val === 1 || val === "1" || val === true;
}
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;
getGoodsParamsPage(searchForm).then((res) => {
loading.value = false;
if (res && res.success) {
data.value = (res.result && res.result.records) || [];
total.value = (res.result && res.result.total) || 0;
}
});
}
function goAdd() {
router.push({ name: "goods-parameter-edit" });
}
function goEdit(row) {
if (!row || !row.id) return;
try {
window.sessionStorage.setItem(`goods-parameter-edit:${row.id}`, JSON.stringify(row));
} catch (e) {}
router.push({ name: "goods-parameter-edit", query: { id: row.id } });
}
function remove(row) {
ElMessageBox.confirm("您确认要删除 " + (row.paramName || "") + " ?", "确认删除", { type: "warning" }).then(() => {
return deleteParams(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
getDataList();
}
});
}).catch(() => {});
}
onMounted(() => {
init();
});
</script>
<style lang="scss">
.link-text {

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -170,145 +170,144 @@
</div>
</template>
<script>
<script setup>
import { nextTick, onMounted, ref } from "vue";
import * as API_Member from "@/api/member";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "goods-review",
data() {
return {
infoData: {},
infoFlag: false,
infoTitle: "",
currentReviewId: "",
detailStatusReady: false,
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
goodsName: "",
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
},
data: [],
total: 0,
};
},
methods: {
gradeTagType(grade) {
if (grade === "GOOD") return "success";
if (grade === "MODERATE") return "warning";
return "danger";
},
gradeText(grade) {
if (grade === "GOOD") return "好评";
if (grade === "MODERATE") return "中评";
return "差评";
},
changeSwitchView(status) {
if (!this.detailStatusReady || !this.currentReviewId) {
return;
defineOptions({ name: "goods-review" });
// 模板中的 el-form ref(原 ref="searchForm",为避免与数据 searchForm 同名互相覆盖而改名),script 内未使用
const searchFormRef = ref(null);
const infoData = ref({});
const infoFlag = ref(false);
const infoTitle = ref("");
const currentReviewId = ref("");
const detailStatusReady = ref(false);
const loading = ref(true);
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
goodsName: "",
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
});
const data = ref([]);
const total = ref(0);
function gradeTagType(grade) {
if (grade === "GOOD") return "success";
if (grade === "MODERATE") return "warning";
return "danger";
}
function gradeText(grade) {
if (grade === "GOOD") return "好评";
if (grade === "MODERATE") return "中评";
return "差评";
}
function changeSwitchView(status) {
if (!detailStatusReady.value || !currentReviewId.value) {
return;
}
API_Member.updateMemberReview(currentReviewId.value, { status }).then((res) => {
if (res.success) {
ElMessage.success("修改成功!");
getDataList();
}
});
}
function init() {
getDataList();
}
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();
}
function changeSwitch(row) {
if (!row || !row.id) {
return;
}
API_Member.updateMemberReview(row.id, { status: row.status }).then((res) => {
if (res.success) {
getDataList();
}
});
}
function updateTop(v) {
const top = !v.top;
API_Member.updateMemberReviewTop(v.id, { top }).then((res) => {
if (res.success) {
data.value = data.value.map((item) => {
if (item.goodsId !== v.goodsId) {
return item;
}
return {
...item,
top: item.id === v.id ? top : false,
};
});
ElMessage.success(top ? "置顶成功!" : "取消置顶成功!");
init();
}
});
}
function getDataList() {
loading.value = true;
API_Member.getMemberReview(searchForm.value)
.then((res) => {
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
API_Member.updateMemberReview(this.currentReviewId, { status }).then((res) => {
if (res.success) {
ElMessage.success("修改成功!");
this.getDataList();
}
})
.finally(() => {
loading.value = false;
});
}
function info(v) {
currentReviewId.value = v.id;
detailStatusReady.value = false;
infoData.value = {};
infoFlag.value = true;
infoTitle.value = `用户${v.memberName}的评价详情`;
API_Member.getMemberInfoReview(v.id).then((res) => {
if (res.result) {
infoData.value = res.result;
nextTick(() => {
detailStatusReady.value = true;
});
},
init() {
this.getDataList();
},
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();
},
changeSwitch(row) {
if (!row || !row.id) {
return;
}
API_Member.updateMemberReview(row.id, { status: row.status }).then((res) => {
if (res.success) {
this.getDataList();
}
});
},
updateTop(v) {
const top = !v.top;
API_Member.updateMemberReviewTop(v.id, { top }).then((res) => {
if (res.success) {
this.data = this.data.map((item) => {
if (item.goodsId !== v.goodsId) {
return item;
}
return {
...item,
top: item.id === v.id ? top : false,
};
});
ElMessage.success(top ? "置顶成功!" : "取消置顶成功!");
this.init();
}
});
},
getDataList() {
this.loading = true;
API_Member.getMemberReview(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
})
.finally(() => {
this.loading = false;
});
},
info(v) {
this.currentReviewId = v.id;
this.detailStatusReady = false;
this.infoData = {};
this.infoFlag = true;
this.infoTitle = `用户${v.memberName}的评价详情`;
API_Member.getMemberInfoReview(v.id).then((res) => {
if (res.result) {
this.infoData = res.result;
this.$nextTick(() => {
this.detailStatusReady = true;
});
}
});
},
onInfoDialogClosed() {
this.detailStatusReady = false;
this.currentReviewId = "";
this.infoData = {};
},
remove(v) {
ElMessageBox.confirm("您确认要删除客户" + v.memberName + "的评论?", "确认删除", { type: "warning" }).then(() => {
return API_Member.delMemberReview(v.id).then(() => {
ElMessage.success("修改成功");
this.init();
});
}).catch(() => {});
},
},
mounted() {
this.init();
},
};
}
});
}
function onInfoDialogClosed() {
detailStatusReady.value = false;
currentReviewId.value = "";
infoData.value = {};
}
function remove(v) {
ElMessageBox.confirm("您确认要删除客户" + v.memberName + "的评论?", "确认删除", { type: "warning" }).then(() => {
return API_Member.delMemberReview(v.id).then(() => {
ElMessage.success("修改成功");
init();
});
}).catch(() => {});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -36,7 +36,7 @@
</el-card>
<el-dialog v-model="addFlag" title="添加商品分组" width="500px" :close-on-click-modal="false" destroy-on-close>
<el-form ref="addForm" :model="formAdd" :rules="rulesAdd" label-width="90px">
<el-form ref="addFormRef" :model="formAdd" :rules="rulesAdd" label-width="90px">
<el-form-item label="分组名称" prop="groupName" style="width: 90%">
<el-input v-model="formAdd.groupName" maxlength="30" placeholder="请输入分组名称" />
</el-form-item>
@@ -51,7 +51,7 @@
</el-dialog>
<el-dialog v-model="editFlag" title="编辑商品分组" width="500px" :close-on-click-modal="false" destroy-on-close>
<el-form ref="editForm" :model="formEdit" :rules="rulesEdit" label-width="90px">
<el-form ref="editFormRef" :model="formEdit" :rules="rulesEdit" label-width="90px">
<el-input v-model="formEdit.id" style="display: none" />
<el-form-item label="分组名称" prop="groupName" style="width: 90%">
<el-input v-model="formEdit.groupName" maxlength="30" placeholder="请输入分组名称" />
@@ -68,166 +68,173 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, nextTick, onMounted } from "vue";
import * as API_Goods from "@/api/goods.js";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "goodsGroup",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
},
data: [],
total: 0,
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: {
groupName: "",
description: "",
},
formEdit: {
id: "",
groupName: "",
description: "",
},
rulesAdd: {
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
},
rulesEdit: {
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
},
};
},
methods: {
init() {
this.getData();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
getData() {
this.loading = true;
API_Goods.getGoodsGroupByPage(this.searchForm)
.then((res) => {
this.loading = false;
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
this.loading = false;
ElMessage.error("加载商品分组失败");
});
},
openAdd() {
this.addFlag = true;
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = { groupName: "", description: "" };
defineOptions({ name: "goodsGroup" });
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
});
const data = ref([]);
const total = ref(0);
const addFlag = ref(false);
const editFlag = ref(false);
const submitAddLoading = ref(false);
const submitEditLoading = ref(false);
const formAdd = ref({
groupName: "",
description: "",
});
const formEdit = ref({
id: "",
groupName: "",
description: "",
});
const rulesAdd = reactive({
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
});
const rulesEdit = reactive({
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
});
const addFormRef = ref(null);
const editFormRef = ref(null);
function init() {
getData();
}
function changePage(v) {
searchForm.pageNumber = v;
getData();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getData();
}
function getData() {
loading.value = true;
API_Goods.getGoodsGroupByPage(searchForm)
.then((res) => {
loading.value = false;
if (res && res.success && res.result) {
data.value = res.result.records || [];
total.value = res.result.total || 0;
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
loading.value = false;
ElMessage.error("加载商品分组失败");
});
}
function openAdd() {
addFlag.value = true;
nextTick(() => {
if (addFormRef.value) addFormRef.value.resetFields();
formAdd.value = { groupName: "", description: "" };
});
}
function submitAdd() {
addFormRef.value.validate((valid) => {
if (!valid) return;
submitAddLoading.value = true;
API_Goods.addGoodsGroup(formAdd.value)
.then((res) => {
submitAddLoading.value = false;
if (res && res.success) {
ElMessage.success("添加成功");
addFlag.value = false;
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
submitAddLoading.value = false;
ElMessage.error("添加失败,请检查权限或后端接口");
});
},
submitAdd() {
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
API_Goods.addGoodsGroup(this.formAdd)
.then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
ElMessage.success("添加成功");
this.addFlag = false;
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
this.submitAddLoading = false;
ElMessage.error("添加失败,请检查权限或后端接口");
});
});
}
function openEdit(row) {
editFlag.value = true;
submitEditLoading.value = false;
API_Goods.getGoodsGroup(row.id)
.then((res) => {
if (res && res.success && res.result) {
formEdit.value = {
id: res.result.id,
groupName: res.result.groupName || "",
description: res.result.description || "",
};
} else {
formEdit.value = {
id: row.id,
groupName: row.groupName || "",
description: row.description || "",
};
}
})
.catch(() => {
ElMessage.error("获取分组详情失败");
});
}
function submitEdit() {
editFormRef.value.validate((valid) => {
if (!valid) return;
submitEditLoading.value = true;
const { id, groupName, description } = formEdit.value;
API_Goods.updateGoodsGroup(id, { id, groupName, description })
.then((res) => {
submitEditLoading.value = false;
if (res && res.success) {
ElMessage.success("修改成功");
editFlag.value = false;
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
submitEditLoading.value = false;
ElMessage.error("修改失败,请检查权限或后端接口");
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
API_Goods.getGoodsGroup(row.id)
.then((res) => {
if (res && res.success && res.result) {
this.formEdit = {
id: res.result.id,
groupName: res.result.groupName || "",
description: res.result.description || "",
};
} else {
this.formEdit = {
id: row.id,
groupName: row.groupName || "",
description: row.description || "",
};
}
})
.catch(() => {
ElMessage.error("获取分组详情失败");
});
},
submitEdit() {
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, groupName, description } = this.formEdit;
API_Goods.updateGoodsGroup(id, { id, groupName, description })
.then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
ElMessage.success("修改成功");
this.editFlag = false;
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
this.submitEditLoading = false;
ElMessage.error("修改失败,请检查权限或后端接口");
});
});
}
function remove(row) {
ElMessageBox.confirm("确定删除该分组?", "提示", { type: "warning" }).then(() => {
return API_Goods.deleteGoodsGroup(row.id)
.then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
ElMessage.error("删除失败,请检查权限或后端接口");
});
},
remove(row) {
ElMessageBox.confirm("确定删除该分组?", "提示", { type: "warning" }).then(() => {
return API_Goods.deleteGoodsGroup(row.id)
.then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
})
.catch(() => {
ElMessage.error("删除失败,请检查权限或后端接口");
});
}).catch(() => {});
},
},
mounted() {
this.init();
},
};
}).catch(() => {});
}
onMounted(() => {
init();
});
</script>
<style scoped>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -68,7 +68,7 @@
</div>
<el-table
ref="table"
ref="tableRef"
v-loading="loading"
:data="data"
class="mt_10"
@@ -208,7 +208,8 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import {
batchUpdateGoodsSkuVirtualSales,
getGoodsSkuData,
@@ -225,223 +226,222 @@ const VIRTUAL_SALES_FIELDS = [
"salesVolume",
];
export default {
name: "goodsVirtualSales",
data() {
return {
loading: true,
editModalVisible: false,
modalSubmitting: false,
modalMode: "single",
currentSku: null,
currentIndex: -1,
selectedRows: [],
editForm: {
virtualSales: 0,
},
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
goodsName: "",
goodsId: "",
sn: "",
storeName: "",
},
data: [],
total: 0,
};
},
methods: {
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.getDataList();
},
handleReset() {
this.searchForm = {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
goodsName: "",
goodsId: "",
sn: "",
storeName: "",
};
this.getDataList();
},
changeSort({ prop, order }) {
if (!order) {
this.searchForm.sort = "create_time";
this.searchForm.order = "desc";
} else {
this.searchForm.sort = prop;
this.searchForm.order = order === "ascending" ? "asc" : "desc";
}
this.getDataList();
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
getDataList() {
this.loading = true;
getGoodsSkuData(this.searchForm)
.then((res) => {
if (res && res.success && res.result) {
this.data = (res.result.records || []).map((item) => ({
...item,
virtualSalesInput: this.getVirtualSalesValue(item),
saving: false,
}));
this.total = res.result.total || 0;
this.selectedRows = [];
} else {
this.data = [];
this.total = 0;
this.selectedRows = [];
ElMessage.error((res && res.message) || "加载规格列表失败");
}
})
.catch(() => {
this.data = [];
this.total = 0;
this.selectedRows = [];
ElMessage.error("加载规格列表失败");
})
.finally(() => {
this.loading = false;
});
},
getVirtualSalesValue(row) {
const field = VIRTUAL_SALES_FIELDS.find(
(item) => row[item] !== undefined && row[item] !== null && row[item] !== ""
);
return field ? Number(row[field]) || 0 : 0;
},
openVirtualSalesModal(row, index) {
this.modalMode = "single";
this.currentSku = { ...row };
this.currentIndex = index;
this.editForm.virtualSales = this.getVirtualSalesValue(row);
this.editModalVisible = true;
},
openBatchVirtualSalesModal() {
if (!this.selectedRows.length) {
ElMessage.warning("请先选择要设置的商品规格");
return;
}
this.modalMode = "batch";
this.currentSku = null;
this.currentIndex = -1;
this.editForm.virtualSales = 0;
this.editModalVisible = true;
},
handleCancelVirtualSales() {
if (this.modalSubmitting) return;
this.resetVirtualSalesModal();
},
resetVirtualSalesModal() {
this.editModalVisible = false;
this.modalMode = "single";
this.currentSku = null;
this.currentIndex = -1;
this.editForm.virtualSales = 0;
},
handleSubmitVirtualSales() {
const virtualSales = Number(this.editForm.virtualSales);
if (!Number.isInteger(virtualSales) || virtualSales < 0) {
ElMessage.warning("请输入大于等于 0 的整数虚拟销量");
return;
}
if (this.modalMode === "batch") {
this.handleBatchSubmitVirtualSales(virtualSales);
return;
}
if (!this.currentSku || this.currentIndex < 0) return;
defineOptions({ name: "goodsVirtualSales" });
const currentIndex = this.currentIndex;
const currentSkuId = this.currentSku.id;
this.modalSubmitting = true;
if (this.data[currentIndex]) {
this.data[currentIndex].saving = true;
// 模板 ref(避免与同名数据字段冲突而改名)
const searchFormRef = ref(null);
const tableRef = ref(null);
const loading = ref(true);
const editModalVisible = ref(false);
const modalSubmitting = ref(false);
const modalMode = ref("single");
const currentSku = ref(null);
const currentIndex = ref(-1);
const selectedRows = ref([]);
const editForm = reactive({
virtualSales: 0,
});
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
goodsName: "",
goodsId: "",
sn: "",
storeName: "",
});
const data = ref([]);
const total = ref(0);
function init() {
getDataList();
}
function changePage(v) {
searchForm.value.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.value.pageNumber = 1;
getDataList();
}
function handleReset() {
searchForm.value = {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
goodsName: "",
goodsId: "",
sn: "",
storeName: "",
};
getDataList();
}
function changeSort({ prop, order }) {
if (!order) {
searchForm.value.sort = "create_time";
searchForm.value.order = "desc";
} else {
searchForm.value.sort = prop;
searchForm.value.order = order === "ascending" ? "asc" : "desc";
}
getDataList();
}
function handleSelectionChange(selection) {
selectedRows.value = selection;
}
function getDataList() {
loading.value = true;
getGoodsSkuData(searchForm.value)
.then((res) => {
if (res && res.success && res.result) {
data.value = (res.result.records || []).map((item) => ({
...item,
virtualSalesInput: getVirtualSalesValue(item),
saving: false,
}));
total.value = res.result.total || 0;
selectedRows.value = [];
} else {
data.value = [];
total.value = 0;
selectedRows.value = [];
ElMessage.error((res && res.message) || "加载规格列表失败");
}
updateGoodsSkuVirtualSales(currentSkuId, { virtualSales })
.then((res) => {
if (res && res.success) {
if (this.data[currentIndex]) {
this.data[currentIndex].virtualSales = virtualSales;
this.data[currentIndex].virtualSalesInput = virtualSales;
}
ElMessage.success("虚拟销量设置成功");
this.resetVirtualSalesModal();
} else {
ElMessage.error((res && res.message) || "规格虚拟销量设置失败");
}
})
.catch(() => {
ElMessage.error("规格虚拟销量设置失败");
})
.finally(() => {
if (this.data[currentIndex]) {
this.data[currentIndex].saving = false;
}
this.modalSubmitting = false;
});
},
handleBatchSubmitVirtualSales(virtualSales) {
const skuIds = this.selectedRows.map((item) => item.id).filter(Boolean);
if (!skuIds.length) {
ElMessage.warning("请先选择要设置的商品规格");
return;
})
.catch(() => {
data.value = [];
total.value = 0;
selectedRows.value = [];
ElMessage.error("加载规格列表失败");
})
.finally(() => {
loading.value = false;
});
}
function getVirtualSalesValue(row) {
const field = VIRTUAL_SALES_FIELDS.find(
(item) => row[item] !== undefined && row[item] !== null && row[item] !== ""
);
return field ? Number(row[field]) || 0 : 0;
}
function openVirtualSalesModal(row, index) {
modalMode.value = "single";
currentSku.value = { ...row };
currentIndex.value = index;
editForm.virtualSales = getVirtualSalesValue(row);
editModalVisible.value = true;
}
function openBatchVirtualSalesModal() {
if (!selectedRows.value.length) {
ElMessage.warning("请先选择要设置的商品规格");
return;
}
modalMode.value = "batch";
currentSku.value = null;
currentIndex.value = -1;
editForm.virtualSales = 0;
editModalVisible.value = true;
}
function handleCancelVirtualSales() {
if (modalSubmitting.value) return;
resetVirtualSalesModal();
}
function resetVirtualSalesModal() {
editModalVisible.value = false;
modalMode.value = "single";
currentSku.value = null;
currentIndex.value = -1;
editForm.virtualSales = 0;
}
function handleSubmitVirtualSales() {
const virtualSales = Number(editForm.virtualSales);
if (!Number.isInteger(virtualSales) || virtualSales < 0) {
ElMessage.warning("请输入大于等于 0 的整数虚拟销量");
return;
}
if (modalMode.value === "batch") {
handleBatchSubmitVirtualSales(virtualSales);
return;
}
if (!currentSku.value || currentIndex.value < 0) return;
const curIndex = currentIndex.value;
const currentSkuId = currentSku.value.id;
modalSubmitting.value = true;
if (data.value[curIndex]) {
data.value[curIndex].saving = true;
}
updateGoodsSkuVirtualSales(currentSkuId, { virtualSales })
.then((res) => {
if (res && res.success) {
if (data.value[curIndex]) {
data.value[curIndex].virtualSales = virtualSales;
data.value[curIndex].virtualSalesInput = virtualSales;
}
ElMessage.success("虚拟销量设置成功");
resetVirtualSalesModal();
} else {
ElMessage.error((res && res.message) || "规格虚拟销量设置失败");
}
this.modalSubmitting = true;
batchUpdateGoodsSkuVirtualSales({ skuIds, virtualSales })
.then((res) => {
if (res && res.success) {
this.data = this.data.map((item) => {
if (!skuIds.includes(item.id)) return item;
return {
...item,
virtualSales,
virtualSalesInput: virtualSales,
};
});
this.selectedRows = [];
this.$refs.table?.clearSelection();
ElMessage.success("虚拟销量设置成功");
this.resetVirtualSalesModal();
} else {
ElMessage.error((res && res.message) || "虚拟销量设置失败");
}
})
.catch(() => {
ElMessage.error("虚拟销量设置失败");
})
.finally(() => {
this.modalSubmitting = false;
})
.catch(() => {
ElMessage.error("规格虚拟销量设置失败");
})
.finally(() => {
if (data.value[curIndex]) {
data.value[curIndex].saving = false;
}
modalSubmitting.value = false;
});
}
function handleBatchSubmitVirtualSales(virtualSales) {
const skuIds = selectedRows.value.map((item) => item.id).filter(Boolean);
if (!skuIds.length) {
ElMessage.warning("请先选择要设置的商品规格");
return;
}
modalSubmitting.value = true;
batchUpdateGoodsSkuVirtualSales({ skuIds, virtualSales })
.then((res) => {
if (res && res.success) {
data.value = data.value.map((item) => {
if (!skuIds.includes(item.id)) return item;
return {
...item,
virtualSales,
virtualSalesInput: virtualSales,
};
});
},
getTotalSales(row) {
return (Number(row.buyCount) || 0) + (Number(row.virtualSalesInput) || 0);
},
},
mounted() {
this.init();
},
};
selectedRows.value = [];
tableRef.value?.clearSelection();
ElMessage.success("虚拟销量设置成功");
resetVirtualSalesModal();
} else {
ElMessage.error((res && res.message) || "虚拟销量设置失败");
}
})
.catch(() => {
ElMessage.error("虚拟销量设置失败");
})
.finally(() => {
modalSubmitting.value = false;
});
}
function getTotalSales(row) {
return (Number(row.buyCount) || 0) + (Number(row.virtualSalesInput) || 0);
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -48,37 +48,37 @@
<h4>今日待办</h4>
<div class="todo-list flex">
<div class="todo-item" @click="navigateTo('applyGoods')">
<div class="counts">{{ $store.state.notices.goods || 0 }}</div>
<div class="counts">{{ notices.goods || 0 }}</div>
<div>待审核商品</div>
</div>
<div class="todo-item" @click="navigateTo('shopAuth')">
<div class="counts">{{ $store.state.notices.store || 0 }}</div>
<div class="counts">{{ notices.store || 0 }}</div>
<div>待审核店铺</div>
</div>
<div class="todo-item" @click="navigateTo('orderComplaint')">
<div class="counts">{{ $store.state.notices.complain || 0 }}</div>
<div class="counts">{{ notices.complain || 0 }}</div>
<div>待审核投诉</div>
</div>
<div class="todo-item" @click="navigateTo('afterSaleOrder')">
<div class="counts">{{ $store.state.notices.refund || 0 }}</div>
<div class="counts">{{ notices.refund || 0 }}</div>
<div>待审核售后</div>
</div>
<div class="todo-item" @click="goDistributionCash">
<div class="counts">
{{ $store.state.notices.distributionCash || 0 }}
{{ notices.distributionCash || 0 }}
</div>
<div>待审核分销提现</div>
</div>
<div class="todo-item" @click="navigateTo('accountStatementBill')">
<div class="counts">{{ $store.state.notices.waitPayBill || 0 }}</div>
<div class="counts">{{ notices.waitPayBill || 0 }}</div>
<div>待审核分账</div>
</div>
<div class="todo-item" @click="goAppointmentWorkOrders">
<div class="counts">{{ $store.state.notices.appointmentWorkOrder || 0 }}</div>
<div class="counts">{{ notices.appointmentWorkOrder || 0 }}</div>
<div>未完成预约工单</div>
</div>
<div class="todo-item" @click="goProcurementAudit">
<div class="counts">{{ $store.state.notices.procurementAudit || 0 }}</div>
<div class="counts">{{ notices.procurementAudit || 0 }}</div>
<div>待审核采购单</div>
</div>
</div>
@@ -237,353 +237,372 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { Picture, User, List, DataLine } from "@element-plus/icons-vue";
import { homeStatistics, hotGoods, hotShops, getNoticePage } from "@/api/index";
import * as API_Goods from "@/api/goods";
import { Chart } from "@antv/g2";
import * as API_Member from "@/api/member";
export default {
name: "home",
components: { Picture, User, List, DataLine },
import { useAppStore } from "@/stores/app";
data() {
return {
topHotGoodsData: [], //热卖商品集合
topHotShopsData: [], //热卖店铺集合
awaitTodoData: "", //今日待办集合
homeData: "", // 首页数据
pvChart: "", // 流量统计
orderChart: "", // 订单统计
historyMemberChart: "", // 最近会员流量统计
rangeKey: "LAST_SEVEN",
rangeMonth: "",
rangeOptions: [
{ title: "今天", searchType: "TODAY" },
{ title: "昨天", searchType: "YESTERDAY" },
{ title: "过去7天", searchType: "LAST_SEVEN" },
{ title: "过去30天", searchType: "LAST_THIRTY" },
],
};
},
computed: {
recentMonths() {
const list = [];
const now = new Date();
for (let i = 0; i < 12; i += 1) {
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
const year = date.getFullYear();
const month = date.getMonth() + 1;
list.push({
value: `${year}-${month}`,
label: `${year}年${month}月`,
defineOptions({ name: "home" });
const router = useRouter();
const appStore = useAppStore();
const topHotGoodsData = ref([]); //热卖商品集合
const topHotShopsData = ref([]); //热卖店铺集合
const awaitTodoData = ref(""); //今日待办集合
const homeData = ref(""); // 首页数据
const pvChart = ref(""); // 流量统计
const orderChart = ref(""); // 订单统计
const historyMemberChart = ref(""); // 最近会员流量统计
const rangeKey = ref("LAST_SEVEN");
const rangeMonth = ref("");
const rangeOptions = ref([
{ title: "今天", searchType: "TODAY" },
{ title: "昨天", searchType: "YESTERDAY" },
{ title: "过去7天", searchType: "LAST_SEVEN" },
{ title: "过去30天", searchType: "LAST_THIRTY" },
]);
// 原代码未在 data 声明、直接挂 this 的字段,补声明为普通变量
let orderChartData = []; // 订单图表数据
let data = []; // 浏览量图表数据
let chartList = []; // 历史在线人数图表数据
const notices = computed(() => appStore.notices || {});
const recentMonths = computed(() => {
const list = [];
const now = new Date();
for (let i = 0; i < 12; i += 1) {
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
const year = date.getFullYear();
const month = date.getMonth() + 1;
list.push({
value: `${year}-${month}`,
label: `${year}年${month}月`,
});
}
return list;
});
const rangeTitle = computed(() => {
if (rangeMonth.value) {
const found = recentMonths.value.find((item) => item.value === rangeMonth.value);
return found ? found.label : rangeMonth.value;
}
const found = rangeOptions.value.find((item) => item.searchType === rangeKey.value);
return found ? found.title : "过去7天";
});
// 路由跳转
function navigateTo(name) {
router.push({
name,
});
}
// 跳转到分销提现,并打开待处理标签页
function goDistributionCash() {
router.push({
name: 'distributionCash',
query: { tabStatus: 'APPLY' }
});
}
function goAppointmentWorkOrders() {
router.push({
name: "manager-appointment-work-orders",
query: { serviceStatus: "OPEN" },
});
}
function goProcurementAudit() {
router.push({
name: "manager-procurement-order-list",
query: { status: "SUBMITTED" },
});
}
function rangeQuery() {
if (rangeMonth.value) {
const [year, month] = rangeMonth.value.split("-");
return { year, month };
}
return { searchType: rangeKey.value || "LAST_SEVEN" };
}
function changeRange(item) {
rangeMonth.value = "";
rangeKey.value = item.searchType;
reloadRange();
}
function changeMonth(value) {
rangeMonth.value = value || "";
if (!rangeMonth.value) {
rangeKey.value = "LAST_SEVEN";
}
reloadRange();
}
function reloadRange() {
toHotGoods();
topHotShops();
getPvChart();
initOrderChartList();
}
// top10热卖商品
async function toHotGoods() {
let res = await hotGoods(rangeQuery());
topHotGoodsData.value = res.success ? res.result || [] : [];
}
// top10热卖店铺
async function topHotShops() {
let res = await hotShops(rangeQuery());
topHotShopsData.value = res.success ? res.result || [] : [];
}
// 今日待办
async function awaitTodo() {
let res = await getNoticePage();
res.success ? (awaitTodoData.value = res.result) : "";
}
//首页统计数据
async function getHomeData() {
let res = await homeStatistics();
if (res.success) {
const price = res.result.todayOrderPrice;
const amount = price === null || price === undefined || price === "" || price === "null"
? 0
: Number(price);
res.result.todayOrderPrice = Number.isNaN(amount) ? 0 : amount;
homeData.value = res.result;
}
}
// 实例化订单图表
async function initOrderChartList() {
orderChart.value ? orderChart.value.clear() : ''
const res = await API_Goods.getOrderChart(rangeQuery());
if (res.success) {
orderChartData = res.result || [];
if (!orderChart.value) {
orderChart.value = new Chart({
container: "orderChart",
autoFit: true,
height: 500,
padding: [70, 70, 70, 70],
});
}
initOrderChart(); //订单表
}
}
// 订单表
function initOrderChart() {
let chartData = orderChartData || [];
chartData.forEach((item) => {
const time = item.createTime ? String(item.createTime) : "";
item.createTime = time.includes(" ") ? time.split(" ")[0] : time;
item.title = "交易额";
});
orderChart.value.data(chartData);
orderChart.value.tooltip({
showCrosshairs: true,
shared: true,
});
orderChart.value
.line()
.position("createTime*price")
.label("price")
.color("title")
.shape("smooth");
orderChart.value
.point()
.position("createTime*price")
.label("price")
.color("title")
.shape("circle")
.style({
stroke: "#fff",
lineWidth: 1,
});
orderChart.value.area().position("createTime*price").color("title").shape("smooth");
orderChart.value.render();
}
// 浏览量统计图
function initPvChart() {
let uv = [];
let pv = [];
const rows = Array.isArray(data) ? data : [];
rows.forEach((item) => {
uv.push({
date: item.date,
uvNum: item.uvNum,
title: "访客数UV",
pv: item.uvNum,
});
pv.push({
date: item.date,
pvNum: item.pvNum,
pv: item.pvNum,
title: "浏览量PV",
});
});
let chartData = [...uv, ...pv];
pvChart.value.data(chartData);
pvChart.value.tooltip({
showCrosshairs: true,
shared: true,
});
pvChart.value
.line()
.position("date*pv")
.color("title")
.label("pv")
.shape("smooth");
pvChart.value
.point()
.position("date*pv")
.color("title")
.label("pv")
.shape("circle")
.style({
stroke: "#fff",
lineWidth: 1,
});
pvChart.value.area().position("date*pv").color("title").shape("smooth");
pvChart.value.render();
}
// 浏览量
async function getPvChart() {
pvChart.value ? pvChart.value.clear() : ''
API_Member.getStatisticsList(rangeQuery()).then((res) => {
if (res.result) {
data = res.result || [];
if (!pvChart.value) {
pvChart.value = new Chart({
container: "pvChart",
autoFit: true,
height: 500,
padding: [70, 70, 70, 70],
});
}
return list;
},
rangeTitle() {
if (this.rangeMonth) {
const found = this.recentMonths.find((item) => item.value === this.rangeMonth);
return found ? found.label : this.rangeMonth;
}
const found = this.rangeOptions.find((item) => item.searchType === this.rangeKey);
return found ? found.title : "过去7天";
},
},
methods: {
// 路由跳转
navigateTo(name) {
this.$router.push({
name,
initPvChart();
}
});
}
// 实例化会员流量图表
async function initHistoryMemberChartList() {
historyMemberChart.value ? historyMemberChart.value.clear() : ''
const res = await API_Member.historyMemberChartList();
if (res.success) {
chartList = res.result;
if (!historyMemberChart.value) {
historyMemberChart.value = new Chart({
container: "historyMemberChart",
autoFit: true,
height: 500,
padding: [70, 70, 70, 70],
});
},
// 跳转到分销提现,并打开待处理标签页
goDistributionCash() {
this.$router.push({
name: 'distributionCash',
query: { tabStatus: 'APPLY' }
});
},
goAppointmentWorkOrders() {
this.$router.push({
name: "manager-appointment-work-orders",
query: { serviceStatus: "OPEN" },
});
},
goProcurementAudit() {
this.$router.push({
name: "manager-procurement-order-list",
query: { status: "SUBMITTED" },
});
},
rangeQuery() {
if (this.rangeMonth) {
const [year, month] = this.rangeMonth.split("-");
return { year, month };
}
return { searchType: this.rangeKey || "LAST_SEVEN" };
},
changeRange(item) {
this.rangeMonth = "";
this.rangeKey = item.searchType;
this.reloadRange();
},
changeMonth(value) {
this.rangeMonth = value || "";
if (!this.rangeMonth) {
this.rangeKey = "LAST_SEVEN";
}
this.reloadRange();
},
reloadRange() {
this.toHotGoods();
this.topHotShops();
this.getPvChart();
this.initOrderChartList();
},
// top10热卖商品
async toHotGoods() {
let res = await hotGoods(this.rangeQuery());
this.topHotGoodsData = res.success ? res.result || [] : [];
},
}
// top10热卖店铺
async topHotShops() {
let res = await hotShops(this.rangeQuery());
this.topHotShopsData = res.success ? res.result || [] : [];
},
// 今日待办
async awaitTodo() {
let res = await getNoticePage();
res.success ? (this.awaitTodoData = res.result) : "";
},
initHistoryMemberChart();
}
}
//首页统计数据
async getHomeData() {
let res = await homeStatistics();
if (res.success) {
const price = res.result.todayOrderPrice;
const amount = price === null || price === undefined || price === "" || price === "null"
? 0
: Number(price);
res.result.todayOrderPrice = Number.isNaN(amount) ? 0 : amount;
this.homeData = res.result;
}
},
// 历史在线人数
function initHistoryMemberChart() {
// 默认已经加载 legend-filter 交互
let chartData = chartList;
let num = [];
let lastNum = [];
chartData.forEach((item) => {
num.push({
date: item.date.substring(5),
title: "最近48小时",
num: item.num,
res: item.num,
});
// 实例化订单图表
async initOrderChartList() {
this.orderChart ? this.orderChart.clear() : ''
const res = await API_Goods.getOrderChart(this.rangeQuery());
if (res.success) {
this.orderChartData = res.result || [];
lastNum.push({
date: item.date.substring(5),
title: "上一周期",
lastNum: item.lastNum || 0,
res: item.lastNum || 0,
});
});
let params = [...num, ...lastNum];
historyMemberChart.value.data(params);
if (!this.orderChart) {
this.orderChart = new Chart({
container: "orderChart",
autoFit: true,
height: 500,
padding: [70, 70, 70, 70],
});
}
historyMemberChart.value.tooltip({
showCrosshairs: true,
shared: true,
});
this.initOrderChart(); //订单表
}
},
historyMemberChart.value
.line()
.position("date*res")
.color("title")
.label("res")
.shape("smooth");
historyMemberChart.value
.point()
.position("date*res")
.color("title")
.label("res")
.shape("circle");
// 订单表
initOrderChart() {
let data = this.orderChartData || [];
historyMemberChart.value
.area()
.position("date*res")
.color("title")
.shape("smooth");
data.forEach((item) => {
const time = item.createTime ? String(item.createTime) : "";
item.createTime = time.includes(" ") ? time.split(" ")[0] : time;
item.title = "交易额";
});
this.orderChart.data(data);
historyMemberChart.value.render();
}
this.orderChart.tooltip({
showCrosshairs: true,
shared: true,
});
// 初始化信息
function init() {
toHotGoods();
topHotShops();
awaitTodo();
getHomeData();
getPvChart();
initOrderChartList();
initHistoryMemberChartList();
}
this.orderChart
.line()
.position("createTime*price")
.label("price")
.color("title")
.shape("smooth");
this.orderChart
.point()
.position("createTime*price")
.label("price")
.color("title")
.shape("circle")
.style({
stroke: "#fff",
lineWidth: 1,
});
this.orderChart.area().position("createTime*price").color("title").shape("smooth");
this.orderChart.render();
},
// 浏览量统计图
initPvChart() {
let uv = [];
let pv = [];
const rows = Array.isArray(this.data) ? this.data : [];
rows.forEach((item) => {
uv.push({
date: item.date,
uvNum: item.uvNum,
title: "访客数UV",
pv: item.uvNum,
});
pv.push({
date: item.date,
pvNum: item.pvNum,
pv: item.pvNum,
title: "浏览量PV",
});
});
let data = [...uv, ...pv];
this.pvChart.data(data);
this.pvChart.tooltip({
showCrosshairs: true,
shared: true,
});
this.pvChart
.line()
.position("date*pv")
.color("title")
.label("pv")
.shape("smooth");
this.pvChart
.point()
.position("date*pv")
.color("title")
.label("pv")
.shape("circle")
.style({
stroke: "#fff",
lineWidth: 1,
});
this.pvChart.area().position("date*pv").color("title").shape("smooth");
this.pvChart.render();
},
// 浏览量
async getPvChart() {
this.pvChart ? this.pvChart.clear() : ''
API_Member.getStatisticsList(this.rangeQuery()).then((res) => {
if (res.result) {
this.data = res.result || [];
if (!this.pvChart) {
this.pvChart = new Chart({
container: "pvChart",
autoFit: true,
height: 500,
padding: [70, 70, 70, 70],
});
}
this.initPvChart();
}
});
},
// 实例化会员流量图表
async initHistoryMemberChartList() {
this.historyMemberChart ? this.historyMemberChart.clear() : ''
const res = await API_Member.historyMemberChartList();
if (res.success) {
this.chartList = res.result;
if (!this.historyMemberChart) {
this.historyMemberChart = new Chart({
container: "historyMemberChart",
autoFit: true,
height: 500,
padding: [70, 70, 70, 70],
});
}
this.initHistoryMemberChart();
}
},
// 历史在线人数
initHistoryMemberChart() {
// 默认已经加载 legend-filter 交互
let data = this.chartList;
let num = [];
let lastNum = [];
data.forEach((item) => {
num.push({
date: item.date.substring(5),
title: "最近48小时",
num: item.num,
res: item.num,
});
lastNum.push({
date: item.date.substring(5),
title: "上一周期",
lastNum: item.lastNum || 0,
res: item.lastNum || 0,
});
});
let params = [...num, ...lastNum];
this.historyMemberChart.data(params);
this.historyMemberChart.tooltip({
showCrosshairs: true,
shared: true,
});
this.historyMemberChart
.line()
.position("date*res")
.color("title")
.label("res")
.shape("smooth");
this.historyMemberChart
.point()
.position("date*res")
.color("title")
.label("res")
.shape("circle");
this.historyMemberChart
.area()
.position("date*res")
.color("title")
.shape("smooth");
this.historyMemberChart.render();
},
// 初始化信息
init() {
this.toHotGoods();
this.topHotShops();
this.awaitTodo();
this.getHomeData();
this.getPvChart();
this.initOrderChartList();
this.initHistoryMemberChartList();
},
},
mounted() {
this.init();
},
};
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
@import "./home.scss";

View File

@@ -4,7 +4,7 @@
<template #header>
<span class="page-title">创建直播</span>
</template>
<el-form ref="form" :model="form" :rules="rules" label-width="100px" class="live-form">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="live-form">
<el-form-item label="直播标题" prop="title">
<el-input
v-model="form.title"
@@ -68,7 +68,7 @@
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
ref="ossManage"
ref="ossManageRef"
:is-component="true"
:initialize="picModelFlag"
@callback="callbackSelected"
@@ -77,11 +77,16 @@
</div>
</template>
<script>
<script setup>
import { nextTick, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Picture } from "@element-plus/icons-vue";
import { createLive } from "@/api/live";
import { Message } from "@/utils/message";
import ossManage from "@/views/sys/oss-manage/ossManage";
defineOptions({ name: "live-add" });
function getCharLength(str) {
if (!str) return 0;
let len = 0;
@@ -98,88 +103,85 @@ function buildEndTime(startTime, hours = 24) {
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
export default {
name: "live-add",
components: { Picture, ossManage },
data() {
const validateTitle = (rule, value, callback) => {
const len = getCharLength(value);
if (len < 6) {
callback(new Error("直播间名字最短3个汉字"));
return;
}
if (len > 34) {
callback(new Error("直播间名字最长17个汉字"));
return;
}
callback();
};
return {
picModelFlag: false,
submitting: false,
form: {
title: "",
displayMode: "0",
startTime: "",
coverImg: "",
liveIntroduce: "",
},
rules: {
title: [
{ required: true, message: "请输入直播标题", trigger: "blur" },
{ validator: validateTitle, trigger: "blur" },
],
displayMode: [{ required: true, message: "请选择显示模式", trigger: "change" }],
startTime: [{ required: true, message: "请选择直播开始时间", trigger: "change" }],
coverImg: [{ required: true, message: "请上传直播封面", trigger: "change" }],
},
};
},
methods: {
openCoverPicker() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
callbackSelected(val) {
this.picModelFlag = false;
if (val?.url) {
this.form.coverImg = val.url;
this.$refs.form?.validateField("coverImg");
}
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitting = true;
createLive({
title: this.form.title,
displayMode: this.form.displayMode,
coverImg: this.form.coverImg,
startTime: this.form.startTime,
endTime: buildEndTime(this.form.startTime),
liveIntroduce: this.form.liveIntroduce,
})
.then((res) => {
this.submitting = false;
if (res.success) {
this.$Message.success("创建成功");
this.$router.push({ path: "/live-list" });
}
})
.catch(() => {
this.submitting = false;
});
});
},
handleBack() {
this.$router.push({ path: "/live-list" });
},
},
const router = useRouter();
const formRef = ref(null);
const ossManageRef = ref(null);
const picModelFlag = ref(false);
const submitting = ref(false);
const form = reactive({
title: "",
displayMode: "0",
startTime: "",
coverImg: "",
liveIntroduce: "",
});
const validateTitle = (rule, value, callback) => {
const len = getCharLength(value);
if (len < 6) {
callback(new Error("直播间名字最短3个汉字"));
return;
}
if (len > 34) {
callback(new Error("直播间名字最长17个汉字"));
return;
}
callback();
};
const rules = {
title: [
{ required: true, message: "请输入直播标题", trigger: "blur" },
{ validator: validateTitle, trigger: "blur" },
],
displayMode: [{ required: true, message: "请选择显示模式", trigger: "change" }],
startTime: [{ required: true, message: "请选择直播开始时间", trigger: "change" }],
coverImg: [{ required: true, message: "请上传直播封面", trigger: "change" }],
};
function openCoverPicker() {
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function callbackSelected(val) {
picModelFlag.value = false;
if (val?.url) {
form.coverImg = val.url;
formRef.value?.validateField("coverImg");
}
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (!valid) return;
submitting.value = true;
createLive({
title: form.title,
displayMode: form.displayMode,
coverImg: form.coverImg,
startTime: form.startTime,
endTime: buildEndTime(form.startTime),
liveIntroduce: form.liveIntroduce,
})
.then((res) => {
submitting.value = false;
if (res.success) {
Message.success("创建成功");
router.push({ path: "/live-list" });
}
})
.catch(() => {
submitting.value = false;
});
});
}
function handleBack() {
router.push({ path: "/live-list" });
}
</script>
<style lang="scss" scoped>

View File

@@ -193,7 +193,8 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, watch, onMounted } from "vue";
import { Refresh } from "@element-plus/icons-vue";
import {
authLiveMessage,
@@ -204,243 +205,261 @@ import {
queryLiveMessagePage,
unblockUser,
} from "@/api/live";
import { Message } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
export default {
name: "CommentReview",
components: { Refresh },
props: {
liveId: {
type: String,
required: true,
},
embedded: {
type: Boolean,
default: false,
},
defineOptions({ name: "CommentReview" });
const props = defineProps({
liveId: {
type: String,
required: true,
},
data() {
return {
activeTab: "message",
searchKeyword: "",
messageLoading: false,
userLoading: false,
blockLoading: false,
messageList: [],
userList: [],
blockList: [],
messagePagination: {
pageNumber: 1,
pageSize: 10,
total: 0,
},
userPagination: {
pageNumber: 1,
pageSize: 10,
total: 0,
},
blockPagination: {
pageNumber: 1,
pageSize: 10,
total: 0,
},
blockDialogVisible: false,
blockTarget: null,
blockReason: "",
blockSubmitting: false,
};
embedded: {
type: Boolean,
default: false,
},
watch: {
liveId() {
this.loadMessages();
},
},
mounted() {
this.loadMessages();
},
methods: {
formatTime(time) {
if (!time) return "-";
return this.$filters.unixToDate(time);
},
authStatusText(status) {
const map = { PASS: "已通过", REFUSE: "已拒绝", TOBEAUDITED: "待审核" };
return map[status] || status || "待审核";
},
authStatusTagType(status) {
const map = { PASS: "success", REFUSE: "danger", TOBEAUDITED: "warning" };
return map[status] || "info";
},
canPassAuth(row) {
const status = row?.authStatus;
return status === "TOBEAUDITED" || status === "REFUSE" || !status;
},
canRejectAuth(row) {
const status = row?.authStatus;
return status === "TOBEAUDITED" || status === "PASS" || !status;
},
parsePageResult(res) {
if (!res) return { records: [], total: 0 };
if (res.records) {
return { records: res.records || [], total: res.total || 0 };
});
const activeTab = ref("message");
const searchKeyword = ref("");
const messageLoading = ref(false);
const userLoading = ref(false);
const blockLoading = ref(false);
const messageList = ref([]);
const userList = ref([]);
const blockList = ref([]);
const messagePagination = reactive({
pageNumber: 1,
pageSize: 10,
total: 0,
});
const userPagination = reactive({
pageNumber: 1,
pageSize: 10,
total: 0,
});
const blockPagination = reactive({
pageNumber: 1,
pageSize: 10,
total: 0,
});
const blockDialogVisible = ref(false);
const blockTarget = ref(null);
const blockReason = ref("");
const blockSubmitting = ref(false);
watch(
() => props.liveId,
() => {
loadMessages();
}
);
function formatTime(time) {
if (!time) return "-";
return unixToDate(time);
}
function authStatusText(status) {
const map = { PASS: "已通过", REFUSE: "已拒绝", TOBEAUDITED: "待审核" };
return map[status] || status || "待审核";
}
function authStatusTagType(status) {
const map = { PASS: "success", REFUSE: "danger", TOBEAUDITED: "warning" };
return map[status] || "info";
}
function canPassAuth(row) {
const status = row?.authStatus;
return status === "TOBEAUDITED" || status === "REFUSE" || !status;
}
function canRejectAuth(row) {
const status = row?.authStatus;
return status === "TOBEAUDITED" || status === "PASS" || !status;
}
function parsePageResult(res) {
if (!res) return { records: [], total: 0 };
if (res.records) {
return { records: res.records || [], total: res.total || 0 };
}
if (res.result?.records) {
return { records: res.result.records || [], total: res.result.total || 0 };
}
if (res.success && res.result) {
return { records: res.result.records || [], total: res.result.total || 0 };
}
return { records: [], total: 0 };
}
function onTabChange(tab) {
if (tab === "message") loadMessages();
if (tab === "user") loadUsers();
if (tab === "block") loadBlocks();
}
function loadMessages() {
if (!props.liveId) return;
messageLoading.value = true;
const params = {
liveStreamId: props.liveId,
pageNumber: messagePagination.pageNumber,
pageSize: messagePagination.pageSize,
};
if (searchKeyword.value?.trim()) {
params.keyword = searchKeyword.value.trim();
}
queryLiveMessagePage(params)
.then((res) => {
messageLoading.value = false;
if (res.success) {
const page = parsePageResult(res);
messageList.value = page.records;
messagePagination.total = page.total;
}
if (res.result?.records) {
return { records: res.result.records || [], total: res.result.total || 0 };
})
.catch(() => {
messageLoading.value = false;
});
}
function loadUsers() {
if (!props.liveId) return;
userLoading.value = true;
liveUserList({
liveRoomId: props.liveId,
pageNumber: userPagination.pageNumber,
pageSize: userPagination.pageSize,
})
.then((res) => {
userLoading.value = false;
if (res.success) {
userList.value = res.result?.records || [];
userPagination.total = res.result?.total || 0;
}
if (res.success && res.result) {
return { records: res.result.records || [], total: res.result.total || 0 };
})
.catch(() => {
userLoading.value = false;
});
}
function loadBlocks() {
if (!props.liveId) return;
blockLoading.value = true;
getLiveBlockPage({
liveRoomId: props.liveId,
pageNumber: blockPagination.pageNumber,
pageSize: blockPagination.pageSize,
})
.then((res) => {
blockLoading.value = false;
const page = parsePageResult(res);
blockList.value = page.records;
blockPagination.total = page.total;
})
.catch(() => {
blockLoading.value = false;
});
}
function handleAuth(record, authStatus) {
authLiveMessage({
...record,
authStatus,
}).then((res) => {
if (res.success) {
Message.success("操作成功");
loadMessages();
}
});
}
function handleToggleMute(record) {
const muteFlag = !record.muteFlag;
editLiveUserMute({
liveUserid: record.id,
muteFlag,
}).then((res) => {
if (res.success) {
Message.success(muteFlag ? "已禁言" : "已解除禁言");
loadUsers();
}
});
}
function handleBlock(record) {
blockTarget.value = record;
blockReason.value = "";
blockDialogVisible.value = true;
}
function confirmBlock() {
if (!blockReason.value?.trim()) {
Message.warning("请输入拉黑原因");
return;
}
blockSubmitting.value = true;
blockUser({
liveRoomId: props.liveId,
userId: blockTarget.value?.userId || blockTarget.value?.id,
reason: blockReason.value.trim(),
})
.then((res) => {
blockSubmitting.value = false;
if (res.success) {
Message.success("拉黑成功");
blockDialogVisible.value = false;
loadUsers();
loadBlocks();
}
return { records: [], total: 0 };
},
onTabChange(tab) {
if (tab === "message") this.loadMessages();
if (tab === "user") this.loadUsers();
if (tab === "block") this.loadBlocks();
},
loadMessages() {
if (!this.liveId) return;
this.messageLoading = true;
const params = {
liveStreamId: this.liveId,
pageNumber: this.messagePagination.pageNumber,
pageSize: this.messagePagination.pageSize,
};
if (this.searchKeyword?.trim()) {
params.keyword = this.searchKeyword.trim();
}
queryLiveMessagePage(params)
.then((res) => {
this.messageLoading = false;
if (res.success) {
const page = this.parsePageResult(res);
this.messageList = page.records;
this.messagePagination.total = page.total;
}
})
.catch(() => {
this.messageLoading = false;
});
},
loadUsers() {
if (!this.liveId) return;
this.userLoading = true;
liveUserList({
liveRoomId: this.liveId,
pageNumber: this.userPagination.pageNumber,
pageSize: this.userPagination.pageSize,
})
.then((res) => {
this.userLoading = false;
if (res.success) {
this.userList = res.result?.records || [];
this.userPagination.total = res.result?.total || 0;
}
})
.catch(() => {
this.userLoading = false;
});
},
loadBlocks() {
if (!this.liveId) return;
this.blockLoading = true;
getLiveBlockPage({
liveRoomId: this.liveId,
pageNumber: this.blockPagination.pageNumber,
pageSize: this.blockPagination.pageSize,
})
.then((res) => {
this.blockLoading = false;
const page = this.parsePageResult(res);
this.blockList = page.records;
this.blockPagination.total = page.total;
})
.catch(() => {
this.blockLoading = false;
});
},
handleAuth(record, authStatus) {
authLiveMessage({
...record,
authStatus,
}).then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.loadMessages();
}
});
},
handleToggleMute(record) {
const muteFlag = !record.muteFlag;
editLiveUserMute({
liveUserid: record.id,
muteFlag,
}).then((res) => {
if (res.success) {
this.$Message.success(muteFlag ? "已禁言" : "已解除禁言");
this.loadUsers();
}
});
},
handleBlock(record) {
this.blockTarget = record;
this.blockReason = "";
this.blockDialogVisible = true;
},
confirmBlock() {
if (!this.blockReason?.trim()) {
this.$Message.warning("请输入拉黑原因");
return;
}
this.blockSubmitting = true;
blockUser({
liveRoomId: this.liveId,
userId: this.blockTarget?.userId || this.blockTarget?.id,
reason: this.blockReason.trim(),
})
.then((res) => {
this.blockSubmitting = false;
if (res.success) {
this.$Message.success("拉黑成功");
this.blockDialogVisible = false;
this.loadUsers();
this.loadBlocks();
}
})
.catch(() => {
this.blockSubmitting = false;
});
},
handleUnblock(record) {
unblockUser({
liveRoomId: this.liveId,
userId: record.userId || record.id,
}).then((res) => {
if (res.success) {
this.$Message.success("已解除拉黑");
this.loadBlocks();
}
});
},
onMessagePageSizeChange() {
this.messagePagination.pageNumber = 1;
this.loadMessages();
},
onUserPageSizeChange() {
this.userPagination.pageNumber = 1;
this.loadUsers();
},
onBlockPageSizeChange() {
this.blockPagination.pageNumber = 1;
this.loadBlocks();
},
handleSearch() {
this.messagePagination.pageNumber = 1;
this.loadMessages();
},
handleRefresh() {
this.loadMessages();
},
},
};
})
.catch(() => {
blockSubmitting.value = false;
});
}
function handleUnblock(record) {
unblockUser({
liveRoomId: props.liveId,
userId: record.userId || record.id,
}).then((res) => {
if (res.success) {
Message.success("已解除拉黑");
loadBlocks();
}
});
}
function onMessagePageSizeChange() {
messagePagination.pageNumber = 1;
loadMessages();
}
function onUserPageSizeChange() {
userPagination.pageNumber = 1;
loadUsers();
}
function onBlockPageSizeChange() {
blockPagination.pageNumber = 1;
loadBlocks();
}
function handleSearch() {
messagePagination.pageNumber = 1;
loadMessages();
}
function handleRefresh() {
loadMessages();
}
onMounted(() => {
loadMessages();
});
</script>
<style lang="scss" scoped>

View File

@@ -105,7 +105,8 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref, watch } from "vue";
import {
cancelRecommendLiveCoupon,
getLiveCouponList,
@@ -114,189 +115,192 @@ import {
setRecommendLiveCoupon,
} from "@/api/live";
import { getPlatformCouponList } from "@/api/promotion";
import { Message, Modal } from "@/utils/message";
import { unitPrice } from "@/utils/filters";
export default {
name: "LiveCoupon",
props: {
liveId: {
type: String,
required: true,
},
liveRoomName: {
type: String,
default: "",
},
defineOptions({ name: "LiveCoupon" });
const props = defineProps({
liveId: {
type: String,
required: true,
},
data() {
return {
loading: false,
couponList: [],
selectedIds: [],
pickerVisible: false,
pickerLoading: false,
pickerSubmitting: false,
pickerList: [],
pickerTotal: 0,
pickerSelected: [],
pickerSearch: {
pageNumber: 1,
pageSize: 10,
couponName: "",
promotionStatus: "START",
sort: "create_time",
order: "desc",
},
};
liveRoomName: {
type: String,
default: "",
},
watch: {
liveId() {
this.loadCoupons();
},
},
mounted() {
this.loadCoupons();
},
methods: {
formatPrice(price) {
return this.$filters.unitPrice(price, "¥");
},
promotionStatusText(status) {
const map = { NEW: "未开始", START: "已开始", END: "已结束", CLOSE: "已关闭" };
return map[status] || "未知";
},
promotionStatusTagType(status) {
const map = { NEW: "info", START: "success", END: "warning", CLOSE: "danger" };
return map[status] || "info";
},
loadCoupons() {
if (!this.liveId) return;
this.loading = true;
getLiveCouponList(this.liveId)
.then((res) => {
this.loading = false;
if (res.success) {
this.couponList = res.result || [];
}
})
.catch(() => {
this.loading = false;
});
},
handleSelectionChange(rows) {
this.selectedIds = (rows || []).map((r) => r.id);
},
openCouponSelector() {
this.pickerVisible = true;
this.pickerSelected = [];
this.pickerSearch.pageNumber = 1;
this.loadPickerCoupons();
},
loadPickerCoupons() {
this.pickerLoading = true;
getPlatformCouponList(this.pickerSearch)
.then((res) => {
this.pickerLoading = false;
if (res.success) {
this.pickerList = res.result?.records || [];
this.pickerTotal = res.result?.total || 0;
}
})
.catch(() => {
this.pickerLoading = false;
});
},
onPickerPageSizeChange() {
this.pickerSearch.pageNumber = 1;
this.loadPickerCoupons();
},
handlePickerSelectionChange(rows) {
this.pickerSelected = rows || [];
},
buildLiveCouponPayload(item) {
return {
couponId: item.id,
couponPrice: Number(item.price ?? 0),
couponName: item.couponName || "",
liveRoomId: this.liveId,
liveRoomName: this.liveRoomName || "",
hideFlag: false,
recommend: false,
};
},
confirmPicker() {
if (!this.pickerSelected.length) {
this.$Message.warning("请选择优惠券");
return;
});
const table = ref(null);
const pickerTable = ref(null);
const loading = ref(false);
const couponList = ref([]);
const selectedIds = ref([]);
const pickerVisible = ref(false);
const pickerLoading = ref(false);
const pickerSubmitting = ref(false);
const pickerList = ref([]);
const pickerTotal = ref(0);
const pickerSelected = ref([]);
const pickerSearch = reactive({
pageNumber: 1,
pageSize: 10,
couponName: "",
promotionStatus: "START",
sort: "create_time",
order: "desc",
});
watch(
() => props.liveId,
() => {
loadCoupons();
}
);
onMounted(() => {
loadCoupons();
});
function formatPrice(price) {
return unitPrice(price, "¥");
}
function promotionStatusText(status) {
const map = { NEW: "未开始", START: "已开始", END: "已结束", CLOSE: "已关闭" };
return map[status] || "未知";
}
function promotionStatusTagType(status) {
const map = { NEW: "info", START: "success", END: "warning", CLOSE: "danger" };
return map[status] || "info";
}
function loadCoupons() {
if (!props.liveId) return;
loading.value = true;
getLiveCouponList(props.liveId)
.then((res) => {
loading.value = false;
if (res.success) {
couponList.value = res.result || [];
}
const existingCouponIds = new Set(this.couponList.map((c) => String(c.couponId)));
const payload = this.pickerSelected
.filter((item) => !existingCouponIds.has(String(item.id)))
.map((item) => this.buildLiveCouponPayload(item));
if (!payload.length) {
this.$Message.warning("所选优惠券均已添加");
return;
})
.catch(() => {
loading.value = false;
});
}
function handleSelectionChange(rows) {
selectedIds.value = (rows || []).map((r) => r.id);
}
function openCouponSelector() {
pickerVisible.value = true;
pickerSelected.value = [];
pickerSearch.pageNumber = 1;
loadPickerCoupons();
}
function loadPickerCoupons() {
pickerLoading.value = true;
getPlatformCouponList(pickerSearch)
.then((res) => {
pickerLoading.value = false;
if (res.success) {
pickerList.value = res.result?.records || [];
pickerTotal.value = res.result?.total || 0;
}
this.pickerSubmitting = true;
saveBatchLiveCoupon(payload)
.then((res) => {
this.pickerSubmitting = false;
if (res.success) {
this.$Message.success("添加成功");
this.pickerVisible = false;
this.loadCoupons();
}
})
.catch(() => {
this.pickerSubmitting = false;
});
},
handleRemove(record) {
this.$Modal.confirm({
title: "确认移除",
content: "确定要移除该优惠券吗?",
onOk: () => {
return removeBatchLiveCoupon([record.id]).then((res) => {
if (res.success) {
this.$Message.success("移除成功");
this.loadCoupons();
}
});
},
});
},
handleBatchRemove() {
this.$Modal.confirm({
title: "确认批量移除",
content: `确定要移除选中的 ${this.selectedIds.length} 个优惠券吗?`,
onOk: () => {
return removeBatchLiveCoupon(this.selectedIds).then((res) => {
if (res.success) {
this.$Message.success("移除成功");
this.selectedIds = [];
this.loadCoupons();
}
});
},
});
},
handleRecommend(record) {
setRecommendLiveCoupon(record.id).then((res) => {
})
.catch(() => {
pickerLoading.value = false;
});
}
function onPickerPageSizeChange() {
pickerSearch.pageNumber = 1;
loadPickerCoupons();
}
function handlePickerSelectionChange(rows) {
pickerSelected.value = rows || [];
}
function buildLiveCouponPayload(item) {
return {
couponId: item.id,
couponPrice: Number(item.price ?? 0),
couponName: item.couponName || "",
liveRoomId: props.liveId,
liveRoomName: props.liveRoomName || "",
hideFlag: false,
recommend: false,
};
}
function confirmPicker() {
if (!pickerSelected.value.length) {
Message.warning("请选择优惠券");
return;
}
const existingCouponIds = new Set(couponList.value.map((c) => String(c.couponId)));
const payload = pickerSelected.value
.filter((item) => !existingCouponIds.has(String(item.id)))
.map((item) => buildLiveCouponPayload(item));
if (!payload.length) {
Message.warning("所选优惠券均已添加");
return;
}
pickerSubmitting.value = true;
saveBatchLiveCoupon(payload)
.then((res) => {
pickerSubmitting.value = false;
if (res.success) {
Message.success("添加成功");
pickerVisible.value = false;
loadCoupons();
}
})
.catch(() => {
pickerSubmitting.value = false;
});
}
function handleRemove(record) {
Modal.confirm({
title: "确认移除",
content: "确定要移除该优惠券吗?",
onOk: () => {
return removeBatchLiveCoupon([record.id]).then((res) => {
if (res.success) {
this.$Message.success("已设为推荐");
this.loadCoupons();
Message.success("移除成功");
loadCoupons();
}
});
},
handleCancelRecommend(record) {
cancelRecommendLiveCoupon(record.id).then((res) => {
});
}
function handleBatchRemove() {
Modal.confirm({
title: "确认批量移除",
content: `确定要移除选中的 ${selectedIds.value.length} 个优惠券吗?`,
onOk: () => {
return removeBatchLiveCoupon(selectedIds.value).then((res) => {
if (res.success) {
this.$Message.success("已取消推荐");
this.loadCoupons();
Message.success("移除成功");
selectedIds.value = [];
loadCoupons();
}
});
},
},
};
});
}
function handleRecommend(record) {
setRecommendLiveCoupon(record.id).then((res) => {
if (res.success) {
Message.success("已设为推荐");
loadCoupons();
}
});
}
function handleCancelRecommend(record) {
cancelRecommendLiveCoupon(record.id).then((res) => {
if (res.success) {
Message.success("已取消推荐");
loadCoupons();
}
});
}
</script>
<style lang="scss" scoped>

View File

@@ -30,136 +30,155 @@
</div>
</template>
<script>
<script setup>
import { computed, onBeforeUnmount, ref, toRef, watch } from "vue";
import Cookies from "js-cookie";
import { toRef } from "vue";
import { useChat } from "../composables/useChat";
import { resolveImConfigFromDetail } from "../composables/useLiveSetting";
export default {
defineOptions({
name: "LiveChatPanel",
props: {
liveId: {
type: String,
required: true,
},
liveDetail: {
type: Object,
default: () => ({}),
},
active: {
type: Boolean,
default: true,
},
});
const props = defineProps({
liveId: {
type: String,
required: true,
},
setup(props) {
let userInfo = {};
try {
userInfo = JSON.parse(Cookies.get("userInfoManager") || "{}");
} catch {
userInfo = {};
liveDetail: {
type: Object,
default: () => ({}),
},
active: {
type: Boolean,
default: true,
},
});
let userInfo = {};
try {
userInfo = JSON.parse(Cookies.get("userInfoManager") || "{}");
} catch {
userInfo = {};
}
const {
messageList,
chatListRef,
bottomAnchorRef,
unreadCount,
isLoadingHistory,
initError,
initTencentIm,
scrollToBottomAndClearUnread,
onScroll,
setGroupID,
cleanup,
} = useChat(toRef(props, "liveId"), userInfo, toRef(props, "liveDetail"));
const initializing = ref(false);
const chatInited = ref(false);
/** 腾讯云 IM 群组:优先后端字段,否则用直播间 ID(与旧版中控台一致) */
const imGroupId = computed(() => {
return props.liveDetail?.imGroupId || props.liveDetail?.groupId || props.liveId || "";
});
const imSdkAppId = computed(() => {
return resolveImConfigFromDetail(props.liveDetail).sdkAppId;
});
const canInitChat = computed(() => {
const { sdkAppId, secretKey, userSig } = resolveImConfigFromDetail(props.liveDetail);
return !!(imGroupId.value && sdkAppId && (userSig || secretKey));
});
watch(
canInitChat,
(ready) => {
if (!ready) return;
setGroupID(imGroupId.value);
if (props.active) {
bootstrapChat();
}
},
{ immediate: true }
);
return useChat(toRef(props, "liveId"), userInfo, toRef(props, "liveDetail"));
},
data() {
return {
initializing: false,
chatInited: false,
};
},
computed: {
/** 腾讯云 IM 群组:优先后端字段,否则用直播间 ID(与旧版中控台一致) */
imGroupId() {
return this.liveDetail?.imGroupId || this.liveDetail?.groupId || this.liveId || "";
},
imSdkAppId() {
return resolveImConfigFromDetail(this.liveDetail).sdkAppId;
},
canInitChat() {
const { sdkAppId, secretKey, userSig } = resolveImConfigFromDetail(this.liveDetail);
return !!(this.imGroupId && sdkAppId && (userSig || secretKey));
},
},
watch: {
canInitChat: {
immediate: true,
handler(ready) {
if (!ready) return;
this.setGroupID(this.imGroupId);
if (this.active) {
this.bootstrapChat();
}
},
},
imGroupId(id) {
if (!id) return;
this.setGroupID(id);
},
active(val) {
if (val && this.canInitChat && !this.chatInited) {
this.bootstrapChat();
}
},
liveId() {
this.chatInited = false;
this.cleanup();
if (this.active && this.canInitChat) {
this.bootstrapChat();
}
},
liveDetail: {
deep: true,
handler(detail, oldDetail) {
const nextConfig = resolveImConfigFromDetail(detail);
const prevConfig = resolveImConfigFromDetail(oldDetail);
if (
nextConfig.sdkAppId &&
(nextConfig.sdkAppId !== prevConfig.sdkAppId ||
nextConfig.secretKey !== prevConfig.secretKey ||
nextConfig.userSig !== prevConfig.userSig)
) {
this.chatInited = false;
this.cleanup();
if (this.active && this.canInitChat) {
this.bootstrapChat();
}
}
},
},
},
beforeUnmount() {
this.cleanup();
},
methods: {
async bootstrapChat() {
if (!this.liveId || this.chatInited || this.initializing || !this.canInitChat) {
return;
}
watch(imGroupId, (id) => {
if (!id) return;
setGroupID(id);
});
const groupId = this.imGroupId;
if (!groupId) {
this.initError = "缺少直播间 ID,无法初始化聊天";
return;
}
this.setGroupID(groupId);
watch(
() => props.active,
(val) => {
if (val && canInitChat.value && !chatInited.value) {
bootstrapChat();
}
}
);
this.initializing = true;
try {
await this.initTencentIm();
if (!this.initError) {
this.chatInited = true;
}
} catch (error) {
console.error("初始化直播聊天失败:", error);
this.initError = "聊天初始化失败,请刷新重试";
this.chatInited = false;
} finally {
this.initializing = false;
watch(
() => props.liveId,
() => {
chatInited.value = false;
cleanup();
if (props.active && canInitChat.value) {
bootstrapChat();
}
}
);
watch(
() => props.liveDetail,
(detail, oldDetail) => {
const nextConfig = resolveImConfigFromDetail(detail);
const prevConfig = resolveImConfigFromDetail(oldDetail);
if (
nextConfig.sdkAppId &&
(nextConfig.sdkAppId !== prevConfig.sdkAppId ||
nextConfig.secretKey !== prevConfig.secretKey ||
nextConfig.userSig !== prevConfig.userSig)
) {
chatInited.value = false;
cleanup();
if (props.active && canInitChat.value) {
bootstrapChat();
}
},
}
},
};
{ deep: true }
);
onBeforeUnmount(() => {
cleanup();
});
async function bootstrapChat() {
if (!props.liveId || chatInited.value || initializing.value || !canInitChat.value) {
return;
}
const groupId = imGroupId.value;
if (!groupId) {
initError.value = "缺少直播间 ID,无法初始化聊天";
return;
}
setGroupID(groupId);
initializing.value = true;
try {
await initTencentIm();
if (!initError.value) {
chatInited.value = true;
}
} catch (error) {
console.error("初始化直播聊天失败:", error);
initError.value = "聊天初始化失败,请刷新重试";
chatInited.value = false;
} finally {
initializing.value = false;
}
}
</script>
<style lang="scss" scoped>

View File

@@ -76,7 +76,7 @@
</div>
<el-empty v-if="!loading && !goodsList.length" description="暂无直播商品" />
<sku-select ref="skuSelect" @selectedGoodsData="selectedGoodsData" />
<sku-select ref="skuSelectRef" @selectedGoodsData="selectedGoodsData" />
<el-dialog v-model="popularityVisible" title="设置商品热度" width="400px">
<el-form label-width="80px">
@@ -92,7 +92,8 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref, watch } from "vue";
import {
cancelRecommendLiveGoods,
getLiveGoodsList,
@@ -104,193 +105,208 @@ import {
setRecommendLiveGoods,
} from "@/api/live";
import skuSelect from "@/components/lili-dialog";
import { Message, Modal } from "@/utils/message";
import { unitPrice } from "@/utils/filters";
export default {
name: "LiveGoods",
components: { skuSelect },
props: {
liveId: {
type: String,
required: true,
},
readonly: {
type: Boolean,
default: false,
},
defineOptions({ name: "LiveGoods" });
const props = defineProps({
liveId: {
type: String,
required: true,
},
data() {
return {
loading: false,
goodsList: [],
selectedIds: [],
popularityVisible: false,
currentGoods: null,
popularityForm: {
popularity: 0,
},
};
readonly: {
type: Boolean,
default: false,
},
watch: {
liveId() {
this.loadGoods();
},
},
mounted() {
this.loadGoods();
},
methods: {
formatPrice(price) {
return this.$filters.unitPrice(price, "¥");
},
isSellOut(row) {
return !!row.soldOutFlag;
},
buildLiveGoodsPayload(item) {
const price = Number(item.price ?? 0);
const originPrice = Number(
item.originPrice ?? item.cost ?? item.originalPrice ?? price
);
return {
liveRoomId: this.liveId,
goodsId: item.goodsId,
skuId: item.id || item.skuId,
goodsName: item.goodsName || "",
stock: String(item.quantity ?? item.stock ?? ""),
price,
thumbnail: item.thumbnail || "",
salesCount: Number(item.salesCount ?? item.buyCount ?? 0),
storeId: item.storeId || "",
storeName: item.storeName || "",
originPrice,
sellPoint: item.sellPoint || item.sellingPoint || "",
popularity: 0,
hideFlag: false,
canBuyFlag: true,
soldOutFlag: false,
recommend: false,
};
},
loadGoods() {
if (!this.liveId) return;
this.loading = true;
getLiveGoodsList(this.liveId)
.then((res) => {
this.loading = false;
if (res.success) {
this.goodsList = res.result || [];
}
})
.catch(() => {
this.loading = false;
});
},
openChooseGoods() {
this.$refs.skuSelect.open("goods");
const data = JSON.parse(JSON.stringify(this.goodsList));
data.forEach((e) => {
if (e.skuId) e.id = e.skuId;
});
this.$refs.skuSelect.goodsData = data;
},
selectedGoodsData(selected) {
if (!selected?.length) return;
const existingSkuIds = new Set(this.goodsList.map((g) => String(g.skuId)));
const payload = selected
.filter((item) => !existingSkuIds.has(String(item.id || item.skuId)))
.map((item) => this.buildLiveGoodsPayload(item));
if (!payload.length) {
this.$Message.warning("所选商品均已添加");
return;
});
const loading = ref(false);
const goodsList = ref([]);
const selectedIds = ref([]);
const popularityVisible = ref(false);
const currentGoods = ref(null);
const popularityForm = reactive({
popularity: 0,
});
// 模板 ref:原 ref="skuSelect" 与 import 的组件同名,改名避免互相覆盖
const skuSelectRef = ref(null);
watch(
() => props.liveId,
() => {
loadGoods();
}
);
function formatPrice(price) {
return unitPrice(price, "¥");
}
function isSellOut(row) {
return !!row.soldOutFlag;
}
function buildLiveGoodsPayload(item) {
const price = Number(item.price ?? 0);
const originPrice = Number(
item.originPrice ?? item.cost ?? item.originalPrice ?? price
);
return {
liveRoomId: props.liveId,
goodsId: item.goodsId,
skuId: item.id || item.skuId,
goodsName: item.goodsName || "",
stock: String(item.quantity ?? item.stock ?? ""),
price,
thumbnail: item.thumbnail || "",
salesCount: Number(item.salesCount ?? item.buyCount ?? 0),
storeId: item.storeId || "",
storeName: item.storeName || "",
originPrice,
sellPoint: item.sellPoint || item.sellingPoint || "",
popularity: 0,
hideFlag: false,
canBuyFlag: true,
soldOutFlag: false,
recommend: false,
};
}
function loadGoods() {
if (!props.liveId) return;
loading.value = true;
getLiveGoodsList(props.liveId)
.then((res) => {
loading.value = false;
if (res.success) {
goodsList.value = res.result || [];
}
saveBatchLiveGoods(payload).then((res) => {
})
.catch(() => {
loading.value = false;
});
}
function openChooseGoods() {
skuSelectRef.value.open("goods");
const data = JSON.parse(JSON.stringify(goodsList.value));
data.forEach((e) => {
if (e.skuId) e.id = e.skuId;
});
skuSelectRef.value.goodsData = data;
}
function selectedGoodsData(selected) {
if (!selected?.length) return;
const existingSkuIds = new Set(goodsList.value.map((g) => String(g.skuId)));
const payload = selected
.filter((item) => !existingSkuIds.has(String(item.id || item.skuId)))
.map((item) => buildLiveGoodsPayload(item));
if (!payload.length) {
Message.warning("所选商品均已添加");
return;
}
saveBatchLiveGoods(payload).then((res) => {
if (res.success) {
Message.success("添加成功");
loadGoods();
}
});
}
function handleSelectionChange(rows) {
selectedIds.value = (rows || []).map((r) => r.id);
}
function handleRemove(record) {
Modal.confirm({
title: "确认移除",
content: "确定要移除该商品吗?",
onOk: () => {
return removeBatchLiveGoods([record.id]).then((res) => {
if (res.success) {
this.$Message.success("添加成功");
this.loadGoods();
Message.success("移除成功");
loadGoods();
}
});
},
handleSelectionChange(rows) {
this.selectedIds = (rows || []).map((r) => r.id);
},
handleRemove(record) {
this.$Modal.confirm({
title: "确认移除",
content: "确定要移除该商品吗?",
onOk: () => {
return removeBatchLiveGoods([record.id]).then((res) => {
if (res.success) {
this.$Message.success("移除成功");
this.loadGoods();
}
});
},
});
},
handleBatchRemove() {
this.$Modal.confirm({
title: "确认批量移除",
content: `确定要移除选中的 ${this.selectedIds.length} 个商品吗?`,
onOk: () => {
return removeBatchLiveGoods(this.selectedIds).then((res) => {
if (res.success) {
this.$Message.success("移除成功");
this.selectedIds = [];
this.loadGoods();
}
});
},
});
},
handleRecommend(record) {
setRecommendLiveGoods(record.id).then((res) => {
});
}
function handleBatchRemove() {
Modal.confirm({
title: "确认批量移除",
content: `确定要移除选中的 ${selectedIds.value.length} 个商品吗?`,
onOk: () => {
return removeBatchLiveGoods(selectedIds.value).then((res) => {
if (res.success) {
this.$Message.success("已设为推荐");
this.loadGoods();
Message.success("移除成功");
selectedIds.value = [];
loadGoods();
}
});
},
handleCancelRecommend(record) {
cancelRecommendLiveGoods(record.id).then((res) => {
if (res.success) {
this.$Message.success("已取消推荐");
this.loadGoods();
}
});
},
handleToggleSoldOut(record) {
const soldOutFlag = !this.isSellOut(record);
setLiveGoodsSoldOut(record.id, soldOutFlag).then((res) => {
if (res.success) {
this.$Message.success(soldOutFlag ? "已设为售罄" : "已取消售罄");
this.loadGoods();
}
});
},
openPopularityModal(record) {
this.currentGoods = record;
getLiveGoodsPopularity(record.id, this.liveId).then((res) => {
this.popularityForm.popularity = res.success
? res.result?.popularity ?? record.popularity ?? 0
: record.popularity ?? 0;
this.popularityVisible = true;
});
},
handleSavePopularity() {
if (!this.currentGoods) return;
setLiveGoodsPopularity(
this.currentGoods.id,
this.liveId,
this.popularityForm.popularity
).then((res) => {
if (res.success) {
this.$Message.success("热度设置成功");
this.popularityVisible = false;
this.loadGoods();
}
});
},
},
};
});
}
function handleRecommend(record) {
setRecommendLiveGoods(record.id).then((res) => {
if (res.success) {
Message.success("已设为推荐");
loadGoods();
}
});
}
function handleCancelRecommend(record) {
cancelRecommendLiveGoods(record.id).then((res) => {
if (res.success) {
Message.success("已取消推荐");
loadGoods();
}
});
}
function handleToggleSoldOut(record) {
const soldOutFlag = !isSellOut(record);
setLiveGoodsSoldOut(record.id, soldOutFlag).then((res) => {
if (res.success) {
Message.success(soldOutFlag ? "已设为售罄" : "已取消售罄");
loadGoods();
}
});
}
function openPopularityModal(record) {
currentGoods.value = record;
getLiveGoodsPopularity(record.id, props.liveId).then((res) => {
popularityForm.popularity = res.success
? res.result?.popularity ?? record.popularity ?? 0
: record.popularity ?? 0;
popularityVisible.value = true;
});
}
function handleSavePopularity() {
if (!currentGoods.value) return;
setLiveGoodsPopularity(
currentGoods.value.id,
props.liveId,
popularityForm.popularity
).then((res) => {
if (res.success) {
Message.success("热度设置成功");
popularityVisible.value = false;
loadGoods();
}
});
}
onMounted(() => {
loadGoods();
});
</script>
<style lang="scss" scoped>

View File

@@ -76,142 +76,144 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref, watch } from "vue";
import { DocumentCopy } from "@element-plus/icons-vue";
import { queryLiveOrderPage } from "@/api/live";
import { Message } from "@/utils/message";
import { unixToDate, unitPrice } from "@/utils/filters";
export default {
name: "LivePurchaseRecords",
components: { DocumentCopy },
props: {
liveId: {
type: String,
required: true,
},
defineOptions({ name: "LivePurchaseRecords" });
const props = defineProps({
liveId: {
type: String,
required: true,
},
data() {
return {
loading: false,
orderList: [],
pagination: {
pageNumber: 1,
pageSize: 5,
total: 0,
});
const loading = ref(false);
const orderList = ref([]);
const pagination = reactive({
pageNumber: 1,
pageSize: 5,
total: 0,
});
watch(
() => props.liveId,
() => {
pagination.pageNumber = 1;
loadData();
}
);
onMounted(() => {
loadData();
});
function refresh() {
loadData();
}
function formatTime(time) {
if (!time) return "-";
if (typeof time === "string" && time.includes("-")) return time;
const ts = typeof time === "number" ? (time > 1e12 ? time / 1000 : time) : time;
return unixToDate(ts);
}
function formatPrice(val) {
return unitPrice(val, "¥");
}
function getOrderItems(order) {
if (order.orderItems?.length) return order.orderItems;
if (order.groupGoodsId) {
return [
{
image: order.groupImages,
name: order.groupName,
goodsPrice: order.groupGoodsPrice,
num: order.groupNum,
},
};
},
watch: {
liveId() {
this.pagination.pageNumber = 1;
this.loadData();
},
},
mounted() {
this.loadData();
},
methods: {
refresh() {
this.loadData();
},
formatTime(time) {
if (!time) return "-";
if (typeof time === "string" && time.includes("-")) return time;
const ts = typeof time === "number" ? (time > 1e12 ? time / 1000 : time) : time;
return this.$filters.unixToDate(ts);
},
formatPrice(val) {
return this.$filters.unitPrice(val, "¥");
},
getOrderItems(order) {
if (order.orderItems?.length) return order.orderItems;
if (order.groupGoodsId) {
return [
{
image: order.groupImages,
name: order.groupName,
goodsPrice: order.groupGoodsPrice,
num: order.groupNum,
},
];
];
}
return [];
}
function clientTypeText(v) {
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
return map[v] || v || "-";
}
function clientTypeDotClass(v) {
const map = { H5: "warning", PC: "primary", WECHAT_MP: "success", APP: "info" };
return map[v] || "info";
}
function orderPromotionText(v) {
const map = {
NORMAL: "普通订单",
PINTUAN: "拼团订单",
GIFT: "赠品订单",
POINTS: "积分订单",
KANJIA: "砍价订单",
};
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 orderStatusDotClass(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 copySn(sn) {
if (!sn) return;
navigator.clipboard.writeText(sn);
Message.success("订单编号已复制");
}
function onPageSizeChange() {
pagination.pageNumber = 1;
loadData();
}
function loadData() {
if (!props.liveId) return;
loading.value = true;
queryLiveOrderPage({
liveRoomId: props.liveId,
pageNumber: pagination.pageNumber,
pageSize: pagination.pageSize,
})
.then((res) => {
loading.value = false;
if (res.success) {
orderList.value = res.result?.records || [];
pagination.total = res.result?.total || 0;
}
return [];
},
clientTypeText(v) {
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
return map[v] || v || "-";
},
clientTypeDotClass(v) {
const map = { H5: "warning", PC: "primary", WECHAT_MP: "success", APP: "info" };
return map[v] || "info";
},
orderPromotionText(v) {
const map = {
NORMAL: "普通订单",
PINTUAN: "拼团订单",
GIFT: "赠品订单",
POINTS: "积分订单",
KANJIA: "砍价订单",
};
return map[v] || v || "-";
},
orderStatusText(v) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
UNDELIVERED: "待发货",
STAY_PICKED_UP: "待自提",
PARTS_DELIVERED: "部分发货",
DELIVERED: "已发货",
COMPLETED: "已完成",
TAKE: "待核验",
CANCELLED: "已取消",
};
return map[v] || v || "-";
},
orderStatusDotClass(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";
},
copySn(sn) {
if (!sn) return;
navigator.clipboard.writeText(sn);
this.$Message.success("订单编号已复制");
},
onPageSizeChange() {
this.pagination.pageNumber = 1;
this.loadData();
},
loadData() {
if (!this.liveId) return;
this.loading = true;
queryLiveOrderPage({
liveRoomId: this.liveId,
pageNumber: this.pagination.pageNumber,
pageSize: this.pagination.pageSize,
})
.then((res) => {
this.loading = false;
if (res.success) {
this.orderList = res.result?.records || [];
this.pagination.total = res.result?.total || 0;
}
})
.catch(() => {
this.loading = false;
});
},
},
};
})
.catch(() => {
loading.value = false;
});
}
defineExpose({ refresh });
</script>
<style lang="scss" scoped>

View File

@@ -238,7 +238,9 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { useRoute } from "vue-router";
import {
endLive,
getLiveInfo,
@@ -248,450 +250,450 @@ import {
} from "@/api/live";
import config from "@/config";
import { DocumentCopy, Refresh } from "@element-plus/icons-vue";
import { Message, Modal } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
import CommentReview from "./components/CommentReview.vue";
import Coupon from "./components/Coupon.vue";
import LiveChatPanel from "./components/LiveChatPanel.vue";
import LiveGoods from "./components/LiveGoods.vue";
import LivePurchaseRecords from "./components/LivePurchaseRecords.vue";
export default {
name: "live-control-panel",
components: { CommentReview, Coupon, LiveChatPanel, LiveGoods, LivePurchaseRecords, DocumentCopy, Refresh },
data() {
return {
loading: false,
liveId: "",
videoUrl: "",
wapUrl: "",
aspectRatio: "16:9",
chatTab: "chat",
statsTab: "general",
manageTab: "comment",
liveDetail: {},
orderStats: {},
onlineUserList: [],
onlineLoading: false,
onlinePagination: {
pageNumber: 1,
pageSize: 10,
total: 0,
},
nowTick: Date.now(),
durationTimer: null,
pollTimer: null,
hlsHttpFallbackUsed: false,
lastHlsPullUrl: "",
};
},
created() {
this.hlsInstance = null;
this.hlsVideoElement = null;
this._hlsVideoClickHandler = null;
},
computed: {
liveStatus() {
return this.liveDetail.liveStatus || this.liveDetail.status || "";
},
isLiving() {
return this.liveStatus === "LIVING";
},
shouldPlayStream() {
return this.isLiving && !!this.videoUrl;
},
showPlayerCover() {
return !this.shouldPlayStream;
},
cumulativeViewers() {
return Number(this.liveDetail.actualViewNumber ?? 0);
},
currentViewers() {
return this.cumulativeViewers;
},
commentPeopleNumber() {
return Number(this.liveDetail.commentPeopleNumber ?? 0);
},
interactionRate() {
if (!this.cumulativeViewers) return "0.00%";
return `${((this.commentPeopleNumber / this.cumulativeViewers) * 100).toFixed(2)}%`;
},
dealConversionRate() {
if (!this.cumulativeViewers) return "0%";
const dealCount = Number(this.orderStats.dealUserCount ?? 0);
return `${((dealCount / this.cumulativeViewers) * 100).toFixed(0)}%`;
},
liveDuration() {
void this.nowTick;
const startMs = this.parseDateTime(this.liveDetail.startTime);
if (!startMs || Date.now() < startMs) return "00:00:00";
let endMs = Date.now();
if (this.liveStatus === "ENDED") {
const endTimeMs = this.parseDateTime(this.liveDetail.endTime);
if (endTimeMs && endTimeMs > startMs) {
endMs = endTimeMs;
}
defineOptions({ name: "live-control-panel" });
const route = useRoute();
const purchaseRecords = ref(null);
const hlsContainer = ref(null);
const loading = ref(false);
const liveId = ref("");
const videoUrl = ref("");
const wapUrl = ref("");
const aspectRatio = ref("16:9");
const chatTab = ref("chat");
const statsTab = ref("general");
const manageTab = ref("comment");
const liveDetail = ref({});
const orderStats = ref({});
const onlineUserList = ref([]);
const onlineLoading = ref(false);
const onlinePagination = reactive({
pageNumber: 1,
pageSize: 10,
total: 0,
});
const nowTick = ref(Date.now());
const durationTimer = ref(null);
const pollTimer = ref(null);
const hlsHttpFallbackUsed = ref(false);
const lastHlsPullUrl = ref("");
// 原 created 中直接挂到 this 上的字段(未在 data 声明),保持非响应式
let hlsInstance = null;
let hlsVideoElement = null;
let _hlsVideoClickHandler = null;
const liveStatus = computed(() => {
return liveDetail.value.liveStatus || liveDetail.value.status || "";
});
const isLiving = computed(() => {
return liveStatus.value === "LIVING";
});
const shouldPlayStream = computed(() => {
return isLiving.value && !!videoUrl.value;
});
const showPlayerCover = computed(() => {
return !shouldPlayStream.value;
});
const cumulativeViewers = computed(() => {
return Number(liveDetail.value.actualViewNumber ?? 0);
});
const currentViewers = computed(() => {
return cumulativeViewers.value;
});
const commentPeopleNumber = computed(() => {
return Number(liveDetail.value.commentPeopleNumber ?? 0);
});
const interactionRate = computed(() => {
if (!cumulativeViewers.value) return "0.00%";
return `${((commentPeopleNumber.value / cumulativeViewers.value) * 100).toFixed(2)}%`;
});
const dealConversionRate = computed(() => {
if (!cumulativeViewers.value) return "0%";
const dealCount = Number(orderStats.value.dealUserCount ?? 0);
return `${((dealCount / cumulativeViewers.value) * 100).toFixed(0)}%`;
});
const liveDuration = computed(() => {
void nowTick.value;
const startMs = parseDateTime(liveDetail.value.startTime);
if (!startMs || Date.now() < startMs) return "00:00:00";
let endMs = Date.now();
if (liveStatus.value === "ENDED") {
const endTimeMs = parseDateTime(liveDetail.value.endTime);
if (endTimeMs && endTimeMs > startMs) {
endMs = endTimeMs;
}
}
return formatDuration(endMs - startMs);
});
watch(videoUrl, () => {
syncH5Player();
});
watch(isLiving, () => {
syncH5Player();
if (isLiving.value) {
startPolling();
} else {
stopPolling();
}
});
watch(aspectRatio, () => {
lastHlsPullUrl.value = "";
nextTick(() => syncH5Player());
});
watch(chatTab, (tab) => {
if (tab === "online") loadOnlineUsers();
});
watch(statsTab, (tab) => {
if (tab === "purchase") purchaseRecords.value?.refresh();
});
onMounted(() => {
liveId.value = String(route.query.id || "");
if (!liveId.value) {
Message.error("缺少直播ID");
return;
}
refreshAll();
startDurationTimer();
});
onBeforeUnmount(() => {
destroyH5HlsPlayer();
stopPolling();
stopDurationTimer();
});
function parseDateTime(value) {
if (!value) return null;
if (typeof value === "string" && value.includes("-")) {
return new Date(value.replace(/-/g, "/")).getTime();
}
if (typeof value === "number") {
return value > 1e12 ? value : value * 1000;
}
return null;
}
function formatDuration(ms) {
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const pad = (n) => String(n).padStart(2, "0");
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
function startDurationTimer() {
stopDurationTimer();
durationTimer.value = setInterval(() => {
nowTick.value = Date.now();
}, 1000);
}
function stopDurationTimer() {
if (durationTimer.value) {
clearInterval(durationTimer.value);
durationTimer.value = null;
}
}
function formatTime(time) {
if (!time) return "-";
if (typeof time === "string" && time.includes("-")) return time;
const ts = typeof time === "number" ? (time > 1e12 ? time / 1000 : time) : time;
return unixToDate(ts);
}
function formatAmount(val) {
return Number(val || 0).toFixed(2);
}
function liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", PAUSED: "已暂停", ENDED: "已结束" };
return map[status] || "未知";
}
function liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", PAUSED: "warning", ENDED: "warning" };
return map[status] || "info";
}
function startPolling() {
stopPolling();
if (!isLiving.value) return;
pollTimer.value = setInterval(() => {
loadDetail(false);
}, 5000);
}
function stopPolling() {
if (pollTimer.value) {
clearInterval(pollTimer.value);
pollTimer.value = null;
}
}
function refreshAll() {
loadDetail();
loadOrderStats();
purchaseRecords.value?.refresh();
if (chatTab.value === "online") loadOnlineUsers();
}
function buildHlsPlayUrl(url, useHttpFallback = false) {
if (!url) return "";
const httpUrl =
useHttpFallback && url.startsWith("https://")
? url.replace("https://", "http://")
: url;
return httpUrl.replace(".flv", ".m3u8");
}
function syncH5Player(retry = 0) {
const shouldPlay = shouldPlayStream.value;
if (!shouldPlay) {
destroyH5HlsPlayer();
lastHlsPullUrl.value = "";
return;
}
if (
videoUrl.value === lastHlsPullUrl.value &&
hlsInstance &&
hlsVideoElement &&
!hlsVideoElement.paused
) {
return;
}
if (videoUrl.value === lastHlsPullUrl.value && hlsInstance) {
ensureH5VideoPlaying(hlsVideoElement);
return;
}
lastHlsPullUrl.value = videoUrl.value;
hlsHttpFallbackUsed.value = false;
nextTick(() => {
const container = hlsContainer.value;
if (!container) {
if (retry < 8) {
setTimeout(() => syncH5Player(retry + 1), 80);
}
return this.formatDuration(endMs - startMs);
},
},
watch: {
videoUrl() {
this.syncH5Player();
},
isLiving() {
this.syncH5Player();
if (this.isLiving) {
this.startPolling();
} else {
this.stopPolling();
}
},
aspectRatio() {
this.lastHlsPullUrl = "";
this.$nextTick(() => this.syncH5Player());
},
chatTab(tab) {
if (tab === "online") this.loadOnlineUsers();
},
statsTab(tab) {
if (tab === "purchase") this.$refs.purchaseRecords?.refresh();
},
},
mounted() {
this.liveId = String(this.$route.query.id || "");
if (!this.liveId) {
this.$Message.error("缺少直播ID");
return;
}
this.refreshAll();
this.startDurationTimer();
},
beforeUnmount() {
this.destroyH5HlsPlayer();
this.stopPolling();
this.stopDurationTimer();
},
methods: {
parseDateTime(value) {
if (!value) return null;
if (typeof value === "string" && value.includes("-")) {
return new Date(value.replace(/-/g, "/")).getTime();
}
if (typeof value === "number") {
return value > 1e12 ? value : value * 1000;
}
return null;
},
formatDuration(ms) {
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const pad = (n) => String(n).padStart(2, "0");
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
},
startDurationTimer() {
this.stopDurationTimer();
this.durationTimer = setInterval(() => {
this.nowTick = Date.now();
}, 1000);
},
stopDurationTimer() {
if (this.durationTimer) {
clearInterval(this.durationTimer);
this.durationTimer = null;
}
},
formatTime(time) {
if (!time) return "-";
if (typeof time === "string" && time.includes("-")) return time;
const ts = typeof time === "number" ? (time > 1e12 ? time / 1000 : time) : time;
return this.$filters.unixToDate(ts);
},
formatAmount(val) {
return Number(val || 0).toFixed(2);
},
liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", PAUSED: "已暂停", ENDED: "已结束" };
return map[status] || "未知";
},
liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", PAUSED: "warning", ENDED: "warning" };
return map[status] || "info";
},
startPolling() {
this.stopPolling();
if (!this.isLiving) return;
this.pollTimer = setInterval(() => {
this.loadDetail(false);
}, 5000);
},
stopPolling() {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
},
refreshAll() {
this.loadDetail();
this.loadOrderStats();
this.$refs.purchaseRecords?.refresh();
if (this.chatTab === "online") this.loadOnlineUsers();
},
buildHlsPlayUrl(url, useHttpFallback = false) {
if (!url) return "";
const httpUrl =
useHttpFallback && url.startsWith("https://")
? url.replace("https://", "http://")
: url;
return httpUrl.replace(".flv", ".m3u8");
},
syncH5Player(retry = 0) {
const shouldPlay = this.shouldPlayStream;
if (!shouldPlay) {
this.destroyH5HlsPlayer();
this.lastHlsPullUrl = "";
return;
}
if (
this.videoUrl === this.lastHlsPullUrl &&
this.hlsInstance &&
this.hlsVideoElement &&
!this.hlsVideoElement.paused
) {
return;
}
if (this.videoUrl === this.lastHlsPullUrl && this.hlsInstance) {
this.ensureH5VideoPlaying(this.hlsVideoElement);
return;
}
this.lastHlsPullUrl = this.videoUrl;
this.hlsHttpFallbackUsed = false;
this.$nextTick(() => {
const container = this.$refs.hlsContainer;
if (!container) {
if (retry < 8) {
setTimeout(() => this.syncH5Player(retry + 1), 80);
initH5HlsPlayer();
});
}
function ensureH5VideoPlaying(video) {
if (!video) return Promise.resolve();
const tryPlay = (muted) => {
video.muted = muted;
return video.play().catch(() => Promise.reject());
};
return tryPlay(false)
.catch(() => tryPlay(true))
.catch(() => {
console.warn("[HLS] 自动播放被阻止,请点击画面播放");
});
}
async function initH5HlsPlayer(useHttpFallback = false) {
const originalUrl = videoUrl.value;
const container = hlsContainer.value;
if (!originalUrl || !container) return;
const url = buildHlsPlayUrl(originalUrl, useHttpFallback);
destroyH5HlsPlayer();
const video = document.createElement("video");
video.style.cssText = "width:100%;height:100%;object-fit:cover;background:#000;";
video.autoplay = true;
video.muted = true;
video.defaultMuted = true;
video.playsInline = true;
video.setAttribute("autoplay", "");
video.setAttribute("muted", "");
video.setAttribute("playsinline", "");
video.setAttribute("webkit-playsinline", "");
video.controls = false;
const onVideoClick = () => {
if (video.muted) {
video.muted = false;
}
video.play().catch(() => {});
};
video.addEventListener("click", onVideoClick);
container.appendChild(video);
hlsVideoElement = video;
_hlsVideoClickHandler = onVideoClick;
const onReadyPlay = () => {
ensureH5VideoPlaying(video);
};
let Hls = null;
try {
const hlsModule = await import("hls.js");
Hls = hlsModule.default;
} catch (error) {
console.error("[HLS] 加载播放器失败:", error);
Message.error("直播播放器加载失败,请刷新页面重试");
return;
}
if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
liveSyncDurationCount: 3,
liveMaxLatencyDurationCount: 6,
maxBufferLength: 10,
maxMaxBufferLength: 30,
});
hlsInstance = hls;
hls.on(Hls.Events.MEDIA_ATTACHED, onReadyPlay);
hls.on(Hls.Events.MANIFEST_PARSED, onReadyPlay);
hls.attachMedia(video);
hls.loadSource(url);
hls.on(Hls.Events.ERROR, (_event, data) => {
if (!data.fatal) return;
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
if (!hlsHttpFallbackUsed.value && originalUrl.startsWith("https://")) {
console.warn("[HLS] HTTPS 加载失败,尝试 HTTP 降级...");
hlsHttpFallbackUsed.value = true;
destroyH5HlsPlayer();
nextTick(() => initH5HlsPlayer(true));
} else {
console.error("[HLS] 网络错误,尝试重连...");
hls.startLoad();
}
return;
}
this.initH5HlsPlayer();
});
},
ensureH5VideoPlaying(video) {
if (!video) return Promise.resolve();
const tryPlay = (muted) => {
video.muted = muted;
return video.play().catch(() => Promise.reject());
};
return tryPlay(false)
.catch(() => tryPlay(true))
.catch(() => {
console.warn("[HLS] 自动播放被阻止,请点击画面播放");
});
},
async initH5HlsPlayer(useHttpFallback = false) {
const originalUrl = this.videoUrl;
const container = this.$refs.hlsContainer;
if (!originalUrl || !container) return;
const url = this.buildHlsPlayUrl(originalUrl, useHttpFallback);
this.destroyH5HlsPlayer();
const video = document.createElement("video");
video.style.cssText = "width:100%;height:100%;object-fit:cover;background:#000;";
video.autoplay = true;
video.muted = true;
video.defaultMuted = true;
video.playsInline = true;
video.setAttribute("autoplay", "");
video.setAttribute("muted", "");
video.setAttribute("playsinline", "");
video.setAttribute("webkit-playsinline", "");
video.controls = false;
const onVideoClick = () => {
if (video.muted) {
video.muted = false;
}
video.play().catch(() => {});
};
video.addEventListener("click", onVideoClick);
container.appendChild(video);
this.hlsVideoElement = video;
this._hlsVideoClickHandler = onVideoClick;
const onReadyPlay = () => {
this.ensureH5VideoPlaying(video);
};
let Hls = null;
try {
const hlsModule = await import("hls.js");
Hls = hlsModule.default;
} catch (error) {
console.error("[HLS] 加载播放器失败:", error);
this.$Message.error("直播播放器加载失败,请刷新页面重试");
return;
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.error("[HLS] 媒体错误,尝试恢复...");
hls.recoverMediaError();
break;
default:
console.error("[HLS] 致命错误:", data);
destroyH5HlsPlayer();
Message.error("直播加载失败");
break;
}
if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
liveSyncDurationCount: 3,
liveMaxLatencyDurationCount: 6,
maxBufferLength: 10,
maxMaxBufferLength: 30,
});
this.hlsInstance = hls;
hls.on(Hls.Events.MEDIA_ATTACHED, onReadyPlay);
hls.on(Hls.Events.MANIFEST_PARSED, onReadyPlay);
hls.attachMedia(video);
hls.loadSource(url);
hls.on(Hls.Events.ERROR, (_event, data) => {
if (!data.fatal) return;
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
if (!this.hlsHttpFallbackUsed && originalUrl.startsWith("https://")) {
console.warn("[HLS] HTTPS 加载失败,尝试 HTTP 降级...");
this.hlsHttpFallbackUsed = true;
this.destroyH5HlsPlayer();
this.$nextTick(() => this.initH5HlsPlayer(true));
} else {
console.error("[HLS] 网络错误,尝试重连...");
hls.startLoad();
}
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.error("[HLS] 媒体错误,尝试恢复...");
hls.recoverMediaError();
break;
default:
console.error("[HLS] 致命错误:", data);
this.destroyH5HlsPlayer();
this.$Message.error("直播加载失败");
break;
}
});
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = url;
video.addEventListener("loadedmetadata", onReadyPlay);
video.addEventListener("canplay", onReadyPlay);
} else {
this.$Message.error("当前浏览器不支持 HLS 播放");
});
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = url;
video.addEventListener("loadedmetadata", onReadyPlay);
video.addEventListener("canplay", onReadyPlay);
} else {
Message.error("当前浏览器不支持 HLS 播放");
}
}
function destroyH5HlsPlayer() {
if (hlsInstance) {
hlsInstance.destroy();
hlsInstance = null;
}
if (hlsVideoElement) {
if (_hlsVideoClickHandler) {
hlsVideoElement.removeEventListener("click", _hlsVideoClickHandler);
_hlsVideoClickHandler = null;
}
hlsVideoElement.remove();
hlsVideoElement = null;
}
const container = hlsContainer.value;
if (container) {
container.innerHTML = "";
}
}
function loadDetail(showLoading = true) {
if (showLoading) loading.value = true;
getLiveInfo(liveId.value)
.then((res) => {
if (showLoading) loading.value = false;
if (res.success) {
const data = res.result || {};
liveDetail.value = data;
videoUrl.value = data.pullStreamUrl || data.streamUrl || "";
syncH5Player();
}
},
destroyH5HlsPlayer() {
if (this.hlsInstance) {
this.hlsInstance.destroy();
this.hlsInstance = null;
})
.catch(() => {
if (showLoading) loading.value = false;
});
}
function loadOrderStats() {
getLiveOrderStatistics(liveId.value).then((res) => {
if (res.success) {
orderStats.value = res.result || {};
}
});
}
function loadOnlineUsers() {
onlineLoading.value = true;
liveUserList({
liveRoomId: liveId.value,
pageNumber: onlinePagination.pageNumber,
pageSize: onlinePagination.pageSize,
})
.then((res) => {
onlineLoading.value = false;
if (res.success) {
onlineUserList.value = res.result?.records || [];
onlinePagination.total = res.result?.total || 0;
}
if (this.hlsVideoElement) {
if (this._hlsVideoClickHandler) {
this.hlsVideoElement.removeEventListener("click", this._hlsVideoClickHandler);
this._hlsVideoClickHandler = null;
}
this.hlsVideoElement.remove();
this.hlsVideoElement = null;
}
const container = this.$refs.hlsContainer;
if (container) {
container.innerHTML = "";
}
},
loadDetail(showLoading = true) {
if (showLoading) this.loading = true;
getLiveInfo(this.liveId)
.then((res) => {
if (showLoading) this.loading = false;
if (res.success) {
const data = res.result || {};
this.liveDetail = data;
this.videoUrl = data.pullStreamUrl || data.streamUrl || "";
this.syncH5Player();
}
})
.catch(() => {
if (showLoading) this.loading = false;
});
},
loadOrderStats() {
getLiveOrderStatistics(this.liveId).then((res) => {
})
.catch(() => {
onlineLoading.value = false;
});
}
function buildShareUrl() {
const base = (wapUrl.value || BASE?.WAP_URL || config.website || "").replace(/\/?$/, "/");
return `${base}pages/promotion/live/room?id=${liveId.value}`;
}
function handleShare() {
const url = buildShareUrl();
if (!url) {
Message.warning("未配置移动端地址");
return;
}
copyText(url, "分享链接");
}
function copyText(text, label = "内容") {
if (!text) {
Message.warning(`${label}为空,无法复制`);
return;
}
navigator.clipboard.writeText(text);
Message.success(`${label}已复制`);
}
function handleStart() {
Modal.confirm({
title: "确认开播",
content: "确定要开始该直播吗?",
onOk: () => {
return startLive(liveId.value).then((res) => {
if (res.success) {
this.orderStats = res.result || {};
Message.success("开播成功");
refreshAll();
}
});
},
loadOnlineUsers() {
this.onlineLoading = true;
liveUserList({
liveRoomId: this.liveId,
pageNumber: this.onlinePagination.pageNumber,
pageSize: this.onlinePagination.pageSize,
})
.then((res) => {
this.onlineLoading = false;
if (res.success) {
this.onlineUserList = res.result?.records || [];
this.onlinePagination.total = res.result?.total || 0;
}
})
.catch(() => {
this.onlineLoading = false;
});
},
buildShareUrl() {
const base = (this.wapUrl || BASE?.WAP_URL || config.website || "").replace(/\/?$/, "/");
return `${base}pages/promotion/live/room?id=${this.liveId}`;
},
handleShare() {
const url = this.buildShareUrl();
if (!url) {
this.$Message.warning("未配置移动端地址");
return;
}
this.copyText(url, "分享链接");
},
copyText(text, label = "内容") {
if (!text) {
this.$Message.warning(`${label}为空,无法复制`);
return;
}
navigator.clipboard.writeText(text);
this.$Message.success(`${label}已复制`);
},
handleStart() {
this.$Modal.confirm({
title: "确认开播",
content: "确定要开始该直播吗?",
onOk: () => {
return startLive(this.liveId).then((res) => {
if (res.success) {
this.$Message.success("开播成功");
this.refreshAll();
}
});
},
});
}
function handleEnd() {
Modal.confirm({
title: "确认结束",
content: "确定要结束该直播吗?",
onOk: () => {
return endLive(liveId.value).then((res) => {
if (res.success) {
Message.success("直播已结束");
destroyH5HlsPlayer();
stopPolling();
refreshAll();
}
});
},
handleEnd() {
this.$Modal.confirm({
title: "确认结束",
content: "确定要结束该直播吗?",
onOk: () => {
return endLive(this.liveId).then((res) => {
if (res.success) {
this.$Message.success("直播已结束");
this.destroyH5HlsPlayer();
this.stopPolling();
this.refreshAll();
}
});
},
});
},
},
};
});
}
</script>
<style lang="scss" scoped>

View File

@@ -42,66 +42,66 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { getLiveInfo } from "@/api/live";
import { Message } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
import LiveGoods from "./components/LiveGoods.vue";
export default {
name: "live-detail",
components: { LiveGoods },
data() {
return {
loading: false,
liveId: "",
detail: {},
};
},
computed: {
liveStatus() {
return this.detail.liveStatus || this.detail.status || "";
},
},
mounted() {
this.liveId = String(this.$route.query.id || "");
if (!this.liveId) {
this.$Message.error("缺少直播ID");
this.handleBack();
return;
}
this.loadDetail();
},
methods: {
formatTime(time) {
if (!time) return "-";
return this.$filters.unixToDate(time);
},
liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", ENDED: "已结束" };
return map[status] || "未知";
},
liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", ENDED: "warning" };
return map[status] || "info";
},
loadDetail() {
this.loading = true;
getLiveInfo(this.liveId)
.then((res) => {
this.loading = false;
if (res.success) {
this.detail = res.result || {};
}
})
.catch(() => {
this.loading = false;
this.handleBack();
});
},
handleBack() {
this.$router.push({ path: "/live-list" });
},
},
};
defineOptions({ name: "live-detail" });
const route = useRoute();
const router = useRouter();
const loading = ref(false);
const liveId = ref("");
const detail = ref({});
const liveStatus = computed(() => {
return detail.value.liveStatus || detail.value.status || "";
});
onMounted(() => {
liveId.value = String(route.query.id || "");
if (!liveId.value) {
Message.error("缺少直播ID");
handleBack();
return;
}
loadDetail();
});
function formatTime(time) {
if (!time) return "-";
return unixToDate(time);
}
function liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", ENDED: "已结束" };
return map[status] || "未知";
}
function liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", ENDED: "warning" };
return map[status] || "info";
}
function loadDetail() {
loading.value = true;
getLiveInfo(liveId.value)
.then((res) => {
loading.value = false;
if (res.success) {
detail.value = res.result || {};
}
})
.catch(() => {
loading.value = false;
handleBack();
});
}
function handleBack() {
router.push({ path: "/live-list" });
}
</script>
<style lang="scss" scoped>

View File

@@ -4,7 +4,7 @@
<template #header>
<span class="page-title">编辑直播</span>
</template>
<el-form ref="form" :model="form" :rules="rules" label-width="100px" class="live-form">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="live-form">
<el-form-item v-if="form.liveStatus !== 'NEW'" label="直播状态">
<el-tag :type="liveStatusTagType(form.liveStatus)">{{ liveStatusText(form.liveStatus) }}</el-tag>
</el-form-item>
@@ -81,7 +81,7 @@
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
ref="ossManage"
ref="ossManageRef"
:is-component="true"
:initialize="picModelFlag"
@callback="callbackSelected"
@@ -90,11 +90,22 @@
</div>
</template>
<script>
<script setup>
import { nextTick, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Picture } from "@element-plus/icons-vue";
import { getLiveInfo, updateLive } from "@/api/live";
import { Message } from "@/utils/message";
import ossManage from "@/views/sys/oss-manage/ossManage";
defineOptions({ name: "live-edit" });
const route = useRoute();
const router = useRouter();
const formRef = ref(null);
const ossManageRef = ref(null);
function getCharLength(str) {
if (!str) return 0;
let len = 0;
@@ -130,133 +141,126 @@ function formatDateTime(value) {
return String(value);
}
export default {
name: "live-edit",
components: { Picture, ossManage },
data() {
const validateTitle = (rule, value, callback) => {
const len = getCharLength(value);
if (len < 6) {
callback(new Error("直播间名字最短3个汉字"));
return;
}
if (len > 34) {
callback(new Error("直播间名字最长17个汉字"));
return;
}
callback();
};
return {
picModelFlag: false,
loading: false,
submitting: false,
liveId: "",
endTime: "",
form: {
title: "",
displayMode: "0",
startTime: "",
coverImg: "",
liveIntroduce: "",
liveStatus: "NEW",
},
rules: {
title: [
{ required: true, message: "请输入直播标题", trigger: "blur" },
{ validator: validateTitle, trigger: "blur" },
],
displayMode: [{ required: true, message: "请选择显示模式", trigger: "change" }],
startTime: [{ required: true, message: "请选择直播开始时间", trigger: "change" }],
coverImg: [{ required: true, message: "请上传直播封面", trigger: "change" }],
},
};
},
mounted() {
this.liveId = String(this.$route.query.id || "");
if (!this.liveId) {
this.$Message.error("缺少直播ID");
this.handleBack();
return;
}
this.loadDetail();
},
methods: {
liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", ENDED: "已结束" };
return map[status] || "未知";
},
liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", ENDED: "warning" };
return map[status] || "info";
},
openCoverPicker() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
callbackSelected(val) {
this.picModelFlag = false;
if (val?.url) {
this.form.coverImg = val.url;
this.$refs.form?.validateField("coverImg");
}
},
loadDetail() {
this.loading = true;
getLiveInfo(this.liveId)
.then((res) => {
this.loading = false;
if (res.success) {
const data = res.result || {};
this.form.title = data.title || "";
this.form.displayMode = normalizeDisplayMode(data.displayMode);
this.form.coverImg = data.coverImg || "";
this.form.liveIntroduce = data.liveIntroduce || "";
this.form.liveStatus = data.liveStatus || data.status || "NEW";
this.endTime = formatDateTime(data.endTime);
this.form.startTime = formatDateTime(data.startTime);
}
})
.catch(() => {
this.loading = false;
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
let endTime = this.endTime;
if (!endTime || endTime <= this.form.startTime) {
endTime = buildEndTime(this.form.startTime);
}
this.submitting = true;
updateLive(this.liveId, {
title: this.form.title,
displayMode: this.form.displayMode,
coverImg: this.form.coverImg,
startTime: this.form.startTime,
endTime,
liveIntroduce: this.form.liveIntroduce,
})
.then((res) => {
this.submitting = false;
if (res.success) {
this.$Message.success("保存成功");
this.$router.push({ path: "/live-list" });
}
})
.catch(() => {
this.submitting = false;
});
});
},
handleBack() {
this.$router.push({ path: "/live-list" });
},
},
const validateTitle = (rule, value, callback) => {
const len = getCharLength(value);
if (len < 6) {
callback(new Error("直播间名字最短3个汉字"));
return;
}
if (len > 34) {
callback(new Error("直播间名字最长17个汉字"));
return;
}
callback();
};
const picModelFlag = ref(false);
const loading = ref(false);
const submitting = ref(false);
const liveId = ref("");
const endTime = ref("");
const form = reactive({
title: "",
displayMode: "0",
startTime: "",
coverImg: "",
liveIntroduce: "",
liveStatus: "NEW",
});
const rules = reactive({
title: [
{ required: true, message: "请输入直播标题", trigger: "blur" },
{ validator: validateTitle, trigger: "blur" },
],
displayMode: [{ required: true, message: "请选择显示模式", trigger: "change" }],
startTime: [{ required: true, message: "请选择直播开始时间", trigger: "change" }],
coverImg: [{ required: true, message: "请上传直播封面", trigger: "change" }],
});
onMounted(() => {
liveId.value = String(route.query.id || "");
if (!liveId.value) {
Message.error("缺少直播ID");
handleBack();
return;
}
loadDetail();
});
function liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", ENDED: "已结束" };
return map[status] || "未知";
}
function liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", ENDED: "warning" };
return map[status] || "info";
}
function openCoverPicker() {
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function callbackSelected(val) {
picModelFlag.value = false;
if (val?.url) {
form.coverImg = val.url;
formRef.value?.validateField("coverImg");
}
}
function loadDetail() {
loading.value = true;
getLiveInfo(liveId.value)
.then((res) => {
loading.value = false;
if (res.success) {
const data = res.result || {};
form.title = data.title || "";
form.displayMode = normalizeDisplayMode(data.displayMode);
form.coverImg = data.coverImg || "";
form.liveIntroduce = data.liveIntroduce || "";
form.liveStatus = data.liveStatus || data.status || "NEW";
endTime.value = formatDateTime(data.endTime);
form.startTime = formatDateTime(data.startTime);
}
})
.catch(() => {
loading.value = false;
});
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (!valid) return;
let endTimeVal = endTime.value;
if (!endTimeVal || endTimeVal <= form.startTime) {
endTimeVal = buildEndTime(form.startTime);
}
submitting.value = true;
updateLive(liveId.value, {
title: form.title,
displayMode: form.displayMode,
coverImg: form.coverImg,
startTime: form.startTime,
endTime: endTimeVal,
liveIntroduce: form.liveIntroduce,
})
.then((res) => {
submitting.value = false;
if (res.success) {
Message.success("保存成功");
router.push({ path: "/live-list" });
}
})
.catch(() => {
submitting.value = false;
});
});
}
function handleBack() {
router.push({ path: "/live-list" });
}
</script>
<style lang="scss" scoped>

View File

@@ -2,7 +2,7 @@
<div class="live-list search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="90px"
@@ -135,202 +135,206 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { queryLivePage, startLive, endLive } from "@/api/live";
import { getSetting } from "@/api/index";
import { Message, Modal } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
import config from "@/config";
export default {
name: "live-list",
data() {
return {
loading: false,
data: [],
total: 0,
activeStatus: "",
searchForm: {
pageNumber: 1,
pageSize: 20,
title: "",
storeName: "",
},
shareVisible: false,
shareTitle: "",
shareH5Url: "",
wapUrl: "",
};
},
mounted() {
this.loadWapSetting();
this.getData();
},
methods: {
formatTime(time) {
if (!time) return "-";
return this.$filters.unixToDate(time);
},
liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", ENDED: "已结束" };
return map[status] || "未知";
},
liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", ENDED: "warning" };
return map[status] || "info";
},
buildParams() {
const params = {
pageNumber: this.searchForm.pageNumber,
pageSize: this.searchForm.pageSize,
};
if (this.searchForm.title) params.title = this.searchForm.title;
if (this.searchForm.storeName) params.storeName = this.searchForm.storeName;
if (this.activeStatus) params.liveStatus = this.activeStatus;
return params;
},
getData() {
this.loading = true;
queryLivePage(this.buildParams())
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result?.records || [];
this.total = res.result?.total || 0;
}
})
.catch(() => {
this.loading = false;
});
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getData();
},
handleReset() {
this.searchForm.title = "";
this.searchForm.storeName = "";
this.searchForm.pageNumber = 1;
this.getData();
},
handleTabChange() {
this.searchForm.pageNumber = 1;
this.getData();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getData();
},
handleAdd() {
this.$router.push({ path: "/live-add" });
},
handleView(row) {
this.$router.push({ path: "/live-manage-detail", query: { id: row.id } });
},
handleEdit(row) {
this.$router.push({ path: "/live-edit", query: { id: row.id } });
},
handleControl(row) {
const route = this.$router.resolve({
path: "/live-control-panel",
query: { id: row.id },
});
window.open(route.href, "_blank");
},
buildH5ShareUrl(liveId) {
const base = (this.wapUrl || BASE?.WAP_URL || config.website || "").replace(/\/?$/, "/");
return `${base}pages/promotion/live/room?id=${liveId}`;
},
handleShare(row) {
this.shareTitle = row.title || "";
this.shareH5Url = this.buildH5ShareUrl(row.id);
this.shareVisible = true;
},
copyShareUrl() {
if (!this.shareH5Url) return;
this.copyText(this.shareH5Url);
},
copyText(text) {
const onSuccess = () => this.$Message.success("链接已复制");
const onFail = () => this.$Message.error("复制失败,请手动复制");
defineOptions({ name: "live-list" });
if (navigator.clipboard?.writeText) {
navigator.clipboard
.writeText(text)
.then(onSuccess)
.catch(() => {
this.copyTextFallback(text) ? onSuccess() : onFail();
});
return;
}
const router = useRouter();
this.copyTextFallback(text) ? onSuccess() : onFail();
},
copyTextFallback(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed";
textArea.style.left = "-9999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
let ok = false;
try {
ok = document.execCommand("copy");
} catch (e) {
ok = false;
const searchFormRef = ref(null);
const loading = ref(false);
const data = ref([]);
const total = ref(0);
const activeStatus = ref("");
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
title: "",
storeName: "",
});
const shareVisible = ref(false);
const shareTitle = ref("");
const shareH5Url = ref("");
const wapUrl = ref("");
onMounted(() => {
loadWapSetting();
getData();
});
function formatTime(time) {
if (!time) return "-";
return unixToDate(time);
}
function liveStatusText(status) {
const map = { NEW: "未开始", LIVING: "直播中", ENDED: "已结束" };
return map[status] || "未知";
}
function liveStatusTagType(status) {
const map = { NEW: "info", LIVING: "success", ENDED: "warning" };
return map[status] || "info";
}
function buildParams() {
const params = {
pageNumber: searchForm.pageNumber,
pageSize: searchForm.pageSize,
};
if (searchForm.title) params.title = searchForm.title;
if (searchForm.storeName) params.storeName = searchForm.storeName;
if (activeStatus.value) params.liveStatus = activeStatus.value;
return params;
}
function getData() {
loading.value = true;
queryLivePage(buildParams())
.then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result?.records || [];
total.value = res.result?.total || 0;
}
document.body.removeChild(textArea);
return ok;
},
handleStart(row) {
if (row.liveStatus === "LIVING") {
this.$Message.warning("直播间已在直播中");
return;
}
if (row.liveStatus === "ENDED") {
this.$Message.warning("直播已结束,无法开播");
return;
}
this.$Modal.confirm({
title: "确认开播",
content: "确定要开始该直播吗?",
onOk: () => {
return startLive(row.id).then((res) => {
if (res.success) {
this.$Message.success("开播成功");
this.getData();
}
});
},
})
.catch(() => {
loading.value = false;
});
}
function handleSearch() {
searchForm.pageNumber = 1;
getData();
}
function handleReset() {
searchForm.title = "";
searchForm.storeName = "";
searchForm.pageNumber = 1;
getData();
}
function handleTabChange() {
searchForm.pageNumber = 1;
getData();
}
function changePageSize() {
searchForm.pageNumber = 1;
getData();
}
function handleAdd() {
router.push({ path: "/live-add" });
}
function handleView(row) {
router.push({ path: "/live-manage-detail", query: { id: row.id } });
}
function handleEdit(row) {
router.push({ path: "/live-edit", query: { id: row.id } });
}
function handleControl(row) {
const route = router.resolve({
path: "/live-control-panel",
query: { id: row.id },
});
window.open(route.href, "_blank");
}
function buildH5ShareUrl(liveId) {
// 历史遗留:BASE 未定义,wapUrl 为空时会抛 ReferenceError,保持原样
const base = (wapUrl.value || BASE?.WAP_URL || config.website || "").replace(/\/?$/, "/");
return `${base}pages/promotion/live/room?id=${liveId}`;
}
function handleShare(row) {
shareTitle.value = row.title || "";
shareH5Url.value = buildH5ShareUrl(row.id);
shareVisible.value = true;
}
function copyShareUrl() {
if (!shareH5Url.value) return;
copyText(shareH5Url.value);
}
function copyText(text) {
const onSuccess = () => Message.success("链接已复制");
const onFail = () => Message.error("复制失败,请手动复制");
if (navigator.clipboard?.writeText) {
navigator.clipboard
.writeText(text)
.then(onSuccess)
.catch(() => {
copyTextFallback(text) ? onSuccess() : onFail();
});
return;
}
copyTextFallback(text) ? onSuccess() : onFail();
}
function copyTextFallback(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed";
textArea.style.left = "-9999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
let ok = false;
try {
ok = document.execCommand("copy");
} catch (e) {
ok = false;
}
document.body.removeChild(textArea);
return ok;
}
function handleStart(row) {
if (row.liveStatus === "LIVING") {
Message.warning("直播间已在直播中");
return;
}
if (row.liveStatus === "ENDED") {
Message.warning("直播已结束,无法开播");
return;
}
Modal.confirm({
title: "确认开播",
content: "确定要开始该直播吗?",
onOk: () => {
return startLive(row.id).then((res) => {
if (res.success) {
Message.success("开播成功");
getData();
}
});
},
handleEnd(row) {
if (row.liveStatus !== "LIVING") {
this.$Message.warning("仅直播中的直播间可以结束");
return;
}
this.$Modal.confirm({
title: "确认结束",
content: "确定要结束该直播吗?",
onOk: () => {
return endLive(row.id).then((res) => {
if (res.success) {
this.$Message.success("直播已结束");
this.getData();
}
});
},
});
}
function handleEnd(row) {
if (row.liveStatus !== "LIVING") {
Message.warning("仅直播中的直播间可以结束");
return;
}
Modal.confirm({
title: "确认结束",
content: "确定要结束该直播吗?",
onOk: () => {
return endLive(row.id).then((res) => {
if (res.success) {
Message.success("直播已结束");
getData();
}
});
},
loadWapSetting() {
getSetting("BASE_SETTING")
.then((res) => {
if (res.success && res.result?.buyerDomain) {
this.wapUrl = res.result.buyerDomain;
}
});
},
},
};
});
}
function loadWapSetting() {
getSetting("BASE_SETTING")
.then((res) => {
if (res.success && res.result?.buyerDomain) {
wapUrl.value = res.result.buyerDomain;
}
});
}
</script>
<style lang="scss" scoped>

View File

@@ -1,5 +1,5 @@
<template>
<div class="login" @click="$refs.verify.show = false">
<div class="login" @click="hideVerify">
<div class="login-brand">
<div class="brand-inner">
<p class="brand-name">LILISHOP</p>
@@ -24,7 +24,7 @@
</div>
<el-form
ref="usernameLoginForm"
ref="usernameLoginFormRef"
:model="form"
:rules="rules"
class="form"
@@ -70,7 +70,7 @@
</el-button>
<verify
ref="verify"
ref="verifyRef"
class="verify-con"
verifyType="LOGIN"
@change="verifyChange"
@@ -81,7 +81,9 @@
</div>
</template>
<script>
<script setup>
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { User, Lock } from "@element-plus/icons-vue";
import { login, userInfo } from "@/api/index";
import Cookies from "js-cookie";
@@ -89,82 +91,79 @@ import Header from "@/views/main-parts/header";
import Footer from "@/views/main-parts/footer";
import util from "@/libs/util.js";
import verify from "@/components/verify";
import { setStore } from "@/libs/storage";
import { md5 } from "@/utils/md5.js";
import { useAppStore } from "@/stores/app";
export default {
components: {
Header,
Footer,
verify,
User,
Lock,
},
data() {
return {
year: new Date().getFullYear(),
loading: false,
form: {
username: "",
password: "",
mobile: "",
code: "",
},
rules: {
username: [
{ required: true, message: "账号不能为空", trigger: "blur" },
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
],
},
};
},
methods: {
afterLogin(res) {
const accessToken = res.result.accessToken;
const refreshToken = res.result.refreshToken;
this.setStore("accessToken", accessToken);
this.setStore("refreshToken", refreshToken);
userInfo().then((res) => {
if (res.success) {
Cookies.set("userInfoManager", JSON.stringify(res.result));
this.$store.commit("setAvatarPath", res.result.avatar);
util.initRouter(this);
this.$store.commit("setOpenedList");
this.$store.commit("initCachePage");
this.$router.push({ name: "home_index" });
} else {
this.loading = false;
}
});
},
submitLogin() {
this.$refs.usernameLoginForm.validate((valid) => {
if (valid) {
this.$refs.verify.init();
}
});
},
verifyChange(con) {
if (!con.status) return;
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) => {
if (res && res.success) {
this.afterLogin(res);
} else {
this.loading = false;
}
})
.catch(() => {
this.loading = false;
});
this.$refs.verify.show = false;
},
},
const router = useRouter();
const appStore = useAppStore();
const year = ref(new Date().getFullYear());
const loading = ref(false);
const form = reactive({
username: "",
password: "",
mobile: "",
code: "",
});
const rules = {
username: [{ required: true, message: "账号不能为空", trigger: "blur" }],
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
};
const usernameLoginFormRef = ref(null);
const verifyRef = ref(null);
function hideVerify() {
verifyRef.value.show = false;
}
function afterLogin(res) {
const accessToken = res.result.accessToken;
const refreshToken = res.result.refreshToken;
setStore("accessToken", accessToken);
setStore("refreshToken", refreshToken);
userInfo().then((res) => {
if (res.success) {
Cookies.set("userInfoManager", JSON.stringify(res.result));
appStore.setAvatarPath(res.result.avatar);
util.initRouter();
appStore.setOpenedList();
appStore.initCachePage();
router.push({ name: "home_index" });
} else {
loading.value = false;
}
});
}
function submitLogin() {
usernameLoginFormRef.value.validate((valid) => {
if (valid) {
verifyRef.value.init();
}
});
}
function verifyChange(con) {
if (!con.status) return;
loading.value = true;
const fd = new FormData();
fd.append("username", form.username);
fd.append("password", md5(form.password));
login(fd)
.then((res) => {
if (res && res.success) {
afterLogin(res);
} else {
loading.value = false;
}
})
.catch(() => {
loading.value = false;
});
verifyRef.value.show = false;
}
</script>
<style lang="scss" scoped>

View File

@@ -59,7 +59,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="form" :model="form" :rules="formValidate" label-width="120px">
<el-form ref="formRef" :model="form" :rules="formValidate" label-width="120px">
<el-form-item label="物流公司名称" prop="name">
<el-input v-model="form.name" clearable style="width: 100%" />
</el-form-item>
@@ -97,13 +97,17 @@
</div>
</template>
<script>
<script setup>
import { nextTick, onMounted, reactive, ref } from "vue";
import {
getLogisticsPage,
updateLogistics,
addLogistics,
delLogistics,
} from "@/api/logistics";
import { Message, Modal } from "@/utils/message";
defineOptions({ name: "logistics" });
const defaultForm = () => ({
name: "",
@@ -114,156 +118,152 @@ const defaultForm = () => ({
const normalizeStandBy = (value) => (value === true || value === "true" ? "true" : "false");
export default {
name: "logistics",
data() {
return {
loading: true,
modalVisible: false,
modalTitle: "",
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
name: "",
},
form: defaultForm(),
formValidate: {
name: [
{
required: true,
message: "请输入物流公司名称",
trigger: "blur",
},
],
code: [
{
required: true,
message: "请输入物流公司编码",
trigger: "blur",
},
],
},
submitLoading: false,
data: [],
total: 0,
id: "",
};
},
methods: {
init() {
this.getDataList();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
getDataList() {
this.loading = true;
getLogisticsPage(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
const data = res.result.records;
data.forEach((e) => {
e.switch = e.disabled === "OPEN";
e.standBy = normalizeStandBy(e.standBy);
});
this.data = data;
this.total = res.result.total;
}
});
},
changeSwitch(row) {
updateLogistics(row.id, {
name: row.name,
code: row.code,
standBy: normalizeStandBy(row.standBy),
disabled: row.disabled === "CLOSE" ? "OPEN" : "CLOSE",
}).then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
} else {
row.switch = !row.switch;
}
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
const table = ref(null);
const formRef = ref(null);
if (this.modalTitle === "添加") {
const payload = {
name: this.form.name,
code: this.form.code,
disabled: this.form.disabled,
standBy: normalizeStandBy(this.form.standBy),
formItems: this.form.formItems,
};
addLogistics(payload).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
updateLogistics(this.id, {
...this.form,
standBy: normalizeStandBy(this.form.standBy),
}).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
const loading = ref(true);
const modalVisible = ref(false);
const modalTitle = ref("");
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
name: "",
});
const form = ref(defaultForm());
const formValidate = reactive({
name: [
{
required: true,
message: "请输入物流公司名称",
trigger: "blur",
},
],
code: [
{
required: true,
message: "请输入物流公司编码",
trigger: "blur",
},
],
});
const submitLoading = ref(false);
const data = ref([]);
const total = ref(0);
const id = ref("");
function init() {
getDataList();
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.pageNumber = 1;
getDataList();
}
function getDataList() {
loading.value = true;
getLogisticsPage(searchForm).then((res) => {
loading.value = false;
if (res.success) {
const records = res.result.records;
records.forEach((e) => {
e.switch = e.disabled === "OPEN";
e.standBy = normalizeStandBy(e.standBy);
});
data.value = records;
total.value = res.result.total;
}
});
}
function changeSwitch(row) {
updateLogistics(row.id, {
name: row.name,
code: row.code,
standBy: normalizeStandBy(row.standBy),
disabled: row.disabled === "CLOSE" ? "OPEN" : "CLOSE",
}).then((res) => {
if (res.success) {
Message.success("操作成功");
getDataList();
} else {
row.switch = !row.switch;
}
});
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (!valid) return;
submitLoading.value = true;
if (modalTitle.value === "添加") {
const payload = {
name: form.value.name,
code: form.value.code,
disabled: form.value.disabled,
standBy: normalizeStandBy(form.value.standBy),
formItems: form.value.formItems,
};
addLogistics(payload).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
} else {
updateLogistics(id.value, {
...form.value,
standBy: normalizeStandBy(form.value.standBy),
}).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}
});
}
function add() {
modalTitle.value = "添加";
form.value = defaultForm();
nextTick(() => {
formRef.value?.resetFields();
});
modalVisible.value = true;
}
function detail(v) {
id.value = v.id;
modalTitle.value = "修改";
form.value = {
name: v.name,
code: v.code,
standBy: normalizeStandBy(v.standBy),
disabled: v.disabled,
};
modalVisible.value = true;
}
function remove(v) {
Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.name + " ?",
onOk: () => {
return delLogistics(v.id).then((res) => {
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
add() {
this.modalTitle = "添加";
this.form = defaultForm();
this.$nextTick(() => {
this.$refs.form?.resetFields();
});
this.modalVisible = true;
},
detail(v) {
this.id = v.id;
this.modalTitle = "修改";
this.form = {
name: v.name,
code: v.code,
standBy: normalizeStandBy(v.standBy),
disabled: v.disabled,
};
this.modalVisible = true;
},
remove(v) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.name + " ?",
onOk: () => {
return delLogistics(v.id).then((res) => {
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
};
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -12,17 +12,11 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import config from "@/config/index";
export default {
data() {
return {
config,
year: new Date().getFullYear(),
};
},
};
const year = ref(new Date().getFullYear());
</script>
<style lang="scss" scoped>

View File

@@ -6,50 +6,46 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { getBaseSite } from "@/api/common.js";
export default {
data() {
return {
domainLogo: "",
};
},
methods: {
applyFavicon(href) {
if (!href) return;
let 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 && res.result.settingValue) {
const { domainLogo, domainIcon, siteName } = JSON.parse(
res.result.settingValue
);
this.domainLogo = domainLogo || "";
const expirationTime = new Date().setHours(new Date().getHours() + 1);
localStorage.setItem("icontitle_expiration_time", expirationTime);
localStorage.setItem("icon", domainLogo || "");
localStorage.setItem("domainIcon", domainIcon || "");
localStorage.setItem("title", siteName || "");
this.applyFavicon(domainIcon || domainLogo);
if (siteName) {
window.document.title = siteName + " - 运营后台";
}
}
});
},
},
mounted() {
this.getSite();
},
};
const domainLogo = ref("");
function applyFavicon(href) {
if (!href) return;
let 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 && res.result.settingValue) {
const { domainLogo: logo, domainIcon, siteName } = JSON.parse(
res.result.settingValue
);
domainLogo.value = logo || "";
const expirationTime = new Date().setHours(new Date().getHours() + 1);
localStorage.setItem("icontitle_expiration_time", expirationTime);
localStorage.setItem("icon", logo || "");
localStorage.setItem("domainIcon", domainIcon || "");
localStorage.setItem("title", siteName || "");
applyFavicon(domainIcon || logo);
if (siteName) {
window.document.title = siteName + " - 运营后台";
}
}
});
}
onMounted(() => {
getSite();
});
</script>
<style lang="scss" scoped>

View File

@@ -12,19 +12,19 @@
</div>
</template>
<script>
<script setup>
import { Platform } from "@element-plus/icons-vue";
import i18n from "@/locale";
import { useAppStore } from "@/stores/app";
export default {
name: "langSwitch",
components: { Platform },
methods: {
langChange(v) {
this.$i18n.locale = v;
this.$store.commit("switchLang", v);
},
},
};
defineOptions({ name: "langSwitch" });
const appStore = useAppStore();
function langChange(v) {
i18n.global.locale = v;
appStore.switchLang(v);
}
</script>
<style lang="scss" scoped>

View File

@@ -40,42 +40,42 @@
</div>
</template>
<script>
<script setup>
import { computed } from "vue";
import { useRouter } from "vue-router";
import { ArrowDown } from "@element-plus/icons-vue";
export default {
name: "messageTip",
components: { ArrowDown },
props: {
res: {
type: Object,
default: () => ({}),
},
defineOptions({ name: "messageTip" });
const props = defineProps({
res: {
type: Object,
default: () => ({}),
},
computed: {
value() {
let count = 0;
const r = this.res || {};
Object.keys(r).forEach((k) => {
if (r[k]) count += r[k];
});
return count;
},
},
methods: {
navigateTo(name) {
if (name === "manager-appointment-work-orders") {
this.$router.push({ name, query: { serviceStatus: "OPEN" } });
return;
}
if (name === "manager-procurement-order-list") {
this.$router.push({ name, query: { status: "SUBMITTED" } });
return;
}
this.$router.push({ name });
},
},
};
});
const router = useRouter();
const value = computed(() => {
let count = 0;
const r = props.res || {};
Object.keys(r).forEach((k) => {
if (r[k]) count += r[k];
});
return count;
});
function navigateTo(name) {
if (name === "manager-appointment-work-orders") {
router.push({ name, query: { serviceStatus: "OPEN" } });
return;
}
if (name === "manager-procurement-order-list") {
router.push({ name, query: { status: "SUBMITTED" } });
return;
}
router.push({ name });
}
</script>
<style scoped>
@@ -109,4 +109,3 @@ export default {
transform: translateY(-50%) translateX(50%);
}
</style>

View File

@@ -18,8 +18,17 @@
</el-breadcrumb>
</template>
<script>
<script setup>
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { otherRouter } from "@/router/router";
import { useAppStore } from "@/stores/app";
defineOptions({ name: "pageBreadcrumb" });
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const stripLayout = (name) => (name || "").replace(/__layout$/, "");
@@ -61,105 +70,101 @@ const findFirstLeaf = (group) => {
return stripLayout(first?.name) || null;
};
export default {
name: "pageBreadcrumb",
computed: {
items() {
const route = this.$route;
if (!route || !route.name || route.name === "home_index") {
return [{ title: "首页" }];
}
return this.buildChain(stripLayout(route.name));
},
},
methods: {
go(target) {
if (!target || !target.name) return;
if (this.$route.name === target.name) return;
this.$router.push(target);
},
findInMenu(name) {
const menuList = this.$store.state.app.menuList || [];
for (const grp of menuList) {
for (const child of grp.children || []) {
if (stripLayout(child.name) === name) {
return { group: grp, child };
}
}
}
return null;
},
routerEntry(name) {
if (Array.isArray(otherRouter.children)) {
return otherRouter.children.find((c) => c.name === name);
}
return null;
},
topItem() {
const navList = this.$store.state.app.navList || [];
const menuList = this.$store.state.app.menuList || [];
const currNavTitle = this.$store.state.app.currNavTitle;
const firstName =
this.$route.meta && this.$route.meta.firstRouterName;
const items = computed(() => {
if (!route || !route.name || route.name === "home_index") {
return [{ title: "首页" }];
}
return buildChain(stripLayout(route.name));
});
let topTitle = currNavTitle;
if (firstName) {
const top = navList.find((n) => n.name === firstName);
if (top) topTitle = top.title;
function go(target) {
if (!target || !target.name) return;
if (route.name === target.name) return;
router.push(target);
}
function findInMenu(name) {
const menuList = appStore.menuList || [];
for (const grp of menuList) {
for (const child of grp.children || []) {
if (stripLayout(child.name) === name) {
return { group: grp, child };
}
if (!topTitle) return null;
}
}
return null;
}
let target = null;
if (menuList && menuList.length) {
const leafName = findFirstLeaf(menuList[0]);
if (leafName) target = { name: leafName };
}
return { title: topTitle, target };
},
buildChain(name, depth = 0) {
if (depth > 6) return [];
function routerEntry(name) {
if (Array.isArray(otherRouter.children)) {
return otherRouter.children.find((c) => c.name === name);
}
return null;
}
const inMenu = this.findInMenu(name);
if (inMenu) {
const list = [];
const top = this.topItem();
if (top) list.push(top);
const groupLeaf = findFirstLeaf(inMenu.group);
list.push({
title: inMenu.group.title,
target: groupLeaf ? { name: groupLeaf } : null,
});
list.push({ title: inMenu.child.title });
return list;
}
function topItem() {
const navList = appStore.navList || [];
const menuList = appStore.menuList || [];
const currNavTitle = appStore.currNavTitle;
const firstName = route.meta && route.meta.firstRouterName;
const parentName = PARENT_MAP[name];
const entry = this.routerEntry(name);
const selfTitle = entry?.title
? entry.title
: (this.$route.meta && this.$route.meta.title
? String(this.$route.meta.title).replace(/\s*-\s*.+$/, "")
: "");
let topTitle = currNavTitle;
if (firstName) {
const top = navList.find((n) => n.name === firstName);
if (top) topTitle = top.title;
}
if (!topTitle) return null;
if (parentName) {
const parentChain = this.buildChain(parentName, depth + 1).map(
(it, idx, arr) =>
idx === arr.length - 1
? { title: it.title, target: { name: parentName } }
: it
);
if (selfTitle) parentChain.push({ title: selfTitle });
return parentChain;
}
let target = null;
if (menuList && menuList.length) {
const leafName = findFirstLeaf(menuList[0]);
if (leafName) target = { name: leafName };
}
return { title: topTitle, target };
}
const list = [];
const top = this.topItem();
if (top) list.push(top);
if (selfTitle) list.push({ title: selfTitle });
return list;
},
},
};
function buildChain(name, depth = 0) {
if (depth > 6) return [];
const inMenu = findInMenu(name);
if (inMenu) {
const list = [];
const top = topItem();
if (top) list.push(top);
const groupLeaf = findFirstLeaf(inMenu.group);
list.push({
title: inMenu.group.title,
target: groupLeaf ? { name: groupLeaf } : null,
});
list.push({ title: inMenu.child.title });
return list;
}
const parentName = PARENT_MAP[name];
const entry = routerEntry(name);
const selfTitle = entry?.title
? entry.title
: (route.meta && route.meta.title
? String(route.meta.title).replace(/\s*-\s*.+$/, "")
: "");
if (parentName) {
const parentChain = buildChain(parentName, depth + 1).map(
(it, idx, arr) =>
idx === arr.length - 1
? { title: it.title, target: { name: parentName } }
: it
);
if (selfTitle) parentChain.push({ title: selfTitle });
return parentChain;
}
const list = [];
const top = topItem();
if (top) list.push(top);
if (selfTitle) list.push({ title: selfTitle });
return list;
}
</script>
<style lang="scss" scoped>

View File

@@ -31,88 +31,90 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useAppStore } from "@/stores/app";
import { setStore } from "@/libs/storage";
import { Message } from "@/utils/message";
import util from "@/libs/util.js";
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;
},
activeMenuName() {
const route = this.$route;
const routePath = util.normalizeRoutePath(route.path);
for (const item of this.menuList) {
for (const menu of item.children || []) {
const menuPath = util.normalizeRoutePath(menu.path);
if (menu.name === route.name || (menuPath && menuPath === routePath)) {
return menu.name;
}
}
defineOptions({ name: "shrinkableMenu" });
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const menuList = computed(() => appStore.menuList);
const navList = computed(() => appStore.navList);
const currNav = computed(() => appStore.currNav);
const activeMenuName = computed(() => {
const routePath = util.normalizeRoutePath(route.path);
for (const item of menuList.value) {
for (const menu of item.children || []) {
const menuPath = util.normalizeRoutePath(menu.path);
if (menu.name === route.name || (menuPath && menuPath === routePath)) {
return menu.name;
}
return route.name;
},
},
watch: {
$route(val) {
if (
val.meta.firstRouterName &&
val.meta.firstRouterName !== this.currNav
) {
this.selectNav(val.meta.firstRouterName);
}
}
return route.name;
});
watch(
() => route.fullPath,
() => {
if (
route.meta.firstRouterName &&
route.meta.firstRouterName !== currNav.value
) {
selectNav(route.meta.firstRouterName);
}
}
);
const changeMenu = (name) => {
if (!name) return;
if (router.hasRoute(name)) {
router.push({ name });
return;
}
const menu = findMenuByName(name);
if (menu?.path) {
const path = menu.path.startsWith("/") ? menu.path : `/${menu.path}`;
router.push({ path });
return;
}
util.initRouter();
nextTick(() => {
if (router.hasRoute(name)) {
router.push({ name });
} else {
Message.warning("菜单路由未就绪,请刷新页面后重试");
}
});
};
const findMenuByName = (name) => {
for (const item of menuList.value) {
for (const menu of item.children || []) {
if (menu.name === name) {
return menu;
}
},
},
methods: {
changeMenu(name) {
if (!name) return;
if (this.$router.hasRoute(name)) {
this.$router.push({ name });
return;
}
const menu = this.findMenuByName(name);
if (menu?.path) {
const path = menu.path.startsWith("/") ? menu.path : `/${menu.path}`;
this.$router.push({ path });
return;
}
util.initRouter(this);
this.$nextTick(() => {
if (this.$router.hasRoute(name)) {
this.$router.push({ name });
} else {
this.$Message.warning("菜单路由未就绪,请刷新页面后重试");
}
});
},
findMenuByName(name) {
for (const item of this.menuList) {
for (const menu of item.children || []) {
if (menu.name === name) {
return menu;
}
}
}
return null;
},
selectNav(name) {
this.$store.commit("setCurrNav", name);
this.setStore("currNav", name);
if (name === 'home_index') {
this.$store.commit("updateMenulist", []);
this.$router.push({ name: 'home_index' });
return;
}
util.initRouter(this);
},
},
}
}
return null;
};
const selectNav = (name) => {
appStore.setCurrNav(name);
setStore("currNav", name);
if (name === 'home_index') {
appStore.updateMenulist([]);
router.push({ name: 'home_index' });
return;
}
util.initRouter();
};
</script>

View File

@@ -41,160 +41,179 @@
</div>
</template>
<script>
<script setup>
import { ref, watch, onBeforeUnmount } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { useAppStore } from "@/stores/app";
import util from "@/libs/util.js";
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: "关闭所有",
},
};
},
methods: {
isActive(item) {
const target = item.children ? item.children[0] : item;
return util.isSameRoutePage(target, {
name: this.currentPageName,
path: this.$route.path,
});
},
tagType(item) {
return this.isActive(item) ? "primary" : "info";
},
itemTitle(item) {
if (typeof item.title === "object") {
return this.$t(item.title.i18n);
}
return item.title;
},
closePage(event, name) {
let pageOpenedList = this.$store.state.app.pageOpenedList;
let lastPageObj = pageOpenedList[0];
if (this.currentPageName === name) {
const len = pageOpenedList.length;
for (let i = 1; i < len; i++) {
if (pageOpenedList[i].name === name) {
lastPageObj =
i < len - 1 ? pageOpenedList[i + 1] : pageOpenedList[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);
pageOpenedList = this.$store.state.app.pageOpenedList;
localStorage.pageOpenedList = JSON.stringify(pageOpenedList);
if (this.currentPageName === name) {
this.linkTo(lastPageObj);
}
},
linkTo(item) {
const target = item.children ? item.children[0] : item;
if (util.isSameRoutePage(target, this.$route)) return;
if (!this.beforePush(item)) return;
defineOptions({ name: "tagsPageOpened" });
const routerObj = { name: item.name };
if (item.argu) routerObj.params = item.argu;
if (item.query) routerObj.query = item.query;
const props = defineProps({
pageTagsList: Array,
beforePush: {
type: Function,
default: () => true,
},
});
if (item.name && this.$router.hasRoute(item.name)) {
this.$router.push(routerObj);
return;
}
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const appStore = useAppStore();
const path = target.path || item.path;
if (path) {
this.$router.push({
path: path.startsWith("/") ? path : `/${path}`,
query: item.query,
});
}
},
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);
},
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 isActive = (item) => {
const target = item.children ? item.children[0] : item;
return util.isSameRoutePage(target, {
name: currentPageName.value,
path: route.path,
});
};
const tagType = (item) => {
return isActive(item) ? "primary" : "info";
};
const itemTitle = (item) => {
if (typeof item.title === "object") {
return t(item.title.i18n);
}
return item.title;
};
const closePage = (event, name) => {
let pageOpenedList = appStore.pageOpenedList;
let lastPageObj = pageOpenedList[0];
if (currentPageName.value === name) {
const len = pageOpenedList.length;
for (let i = 1; i < len; i++) {
if (pageOpenedList[i].name === name) {
lastPageObj =
i < len - 1 ? pageOpenedList[i + 1] : pageOpenedList[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);
pageOpenedList = appStore.pageOpenedList;
localStorage.pageOpenedList = JSON.stringify(pageOpenedList);
if (currentPageName.value === name) {
linkTo(lastPageObj);
}
};
const linkTo = (item) => {
const target = item.children ? item.children[0] : item;
if (util.isSameRoutePage(target, route)) return;
if (!props.beforePush(item)) return;
const routerObj = { name: item.name };
if (item.argu) routerObj.params = item.argu;
if (item.query) routerObj.query = item.query;
if (item.name && router.hasRoute(item.name)) {
router.push(routerObj);
return;
}
const path = target.path || item.path;
if (path) {
router.push({
path: path.startsWith("/") ? path : `/${path}`,
query: item.query,
});
}
};
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">

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -90,74 +90,74 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { getUserRecharge } from "@/api/member";
export default {
name: "recharge",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
},
selectDate: null,
data: [],
total: 0,
};
},
methods: {
rechargeWayText(way) {
if (way === "ALIPAY") return "支付宝";
if (way === "WECHAT") return "微信";
if (way === "BANK_TRANSFER") return "线下转账";
return "";
},
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();
},
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;
getUserRecharge(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "recharge" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
});
const selectDate = ref(null);
const data = ref([]);
const total = ref(0);
function rechargeWayText(way) {
if (way === "ALIPAY") return "支付宝";
if (way === "WECHAT") return "微信";
if (way === "BANK_TRANSFER") return "线下转账";
return "";
}
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 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;
getUserRecharge(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -85,75 +85,75 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { getUserWallet } from "@/api/member";
export default {
name: "walletLog",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
},
selectDate: null,
data: [],
total: 0,
};
},
methods: {
serviceTypeText(type) {
if (type === "WALLET_WITHDRAWAL") return "余额提现";
if (type === "WALLET_PAY") return "余额支付";
if (type === "WALLET_REFUND") return "余额退款";
if (type === "WALLET_RECHARGE") return "余额充值";
return "佣金提成";
},
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();
},
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;
getUserWallet(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "walletLog" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
});
const selectDate = ref(null);
const data = ref([]);
const total = ref(0);
function serviceTypeText(type) {
if (type === "WALLET_WITHDRAWAL") return "余额提现";
if (type === "WALLET_PAY") return "余额支付";
if (type === "WALLET_REFUND") return "余额退款";
if (type === "WALLET_RECHARGE") return "余额充值";
return "佣金提成";
}
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 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;
getUserWallet(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -169,168 +169,169 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { getUserWithdrawApply, withdrawApply } from "@/api/member";
import { ElMessage } from "element-plus";
export default {
name: "withdrawApply",
data() {
return {
modalTitle: "",
loading: true,
audit: "",
roleModalVisible: false,
queryModalVisible: false,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
applyStatus: "",
},
selectDate: null,
submitLoading: false,
selectList: [],
selectCount: 0,
showList: {},
data: [],
total: 0,
};
},
methods: {
paramTypeFilter(val) {
if (val === "APPLY") return "申请中";
if (val === "VIA_AUDITING") return "审核通过(提现成功)";
if (val === "WAIT_USER_CONFIRM") return "用户确认";
if (val === "FAIL_AUDITING") return "审核拒绝";
if (val === "SUCCESS") return "提现成功";
if (val === "ERROR") return "提现失败";
return "未知状态";
},
applyStatusText(status) {
if (status === "APPLY") return "申请中";
if (status === "VIA_AUDITING") return "审核通过";
if (status === "WAIT_USER_CONFIRM") return "用户确认";
if (status === "SUCCESS") return "提现成功";
if (status === "ERROR") return "提现失败";
return "审核拒绝";
},
applyStatusTagType(status) {
if (status === "APPLY") return "warning";
if (status === "VIA_AUDITING") return "success";
if (status === "WAIT_USER_CONFIRM") return "warning";
if (status === "SUCCESS") return "primary";
if (status === "ERROR") return "primary";
return "danger";
},
openAudit(row) {
this.showList = { ...row };
this.audit = "";
this.roleModalVisible = true;
},
openQuery(row) {
this.showList = { ...row };
this.modalTitle = "查看";
this.queryModalVisible = true;
},
submitRole(res) {
const params = {};
params.applyId = this.showList.id;
params.result = res;
params.remark = this.audit;
if (res === false && params.remark === "") {
ElMessage.error("审核备注不能为空");
return;
defineOptions({ name: "withdrawApply" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
// 模板 el-table 的 ref
const table = ref(null);
const modalTitle = ref("");
const loading = ref(true);
const audit = ref("");
const roleModalVisible = ref(false);
const queryModalVisible = ref(false);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
applyStatus: "",
});
const selectDate = ref(null);
const submitLoading = ref(false);
const selectList = ref([]);
const selectCount = ref(0);
const showList = ref({});
const data = ref([]);
const total = ref(0);
function paramTypeFilter(val) {
if (val === "APPLY") return "申请中";
if (val === "VIA_AUDITING") return "审核通过(提现成功)";
if (val === "WAIT_USER_CONFIRM") return "用户确认";
if (val === "FAIL_AUDITING") return "审核拒绝";
if (val === "SUCCESS") return "提现成功";
if (val === "ERROR") return "提现失败";
return "未知状态";
}
function applyStatusText(status) {
if (status === "APPLY") return "申请中";
if (status === "VIA_AUDITING") return "审核通过";
if (status === "WAIT_USER_CONFIRM") return "用户确认";
if (status === "SUCCESS") return "提现成功";
if (status === "ERROR") return "提现失败";
return "审核拒绝";
}
function applyStatusTagType(status) {
if (status === "APPLY") return "warning";
if (status === "VIA_AUDITING") return "success";
if (status === "WAIT_USER_CONFIRM") return "warning";
if (status === "SUCCESS") return "primary";
if (status === "ERROR") return "primary";
return "danger";
}
function openAudit(row) {
showList.value = { ...row };
audit.value = "";
roleModalVisible.value = true;
}
function openQuery(row) {
showList.value = { ...row };
modalTitle.value = "查看";
queryModalVisible.value = true;
}
function submitRole(res) {
const params = {};
params.applyId = showList.value.id;
params.result = res;
params.remark = audit.value;
if (res === false && params.remark === "") {
ElMessage.error("审核备注不能为空");
return;
}
submitLoading.value = true;
withdrawApply(params)
.then((result) => {
const success = result === true || (result && result.success === true);
if (success) {
ElMessage.success("操作成功");
roleModalVisible.value = false;
queryModalVisible.value = false;
audit.value = "";
showList.value = {};
getDataList();
}
this.submitLoading = true;
withdrawApply(params)
.then((result) => {
const success = result === true || (result && result.success === true);
if (success) {
ElMessage.success("操作成功");
this.roleModalVisible = false;
this.queryModalVisible = false;
this.audit = "";
this.showList = {};
this.getDataList();
}
})
.finally(() => {
this.submitLoading = false;
});
},
init() {
this.getDataList();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
handleReset() {
this.$refs.searchForm.resetFields();
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.selectDate = null;
this.searchForm.startDate = "";
this.searchForm.endDate = "";
this.searchForm.memberName = "";
this.getDataList();
},
changeSort(e) {
this.searchForm.sort = e.prop || e.key;
this.searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
if (!e.order) {
this.searchForm.order = "";
}
this.getDataList();
},
clearSelectAll() {
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
},
changeSelect(e) {
this.selectList = e;
this.selectCount = e.length;
},
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;
getUserWithdrawApply(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
},
mounted() {
this.init();
},
};
})
.finally(() => {
submitLoading.value = false;
});
}
function init() {
getDataList();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
clearSelectAll();
}
function changePageSize(v) {
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function handleReset() {
searchFormRef.value.resetFields();
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
selectDate.value = null;
searchForm.startDate = "";
searchForm.endDate = "";
searchForm.memberName = "";
getDataList();
}
function changeSort(e) {
searchForm.sort = e.prop || e.key;
searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
if (!e.order) {
searchForm.order = "";
}
getDataList();
}
function clearSelectAll() {
if (table.value) {
table.value.clearSelection();
}
}
function changeSelect(e) {
selectList.value = e;
selectCount.value = e.length;
}
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;
getUserWithdrawApply(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
.ops a {

View File

@@ -343,7 +343,8 @@
</div>
</template>
<script>
<script setup>
import { ref, computed, nextTick, onMounted } from "vue";
import * as API_Member from "@/api/member.js";
import { getPlatformCoupon } from "@/api/promotion";
import { formatPromotionCouponValidityHtml } from "@/utils/promotions";
@@ -351,6 +352,8 @@ import couponTemplate from "@/views/promotions/coupon/coupon.vue";
import uploadPicInput from "@/components/lili/upload-pic-input.vue";
import { ElMessage, ElMessageBox } from "element-plus";
defineOptions({ name: "memberBenefit" });
const GIFT_POINT = "GIFT_POINT";
const COUPON_PACKAGE = "COUPON_PACKAGE";
@@ -368,496 +371,490 @@ const buildDefaultForm = () => ({
couponPackageRows: [],
});
export default {
name: "memberBenefit",
components: {
uploadPicInput,
couponTemplate,
},
data() {
return {
benefitTypeOptions: [],
loading: false,
total: 0,
searchForm: {
pageNumber: 1,
pageSize: 20,
// 模板 ref(couponPicker 为优惠券选择组件,addForm/editForm 为表单)
const couponPicker = ref(null);
const addForm = ref(null);
const editForm = ref(null);
// 动态 ref 映射表(原 this.$refs[refName])
const formRefs = { addForm, editForm };
const benefitTypeOptions = ref([]);
const loading = ref(false);
const total = ref(0);
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
});
const data = ref([]);
const addFlag = ref(false);
const editFlag = ref(false);
const submitAddLoading = ref(false);
const submitEditLoading = ref(false);
const formAdd = ref(buildDefaultForm());
const formEdit = ref(buildDefaultForm());
const couponPickerVisible = ref(false);
const couponPickerWhich = ref("add");
// 动态表单映射表(原 this[formKey])
const formMap = { formAdd, formEdit };
const rulesAdd = computed(() => buildFormRules("add"));
const rulesEdit = computed(() => buildFormRules("edit"));
const couponPickerSelectedList = computed(() => {
const form = couponPickerWhich.value === "add" ? formAdd.value : formEdit.value;
return (form.couponPackageRows || []).map((r) => ({
id: r.couponId,
couponName: r.couponName || "",
}));
});
function benefitTypeLabel(v) {
if (!v) return "-";
const opt = benefitTypeOptions.value.find((o) => o.value === v);
return opt ? opt.description : v;
}
function touchFormField(refName, field) {
nextTick(() => {
const ref = formRefs[refName]?.value;
if (ref && typeof ref.validateField === "function") {
// validateField rejects on failure; catch to avoid dev-server [object Object] overlay
ref.validateField(field, () => {});
}
});
}
function onCouponQuantityChange(which, index, val) {
const formKey = which === "add" ? "formAdd" : "formEdit";
const refName = which === "add" ? "addForm" : "editForm";
formMap[formKey].value.couponPackageRows[index].quantity = val;
touchFormField(refName, "couponPackageRows");
}
function buildFormRules(which) {
const formKey = which === "add" ? "formAdd" : "formEdit";
return {
benefitType: [{ required: true, message: "请选择权益类型", trigger: "change" }],
benefitName: [{ required: true, message: "请输入权益名称", trigger: "blur" }],
benefitLogo: [{ required: true, message: "请配置权益LOGO", trigger: "change" }],
benefitSort: [{ required: true, type: "number", message: "请输入排序", trigger: "change" }],
benefitState: [{ required: true, message: "请选择启用状态", trigger: "change" }],
giftPoint: [
{
validator: (rule, value, callback) => {
const form = formMap[formKey].value;
validateGiftPoint(form, value, callback);
},
trigger: ["change", "blur"],
},
data: [],
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: buildDefaultForm(),
formEdit: buildDefaultForm(),
couponPickerVisible: false,
couponPickerWhich: "add",
};
},
computed: {
rulesAdd() {
return this.buildFormRules("add");
},
rulesEdit() {
return this.buildFormRules("edit");
},
couponPickerSelectedList() {
const form = this.couponPickerWhich === "add" ? this.formAdd : this.formEdit;
return (form.couponPackageRows || []).map((r) => ({
id: r.couponId,
couponName: r.couponName || "",
],
couponPackageRows: [
{
validator: (rule, value, callback) => {
const form = formMap[formKey].value;
validateCouponPackage(form, callback);
},
trigger: "change",
},
],
};
}
function validateGiftPoint(form, value, callback) {
if (form.benefitType !== GIFT_POINT) return callback();
if (value === null || value === undefined || value === "") {
return callback(new Error("请输入赠送积分"));
}
const n = Number(value);
if (Number.isNaN(n) || n < 0 || n > 99999) {
return callback(new Error("赠送积分范围为 0-99999"));
}
callback();
}
function validateCouponPackage(form, callback) {
if (form.benefitType !== COUPON_PACKAGE) return callback();
const rows = form.couponPackageRows || [];
if (!rows.length) {
return callback(new Error("请添加至少一张优惠券"));
}
for (let i = 0; i < rows.length; i++) {
const q = Number(rows[i].quantity);
if (Number.isNaN(q) || q < 1 || q > 10) {
return callback(new Error("每张券赠送张数需在 1~10 之间"));
}
}
callback();
}
function formatCouponFace(row) {
if (!row) return "-";
if (row.price !== undefined && row.price !== null && row.price !== "") {
return `¥${row.price}`;
}
if (row.couponDiscount !== undefined && row.couponDiscount !== null && row.couponDiscount !== "") {
return `${row.couponDiscount}折`;
}
return "-";
}
function formatCouponValidity(row) {
return formatPromotionCouponValidityHtml(row);
}
function buildCouponDisplayRow(detail, quantity) {
const qty = Math.min(10, Math.max(1, Number(quantity) || 1));
return {
couponId: detail.id,
quantity: qty,
couponName: detail.couponName || "",
faceValueLabel: formatCouponFace(detail),
validRange: formatCouponValidity(detail),
};
}
function buildBenefitConfigString(slice) {
const { benefitType, giftPoint, couponPackageRows } = slice;
if (benefitType === GIFT_POINT) {
return JSON.stringify({ giftPoint: Number(giftPoint) });
}
if (benefitType === COUPON_PACKAGE) {
const coupons = (couponPackageRows || []).map((r) => ({
couponId: String(r.couponId),
quantity: Math.min(10, Math.max(1, Number(r.quantity) || 1)),
}));
return JSON.stringify({ coupons });
}
return "";
}
function parseGiftPointFromConfig(benefitType, benefitConfigStr) {
if (benefitType !== GIFT_POINT || !benefitConfigStr) return null;
try {
const o = JSON.parse(benefitConfigStr);
if (o && typeof o.giftPoint !== "undefined") {
const n = Number(o.giftPoint);
return Number.isNaN(n) ? null : n;
}
} catch (e) {
/* ignore */
}
return null;
}
function onAddBenefitTypeChange(val) {
if (val !== GIFT_POINT) {
formAdd.value.giftPoint = null;
}
if (val !== COUPON_PACKAGE) {
formAdd.value.couponPackageRows = [];
}
if (addForm.value && val !== GIFT_POINT) {
touchFormField("addForm", "giftPoint");
}
if (addForm.value && val !== COUPON_PACKAGE) {
touchFormField("addForm", "couponPackageRows");
}
}
function onEditBenefitTypeChange(val) {
if (val !== GIFT_POINT) {
formEdit.value.giftPoint = null;
}
if (val !== COUPON_PACKAGE) {
formEdit.value.couponPackageRows = [];
}
if (editForm.value && val !== GIFT_POINT) {
touchFormField("editForm", "giftPoint");
}
if (editForm.value && val !== COUPON_PACKAGE) {
touchFormField("editForm", "couponPackageRows");
}
}
function parseCouponsFromConfig(str) {
if (!str) return [];
try {
const o = JSON.parse(str);
if (o && Array.isArray(o.coupons)) {
return o.coupons.map((c) => ({
couponId: c.couponId,
quantity: c.quantity,
}));
},
},
methods: {
benefitTypeLabel(v) {
if (!v) return "-";
const opt = this.benefitTypeOptions.find((o) => o.value === v);
return opt ? opt.description : v;
},
touchFormField(refName, field) {
this.$nextTick(() => {
const ref = this.$refs[refName];
if (ref && typeof ref.validateField === "function") {
// validateField rejects on failure; catch to avoid dev-server [object Object] overlay
ref.validateField(field, () => {});
}
}
} catch (e) {
/* ignore */
}
return [];
}
function normalizeCouponFaceFromDetail(item) {
let face = formatCouponFace(item);
if (face !== "-") return face;
const raw = item.faceValueText;
if (raw == null || raw === "") return "-";
return String(raw)
.replace(/^减免现金\s*/, "")
.replace(/^减免\s*/, "")
.trim() || "-";
}
function mapCouponItemsToPackageRows(items) {
if (!Array.isArray(items)) return [];
return items.map((item) => {
const face = normalizeCouponFaceFromDetail(item);
const validHtml = item.validityText
? String(item.validityText).replace(/\n/g, "<br/>")
: "-";
return {
couponId: item.couponId,
quantity: Math.min(10, Math.max(1, Number(item.quantity) || 1)),
couponName: item.couponName || "",
faceValueLabel: face,
validRange: validHtml,
};
});
}
async function hydrateCouponRowsForEdit(snippets) {
const rows = [];
for (const s of snippets) {
const cid = s.couponId;
const qty = s.quantity;
try {
const res = await getPlatformCoupon(cid);
if (res && res.success && res.result) {
rows.push(buildCouponDisplayRow(res.result, qty));
} else {
rows.push({
couponId: cid,
quantity: Math.min(10, Math.max(1, Number(qty) || 1)),
couponName: String(cid),
faceValueLabel: "-",
validRange: "-",
});
}
} catch (e) {
rows.push({
couponId: cid,
quantity: Math.min(10, Math.max(1, Number(qty) || 1)),
couponName: String(cid),
faceValueLabel: "-",
validRange: "-",
});
},
onCouponQuantityChange(which, index, val) {
const formKey = which === "add" ? "formAdd" : "formEdit";
const refName = which === "add" ? "addForm" : "editForm";
this[formKey].couponPackageRows[index].quantity = val;
this.touchFormField(refName, "couponPackageRows");
},
buildFormRules(which) {
const formKey = which === "add" ? "formAdd" : "formEdit";
return {
benefitType: [{ required: true, message: "请选择权益类型", trigger: "change" }],
benefitName: [{ required: true, message: "请输入权益名称", trigger: "blur" }],
benefitLogo: [{ required: true, message: "请配置权益LOGO", trigger: "change" }],
benefitSort: [{ required: true, type: "number", message: "请输入排序", trigger: "change" }],
benefitState: [{ required: true, message: "请选择启用状态", trigger: "change" }],
giftPoint: [
{
validator: (rule, value, callback) => {
const form = this[formKey];
this.validateGiftPoint(form, value, callback);
},
trigger: ["change", "blur"],
},
],
couponPackageRows: [
{
validator: (rule, value, callback) => {
const form = this[formKey];
this.validateCouponPackage(form, callback);
},
trigger: "change",
},
],
}
}
formEdit.value.couponPackageRows = rows;
}
function handleCouponPickerClose() {
couponPickerVisible.value = false;
}
function openCouponPicker(which) {
couponPickerWhich.value = which;
couponPickerVisible.value = true;
}
function confirmCouponPicker() {
const list = couponPicker.value?.getSelection?.() || [];
if (!list.length) {
ElMessage.warning("请至少选择一张优惠券");
return;
}
onCouponTemplateSelected(list);
couponPickerVisible.value = false;
}
function onCouponTemplateSelected(selectedRows) {
const which = couponPickerWhich.value;
const form = which === "add" ? formAdd.value : formEdit.value;
const refName = which === "add" ? "addForm" : "editForm";
const list = selectedRows || [];
const rows = list.map((row) => {
const existing = (form.couponPackageRows || []).find((r) => String(r.couponId) === String(row.id));
const qty = existing ? existing.quantity : 1;
return buildCouponDisplayRow(row, qty);
});
form.couponPackageRows = rows;
touchFormField(refName, "couponPackageRows");
}
function removeCouponRow(which, index) {
const form = which === "add" ? formAdd.value : formEdit.value;
const refName = which === "add" ? "addForm" : "editForm";
form.couponPackageRows.splice(index, 1);
touchFormField(refName, "couponPackageRows");
}
function init() {
loadBenefitTypes();
getData();
}
function loadBenefitTypes() {
API_Member.getMemberBenefitTypes().then((res) => {
if (res && res.success && Array.isArray(res.result)) {
benefitTypeOptions.value = res.result;
} else {
benefitTypeOptions.value = [];
}
});
}
function changePage(page) {
searchForm.value.pageNumber = page;
getData();
}
function changePageSize(size) {
searchForm.value.pageSize = size;
searchForm.value.pageNumber = 1;
getData();
}
function getData() {
loading.value = true;
const params = { ...searchForm.value };
API_Member.getMemberBenefitByPage(params).then((res) => {
loading.value = false;
if (res && res.success && res.result) {
data.value = Array.isArray(res.result.records) ? res.result.records : [];
total.value = Number(res.result.total) || 0;
} else {
data.value = [];
total.value = 0;
}
});
}
function openAdd() {
addFlag.value = true;
submitAddLoading.value = false;
nextTick(() => {
if (addForm.value) addForm.value.resetFields();
formAdd.value = buildDefaultForm();
});
}
function submitAdd() {
if (!addForm.value) {
ElMessage.warning("表单未就绪,请稍后重试");
return;
}
addForm.value.validate((valid) => {
if (!valid) return;
submitAddLoading.value = true;
const { giftPoint, couponPackageRows, ...rest } = formAdd.value;
const payload = {
...rest,
benefitConfig: buildBenefitConfigString({
benefitType: formAdd.value.benefitType,
giftPoint,
couponPackageRows,
}),
};
API_Member.addMemberBenefit(payload).then((res) => {
submitAddLoading.value = false;
if (res && res.success) {
ElMessage.success("添加成功");
addFlag.value = false;
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
} else {
ElMessage.error("添加失败");
}
}).catch(() => {
submitAddLoading.value = false;
ElMessage.error("网络异常,请稍后重试");
});
});
}
function openEdit(row) {
editFlag.value = true;
submitEditLoading.value = false;
nextTick(() => {
if (editForm.value) editForm.value.resetFields();
});
API_Member.getMemberBenefit(row.id).then((res) => {
if (res && res.success && res.result) {
const raw = res.result;
const detail = raw.benefit != null ? raw.benefit : raw;
const couponItems = raw.couponItems;
const bt = detail.benefitType || "";
const parsed = parseGiftPointFromConfig(bt, detail.benefitConfig || "");
const couponSnippets = parseCouponsFromConfig(detail.benefitConfig || "");
let couponRows = [];
if (bt === COUPON_PACKAGE && Array.isArray(couponItems) && couponItems.length) {
couponRows = mapCouponItemsToPackageRows(couponItems);
}
formEdit.value = {
id: detail.id || row.id || "",
benefitName: detail.benefitName || "",
benefitCode: detail.benefitCode || "",
benefitType: bt,
benefitLogo: detail.benefitLogo || "",
benefitDesc: detail.benefitDesc || "",
benefitSort: Number(detail.benefitSort) > 0 ? Number(detail.benefitSort) : 1,
benefitState: detail.benefitState || "OPEN",
benefitConfig: detail.benefitConfig || "",
giftPoint: bt === GIFT_POINT ? (parsed !== null ? parsed : null) : null,
couponPackageRows: couponRows,
};
},
validateGiftPoint(form, value, callback) {
if (form.benefitType !== GIFT_POINT) return callback();
if (value === null || value === undefined || value === "") {
return callback(new Error("请输入赠送积分"));
if (bt === COUPON_PACKAGE && couponRows.length === 0 && couponSnippets.length) {
hydrateCouponRowsForEdit(couponSnippets);
}
const n = Number(value);
if (Number.isNaN(n) || n < 0 || n > 99999) {
return callback(new Error("赠送积分范围为 0-99999"));
}
});
}
function submitEdit() {
if (!editForm.value) {
ElMessage.warning("表单未就绪,请稍后重试");
return;
}
editForm.value.validate((valid) => {
if (!valid) return;
submitEditLoading.value = true;
const { id, giftPoint, couponPackageRows, ...rest } = formEdit.value;
const payload = {
...rest,
benefitConfig: buildBenefitConfigString({
benefitType: formEdit.value.benefitType,
giftPoint,
couponPackageRows,
}),
};
API_Member.updateMemberBenefit(id, payload).then((res) => {
submitEditLoading.value = false;
if (res && res.success) {
ElMessage.success("修改成功");
editFlag.value = false;
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
} else {
ElMessage.error("修改失败");
}
callback();
},
validateCouponPackage(form, callback) {
if (form.benefitType !== COUPON_PACKAGE) return callback();
const rows = form.couponPackageRows || [];
if (!rows.length) {
return callback(new Error("请添加至少一张优惠券"));
}
for (let i = 0; i < rows.length; i++) {
const q = Number(rows[i].quantity);
if (Number.isNaN(q) || q < 1 || q > 10) {
return callback(new Error("每张券赠送张数需在 1~10 之间"));
}
}
callback();
},
formatCouponFace(row) {
if (!row) return "-";
if (row.price !== undefined && row.price !== null && row.price !== "") {
return `¥${row.price}`;
}
if (row.couponDiscount !== undefined && row.couponDiscount !== null && row.couponDiscount !== "") {
return `${row.couponDiscount}折`;
}
return "-";
},
formatCouponValidity(row) {
return formatPromotionCouponValidityHtml(row);
},
buildCouponDisplayRow(detail, quantity) {
const qty = Math.min(10, Math.max(1, Number(quantity) || 1));
return {
couponId: detail.id,
quantity: qty,
couponName: detail.couponName || "",
faceValueLabel: this.formatCouponFace(detail),
validRange: this.formatCouponValidity(detail),
};
},
buildBenefitConfigString(slice) {
const { benefitType, giftPoint, couponPackageRows } = slice;
if (benefitType === GIFT_POINT) {
return JSON.stringify({ giftPoint: Number(giftPoint) });
}
if (benefitType === COUPON_PACKAGE) {
const coupons = (couponPackageRows || []).map((r) => ({
couponId: String(r.couponId),
quantity: Math.min(10, Math.max(1, Number(r.quantity) || 1)),
}));
return JSON.stringify({ coupons });
}
return "";
},
parseGiftPointFromConfig(benefitType, benefitConfigStr) {
if (benefitType !== GIFT_POINT || !benefitConfigStr) return null;
try {
const o = JSON.parse(benefitConfigStr);
if (o && typeof o.giftPoint !== "undefined") {
const n = Number(o.giftPoint);
return Number.isNaN(n) ? null : n;
}
} catch (e) {
/* ignore */
}
return null;
},
onAddBenefitTypeChange(val) {
if (val !== GIFT_POINT) {
this.formAdd.giftPoint = null;
}
if (val !== COUPON_PACKAGE) {
this.formAdd.couponPackageRows = [];
}
if (this.$refs.addForm && val !== GIFT_POINT) {
this.touchFormField("addForm", "giftPoint");
}
if (this.$refs.addForm && val !== COUPON_PACKAGE) {
this.touchFormField("addForm", "couponPackageRows");
}
},
onEditBenefitTypeChange(val) {
if (val !== GIFT_POINT) {
this.formEdit.giftPoint = null;
}
if (val !== COUPON_PACKAGE) {
this.formEdit.couponPackageRows = [];
}
if (this.$refs.editForm && val !== GIFT_POINT) {
this.touchFormField("editForm", "giftPoint");
}
if (this.$refs.editForm && val !== COUPON_PACKAGE) {
this.touchFormField("editForm", "couponPackageRows");
}
},
parseCouponsFromConfig(str) {
if (!str) return [];
try {
const o = JSON.parse(str);
if (o && Array.isArray(o.coupons)) {
return o.coupons.map((c) => ({
couponId: c.couponId,
quantity: c.quantity,
}));
}
} catch (e) {
/* ignore */
}
return [];
},
normalizeCouponFaceFromDetail(item) {
let face = this.formatCouponFace(item);
if (face !== "-") return face;
const raw = item.faceValueText;
if (raw == null || raw === "") return "-";
return String(raw)
.replace(/^减免现金\s*/, "")
.replace(/^减免\s*/, "")
.trim() || "-";
},
mapCouponItemsToPackageRows(items) {
if (!Array.isArray(items)) return [];
return items.map((item) => {
const face = this.normalizeCouponFaceFromDetail(item);
const validHtml = item.validityText
? String(item.validityText).replace(/\n/g, "<br/>")
: "-";
return {
couponId: item.couponId,
quantity: Math.min(10, Math.max(1, Number(item.quantity) || 1)),
couponName: item.couponName || "",
faceValueLabel: face,
validRange: validHtml,
};
});
},
async hydrateCouponRowsForEdit(snippets) {
const rows = [];
for (const s of snippets) {
const cid = s.couponId;
const qty = s.quantity;
try {
const res = await getPlatformCoupon(cid);
if (res && res.success && res.result) {
rows.push(this.buildCouponDisplayRow(res.result, qty));
} else {
rows.push({
couponId: cid,
quantity: Math.min(10, Math.max(1, Number(qty) || 1)),
couponName: String(cid),
faceValueLabel: "-",
validRange: "-",
});
}
} catch (e) {
rows.push({
couponId: cid,
quantity: Math.min(10, Math.max(1, Number(qty) || 1)),
couponName: String(cid),
faceValueLabel: "-",
validRange: "-",
});
}
}
this.formEdit.couponPackageRows = rows;
},
handleCouponPickerClose() {
this.couponPickerVisible = false;
},
openCouponPicker(which) {
this.couponPickerWhich = which;
this.couponPickerVisible = true;
},
confirmCouponPicker() {
const list = this.$refs.couponPicker?.getSelection?.() || [];
if (!list.length) {
ElMessage.warning("请至少选择一张优惠券");
return;
}
this.onCouponTemplateSelected(list);
this.couponPickerVisible = false;
},
onCouponTemplateSelected(selectedRows) {
const which = this.couponPickerWhich;
const form = which === "add" ? this.formAdd : this.formEdit;
const refName = which === "add" ? "addForm" : "editForm";
const list = selectedRows || [];
const rows = list.map((row) => {
const existing = (form.couponPackageRows || []).find((r) => String(r.couponId) === String(row.id));
const qty = existing ? existing.quantity : 1;
return this.buildCouponDisplayRow(row, qty);
});
form.couponPackageRows = rows;
this.touchFormField(refName, "couponPackageRows");
},
removeCouponRow(which, index) {
const form = which === "add" ? this.formAdd : this.formEdit;
const refName = which === "add" ? "addForm" : "editForm";
form.couponPackageRows.splice(index, 1);
this.touchFormField(refName, "couponPackageRows");
},
init() {
this.loadBenefitTypes();
this.getData();
},
loadBenefitTypes() {
API_Member.getMemberBenefitTypes().then((res) => {
if (res && res.success && Array.isArray(res.result)) {
this.benefitTypeOptions = res.result;
}).catch(() => {
submitEditLoading.value = false;
ElMessage.error("网络异常,请稍后重试");
});
});
}
function onBenefitStateSwitch(row, checked) {
const nextState = checked ? "OPEN" : "CLOSE";
const prevState = row.benefitState;
if (nextState === prevState) return;
const text = checked ? "开启" : "关闭";
ElMessageBox.confirm(`确定${text}该客户权益?`, "提示", { type: "warning" }).then(() => {
row._benefitStateLoading = true;
return API_Member.updateMemberBenefitState(row.id, nextState)
.then((res) => {
row._benefitStateLoading = false;
if (res && res.success) {
ElMessage.success(`${text}成功`);
row.benefitState = nextState;
} else {
this.benefitTypeOptions = [];
getData();
}
})
.catch(() => {
row._benefitStateLoading = false;
});
},
changePage(page) {
this.searchForm.pageNumber = page;
this.getData();
},
changePageSize(size) {
this.searchForm.pageSize = size;
this.searchForm.pageNumber = 1;
this.getData();
},
getData() {
this.loading = true;
const params = { ...this.searchForm };
API_Member.getMemberBenefitByPage(params).then((res) => {
this.loading = false;
if (res && res.success && res.result) {
this.data = Array.isArray(res.result.records) ? res.result.records : [];
this.total = Number(res.result.total) || 0;
} else {
this.data = [];
this.total = 0;
}
});
},
openAdd() {
this.addFlag = true;
this.submitAddLoading = false;
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = buildDefaultForm();
});
},
submitAdd() {
if (!this.$refs.addForm) {
ElMessage.warning("表单未就绪,请稍后重试");
return;
}).catch(() => {});
}
function remove(row) {
ElMessageBox.confirm("确定删除该客户权益?", "提示", { type: "warning" }).then(() => {
API_Member.deleteMemberBenefit(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
const { giftPoint, couponPackageRows, ...rest } = this.formAdd;
const payload = {
...rest,
benefitConfig: this.buildBenefitConfigString({
benefitType: this.formAdd.benefitType,
giftPoint,
couponPackageRows,
}),
};
API_Member.addMemberBenefit(payload).then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
ElMessage.success("添加成功");
this.addFlag = false;
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
} else {
ElMessage.error("添加失败");
}
}).catch(() => {
this.submitAddLoading = false;
ElMessage.error("网络异常,请稍后重试");
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
this.$nextTick(() => {
if (this.$refs.editForm) this.$refs.editForm.resetFields();
});
API_Member.getMemberBenefit(row.id).then((res) => {
if (res && res.success && res.result) {
const raw = res.result;
const detail = raw.benefit != null ? raw.benefit : raw;
const couponItems = raw.couponItems;
const bt = detail.benefitType || "";
const parsed = this.parseGiftPointFromConfig(bt, detail.benefitConfig || "");
const couponSnippets = this.parseCouponsFromConfig(detail.benefitConfig || "");
});
}).catch(() => {});
}
let couponRows = [];
if (bt === COUPON_PACKAGE && Array.isArray(couponItems) && couponItems.length) {
couponRows = this.mapCouponItemsToPackageRows(couponItems);
}
this.formEdit = {
id: detail.id || row.id || "",
benefitName: detail.benefitName || "",
benefitCode: detail.benefitCode || "",
benefitType: bt,
benefitLogo: detail.benefitLogo || "",
benefitDesc: detail.benefitDesc || "",
benefitSort: Number(detail.benefitSort) > 0 ? Number(detail.benefitSort) : 1,
benefitState: detail.benefitState || "OPEN",
benefitConfig: detail.benefitConfig || "",
giftPoint: bt === GIFT_POINT ? (parsed !== null ? parsed : null) : null,
couponPackageRows: couponRows,
};
if (bt === COUPON_PACKAGE && couponRows.length === 0 && couponSnippets.length) {
this.hydrateCouponRowsForEdit(couponSnippets);
}
}
});
},
submitEdit() {
if (!this.$refs.editForm) {
ElMessage.warning("表单未就绪,请稍后重试");
return;
}
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, giftPoint, couponPackageRows, ...rest } = this.formEdit;
const payload = {
...rest,
benefitConfig: this.buildBenefitConfigString({
benefitType: this.formEdit.benefitType,
giftPoint,
couponPackageRows,
}),
};
API_Member.updateMemberBenefit(id, payload).then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
ElMessage.success("修改成功");
this.editFlag = false;
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
} else {
ElMessage.error("修改失败");
}
}).catch(() => {
this.submitEditLoading = false;
ElMessage.error("网络异常,请稍后重试");
});
});
},
onBenefitStateSwitch(row, checked) {
const nextState = checked ? "OPEN" : "CLOSE";
const prevState = row.benefitState;
if (nextState === prevState) return;
const text = checked ? "开启" : "关闭";
ElMessageBox.confirm(`确定${text}该客户权益?`, "提示", { type: "warning" }).then(() => {
row._benefitStateLoading = true;
return API_Member.updateMemberBenefitState(row.id, nextState)
.then((res) => {
row._benefitStateLoading = false;
if (res && res.success) {
ElMessage.success(`${text}成功`);
row.benefitState = nextState;
} else {
this.getData();
}
})
.catch(() => {
row._benefitStateLoading = false;
});
}).catch(() => {});
},
remove(row) {
ElMessageBox.confirm("确定删除该客户权益?", "提示", { type: "warning" }).then(() => {
API_Member.deleteMemberBenefit(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
});
}).catch(() => {});
},
},
mounted() {
this.init();
},
};
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="90px"
@@ -78,7 +78,8 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Member from "@/api/member";
const RULE_OPTIONS = [
@@ -95,79 +96,76 @@ const RULE_OPTIONS = [
{ value: "SHARE_BUY", label: "分享购买" },
];
export default {
name: "memberGradeExperienceLog",
data() {
return {
loading: false,
total: 0,
data: [],
ruleOptions: RULE_OPTIONS,
searchForm: {
pageNumber: 1,
pageSize: 20,
memberMobile: "",
ruleKey: "",
},
};
},
mounted() {
this.init();
},
methods: {
formatExperienceValue(row) {
const v = row.value ?? row.variableExperience ?? row.experience ?? row.variableValue;
return v == null ? "-" : v;
},
formatMaxValue(row) {
const v = row.maxValue ?? row.maxExperience ?? row.limitValue;
return v == null ? "-" : v;
},
findRuleName(ruleKey) {
const hit = this.ruleOptions.find((item) => item.value === ruleKey);
return hit ? hit.label : "";
},
init() {
this.getData();
},
buildParams() {
const params = {
pageNumber: this.searchForm.pageNumber,
pageSize: this.searchForm.pageSize,
};
if (this.searchForm.memberMobile) params.mobile = this.searchForm.memberMobile;
if (this.searchForm.ruleKey) params.ruleKey = this.searchForm.ruleKey;
return params;
},
getData() {
this.loading = true;
API_Member.getMemberExperienceByPage(this.buildParams())
.then((res) => {
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
} else {
this.data = [];
this.total = 0;
}
})
.finally(() => {
this.loading = false;
});
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getData();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
},
};
defineOptions({ name: "memberGradeExperienceLog" });
const searchFormRef = ref(null);
const loading = ref(false);
const total = ref(0);
const data = ref([]);
const ruleOptions = RULE_OPTIONS;
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
memberMobile: "",
ruleKey: "",
});
onMounted(() => {
init();
});
function formatExperienceValue(row) {
const v = row.value ?? row.variableExperience ?? row.experience ?? row.variableValue;
return v == null ? "-" : v;
}
function formatMaxValue(row) {
const v = row.maxValue ?? row.maxExperience ?? row.limitValue;
return v == null ? "-" : v;
}
function findRuleName(ruleKey) {
const hit = ruleOptions.find((item) => item.value === ruleKey);
return hit ? hit.label : "";
}
function init() {
getData();
}
function buildParams() {
const params = {
pageNumber: searchForm.pageNumber,
pageSize: searchForm.pageSize,
};
if (searchForm.memberMobile) params.mobile = searchForm.memberMobile;
if (searchForm.ruleKey) params.ruleKey = searchForm.ruleKey;
return params;
}
function getData() {
loading.value = true;
API_Member.getMemberExperienceByPage(buildParams())
.then((res) => {
if (res && res.success && res.result) {
data.value = res.result.records || [];
total.value = res.result.total || 0;
} else {
data.value = [];
total.value = 0;
}
})
.finally(() => {
loading.value = false;
});
}
function handleSearch() {
searchForm.pageNumber = 1;
getData();
}
function changePage(v) {
searchForm.pageNumber = v;
getData();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getData();
}
</script>

View File

@@ -237,10 +237,13 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { getSetting, setSetting } from "@/api/index";
import { ElMessage } from "element-plus";
defineOptions({ name: "memberGradeExperienceSetting" });
const RULE_OPTIONS = [
{ ruleKey: "CONSUME", ruleName: "消费" },
{ ruleKey: "REGISTER", ruleName: "注册" },
@@ -268,176 +271,169 @@ const defaultForm = () => ({
description: "",
});
export default {
name: "memberGradeExperienceSetting",
data() {
return {
loading: false,
submitLoading: false,
form: defaultForm(),
const loading = ref(false);
const submitLoading = ref(false);
const form = ref(defaultForm());
function formatInputValue(v) {
return v == null ? "" : String(v);
}
function getRawInputValue(v) {
return v && v.target ? v.target.value : v;
}
function updateRuleEnabled(index, enabled) {
const item = form.value.items[index] || {};
form.value.items[index] = {
...item,
enabled: !!enabled,
};
}
function updateRuleValue(index, v) {
const raw = getRawInputValue(v);
const next = Number(raw);
const item = form.value.items[index] || {};
form.value.items[index] = {
...item,
value: Number.isFinite(next) ? next : null,
};
}
function commitRuleValue(index) {
const item = form.value.items[index] || {};
const next = Number(item.value);
let value = 1;
if (Number.isFinite(next)) {
if (next < 1) value = 1;
else if (next > 100) value = 100;
else value = Math.floor(next);
}
form.value.items[index] = {
...item,
value,
};
}
function updateRuleMaxValue(index, v) {
const raw = getRawInputValue(v);
const item = form.value.items[index] || {};
if (raw == null || raw === "") {
form.value.items[index] = {
...item,
maxValue: null,
};
},
mounted() {
this.loadData();
},
methods: {
formatInputValue(v) {
return v == null ? "" : String(v);
},
getRawInputValue(v) {
return v && v.target ? v.target.value : v;
},
updateRuleEnabled(index, enabled) {
const item = this.form.items[index] || {};
this.form.items[index] = {
...item,
enabled: !!enabled,
};
},
updateRuleValue(index, v) {
const raw = this.getRawInputValue(v);
const next = Number(raw);
const item = this.form.items[index] || {};
this.form.items[index] = {
...item,
value: Number.isFinite(next) ? next : null,
};
},
commitRuleValue(index) {
const item = this.form.items[index] || {};
const next = Number(item.value);
let value = 1;
if (Number.isFinite(next)) {
if (next < 1) value = 1;
else if (next > 100) value = 100;
else value = Math.floor(next);
}
this.form.items[index] = {
...item,
value,
};
},
updateRuleMaxValue(index, v) {
const raw = this.getRawInputValue(v);
const item = this.form.items[index] || {};
if (raw == null || raw === "") {
this.form.items[index] = {
...item,
maxValue: null,
};
return;
}
const next = Number(raw);
this.form.items[index] = {
...item,
maxValue: Number.isFinite(next) ? next : null,
};
},
commitRuleMaxValue(index) {
const item = this.form.items[index] || {};
if (item.maxValue == null || item.maxValue === "") {
this.form.items[index] = {
...item,
maxValue: null,
};
return;
}
const next = Number(item.maxValue);
this.form.items[index] = {
...item,
maxValue: Number.isFinite(next) && next >= 1 ? Math.floor(next) : null,
};
},
normalizeConfig(val) {
if (!val) return {};
if (typeof val === "string") {
try {
return JSON.parse(val);
} catch (e) {
return {};
}
}
if (typeof val === "object") return val;
return;
}
const next = Number(raw);
form.value.items[index] = {
...item,
maxValue: Number.isFinite(next) ? next : null,
};
}
function commitRuleMaxValue(index) {
const item = form.value.items[index] || {};
if (item.maxValue == null || item.maxValue === "") {
form.value.items[index] = {
...item,
maxValue: null,
};
return;
}
const next = Number(item.maxValue);
form.value.items[index] = {
...item,
maxValue: Number.isFinite(next) && next >= 1 ? Math.floor(next) : null,
};
}
function normalizeConfig(val) {
if (!val) return {};
if (typeof val === "string") {
try {
return JSON.parse(val);
} catch (e) {
return {};
},
normalizeItems(items) {
const map = {};
if (Array.isArray(items)) {
items.forEach((item) => {
if (!item || !item.ruleKey) return;
map[item.ruleKey] = item;
});
}
return RULE_OPTIONS.map((rule) => {
const hit = map[rule.ruleKey] || {};
return {
ruleKey: rule.ruleKey,
ruleName: hit.ruleName || rule.ruleName,
enabled: !!hit.enabled,
value: Number(hit.value) > 0 ? Number(hit.value) : 1,
maxValue: hit.maxValue == null || hit.maxValue === "" ? null : Number(hit.maxValue),
}
}
if (typeof val === "object") return val;
return {};
}
function normalizeItems(items) {
const map = {};
if (Array.isArray(items)) {
items.forEach((item) => {
if (!item || !item.ruleKey) return;
map[item.ruleKey] = item;
});
}
return RULE_OPTIONS.map((rule) => {
const hit = map[rule.ruleKey] || {};
return {
ruleKey: rule.ruleKey,
ruleName: hit.ruleName || rule.ruleName,
enabled: !!hit.enabled,
value: Number(hit.value) > 0 ? Number(hit.value) : 1,
maxValue: hit.maxValue == null || hit.maxValue === "" ? null : Number(hit.maxValue),
};
});
}
function loadData() {
loading.value = true;
getSetting("EXPERIENCE_SETTING")
.then((res) => {
if (res && res.success) {
const cfg = normalizeConfig(res.result);
form.value = {
items: normalizeItems(cfg.items),
description: cfg.description || "",
};
});
},
loadData() {
this.loading = true;
getSetting("EXPERIENCE_SETTING")
.then((res) => {
if (res && res.success) {
const cfg = this.normalizeConfig(res.result);
this.form = {
items: this.normalizeItems(cfg.items),
description: cfg.description || "",
};
} else {
this.form = defaultForm();
}
})
.finally(() => {
this.loading = false;
});
},
validateForm() {
const invalid = this.form.items.find((item) => {
const v = Number(item.value);
if (!Number.isInteger(v) || v < 1 || v > 100) return true;
if (item.maxValue != null && item.maxValue !== "") {
const m = Number(item.maxValue);
if (!Number.isInteger(m) || m < 1) return true;
}
return false;
});
if (invalid) {
ElMessage.error("请检查经验值配置,经验值范围为1-100,限额需为正整数");
return false;
} else {
form.value = defaultForm();
}
return true;
},
submit() {
if (!this.validateForm()) return;
const payload = {
items: this.form.items.map((item) => ({
ruleKey: item.ruleKey,
ruleName: item.ruleName,
enabled: !!item.enabled,
value: Number(item.value),
maxValue: item.maxValue == null || item.maxValue === "" ? null : Number(item.maxValue),
})),
description: this.form.description || "",
};
this.submitLoading = true;
setSetting("EXPERIENCE_SETTING", payload)
.then((res) => {
if (res && res.success) {
ElMessage.success("保存成功");
}
})
.finally(() => {
this.submitLoading = false;
});
},
},
};
})
.finally(() => {
loading.value = false;
});
}
function validateForm() {
const invalid = form.value.items.find((item) => {
const v = Number(item.value);
if (!Number.isInteger(v) || v < 1 || v > 100) return true;
if (item.maxValue != null && item.maxValue !== "") {
const m = Number(item.maxValue);
if (!Number.isInteger(m) || m < 1) return true;
}
return false;
});
if (invalid) {
ElMessage.error("请检查经验值配置,经验值范围为1-100,限额需为正整数");
return false;
}
return true;
}
function submit() {
if (!validateForm()) return;
const payload = {
items: form.value.items.map((item) => ({
ruleKey: item.ruleKey,
ruleName: item.ruleName,
enabled: !!item.enabled,
value: Number(item.value),
maxValue: item.maxValue == null || item.maxValue === "" ? null : Number(item.maxValue),
})),
description: form.value.description || "",
};
submitLoading.value = true;
setSetting("EXPERIENCE_SETTING", payload)
.then((res) => {
if (res && res.success) {
ElMessage.success("保存成功");
}
})
.finally(() => {
submitLoading.value = false;
});
}
onMounted(() => {
loadData();
});
</script>
<style scoped lang="scss">

View File

@@ -178,11 +178,14 @@
</div>
</template>
<script>
<script setup>
import { ref, nextTick, onMounted } from "vue";
import * as API_Member from "@/api/member.js";
import uploadPicInput from "@/components/lili/upload-pic-input";
import { ElMessage, ElMessageBox } from "element-plus";
defineOptions({ name: "memberGrade" });
const buildDefaultForm = () => ({
id: "",
gradeName: "",
@@ -209,254 +212,248 @@ function syncOrderedBenefitIds(prevOrder, selected) {
return out;
}
export default {
name: "memberGrade",
components: {
uploadPicInput,
},
data() {
return {
loading: true,
data: [],
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: buildDefaultForm(),
formEdit: buildDefaultForm(),
addBenefitOrder: [],
editBenefitOrder: [],
benefitOptions: [],
benefitTypeOptions: [],
benefitOptionsLoading: false,
editDetailLoading: false,
rules: {
gradeName: [{ required: true, message: "请输入等级名称", trigger: "blur" }],
gradeImage: [{ required: true, message: "请上传等级图标", trigger: "change" }],
requiredExperience: [{ required: true, type: "number", message: "请输入所需经验值", trigger: "change" }],
gradeSort: [{ required: true, type: "number", message: "请输入等级排序", trigger: "change" }],
gradeState: [{ required: true, message: "请选择等级开关", trigger: "change" }],
},
};
},
methods: {
benefitOptionLabel(b) {
if (!b) return "";
const name = b.benefitName || String(b.id);
const opt = this.benefitTypeOptions.find((o) => o.value === b.benefitType);
const typeText = opt ? opt.description : b.benefitType || "";
return typeText ? `${name}(${typeText})` : name;
},
onAddBenefitIdsChange(val) {
this.addBenefitOrder = syncOrderedBenefitIds(this.addBenefitOrder, val);
},
onEditBenefitIdsChange(val) {
this.editBenefitOrder = syncOrderedBenefitIds(this.editBenefitOrder, val);
},
loadBenefitTypes() {
API_Member.getMemberBenefitTypes().then((res) => {
if (res && res.success && Array.isArray(res.result)) {
this.benefitTypeOptions = res.result;
} else {
this.benefitTypeOptions = [];
}
});
},
loadBenefitOptions() {
this.benefitOptionsLoading = true;
const pageSize = 500;
const fetchPage = (pageNumber) =>
API_Member.getMemberBenefitByPage({ pageNumber, pageSize, sort: "benefitSort", order: "asc" });
return fetchPage(1)
.then((res) => {
if (!(res && res.success && res.result)) {
this.benefitOptions = [];
return;
}
const records = Array.isArray(res.result.records) ? res.result.records : [];
const total = Number(res.result.total) || records.length;
let all = records.slice();
if (total > pageSize) {
const pages = Math.ceil(total / pageSize);
const rest = [];
for (let p = 2; p <= pages; p++) {
rest.push(fetchPage(p));
}
return Promise.all(rest).then((results) => {
results.forEach((r) => {
if (r && r.success && r.result && Array.isArray(r.result.records)) {
all = all.concat(r.result.records);
}
});
this.benefitOptions = all;
});
}
this.benefitOptions = all;
})
.catch(() => {
this.benefitOptions = [];
})
.finally(() => {
this.benefitOptionsLoading = false;
});
},
parseBenefitIdsFromString(str) {
return String(str || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
},
fillEditFormFromGrade(grade, benefitsOrderedIds) {
this.formEdit = {
id: grade.id || "",
gradeName: grade.gradeName || "",
isDefault: grade.isDefault === true,
gradeImage: grade.gradeImage || "",
gradeBackground: grade.gradeBackground || "",
gradeFontColor: grade.gradeFontColor || "",
requiredExperience: Number(grade.requiredExperience) > 0 ? Number(grade.requiredExperience) : 1,
gradeSort: Number(grade.gradeSort) > 0 ? Number(grade.gradeSort) : 1,
gradeState: grade.gradeState || "OPEN",
benefitIds: grade.benefitIds || "",
};
this.editBenefitOrder = (benefitsOrderedIds || []).map((id) => String(id));
},
init() {
this.getData();
this.loadBenefitTypes();
this.loadBenefitOptions();
},
getData() {
this.loading = true;
API_Member.getMemberGradeByPage().then((res) => {
this.loading = false;
if (res && res.success) {
this.data = Array.isArray(res.result) ? res.result : [];
}
});
},
openAdd() {
this.addFlag = true;
this.submitAddLoading = false;
if (!this.benefitOptions.length && !this.benefitOptionsLoading) {
this.loadBenefitOptions();
// 模板 el-form 的 ref(ref="addForm" / ref="editForm")
const addForm = ref(null);
const editForm = ref(null);
const loading = ref(true);
const data = ref([]);
const addFlag = ref(false);
const editFlag = ref(false);
const submitAddLoading = ref(false);
const submitEditLoading = ref(false);
const formAdd = ref(buildDefaultForm());
const formEdit = ref(buildDefaultForm());
const addBenefitOrder = ref([]);
const editBenefitOrder = ref([]);
const benefitOptions = ref([]);
const benefitTypeOptions = ref([]);
const benefitOptionsLoading = ref(false);
const editDetailLoading = ref(false);
const rules = ref({
gradeName: [{ required: true, message: "请输入等级名称", trigger: "blur" }],
gradeImage: [{ required: true, message: "请上传等级图标", trigger: "change" }],
requiredExperience: [{ required: true, type: "number", message: "请输入所需经验值", trigger: "change" }],
gradeSort: [{ required: true, type: "number", message: "请输入等级排序", trigger: "change" }],
gradeState: [{ required: true, message: "请选择等级开关", trigger: "change" }],
});
function benefitOptionLabel(b) {
if (!b) return "";
const name = b.benefitName || String(b.id);
const opt = benefitTypeOptions.value.find((o) => o.value === b.benefitType);
const typeText = opt ? opt.description : b.benefitType || "";
return typeText ? `${name}(${typeText})` : name;
}
function onAddBenefitIdsChange(val) {
addBenefitOrder.value = syncOrderedBenefitIds(addBenefitOrder.value, val);
}
function onEditBenefitIdsChange(val) {
editBenefitOrder.value = syncOrderedBenefitIds(editBenefitOrder.value, val);
}
function loadBenefitTypes() {
API_Member.getMemberBenefitTypes().then((res) => {
if (res && res.success && Array.isArray(res.result)) {
benefitTypeOptions.value = res.result;
} else {
benefitTypeOptions.value = [];
}
});
}
function loadBenefitOptions() {
benefitOptionsLoading.value = true;
const pageSize = 500;
const fetchPage = (pageNumber) =>
API_Member.getMemberBenefitByPage({ pageNumber, pageSize, sort: "benefitSort", order: "asc" });
return fetchPage(1)
.then((res) => {
if (!(res && res.success && res.result)) {
benefitOptions.value = [];
return;
}
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = buildDefaultForm();
this.addBenefitOrder = [];
});
},
submitAdd() {
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
const { benefitIds: _omit, ...rest } = this.formAdd;
const payload = {
...rest,
benefitIds: (this.addBenefitOrder || []).join(","),
};
API_Member.addMemberGrade(payload).then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
ElMessage.success("添加成功");
this.addFlag = false;
this.getData();
}
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
this.editDetailLoading = true;
if (!this.benefitOptions.length && !this.benefitOptionsLoading) {
this.loadBenefitOptions();
}
this.$nextTick(() => {
if (this.$refs.editForm) this.$refs.editForm.resetFields();
});
API_Member.getMemberGrade(row.id)
.then((res) => {
this.editDetailLoading = false;
if (res && res.success && res.result) {
const raw = res.result;
const grade = raw.grade != null ? raw.grade : raw;
const benefits = Array.isArray(raw.benefits) ? raw.benefits : [];
const orderedIds = benefits.length
? benefits.map((b) => b.id).filter((id) => id != null && id !== "")
: String(grade.benefitIds || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
this.fillEditFormFromGrade(grade, orderedIds);
} else {
this.fillEditFormFromGrade(row, this.parseBenefitIdsFromString(row.benefitIds));
}
})
.catch(() => {
this.editDetailLoading = false;
this.fillEditFormFromGrade(row, this.parseBenefitIdsFromString(row.benefitIds));
});
},
submitEdit() {
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, benefitIds: _omit, ...rest } = this.formEdit;
const payload = {
...rest,
benefitIds: (this.editBenefitOrder || []).join(","),
};
API_Member.updateMemberGrade(id, payload).then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
ElMessage.success("修改成功");
this.editFlag = false;
this.getData();
}
});
});
},
onGradeStateSwitch(row, checked) {
const nextState = checked ? "OPEN" : "CLOSE";
const prevState = row.gradeState;
if (nextState === prevState) return;
const text = checked ? "开启" : "关闭";
ElMessageBox.confirm(`确定${text}该客户等级?`, "提示", { type: "warning" }).then(() => {
row._gradeStateLoading = true;
return API_Member.updateMemberGradeState(row.id, nextState)
.then((res) => {
row._gradeStateLoading = false;
if (res && res.success) {
ElMessage.success(`${text}成功`);
row.gradeState = nextState;
} else {
this.getData();
const records = Array.isArray(res.result.records) ? res.result.records : [];
const total = Number(res.result.total) || records.length;
let all = records.slice();
if (total > pageSize) {
const pages = Math.ceil(total / pageSize);
const rest = [];
for (let p = 2; p <= pages; p++) {
rest.push(fetchPage(p));
}
return Promise.all(rest).then((results) => {
results.forEach((r) => {
if (r && r.success && r.result && Array.isArray(r.result.records)) {
all = all.concat(r.result.records);
}
})
.catch(() => {
row._gradeStateLoading = false;
});
}).catch(() => {});
},
remove(row) {
ElMessageBox.confirm("确定删除该客户等级?", "提示", { type: "warning" }).then(() => {
API_Member.deleteMemberGrade(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
benefitOptions.value = all;
});
}).catch(() => {});
},
},
mounted() {
this.init();
},
};
}
benefitOptions.value = all;
})
.catch(() => {
benefitOptions.value = [];
})
.finally(() => {
benefitOptionsLoading.value = false;
});
}
function parseBenefitIdsFromString(str) {
return String(str || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
function fillEditFormFromGrade(grade, benefitsOrderedIds) {
formEdit.value = {
id: grade.id || "",
gradeName: grade.gradeName || "",
isDefault: grade.isDefault === true,
gradeImage: grade.gradeImage || "",
gradeBackground: grade.gradeBackground || "",
gradeFontColor: grade.gradeFontColor || "",
requiredExperience: Number(grade.requiredExperience) > 0 ? Number(grade.requiredExperience) : 1,
gradeSort: Number(grade.gradeSort) > 0 ? Number(grade.gradeSort) : 1,
gradeState: grade.gradeState || "OPEN",
benefitIds: grade.benefitIds || "",
};
editBenefitOrder.value = (benefitsOrderedIds || []).map((id) => String(id));
}
function init() {
getData();
loadBenefitTypes();
loadBenefitOptions();
}
function getData() {
loading.value = true;
API_Member.getMemberGradeByPage().then((res) => {
loading.value = false;
if (res && res.success) {
data.value = Array.isArray(res.result) ? res.result : [];
}
});
}
function openAdd() {
addFlag.value = true;
submitAddLoading.value = false;
if (!benefitOptions.value.length && !benefitOptionsLoading.value) {
loadBenefitOptions();
}
nextTick(() => {
if (addForm.value) addForm.value.resetFields();
formAdd.value = buildDefaultForm();
addBenefitOrder.value = [];
});
}
function submitAdd() {
addForm.value.validate((valid) => {
if (!valid) return;
submitAddLoading.value = true;
const { benefitIds: _omit, ...rest } = formAdd.value;
const payload = {
...rest,
benefitIds: (addBenefitOrder.value || []).join(","),
};
API_Member.addMemberGrade(payload).then((res) => {
submitAddLoading.value = false;
if (res && res.success) {
ElMessage.success("添加成功");
addFlag.value = false;
getData();
}
});
});
}
function openEdit(row) {
editFlag.value = true;
submitEditLoading.value = false;
editDetailLoading.value = true;
if (!benefitOptions.value.length && !benefitOptionsLoading.value) {
loadBenefitOptions();
}
nextTick(() => {
if (editForm.value) editForm.value.resetFields();
});
API_Member.getMemberGrade(row.id)
.then((res) => {
editDetailLoading.value = false;
if (res && res.success && res.result) {
const raw = res.result;
const grade = raw.grade != null ? raw.grade : raw;
const benefits = Array.isArray(raw.benefits) ? raw.benefits : [];
const orderedIds = benefits.length
? benefits.map((b) => b.id).filter((id) => id != null && id !== "")
: String(grade.benefitIds || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
fillEditFormFromGrade(grade, orderedIds);
} else {
fillEditFormFromGrade(row, parseBenefitIdsFromString(row.benefitIds));
}
})
.catch(() => {
editDetailLoading.value = false;
fillEditFormFromGrade(row, parseBenefitIdsFromString(row.benefitIds));
});
}
function submitEdit() {
editForm.value.validate((valid) => {
if (!valid) return;
submitEditLoading.value = true;
const { id, benefitIds: _omit, ...rest } = formEdit.value;
const payload = {
...rest,
benefitIds: (editBenefitOrder.value || []).join(","),
};
API_Member.updateMemberGrade(id, payload).then((res) => {
submitEditLoading.value = false;
if (res && res.success) {
ElMessage.success("修改成功");
editFlag.value = false;
getData();
}
});
});
}
function onGradeStateSwitch(row, checked) {
const nextState = checked ? "OPEN" : "CLOSE";
const prevState = row.gradeState;
if (nextState === prevState) return;
const text = checked ? "开启" : "关闭";
ElMessageBox.confirm(`确定${text}该客户等级?`, "提示", { type: "warning" }).then(() => {
row._gradeStateLoading = true;
return API_Member.updateMemberGradeState(row.id, nextState)
.then((res) => {
row._gradeStateLoading = false;
if (res && res.success) {
ElMessage.success(`${text}成功`);
row.gradeState = nextState;
} else {
getData();
}
})
.catch(() => {
row._gradeStateLoading = false;
});
}).catch(() => {});
}
function remove(row) {
ElMessageBox.confirm("确定删除该客户等级?", "提示", { type: "warning" }).then(() => {
API_Member.deleteMemberGrade(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
});
}).catch(() => {});
}
onMounted(() => {
init();
});
</script>
<style scoped>
.ops a {

View File

@@ -70,137 +70,136 @@
</div>
</template>
<script>
<script setup>
import { nextTick, onMounted, reactive, ref } from "vue";
import * as API_Member from "@/api/member.js";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "memberGroup",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
},
data: [],
total: 0,
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: {
groupName: "",
description: "",
},
formEdit: {
id: "",
groupName: "",
description: "",
},
rulesAdd: {
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
},
rulesEdit: {
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
},
};
},
methods: {
init() {
this.getData();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
getData() {
this.loading = true;
API_Member.getMemberGroupByPage(this.searchForm).then((res) => {
this.loading = false;
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
}
});
},
openAdd() {
this.addFlag = true;
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = { groupName: "", description: "" };
});
},
submitAdd() {
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
API_Member.addMemberGroup(this.formAdd).then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
ElMessage.success("添加成功");
this.addFlag = false;
this.getData();
}
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
API_Member.getMemberGroup(row.id).then((res) => {
if (res && res.success && res.result) {
this.formEdit = {
id: res.result.id,
groupName: res.result.groupName || "",
description: res.result.description || "",
};
} else {
this.formEdit = {
id: row.id,
groupName: row.groupName || "",
description: row.description || "",
};
}
});
},
submitEdit() {
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, groupName, description } = this.formEdit;
API_Member.updateMemberGroup(id, { groupName, description }).then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
ElMessage.success("修改成功");
this.editFlag = false;
this.getData();
}
});
});
},
remove(row) {
ElMessageBox.confirm("确定删除该分组?", "提示", { type: "warning" }).then(() => {
API_Member.deleteMemberGroup(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
this.getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
});
}).catch(() => {});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "memberGroup" });
const addForm = ref(null);
const editForm = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
});
const data = ref([]);
const total = ref(0);
const addFlag = ref(false);
const editFlag = ref(false);
const submitAddLoading = ref(false);
const submitEditLoading = ref(false);
const formAdd = ref({
groupName: "",
description: "",
});
const formEdit = ref({
id: "",
groupName: "",
description: "",
});
const rulesAdd = reactive({
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
});
const rulesEdit = reactive({
groupName: [{ required: true, message: "请输入分组名称", trigger: "blur" }],
});
function init() {
getData();
}
function changePage(v) {
searchForm.pageNumber = v;
getData();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getData();
}
function getData() {
loading.value = true;
API_Member.getMemberGroupByPage(searchForm).then((res) => {
loading.value = false;
if (res && res.success && res.result) {
data.value = res.result.records || [];
total.value = res.result.total || 0;
}
});
}
function openAdd() {
addFlag.value = true;
nextTick(() => {
if (addForm.value) addForm.value.resetFields();
formAdd.value = { groupName: "", description: "" };
});
}
function submitAdd() {
addForm.value.validate((valid) => {
if (!valid) return;
submitAddLoading.value = true;
API_Member.addMemberGroup(formAdd.value).then((res) => {
submitAddLoading.value = false;
if (res && res.success) {
ElMessage.success("添加成功");
addFlag.value = false;
getData();
}
});
});
}
function openEdit(row) {
editFlag.value = true;
submitEditLoading.value = false;
API_Member.getMemberGroup(row.id).then((res) => {
if (res && res.success && res.result) {
formEdit.value = {
id: res.result.id,
groupName: res.result.groupName || "",
description: res.result.description || "",
};
} else {
formEdit.value = {
id: row.id,
groupName: row.groupName || "",
description: row.description || "",
};
}
});
}
function submitEdit() {
editForm.value.validate((valid) => {
if (!valid) return;
submitEditLoading.value = true;
const { id, groupName, description } = formEdit.value;
API_Member.updateMemberGroup(id, { groupName, description }).then((res) => {
submitEditLoading.value = false;
if (res && res.success) {
ElMessage.success("修改成功");
editFlag.value = false;
getData();
}
});
});
}
function remove(row) {
ElMessageBox.confirm("确定删除该分组?", "提示", { type: "warning" }).then(() => {
API_Member.deleteMemberGroup(row.id).then((res) => {
if (res && res.success) {
ElMessage.success("删除成功");
getData();
} else if (res && res.message) {
ElMessage.error(res.message);
}
});
}).catch(() => {});
}
onMounted(() => {
init();
});
</script>
<style scoped>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -168,7 +168,7 @@
<el-dialog v-model="addFlag" title="添加会员" width="500px" destroy-on-close class="add-member-dialog">
<el-form
ref="addMemberForm"
ref="addMemberFormRef"
:model="addMemberForm"
:rules="addRule"
label-width="100px"
@@ -210,7 +210,7 @@
</el-dialog>
<el-dialog v-model="descFlag" :title="descTitle" width="500px" destroy-on-close>
<el-form ref="form" :model="form" :rules="ruleValidate" label-width="80px">
<el-form ref="formRef" :model="form" :rules="ruleValidate" label-width="80px">
<el-input v-model="form.id" style="display: none" />
<el-form-item label="头像">
<img :src="form.face || defaultPic" class="face" />
@@ -251,7 +251,7 @@
</el-dialog>
<el-dialog v-model="walletIncreaseFlag" title="增加余额" width="420px" destroy-on-close>
<el-form ref="walletIncreaseForm" :model="walletIncreaseForm" :rules="walletIncreaseRule" label-width="90px">
<el-form ref="walletIncreaseFormRef" :model="walletIncreaseForm" :rules="walletIncreaseRule" label-width="90px">
<el-form-item label="充值金额" prop="rechargeMoney">
<el-input-number v-model="walletIncreaseForm.rechargeMoney" :min="0.01" :precision="2" style="width: 240px" />
</el-form-item>
@@ -263,7 +263,7 @@
</el-dialog>
<el-dialog v-model="memberPointFlag" title="修改积分" width="420px" destroy-on-close>
<el-form ref="memberPointForm" :model="memberPointForm" :rules="memberPointRule" label-width="90px">
<el-form ref="memberPointFormRef" :model="memberPointForm" :rules="memberPointRule" label-width="90px">
<el-form-item label="类型" prop="type">
<el-radio-group v-model="memberPointForm.type">
<el-radio-button value="INCREASE">增加</el-radio-button>
@@ -281,7 +281,7 @@
</el-dialog>
<el-dialog v-model="memberGroupFlag" title="设定会员分组" width="420px" destroy-on-close>
<el-form ref="memberGroupForm" :model="memberGroupForm" :rules="memberGroupRule" label-width="90px">
<el-form ref="memberGroupFormRef" :model="memberGroupForm" :rules="memberGroupRule" label-width="90px">
<el-form-item label="会员分组" prop="groupId">
<el-select v-model="memberGroupForm.groupId" clearable filterable style="width: 240px">
<el-option
@@ -300,13 +300,14 @@
</el-dialog>
<el-dialog v-model="picModelFlag" width="1200px">
<ossManage ref="ossManage" @callback="callbackSelected" :isComponent="true" :initialize="picModelFlag" />
<ossManage ref="ossManageRef" @callback="callbackSelected" :isComponent="true" :initialize="picModelFlag" />
</el-dialog>
<multipleMap ref="map" @callback="selectedRegion" />
</div>
</template>
<script>
<script setup>
import { ref, watch, nextTick, onMounted } from "vue";
import { ArrowDown } from "@element-plus/icons-vue";
import multipleMap from "@/components/map/multiple-map";
import * as API_Member from "@/api/member.js";
@@ -314,451 +315,456 @@ import ossManage from "@/views/sys/oss-manage/ossManage";
import * as RegExp from "@/libs/RegExp.js";
import { ElMessage, ElMessageBox } from "element-plus";
import { customRouterPush } from "@/utils/filters";
import { md5 } from "@/utils/md5.js";
import defaultPic from "@/assets/default.png";
export default {
name: "member",
components: {
multipleMap,
ossManage,
ArrowDown,
defineOptions({ name: "member" });
const props = defineProps({
selectedMember: {
type: Boolean,
default: false,
},
props: {
selectedMember: {
type: Boolean,
default: false,
},
selectedList: {
type: null,
default: () => [],
},
checkboxSelect: {
type: Boolean,
default: false,
},
selectedList: {
type: null,
default: () => [],
},
data() {
return {
defaultPic,
descTitle: "",
descFlag: false,
loading: true,
addFlag: false,
addMemberForm: {
mobile: "",
username: "",
password: "",
},
searchForm: {
pageNumber: 1,
pageSize: 20,
order: "desc",
id: "",
username: "",
mobile: "",
disabled: "OPEN",
groupId: "",
},
picModelFlag: false,
form: {},
walletIncreaseFlag: false,
walletIncreaseLoading: false,
walletIncreaseForm: {
memberId: "",
rechargeMoney: null,
},
walletIncreaseRule: {
rechargeMoney: [
{ required: true, type: "number", message: "请输入充值金额", trigger: "change" },
{
validator: (rule, value, callback) => {
if (typeof value !== "number" || value <= 0) {
callback(new Error("充值金额必须大于0"));
return;
}
callback();
},
trigger: "change",
},
],
},
memberPointFlag: false,
memberPointLoading: false,
memberPointForm: {
memberId: "",
point: null,
type: "INCREASE",
},
selectedRows: [],
memberGroupFlag: false,
memberGroupLoading: false,
memberGroupForm: {
groupId: "",
},
memberGroupRule: {
groupId: [{ required: true, message: "请选择会员分组", trigger: "change" }],
},
memberGroupList: [],
memberGradeList: [],
memberPointRule: {
type: [{ required: true, message: "请选择类型", trigger: "change" }],
point: [
{ required: true, type: "number", message: "请输入积分", trigger: "change" },
{
validator: (rule, value, callback) => {
if (typeof value !== "number" || value <= 0) {
callback(new Error("积分必须大于0"));
return;
}
callback();
},
trigger: "change",
},
],
},
addRule: {
mobile: [
{ required: true, message: "请输入手机号码" },
{ pattern: RegExp.mobile, message: "请输入正确的手机号" },
],
username: [{ required: true, message: "请输入客户名称" }],
password: [{ required: true, message: "请输入密码" }],
},
ruleValidate: {},
data: [],
total: 0,
selectMember: [],
};
checkboxSelect: {
type: Boolean,
default: false,
},
watch: {
selectedList: {
handler(val) {
this.selectMember = JSON.parse(JSON.stringify(val));
this.init(this.data);
});
const emit = defineEmits(["callback"]);
// 模板 el-form/el-table 的模板 ref(原 ref="searchForm"/"addMemberForm"/"form" 等与 data 同名会互相覆盖,
// 改名为 xxxRef;ossManage 与 import 同名,改名 ossManageRef)
const searchFormRef = ref(null);
const table = ref(null);
const addMemberFormRef = ref(null);
const formRef = ref(null);
const walletIncreaseFormRef = ref(null);
const memberPointFormRef = ref(null);
const memberGroupFormRef = ref(null);
const ossManageRef = ref(null);
const descTitle = ref("");
const descFlag = ref(false);
const loading = ref(true);
const addFlag = ref(false);
const addMemberForm = ref({
mobile: "",
username: "",
password: "",
});
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
order: "desc",
id: "",
username: "",
mobile: "",
disabled: "OPEN",
groupId: "",
});
const picModelFlag = ref(false);
const form = ref({});
const walletIncreaseFlag = ref(false);
const walletIncreaseLoading = ref(false);
const walletIncreaseForm = ref({
memberId: "",
rechargeMoney: null,
});
const walletIncreaseRule = ref({
rechargeMoney: [
{ required: true, type: "number", message: "请输入充值金额", trigger: "change" },
{
validator: (rule, value, callback) => {
if (typeof value !== "number" || value <= 0) {
callback(new Error("充值金额必须大于0"));
return;
}
callback();
},
deep: true,
immediate: true,
trigger: "change",
},
],
});
const memberPointFlag = ref(false);
const memberPointLoading = ref(false);
const memberPointForm = ref({
memberId: "",
point: null,
type: "INCREASE",
});
const selectedRows = ref([]);
const memberGroupFlag = ref(false);
const memberGroupLoading = ref(false);
const memberGroupForm = ref({
groupId: "",
});
const memberGroupRule = ref({
groupId: [{ required: true, message: "请选择会员分组", trigger: "change" }],
});
const memberGroupList = ref([]);
const memberGradeList = ref([]);
const memberPointRule = ref({
type: [{ required: true, message: "请选择类型", trigger: "change" }],
point: [
{ required: true, type: "number", message: "请输入积分", trigger: "change" },
{
validator: (rule, value, callback) => {
if (typeof value !== "number" || value <= 0) {
callback(new Error("积分必须大于0"));
return;
}
callback();
},
trigger: "change",
},
],
});
const addRule = ref({
mobile: [
{ required: true, message: "请输入手机号码" },
{ pattern: RegExp.mobile, message: "请输入正确的手机号" },
],
username: [{ required: true, message: "请输入客户名称" }],
password: [{ required: true, message: "请输入密码" }],
});
const ruleValidate = ref({});
const data = ref([]);
const total = ref(0);
const selectMember = ref([]);
watch(
() => props.selectedList,
(val) => {
selectMember.value = JSON.parse(JSON.stringify(val));
init(data.value);
},
methods: {
handleMoreCommand(name, row) {
if (name === "edit") this.editPerm(row);
if (name === "disabled") this.disabled(row);
if (name === "increaseWallet") this.openWalletIncrease(row);
if (name === "updatePoint") this.openMemberPoint(row);
},
onSelectionChange(selection) {
if (this.selectedMember && this.checkboxSelect) {
this.selectMember = selection || [];
return;
{ deep: true, immediate: true }
);
function handleMoreCommand(name, row) {
if (name === "edit") editPerm(row);
if (name === "disabled") disabled(row);
if (name === "increaseWallet") openWalletIncrease(row);
if (name === "updatePoint") openMemberPoint(row);
}
function onSelectionChange(selection) {
if (props.selectedMember && props.checkboxSelect) {
selectMember.value = selection || [];
return;
}
selectedRows.value = selection || [];
}
function getSelection() {
return selectMember.value || [];
}
function syncTableSelection() {
if (!props.selectedMember || !props.checkboxSelect) {
return;
}
nextTick(() => {
clearSelection();
data.value.forEach((row) => {
if (selectMember.value.some((member) => member.id === row.id)) {
table.value?.toggleRowSelection(row, true);
}
this.selectedRows = selection || [];
},
getSelection() {
return this.selectMember || [];
},
syncTableSelection() {
if (!this.selectedMember || !this.checkboxSelect) {
return;
});
});
}
function openSetMemberGroup() {
memberGroupFlag.value = true;
memberGroupLoading.value = false;
memberGroupForm.value = { groupId: "" };
nextTick(() => {
if (memberGroupFormRef.value) memberGroupFormRef.value.resetFields();
});
loadMemberGroupList();
}
function loadMemberGroupList() {
API_Member.getMemberGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
memberGroupList.value = res.result.records || [];
}
});
}
function loadMemberGradeList() {
API_Member.getMemberGradeByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
memberGradeList.value = res.result || [];
}
});
}
function clearSelection() {
table.value?.clearSelection();
}
function submitSetMemberGroup() {
if (selectedRows.value.length === 0) {
ElMessage.warning("请先选择会员");
return;
}
memberGroupFormRef.value.validate((valid) => {
if (!valid) return;
const memberIds = selectedRows.value.map((item) => item.id);
const groupId = memberGroupForm.value.groupId;
memberGroupLoading.value = true;
Promise.all(
memberIds.map((memberId) => API_Member.setMemberUserGroups(memberId, [groupId]))
)
.then((results) => {
memberGroupLoading.value = false;
const failed = results.find((res) => !res || !res.success);
if (failed) {
ElMessage.error(failed.message || "设置失败");
return;
}
ElMessage.success("设置成功");
memberGroupFlag.value = false;
selectedRows.value = [];
clearSelection();
getData();
})
.catch(() => {
memberGroupLoading.value = false;
ElMessage.error("设置失败,请稍后重试");
});
});
}
function callback(val) {
selectMember.value.forEach((item) => {
item.___selected = false;
});
val.___selected = !val.___selected;
let findUser = selectMember.value.find((item) => item.id == val.id);
if (!findUser) {
selectMember.value.push(val);
} else {
selectMember.value.map((item, index) => {
if (item.id == findUser.id) {
selectMember.value.splice(index, 1);
}
this.$nextTick(() => {
this.clearSelection();
this.data.forEach((row) => {
if (this.selectMember.some((member) => member.id === row.id)) {
this.$refs.table?.toggleRowSelection(row, true);
}
});
});
},
openSetMemberGroup() {
this.memberGroupFlag = true;
this.memberGroupLoading = false;
this.memberGroupForm = { groupId: "" };
this.$nextTick(() => {
if (this.$refs.memberGroupForm) this.$refs.memberGroupForm.resetFields();
});
this.loadMemberGroupList();
},
loadMemberGroupList() {
API_Member.getMemberGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
this.memberGroupList = res.result.records || [];
});
}
emit("callback", val);
}
function init(list) {
list.forEach((item) => {
if (selectMember.value.length != 0) {
selectMember.value.forEach((member) => {
if (member.id == item.id) {
item.___selected = true;
}
});
},
loadMemberGradeList() {
API_Member.getMemberGradeByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
this.memberGradeList = res.result || [];
} else {
item.___selected = false;
}
});
data.value = list;
}
function changePage() {
getData();
}
function changePageSize() {
searchForm.value.pageNumber = 1;
getData();
}
function handleSearch() {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = 20;
getData();
}
function editPerm(val) {
descTitle.value = `查看用户 ${val.username}`;
descFlag.value = true;
getMemberInfo(val.id);
}
function addMember() {
addMemberForm.value = {
mobile: "",
username: "",
password: "",
};
addFlag.value = true;
nextTick(() => {
addMemberFormRef.value?.clearValidate();
});
}
function getMemberInfo(id) {
API_Member.getMemberInfoData(id).then((res) => {
if (res.result) {
form.value = res.result;
}
});
}
function getData() {
loading.value = true;
if (!props.selectedMember) {
selectedRows.value = [];
clearSelection();
}
API_Member.getMemberListData(searchForm.value).then((res) => {
if (res.result.records) {
loading.value = false;
init(res.result.records);
total.value = res.result.total;
syncTableSelection();
}
});
}
function openPicSelector() {
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function callbackSelected(val) {
picModelFlag.value = false;
form.value.face = val.url;
}
function addMemberSubmit() {
addMemberForm.value.password = md5(addMemberForm.value.password);
addMemberFormRef.value.validate((valid) => {
if (valid) {
API_Member.addMember(addMemberForm.value).then((res) => {
if (res.result) {
addMemberFormRef.value.resetFields();
getData();
ElMessage.success("添加成功!");
addFlag.value = false;
}
});
},
clearSelection() {
this.$refs.table?.clearSelection();
},
submitSetMemberGroup() {
if (this.selectedRows.length === 0) {
ElMessage.warning("请先选择会员");
return;
}
});
}
function selectedRegion(val) {
if (val.type === "select") {
const paths = val.data.map((item) => item.name).join(",");
const ids = val.data.map((item) => item.id).join(",");
form.value.region = paths;
form.value.regionId = ids;
} else {
form.value.region = val.data.addr;
form.value.regionId = val.data.addrId;
}
}
function detail(row) {
customRouterPush({ name: "member-detail", query: { id: row.id } });
}
function disabled(v) {
let params = {
memberIds: [v.id],
disabled: false,
};
ElMessageBox.confirm("确认禁用此会员?", "提示", { type: "warning" }).then(() => {
API_Member.updateMemberStatus(params).then((res) => {
if (res.success) {
ElMessage.success("禁用成功");
getData();
}
this.$refs.memberGroupForm.validate((valid) => {
if (!valid) return;
const memberIds = this.selectedRows.map((item) => item.id);
const groupId = this.memberGroupForm.groupId;
this.memberGroupLoading = true;
Promise.all(
memberIds.map((memberId) => API_Member.setMemberUserGroups(memberId, [groupId]))
)
.then((results) => {
this.memberGroupLoading = false;
const failed = results.find((res) => !res || !res.success);
if (failed) {
ElMessage.error(failed.message || "设置失败");
return;
}
ElMessage.success("设置成功");
this.memberGroupFlag = false;
this.selectedRows = [];
this.clearSelection();
this.getData();
})
.catch(() => {
this.memberGroupLoading = false;
ElMessage.error("设置失败,请稍后重试");
});
});
},
callback(val) {
this.selectMember.forEach((item) => {
item.___selected = false;
});
val.___selected = !val.___selected;
let findUser = this.selectMember.find((item) => item.id == val.id);
if (!findUser) {
this.selectMember.push(val);
} else {
this.selectMember.map((item, index) => {
if (item.id == findUser.id) {
this.selectMember.splice(index, 1);
}
});
}
this.$emit("callback", val);
},
init(data) {
data.forEach((item) => {
if (this.selectMember.length != 0) {
this.selectMember.forEach((member) => {
if (member.id == item.id) {
item.___selected = true;
}
});
});
}).catch(() => {});
}
function openWalletIncrease(row) {
walletIncreaseLoading.value = false;
walletIncreaseForm.value = { memberId: row.id, rechargeMoney: null };
walletIncreaseFlag.value = true;
nextTick(() => {
walletIncreaseFormRef.value && walletIncreaseFormRef.value.resetFields();
});
}
function submitWalletIncrease() {
walletIncreaseFormRef.value.validate((valid) => {
if (!valid) return;
walletIncreaseLoading.value = true;
API_Member.increaseMemberWallet({
memberId: walletIncreaseForm.value.memberId,
rechargeMoney: walletIncreaseForm.value.rechargeMoney,
})
.then((res) => {
if (res && res.success) {
ElMessage.success("充值成功");
walletIncreaseFlag.value = false;
getData();
} else {
item.___selected = false;
ElMessage.error((res && res.message) || "充值失败");
}
})
.finally(() => {
walletIncreaseLoading.value = false;
});
this.data = data;
},
changePage() {
this.getData();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getData();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getData();
},
editPerm(val) {
this.descTitle = `查看用户 ${val.username}`;
this.descFlag = true;
this.getMemberInfo(val.id);
},
addMember() {
this.addMemberForm = {
mobile: "",
username: "",
password: "",
};
this.addFlag = true;
this.$nextTick(() => {
this.$refs.addMemberForm?.clearValidate();
});
},
getMemberInfo(id) {
API_Member.getMemberInfoData(id).then((res) => {
if (res.result) {
this.form = res.result;
});
}
function openMemberPoint(row) {
memberPointLoading.value = false;
memberPointForm.value = { memberId: row.id, point: null, type: "INCREASE" };
memberPointFlag.value = true;
nextTick(() => {
memberPointFormRef.value && memberPointFormRef.value.resetFields();
});
}
function submitMemberPoint() {
memberPointFormRef.value.validate((valid) => {
if (!valid) return;
memberPointLoading.value = true;
API_Member.updateMemberPoint({
memberId: memberPointForm.value.memberId,
point: memberPointForm.value.point,
type: memberPointForm.value.type,
})
.then((res) => {
if (res && res.success) {
ElMessage.success("修改成功");
memberPointFlag.value = false;
getData();
} else {
ElMessage.error((res && res.message) || "修改失败");
}
})
.finally(() => {
memberPointLoading.value = false;
});
},
getData() {
this.loading = true;
if (!this.selectedMember) {
this.selectedRows = [];
this.clearSelection();
}
API_Member.getMemberListData(this.searchForm).then((res) => {
if (res.result.records) {
this.loading = false;
this.init(res.result.records);
this.total = res.result.total;
this.syncTableSelection();
}
});
},
openPicSelector() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
callbackSelected(val) {
this.picModelFlag = false;
this.form.face = val.url;
},
addMemberSubmit() {
this.addMemberForm.password = this.md5(this.addMemberForm.password);
this.$refs.addMemberForm.validate((valid) => {
if (valid) {
API_Member.addMember(this.addMemberForm).then((res) => {
if (res.result) {
this.$refs.addMemberForm.resetFields();
this.getData();
ElMessage.success("添加成功!");
this.addFlag = false;
}
});
}
});
},
selectedRegion(val) {
if (val.type === "select") {
const paths = val.data.map((item) => item.name).join(",");
const ids = val.data.map((item) => item.id).join(",");
this.form.region = paths;
this.form.regionId = ids;
} else {
this.form.region = val.data.addr;
this.form.regionId = val.data.addrId;
}
},
detail(row) {
customRouterPush({ name: "member-detail", query: { id: row.id } });
},
disabled(v) {
let params = {
memberIds: [v.id],
disabled: false,
};
ElMessageBox.confirm("确认禁用此会员?", "提示", { type: "warning" }).then(() => {
API_Member.updateMemberStatus(params).then((res) => {
if (res.success) {
ElMessage.success("禁用成功");
this.getData();
}
});
}).catch(() => {});
},
openWalletIncrease(row) {
this.walletIncreaseLoading = false;
this.walletIncreaseForm = { memberId: row.id, rechargeMoney: null };
this.walletIncreaseFlag = true;
this.$nextTick(() => {
this.$refs.walletIncreaseForm && this.$refs.walletIncreaseForm.resetFields();
});
},
submitWalletIncrease() {
this.$refs.walletIncreaseForm.validate((valid) => {
if (!valid) return;
this.walletIncreaseLoading = true;
API_Member.increaseMemberWallet({
memberId: this.walletIncreaseForm.memberId,
rechargeMoney: this.walletIncreaseForm.rechargeMoney,
})
.then((res) => {
if (res && res.success) {
ElMessage.success("充值成功");
this.walletIncreaseFlag = false;
this.getData();
} else {
ElMessage.error((res && res.message) || "充值失败");
}
})
.finally(() => {
this.walletIncreaseLoading = false;
});
});
},
openMemberPoint(row) {
this.memberPointLoading = false;
this.memberPointForm = { memberId: row.id, point: null, type: "INCREASE" };
this.memberPointFlag = true;
this.$nextTick(() => {
this.$refs.memberPointForm && this.$refs.memberPointForm.resetFields();
});
},
submitMemberPoint() {
this.$refs.memberPointForm.validate((valid) => {
if (!valid) return;
this.memberPointLoading = true;
API_Member.updateMemberPoint({
memberId: this.memberPointForm.memberId,
point: this.memberPointForm.point,
type: this.memberPointForm.type,
})
.then((res) => {
if (res && res.success) {
ElMessage.success("修改成功");
this.memberPointFlag = false;
this.getData();
} else {
ElMessage.error((res && res.message) || "修改失败");
}
})
.finally(() => {
this.memberPointLoading = false;
});
});
},
handleSubmitModal() {
const { nickName, sex, username, face, newPassword, id, regionId, region } = this.form;
let time = new Date(this.form.birthday);
let birthday =
this.form.birthday === undefined
? ""
: time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
let submit = {
regionId,
region,
nickName,
sex,
birthday,
face,
id,
};
if (newPassword) {
submit.password = this.md5(newPassword);
}
API_Member.updateMember(submit).then((res) => {
if (res.result) {
ElMessage.success("修改成功!");
this.descFlag = false;
this.getData();
}
});
},
},
mounted() {
this.getData();
this.loadMemberGroupList();
this.loadMemberGradeList();
},
};
});
}
function handleSubmitModal() {
const { nickName, sex, username, face, newPassword, id, regionId, region } = form.value;
let time = new Date(form.value.birthday);
let birthday =
form.value.birthday === undefined
? ""
: time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
let submit = {
regionId,
region,
nickName,
sex,
birthday,
face,
id,
};
if (newPassword) {
submit.password = md5(newPassword);
}
API_Member.updateMember(submit).then((res) => {
if (res.result) {
ElMessage.success("修改成功!");
descFlag.value = false;
getData();
}
});
}
onMounted(() => {
getData();
loadMemberGroupList();
loadMemberGradeList();
});
// 外部通过 ref 调用(promotions/coupon-activity/coupon-publish.vue 的 memberLayoutRef.getSelection())
defineExpose({ getSelection });
</script>
<style lang="scss" scoped>
.face {

View File

@@ -379,7 +379,7 @@
width="500px"
:close-on-click-modal="false"
>
<el-form ref="addressForm" :model="addressForm" label-width="100px" :rules="addressFormValidate">
<el-form ref="addressFormRef" :model="addressForm" label-width="100px" :rules="addressFormValidate">
<el-form-item label="收货人姓名" prop="name">
<el-input v-model="addressForm.name" maxlength="8" clearable style="width: 80%" />
</el-form-item>
@@ -388,7 +388,7 @@
</el-form-item>
<el-form-item label="收货人地址" prop="consigneeAddressPath">
<span>{{ addressForm.consigneeAddressPath || "暂无地址" }}</span>
<el-button style="margin-left: 10px" @click="$refs.map.open()">选择</el-button>
<el-button style="margin-left: 10px" @click="map.open()">选择</el-button>
</el-form-item>
<el-form-item label="详细地址" prop="detail">
<el-input v-model="addressForm.detail" maxlength="35" clearable style="width: 80%" />
@@ -412,7 +412,9 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, nextTick, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import * as API_Member from "@/api/member.js";
import ossManage from "@/views/sys/oss-manage/ossManage";
import multipleMap from "@/components/map/multiple-map";
@@ -420,374 +422,373 @@ import * as RegExp from "@/libs/RegExp.js";
import * as API_Order from "@/api/order.js";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: "memberDetail",
components: {
ossManage,
multipleMap,
},
data() {
return {
id: "",
activeTab: "point",
loading: true,
addressLoading: false,
memberInfo: {},
memberWalletInfo: {},
addressModalTitle: "",
addressModalVisible: false,
addressForm: {
id: "",
isDefault: 0,
consigneeAddressPath: "",
consigneeAddressIdPath: "",
},
selectDate: null,
submitLoading: false,
addressFormValidate: {
name: [{ required: true, message: "收货人姓名不能为空" }],
mobile: [
{ required: true, message: "请输入收货人手机号码" },
{
pattern: RegExp.mobile,
message: "请输入正确的手机号",
},
],
consigneeAddressPath: [{ required: true, message: "收货人地址不能为空" }],
detail: [{ required: true, message: "收货人详细地址不能为空" }],
},
pointData: [],
pointTotal: 0,
pointSearchForm: {
pageNumber: 1,
pageSize: 20,
},
orderData: [],
orderTotal: 0,
orderSearchForm: {
pageNumber: 1,
pageSize: 20,
payStatus: "",
orderSn: "",
orderType: "",
},
addressData: [],
addressTotal: 0,
addressSearchForm: {
pageNumber: 1,
pageSize: 20,
},
walletSearchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
},
walletData: [],
walletTotal: 0,
receiptRecordSearchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
},
receiptRecordData: [],
receiptRecordTotal: 0,
};
},
methods: {
orderTypeText(v) {
const map = {
NORMAL: "普通订单",
VIRTUAL: "虚拟订单",
GIFT: "赠品订单",
PINTUAN: "拼团订单",
};
return map[v] || v || "";
},
clientTypeText(v) {
const map = {
H5: "移动端",
PC: "PC端",
WECHAT_MP: "小程序端",
APP: "移动应用端",
};
return map[v] || v || "";
},
orderStatusText(v) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
UNDELIVERED: "待发货",
DELIVERED: "已发货",
COMPLETED: "已完成",
TAKE: "待核验",
CANCELLED: "已关闭",
};
return map[v] || v || "";
},
payStatusText(v) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
};
return map[v] || v || "";
},
groupAfterSaleStatusText(v) {
const map = {
NEW: "未申请",
NOT_APPLIED: "未申请",
ALREADY_APPLIED: "已申请",
EXPIRED: "已失效",
};
return map[v] || v || "";
},
groupComplainStatusText(v) {
const map = {
NEW: "未申请",
NO_APPLY: "未申请",
APPLYING: "申请中",
COMPLETE: "已完成",
EXPIRED: "已失效",
CANCEL: "取消投诉",
};
return map[v] || v || "";
},
walletServiceTypeText(type) {
if (type === "WALLET_WITHDRAWAL") return "余额提现";
if (type === "WALLET_PAY") return "余额支付";
if (type === "WALLET_REFUND") return "余额退款";
if (type === "WALLET_RECHARGE") return "余额充值";
return "佣金提成";
},
init() {
this.getMemberInfo();
this.getPointData();
},
memberInfoChange(v) {
if (v == "point") {
this.getPointData();
}
if (v == "address") {
this.$nextTick(() => {
this.getAddressData();
});
}
if (v == "order") {
this.getOrderData();
}
if (v == "wallet") {
this.getMemberWalletData();
this.getDepositLogData();
}
if (v == "receipt") {
this.getReceiptRecordData();
}
},
getMemberInfo() {
API_Member.getMemberInfoData(this.id).then((res) => {
this.memberInfo = res.result;
});
},
memberStatusChange(v) {
let params = {
memberIds: [this.id],
disabled: v,
};
API_Member.updateMemberStatus(params).then((res) => {});
},
getMemberWalletData() {
this.loading = true;
let params = {
memberId: this.id,
};
API_Member.getMemberWallet(params).then((res) => {
this.loading = false;
if (res.success) {
this.memberWalletInfo = res.result;
}
});
this.loading = false;
},
getDepositLogData() {
this.loading = true;
this.walletSearchForm.memberId = this.id;
API_Member.getUserWallet(this.walletSearchForm).then((res) => {
this.loading = false;
if (res.success) {
this.walletData = res.result.records;
this.walletTotal = res.result.total;
}
});
this.loading = false;
},
getReceiptRecordData() {
this.loading = true;
this.receiptRecordSearchForm.pageNumber = 1;
this.receiptRecordSearchForm.pageSize = 20;
this.receiptRecordSearchForm.memberId = this.id;
API_Order.getReceiptPage(this.receiptRecordSearchForm).then((res) => {
this.loading = false;
if (res.success) {
this.receiptRecordData = res.result.records;
this.receiptRecordTotal = res.result.total;
}
});
this.loading = false;
},
getOrderData() {
this.loading = true;
this.orderSearchForm.pageNumber = 1;
this.orderSearchForm.pageSize = 20;
this.orderSearchForm.memberId = this.id;
API_Order.getOrderList(this.orderSearchForm).then((res) => {
this.loading = false;
if (res.success) {
this.orderData = res.result.records;
this.orderTotal = res.result.total;
}
});
this.loading = false;
},
orderDetail(v) {
this.$router.push({
name: "order-detail",
query: { sn: v },
});
},
getPointData() {
this.loading = true;
this.pointSearchForm.memberId = this.id;
API_Member.getHistoryPointData(this.pointSearchForm).then((res) => {
this.loading = false;
if (res.success) {
this.pointData = res.result.records;
this.pointTotal = res.result.total;
}
});
this.loading = false;
},
addressSubmit() {
this.addressForm.memberId = this.id;
this.$refs.addressForm.validate((valid) => {
if (valid) {
this.submitLoading = true;
let submit = JSON.parse(JSON.stringify(this.addressForm));
submit.isDefault ? (submit.isDefault = true) : (submit.isDefault = false);
if (submit.id != "") {
API_Member.editMemberAddress(submit).then((res) => {
this.submitLoading = false;
if (res && res.success) {
ElMessage.success("修改成功");
this.addressModalVisible = false;
this.getAddressData();
}
});
} else {
API_Member.addMemberAddress(submit).then((res) => {
this.submitLoading = false;
if (res && res.success) {
ElMessage.success("添加成功");
this.addressModalVisible = false;
this.getAddressData();
}
});
}
}
});
},
getAddress(val) {
if (val.type === "select") {
const paths = val.data.map((item) => item.name).join(",");
const ids = val.data.map((item) => item.id).join(",");
defineOptions({ name: "memberDetail" });
this.addressForm.consigneeAddressPath = paths;
this.addressForm.consigneeAddressIdPath = ids;
const route = useRoute();
const router = useRouter();
const coord = val.data[val.data.length - 1].center.split(",");
this.addressForm.lat = coord[1];
this.addressForm.lon = coord[0];
} else {
this.addressForm.consigneeAddressPath = val.data.addr;
this.addressForm.consigneeAddressIdPath = val.data.addrId;
this.addressForm.lat = val.data.position.lat;
this.addressForm.lon = val.data.position.lng;
}
// 模板 el-form 的 ref(原 ref="addressForm",与 data 同名会互相覆盖,改名为 addressFormRef)
const addressFormRef = ref(null);
// 模板 multipleMap 的 ref(ref="map")
const map = ref(null);
const id = ref("");
const activeTab = ref("point");
const loading = ref(true);
const addressLoading = ref(false);
const memberInfo = ref({});
const memberWalletInfo = ref({});
const addressModalTitle = ref("");
const addressModalVisible = ref(false);
const addressForm = reactive({
id: "",
isDefault: 0,
consigneeAddressPath: "",
consigneeAddressIdPath: "",
});
const selectDate = ref(null);
const submitLoading = ref(false);
const addressFormValidate = reactive({
name: [{ required: true, message: "收货人姓名不能为空" }],
mobile: [
{ required: true, message: "请输入收货人手机号码" },
{
pattern: RegExp.mobile,
message: "请输入正确的手机号",
},
memberAddressRemove(v) {
ElMessageBox.confirm("确定要删除此收货地址?", "删除", { type: "warning" }).then(() => {
API_Member.removeMemberAddress(v.id).then((res) => {
if (res.success) {
ElMessage.success("删除成功");
this.getAddressData();
],
consigneeAddressPath: [{ required: true, message: "收货人地址不能为空" }],
detail: [{ required: true, message: "收货人详细地址不能为空" }],
});
const pointData = ref([]);
const pointTotal = ref(0);
const pointSearchForm = ref({
pageNumber: 1,
pageSize: 20,
});
const orderData = ref([]);
const orderTotal = ref(0);
const orderSearchForm = ref({
pageNumber: 1,
pageSize: 20,
payStatus: "",
orderSn: "",
orderType: "",
});
const addressData = ref([]);
const addressTotal = ref(0);
const addressSearchForm = ref({
pageNumber: 1,
pageSize: 20,
});
const walletSearchForm = ref({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
});
const walletData = ref([]);
const walletTotal = ref(0);
const receiptRecordSearchForm = ref({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
});
const receiptRecordData = ref([]);
const receiptRecordTotal = ref(0);
function orderTypeText(v) {
const map = {
NORMAL: "普通订单",
VIRTUAL: "虚拟订单",
GIFT: "赠品订单",
PINTUAN: "拼团订单",
};
return map[v] || v || "";
}
function clientTypeText(v) {
const map = {
H5: "移动端",
PC: "PC端",
WECHAT_MP: "小程序端",
APP: "移动应用端",
};
return map[v] || v || "";
}
function orderStatusText(v) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
UNDELIVERED: "待发货",
DELIVERED: "已发货",
COMPLETED: "已完成",
TAKE: "待核验",
CANCELLED: "已关闭",
};
return map[v] || v || "";
}
function payStatusText(v) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
};
return map[v] || v || "";
}
function groupAfterSaleStatusText(v) {
const map = {
NEW: "未申请",
NOT_APPLIED: "未申请",
ALREADY_APPLIED: "已申请",
EXPIRED: "已失效",
};
return map[v] || v || "";
}
function groupComplainStatusText(v) {
const map = {
NEW: "未申请",
NO_APPLY: "未申请",
APPLYING: "申请中",
COMPLETE: "已完成",
EXPIRED: "已失效",
CANCEL: "取消投诉",
};
return map[v] || v || "";
}
function walletServiceTypeText(type) {
if (type === "WALLET_WITHDRAWAL") return "余额提现";
if (type === "WALLET_PAY") return "余额支付";
if (type === "WALLET_REFUND") return "余额退款";
if (type === "WALLET_RECHARGE") return "余额充值";
return "佣金提成";
}
function init() {
getMemberInfo();
getPointData();
}
function memberInfoChange(v) {
if (v == "point") {
getPointData();
}
if (v == "address") {
nextTick(() => {
getAddressData();
});
}
if (v == "order") {
getOrderData();
}
if (v == "wallet") {
getMemberWalletData();
getDepositLogData();
}
if (v == "receipt") {
getReceiptRecordData();
}
}
function getMemberInfo() {
API_Member.getMemberInfoData(id.value).then((res) => {
memberInfo.value = res.result;
});
}
function memberStatusChange(v) {
let params = {
memberIds: [id.value],
disabled: v,
};
API_Member.updateMemberStatus(params).then((res) => {});
}
function getMemberWalletData() {
loading.value = true;
let params = {
memberId: id.value,
};
API_Member.getMemberWallet(params).then((res) => {
loading.value = false;
if (res.success) {
memberWalletInfo.value = res.result;
}
});
loading.value = false;
}
function getDepositLogData() {
loading.value = true;
walletSearchForm.value.memberId = id.value;
API_Member.getUserWallet(walletSearchForm.value).then((res) => {
loading.value = false;
if (res.success) {
walletData.value = res.result.records;
walletTotal.value = res.result.total;
}
});
loading.value = false;
}
function getReceiptRecordData() {
loading.value = true;
receiptRecordSearchForm.value.pageNumber = 1;
receiptRecordSearchForm.value.pageSize = 20;
receiptRecordSearchForm.value.memberId = id.value;
API_Order.getReceiptPage(receiptRecordSearchForm.value).then((res) => {
loading.value = false;
if (res.success) {
receiptRecordData.value = res.result.records;
receiptRecordTotal.value = res.result.total;
}
});
loading.value = false;
}
function getOrderData() {
loading.value = true;
orderSearchForm.value.pageNumber = 1;
orderSearchForm.value.pageSize = 20;
orderSearchForm.value.memberId = id.value;
API_Order.getOrderList(orderSearchForm.value).then((res) => {
loading.value = false;
if (res.success) {
orderData.value = res.result.records;
orderTotal.value = res.result.total;
}
});
loading.value = false;
}
function orderDetail(v) {
router.push({
name: "order-detail",
query: { sn: v },
});
}
function getPointData() {
loading.value = true;
pointSearchForm.value.memberId = id.value;
API_Member.getHistoryPointData(pointSearchForm.value).then((res) => {
loading.value = false;
if (res.success) {
pointData.value = res.result.records;
pointTotal.value = res.result.total;
}
});
loading.value = false;
}
function addressSubmit() {
addressForm.memberId = id.value;
addressFormRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
let submit = JSON.parse(JSON.stringify(addressForm));
submit.isDefault ? (submit.isDefault = true) : (submit.isDefault = false);
if (submit.id != "") {
API_Member.editMemberAddress(submit).then((res) => {
submitLoading.value = false;
if (res && res.success) {
ElMessage.success("修改成功");
addressModalVisible.value = false;
getAddressData();
}
});
}).catch(() => {});
},
getAddressData() {
this.addressLoading = true;
API_Member.getMemberAddressData(this.id, this.addressSearchForm)
.then((res) => {
if (res.success) {
this.addressData = res.result.records;
this.addressTotal = res.result.total;
}
})
.finally(() => {
this.addressLoading = false;
});
},
pointChangePage(v) {
this.pointSearchForm.pageNumber = v;
this.getPointData();
},
pointChangePageSize(v) {
this.pointSearchForm.pageNumber = 1;
this.pointSearchForm.pageSize = v;
this.getPointData();
},
addressChangePage(v) {
this.addressSearchForm.pageNumber = v;
this.getAddressData();
},
addressChangePageSize(v) {
this.addressSearchForm.pageNumber = 1;
this.addressSearchForm.pageSize = v;
this.getAddressData();
},
walletChangePage(v) {
this.walletSearchForm.pageNumber = v;
this.getDepositLogData();
},
walletChangePageSize(v) {
this.walletSearchForm.pageNumber = 1;
this.walletSearchForm.pageSize = v;
this.getDepositLogData();
},
orderChangePage(v) {
this.orderSearchForm.pageNumber = v;
this.getOrderData();
},
orderChangePageSize(v) {
this.orderSearchForm.pageNumber = 1;
this.orderSearchForm.pageSize = v;
this.getOrderData();
},
selectDateRange(v) {
if (v && v.length === 2) {
this.orderSearchForm.startDate = v[0];
this.orderSearchForm.endDate = v[1];
} else {
delete this.orderSearchForm.startDate;
delete this.orderSearchForm.endDate;
API_Member.addMemberAddress(submit).then((res) => {
submitLoading.value = false;
if (res && res.success) {
ElMessage.success("添加成功");
addressModalVisible.value = false;
getAddressData();
}
});
}
},
},
mounted() {
this.id = this.$route.query.id;
this.init();
},
};
}
});
}
function getAddress(val) {
if (val.type === "select") {
const paths = val.data.map((item) => item.name).join(",");
const ids = val.data.map((item) => item.id).join(",");
addressForm.consigneeAddressPath = paths;
addressForm.consigneeAddressIdPath = ids;
const coord = val.data[val.data.length - 1].center.split(",");
addressForm.lat = coord[1];
addressForm.lon = coord[0];
} else {
addressForm.consigneeAddressPath = val.data.addr;
addressForm.consigneeAddressIdPath = val.data.addrId;
addressForm.lat = val.data.position.lat;
addressForm.lon = val.data.position.lng;
}
}
function memberAddressRemove(v) {
ElMessageBox.confirm("确定要删除此收货地址?", "删除", { type: "warning" }).then(() => {
API_Member.removeMemberAddress(v.id).then((res) => {
if (res.success) {
ElMessage.success("删除成功");
getAddressData();
}
});
}).catch(() => {});
}
function getAddressData() {
addressLoading.value = true;
API_Member.getMemberAddressData(id.value, addressSearchForm.value)
.then((res) => {
if (res.success) {
addressData.value = res.result.records;
addressTotal.value = res.result.total;
}
})
.finally(() => {
addressLoading.value = false;
});
}
function pointChangePage(v) {
pointSearchForm.value.pageNumber = v;
getPointData();
}
function pointChangePageSize(v) {
pointSearchForm.value.pageNumber = 1;
pointSearchForm.value.pageSize = v;
getPointData();
}
function addressChangePage(v) {
addressSearchForm.value.pageNumber = v;
getAddressData();
}
function addressChangePageSize(v) {
addressSearchForm.value.pageNumber = 1;
addressSearchForm.value.pageSize = v;
getAddressData();
}
function walletChangePage(v) {
walletSearchForm.value.pageNumber = v;
getDepositLogData();
}
function walletChangePageSize(v) {
walletSearchForm.value.pageNumber = 1;
walletSearchForm.value.pageSize = v;
getDepositLogData();
}
function orderChangePage(v) {
orderSearchForm.value.pageNumber = v;
getOrderData();
}
function orderChangePageSize(v) {
orderSearchForm.value.pageNumber = 1;
orderSearchForm.value.pageSize = v;
getOrderData();
}
function selectDateRange(v) {
if (v && v.length === 2) {
orderSearchForm.value.startDate = v[0];
orderSearchForm.value.endDate = v[1];
} else {
delete orderSearchForm.value.startDate;
delete orderSearchForm.value.endDate;
}
}
onMounted(() => {
id.value = route.query.id;
init();
});
</script>
<style lang="scss" scoped>
@import "memberDetail.scss";

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -96,7 +96,7 @@
</el-card>
<el-dialog v-model="descFlag" :title="descTitle" width="500px" destroy-on-close>
<el-form ref="formValidate" :model="formValidate" :rules="ruleValidate" label-width="80px">
<el-form ref="formValidateRef" :model="formValidate" :rules="ruleValidate" label-width="80px">
<el-form-item label="头像">
<img :src="formValidate.face" class="face" />
<el-button
@@ -143,7 +143,7 @@
<el-dialog v-model="picModelFlag" width="1200px">
<ossManage
ref="ossManage"
ref="ossManageRef"
@callback="callbackSelected"
:isComponent="true"
:initialize="picModelFlag"
@@ -153,152 +153,156 @@
</div>
</template>
<script>
<script setup>
import { ref, nextTick, onMounted } from "vue";
import * as API_Member from "@/api/member.js";
import ossManage from "@/views/sys/oss-manage/ossManage";
import multipleMap from "@/components/map/multiple-map";
import { ElMessage, ElMessageBox } from "element-plus";
import { customRouterPush } from "@/utils/filters";
import { md5 } from "@/utils/md5.js";
export default {
name: "memberRecycle",
components: {
ossManage,
multipleMap,
},
data() {
return {
selectedMember: false,
descTitle: "",
descFlag: false,
openSearch: true,
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
order: "desc",
id: "",
username: "",
mobile: "",
disabled: "CLOSE",
},
picModelFlag: false,
formValidate: {},
ruleValidate: {},
data: [],
total: 0,
};
},
methods: {
callback(val) {
this.$emit("callback", val);
},
init() {
this.getData();
},
changePage() {
this.getData();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getData();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getData();
},
editPerm(val) {
this.descTitle = `查看用户 ${val.username}`;
this.descFlag = true;
this.getMemberInfo(val.id);
},
getMemberInfo(id) {
API_Member.getMemberInfoData(id).then((res) => {
if (res.result) {
this.formValidate = res.result;
}
});
},
getData() {
this.loading = true;
API_Member.getMemberListData(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
openPicSelector() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
callbackSelected(val) {
this.picModelFlag = false;
this.formValidate.face = val.url;
},
selectedRegion(val) {
if (val.type === "select") {
const paths = val.data.map((item) => item.name).join(",");
const ids = val.data.map((item) => item.id).join(",");
this.formValidate.region = paths;
this.formValidate.regionId = ids;
} else {
this.formValidate.region = val.data.addr;
this.formValidate.regionId = val.data.addrId;
defineOptions({ name: "memberRecycle" });
const emit = defineEmits(["callback"]);
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
// 模板编辑弹窗 el-form 的 ref(原 ref="formValidate",与 data 同名会互相覆盖,改名为 formValidateRef)
const formValidateRef = ref(null);
// 模板 ossManage 组件的 ref(原 ref="ossManage",与组件 import 同名会冲突,改名为 ossManageRef)
const ossManageRef = ref(null);
const selectedMember = ref(false);
const descTitle = ref("");
const descFlag = ref(false);
const openSearch = ref(true);
const loading = ref(true);
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
order: "desc",
id: "",
username: "",
mobile: "",
disabled: "CLOSE",
});
const picModelFlag = ref(false);
const formValidate = ref({});
const ruleValidate = ref({});
const data = ref([]);
const total = ref(0);
function callback(val) {
emit("callback", val);
}
function init() {
getData();
}
function changePage() {
getData();
}
function changePageSize() {
searchForm.value.pageNumber = 1;
getData();
}
function handleSearch() {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = 20;
getData();
}
function editPerm(val) {
descTitle.value = `查看用户 ${val.username}`;
descFlag.value = true;
getMemberInfo(val.id);
}
function getMemberInfo(id) {
API_Member.getMemberInfoData(id).then((res) => {
if (res.result) {
formValidate.value = res.result;
}
});
}
function getData() {
loading.value = true;
API_Member.getMemberListData(searchForm.value).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function openPicSelector() {
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function callbackSelected(val) {
picModelFlag.value = false;
formValidate.value.face = val.url;
}
function selectedRegion(val) {
if (val.type === "select") {
const paths = val.data.map((item) => item.name).join(",");
const ids = val.data.map((item) => item.id).join(",");
formValidate.value.region = paths;
formValidate.value.regionId = ids;
} else {
formValidate.value.region = val.data.addr;
formValidate.value.regionId = val.data.addrId;
}
}
function detail(row) {
customRouterPush({ name: "member-detail", query: { id: row.id } });
}
function enable(v) {
let params = {
memberIds: [v.id],
disabled: true,
};
ElMessageBox.confirm("确定启用此会员?", "提示", { type: "warning" }).then(() => {
API_Member.updateMemberStatus(params).then((res) => {
if (res.success) {
ElMessage.success("启用成功");
getData();
}
},
detail(row) {
customRouterPush({ name: "member-detail", query: { id: row.id } });
},
enable(v) {
let params = {
memberIds: [v.id],
disabled: true,
};
ElMessageBox.confirm("确定启用此会员?", "提示", { type: "warning" }).then(() => {
API_Member.updateMemberStatus(params).then((res) => {
if (res.success) {
ElMessage.success("启用成功");
this.getData();
}
});
}).catch(() => {});
},
handleSubmitModal() {
const { nickName, sex, username, face, newPassword, id, regionId, region } = this.formValidate;
let time = new Date(this.formValidate.birthday);
let birthday = time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
let submit = {
regionId: regionId,
region: region,
nickName,
sex,
birthday,
face: face || "",
id,
};
if (newPassword) {
submit.password = this.md5(newPassword);
}
API_Member.updateMember(submit).then((res) => {
if (res.result) {
ElMessage.success("修改成功!");
this.descFlag = false;
this.init();
}
});
},
},
mounted() {
this.init();
},
};
});
}).catch(() => {});
}
function handleSubmitModal() {
const { nickName, sex, username, face, newPassword, id, regionId, region } = formValidate.value;
let time = new Date(formValidate.value.birthday);
let birthday = time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
let submit = {
regionId: regionId,
region: region,
nickName,
sex,
birthday,
face: face || "",
id,
};
if (newPassword) {
// 原 this.md5 为 main.js 注册的全局属性,改为具名导入(同 login.vue / userManage.vue)
submit.password = md5(newPassword);
}
API_Member.updateMember(submit).then((res) => {
if (res.result) {
ElMessage.success("修改成功!");
descFlag.value = false;
init();
}
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
.face {

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-dialog v-model="wechatModal" title="微信设置" width="530px">
<el-form ref="wechatFormData" :model="wechatFormData" label-width="100px">
<el-form ref="wechatFormRef" :model="wechatFormData" label-width="100px">
<el-form-item v-if="tab === 'WECHAT'" label="模板名称">
<el-input v-model="wechatFormData.name" maxlength="9" disabled />
</el-form-item>
@@ -105,171 +105,169 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import {
wechatMessageSync,
getWechatMessagePage,
getWechatMessagePage as fetchWechatMessagePage,
editWechatMessageTemplate,
delWechatMessageTemplate,
wechatMPMessageSync,
getWechatMPMessagePage,
getWechatMPMessagePage as fetchWechatMPMessagePage,
editWechatMPMessageTemplate,
delWechatMPMessageTemplate,
} from "@/api/setting";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
title: "wechat-message-manage",
data() {
return {
wechatModal: false,
wechatFormData: {},
wechatMPFormData: {},
tab: "WECHAT",
searchForm: { type: "WECHAT" },
loading: true,
id: "",
weChatSearchForm: { pageNumber: 1, pageSize: 20 },
weChatMPSearchForm: { pageNumber: 1, pageSize: 20 },
weChatData: [],
weChatMPData: [],
weChatTotal: 0,
weChatMPTotal: 0,
};
},
methods: {
init() {
this.getDataList();
},
changePage(v) {
this.searchForm.type = this.tab;
if (this.tab === "WECHAT") {
this.weChatSearchForm.pageNumber = v;
this.getWechatMessagePage();
} else {
this.weChatMPSearchForm.pageNumber = v;
this.getWechatMPMessagePage();
}
},
changePageSize(v) {
this.searchForm.type = this.tab;
if (this.tab === "WECHAT") {
this.weChatSearchForm.pageSize = v;
this.getWechatMessagePage();
} else {
this.weChatMPSearchForm.pageSize = v;
this.getWechatMPMessagePage();
}
},
wechatSettingAlert(v) {
this.wechatFormData = v;
this.id = v.id;
this.wechatModal = true;
},
weChatSync(mp) {
ElMessageBox.confirm("确认要初始化微信小程序消息订阅?", "提示", { type: "warning" }).then(() => {
if (mp === "mp") {
wechatMPMessageSync().then((res) => {
if (res.success) {
ElMessage.success("微信小程序消息订阅初始化");
this.getWechatMPMessagePage();
}
});
} else {
wechatMessageSync().then((res) => {
if (res.success) {
ElMessage.success("微信消息模板初始化成功");
this.getWechatMessagePage();
}
});
}
}).catch(() => {});
},
wechatFormDataEdit() {
this.$refs.wechatFormData.validate((valid) => {
if (valid) {
if (!this.wechatFormData.updateTime) {
this.wechatFormData.updateTime = "";
}
editWechatMessageTemplate(this.id, this.wechatFormData).then((res) => {
if (res.message === "success") {
ElMessage.success("微信模板修改成功");
this.wechatModal = false;
this.getWechatMessagePage();
}
});
}
});
},
wechatMPFormDataEdit() {
this.$refs.wechatFormData.validate((valid) => {
if (valid) {
editWechatMPMessageTemplate(this.id, this.wechatMPFormData).then((res) => {
if (res.message === "success") {
ElMessage.success("微信消息订阅模板修改成功");
this.wechatModal = false;
this.getWechatMPMessagePage();
}
});
}
});
},
delWeChat(v) {
ElMessageBox.confirm("确定删除此模板?", "提示", { type: "warning" }).then(() => {
if (this.tab === "WECHAT") {
delWechatMessageTemplate(v.id).then((res) => {
if (res.success) {
ElMessage.success("微信模板删除成功");
this.getWechatMessagePage();
}
});
} else {
delWechatMPMessageTemplate(v.id).then((res) => {
if (res.success) {
ElMessage.success("微信消息订阅删除成功");
this.getWechatMPMessagePage();
}
});
}
}).catch(() => {});
},
getDataList() {
this.getWechatMessagePage();
},
getWechatMessagePage() {
this.loading = true;
getWechatMessagePage(this.weChatSearchForm).then((res) => {
this.loading = false;
defineOptions({ title: "wechat-message-manage" });
const wechatFormRef = ref(null);
const wechatModal = ref(false);
const wechatFormData = ref({});
const wechatMPFormData = ref({});
const tab = ref("WECHAT");
const searchForm = reactive({ type: "WECHAT" });
const loading = ref(true);
const id = ref("");
const weChatSearchForm = reactive({ pageNumber: 1, pageSize: 20 });
const weChatMPSearchForm = reactive({ pageNumber: 1, pageSize: 20 });
const weChatData = ref([]);
const weChatMPData = ref([]);
const weChatTotal = ref(0);
const weChatMPTotal = ref(0);
onMounted(() => {
init();
});
function init() {
getDataList();
}
function changePage(v) {
searchForm.type = tab.value;
if (tab.value === "WECHAT") {
weChatSearchForm.pageNumber = v;
getWechatMessagePage();
} else {
weChatMPSearchForm.pageNumber = v;
getWechatMPMessagePage();
}
}
function changePageSize(v) {
searchForm.type = tab.value;
if (tab.value === "WECHAT") {
weChatSearchForm.pageSize = v;
getWechatMessagePage();
} else {
weChatMPSearchForm.pageSize = v;
getWechatMPMessagePage();
}
}
function wechatSettingAlert(v) {
wechatFormData.value = v;
id.value = v.id;
wechatModal.value = true;
}
function weChatSync(mp) {
ElMessageBox.confirm("确认要初始化微信小程序消息订阅?", "提示", { type: "warning" }).then(() => {
if (mp === "mp") {
wechatMPMessageSync().then((res) => {
if (res.success) {
this.weChatData = res.result.records;
this.weChatTotal = res.result.total;
ElMessage.success("微信小程序消息订阅初始化");
getWechatMPMessagePage();
}
});
},
getWechatMPMessagePage() {
this.loading = true;
getWechatMPMessagePage(this.weChatMPSearchForm).then((res) => {
this.loading = false;
} else {
wechatMessageSync().then((res) => {
if (res.success) {
this.weChatMPData = res.result.records;
this.weChatMPTotal = res.result.total;
ElMessage.success("微信消息模板初始化成功");
getWechatMessagePage();
}
});
},
tabPaneChange(tab) {
const v = tab.paneName || this.tab;
this.searchForm.type = v;
if (v === "WECHAT") {
this.getWechatMessagePage();
} else if (v === "WECHATMP") {
this.getWechatMPMessagePage();
}
}).catch(() => {});
}
function wechatFormDataEdit() {
wechatFormRef.value.validate((valid) => {
if (valid) {
if (!wechatFormData.value.updateTime) {
wechatFormData.value.updateTime = "";
}
},
},
mounted() {
this.init();
},
};
editWechatMessageTemplate(id.value, wechatFormData.value).then((res) => {
if (res.message === "success") {
ElMessage.success("微信模板修改成功");
wechatModal.value = false;
getWechatMessagePage();
}
});
}
});
}
function wechatMPFormDataEdit() {
wechatFormRef.value.validate((valid) => {
if (valid) {
editWechatMPMessageTemplate(id.value, wechatMPFormData.value).then((res) => {
if (res.message === "success") {
ElMessage.success("微信消息订阅模板修改成功");
wechatModal.value = false;
getWechatMPMessagePage();
}
});
}
});
}
function delWeChat(v) {
ElMessageBox.confirm("确定删除此模板?", "提示", { type: "warning" }).then(() => {
if (tab.value === "WECHAT") {
delWechatMessageTemplate(v.id).then((res) => {
if (res.success) {
ElMessage.success("微信模板删除成功");
getWechatMessagePage();
}
});
} else {
delWechatMPMessageTemplate(v.id).then((res) => {
if (res.success) {
ElMessage.success("微信消息订阅删除成功");
getWechatMPMessagePage();
}
});
}
}).catch(() => {});
}
function getDataList() {
getWechatMessagePage();
}
function getWechatMessagePage() {
loading.value = true;
fetchWechatMessagePage(weChatSearchForm).then((res) => {
loading.value = false;
if (res.success) {
weChatData.value = res.result.records;
weChatTotal.value = res.result.total;
}
});
}
function getWechatMPMessagePage() {
loading.value = true;
fetchWechatMPMessagePage(weChatMPSearchForm).then((res) => {
loading.value = false;
if (res.success) {
weChatMPData.value = res.result.records;
weChatMPTotal.value = res.result.total;
}
});
}
function tabPaneChange(tabPane) {
const v = tabPane.paneName || tab.value;
searchForm.type = v;
if (v === "WECHAT") {
getWechatMessagePage();
} else if (v === "WECHATMP") {
getWechatMPMessagePage();
}
}
</script>
<style scoped>
.link-text {

View File

@@ -19,7 +19,7 @@
<el-tab-pane label="积分列表" name="pointList">
<el-card class="point-content-card">
<el-form
ref="memberSearchForm"
ref="memberSearchFormRef"
:model="memberSearchForm"
inline
label-width="70px"
@@ -131,7 +131,7 @@
<el-tab-pane label="积分增减记录" name="pointChangeRecord">
<el-card class="point-content-card">
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -203,132 +203,135 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Member from "@/api/member.js";
import { customRouterPush } from "@/utils/filters";
export default {
name: "point",
data() {
return {
activeTab: "pointList",
loading: true,
memberLoading: false,
pointsStatistics: {
totalPoint: 0,
unUsedPoint: 0,
},
searchForm: {
pageNumber: 1,
pageSize: 20,
},
memberSearchForm: {
pageNumber: 1,
pageSize: 20,
nickName: "",
username: "",
disabled: "",
minPoint: null,
maxPoint: null,
},
data: [],
memberData: [],
memberTotal: 0,
total: 0,
};
},
methods: {
callback(val) {
this.$emit("callback", val);
},
detail(row) {
customRouterPush({ name: "member-detail", query: { id: row.id } });
},
init() {
this.getStatistics();
this.getMemberList();
this.getData();
},
getStatistics() {
API_Member.queryMemberPointsStatistics().then((res) => {
if (res && res.success && res.result) {
this.pointsStatistics = {
totalPoint: res.result.totalPoint || 0,
unUsedPoint: res.result.unUsedPoint || 0,
};
}
});
},
formatNumber(value) {
const numericValue = Number(value || 0);
if (!Number.isFinite(numericValue)) return "0";
return numericValue.toLocaleString();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getData();
},
handleMemberSearch() {
this.memberSearchForm.pageNumber = 1;
this.getMemberList();
},
changeMemberPage(v) {
this.memberSearchForm.pageNumber = v;
this.getMemberList();
},
changeMemberPageSize(v) {
this.memberSearchForm.pageNumber = 1;
this.memberSearchForm.pageSize = v;
this.getMemberList();
},
getMemberList() {
this.memberLoading = true;
const params = {
pageNumber: this.memberSearchForm.pageNumber,
pageSize: this.memberSearchForm.pageSize,
nickName: this.memberSearchForm.nickName || undefined,
username: this.memberSearchForm.username || undefined,
disabled: this.memberSearchForm.disabled || undefined,
minPoint: this.memberSearchForm.minPoint,
maxPoint: this.memberSearchForm.maxPoint,
defineOptions({ name: "point" });
const emit = defineEmits(["callback"]);
// 模板 ref,script 内未使用
const memberSearchFormRef = ref(null);
const searchFormRef = ref(null);
const table = ref(null);
const activeTab = ref("pointList");
const loading = ref(true);
const memberLoading = ref(false);
const pointsStatistics = ref({
totalPoint: 0,
unUsedPoint: 0,
});
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
});
const memberSearchForm = reactive({
pageNumber: 1,
pageSize: 20,
nickName: "",
username: "",
disabled: "",
minPoint: null,
maxPoint: null,
});
const data = ref([]);
const memberData = ref([]);
const memberTotal = ref(0);
const total = ref(0);
function callback(val) {
emit("callback", val);
}
function detail(row) {
customRouterPush({ name: "member-detail", query: { id: row.id } });
}
function init() {
getStatistics();
getMemberList();
getData();
}
function getStatistics() {
API_Member.queryMemberPointsStatistics().then((res) => {
if (res && res.success && res.result) {
pointsStatistics.value = {
totalPoint: res.result.totalPoint || 0,
unUsedPoint: res.result.unUsedPoint || 0,
};
API_Member.getMemberListData(params)
.then((res) => {
this.memberLoading = false;
if (res && res.success && res.result && res.result.records) {
this.memberData = res.result.records;
this.memberTotal = res.result.total;
}
})
.catch(() => {
this.memberLoading = false;
});
},
getData() {
this.loading = true;
API_Member.getHistoryPointData(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
},
mounted() {
this.init();
},
};
}
});
}
function formatNumber(value) {
const numericValue = Number(value || 0);
if (!Number.isFinite(numericValue)) return "0";
return numericValue.toLocaleString();
}
function changePage(v) {
searchForm.pageNumber = v;
getData();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getData();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getData();
}
function handleMemberSearch() {
memberSearchForm.pageNumber = 1;
getMemberList();
}
function changeMemberPage(v) {
memberSearchForm.pageNumber = v;
getMemberList();
}
function changeMemberPageSize(v) {
memberSearchForm.pageNumber = 1;
memberSearchForm.pageSize = v;
getMemberList();
}
function getMemberList() {
memberLoading.value = true;
const params = {
pageNumber: memberSearchForm.pageNumber,
pageSize: memberSearchForm.pageSize,
nickName: memberSearchForm.nickName || undefined,
username: memberSearchForm.username || undefined,
disabled: memberSearchForm.disabled || undefined,
minPoint: memberSearchForm.minPoint,
maxPoint: memberSearchForm.maxPoint,
};
API_Member.getMemberListData(params)
.then((res) => {
memberLoading.value = false;
if (res && res.success && res.result && res.result.records) {
memberData.value = res.result.records;
memberTotal.value = res.result.total;
}
})
.catch(() => {
memberLoading.value = false;
});
}
function getData() {
loading.value = true;
API_Member.getHistoryPointData(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
.points-statistics-card {

View File

@@ -55,7 +55,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="售后原因" prop="reason">
<el-input v-model="form.reason" maxlength="20" clearable style="width: 100%" />
</el-form-item>
@@ -67,116 +67,113 @@
</el-dialog>
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Order from "@/api/order";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
data() {
return {
activeTab: "RETURN_MONEY",
modalVisible: false,
modalTitle: "",
loading: true,
submitLoading: false,
form: { reason: "" },
formValidate: {
reason: [{ required: true, message: "请输入售后原因", trigger: "blur" }],
},
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
serviceType: "RETURN_MONEY",
},
data: [],
total: 0,
};
},
methods: {
onTabClick(tab) {
this.handleClickType(tab.paneName);
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
handleClickType(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.searchForm.serviceType = v;
this.getDataList();
},
getDataList() {
this.loading = true;
API_Order.getAfterSaleReasonPage(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
add() {
this.form.reason = "";
this.modalVisible = true;
this.modalTitle = "添加售后原因";
},
edit(v) {
this.form.reason = v.reason;
this.form.id = v.id;
this.modalVisible = true;
this.modalTitle = "修改售后原因";
},
handleSubmit() {
this.form.serviceType = this.searchForm.serviceType;
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
if (this.modalTitle == "添加售后原因") {
delete this.form.id;
API_Order.addAfterSaleReason(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("添加成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
API_Order.editAfterSaleReason(this.form.id, this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("修改成功");
this.getDataList();
this.modalVisible = false;
}
});
}
}
});
},
remove(v) {
ElMessageBox.confirm("确认要删除此售后原因?", "确认删除", { type: "warning" }).then(() => {
API_Order.delAfterSaleReason(v.id).then((res) => {
const activeTab = ref("RETURN_MONEY");
const modalVisible = ref(false);
const modalTitle = ref("");
const loading = ref(true);
const submitLoading = ref(false);
const form = reactive({ reason: "" });
const formValidate = reactive({
reason: [{ required: true, message: "请输入售后原因", trigger: "blur" }],
});
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
serviceType: "RETURN_MONEY",
});
const data = ref([]);
const total = ref(0);
const formRef = ref(null);
function onTabClick(tab) {
handleClickType(tab.paneName);
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getDataList();
}
function handleClickType(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
searchForm.serviceType = v;
getDataList();
}
function getDataList() {
loading.value = true;
API_Order.getAfterSaleReasonPage(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function add() {
form.reason = "";
modalVisible.value = true;
modalTitle.value = "添加售后原因";
}
function edit(v) {
form.reason = v.reason;
form.id = v.id;
modalVisible.value = true;
modalTitle.value = "修改售后原因";
}
function handleSubmit() {
form.serviceType = searchForm.serviceType;
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
if (modalTitle.value == "添加售后原因") {
delete form.id;
API_Order.addAfterSaleReason(form).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("售后原因已删除");
this.getDataList();
ElMessage.success("添加成功");
getDataList();
modalVisible.value = false;
}
});
}).catch(() => {});
},
},
mounted() {
this.getDataList();
},
};
} else {
API_Order.editAfterSaleReason(form.id, form).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("修改成功");
getDataList();
modalVisible.value = false;
}
});
}
}
});
}
function remove(v) {
ElMessageBox.confirm("确认要删除此售后原因?", "确认删除", { type: "warning" }).then(() => {
API_Order.delAfterSaleReason(v.id).then((res) => {
if (res.success) {
ElMessage.success("售后原因已删除");
getDataList();
}
});
}).catch(() => {});
}
onMounted(() => {
getDataList();
});
</script>
<style scoped>
.link-text {

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -167,194 +167,193 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import * as API_Order from "@/api/order";
import { unitPrice, 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: "",
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" });
// 模板 ref(模板 ref="searchForm" 与 data 同名,改名 searchFormRef 避免互相覆盖)
const searchFormRef = ref(null);
// 模板 ref="table"
const table = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
orderSn: "",
memberName: "",
serviceStatus: "",
storeName: "",
sn: "",
keywords: "",
});
const selectDate = ref(null);
const data = ref([]);
const total = ref(0);
const currentStatus = ref("");
const afterSaleNumData = ref({});
const serviceStatusWithCount = computed(() => {
return [
{ title: "全部", value: "" },
{
title: `申请售后${afterSaleNumData.value.applyNum ? "(" + afterSaleNumData.value.applyNum + ")" : ""}`,
value: "APPLY",
},
},
methods: {
unitPrice,
serviceTypeText(type) {
const map = {
RETURN_MONEY: "退款",
RETURN_GOODS: "退货",
EXCHANGE_GOODS: "换货",
};
return map[type] || type || "-";
{
title: `通过售后${afterSaleNumData.value.passNum ? "(" + afterSaleNumData.value.passNum + ")" : ""}`,
value: "PASS",
},
serviceTypeTagType(type) {
const map = {
RETURN_MONEY: "primary",
RETURN_GOODS: "warning",
EXCHANGE_GOODS: "success",
};
return map[type] || "info";
{
title: `拒绝售后${afterSaleNumData.value.refuseNum ? "(" + afterSaleNumData.value.refuseNum + ")" : ""}`,
value: "REFUSE",
},
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.buyerReturnNum ? "(" + afterSaleNumData.value.buyerReturnNum + ")" : ""}`,
value: "BUYER_RETURN",
},
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.sellerConfirmNum ? "(" + afterSaleNumData.value.sellerConfirmNum + ")" : ""}`,
value: "SELLER_CONFIRM",
},
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",
},
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
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;
}
});
},
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;
customRouterPush({
name: "after-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() {
this.init();
},
};
];
});
function serviceTypeText(type) {
const map = {
RETURN_MONEY: "退款",
RETURN_GOODS: "退货",
EXCHANGE_GOODS: "换货",
};
return map[type] || type || "-";
}
function serviceTypeTagType(type) {
const map = {
RETURN_MONEY: "primary",
RETURN_GOODS: "warning",
EXCHANGE_GOODS: "success",
};
return map[type] || "info";
}
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;
}
});
}
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: "after-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(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -332,185 +332,184 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute } from "vue-router";
import * as API_Order from "@/api/order";
import vueQr from "vue-qr";
import { ElMessage, ElMessageBox } from "element-plus";
export default {
components: {
"vue-qr": vueQr,
},
name: "orderDetail",
data() {
return {
sn: "", // 订单号
logisticsModal: false, //查询物流模态框
logisticsInfo: {}, //物流信息
afterSaleInfo: {}, // 售后信息
afterSaleImage: [], //会员申诉图片
submitLoading: false, // 添加或编辑提交状态
storeMsg: {}, // 商家地址信息
//商家处理意见
params: {
serviceStatus: "PASS",
remark: "",
},
//平台退款
refundPriceForm: {
remark: "",
},
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: "orderDetail" });
const route = useRoute();
const sn = ref(""); // 订单号
const logisticsModal = ref(false); //查询物流模态框
const logisticsInfo = ref({}); //物流信息
const afterSaleInfo = ref({}); // 售后信息
const afterSaleImage = ref([]); //会员申诉图片
const submitLoading = ref(false); // 添加或编辑提交状态
const storeMsg = ref({}); // 商家地址信息
// 原代码未在 data 声明、直接挂 this 上的字段,补声明避免 ReferenceError
let loading = false;
//商家处理意见
const params = reactive({
serviceStatus: "PASS",
remark: "",
});
//平台退款
const refundPriceForm = reactive({
remark: "",
});
const afterSaleStatusList = ref([
// 售后状态列表
{
name: "申请中",
status: "APPLY",
},
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: "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",
},
]);
// 获取售后详情
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;
}
});
}
}
});
},
//平台退款
refundPriceSubmit() {
if (this.refundPriceForm.remark == "") {
ElMessage.error("请输入退款备注");
return;
}
ElMessageBox.confirm("请确认退款?", "确认退款", { type: "warning" }).then(() => {
API_Order.refundPrice(this.sn, this.refundPriceForm).then((res) => {
if (res.success) {
ElMessage.success("收款成功");
this.getDetail();
storeMsg.value = obj;
}
});
}).catch(() => {});
},
//查询物流
logisticsSeller() {
this.logisticsModal = true;
API_Order.getAfterSaleTraces(this.sn).then((res) => {
if (res.success && res.result != null) {
this.logisticsInfo = res.result;
}
});
},
//关闭物流弹出框
logisticsClose() {
this.logisticsModal = false;
},
//回复
handleSubmit() {
if (this.params.remark == "") {
ElMessage.error("请输入备注信息");
return;
}
if (this.params.actualRefundPrice == "") {
ElMessage.error("请输入退款金额");
return;
}
});
}
//平台退款
function refundPriceSubmit() {
if (refundPriceForm.remark == "") {
ElMessage.error("请输入退款备注");
return;
}
ElMessageBox.confirm("请确认退款?", "确认退款", { type: "warning" }).then(() => {
API_Order.refundPrice(sn.value, refundPriceForm).then((res) => {
if (res.success) {
ElMessage.success("收款成功");
getDetail();
}
API_Order.afterSaleSellerReview(this.sn, this.params).then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("审核成功");
this.params.remark = "";
this.getDetail();
}
});
},
filterOrderStatus(status) {
// 获取订单状态中文
const ob = this.afterSaleStatusList.filter((e) => {
return 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;
}
});
return flag;
},
},
mounted() {
this.sn = this.$route.query.sn;
this.getDetail();
},
};
});
}).catch(() => {});
}
//查询物流
function logisticsSeller() {
logisticsModal.value = true;
API_Order.getAfterSaleTraces(sn.value).then((res) => {
if (res.success && res.result != null) {
logisticsInfo.value = res.result;
}
});
}
//关闭物流弹出框
function logisticsClose() {
logisticsModal.value = false;
}
//回复
function handleSubmit() {
if (params.remark == "") {
ElMessage.error("请输入备注信息");
return;
}
if (params.actualRefundPrice == "") {
ElMessage.error("请输入退款金额");
return;
}
API_Order.afterSaleSellerReview(sn.value, params).then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("审核成功");
params.remark = "";
getDetail();
}
});
}
function filterOrderStatus(status) {
// 获取订单状态中文
const ob = afterSaleStatusList.value.filter((e) => {
return 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 {

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -104,97 +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) {
customRouterPush({
name: "order-complaint-detail",
query: { id: v.id },
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "orderComplaint" });
const route = useRoute();
const searchFormRef = ref(null);
const table = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
});
const data = ref([]);
const total = ref(0);
watch(
() => route.query,
() => {
getDataList();
}
);
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 },
});
}
onMounted(() => {
init();
});
</script>
<style scoped>

View File

@@ -203,140 +203,141 @@
</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 { ElMessage } from "element-plus";
import { unitPrice } from "@/utils/filters";
defineOptions({ name: "orderComplaintDetail" });
const route = useRoute();
const router = useRouter();
const emptyComplaint = () => ({
id: "",
orderComplaintCommunications: [],
});
export default {
name: "orderComplaintDetail",
data() {
return {
loading: false,
id: "",
complaintInfo: emptyComplaint(),
images: [],
appealImages: [],
submitLoading: false,
params: {
content: "",
complainId: "",
},
arbitrationParams: {
arbitrationResult: "",
},
arbitrationResultShow: false,
};
},
methods: {
unitPrice,
complainStatusText(v) {
const map = {
NEW: "新投诉",
CANCEL: "已撤销",
WAIT_APPEAL: "待申诉",
COMMUNICATION: "对话中",
WAIT_ARBITRATION: "等待仲裁",
COMPLETE: "已完成",
};
return map[v] || v || "-";
},
handleStoreComplaint() {
API_Order.storeComplain({
complainStatus: "WAIT_APPEAL",
complainId: this.complaintInfo.id,
}).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
this.getDetail();
}
});
},
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" });
},
arbitrationHandle() {
this.arbitrationResultShow = true;
},
arbitrationHandleSubmit() {
if (!this.arbitrationParams.arbitrationResult) {
ElMessage.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 arbitrationParams = reactive({
arbitrationResult: "",
});
const arbitrationResultShow = ref(false);
function complainStatusText(v) {
const map = {
NEW: "新投诉",
CANCEL: "已撤销",
WAIT_APPEAL: "待申诉",
COMMUNICATION: "对话中",
WAIT_ARBITRATION: "等待仲裁",
COMPLETE: "已完成",
};
return map[v] || v || "-";
}
function handleStoreComplaint() {
API_Order.storeComplain({
complainStatus: "WAIT_APPEAL",
complainId: complaintInfo.value.id,
}).then((res) => {
if (res.success) {
ElMessage.success("操作成功");
getDetail();
}
});
}
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;
API_Order.orderComplete(this.id, this.arbitrationParams)
.then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.success("仲裁成功");
this.arbitrationParams.arbitrationResult = "";
this.arbitrationResultShow = false;
this.getDetail();
}
})
.catch(() => {
this.submitLoading = false;
});
},
handleSubmit() {
if (!this.params.content) {
ElMessage.error("请填写对话内容");
return;
})
.catch(() => {
loading.value = false;
});
}
function returnDataList() {
router.push({ name: "orderComplaint" });
}
function arbitrationHandle() {
arbitrationResultShow.value = true;
}
function arbitrationHandleSubmit() {
if (!arbitrationParams.arbitrationResult) {
ElMessage.error("请填写仲裁内容");
return;
}
submitLoading.value = true;
API_Order.orderComplete(id.value, arbitrationParams)
.then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("仲裁成功");
arbitrationParams.arbitrationResult = "";
arbitrationResultShow.value = false;
getDetail();
}
this.submitLoading = true;
this.params.complainId = this.id;
API_Order.addOrderCommunication(this.params)
.then((res) => {
this.submitLoading = false;
if (res.success) {
ElMessage.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) {
ElMessage.error("请填写对话内容");
return;
}
submitLoading.value = true;
params.complainId = id.value;
API_Order.addOrderCommunication(params)
.then((res) => {
submitLoading.value = false;
if (res.success) {
ElMessage.success("对话成功");
params.content = "";
getDetail();
}
})
.catch(() => {
submitLoading.value = false;
});
}
onMounted(() => {
id.value = route.query.id;
getDetail();
});
watch(
() => route.query.id,
(val) => {
id.value = val;
getDetail();
}
);
</script>
<style lang="scss" scoped>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -111,62 +111,60 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Order from "@/api/order";
export default {
name: "paymentLog",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
sn: "",
payStatus: "",
startDate: "",
endDate: "",
},
times: [],
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 = 20;
this.getDataList();
},
changeDate(val) {
this.searchForm.startDate = val[0];
this.searchForm.endDate = val[1];
},
getDataList() {
this.loading = true;
API_Order.paymentLog(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "paymentLog" });
const searchFormRef = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
sn: "",
payStatus: "",
startDate: "",
endDate: "",
});
const times = ref([]);
const data = ref([]);
const total = ref(0);
function init() {
getDataList();
}
function changePage() {
getDataList();
}
function changePageSize() {
searchForm.pageNumber = 1;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function changeDate(val) {
searchForm.startDate = val[0];
searchForm.endDate = val[1];
}
function getDataList() {
loading.value = true;
API_Order.paymentLog(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -2,7 +2,7 @@
<div class="search">
<el-card>
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -108,71 +108,68 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Order from "@/api/order";
import { unitPrice } from "@/utils/filters";
export default {
name: "refundLog",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
orderSn: "",
isRefund: "",
},
selectDate: null,
data: [],
total: 0,
};
},
methods: {
unitPrice,
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();
},
selectDateRange(v) {
if (v && v[0] && v[1]) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
getDataList() {
this.loading = true;
API_Order.refundLog(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "refundLog" });
const searchFormRef = ref(null);
const loading = ref(true);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
orderSn: "",
isRefund: "",
});
const selectDate = ref(null);
const data = ref([]);
const total = ref(0);
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 selectDateRange(v) {
if (v && v[0] && v[1]) {
searchForm.startDate = v[0];
searchForm.endDate = v[1];
} else {
searchForm.startDate = "";
searchForm.endDate = "";
}
}
function getDataList() {
loading.value = true;
API_Order.refundLog(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
});
</script>

View File

@@ -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,107 +126,106 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import * as API_Order from "@/api/order";
import { ElMessage, ElMessageBox } from "element-plus";
import { customRouterPush } from "@/utils/filters";
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];
defineOptions({ name: "fictitiousOrderList" });
const searchFormRef = ref(null);
const tableRef = ref(null);
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("");
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) {
ElMessageBox.confirm("您确定要收款吗?", "确认收款", { type: "warning" }).then(() => {
API_Order.orderPay(v.sn).then((res) => {
if (res.success) {
ElMessage.success("收款成功");
getDataList();
}
},
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;
}
});
},
confirmPrice(v) {
ElMessageBox.confirm("您确定要收款吗?", "确认收款", { type: "warning" }).then(() => {
API_Order.orderPay(v.sn).then((res) => {
if (res.success) {
ElMessage.success("收款成功");
this.getDataList();
}
});
}).catch(() => {});
},
detail(v) {
let sn = v.sn;
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();
},
};
});
}).catch(() => {});
}
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>

View File

@@ -357,7 +357,7 @@
</div>
<el-dialog v-model="modal" title="修改金额" width="530px">
<el-form ref="modifyPriceForm" :model="modifyPriceForm" label-width="70px" :rules="modifyPriceValidate" @submit.prevent>
<el-form ref="modifyPriceFormRef" :model="modifyPriceForm" label-width="70px" :rules="modifyPriceValidate" @submit.prevent>
<el-form-item label="订单金额" prop="price">
<el-input-number v-model="modifyPriceForm.price" :min="0" :max="999999" style="width: 120px" />
<span class="ml_10">元</span>
@@ -370,7 +370,7 @@
</el-dialog>
<el-dialog v-model="orderCancelModal" title="订单取消" width="530px">
<el-form ref="orderCancelForm" :model="orderCancelForm" label-width="100px" :rules="orderCancelValidate">
<el-form ref="orderCancelFormRef" :model="orderCancelForm" label-width="100px" :rules="orderCancelValidate">
<el-form-item label="取消原因" prop="reason">
<el-input v-model="orderCancelForm.reason" type="textarea" :rows="3" placeholder="请输入取消原因" />
</el-form-item>
@@ -382,7 +382,7 @@
</el-dialog>
<el-dialog v-model="addressModal" title="修改收件信息" width="530px">
<el-form ref="addressForm" :model="addressForm" label-width="100px" :rules="addressRule">
<el-form ref="addressFormRef" :model="addressForm" label-width="100px" :rules="addressRule">
<el-form-item label="收件人" prop="consigneeName">
<el-input v-model="addressForm.consigneeName" maxlength="20" />
</el-form-item>
@@ -518,7 +518,9 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import * as API_Order from "@/api/order";
import { platformRefund, noShowRefund, getAppointmentOrderDetail } from "@/api/appointment";
import * as RegExp from "@/libs/RegExp.js";
@@ -529,457 +531,456 @@ import { printElement } from "@/utils/print";
import { ElMessage, ElMessageBox } from "element-plus";
import { unitPrice, clientTypeWay } from "@/utils/filters";
import { formatCardKeyStatus } from "@/constants/cardKey";
export default {
name: "orderList",
components: {
multipleMap,
"vue-qr": vueQr,
defineOptions({ name: "orderList" });
const route = useRoute();
const router = useRouter();
// 模板 ref(与表单 data 同名的改为 xxxRef,避免互相覆盖)
const modifyPriceFormRef = ref(null);
const orderCancelFormRef = ref(null);
const addressFormRef = ref(null);
const map = ref(null);
const typeList = ref([]);
const showPrices = ref(false);
const printHiddenFlag = ref(false);//隐藏信息
const loading = ref(false); //加载表格
const addr = ref(""); //地区
let regionId = ""; // 原代码未在 data 声明直接挂 this 的历史遗留字段
const orderLogInfo = ref([]); //订单日志数据
const orderLogModal = ref(false); //弹出调整价格框
const checkedLogistics = ref([]); //选中的物流公司集合
const allowOperation = ref({}); //订单可才做选项
const sn = ref(""); //订单编号
const orderInfo = ref({
order: {
priceDetailDTO: {},
},
computed: {
/** 电子卡券订单:无物流区,卡密来自 orderItems[].cardKeys */
isECouponOrder() {
const t = this.orderInfo?.order?.orderType;
return t === "E_COUPON" || this.$route.query.orderType === "E_COUPON";
});
const appointmentExt = ref({});
const modal = ref(false); //弹出调整价格框
const searchForm = reactive({
pageNumber: 1, // 当前页数
pageSize: 100, // 页面大小
orderSn: "", //订单sn
});
//调整价格表单
const modifyPriceForm = reactive({
price: 0,
});
//订单取消表单
const orderCancelForm = reactive({
reason: "",
});
//弹出订单取消框
const orderCancelModal = ref(false);
//订单发货
const orderDeliveryForm = reactive({
logisticsNo: "", //发货单号
logisticsId: "", //物流公司
});
//验证要调整的订单金额
const modifyPriceValidate = {
reason: [
{ required: true, message: "请输入大于0小于99999的合法金额" },
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: "请输入大于0小于9999的合法金额",
trigger: "change",
},
isVirtualOrder() {
const t = this.orderInfo?.order?.orderType;
return t === "VIRTUAL" || this.$route.query.orderType === "VIRTUAL";
},
isNonPhysicalOrder() {
return this.isVirtualOrder || this.isECouponOrder || this.isAppointmentOrder;
},
isAppointmentOrder() {
const t = this.orderInfo?.order?.orderType;
return t === "APPOINTMENT" || this.$route.query.orderType === "APPOINTMENT";
},
appointmentSlotText() {
const v = this.appointmentExt || {};
if (!v.slotDate) return "";
const date = String(v.slotDate).slice(0, 10);
const slot = v.slotStartTime && v.slotEndTime ? `${v.slotStartTime}-${v.slotEndTime}` : "";
return [date, slot].filter(Boolean).join(" ");
},
appointmentFormGroups() {
return this.parseFormAnswerGroups(this.appointmentExt?.formAnswerJson);
},
canPlatformRefund() {
const order = this.orderInfo?.order;
return order && order.payStatus === "PAID" && order.orderStatus !== "CANCELLED";
},
canNoShowRefund() {
return this.canPlatformRefund;
},
ecouponCardKeyDelivered() {
return (this.data || []).some((item) => item.cardKeyDelivered);
},
ecouponCardKeyRows() {
const rows = [];
(this.data || []).forEach((item) => {
(item.cardKeys || []).forEach((ck) => {
rows.push({ ...ck });
});
});
return rows;
},
},
data () {
return {
typeList: [],
showPrices: false,
printHiddenFlag: false,//隐藏信息
loading: false, //加载表格
addr: "", //地区
orderLogInfo: [], //订单日志数据
orderLogModal: false, //弹出调整价格框
checkedLogistics: [], //选中的物流公司集合
allowOperation: {}, //订单可才做选项
sn: "", //订单编号
orderInfo: {
order: {
priceDetailDTO: {},
},
},
appointmentExt: {},
modal: false, //弹出调整价格框
searchForm: {
pageNumber: 1, // 当前页数
pageSize: 100, // 页面大小
orderSn: "", //订单sn
},
//调整价格表单
modifyPriceForm: {
price: 0,
},
//订单取消表单
orderCancelForm: {
reason: "",
},
//弹出订单取消框
orderCancelModal: false,
//订单发货
orderDeliveryForm: {
logisticsNo: "", //发货单号
logisticsId: "", //物流公司
},
//验证要调整的订单金额
modifyPriceValidate: {
reason: [
{ required: true, message: "请输入大于0小于99999的合法金额" },
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: "请输入大于0小于9999的合法金额",
trigger: "change",
},
],
},
//验证取消订单原因
orderCancelValidate: {
reason: [{ required: true, message: "取消原因不能为空", trigger: "blur" }],
},
addressModal: false, //弹出修改收件信息框
printModal: false,
platformRefundModal: false,
noShowRefundModal: false,
refundLoading: false,
platformRefundForm: {
refundType: "UNSERVED",
refundAmount: 0,
reason: "",
},
noShowRefundForm: {
refundAmount: 0,
reason: "",
},
//收件地址表单
addressForm: {
consigneeName: "",
consigneeMobile: "",
consigneeDetail: "",
consigneeAddressPath: "",
consigneeAddressIdPath: "",
},
orderDeliverFormValidate: {
logisticsNo: [{ required: true, message: "发货单号不能为空", trigger: "change" }],
logisticsId: [{ required: true, message: "请选择物流公司", trigger: "blur" }],
},
addressRule: {
consigneeName: [
{ required: true, message: "收货人姓名不能为空", trigger: "blur" },
],
consigneeMobile: [
{ required: true, message: "联系方式不能为空", trigger: "blur" },
{
pattern: RegExp.mobile,
trigger: "blur",
message: "请输入正确的手机号",
},
],
consigneeDetail: [
{ required: true, message: "详细地址不能为空", trigger: "blur" },
],
},
data: [], // 表单数据
};
},
watch: {
$route (to, from) {
this.$router.go(0);
},
},
methods: {
unitPrice,
clientTypeWay,
formatCardKeyStatus,
copyCardKey(row) {
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
ElMessage.success("已复制到剪贴板");
});
} else {
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
ElMessage.success("已复制到剪贴板");
}
},
getPromotionText(row) {
let resultText = "";
if (row && row.promotionType) {
const type = row.promotionType.split(",");
if (type.indexOf("PINTUAN") != -1) resultText += "拼团 ";
if (type.indexOf("SECKILL") != -1) resultText += "秒杀 ";
if (type.indexOf("COUPON") != -1) resultText += "优惠券 ";
if (type.indexOf("FULL_DISCOUNT") != -1) resultText += "满减 ";
if (type.indexOf("POINTS_GOODS") != -1) resultText += "积分商品 ";
}
return resultText === "" ? "未参与" : resultText;
},
gotoHomes () {
return false
},
//确认收款
confirmPrice () {
ElMessageBox.confirm(
"您确定要收款吗?线下收款涉及库存变更,需异步进行,等待约一分钟刷新列表查看",
"提示",
{ type: "warning" }
).then(() => {
API_Order.orderPay(this.sn).then((res) => {
if (res.success) {
ElMessage.success("收款成功");
this.getDataList();
}
});
}).catch(() => {});
},
getOrderPrice () {
if (this.showPrices) {
this.showPrices = false
} else if (!this.showPrices) {
this.showPrices = true
}
},
getContentPrice () {
for (let i = 0; i < this.typeList.length; i++) {
for (let j = i + 1; j < this.typeList.length; j++) {
if (this.typeList[i].promotionId === this.typeList[j].promotionId) {
this.typeList[i].discountPrice = this.typeList[i].discountPrice + this.typeList[j].discountPrice
this.typeList.splice(j, 1)
}
}
}
if (this.typeList.length >= 3) {
this.getContentPrice()
}
},
// 获取订单详情
getDataList () {
this.loading = true;
API_Order.orderDetail(this.sn).then((res) => {
this.loading = false;
if (res.success) {
this.orderInfo = res.result;
this.allowOperation = res.result.allowOperationVO;
this.data = res.result.orderItems;
this.typeList = JSON.parse(JSON.stringify(res.result.order.priceDetailDTO.discountPriceDetail));
this.getContentPrice()
this.getOrderPrice()
if (this.isAppointmentOrder) {
this.loadAppointmentExt();
}
}
});
},
loadAppointmentExt() {
getAppointmentOrderDetail(this.sn)
.then((res) => {
if (res.success) {
this.appointmentExt = res.result || {};
}
})
.catch(() => {
this.appointmentExt = {};
});
},
parseFormAnswerGroups(formAnswerJson) {
if (!formAnswerJson) return [];
let data = formAnswerJson;
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
return [];
}
}
const fields = Array.isArray(data?.fields) ? data.fields : [];
const answers = Array.isArray(data?.answers) ? data.answers : [];
return answers
.map((group) => {
const map = group && typeof group === "object" && !Array.isArray(group) ? group : {};
const source = fields.length
? fields
: Object.keys(map).map((key) => ({ fieldKey: key, fieldLabel: key }));
return source
.map((field) => ({
fieldKey: field.fieldKey,
fieldLabel: field.fieldLabel || field.fieldKey,
value: map[field.fieldKey] ?? "",
}))
.filter((row) => row.value !== "" && row.value != null);
})
.filter((rows) => rows.length);
},
modifyPrice () {
//默认要修改的金额为订单总金额
this.modifyPriceForm.price = this.orderInfo.order.flowPrice;
this.modal = true;
},
//修改订单金额提交
modifyPriceSubmit () {
this.$refs.modifyPriceForm.validate((valid) => {
if (valid) {
API_Order.updateOrderPrice(this.sn, this.modifyPriceForm).then((res) => {
if (res.success) {
ElMessage.success("修改订单金额成功");
this.modal = false;
this.getDataList();
}
});
}
});
},
// 选中的地址
selectedRegion (val) {
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
this.addr = paths;
this.regionId = ids;
}
else{
this.addr = val.data.addr;
this.regionId = val.data.addrId;
}
},
//订单取消
orderCancel () {
this.orderCancelModal = true;
},
//订单取消提交
orderCancelSubmit () {
this.$refs.orderCancelForm.validate((valid) => {
if (valid) {
API_Order.orderCancel(this.sn, this.orderCancelForm).then((res) => {
if (res.success) {
ElMessage.success("取消成功");
this.getDataList();
}
this.orderCancelModal = false;
});
}
});
},
openPlatformRefund() {
this.platformRefundForm = {
refundType: "UNSERVED",
refundAmount: this.orderInfo?.order?.flowPrice || 0,
reason: "",
};
this.platformRefundModal = true;
},
submitPlatformRefund() {
if (!this.platformRefundForm.reason) {
ElMessage.warning("请填写退款原因");
return;
}
this.refundLoading = true;
platformRefund(this.sn, this.platformRefundForm)
.then((res) => {
if (res.success) {
ElMessage.success("退款成功");
this.platformRefundModal = false;
this.getDataList();
}
})
.finally(() => {
this.refundLoading = false;
});
},
openNoShowRefund() {
this.noShowRefundForm = {
refundAmount: this.orderInfo?.order?.flowPrice || 0,
reason: "",
};
this.noShowRefundModal = true;
},
submitNoShowRefund() {
if (!this.noShowRefundForm.reason) {
ElMessage.warning("请填写退款原因");
return;
}
this.refundLoading = true;
noShowRefund(this.sn, this.noShowRefundForm)
.then((res) => {
if (res.success) {
ElMessage.success("退款成功");
this.noShowRefundModal = false;
this.getDataList();
}
})
.finally(() => {
this.refundLoading = false;
});
},
//订单日志
orderLog () {
this.orderLogModal = true;
},
//订单日志取消
handelCancel () {
this.orderLogModal = false;
},
//打印发货单
printOrder () {
this.printModal = true;
},
printHiddenInfo () {
this.printHiddenFlag = !this.printHiddenFlag;
},
printCancel () {
// this.printHiddenFlag = false;
},
handlePrintInvoice () {
printElement("printInfo", "发货单");
},
//弹出修改收货地址框
editAddress () {
this.addressModal = true;
this.addr = this.orderInfo.order.consigneeAddressPath;
this.regionId = this.orderInfo.order.consigneeAddressIdPath;
this.addressForm.consigneeName = this.orderInfo.order.consigneeName;
this.addressForm.consigneeMobile = this.orderInfo.order.consigneeMobile;
this.addressForm.consigneeDetail = this.orderInfo.order.consigneeDetail;
this.addressForm.consigneeAddressPath = this.orderInfo.order.consigneeAddressPath;
this.addressForm.consigneeAddressIdPath = this.orderInfo.order.consigneeAddressIdPath;
},
//修改收货地址
editAddressSubmit () {
if (this.regionId == "") {
ElMessage.error("请选择地址");
return;
}
this.addressForm.consigneeAddressPath = this.addr;
this.addressForm.consigneeAddressIdPath = this.regionId;
this.$refs.addressForm.validate((valid) => {
if (valid) {
API_Order.editOrderConsignee(this.sn, this.addressForm).then((res) => {
if (res.success) {
ElMessage.success("收货地址修改成功");
this.addressModal = false;
this.getDataList();
}
});
}
});
},
},
mounted () {
this.sn = this.$route.query.sn;
this.getDataList();
},
],
};
//验证取消订单原因
const orderCancelValidate = {
reason: [{ required: true, message: "取消原因不能为空", trigger: "blur" }],
};
const addressModal = ref(false); //弹出修改收件信息框
const printModal = ref(false);
const platformRefundModal = ref(false);
const noShowRefundModal = ref(false);
const refundLoading = ref(false);
const platformRefundForm = ref({
refundType: "UNSERVED",
refundAmount: 0,
reason: "",
});
const noShowRefundForm = ref({
refundAmount: 0,
reason: "",
});
//收件地址表单
const addressForm = reactive({
consigneeName: "",
consigneeMobile: "",
consigneeDetail: "",
consigneeAddressPath: "",
consigneeAddressIdPath: "",
});
const orderDeliverFormValidate = {
logisticsNo: [{ required: true, message: "发货单号不能为空", trigger: "change" }],
logisticsId: [{ required: true, message: "请选择物流公司", trigger: "blur" }],
};
const addressRule = {
consigneeName: [
{ required: true, message: "收货人姓名不能为空", trigger: "blur" },
],
consigneeMobile: [
{ required: true, message: "联系方式不能为空", trigger: "blur" },
{
pattern: RegExp.mobile,
trigger: "blur",
message: "请输入正确的手机号",
},
],
consigneeDetail: [
{ required: true, message: "详细地址不能为空", trigger: "blur" },
],
};
const data = ref([]); // 表单数据
/** 电子卡券订单:无物流区,卡密来自 orderItems[].cardKeys */
const isECouponOrder = computed(() => {
const t = orderInfo.value?.order?.orderType;
return t === "E_COUPON" || route.query.orderType === "E_COUPON";
});
const isVirtualOrder = computed(() => {
const t = orderInfo.value?.order?.orderType;
return t === "VIRTUAL" || route.query.orderType === "VIRTUAL";
});
const isNonPhysicalOrder = computed(() => {
return isVirtualOrder.value || isECouponOrder.value || isAppointmentOrder.value;
});
const isAppointmentOrder = computed(() => {
const t = orderInfo.value?.order?.orderType;
return t === "APPOINTMENT" || route.query.orderType === "APPOINTMENT";
});
const appointmentSlotText = computed(() => {
const v = appointmentExt.value || {};
if (!v.slotDate) return "";
const date = String(v.slotDate).slice(0, 10);
const slot = v.slotStartTime && v.slotEndTime ? `${v.slotStartTime}-${v.slotEndTime}` : "";
return [date, slot].filter(Boolean).join(" ");
});
const appointmentFormGroups = computed(() => {
return parseFormAnswerGroups(appointmentExt.value?.formAnswerJson);
});
const canPlatformRefund = computed(() => {
const order = orderInfo.value?.order;
return order && order.payStatus === "PAID" && order.orderStatus !== "CANCELLED";
});
const canNoShowRefund = computed(() => {
return canPlatformRefund.value;
});
const ecouponCardKeyDelivered = computed(() => {
return (data.value || []).some((item) => item.cardKeyDelivered);
});
const ecouponCardKeyRows = computed(() => {
const rows = [];
(data.value || []).forEach((item) => {
(item.cardKeys || []).forEach((ck) => {
rows.push({ ...ck });
});
});
return rows;
});
watch(
() => route.query,
(to, from) => {
router.go(0);
}
);
onMounted(() => {
sn.value = route.query.sn;
getDataList();
});
function copyCardKey(row) {
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
ElMessage.success("已复制到剪贴板");
});
} else {
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
ElMessage.success("已复制到剪贴板");
}
}
function getPromotionText(row) {
let resultText = "";
if (row && row.promotionType) {
const type = row.promotionType.split(",");
if (type.indexOf("PINTUAN") != -1) resultText += "拼团 ";
if (type.indexOf("SECKILL") != -1) resultText += "秒杀 ";
if (type.indexOf("COUPON") != -1) resultText += "优惠券 ";
if (type.indexOf("FULL_DISCOUNT") != -1) resultText += "满减 ";
if (type.indexOf("POINTS_GOODS") != -1) resultText += "积分商品 ";
}
return resultText === "" ? "未参与" : resultText;
}
function gotoHomes () {
return false
}
//确认收款
function confirmPrice () {
ElMessageBox.confirm(
"您确定要收款吗?线下收款涉及库存变更,需异步进行,等待约一分钟刷新列表查看",
"提示",
{ type: "warning" }
).then(() => {
API_Order.orderPay(sn.value).then((res) => {
if (res.success) {
ElMessage.success("收款成功");
getDataList();
}
});
}).catch(() => {});
}
function getOrderPrice () {
if (showPrices.value) {
showPrices.value = false
} else if (!showPrices.value) {
showPrices.value = true
}
}
function getContentPrice () {
for (let i = 0; i < typeList.value.length; i++) {
for (let j = i + 1; j < typeList.value.length; j++) {
if (typeList.value[i].promotionId === typeList.value[j].promotionId) {
typeList.value[i].discountPrice = typeList.value[i].discountPrice + typeList.value[j].discountPrice
typeList.value.splice(j, 1)
}
}
}
if (typeList.value.length >= 3) {
getContentPrice()
}
}
// 获取订单详情
function getDataList () {
loading.value = true;
API_Order.orderDetail(sn.value).then((res) => {
loading.value = false;
if (res.success) {
orderInfo.value = res.result;
allowOperation.value = res.result.allowOperationVO;
data.value = res.result.orderItems;
typeList.value = JSON.parse(JSON.stringify(res.result.order.priceDetailDTO.discountPriceDetail));
getContentPrice()
getOrderPrice()
if (isAppointmentOrder.value) {
loadAppointmentExt();
}
}
});
}
function loadAppointmentExt() {
getAppointmentOrderDetail(sn.value)
.then((res) => {
if (res.success) {
appointmentExt.value = res.result || {};
}
})
.catch(() => {
appointmentExt.value = {};
});
}
function parseFormAnswerGroups(formAnswerJson) {
if (!formAnswerJson) return [];
let data = formAnswerJson;
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
return [];
}
}
const fields = Array.isArray(data?.fields) ? data.fields : [];
const answers = Array.isArray(data?.answers) ? data.answers : [];
return answers
.map((group) => {
const map = group && typeof group === "object" && !Array.isArray(group) ? group : {};
const source = fields.length
? fields
: Object.keys(map).map((key) => ({ fieldKey: key, fieldLabel: key }));
return source
.map((field) => ({
fieldKey: field.fieldKey,
fieldLabel: field.fieldLabel || field.fieldKey,
value: map[field.fieldKey] ?? "",
}))
.filter((row) => row.value !== "" && row.value != null);
})
.filter((rows) => rows.length);
}
function modifyPrice () {
//默认要修改的金额为订单总金额
modifyPriceForm.price = orderInfo.value.order.flowPrice;
modal.value = true;
}
//修改订单金额提交
function modifyPriceSubmit () {
modifyPriceFormRef.value.validate((valid) => {
if (valid) {
API_Order.updateOrderPrice(sn.value, modifyPriceForm).then((res) => {
if (res.success) {
ElMessage.success("修改订单金额成功");
modal.value = false;
getDataList();
}
});
}
});
}
// 选中的地址
function selectedRegion (val) {
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
addr.value = paths;
regionId = ids;
}
else{
addr.value = val.data.addr;
regionId = val.data.addrId;
}
}
//订单取消
function orderCancel () {
orderCancelModal.value = true;
}
//订单取消提交
function orderCancelSubmit () {
orderCancelFormRef.value.validate((valid) => {
if (valid) {
API_Order.orderCancel(sn.value, orderCancelForm).then((res) => {
if (res.success) {
ElMessage.success("取消成功");
getDataList();
}
orderCancelModal.value = false;
});
}
});
}
function openPlatformRefund() {
platformRefundForm.value = {
refundType: "UNSERVED",
refundAmount: orderInfo.value?.order?.flowPrice || 0,
reason: "",
};
platformRefundModal.value = true;
}
function submitPlatformRefund() {
if (!platformRefundForm.value.reason) {
ElMessage.warning("请填写退款原因");
return;
}
refundLoading.value = true;
platformRefund(sn.value, platformRefundForm.value)
.then((res) => {
if (res.success) {
ElMessage.success("退款成功");
platformRefundModal.value = false;
getDataList();
}
})
.finally(() => {
refundLoading.value = false;
});
}
function openNoShowRefund() {
noShowRefundForm.value = {
refundAmount: orderInfo.value?.order?.flowPrice || 0,
reason: "",
};
noShowRefundModal.value = true;
}
function submitNoShowRefund() {
if (!noShowRefundForm.value.reason) {
ElMessage.warning("请填写退款原因");
return;
}
refundLoading.value = true;
noShowRefund(sn.value, noShowRefundForm.value)
.then((res) => {
if (res.success) {
ElMessage.success("退款成功");
noShowRefundModal.value = false;
getDataList();
}
})
.finally(() => {
refundLoading.value = false;
});
}
//订单日志
function orderLog () {
orderLogModal.value = true;
}
//订单日志取消
function handelCancel () {
orderLogModal.value = false;
}
//打印发货单
function printOrder () {
printModal.value = true;
}
function printHiddenInfo () {
printHiddenFlag.value = !printHiddenFlag.value;
}
function printCancel () {
// printHiddenFlag.value = false;
}
function handlePrintInvoice () {
printElement("printInfo", "发货单");
}
//弹出修改收货地址框
function editAddress () {
addressModal.value = true;
addr.value = orderInfo.value.order.consigneeAddressPath;
regionId = orderInfo.value.order.consigneeAddressIdPath;
addressForm.consigneeName = orderInfo.value.order.consigneeName;
addressForm.consigneeMobile = orderInfo.value.order.consigneeMobile;
addressForm.consigneeDetail = orderInfo.value.order.consigneeDetail;
addressForm.consigneeAddressPath = orderInfo.value.order.consigneeAddressPath;
addressForm.consigneeAddressIdPath = orderInfo.value.order.consigneeAddressIdPath;
}
//修改收货地址
function editAddressSubmit () {
if (regionId == "") {
ElMessage.error("请选择地址");
return;
}
addressForm.consigneeAddressPath = addr.value;
addressForm.consigneeAddressIdPath = regionId;
addressFormRef.value.validate((valid) => {
if (valid) {
API_Order.editOrderConsignee(sn.value, addressForm).then((res) => {
if (res.success) {
ElMessage.success("收货地址修改成功");
addressModal.value = false;
getDataList();
}
});
}
});
}
</script>
<style lang="scss">
.lineH30 {

View File

@@ -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"
@@ -74,7 +74,7 @@
<el-button type="warning" class="export" :loading="exporting" @click="exportOrder">导出订单</el-button>
</div>
<el-table v-loading="loading" :data="data" ref="table" class="mt_10" style="width: 100%">
<el-table v-loading="loading" :data="data" ref="tableRef" class="mt_10" style="width: 100%">
<el-table-column prop="sn" label="订单号" min-width="200" show-overflow-tooltip />
<el-table-column label="商品" min-width="280">
<template #default="{ row }">
@@ -149,7 +149,8 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref } from "vue";
import * as API_Order from "@/api/order";
import { ElMessage, ElMessageBox } from "element-plus";
import { unitPrice, customRouterPush } from "@/utils/filters";
@@ -229,264 +230,260 @@ function readSavedQuery() {
}
}
export default {
name: "orderList",
data() {
return {
loading: true,
exporting: false,
pageSizes: PAGE_SIZES,
promotionOptions: PROMOTION_OPTIONS,
orderTypeOptions: ORDER_TYPE_OPTIONS,
paymentOptions: PAYMENT_OPTIONS,
searchForm: emptySearchForm(),
selectDate: null,
data: [],
total: 0,
orderNumData: {},
currentStatus: "ALL",
};
},
computed: {
orderStatusWithCount() {
const count = (value) => (value ? `(${value})` : "");
return [
{ title: "全部", value: "ALL" },
{ title: `未付款${count(this.orderNumData.waitPayNum)}`, value: "UNPAID" },
{ title: `已付款${count(this.orderNumData.waitDeliveryNum)}`, value: "PAID" },
{ title: `待发货${count(this.orderNumData.waitShipNum)}`, value: "UNDELIVERED" },
{ title: `部分发货${count(this.orderNumData.partsDeliveredNumNum)}`, value: "PARTS_DELIVERED" },
{ title: `待收货${count(this.orderNumData.deliveredNum)}`, value: "DELIVERED" },
{ title: `待核验${count(this.orderNumData.waitCheckNum)}`, value: "TAKE" },
{ title: `待自提${count(this.orderNumData.waitSelfPickNum)}`, value: "STAY_PICKED_UP" },
{ title: `已完成${count(this.orderNumData.finishNum)}`, value: "COMPLETED" },
{ title: `已关闭${count(this.orderNumData.closeNum)}`, value: "CANCELLED" },
];
},
},
methods: {
unitPrice,
onStatusTabClick(tab) {
this.orderStatusClick(tab.paneName);
},
orderGoods(row) {
const items = row && row.orderItems;
return items && items.length ? items : [];
},
clientTypeText(v) {
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
return map[v] || v || "—";
},
orderTypeText(v) {
const found = ORDER_TYPE_OPTIONS.find((item) => item.value === v);
return found ? found.label : v || "—";
},
orderPromotionText(v) {
const found = PROMOTION_OPTIONS.find((item) => item.value === v);
return found ? found.label : v || "—";
},
orderPromotionTagType(v) {
const map = {
NORMAL: "primary",
PINTUAN: "danger",
GIFT: "success",
POINTS: "info",
KANJIA: "warning",
};
return map[v] || "info";
},
deliveryMethodText(v) {
const map = {
SELF_PICK_UP: "自提",
LOCAL_TOWN_DELIVERY: "同城配送",
LOGISTICS: "物流",
VIRTUAL: "虚拟发货",
};
return map[v] || v || "—";
},
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";
},
rememberQuery() {
sessionStorage.setItem(
QUERY_KEY,
JSON.stringify({
searchForm: this.searchForm,
selectDate: this.selectDate,
currentStatus: this.currentStatus,
})
defineOptions({ name: "orderList" });
const searchFormRef = ref(null);
const tableRef = ref(null);
const loading = ref(true);
const exporting = ref(false);
const pageSizes = ref(PAGE_SIZES);
const promotionOptions = ref(PROMOTION_OPTIONS);
const orderTypeOptions = ref(ORDER_TYPE_OPTIONS);
const paymentOptions = ref(PAYMENT_OPTIONS);
const searchForm = ref(emptySearchForm());
const selectDate = ref(null);
const data = ref([]);
const total = ref(0);
const orderNumData = ref({});
const currentStatus = ref("ALL");
const orderStatusWithCount = computed(() => {
const count = (value) => (value ? `(${value})` : "");
return [
{ title: "全部", value: "ALL" },
{ title: `未付款${count(orderNumData.value.waitPayNum)}`, value: "UNPAID" },
{ title: `已付款${count(orderNumData.value.waitDeliveryNum)}`, value: "PAID" },
{ title: `待发货${count(orderNumData.value.waitShipNum)}`, value: "UNDELIVERED" },
{ title: `部分发货${count(orderNumData.value.partsDeliveredNumNum)}`, value: "PARTS_DELIVERED" },
{ title: `待收货${count(orderNumData.value.deliveredNum)}`, value: "DELIVERED" },
{ title: `待核验${count(orderNumData.value.waitCheckNum)}`, value: "TAKE" },
{ title: `待自提${count(orderNumData.value.waitSelfPickNum)}`, value: "STAY_PICKED_UP" },
{ title: `已完成${count(orderNumData.value.finishNum)}`, value: "COMPLETED" },
{ title: `已关闭${count(orderNumData.value.closeNum)}`, value: "CANCELLED" },
];
});
function onStatusTabClick(tab) {
orderStatusClick(tab.paneName);
}
function orderGoods(row) {
const items = row && row.orderItems;
return items && items.length ? items : [];
}
function clientTypeText(v) {
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
return map[v] || v || "—";
}
function orderTypeText(v) {
const found = ORDER_TYPE_OPTIONS.find((item) => item.value === v);
return found ? found.label : v || "—";
}
function orderPromotionText(v) {
const found = PROMOTION_OPTIONS.find((item) => item.value === v);
return found ? found.label : v || "—";
}
function orderPromotionTagType(v) {
const map = {
NORMAL: "primary",
PINTUAN: "danger",
GIFT: "success",
POINTS: "info",
KANJIA: "warning",
};
return map[v] || "info";
}
function deliveryMethodText(v) {
const map = {
SELF_PICK_UP: "自提",
LOCAL_TOWN_DELIVERY: "同城配送",
LOGISTICS: "物流",
VIRTUAL: "虚拟发货",
};
return map[v] || v || "—";
}
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 rememberQuery() {
sessionStorage.setItem(
QUERY_KEY,
JSON.stringify({
searchForm: searchForm.value,
selectDate: selectDate.value,
currentStatus: currentStatus.value,
})
);
}
function restoreQuery() {
const saved = readSavedQuery();
if (!saved) return;
searchForm.value = saved.searchForm;
selectDate.value = saved.selectDate;
currentStatus.value = saved.currentStatus;
}
function init() {
getDataList();
getOrderNumData();
}
function changePage(v) {
searchForm.value.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.value.pageNumber = 1;
getDataList();
getOrderNumData();
}
function handleReset() {
const pageSize = searchForm.value.pageSize;
searchForm.value = emptySearchForm(pageSize);
selectDate.value = null;
currentStatus.value = "ALL";
sessionStorage.removeItem(QUERY_KEY);
init();
}
function selectDateRange(v) {
if (v && v.length === 2) {
searchForm.value.startDate = v[0];
searchForm.value.endDate = v[1];
} else {
searchForm.value.startDate = "";
searchForm.value.endDate = "";
}
}
function countParams() {
const params = { ...searchForm.value };
delete params.orderStatus;
delete params.pageNumber;
delete params.pageSize;
delete params.sort;
delete params.order;
return params;
}
function getDataList() {
loading.value = true;
rememberQuery();
API_Order.getOrderList(searchForm.value)
.then((res) => {
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
})
.finally(() => {
loading.value = false;
});
}
function detail(v) {
customRouterPush({
name: "order-detail",
query: { sn: v.sn },
});
}
async function exportOrder() {
if (!searchForm.value.startDate || !searchForm.value.endDate) {
try {
await ElMessageBox.confirm(
"未选择下单时间,将按当前筛选条件导出全部匹配订单。",
"导出订单",
{ type: "warning", confirmButtonText: "继续导出", cancelButtonText: "取消" }
);
},
restoreQuery() {
const saved = readSavedQuery();
if (!saved) return;
this.searchForm = saved.searchForm;
this.selectDate = saved.selectDate;
this.currentStatus = saved.currentStatus;
},
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.getDataList();
this.getOrderNumData();
},
handleReset() {
const pageSize = this.searchForm.pageSize;
this.searchForm = emptySearchForm(pageSize);
this.selectDate = null;
this.currentStatus = "ALL";
sessionStorage.removeItem(QUERY_KEY);
this.init();
},
selectDateRange(v) {
if (v && v.length === 2) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
countParams() {
const params = { ...this.searchForm };
delete params.orderStatus;
delete params.pageNumber;
delete params.pageSize;
delete params.sort;
delete params.order;
return params;
},
getDataList() {
this.loading = true;
this.rememberQuery();
API_Order.getOrderList(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
})
.finally(() => {
this.loading = false;
});
},
detail(v) {
customRouterPush({
name: "order-detail",
query: { sn: v.sn },
} catch (e) {
return;
}
}
exporting.value = true;
API_Order.exportOrder(searchForm.value)
.then((res) => {
const blob = new Blob([res], {
type: "application/vnd.ms-excel;charset=utf-8",
});
},
async exportOrder() {
if (!this.searchForm.startDate || !this.searchForm.endDate) {
try {
await ElMessageBox.confirm(
"未选择下单时间,将按当前筛选条件导出全部匹配订单。",
"导出订单",
{ type: "warning", confirmButtonText: "继续导出", cancelButtonText: "取消" }
);
} catch (e) {
return;
}
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);
})
.catch(() => {
ElMessage.error("导出失败");
})
.finally(() => {
exporting.value = false;
});
}
function orderStatusClick(name) {
if (name === "ALL" || name === "" || name === undefined) {
searchForm.value.orderStatus = "";
currentStatus.value = "ALL";
} else {
searchForm.value.orderStatus = name;
currentStatus.value = name;
}
searchForm.value.pageNumber = 1;
getDataList();
}
function getOrderNumData() {
API_Order.getOrderNum(countParams())
.then((res) => {
if (res.success) {
orderNumData.value = res.result;
}
this.exporting = true;
API_Order.exportOrder(this.searchForm)
.then((res) => {
const blob = new Blob([res], {
type: "application/vnd.ms-excel;charset=utf-8",
});
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);
})
.catch(() => {
ElMessage.error("导出失败");
})
.finally(() => {
this.exporting = false;
});
},
orderStatusClick(name) {
if (name === "ALL" || name === "" || name === undefined) {
this.searchForm.orderStatus = "";
this.currentStatus = "ALL";
} else {
this.searchForm.orderStatus = name;
this.currentStatus = name;
}
this.searchForm.pageNumber = 1;
this.getDataList();
},
getOrderNumData() {
API_Order.getOrderNum(this.countParams())
.then((res) => {
if (res.success) {
this.orderNumData = res.result;
}
})
.catch(() => {
this.orderNumData = {};
});
},
},
created() {
this.restoreQuery();
},
mounted() {
this.init();
},
};
})
.catch(() => {
orderNumData.value = {};
});
}
// created
restoreQuery();
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -82,159 +82,162 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { useRouter } from "vue-router";
import * as API_floor from "@/api/other.js";
export default {
name: "floorList",
data() {
return {
showModal: false,
total: 0,
formData: {
status: false,
name: "",
},
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
pageType: "INDEX",
pageClientType: "PC",
},
loading: false,
pageTypes: [
{ type: "INDEX", title: "首页" },
{ type: "SPECIAL", title: "专题" },
],
list: [],
};
},
mounted() {
this.getTemplateList();
},
methods: {
newTemplate() {
this.$refs.form.validate((valid) => {
if (valid) {
const data = this.formData;
data.status ? (data.pageShow = "OPEN") : (data.pageShow = "CLOSE");
delete data.status;
(data.pageType = this.searchForm.pageType),
(data.pageClientType = "PC");
if (data.id) {
API_floor.updateHome(data.id, data).then((res) => {
this.$Message.success("编辑模板成功");
this.showModal = false;
this.getTemplateList();
});
} else {
API_floor.setHomeSetup(data).then((res) => {
this.$Message.success("新建模板成功");
this.showModal = false;
this.getTemplateList();
});
}
} else {
this.loading = false;
}
});
},
import { Message, Modal } from "@/utils/message";
handleTabChange(type) {
this.searchForm.pageNumber = 1;
this.searchForm.pageType = type;
this.getTemplateList();
},
defineOptions({ name: "floorList" });
createTemp() {
this.$refs.form?.resetFields();
this.formData = { status: false, name: "" };
this.showModal = true;
},
const router = useRouter();
Template(item) {
item.status = item.pageShow;
this.formData = { ...item };
this.showModal = true;
},
const showModal = ref(false);
const total = ref(0);
const formData = ref({
status: false,
name: "",
});
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
pageType: "INDEX",
pageClientType: "PC",
});
const loading = ref(false);
const pageTypes = ref([
{ type: "INDEX", title: "首页" },
{ type: "SPECIAL", title: "专题" },
]);
const list = ref([]);
decorate(val) {
const data = { id: val.id, pageShow: val.pageShow, pageType: this.searchForm.pageType };
const form = ref(null);
this.$router.push({
name: "renovation",
query: data,
});
},
onMounted(() => {
getTemplateList();
});
changePageNum(val) {
this.searchForm.pageNumber = val;
this.getTemplateList();
},
changePageSize(val) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = val;
this.getTemplateList();
},
getTemplateList() {
this.loading = true;
API_floor.getHomeList(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.total = res.result.total;
this.list = res.result.records;
this.list.forEach((e) => {
if (e.pageShow === "OPEN") {
e.pageShow = true;
} else {
e.pageShow = false;
}
});
}
});
},
beforeRelease(row) {
if (row.pageShow) {
this.$Message.warning(
this.total === 1
? "当前为唯一页面,无法关闭"
: "请直接开启其他页面,当前页面会自动关闭"
);
return false;
}
return new Promise((resolve, reject) => {
API_floor.releasePageHome(row.id).then((res) => {
if (res && res.success) {
this.$Message.success("发布模板成功");
resolve(true);
this.getTemplateList();
} else {
reject(new Error("release failed"));
}
}).catch(() => {
reject(new Error("release failed"));
function newTemplate() {
form.value.validate((valid) => {
if (valid) {
const data = formData.value;
data.status ? (data.pageShow = "OPEN") : (data.pageShow = "CLOSE");
delete data.status;
(data.pageType = searchForm.pageType),
(data.pageClientType = "PC");
if (data.id) {
API_floor.updateHome(data.id, data).then((res) => {
Message.success("编辑模板成功");
showModal.value = false;
getTemplateList();
});
});
},
confirmDel(id) {
this.$Modal.confirm({
title: "提示",
content: "删除此模板?",
onOk: () => this.delTemplate(id),
});
},
delTemplate(id) {
API_floor.removePageHome(id).then((res) => {
if (res.success) {
this.$Message.success("删除模板成功");
this.getTemplateList();
} else {
API_floor.setHomeSetup(data).then((res) => {
Message.success("新建模板成功");
showModal.value = false;
getTemplateList();
});
}
} else {
loading.value = false;
}
});
}
function handleTabChange(type) {
searchForm.pageNumber = 1;
searchForm.pageType = type;
getTemplateList();
}
function createTemp() {
form.value?.resetFields();
formData.value = { status: false, name: "" };
showModal.value = true;
}
function Template(item) {
item.status = item.pageShow;
formData.value = { ...item };
showModal.value = true;
}
function decorate(val) {
const data = { id: val.id, pageShow: val.pageShow, pageType: searchForm.pageType };
router.push({
name: "renovation",
query: data,
});
}
function changePageNum(val) {
searchForm.pageNumber = val;
getTemplateList();
}
function changePageSize(val) {
searchForm.pageNumber = 1;
searchForm.pageSize = val;
getTemplateList();
}
function getTemplateList() {
loading.value = true;
API_floor.getHomeList(searchForm).then((res) => {
loading.value = false;
if (res.success) {
total.value = res.result.total;
list.value = res.result.records;
list.value.forEach((e) => {
if (e.pageShow === "OPEN") {
e.pageShow = true;
} else {
e.pageShow = false;
}
});
},
},
};
}
});
}
function beforeRelease(row) {
if (row.pageShow) {
Message.warning(
total.value === 1
? "当前为唯一页面,无法关闭"
: "请直接开启其他页面,当前页面会自动关闭"
);
return false;
}
return new Promise((resolve, reject) => {
API_floor.releasePageHome(row.id).then((res) => {
if (res && res.success) {
Message.success("发布模板成功");
resolve(true);
getTemplateList();
} else {
reject(new Error("release failed"));
}
}).catch(() => {
reject(new Error("release failed"));
});
});
}
function confirmDel(id) {
Modal.confirm({
title: "提示",
content: "删除此模板?",
onOk: () => delTemplate(id),
});
}
function delTemplate(id) {
API_floor.removePageHome(id).then((res) => {
if (res.success) {
Message.success("删除模板成功");
getTemplateList();
}
});
}
</script>
<style lang="scss" scoped>

View File

@@ -141,162 +141,174 @@
<el-button type="primary" @click="handleConfirmNavModal">确定</el-button>
</template>
</el-dialog>
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<liliDialog ref="liliDialogRef" @selectedLink="selectedLink"></liliDialog>
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
@callback="callbackSelected"
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick, onMounted } from "vue";
import { ShoppingCart } from "@element-plus/icons-vue";
import Draggable from "vuedraggable";
import ModelFormItem from "./modelFormItem.vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
import logoImg from "@/assets/logo.png";
export default {
name: "modelForm",
components: {
Draggable,
ModelFormItem,
ossManage,
ShoppingCart,
},
props: ["data"],
data() {
return {
logoImg,
picModelFlag: false,
showModal: false,
showModalNav: false,
navListBackup: null,
draftTopAdvert: null,
selectedNav: null,
promotionTags: ["买2免1", "领200神券", "199减100", "母婴5折抢", "充100送20"],
topAdvert: {
type: "topAdvert",
img: "",
url: "",
bgColor: "#de000d",
size: "1200*80",
},
currentIndex: 0,
navList: {
type: "navBar",
list: [
{ name: "秒杀", url: "", title: "" },
{ name: "闪购", url: "", title: "" },
{ name: "优惠券", url: "", title: "" },
{ name: "拍卖", url: "", title: "" },
{ name: "服装城", url: "", title: "" },
],
},
};
},
mounted() {
document.body.ondrop = function (event) {
let isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox) {
event.preventDefault();
event.stopPropagation();
}
};
},
methods: {
getModelItemKey(element) {
return element.model || element.key || element.type;
import { formatLinkType } from "@/utils/filters";
defineOptions({ name: "modelForm" });
const props = defineProps(["data"]);
const picModelFlag = ref(false);
const showModal = ref(false);
const showModalNav = ref(false);
const navListBackup = ref(null);
const draftTopAdvert = ref(null);
const selectedNav = ref(null);
const promotionTags = ref(["买2免1", "领200神券", "199减100", "母婴5折抢", "充100送20"]);
const topAdvert = ref({
type: "topAdvert",
img: "",
url: "",
bgColor: "#de000d",
size: "1200*80",
});
const currentIndex = ref(0);
const navList = ref({
type: "navBar",
list: [
{ name: "秒杀", url: "", title: "" },
{ name: "闪购", url: "", title: "" },
{ name: "优惠券", url: "", title: "" },
{ name: "拍卖", url: "", title: "" },
{ name: "服装城", url: "", title: "" },
],
});
const liliDialogRef = ref(null);
const ossManageRef = ref(null);
// 父组件 renovation.vue 通过 $refs.modelForm 读取并整体赋值 navList / topAdvert
defineExpose({ navList, topAdvert });
onMounted(() => {
document.body.ondrop = function (event) {
let isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox) {
event.preventDefault();
event.stopPropagation();
}
};
});
function getModelItemKey(element) {
return element.model || element.key || element.type;
}
function handleSelectLink(item, index) {
if (item) selectedNav.value = item;
liliDialogRef.value.open("link");
currentIndex.value = index;
}
function selectedLink(val) {
if (showModalNav.value) {
selectedNav.value.url = formatLinkType(val);
selectedNav.value.type = val.___type === "other" && val.url === "" ? "link" : "other";
navList.value.list[currentIndex.value].title = val.title;
} else if (showModal.value && draftTopAdvert.value) {
draftTopAdvert.value.url = formatLinkType(val);
draftTopAdvert.value.type = val.___type === "other" && val.url === "" ? "link" : "other";
}
}
function handleDelNav(index) {
navList.value.list.splice(index, 1);
}
function handleAddNav() {
navList.value.list.push({ name: "", url: "", title: "" });
}
function handleMoveEnd({ newIndex, oldIndex }) {}
function handleModel(type) {
if (type == "topAdvert") {
draftTopAdvert.value = JSON.parse(JSON.stringify(topAdvert.value));
showModal.value = true;
} else {
navListBackup.value = JSON.parse(JSON.stringify(navList.value));
showModalNav.value = true;
}
}
function handleCancelTopAdvertModal() {
draftTopAdvert.value = null;
showModal.value = false;
}
function handleConfirmTopAdvertModal() {
if (draftTopAdvert.value) {
Object.assign(topAdvert.value, JSON.parse(JSON.stringify(draftTopAdvert.value)));
}
draftTopAdvert.value = null;
showModal.value = false;
}
function handleCancelNavModal() {
showModalNav.value = false;
}
function handleConfirmNavModal() {
navListBackup.value = null;
showModalNav.value = false;
}
function handleNavDialogClose() {
if (navListBackup.value) {
navList.value = JSON.parse(JSON.stringify(navListBackup.value));
navListBackup.value = null;
}
}
function handleSelectImg() {
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function callbackSelected(item) {
picModelFlag.value = false;
if (!item?.url || !draftTopAdvert.value) return;
draftTopAdvert.value.img = item.url;
}
function handleModelAdd(evt) {
const newIndex = evt.newIndex;
const current = props.data.list[newIndex];
if (!current || current.key) {
return;
}
const key = Date.now() + "_" + Math.ceil(Math.random() * 99999);
props.data.list[newIndex] = {
...JSON.parse(JSON.stringify(current)),
options: {
...current.options,
},
handleSelectLink(item, index) {
if (item) this.selectedNav = item;
this.$refs.liliDialog.open("link");
this.currentIndex = index;
},
selectedLink(val) {
if (this.showModalNav) {
this.selectedNav.url = this.$filters.formatLinkType(val);
this.selectedNav.type = val.___type === "other" && val.url === "" ? "link" : "other";
this.navList.list[this.currentIndex].title = val.title;
} else if (this.showModal && this.draftTopAdvert) {
this.draftTopAdvert.url = this.$filters.formatLinkType(val);
this.draftTopAdvert.type = val.___type === "other" && val.url === "" ? "link" : "other";
}
},
handleDelNav(index) {
this.navList.list.splice(index, 1);
},
handleAddNav() {
this.navList.list.push({ name: "", url: "", title: "" });
},
handleMoveEnd({ newIndex, oldIndex }) {},
handleModel(type) {
if (type == "topAdvert") {
this.draftTopAdvert = JSON.parse(JSON.stringify(this.topAdvert));
this.showModal = true;
} else {
this.navListBackup = JSON.parse(JSON.stringify(this.navList));
this.showModalNav = true;
}
},
handleCancelTopAdvertModal() {
this.draftTopAdvert = null;
this.showModal = false;
},
handleConfirmTopAdvertModal() {
if (this.draftTopAdvert) {
Object.assign(this.topAdvert, JSON.parse(JSON.stringify(this.draftTopAdvert)));
}
this.draftTopAdvert = null;
this.showModal = false;
},
handleCancelNavModal() {
this.showModalNav = false;
},
handleConfirmNavModal() {
this.navListBackup = null;
this.showModalNav = false;
},
handleNavDialogClose() {
if (this.navListBackup) {
this.navList = JSON.parse(JSON.stringify(this.navListBackup));
this.navListBackup = null;
}
},
handleSelectImg() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
callbackSelected(item) {
this.picModelFlag = false;
if (!item?.url || !this.draftTopAdvert) return;
this.draftTopAdvert.img = item.url;
},
handleModelAdd(evt) {
const newIndex = evt.newIndex;
const current = this.data.list[newIndex];
if (!current || current.key) {
return;
}
const key = Date.now() + "_" + Math.ceil(Math.random() * 99999);
this.data.list[newIndex] = {
...JSON.parse(JSON.stringify(current)),
options: {
...current.options,
},
key,
model: current.type + "_" + key,
};
},
},
};
key,
model: current.type + "_" + key,
};
}
</script>
<style lang="scss" scoped>
@import "./modelList/setup-box.scss";

View File

@@ -250,7 +250,7 @@
</template>
</el-dialog>
<!-- 选择商品。链接 -->
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<liliDialog ref="liliDialogRef" @selectedLink="selectedLink"></liliDialog>
<!-- 选择图片 -->
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
@@ -258,13 +258,14 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ModelCarousel from "./modelList/carousel.vue";
import ModelCarousel1 from "./modelList/carousel1.vue";
import ModelCarousel2 from "./modelList/carousel2.vue";
@@ -283,98 +284,82 @@ import onlyGoodsModel from "./modelList/onlyGoodsModel.vue";
import oneRowThreeColumns from "./modelList/oneRowThreeColumns.vue";
import seckillOnlyAlbum from "./modelList/seckill-only-album.vue";
import discountAdvertBg from "@/assets/nav/decorate.png";
export default {
name: "modelFormItem",
props: ["element", "select", "index", "data"],
components: {
ModelCarousel,
ModelCarousel1,
ModelCarousel2,
Recommend,
NewGoodsSort,
FirstPageAdvert,
NotEnough,
Seckill,
goodsType,
ossManage,
oneRowThreeColumns,
"seckill-only-album":seckillOnlyAlbum,
onlyGoodsModel,
mixModel,
forYour
},
data() {
return {
discountAdvertBg,
showModal: false, // modal显隐
selected: {}, // 弹窗编辑草稿
editTarget: null, // 确认后写回的原数据
picModelFlag: false, // 图片选择器
};
},
methods: {
cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
},
// 编辑模块
handleSelectModel(item) {
this.editTarget = item;
this.selected = this.cloneItem(item);
this.showModal = true;
},
// 删除模块
handleModelDelete() {
this.$Modal.confirm({
title: "提示",
content: "<p>确定删除当前模块吗?</p>",
onOk: () => {
this.$nextTick(() => {
this.data.list.splice(this.index, 1);
});
},
});
},
handleSelectLink(item, index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
},
// 确定选择链接
selectedLink(val) {
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
import { Modal } from "@/utils/message";
import { formatLinkType } from "@/utils/filters";
handleSelectImg() {
// 选择图片
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
defineOptions({ name: "modelFormItem" });
const props = defineProps(["element", "select", "index", "data"]);
const showModal = ref(false); // modal显隐
const selected = ref({}); // 弹窗编辑草稿
const editTarget = ref(null); // 确认后写回的原数据
const picModelFlag = ref(false); // 图片选择器
// 模板 ref,与 import 同名会互相覆盖,故加 Ref 后缀
const liliDialogRef = ref(null);
const ossManageRef = ref(null);
function cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
}
// 编辑模块
function handleSelectModel(item) {
editTarget.value = item;
selected.value = cloneItem(item);
showModal.value = true;
}
// 删除模块
function handleModelDelete() {
Modal.confirm({
title: "提示",
content: "<p>确定删除当前模块吗?</p>",
onOk: () => {
nextTick(() => {
props.data.list.splice(props.index, 1);
});
},
// 回显图片
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.selected.img = val.url;
},
handleCancelModal() {
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
handleConfirmModal() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
},
};
});
}
function handleSelectLink(item, index) {
// 调起选择链接弹窗
liliDialogRef.value.open("link");
}
// 确定选择链接
function selectedLink(val) {
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
function handleSelectImg() {
// 选择图片
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
// 回显图片
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url) return;
selected.value.img = val.url;
}
function handleCancelModal() {
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
function handleConfirmModal() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
</script>
<style lang="scss" scoped>
@import "./modelList/setup-box.scss";

View File

@@ -125,93 +125,91 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
name: "modelCarousel",
props: ["data"],
components: {
ossManage,
},
data() {
return {
showModal: false, // modal显隐
selected: null, // 已选数据
draftList: [], // 弹窗编辑草稿
picModelFlag: false, // 选择图片modal
userInfo: {},
articleList: [
{ title: "促销计算规则" },
{ title: "商家申请开店" },
{ title: "商家账号注册" },
{ title: "促销计算规则" },
],
};
},
import { formatLinkType } from "@/utils/filters";
methods: {
cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
},
handleSelectModel() {
this.draftList = this.cloneList(this.data.options.list);
this.showModal = true;
},
handleCancelModal() {
this.draftList = [];
this.showModal = false;
},
handleConfirmModal() {
this.data.options.list.splice(
0,
this.data.options.list.length,
...this.cloneList(this.draftList)
);
this.draftList = [];
this.showModal = false;
},
// 添加轮播图
handleAdd() {
this.draftList.push({ img: "", url: "", type: "" });
},
handleSelectLink(item) {
// 选择链接
this.$refs.liliDialog.open("link");
this.selected = item;
},
callbackSelected(item) {
// 选择图片回调
this.picModelFlag = false;
if (!item?.url) return;
this.selected.img = item.url;
},
handleDel(index) {
this.draftList.splice(index, 1);
},
selectedLink(val) {
// 选择链接回调
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
// 选择图片
handleSelectImg(item) {
this.selected = item;
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
},
};
defineOptions({ name: "modelCarousel" });
const props = defineProps(["data"]);
const showModal = ref(false); // modal显隐
const selected = ref(null); // 已选数据
const draftList = ref([]); // 弹窗编辑草稿
const picModelFlag = ref(false); // 选择图片modal
const userInfo = ref({});
const articleList = ref([
{ title: "促销计算规则" },
{ title: "商家申请开店" },
{ title: "商家账号注册" },
{ title: "促销计算规则" },
]);
// ossManageRef:模板 ref 与组件 import 同名,改名避免互相覆盖
const liliDialog = ref(null);
const ossManageRef = ref(null);
function cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
}
function handleSelectModel() {
draftList.value = cloneList(props.data.options.list);
showModal.value = true;
}
function handleCancelModal() {
draftList.value = [];
showModal.value = false;
}
function handleConfirmModal() {
props.data.options.list.splice(
0,
props.data.options.list.length,
...cloneList(draftList.value)
);
draftList.value = [];
showModal.value = false;
}
// 添加轮播图
function handleAdd() {
draftList.value.push({ img: "", url: "", type: "" });
}
function handleSelectLink(item) {
// 选择链接
liliDialog.value.open("link");
selected.value = item;
}
function callbackSelected(item) {
// 选择图片回调
picModelFlag.value = false;
if (!item?.url) return;
selected.value.img = item.url;
}
function handleDel(index) {
draftList.value.splice(index, 1);
}
function selectedLink(val) {
// 选择链接回调
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
// 选择图片
function handleSelectImg(item) {
selected.value = item;
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
</script>
<style scoped lang="scss">

View File

@@ -84,77 +84,81 @@
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<!-- 选择图片 -->
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage @callback="callbackSelected" :is-component="true" :initialize="picModelFlag" :max-select="1" ref="ossManage" />
<ossManage @callback="callbackSelected" :is-component="true" :initialize="picModelFlag" :max-select="1" ref="ossManageRef" />
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, onMounted, nextTick, getCurrentInstance } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
import { formatLinkType } from "@/utils/filters";
defineOptions({
name: "modelCarousel",
props: ["data"],
components: {
ossManage,
},
data() {
return {
showModal: false, // modal显隐
selected: null, // 已选数据
picModelFlag: false, // 选择图片modal
bgColor: "#fff", // 轮播背景色
};
},
mounted() {
this.bgColor = this.data.options.list[0].bgColor;
},
methods: {
handleSelectModel() {
// 编辑模块
this.showModal = true;
},
// 自动切换时改变背景色
autoChange(oVal, val) {
this.bgColor = this.data.options.list[val].bgColor;
},
// 添加轮播图片和链接
handleAdd() {
this.data.options.list.push({ img: "", url: "", bgColor: "#fff" });
this.$forceUpdate();
},
// 打开选择链接modal
handleSelectLink(item) {
this.$refs.liliDialog.open("link");
this.selected = item;
},
callbackSelected(item) {
// 选择图片回调
this.picModelFlag = false;
if (!item?.url) return;
this.selected.img = item.url;
},
// 删除图片
handleDel(index) {
this.data.options.list.splice(index, 1);
},
selectedLink(val) {
// 选择链接回调
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
// 打开选择图片modal
handleSelectImg(item) {
this.selected = item;
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
},
};
});
const props = defineProps(["data"]);
const { proxy } = getCurrentInstance();
const showModal = ref(false); // modal显隐
const selected = ref(null); // 已选数据
const picModelFlag = ref(false); // 选择图片modal
const bgColor = ref("#fff"); // 轮播背景色
const liliDialog = ref(null);
// ossManageRef:模板 ref 与组件 import 同名,改名避免互相覆盖
const ossManageRef = ref(null);
onMounted(() => {
bgColor.value = props.data.options.list[0].bgColor;
});
function handleSelectModel() {
// 编辑模块
showModal.value = true;
}
// 自动切换时改变背景色
function autoChange(oVal, val) {
bgColor.value = props.data.options.list[val].bgColor;
}
// 添加轮播图片和链接
function handleAdd() {
props.data.options.list.push({ img: "", url: "", bgColor: "#fff" });
proxy.$forceUpdate();
}
// 打开选择链接modal
function handleSelectLink(item) {
liliDialog.value.open("link");
selected.value = item;
}
function callbackSelected(item) {
// 选择图片回调
picModelFlag.value = false;
if (!item?.url) return;
selected.value.img = item.url;
}
// 删除图片
function handleDel(index) {
props.data.options.list.splice(index, 1);
}
function selectedLink(val) {
// 选择链接回调
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
// 打开选择图片modal
function handleSelectImg(item) {
selected.value = item;
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
</script>
<style scoped lang="scss">

View File

@@ -204,89 +204,89 @@
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<!-- 选择图片 -->
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage @callback="callbackSelected" :is-component="true" :initialize="picModelFlag" :max-select="1" ref="ossManage" />
<ossManage @callback="callbackSelected" :is-component="true" :initialize="picModelFlag" :max-select="1" ref="ossManageRef" />
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, reactive, nextTick, getCurrentInstance } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
name: "modelCarousel",
props: ["data"],
components: {
ossManage,
},
data() {
return {
showModal: false, // modal显隐
selected: null, // 已选数据
picModelFlag: false, // 选择图片modal
userInfo: {},
articleList: [
{ title: "促销计算规则" },
{ title: "商家申请开店" },
{ title: "商家账号注册" },
{ title: "促销计算规则" },
],
};
},
import { formatLinkType } from "@/utils/filters";
methods: {
handleSelectModel() {
// 编辑模块
this.showModal = true;
},
// 添加轮播
handleAdd() {
this.data.options.list.push({ img: "", url: "" });
this.$forceUpdate();
},
// 添加图片组
handleAddGroup() {
this.data.options.listRight.push([
{ img: "", url: "" },
{ img: "", url: "" },
{ img: "", url: "" },
]);
},
// 删除图片组
handleTabRemove(index) {
this.data.options.listRight.splice(index, 1);
},
// 打开图片链接
handleSelectLink(item) {
this.$refs.liliDialog.open("link");
this.selected = item;
},
callbackSelected(item) {
// 选择图片回调
this.picModelFlag = false;
if (!item?.url) return;
this.selected.img = item.url;
},
// 删除图片
handleDel(index) {
this.data.options.list.splice(index, 1);
},
selectedLink(val) {
// 选择链接回调
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
// 打开选择图片modal
handleSelectImg(item) {
this.selected = item;
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
},
};
defineOptions({ name: "modelCarousel" });
const props = defineProps(["data"]);
const { proxy } = getCurrentInstance();
const showModal = ref(false); // modal显隐
const selected = ref(null); // 已选数据
const picModelFlag = ref(false); // 选择图片modal
const userInfo = reactive({});
const articleList = ref([
{ title: "促销计算规则" },
{ title: "商家申请开店" },
{ title: "商家账号注册" },
{ title: "促销计算规则" },
]);
const liliDialog = ref(null);
// ossManageRef:模板 ref 与组件 import 同名,改名避免互相覆盖
const ossManageRef = ref(null);
function handleSelectModel() {
// 编辑模块
showModal.value = true;
}
// 添加轮播
function handleAdd() {
props.data.options.list.push({ img: "", url: "" });
proxy.$forceUpdate();
}
// 添加图片组
function handleAddGroup() {
props.data.options.listRight.push([
{ img: "", url: "" },
{ img: "", url: "" },
{ img: "", url: "" },
]);
}
// 删除图片组
function handleTabRemove(index) {
props.data.options.listRight.splice(index, 1);
}
// 打开图片链接
function handleSelectLink(item) {
liliDialog.value.open("link");
selected.value = item;
}
function callbackSelected(item) {
// 选择图片回调
picModelFlag.value = false;
if (!item?.url) return;
selected.value.img = item.url;
}
// 删除图片
function handleDel(index) {
props.data.options.list.splice(index, 1);
}
function selectedLink(val) {
// 选择链接回调
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
// 打开选择图片modal
function handleSelectImg(item) {
selected.value = item;
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
</script>
<style scoped lang="scss">

View File

@@ -105,7 +105,7 @@
</template>
</el-dialog>
<!-- 选择商品。链接 -->
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<liliDialog ref="liliDialogRef" @selectedLink="selectedLink"></liliDialog>
<!-- 选择图片 -->
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
@@ -113,80 +113,79 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
props: {
data: {
type: Object,
default: null,
},
import { formatLinkType } from "@/utils/filters";
const props = defineProps({
data: {
type: Object,
default: null,
},
components: { ossManage },
data() {
return {
options: this.data.options, // 当前类型数据
showModal: false, // modal显隐
selected: {}, // 弹窗编辑草稿
editTarget: null, // 确认后写回的原数据
picModelFlag: false, // 图片选择器
};
},
methods: {
cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
},
// 打开装修modal
handleSelectModel(item) {
this.editTarget = item;
this.selected = this.cloneItem(item);
this.showModal = true;
},
handleSelectLink(item, index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
},
// 选择链接回调
selectedLink(val) {
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
handleSelectImg() {
// 选择图片
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
// 选择图片回调
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.selected.img = val.url;
},
handleCancelModal() {
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
handleConfirmModal() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
},
};
});
const options = ref(props.data.options); // 当前类型数据
const showModal = ref(false); // modal显隐
const selected = ref({}); // 弹窗编辑草稿
const editTarget = ref(null); // 确认后写回的原数据
const picModelFlag = ref(false); // 图片选择器
const liliDialogRef = ref(null);
const ossManageRef = ref(null);
function cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
}
// 打开装修modal
function handleSelectModel(item) {
editTarget.value = item;
selected.value = cloneItem(item);
showModal.value = true;
}
function handleSelectLink(item, index) {
// 调起选择链接弹窗
liliDialogRef.value.open("link");
}
// 选择链接回调
function selectedLink(val) {
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
function handleSelectImg() {
// 选择图片
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
// 选择图片回调
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url) return;
selected.value.img = val.url;
}
function handleCancelModal() {
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
function handleConfirmModal() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
</script>
<style lang="scss" scoped>
@import "./setup-box.scss";

View File

@@ -174,117 +174,115 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage.vue";
import { formatLinkType } from "@/utils/filters";
export default {
defineOptions({
name: "forYour",
data() {
return {
showModal: false,
draftOptions: null,
picModelFlag: false,
selectedData: "", //选中的数据
goodsIndex: 0, // 商品索引
defaultCallbackImageType: "",
};
},
props: {
data: {
type: Object,
default: {},
},
},
components: {
ossManage,
},
mounted() {
},
methods: {
cloneOptions(options) {
return JSON.parse(JSON.stringify(options || {}));
},
handleSelectImg(type, index) {
this.defaultCallbackImageType = type;
this.goodsIndex = index;
// 选择图片
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
});
handleSelectGoods(val, index) {
this.$refs.liliDialog.open("goods");
this.$refs.liliDialog.singleGoods();
this.selectedData = val;
this.goodsIndex = index;
},
handleSelectLink(val, index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
this.selectedData = val;
this.goodsIndex = index;
},
// 回显图片
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url || !this.draftOptions) return;
if (this.defaultCallbackImageType == "image") {
this.draftOptions.data.image.src = val.url;
} else {
this.draftOptions.data[this.defaultCallbackImageType][
this.goodsIndex
].img = val.url;
}
},
// 选择商品回调
selectedGoodsData(val) {
if (!val?.length || !this.draftOptions) return;
const item = this.draftOptions.data.hot.list[this.goodsIndex];
item.img = val[0].thumbnail;
item.title = val[0].goodsName;
item.price = val[0].price;
item.url = this.$filters.formatLinkType(val[0]);
},
// 选择链接回调
selectedLink(val) {
if (!this.draftOptions) return;
if (this.selectedData == "image") {
this.draftOptions.data.image.url = this.$filters.formatLinkType(val);
} else {
this.draftOptions.data.list[this.goodsIndex].url =
this.$filters.formatLinkType(val);
}
},
handleSelectModel() {
this.draftOptions = this.cloneOptions(this.data.options);
this.showModal = true;
},
handleCancelModal() {
this.draftOptions = null;
this.showModal = false;
},
handleConfirmModal() {
if (this.draftOptions) {
const saved = this.cloneOptions(this.draftOptions);
this.data.options.title = saved.title;
this.data.options.data = saved.data;
}
this.draftOptions = null;
this.showModal = false;
},
const props = defineProps({
data: {
type: Object,
default: {},
},
};
});
const showModal = ref(false);
const draftOptions = ref(null);
const picModelFlag = ref(false);
const selectedData = ref(""); //选中的数据
const goodsIndex = ref(0); // 商品索引
const defaultCallbackImageType = ref("");
const liliDialog = ref(null);
// ossManageRef:模板 ref 与组件 import 同名,改名避免互相覆盖
const ossManageRef = ref(null);
function cloneOptions(options) {
return JSON.parse(JSON.stringify(options || {}));
}
function handleSelectImg(type, index) {
defaultCallbackImageType.value = type;
goodsIndex.value = index;
// 选择图片
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function handleSelectGoods(val, index) {
liliDialog.value.open("goods");
liliDialog.value.singleGoods();
selectedData.value = val;
goodsIndex.value = index;
}
function handleSelectLink(val, index) {
// 调起选择链接弹窗
liliDialog.value.open("link");
selectedData.value = val;
goodsIndex.value = index;
}
// 回显图片
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url || !draftOptions.value) return;
if (defaultCallbackImageType.value == "image") {
draftOptions.value.data.image.src = val.url;
} else {
draftOptions.value.data[defaultCallbackImageType.value][
goodsIndex.value
].img = val.url;
}
}
// 选择商品回调
function selectedGoodsData(val) {
if (!val?.length || !draftOptions.value) return;
const item = draftOptions.value.data.hot.list[goodsIndex.value];
item.img = val[0].thumbnail;
item.title = val[0].goodsName;
item.price = val[0].price;
item.url = formatLinkType(val[0]);
}
// 选择链接回调
function selectedLink(val) {
if (!draftOptions.value) return;
if (selectedData.value == "image") {
draftOptions.value.data.image.url = formatLinkType(val);
} else {
draftOptions.value.data.list[goodsIndex.value].url =
formatLinkType(val);
}
}
function handleSelectModel() {
draftOptions.value = cloneOptions(props.data.options);
showModal.value = true;
}
function handleCancelModal() {
draftOptions.value = null;
showModal.value = false;
}
function handleConfirmModal() {
if (draftOptions.value) {
const saved = cloneOptions(draftOptions.value);
props.data.options.title = saved.title;
props.data.options.data = saved.data;
}
draftOptions.value = null;
showModal.value = false;
}
</script>
<style scoped lang="scss">

View File

@@ -127,137 +127,132 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { formatLinkType } from "@/utils/filters";
export default {
name: "goods",
defineOptions({ name: "goods" });
props: {
data: {
type: Object,
default: {},
},
const props = defineProps({
data: {
type: Object,
default: {},
},
data() {
return {
tabIndex: 0,
current: 0,
showModal: false,
draftOptions: null,
tabCurrentlyIndex: 0, // 选项卡索引
};
},
mounted(){
this.tabIndex = this.data.options.labels[0].___index
},
methods: {
cloneOptions(options) {
return JSON.parse(JSON.stringify(options || {}));
},
/**
* 算出最大的index 然后索引叠加
*/
handleTabsAdd() {
if (!this.draftOptions) return;
const findAllIndex = this.draftOptions.labels.map((item) => item.___index);
const max = Math.max.apply(null, findAllIndex);
});
this.draftOptions.labels.push({
label: "标签" + (max + 1),
___index: max + 1,
});
},
// 删除标签
handleContextMenuDelete(item, index) {
if (!this.draftOptions) return;
this.draftOptions.list = this.draftOptions.list.filter(
(lab) => lab.___index != item.___index
);
this.draftOptions.labels.splice(index, 1);
},
// 删除商品
delGoods(val) {
if (!this.draftOptions) return;
this.draftOptions.list.forEach((item, i) => {
if (
item.title == val.title &&
item.___index == this.tabCurrentlyIndex
) {
this.draftOptions.list.splice(i, 1);
}
});
},
// 切换选项卡
handleClickTab(name) {
this.tabCurrentlyIndex = name.paneName ?? name;
},
// 添加当前选项卡中的商品
addCurrentGoods() {
this.$refs.liliDialog.clearGoodsSelected();
this.$refs.liliDialog.open("goods");
},
// 编辑模块
handleSelectModel() {
this.draftOptions = this.cloneOptions({
title: this.data.options.title,
labels: this.data.options.labels,
list: this.data.options.list,
});
this.tabCurrentlyIndex = this.draftOptions.labels[0]?.___index ?? 0;
this.tabIndex = String(this.tabCurrentlyIndex);
this.showModal = true;
},
// 选择商品回调
selectedGoodsData(val) {
if (!val?.length || !this.draftOptions) return;
val.forEach((item) => {
this.draftOptions.list.push({
img: item.thumbnail,
price: item.price,
title: item.goodsName,
desc: "",
url: `/goodsDetail?skuId=${item.id}&goodsId=${item.goodsId}`,
___index: this.tabCurrentlyIndex,
});
});
},
// 选择链接回调
selectedLink(val) {
if (!this.draftOptions?.list?.length) return;
const item = this.draftOptions.list[this.current];
if (!item) return;
item.url = this.$filters.formatLinkType(val);
item.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
handleSelectLink(index) {
this.$refs.liliDialog.open("link");
this.current = index;
},
handleCancelModal() {
this.draftOptions = null;
this.showModal = false;
},
handleConfirmModal() {
if (this.draftOptions) {
const saved = this.cloneOptions(this.draftOptions);
this.data.options.title = saved.title;
this.data.options.labels.splice(
0,
this.data.options.labels.length,
...saved.labels
);
this.data.options.list.splice(
0,
this.data.options.list.length,
...saved.list
);
}
this.draftOptions = null;
this.showModal = false;
},
},
};
const tabIndex = ref(0);
const current = ref(0);
const showModal = ref(false);
const draftOptions = ref(null);
const tabCurrentlyIndex = ref(0); // 选项卡索引
const liliDialog = ref(null);
onMounted(() => {
tabIndex.value = props.data.options.labels[0].___index;
});
function cloneOptions(options) {
return JSON.parse(JSON.stringify(options || {}));
}
/**
* 算出最大的index 然后索引叠加
*/
function handleTabsAdd() {
if (!draftOptions.value) return;
const findAllIndex = draftOptions.value.labels.map((item) => item.___index);
const max = Math.max.apply(null, findAllIndex);
draftOptions.value.labels.push({
label: "标签" + (max + 1),
___index: max + 1,
});
}
// 删除标签
function handleContextMenuDelete(item, index) {
if (!draftOptions.value) return;
draftOptions.value.list = draftOptions.value.list.filter(
(lab) => lab.___index != item.___index
);
draftOptions.value.labels.splice(index, 1);
}
// 删除商品
function delGoods(val) {
if (!draftOptions.value) return;
draftOptions.value.list.forEach((item, i) => {
if (item.title == val.title && item.___index == tabCurrentlyIndex.value) {
draftOptions.value.list.splice(i, 1);
}
});
}
// 切换选项卡
function handleClickTab(name) {
tabCurrentlyIndex.value = name.paneName ?? name;
}
// 添加当前选项卡中的商品
function addCurrentGoods() {
liliDialog.value.clearGoodsSelected();
liliDialog.value.open("goods");
}
// 编辑模块
function handleSelectModel() {
draftOptions.value = cloneOptions({
title: props.data.options.title,
labels: props.data.options.labels,
list: props.data.options.list,
});
tabCurrentlyIndex.value = draftOptions.value.labels[0]?.___index ?? 0;
tabIndex.value = String(tabCurrentlyIndex.value);
showModal.value = true;
}
// 选择商品回调
function selectedGoodsData(val) {
if (!val?.length || !draftOptions.value) return;
val.forEach((item) => {
draftOptions.value.list.push({
img: item.thumbnail,
price: item.price,
title: item.goodsName,
desc: "",
url: `/goodsDetail?skuId=${item.id}&goodsId=${item.goodsId}`,
___index: tabCurrentlyIndex.value,
});
});
}
// 选择链接回调
function selectedLink(val) {
if (!draftOptions.value?.list?.length) return;
const item = draftOptions.value.list[current.value];
if (!item) return;
item.url = formatLinkType(val);
item.type = val.___type === "other" && val.url === "" ? "link" : "other";
}
function handleSelectLink(index) {
liliDialog.value.open("link");
current.value = index;
}
function handleCancelModal() {
draftOptions.value = null;
showModal.value = false;
}
function handleConfirmModal() {
if (draftOptions.value) {
const saved = cloneOptions(draftOptions.value);
props.data.options.title = saved.title;
props.data.options.labels.splice(
0,
props.data.options.labels.length,
...saved.labels
);
props.data.options.list.splice(
0,
props.data.options.list.length,
...saved.list
);
}
draftOptions.value = null;
showModal.value = false;
}
</script>
<style scoped lang="scss">

View File

@@ -138,7 +138,7 @@
<!-- 选择商品。链接 -->
<liliDialog
ref="liliDialog"
ref="liliDialogRef"
@selectedLink="selectedLink"
@selectedGoodsData="selectedGoodsData"
></liliDialog>
@@ -150,119 +150,121 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import left from "./mixs/mix-goods";
import right from "./mixs/mix-brand";
import ossManage from "@/views/sys/oss-manage/ossManage.vue";
import { formatLinkType } from "@/utils/filters";
export default {
name: "mixModel",
data() {
return {
showModal: false,
current: null, // 当前编辑的模块草稿
picModelFlag: false,
selectedData: "", //选中的数据
goodsIndex: 0, // 商品索引
align: "",
defaultCallbackImageType: "",
};
},
props: {
data: {
type: Object,
default: {},
},
},
components: {
left,
right,
ossManage,
},
mounted() {},
methods: {
cloneModule(module) {
return JSON.parse(JSON.stringify(module || {}));
},
handleSelectImg(type, index) {
this.defaultCallbackImageType = type;
this.goodsIndex = index;
// 选择图片
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
defineOptions({ name: "mixModel" });
handleSelectGoods(val, index) {
this.$refs.liliDialog.open("goods");
this.$refs.liliDialog.singleGoods();
this.selectedData = val;
this.goodsIndex = index;
},
handleSelectLink(val, index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
this.selectedData = val;
this.goodsIndex = index;
},
// 回显图片
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url || !this.current) return;
if (this.defaultCallbackImageType == "default") {
this.current.data.image.src = val.url;
} else {
this.current.data.list[this.goodsIndex].img = val.url;
}
},
// 选择商品回调
selectedGoodsData(val) {
if (!val?.length || !this.current) return;
const goods = val[0];
const item = this.current.data.list[this.goodsIndex];
item.img = goods.thumbnail;
item.title = goods.goodsName;
item.price = goods.price;
item.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
},
// 选择链接回调
selectedLink(val) {
if (!this.current) return;
const formattedUrl = this.$filters.formatLinkType(val);
if (this.selectedData != "list") {
this.current.data[this.selectedData].url = formattedUrl;
}
if (this.selectedData == "list") {
this.current.data.list[this.goodsIndex].url = formattedUrl;
}
},
handleSelectModel(align) {
this.align = align;
this.current = this.cloneModule(this.data.options[align]);
this.showModal = true;
},
handleCancelModal() {
this.current = null;
this.showModal = false;
},
handleConfirmModal() {
if (this.current && this.align) {
this.data.options[this.align] = this.cloneModule(this.current);
}
this.current = null;
this.showModal = false;
},
const props = defineProps({
data: {
type: Object,
default: {},
},
};
});
const showModal = ref(false);
const current = ref(null); // 当前编辑的模块草稿
const picModelFlag = ref(false);
const selectedData = ref(""); //选中的数据
const goodsIndex = ref(0); // 商品索引
const align = ref("");
const defaultCallbackImageType = ref("");
const liliDialogRef = ref(null);
const ossManageRef = ref(null);
function cloneModule(module) {
return JSON.parse(JSON.stringify(module || {}));
}
function handleSelectImg(type, index) {
defaultCallbackImageType.value = type;
goodsIndex.value = index;
// 选择图片
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function handleSelectGoods(val, index) {
liliDialogRef.value.open("goods");
liliDialogRef.value.singleGoods();
selectedData.value = val;
goodsIndex.value = index;
}
function handleSelectLink(val, index) {
// 调起选择链接弹窗
liliDialogRef.value.open("link");
selectedData.value = val;
goodsIndex.value = index;
}
// 回显图片
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url || !current.value) return;
if (defaultCallbackImageType.value == "default") {
current.value.data.image.src = val.url;
} else {
current.value.data.list[goodsIndex.value].img = val.url;
}
}
// 选择商品回调
function selectedGoodsData(val) {
if (!val?.length || !current.value) return;
const goods = val[0];
const item = current.value.data.list[goodsIndex.value];
item.img = goods.thumbnail;
item.title = goods.goodsName;
item.price = goods.price;
item.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
}
// 选择链接回调
function selectedLink(val) {
if (!current.value) return;
const formattedUrl = formatLinkType(val);
if (selectedData.value != "list") {
current.value.data[selectedData.value].url = formattedUrl;
}
if (selectedData.value == "list") {
current.value.data.list[goodsIndex.value].url = formattedUrl;
}
}
function handleSelectModel(type) {
align.value = type;
current.value = cloneModule(props.data.options[type]);
showModal.value = true;
}
function handleCancelModal() {
current.value = null;
showModal.value = false;
}
function handleConfirmModal() {
if (current.value && align.value) {
props.data.options[align.value] = cloneModule(current.value);
}
current.value = null;
showModal.value = false;
}
</script>
<style scoped lang="scss">

View File

@@ -24,19 +24,17 @@
</div>
</template>
<script>
<script setup>
import { ArrowRight } from "@element-plus/icons-vue";
export default {
name: "mix-brand",
components: { ArrowRight },
props: {
data: {
type: Object,
default: () => ({}),
},
defineOptions({ name: "mix-brand" });
defineProps({
data: {
type: Object,
default: () => ({}),
},
};
});
</script>
<style scoped lang="scss">

View File

@@ -28,16 +28,17 @@
</div>
</template>
<script>
export default {
<script setup>
defineOptions({
name: "mix-goods",
props: {
data: {
type: Object,
default: () => ({}),
},
});
defineProps({
data: {
type: Object,
default: () => ({}),
},
};
});
</script>
<style scoped lang="scss">

View File

@@ -242,125 +242,123 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
props: {
data: {
type: Object,
default: null,
},
import { formatLinkType } from "@/utils/filters";
const props = defineProps({
data: {
type: Object,
default: null,
},
components: {
ossManage,
},
data() {
return {
options: this.data.options, // 当前数据
showModal: false, // modal显隐
showModal1: false, // modal显隐
selected: {}, // 弹窗编辑草稿
editTarget: null, // 确认后写回的原数据
picModelFlag: false, // 选择图片modal
};
},
methods: {
cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
},
// 装修modal
handleSelectModel(item, type) {
this.editTarget = item;
this.selected = this.cloneItem(item);
if (type) {
this.showModal1 = true;
} else {
this.showModal = true;
}
},
handleSelectLink() {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
},
handleSelectGoods(item) {
if (item) {
this.editTarget = item;
this.selected = this.cloneItem(item);
}
this.$refs.liliDialog.open("goods", "single");
setTimeout(() => {
this.$refs.liliDialog.goodsData = [this.selected];
}, 500);
},
// 选择链接回调
selectedLink(val) {
if (!this.selected) return;
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
// 选择商品回调
selectedGoodsData(val) {
if (!val?.length || !this.selected) return;
const goods = val[0];
this.selected.img = goods.thumbnail;
this.selected.price = goods.price;
this.selected.name = goods.goodsName;
this.selected.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
if (this.editTarget && !this.showModal && !this.showModal1) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
this.editTarget = null;
this.selected = {};
}
},
handleSelectImg() {
// 选择图片
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
// 选择图片回显
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.selected.img = val.url;
},
handleCancelModal() {
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
handleConfirmModal() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
handleCancelModal1() {
this.editTarget = null;
this.selected = {};
this.showModal1 = false;
},
handleConfirmModal1() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showModal1 = false;
},
},
};
});
const options = ref(props.data.options); // 当前数据
const showModal = ref(false); // modal显隐
const showModal1 = ref(false); // modal显隐
const selected = ref({}); // 弹窗编辑草稿
const editTarget = ref(null); // 确认后写回的原数据
const picModelFlag = ref(false); // 选择图片modal
// ossManageRef:模板 ref 与组件 import 同名,改名避免互相覆盖
const liliDialog = ref(null);
const ossManageRef = ref(null);
function cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
}
// 装修modal
function handleSelectModel(item, type) {
editTarget.value = item;
selected.value = cloneItem(item);
if (type) {
showModal1.value = true;
} else {
showModal.value = true;
}
}
function handleSelectLink() {
// 调起选择链接弹窗
liliDialog.value.open("link");
}
function handleSelectGoods(item) {
if (item) {
editTarget.value = item;
selected.value = cloneItem(item);
}
liliDialog.value.open("goods", "single");
setTimeout(() => {
liliDialog.value.goodsData = [selected.value];
}, 500);
}
// 选择链接回调
function selectedLink(val) {
if (!selected.value) return;
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
// 选择商品回调
function selectedGoodsData(val) {
if (!val?.length || !selected.value) return;
const goods = val[0];
selected.value.img = goods.thumbnail;
selected.value.price = goods.price;
selected.value.name = goods.goodsName;
selected.value.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
if (editTarget.value && !showModal.value && !showModal1.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
editTarget.value = null;
selected.value = {};
}
}
function handleSelectImg() {
// 选择图片
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
// 选择图片回显
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url) return;
selected.value.img = val.url;
}
function handleCancelModal() {
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
function handleConfirmModal() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
function handleCancelModal1() {
editTarget.value = null;
selected.value = {};
showModal1.value = false;
}
function handleConfirmModal1() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showModal1.value = false;
}
</script>
<style lang="scss" scoped>
@import "./setup-box.scss";

View File

@@ -96,118 +96,116 @@
></liliDialog>
</div>
</template>
<script>
export default {
props:{
data:{
type: Object,
default: null
}
},
data() {
return {
currentIndex:0,
conData:this.data,
selected:{},
editTarget: null,
showModal:false,
showGoodsModal: false,
showContent:true,
draftNavList: [],
draftGoodsList: [],
}
},
watch:{
data:function(val){
this.conData = val
},
conData:function(val){
this.$emit('content',val)
}
},
methods:{
cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
},
cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
},
changeCurr(index){
this.currentIndex = index;
},
handleSelectModel () {
this.draftNavList = this.cloneList(this.conData.options.navList);
this.draftGoodsList = this.cloneList(this.conData.options.list);
this.showModal = true;
},
handleSelectGoods(item) {
this.editTarget = item;
this.selected = this.cloneItem(item);
this.showGoodsModal = true;
},
handleOpenGoodsPicker() {
this.$refs.liliDialog.open('goods', 'single');
setTimeout(() => {
this.$refs.liliDialog.goodsData = [this.selected];
}, 500);
},
selectedGoodsData(val){
if (!val?.length) return;
const goods = val[0];
this.selected.img = goods.thumbnail;
this.selected.price = goods.price;
this.selected.name = goods.goodsName;
this.selected.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
},
handleDelNav(index){
this.draftNavList.splice(index,1);
this.draftGoodsList.splice(index,1);
},
handleAddNav(){
this.draftNavList.push({ title:'', desc:'' });
this.draftGoodsList.push([
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
]);
},
handleCancelModal() {
this.draftNavList = [];
this.draftGoodsList = [];
this.showModal = false;
},
handleConfirmModal() {
this.conData.options.navList = this.cloneList(this.draftNavList);
this.conData.options.list = this.cloneList(this.draftGoodsList);
this.draftNavList = [];
this.draftGoodsList = [];
this.showContent = false;
this.$nextTick(() => {
this.showContent = true;
});
this.showModal = false;
},
handleCancelGoodsModal() {
this.editTarget = null;
this.selected = {};
this.showGoodsModal = false;
},
handleConfirmGoodsModal() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showGoodsModal = false;
},
<script setup>
import { ref, watch, nextTick } from "vue";
const props = defineProps({
data:{
type: Object,
default: null
}
});
const emit = defineEmits(['content']);
const currentIndex = ref(0);
const conData = ref(props.data);
const selected = ref({});
const editTarget = ref(null);
const showModal = ref(false);
const showGoodsModal = ref(false);
const showContent = ref(true);
const draftNavList = ref([]);
const draftGoodsList = ref([]);
const liliDialog = ref(null);
watch(() => props.data, function(val){
conData.value = val
});
watch(conData, function(val){
emit('content', val)
});
function cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
}
function cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
}
function changeCurr(index){
currentIndex.value = index;
}
function handleSelectModel () {
draftNavList.value = cloneList(conData.value.options.navList);
draftGoodsList.value = cloneList(conData.value.options.list);
showModal.value = true;
}
function handleSelectGoods(item) {
editTarget.value = item;
selected.value = cloneItem(item);
showGoodsModal.value = true;
}
function handleOpenGoodsPicker() {
liliDialog.value.open('goods', 'single');
setTimeout(() => {
liliDialog.value.goodsData = [selected.value];
}, 500);
}
function selectedGoodsData(val){
if (!val?.length) return;
const goods = val[0];
selected.value.img = goods.thumbnail;
selected.value.price = goods.price;
selected.value.name = goods.goodsName;
selected.value.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
}
function handleDelNav(index){
draftNavList.value.splice(index,1);
draftGoodsList.value.splice(index,1);
}
function handleAddNav(){
draftNavList.value.push({ title:'', desc:'' });
draftGoodsList.value.push([
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
{ img:'', name:'', price:0, url:'' },
]);
}
function handleCancelModal() {
draftNavList.value = [];
draftGoodsList.value = [];
showModal.value = false;
}
function handleConfirmModal() {
conData.value.options.navList = cloneList(draftNavList.value);
conData.value.options.list = cloneList(draftGoodsList.value);
draftNavList.value = [];
draftGoodsList.value = [];
showContent.value = false;
nextTick(() => {
showContent.value = true;
});
showModal.value = false;
}
function handleCancelGoodsModal() {
editTarget.value = null;
selected.value = {};
showGoodsModal.value = false;
}
function handleConfirmGoodsModal() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showGoodsModal.value = false;
}
</script>
<style lang="scss" scoped>

View File

@@ -49,7 +49,7 @@
</el-dialog>
<!-- 选择商品。链接 -->
<liliDialog
ref="liliDialog"
ref="liliDialogRef"
@selectedLink="selectedLink"
@selectedGoodsData="selectedGoodsData"
></liliDialog>
@@ -60,89 +60,89 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
name: "oneRowThreeColumns",
components:{ossManage},
props: {
data: {
type: Object,
default: {}
}
},
data () {
return {
showModal:false,
draftList: [],
picModelFlag:false,
current:0,
};
},
methods: {
cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
},
// 回显图片
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.draftList[this.current].img = val.url;
},
// 编辑模块
handleSelectModel() {
this.draftList = this.cloneList(this.data.options.list);
this.showModal = true;
},
// 选择商品回调
selectedGoodsData(val) {
const goods = val[0];
if (!goods) return;
const item = this.draftList[this.current];
item.img = goods.thumbnail;
item.price = goods.price;
item.name = goods.goodsName;
item.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
},
// 选择链接回调
selectedLink(val) {
const item = this.draftList[this.current];
item.url = this.$filters.formatLinkType(val);
item.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
handleSelectLink(index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
this.current = index;
},
handleSelectImg(index){
// 选择图片
this.current = index;
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
handleCancelModal() {
this.draftList = [];
this.showModal = false;
},
handleConfirmModal() {
const saved = this.cloneList(this.draftList);
this.data.options.list.splice(0, this.data.options.list.length, ...saved);
this.draftList = [];
this.showModal = false;
},
import { formatLinkType } from "@/utils/filters";
defineOptions({ name: "oneRowThreeColumns" });
const props = defineProps({
data: {
type: Object,
default: {}
}
});
const showModal = ref(false);
const draftList = ref([]);
const picModelFlag = ref(false);
const current = ref(0);
const liliDialogRef = ref(null);
const ossManageRef = ref(null);
function cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
}
// 回显图片
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url) return;
draftList.value[current.value].img = val.url;
}
// 编辑模块
function handleSelectModel() {
draftList.value = cloneList(props.data.options.list);
showModal.value = true;
}
// 选择商品回调
function selectedGoodsData(val) {
const goods = val[0];
if (!goods) return;
const item = draftList.value[current.value];
item.img = goods.thumbnail;
item.price = goods.price;
item.name = goods.goodsName;
item.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
}
// 选择链接回调
function selectedLink(val) {
const item = draftList.value[current.value];
item.url = formatLinkType(val);
item.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
function handleSelectLink(index) {
// 调起选择链接弹窗
liliDialogRef.value.open("link");
current.value = index;
}
function handleSelectImg(index) {
// 选择图片
current.value = index;
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function handleCancelModal() {
draftList.value = [];
showModal.value = false;
}
function handleConfirmModal() {
const saved = cloneList(draftList.value);
props.data.options.list.splice(0, props.data.options.list.length, ...saved);
draftList.value = [];
showModal.value = false;
}
</script>

View File

@@ -80,92 +80,97 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import { formatLinkType } from "@/utils/filters";
export default {
name: "onlyGoodsList",
props: {
data: {
type: Object,
default: {},
},
},
data() {
return {
current: 0,
showModal: false,
draftList: [],
};
},
mounted(){
defineOptions({ name: "onlyGoodsList" });
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
methods: {
cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
},
// 删除商品
delGoods(val, i) {
this.draftList.splice(i, 1);
},
// 添加当前选项卡中的商品
addCurrentGoods() {
this.$refs.liliDialog.open("goods");
},
// 编辑模块
handleSelectModel() {
this.draftList = this.cloneList(this.data.options.list);
this.showModal = true;
},
// 选择商品回调
selectedGoodsData(val) {
if (!val?.length || !this.draftList) return;
val.forEach((item) => {
if (this.isGoodsInDraftList(item)) return;
this.draftList.push({
img: item.thumbnail,
price: item.price,
title: item.goodsName,
desc: "",
skuId: item.id,
goodsId: item.goodsId,
url: `/goodsDetail?skuId=${item.id}&goodsId=${item.goodsId}`,
});
});
},
isGoodsInDraftList(goods) {
const targetUrl = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
return this.draftList.some(
(draft) =>
(draft.skuId && String(draft.skuId) === String(goods.id)) ||
(draft.url && draft.url === targetUrl)
);
},
// 选择链接回调
selectedLink(val) {
if (!this.draftList?.length) return;
const item = this.draftList[this.current];
item.url = this.$filters.formatLinkType(val);
item.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
handleSelectLink(index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
this.current = index;
},
handleCancelModal() {
this.draftList = [];
this.showModal = false;
},
handleConfirmModal() {
const saved = this.cloneList(this.draftList);
this.data.options.list.splice(0, this.data.options.list.length, ...saved);
this.draftList = [];
this.showModal = false;
},
},
};
});
const current = ref(0);
const showModal = ref(false);
const draftList = ref([]);
const liliDialog = ref(null);
function cloneList(list) {
return JSON.parse(JSON.stringify(list || []));
}
// 删除商品
function delGoods(val, i) {
draftList.value.splice(i, 1);
}
// 添加当前选项卡中的商品
function addCurrentGoods() {
liliDialog.value.open("goods");
}
// 编辑模块
function handleSelectModel() {
draftList.value = cloneList(props.data.options.list);
showModal.value = true;
}
// 选择商品回调
function selectedGoodsData(val) {
if (!val?.length || !draftList.value) return;
val.forEach((item) => {
if (isGoodsInDraftList(item)) return;
draftList.value.push({
img: item.thumbnail,
price: item.price,
title: item.goodsName,
desc: "",
skuId: item.id,
goodsId: item.goodsId,
url: `/goodsDetail?skuId=${item.id}&goodsId=${item.goodsId}`,
});
});
}
function isGoodsInDraftList(goods) {
const targetUrl = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
return draftList.value.some(
(draft) =>
(draft.skuId && String(draft.skuId) === String(goods.id)) ||
(draft.url && draft.url === targetUrl)
);
}
// 选择链接回调
function selectedLink(val) {
if (!draftList.value?.length) return;
const item = draftList.value[current.value];
item.url = formatLinkType(val);
item.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
function handleSelectLink(index) {
// 调起选择链接弹窗
liliDialog.value.open("link");
current.value = index;
}
function handleCancelModal() {
draftList.value = [];
showModal.value = false;
}
function handleConfirmModal() {
const saved = cloneList(draftList.value);
props.data.options.list.splice(0, props.data.options.list.length, ...saved);
draftList.value = [];
showModal.value = false;
}
</script>
<style scoped lang="scss">

View File

@@ -227,113 +227,111 @@
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
ref="ossManageRef"
/>
</el-dialog>
</div>
</template>
<script>
<script setup>
import { ref, nextTick } from "vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
props: {
data: {
type: Object,
default: {},
},
import { formatLinkType } from "@/utils/filters";
const props = defineProps({
data: {
type: Object,
default: {}, // 历史遗留:对象默认值未用工厂函数,原样保留
},
components: {
ossManage,
},
data() {
return {
msgLeft: this.data.options.contentLeft, // 左侧数据
msgRight: this.data.options.contentRight, // 右侧数据
showModal: false, // modal显隐
showModal1: false, // modal显隐
selected: {}, // 弹窗编辑草稿
editTarget: null, // 确认后写回的原数据
picModelFlag: false, // 图片选择
};
},
methods: {
cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
},
// 编辑
handleSelectModel(item, type) {
this.editTarget = item;
this.selected = this.cloneItem(item);
if (type) {
this.showModal1 = true;
} else {
this.showModal = true;
}
},
handleSelectLink(item, index) {
// 调起选择链接弹窗
this.$refs.liliDialog.open("link");
},
handleSelectGoods(item) {
// 调起选择商品
this.$refs.liliDialog.open("goods", "single");
},
// 选择链接回调
selectedLink(val) {
this.selected.url = this.$filters.formatLinkType(val);
this.selected.type =
val.___type === "other" && val.url === "" ? "link" : "other";
},
// 选择商品回调
selectedGoodsData(val) {
let goods = val[0];
this.selected.img = goods.thumbnail;
this.selected.price = goods.price;
this.selected.name = goods.goodsName;
this.selected.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
},
handleSelectImg() {
// 选择图片
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
// 选择图片回调
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.selected.img = val.url;
},
handleCancelModal() {
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
handleConfirmModal() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showModal = false;
},
handleCancelModal1() {
this.editTarget = null;
this.selected = {};
this.showModal1 = false;
},
handleConfirmModal1() {
if (this.editTarget) {
Object.assign(this.editTarget, this.cloneItem(this.selected));
}
this.editTarget = null;
this.selected = {};
this.showModal1 = false;
},
},
};
});
const msgLeft = ref(props.data.options.contentLeft); // 左侧数据
const msgRight = ref(props.data.options.contentRight); // 右侧数据
const showModal = ref(false); // modal显隐
const showModal1 = ref(false); // modal显隐
const selected = ref({}); // 弹窗编辑草稿
const editTarget = ref(null); // 确认后写回的原数据
const picModelFlag = ref(false); // 图片选择
// ossManageRef:模板 ref 与组件 import 同名,改名避免互相覆盖
const liliDialog = ref(null);
const ossManageRef = ref(null);
function cloneItem(item) {
return JSON.parse(JSON.stringify(item || {}));
}
// 编辑
function handleSelectModel(item, type) {
editTarget.value = item;
selected.value = cloneItem(item);
if (type) {
showModal1.value = true;
} else {
showModal.value = true;
}
}
function handleSelectLink(item, index) {
// 调起选择链接弹窗
liliDialog.value.open("link");
}
function handleSelectGoods(item) {
// 调起选择商品
liliDialog.value.open("goods", "single");
}
// 选择链接回调
function selectedLink(val) {
selected.value.url = formatLinkType(val);
selected.value.type =
val.___type === "other" && val.url === "" ? "link" : "other";
}
// 选择商品回调
function selectedGoodsData(val) {
let goods = val[0];
selected.value.img = goods.thumbnail;
selected.value.price = goods.price;
selected.value.name = goods.goodsName;
selected.value.url = `/goodsDetail?skuId=${goods.id}&goodsId=${goods.goodsId}`;
}
function handleSelectImg() {
// 选择图片
picModelFlag.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
// 选择图片回调
function callbackSelected(val) {
picModelFlag.value = false;
if (!val?.url) return;
selected.value.img = val.url;
}
function handleCancelModal() {
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
function handleConfirmModal() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showModal.value = false;
}
function handleCancelModal1() {
editTarget.value = null;
selected.value = {};
showModal1.value = false;
}
function handleConfirmModal1() {
if (editTarget.value) {
Object.assign(editTarget.value, cloneItem(selected.value));
}
editTarget.value = null;
selected.value = {};
showModal1.value = false;
}
</script>
<style lang="scss" scoped>
@import "./setup-box.scss";

View File

@@ -35,103 +35,100 @@
</div>
</div>
</template>
<script>
export default {
props: {
data: {
type: Object,
default: null,
},
},
data() {
return {
options: this.data.options, // 当前数据
actStatus: 0, // 0 未开始 1 进行中 2 已结束
actName: "限时秒杀",
currHour: "00", // 当前秒杀场
diffSeconds: 0, // 倒地时
hours: "00", // 小时
minutes: "00", // 分钟
seconds: "00", // 秒
interval: undefined, // 定时器
};
},
watch: {
diffSeconds(val) {
const hours = Math.floor(val / 3600);
// 当前秒数 / 60,向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模,超过小时数的分钟数
const minutes = Math.floor(val / 60) % 60;
// 当前的秒数 % 60,获取到 超过小时数、分钟数的秒数(秒数)
const seconds = val % 60;
this.hours = hours < 10 ? "0" + hours : hours;
this.minutes = minutes < 10 ? "0" + minutes : minutes;
this.seconds = seconds < 10 ? "0" + seconds : seconds;
<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
if (val === 0) {
clearInterval(this.interval);
this.hours = 0;
this.minutes = 0;
this.seconds = 0;
this.countDown(this.options.list);
const props = defineProps({
data: {
type: Object,
default: null,
},
});
const options = ref(props.data.options); // 当前数据
const actStatus = ref(0); // 0 未开始 1 进行中 2 已结束
const actName = ref("限时秒杀");
const currHour = ref("00"); // 当前秒杀场
const diffSeconds = ref(0); // 倒地时
const hours = ref("00"); // 小时
const minutes = ref("00"); // 分钟
const seconds = ref("00"); // 秒
const interval = ref(undefined); // 定时器
watch(diffSeconds, (val) => {
const hours = Math.floor(val / 3600);
// 当前秒数 / 60,向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模,超过小时数的分钟数
const minutes = Math.floor(val / 60) % 60;
// 当前的秒数 % 60,获取到 超过小时数、分钟数的秒数(秒数)
const seconds = val % 60;
hours.value = hours < 10 ? "0" + hours : hours;
minutes.value = minutes < 10 ? "0" + minutes : minutes;
seconds.value = seconds < 10 ? "0" + seconds : seconds;
if (val === 0) {
clearInterval(interval.value);
hours.value = 0;
minutes.value = 0;
seconds.value = 0;
countDown(options.value.list);
}
});
onMounted(() => {
countDown(options.value.list);
});
onBeforeUnmount(() => {
clearInterval(interval.value);
});
// 倒计时
function countDown(list) {
/**
* 默认倒计时两小时
* 如果没有开始,则显示未开始
* 进行中显示倒计时
* 今天的秒杀结束则显示已结束
*/
let nowHour = new Date().getHours();
if (nowHour < Number(list[0].time)) {
// 活动未开始
currHour.value = list[0].time;
actStatus.value = 0;
} else if (nowHour >= Number(list[list.length - 1].time + 2)) {
// 活动已结束
actStatus.value = 2;
currHour.value = list[list.length - 1].time;
} else {
// 活动进行中
actStatus.value = 1;
for (let i = 0; i < list.length; i++) {
if (nowHour == Number(list[i].time)) {
currHour.value = list[i].time;
}
},
},
mounted() {
this.countDown(this.options.list);
},
beforeUnmount() {
clearInterval(this.interval);
},
methods: {
// 倒计时
countDown(list) {
/**
* 默认倒计时两小时
* 如果没有开始,则显示未开始
* 进行中显示倒计时
* 今天的秒杀结束则显示已结束
*/
let nowHour = new Date().getHours();
if (nowHour < Number(list[0].time)) {
// 活动未开始
this.currHour = list[0].time;
this.actStatus = 0;
} else if (nowHour >= Number(list[list.length - 1].time + 2)) {
// 活动已结束
this.actStatus = 2;
this.currHour = list[list.length - 1].time;
} else {
// 活动进行中
this.actStatus = 1;
for (let i = 0; i < list.length; i++) {
if (nowHour == Number(list[i].time)) {
this.currHour = list[i].time;
}
if (
nowHour > Number(list[i].time) &&
nowHour < Number(list[i].time + 2)
) {
this.currHour = list[i].time;
}
}
// 当前0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
// 活动倒计时
this.diffSeconds = Math.floor(
(zeroTime +
3600 * 1000 * (this.currHour + 2) -
new Date().getTime()) /
1000
);
this.interval = setInterval(() => {
this.diffSeconds--;
}, 1000);
if (
nowHour > Number(list[i].time) &&
nowHour < Number(list[i].time + 2)
) {
currHour.value = list[i].time;
}
},
},
};
}
// 当前0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
// 活动倒计时
diffSeconds.value = Math.floor(
(zeroTime +
3600 * 1000 * (currHour.value + 2) -
new Date().getTime()) /
1000
);
interval.value = setInterval(() => {
diffSeconds.value--;
}, 1000);
}
}
</script>
<style lang="scss" scoped>
.seckill {

View File

@@ -35,103 +35,100 @@
</div>
</div>
</template>
<script>
export default {
props: {
data: {
type: Object,
default: null,
},
},
data() {
return {
options: this.data.options, // 当前数据
actStatus: 0, // 0 未开始 1 进行中 2 已结束
actName: "限时秒杀",
currHour: "00", // 当前秒杀场
diffSeconds: 0, // 倒地时
hours: "00", // 小时
minutes: "00", // 分钟
seconds: "00", // 秒
interval: undefined, // 定时器
};
},
watch: {
diffSeconds(val) {
const hours = Math.floor(val / 3600);
// 当前秒数 / 60,向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模,超过小时数的分钟数
const minutes = Math.floor(val / 60) % 60;
// 当前的秒数 % 60,获取到 超过小时数、分钟数的秒数(秒数)
const seconds = val % 60;
this.hours = hours < 10 ? "0" + hours : hours;
this.minutes = minutes < 10 ? "0" + minutes : minutes;
this.seconds = seconds < 10 ? "0" + seconds : seconds;
<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
if (val === 0) {
clearInterval(this.interval);
this.hours = 0;
this.minutes = 0;
this.seconds = 0;
this.countDown(this.options.list);
const props = defineProps({
data: {
type: Object,
default: null,
},
});
const options = ref(props.data.options); // 当前数据
const actStatus = ref(0); // 0 未开始 1 进行中 2 已结束
const actName = ref("限时秒杀");
const currHour = ref("00"); // 当前秒杀场
const diffSeconds = ref(0); // 倒地时
const hours = ref("00"); // 小时
const minutes = ref("00"); // 分钟
const seconds = ref("00"); // 秒
const interval = ref(undefined); // 定时器
watch(diffSeconds, (val) => {
const h = Math.floor(val / 3600);
// 当前秒数 / 60,向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模,超过小时数的分钟数
const m = Math.floor(val / 60) % 60;
// 当前的秒数 % 60,获取到 超过小时数、分钟数的秒数(秒数)
const s = val % 60;
hours.value = h < 10 ? "0" + h : h;
minutes.value = m < 10 ? "0" + m : m;
seconds.value = s < 10 ? "0" + s : s;
if (val === 0) {
clearInterval(interval.value);
hours.value = 0;
minutes.value = 0;
seconds.value = 0;
countDown(options.value.list);
}
});
onMounted(() => {
countDown(options.value.list);
});
onBeforeUnmount(() => {
clearInterval(interval.value);
});
// 倒计时
function countDown(list) {
/**
* 默认倒计时两小时
* 如果没有开始,则显示未开始
* 进行中显示倒计时
* 今天的秒杀结束则显示已结束
*/
let nowHour = new Date().getHours();
if (nowHour < Number(list[0].time)) {
// 活动未开始
currHour.value = list[0].time;
actStatus.value = 0;
} else if (nowHour >= Number(list[list.length - 1].time + 2)) {
// 活动已结束
actStatus.value = 2;
currHour.value = list[list.length - 1].time;
} else {
// 活动进行中
actStatus.value = 1;
for (let i = 0; i < list.length; i++) {
if (nowHour == Number(list[i].time)) {
currHour.value = list[i].time;
}
},
},
mounted() {
this.countDown(this.options.list);
},
beforeUnmount() {
clearInterval(this.interval);
},
methods: {
// 倒计时
countDown(list) {
/**
* 默认倒计时两小时
* 如果没有开始,则显示未开始
* 进行中显示倒计时
* 今天的秒杀结束则显示已结束
*/
let nowHour = new Date().getHours();
if (nowHour < Number(list[0].time)) {
// 活动未开始
this.currHour = list[0].time;
this.actStatus = 0;
} else if (nowHour >= Number(list[list.length - 1].time + 2)) {
// 活动已结束
this.actStatus = 2;
this.currHour = list[list.length - 1].time;
} else {
// 活动进行中
this.actStatus = 1;
for (let i = 0; i < list.length; i++) {
if (nowHour == Number(list[i].time)) {
this.currHour = list[i].time;
}
if (
nowHour > Number(list[i].time) &&
nowHour < Number(list[i].time + 2)
) {
this.currHour = list[i].time;
}
}
// 当前0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
// 活动倒计时
this.diffSeconds = Math.floor(
(zeroTime +
3600 * 1000 * (this.currHour + 2) -
new Date().getTime()) /
1000
);
this.interval = setInterval(() => {
this.diffSeconds--;
}, 1000);
if (
nowHour > Number(list[i].time) &&
nowHour < Number(list[i].time + 2)
) {
currHour.value = list[i].time;
}
},
},
};
}
// 当前0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
// 活动倒计时
diffSeconds.value = Math.floor(
(zeroTime +
3600 * 1000 * (currHour.value + 2) -
new Date().getTime()) /
1000
);
interval.value = setInterval(() => {
diffSeconds.value--;
}, 1000);
}
}
</script>
<style lang="scss" scoped>
.seckill {

Some files were not shown because too many files have changed in this diff Show More