mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-07 17:38:55 +08:00
merge: 合并远端 script setup + pinia 迁移,保留装修编辑器 CRMEB 化功能
This commit is contained in:
@@ -25,6 +25,8 @@ module.exports = {
|
||||
"vue/multiline-html-element-content-newline":"off",
|
||||
"vue/name-property-casing": ["error", "PascalCase"],
|
||||
"vue/no-v-html": "off",
|
||||
// 全端已完成 Options API → <script setup> 迁移,防止新代码回退
|
||||
'vue/component-api-style': ['warn', ['script-setup']],
|
||||
'accessor-pairs': 2,
|
||||
'arrow-spacing': [2, {
|
||||
'before': true,
|
||||
|
||||
@@ -2,11 +2,33 @@
|
||||
|
||||
## 已完成(P1–P3 骨架)
|
||||
|
||||
- 依赖升级:`vue@3`、`vue-router@4`、`vuex@4`、`vue-i18n@9`、`element-plus`
|
||||
- 入口:`src/main.js`(`createApp`)
|
||||
- 依赖升级:`vue@3`、`vue-router@4`、`vue-i18n@9`、`element-plus`、`pinia@4`
|
||||
- 入口:`src/main.js`(`createApp` + `createPinia`)
|
||||
- 路由、动态路由、全局 `$Message` / `$Modal` / `$Notice`、`$filters`、`priceColorScheme`、`vue-qr@5`
|
||||
- TinyMCE:移除已弃用 `template` 插件
|
||||
|
||||
## 组合式 API 迁移(2026-10 完成)
|
||||
|
||||
全部 280 个 `.vue` 组件已从 Options API 转换为 `<script setup>`,并完成 vuex → pinia:
|
||||
|
||||
- **状态管理**:`src/stores/` 下 4 个 store(app/user/setting/dict)。app store 合并了原 vuex 根 state(`openStyleStore/openStoreId/notices`)+ 原 app 模块;mutation 全部转为同名 action,两个例外:`pageOpenedList` mutation 改名 `updateOpenedList`(避免与 state 同名);`clearOtherTags` 改收路由 name 字符串(不再传组件实例 vm)。原 `src/store/` 已删除,package.json 不再依赖 vuex。
|
||||
- **util.js 去 vm 化**:`src/libs/util.js` 的 `openNewPage(name, argu, query)` / `initRouter()` / `initMenuData(data)` 已去掉 vm 首参,内部直接用 `useAppStore()`。
|
||||
- **defineExpose 清单**(被外部通过 ref 访问的组件,改其内部成员时注意保持暴露面):
|
||||
- `components/lili-dialog/index.vue`:`open` / `singleGoods` / `clearGoodsSelected` / `goodsData` / `goodsFlag` / `flag` / `close`
|
||||
- `components/lili-dialog/goods-dialog.vue`:`type`
|
||||
- `components/verify/index.vue`:`show` / `init`
|
||||
- `components/lili/config-drawer.vue`:`open` / `close`
|
||||
- `components/lili/department-tree-choose.vue`:`setData` / `clearSelect`
|
||||
- `components/map/multiple-map.vue`:`open`
|
||||
- `components/editor/index.vue`(Tinymce):`getEditor` / `setContent` / `getContent`
|
||||
- `sys/oss-manage/ossManage.vue`:`selectImage` / `fileType` / `changeFileType`
|
||||
- `components/hotzone/index.vue`:`open` / `close`;`hotzone/components/Hotzone.vue`:`zones` / `addItem` / `eraseItem` / `isOverRange` / `overRange` / `changeItem`(被自定义指令经 `binding.instance` / `$parent` 访问);`hotzone/components/Zone.vue`:`showModalFn` / `delItem` / `handlehideZone` / `changeInfo`
|
||||
- `page-decoration/modelForm.vue`:`navList` / `topAdvert`
|
||||
- `promotions/coupon/coupon.vue`:`check` / `getSelection` / `clearSelectAll`
|
||||
- `live/components/LivePurchaseRecords.vue`:`refresh`
|
||||
- **i18n**:script 内 `this.$t` 统一改为 `i18n.global.t`(`@/locale`),模板 `$t` 不变。
|
||||
- **修复的编译级问题**:`page-decoration/wap/index.vue` 的 `function package()` 改名 `packageModel`(保留字)。
|
||||
|
||||
## P5 批量修复(Table / 过滤器)
|
||||
|
||||
列表页 **60+** 已 `el-table`;过滤器已改为 `$filters.*`;全项目已无 `$set` / `$options.filters`。
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"hls.js": "^1.6.16",
|
||||
"js-cookie": "^2.2.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^4.0.3",
|
||||
"sass": "^1.63.6",
|
||||
"sass-loader": "^10.4.1",
|
||||
"sockjs-client": "^1.4.0",
|
||||
@@ -34,7 +35,6 @@
|
||||
"vue-qr": "^5.0.3",
|
||||
"vue-router": "^4.6.4",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.1.0",
|
||||
"xss": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -4,32 +4,39 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, onUpdated } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import Cookies from "js-cookie";
|
||||
import { getCategoryTree } from "@/api/goods.js";
|
||||
import util from "@/libs/util";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
updated() {
|
||||
if (!localStorage.getItem("category") && this.$route.path !== "/login") {
|
||||
getCategoryTree(0).then((res) => {
|
||||
if (res.success) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const loggedIn = this.getStore("accessToken") || Cookies.get("userInfoManager");
|
||||
if (loggedIn) {
|
||||
util.bootstrapDynamicRoutesFromCache();
|
||||
util.initRouter(this);
|
||||
this.$store.commit("setOpenedList");
|
||||
this.$store.commit("initCachePage");
|
||||
}
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "App" });
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
|
||||
onUpdated(() => {
|
||||
if (!localStorage.getItem("category") && route.path !== "/login") {
|
||||
getCategoryTree(0).then((res) => {
|
||||
if (res.success) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const loggedIn = getStore("accessToken") || Cookies.get("userInfoManager");
|
||||
if (loggedIn) {
|
||||
util.bootstrapDynamicRoutesFromCache();
|
||||
util.initRouter();
|
||||
appStore.setOpenedList();
|
||||
appStore.initCachePage();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -46,120 +46,117 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
|
||||
export default {
|
||||
props: ["closeShop"],
|
||||
data() {
|
||||
return {
|
||||
month: "",
|
||||
selectedWay: {
|
||||
title: "过去7天",
|
||||
selected: true,
|
||||
searchType: "LAST_SEVEN",
|
||||
},
|
||||
storeId: "",
|
||||
dates: [],
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
storeName: "",
|
||||
},
|
||||
dateList: [
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
],
|
||||
originDateList: [
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
],
|
||||
shopTotal: 0,
|
||||
shopsData: [],
|
||||
defineProps(["closeShop"]);
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const month = ref("");
|
||||
const selectedWay = ref({
|
||||
title: "过去7天",
|
||||
selected: true,
|
||||
searchType: "LAST_SEVEN",
|
||||
});
|
||||
const storeId = ref("");
|
||||
const dates = ref([]);
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
storeName: "",
|
||||
});
|
||||
const dateList = ref([
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
]);
|
||||
const originDateList = ref([
|
||||
{ title: "今天", selected: false, searchType: "TODAY" },
|
||||
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
|
||||
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
|
||||
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
|
||||
]);
|
||||
const shopTotal = ref(0);
|
||||
const shopsData = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
getFiveYears();
|
||||
getShopList();
|
||||
});
|
||||
|
||||
function getShopList() {
|
||||
getShopListData(params.value).then((res) => {
|
||||
if (res.success) {
|
||||
shopTotal.value = res.result.total;
|
||||
shopsData.value = res.result.records || [];
|
||||
}
|
||||
});
|
||||
}
|
||||
function changeshop() {
|
||||
selectedWay.value.storeId = storeId.value;
|
||||
emit("selected", selectedWay.value);
|
||||
}
|
||||
function getFiveYears() {
|
||||
const getYear = new Date().getFullYear();
|
||||
const lastFiveYear = getYear - 5;
|
||||
const maxMonth = new Date().getMonth() + 1;
|
||||
const list = [];
|
||||
for (let year = lastFiveYear; year <= getYear; year++) {
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
if (year === getYear && month > maxMonth) {
|
||||
continue;
|
||||
}
|
||||
list.push({ year, month });
|
||||
}
|
||||
}
|
||||
dates.value = list.reverse();
|
||||
}
|
||||
function changeSelect(e) {
|
||||
month.value = e;
|
||||
if (month.value) {
|
||||
dateList.value.forEach((res) => {
|
||||
res.selected = false;
|
||||
});
|
||||
const parts = String(month.value).split("-");
|
||||
selectedWay.value = {
|
||||
...selectedWay.value,
|
||||
year: parts[0],
|
||||
month: parts[1],
|
||||
searchType: "",
|
||||
storeId: storeId.value,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getFiveYears();
|
||||
this.getShopList();
|
||||
},
|
||||
methods: {
|
||||
getShopList() {
|
||||
getShopListData(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
this.shopTotal = res.result.total;
|
||||
this.shopsData = res.result.records || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
changeshop() {
|
||||
this.selectedWay.storeId = this.storeId;
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
getFiveYears() {
|
||||
const getYear = new Date().getFullYear();
|
||||
const lastFiveYear = getYear - 5;
|
||||
const maxMonth = new Date().getMonth() + 1;
|
||||
const dates = [];
|
||||
for (let year = lastFiveYear; year <= getYear; year++) {
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
if (year === getYear && month > maxMonth) {
|
||||
continue;
|
||||
}
|
||||
dates.push({ year, month });
|
||||
}
|
||||
}
|
||||
this.dates = dates.reverse();
|
||||
},
|
||||
changeSelect(e) {
|
||||
this.month = e;
|
||||
if (this.month) {
|
||||
this.dateList.forEach((res) => {
|
||||
res.selected = false;
|
||||
});
|
||||
const parts = String(this.month).split("-");
|
||||
this.selectedWay = {
|
||||
...this.selectedWay,
|
||||
year: parts[0],
|
||||
month: parts[1],
|
||||
searchType: "",
|
||||
storeId: this.storeId,
|
||||
};
|
||||
this.$emit("selected", this.selectedWay);
|
||||
} else {
|
||||
const current =
|
||||
this.dateList.find((item) => item.selected) ||
|
||||
this.dateList.find((item) => item.title === this.selectedWay.title) ||
|
||||
this.dateList.find((item) => item.searchType === "LAST_SEVEN");
|
||||
this.clickBreadcrumb(current);
|
||||
}
|
||||
},
|
||||
clickBreadcrumb(item) {
|
||||
if (!item) return;
|
||||
let currentIndex;
|
||||
this.dateList.forEach((res, index) => {
|
||||
res.selected = false;
|
||||
if (res.title === item.title) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
item.selected = true;
|
||||
item.storeId = this.storeId;
|
||||
this.month = "";
|
||||
if (item.searchType === "") {
|
||||
const origin = this.originDateList[currentIndex];
|
||||
item.searchType = (origin && origin.searchType) || "LAST_SEVEN";
|
||||
}
|
||||
this.selectedWay = item;
|
||||
this.selectedWay.year = new Date().getFullYear();
|
||||
this.selectedWay.month = "";
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
},
|
||||
};
|
||||
emit("selected", selectedWay.value);
|
||||
} else {
|
||||
const current =
|
||||
dateList.value.find((item) => item.selected) ||
|
||||
dateList.value.find((item) => item.title === selectedWay.value.title) ||
|
||||
dateList.value.find((item) => item.searchType === "LAST_SEVEN");
|
||||
clickBreadcrumb(current);
|
||||
}
|
||||
}
|
||||
function clickBreadcrumb(item) {
|
||||
if (!item) return;
|
||||
let currentIndex;
|
||||
dateList.value.forEach((res, index) => {
|
||||
res.selected = false;
|
||||
if (res.title === item.title) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
item.selected = true;
|
||||
item.storeId = storeId.value;
|
||||
month.value = "";
|
||||
if (item.searchType === "") {
|
||||
const origin = originDateList.value[currentIndex];
|
||||
item.searchType = (origin && origin.searchType) || "LAST_SEVEN";
|
||||
}
|
||||
selectedWay.value = item;
|
||||
selectedWay.value.year = new Date().getFullYear();
|
||||
selectedWay.value.month = "";
|
||||
emit("selected", selectedWay.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -63,120 +63,120 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { getDistributionMemberPage } from "@/api/distribution";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "distributionSelector",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
excludeId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
defineOptions({ name: "distributionSelector" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
emits: ["update:modelValue", "confirm"],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
mobile: "",
|
||||
nickname: "",
|
||||
},
|
||||
data: [],
|
||||
total: 0,
|
||||
selectedId: "",
|
||||
selectedRow: null,
|
||||
};
|
||||
excludeId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
if (val) {
|
||||
this.resetState();
|
||||
this.getDataList();
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "confirm"]);
|
||||
|
||||
const loading = ref(false);
|
||||
// searchForm 在 resetState 中被整体替换,用 ref 而非 reactive
|
||||
const searchForm = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
mobile: "",
|
||||
nickname: "",
|
||||
});
|
||||
const data = ref([]);
|
||||
const total = ref(0);
|
||||
const selectedId = ref("");
|
||||
const selectedRow = ref(null);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
resetState();
|
||||
getDataList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function resetState() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
mobile: "",
|
||||
nickname: "",
|
||||
};
|
||||
selectedId.value = "";
|
||||
selectedRow.value = null;
|
||||
}
|
||||
function buildParams() {
|
||||
const params = {
|
||||
pageNumber: searchForm.value.pageNumber,
|
||||
pageSize: searchForm.value.pageSize,
|
||||
distributionStatus: "PASS",
|
||||
};
|
||||
if (searchForm.value.mobile) params.mobile = searchForm.value.mobile;
|
||||
if (searchForm.value.nickname) params.nickname = searchForm.value.nickname;
|
||||
return params;
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
getDistributionMemberPage(buildParams())
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (!res.success) return;
|
||||
const result = res.result || {};
|
||||
let records = result.records || [];
|
||||
const beforeCount = records.length;
|
||||
if (props.excludeId) {
|
||||
records = records.filter((item) => item.id !== props.excludeId);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetState() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
mobile: "",
|
||||
nickname: "",
|
||||
};
|
||||
this.selectedId = "";
|
||||
this.selectedRow = null;
|
||||
},
|
||||
buildParams() {
|
||||
const params = {
|
||||
pageNumber: this.searchForm.pageNumber,
|
||||
pageSize: this.searchForm.pageSize,
|
||||
distributionStatus: "PASS",
|
||||
};
|
||||
if (this.searchForm.mobile) params.mobile = this.searchForm.mobile;
|
||||
if (this.searchForm.nickname) params.nickname = this.searchForm.nickname;
|
||||
return params;
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getDistributionMemberPage(this.buildParams())
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (!res.success) return;
|
||||
const result = res.result || {};
|
||||
let records = result.records || [];
|
||||
const beforeCount = records.length;
|
||||
if (this.excludeId) {
|
||||
records = records.filter((item) => item.id !== this.excludeId);
|
||||
}
|
||||
this.data = records;
|
||||
const excludedCount = beforeCount - records.length;
|
||||
this.total = Math.max(0, (result.total || 0) - excludedCount);
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleRowClick(row) {
|
||||
if (!row) return;
|
||||
this.selectedRow = row;
|
||||
this.selectedId = row.id;
|
||||
},
|
||||
handleCurrentChange(row) {
|
||||
if (!row) return;
|
||||
this.selectedRow = row;
|
||||
this.selectedId = row.id;
|
||||
},
|
||||
handleRadioChange(row) {
|
||||
this.selectedRow = row;
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit("update:modelValue", false);
|
||||
},
|
||||
handleConfirm() {
|
||||
if (!this.selectedRow) {
|
||||
this.$Message.warning("请选择分销员");
|
||||
return;
|
||||
}
|
||||
this.$emit("confirm", this.selectedRow);
|
||||
this.handleClose();
|
||||
},
|
||||
},
|
||||
};
|
||||
data.value = records;
|
||||
const excludedCount = beforeCount - records.length;
|
||||
total.value = Math.max(0, (result.total || 0) - excludedCount);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function handleSearch() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function changePageSize() {
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function handleRowClick(row) {
|
||||
if (!row) return;
|
||||
selectedRow.value = row;
|
||||
selectedId.value = row.id;
|
||||
}
|
||||
function handleCurrentChange(row) {
|
||||
if (!row) return;
|
||||
selectedRow.value = row;
|
||||
selectedId.value = row.id;
|
||||
}
|
||||
function handleRadioChange(row) {
|
||||
selectedRow.value = row;
|
||||
}
|
||||
function handleClose() {
|
||||
emit("update:modelValue", false);
|
||||
}
|
||||
function handleConfirm() {
|
||||
if (!selectedRow.value) {
|
||||
Message.warning("请选择分销员");
|
||||
return;
|
||||
}
|
||||
emit("confirm", selectedRow.value);
|
||||
handleClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -8,132 +8,142 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { initEditor } from "@/components/editor/config";
|
||||
import uploadImage from "@/components/editor/upload-image.vue";
|
||||
export default {
|
||||
components:{uploadImage},
|
||||
name: "Tinymce",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
height:{
|
||||
type:String,
|
||||
default:'500px'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorContent() {
|
||||
return this.modelValue ?? this.value ?? "";
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 引入编辑器的配置
|
||||
initEditor,
|
||||
hasChange: false, // 标记内容是否有更改
|
||||
hasInit: false, // 标记编辑器是否已初始化
|
||||
tinymceId:
|
||||
"tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""), // 生成唯一的编辑器 ID
|
||||
fullscreen: false, // 标记编辑器是否处于全屏模式
|
||||
toolbar: [], // 工具栏配置
|
||||
content: "", // 编辑器内容
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
watch: {
|
||||
editorContent: {
|
||||
handler(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
// 当内容有更改且编辑器已初始化时,更新编辑器的内容
|
||||
this.$nextTick(() => {
|
||||
const editor = window.tinymce?.get(this.tinymceId);
|
||||
if (editor) editor.setContent(val || "");
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitContent(content) {
|
||||
this.$emit("update:modelValue", content);
|
||||
this.$emit("input", content);
|
||||
},
|
||||
// 数据返回并给富文本框插入图片
|
||||
insertImage(arr){
|
||||
arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img src="${v}" >`))
|
||||
},
|
||||
init() {
|
||||
// 初始化编辑器
|
||||
this.initTinymce();
|
||||
},
|
||||
initTinymce() {
|
||||
const _this = this;
|
||||
window.tinymce.init({
|
||||
selector: `#${this.tinymceId}`,
|
||||
convert_urls: false,
|
||||
init_instance_callback: (editor) => {
|
||||
if (_this.editorContent) {
|
||||
// 如果有初始值,则设置编辑器的内容为初始值
|
||||
_this.$nextTick(() => editor.setContent(_this.editorContent));
|
||||
}
|
||||
_this.hasInit = true;
|
||||
// 监听编辑器内容的变化
|
||||
editor.on("NodeChange Change KeyUp SetContent", () => {
|
||||
_this.hasChange = true;
|
||||
// Vue3 v-model 使用 update:modelValue,同时保留 input 兼容旧用法
|
||||
_this.emitContent(editor.getContent());
|
||||
});
|
||||
},
|
||||
setup(editor) {
|
||||
// 监听全屏状态变化
|
||||
editor.on("FullscreenStateChanged", (e) => {
|
||||
_this.fullscreen = e.state;
|
||||
});
|
||||
},
|
||||
..._this.initEditor,
|
||||
|
||||
height:this.height
|
||||
defineOptions({ name: "Tinymce" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "500px",
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input"]);
|
||||
|
||||
// 引入编辑器的配置(data 中的 initEditor 与 import 同名,直接使用 import)
|
||||
const hasChange = ref(false); // 标记内容是否有更改
|
||||
const hasInit = ref(false); // 标记编辑器是否已初始化
|
||||
const tinymceId =
|
||||
"tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""); // 生成唯一的编辑器 ID
|
||||
const fullscreen = ref(false); // 标记编辑器是否处于全屏模式
|
||||
const toolbar = ref([]); // 工具栏配置
|
||||
const content = ref(""); // 编辑器内容
|
||||
|
||||
const editorContent = computed(() => {
|
||||
return props.modelValue ?? props.value ?? "";
|
||||
});
|
||||
|
||||
watch(editorContent, (val) => {
|
||||
if (!hasChange.value && hasInit.value) {
|
||||
// 当内容有更改且编辑器已初始化时,更新编辑器的内容
|
||||
nextTick(() => {
|
||||
const editor = window.tinymce?.get(tinymceId);
|
||||
if (editor) editor.setContent(val || "");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function emitContent(content) {
|
||||
emit("update:modelValue", content);
|
||||
emit("input", content);
|
||||
}
|
||||
// 数据返回并给富文本框插入图片
|
||||
function insertImage(arr) {
|
||||
arr.forEach((v) =>
|
||||
window.tinymce.get(tinymceId).insertContent(`<img src="${v}" >`)
|
||||
);
|
||||
}
|
||||
function init() {
|
||||
// 初始化编辑器
|
||||
initTinymce();
|
||||
}
|
||||
function initTinymce() {
|
||||
window.tinymce.init({
|
||||
selector: `#${tinymceId}`,
|
||||
convert_urls: false,
|
||||
init_instance_callback: (editor) => {
|
||||
if (editorContent.value) {
|
||||
// 如果有初始值,则设置编辑器的内容为初始值
|
||||
nextTick(() => editor.setContent(editorContent.value));
|
||||
}
|
||||
hasInit.value = true;
|
||||
// 监听编辑器内容的变化
|
||||
editor.on("NodeChange Change KeyUp SetContent", () => {
|
||||
hasChange.value = true;
|
||||
// Vue3 v-model 使用 update:modelValue,同时保留 input 兼容旧用法
|
||||
emitContent(editor.getContent());
|
||||
});
|
||||
},
|
||||
setContent(value) {
|
||||
// 设置编辑器的内容
|
||||
window.tinymce.get(this.tinymceId).setContent(value);
|
||||
setup(editor) {
|
||||
// 监听全屏状态变化
|
||||
editor.on("FullscreenStateChanged", (e) => {
|
||||
fullscreen.value = e.state;
|
||||
});
|
||||
},
|
||||
getContent() {
|
||||
// 获取编辑器的内容
|
||||
return window.tinymce.get(this.tinymceId).getContent();
|
||||
},
|
||||
destroyTinymce() {
|
||||
const tinymce = window.tinymce.get(this.tinymceId);
|
||||
...initEditor,
|
||||
|
||||
if (tinymce) {
|
||||
// 销毁编辑器实例
|
||||
tinymce.destroy();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
activated() {
|
||||
if (window.tinymce) {
|
||||
this.initTinymce();
|
||||
}
|
||||
},
|
||||
deactivated() {
|
||||
this.destroyTinymce();
|
||||
},
|
||||
unmounted() {
|
||||
this.destroyTinymce();
|
||||
},
|
||||
};
|
||||
height: props.height,
|
||||
});
|
||||
}
|
||||
function getEditor() {
|
||||
// 获取 tinymce 实例(原组件无此方法,为对齐 seller 版暴露能力而新增)
|
||||
return window.tinymce?.get(tinymceId);
|
||||
}
|
||||
function setContent(value) {
|
||||
// 设置编辑器的内容
|
||||
window.tinymce.get(tinymceId).setContent(value);
|
||||
}
|
||||
function getContent() {
|
||||
// 获取编辑器的内容
|
||||
return window.tinymce.get(tinymceId).getContent();
|
||||
}
|
||||
function destroyTinymce() {
|
||||
const tinymce = window.tinymce.get(tinymceId);
|
||||
|
||||
if (tinymce) {
|
||||
// 销毁编辑器实例
|
||||
tinymce.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// created 钩子:顶层直接执行(保留原 created + mounted 双重 init 的历史行为)
|
||||
init();
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
onActivated(() => {
|
||||
if (window.tinymce) {
|
||||
initTinymce();
|
||||
}
|
||||
});
|
||||
onDeactivated(() => {
|
||||
destroyTinymce();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
destroyTinymce();
|
||||
});
|
||||
|
||||
// 父组件通过 ref 调用:articleList 使用 getContent;setContent/getEditor 对齐 seller 版暴露能力
|
||||
defineExpose({ getEditor, setContent, getContent });
|
||||
</script>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</vuedraggable>
|
||||
<div class="upload-box">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
ref="uploadRef"
|
||||
:action="uploadFileUrl"
|
||||
:headers="accessToken"
|
||||
:show-file-list="false"
|
||||
@@ -64,7 +64,7 @@
|
||||
@closed="resetOssSelection"
|
||||
>
|
||||
<OssManage
|
||||
ref="ossManage"
|
||||
ref="ossManageRef"
|
||||
:is-component="true"
|
||||
:initialize="showOssManager"
|
||||
:hide-select-footer="true"
|
||||
@@ -84,123 +84,120 @@
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { Delete, Plus, ZoomIn } from "@element-plus/icons-vue";
|
||||
import vuedraggable from "vuedraggable";
|
||||
import { uploadFile } from "@/libs/axios";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { Message } from "@/utils/message";
|
||||
import OssManage from "@/views/sys/oss-manage/ossManage.vue";
|
||||
|
||||
export default {
|
||||
name: "upload-image",
|
||||
components: {
|
||||
OssManage,
|
||||
vuedraggable,
|
||||
Delete,
|
||||
Plus,
|
||||
ZoomIn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
uploadFileUrl: uploadFile,
|
||||
accessToken: {},
|
||||
showOssManager: false,
|
||||
images: [],
|
||||
selectedImage: [],
|
||||
viewImage: false,
|
||||
previewUrl: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
draggableItemKey(item) {
|
||||
return item?.url || item;
|
||||
},
|
||||
handleClickUploadImage() {
|
||||
this.show = true;
|
||||
},
|
||||
handleView(url) {
|
||||
this.previewUrl = url;
|
||||
this.viewImage = true;
|
||||
},
|
||||
callback() {
|
||||
const formatImages = this.images.map((item) => item.url);
|
||||
this.$emit("callback", formatImages);
|
||||
this.show = false;
|
||||
},
|
||||
handleRemoveGoodsPicture(__index) {
|
||||
this.images.splice(__index, 1);
|
||||
},
|
||||
handleBeforeUpload(file) {
|
||||
const okType = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
|
||||
if (!okType) {
|
||||
this.$Message.warning("文件 " + file.name + " 的格式不正确,请选择 jpg/jpeg/png");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
this.$Message.warning("图片大小不能超过10MB");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSuccessGoodsPicture(res) {
|
||||
const url = res?.result ?? res?.data?.result;
|
||||
if (url) {
|
||||
this.images.push({ url });
|
||||
} else {
|
||||
this.$Message.error(res?.message || "上传失败");
|
||||
}
|
||||
},
|
||||
handleUploadError(err) {
|
||||
this.$Message.error(err?.message || String(err));
|
||||
},
|
||||
confirmUrls() {
|
||||
this.applySelectedImages();
|
||||
this.showOssManager = false;
|
||||
},
|
||||
cancelOssImport() {
|
||||
this.selectedImage = [];
|
||||
this.showOssManager = false;
|
||||
},
|
||||
resetOssSelection() {
|
||||
this.selectedImage = [];
|
||||
},
|
||||
handleOssSelected(list) {
|
||||
this.selectedImage = Array.isArray(list) ? list : [];
|
||||
},
|
||||
parseOssSelectionUrl(item) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
if (typeof item === "string") {
|
||||
const index = item.indexOf(",");
|
||||
return index >= 0 ? item.slice(index + 1) : item;
|
||||
}
|
||||
return item.url || "";
|
||||
},
|
||||
applySelectedImages() {
|
||||
(this.selectedImage || []).forEach((item) => {
|
||||
const url = this.parseOssSelectionUrl(item);
|
||||
if (url) {
|
||||
this.images.push({ url });
|
||||
}
|
||||
});
|
||||
},
|
||||
importOSS() {
|
||||
this.selectedImage = [];
|
||||
this.showOssManager = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ossManage) {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "upload-image" });
|
||||
|
||||
const emit = defineEmits(["callback"]);
|
||||
|
||||
const show = ref(false);
|
||||
const uploadFileUrl = uploadFile;
|
||||
const accessToken = ref({});
|
||||
const showOssManager = ref(false);
|
||||
const images = ref([]);
|
||||
const selectedImage = ref([]);
|
||||
const viewImage = ref(false);
|
||||
const previewUrl = ref("");
|
||||
|
||||
// 模板 ref:upload/ossManage 原名与顶层变量无冲突,但统一改名避免与后续变量互相覆盖
|
||||
const uploadRef = ref(null);
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
accessToken.value = {
|
||||
accessToken: getStore("accessToken"),
|
||||
};
|
||||
});
|
||||
|
||||
function draggableItemKey(item) {
|
||||
return item?.url || item;
|
||||
}
|
||||
function handleClickUploadImage() {
|
||||
show.value = true;
|
||||
}
|
||||
function handleView(url) {
|
||||
previewUrl.value = url;
|
||||
viewImage.value = true;
|
||||
}
|
||||
function callback() {
|
||||
const formatImages = images.value.map((item) => item.url);
|
||||
emit("callback", formatImages);
|
||||
show.value = false;
|
||||
}
|
||||
function handleRemoveGoodsPicture(__index) {
|
||||
images.value.splice(__index, 1);
|
||||
}
|
||||
function handleBeforeUpload(file) {
|
||||
const okType = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
|
||||
if (!okType) {
|
||||
Message.warning("文件 " + file.name + " 的格式不正确,请选择 jpg/jpeg/png");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
Message.warning("图片大小不能超过10MB");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleSuccessGoodsPicture(res) {
|
||||
const url = res?.result ?? res?.data?.result;
|
||||
if (url) {
|
||||
images.value.push({ url });
|
||||
} else {
|
||||
Message.error(res?.message || "上传失败");
|
||||
}
|
||||
}
|
||||
function handleUploadError(err) {
|
||||
Message.error(err?.message || String(err));
|
||||
}
|
||||
function confirmUrls() {
|
||||
applySelectedImages();
|
||||
showOssManager.value = false;
|
||||
}
|
||||
function cancelOssImport() {
|
||||
selectedImage.value = [];
|
||||
showOssManager.value = false;
|
||||
}
|
||||
function resetOssSelection() {
|
||||
selectedImage.value = [];
|
||||
}
|
||||
function handleOssSelected(list) {
|
||||
selectedImage.value = Array.isArray(list) ? list : [];
|
||||
}
|
||||
function parseOssSelectionUrl(item) {
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
if (typeof item === "string") {
|
||||
const index = item.indexOf(",");
|
||||
return index >= 0 ? item.slice(index + 1) : item;
|
||||
}
|
||||
return item.url || "";
|
||||
}
|
||||
function applySelectedImages() {
|
||||
(selectedImage.value || []).forEach((item) => {
|
||||
const url = parseOssSelectionUrl(item);
|
||||
if (url) {
|
||||
images.value.push({ url });
|
||||
}
|
||||
});
|
||||
}
|
||||
function importOSS() {
|
||||
selectedImage.value = [];
|
||||
showOssManager.value = true;
|
||||
nextTick(() => {
|
||||
if (ossManageRef.value) {
|
||||
ossManageRef.value.selectImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:key="index"
|
||||
:index="index"
|
||||
:setting="zone"
|
||||
:ref="`zone${index}`"
|
||||
:ref="(el) => setZoneRef(el, index)"
|
||||
@delItem="removeItem($event)"
|
||||
@changeInfo="changeInfo($event)"
|
||||
></zone>
|
||||
@@ -62,160 +62,188 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance } from "vue";
|
||||
import Zone from "./Zone";
|
||||
import addItem from "../directives/addItem";
|
||||
import vAddItem from "../directives/addItem";
|
||||
|
||||
export default {
|
||||
name: "HotZone",
|
||||
data() {
|
||||
return {
|
||||
zones: [],
|
||||
};
|
||||
defineOptions({ name: "HotZone" });
|
||||
|
||||
const props = defineProps({
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
zonesInit: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
},
|
||||
zonesInit: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
mounted() {
|
||||
this.zones = this.zonesInit.concat();
|
||||
max: {
|
||||
type: Number,
|
||||
},
|
||||
methods: {
|
||||
async addHotzone() {
|
||||
let perInfo = {
|
||||
topPer: 0.15,
|
||||
leftPer: 0.3,
|
||||
widthPer: 0.2,
|
||||
heightPer: 0.2,
|
||||
img: "",
|
||||
link: "",
|
||||
type: "",
|
||||
title: "",
|
||||
};
|
||||
let images = await this.getImageSize(this.image);
|
||||
if (images) {
|
||||
if (images.height >= 1000) {
|
||||
perInfo.heightPer = this.convertNumberToDecimal(images.height) / (images.height / 1000);
|
||||
} else {
|
||||
perInfo.heightPer = this.convertNumberToDecimal(images.height);
|
||||
}
|
||||
perInfo.widthPer = this.convertNumberToDecimal(images.width) / 2;
|
||||
}
|
||||
this.addItem(perInfo);
|
||||
},
|
||||
convertNumberToDecimal(num) {
|
||||
if (num >= 10000) {
|
||||
return num / 100000;
|
||||
} else if (num >= 1000) {
|
||||
return num / 10000;
|
||||
} else if (num >= 100) {
|
||||
return num / 1000;
|
||||
} else if (num >= 10) {
|
||||
return num / 100;
|
||||
}
|
||||
},
|
||||
getImageSize(url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let image = new Image();
|
||||
image.onload = function () {
|
||||
resolve({
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
});
|
||||
};
|
||||
image.onerror = function () {
|
||||
reject(new Error("error"));
|
||||
};
|
||||
image.src = url;
|
||||
});
|
||||
const emit = defineEmits([
|
||||
"choose",
|
||||
"erase",
|
||||
"overRange",
|
||||
"remove",
|
||||
"add",
|
||||
"change",
|
||||
]);
|
||||
|
||||
const zones = ref([]);
|
||||
const zoneRefs = ref([]);
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
onMounted(() => {
|
||||
zones.value = props.zonesInit.concat();
|
||||
});
|
||||
|
||||
// v-for 内的动态 ref:按 index 收集 Zone 组件实例
|
||||
function setZoneRef(el, index) {
|
||||
if (el) zoneRefs.value[index] = el;
|
||||
}
|
||||
|
||||
async function addHotzone() {
|
||||
let perInfo = {
|
||||
topPer: 0.15,
|
||||
leftPer: 0.3,
|
||||
widthPer: 0.2,
|
||||
heightPer: 0.2,
|
||||
img: "",
|
||||
link: "",
|
||||
type: "",
|
||||
title: "",
|
||||
};
|
||||
let images = await getImageSize(props.image);
|
||||
if (images) {
|
||||
if (images.height >= 1000) {
|
||||
perInfo.heightPer = convertNumberToDecimal(images.height) / (images.height / 1000);
|
||||
} else {
|
||||
perInfo.heightPer = convertNumberToDecimal(images.height);
|
||||
}
|
||||
perInfo.widthPer = convertNumberToDecimal(images.width) / 2;
|
||||
}
|
||||
addItem(perInfo);
|
||||
}
|
||||
|
||||
function convertNumberToDecimal(num) {
|
||||
if (num >= 10000) {
|
||||
return num / 100000;
|
||||
} else if (num >= 1000) {
|
||||
return num / 10000;
|
||||
} else if (num >= 100) {
|
||||
return num / 1000;
|
||||
} else if (num >= 10) {
|
||||
return num / 100;
|
||||
}
|
||||
}
|
||||
|
||||
function getImageSize(url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let image = new Image();
|
||||
image.onload = function () {
|
||||
resolve({
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
});
|
||||
},
|
||||
editZone(index) {
|
||||
this.$refs[`zone${index}`][0].showModalFn(index);
|
||||
},
|
||||
delZone(index) {
|
||||
this.$refs[`zone${index}`][0].delItem(index);
|
||||
},
|
||||
showZoneText(zone) {
|
||||
switch (zone.type) {
|
||||
case "goods":
|
||||
return `商品:${zone.goodsName}`;
|
||||
case "category":
|
||||
return `分类:${zone.name}`;
|
||||
case "shops":
|
||||
return `店铺:${zone.storeName}`;
|
||||
case "pages":
|
||||
return `文章:${zone.title}`;
|
||||
case "marketing":
|
||||
return `促销活动商品:${zone.goodsName}`;
|
||||
case "other":
|
||||
return `${zone.title}`;
|
||||
case "special":
|
||||
return `专题:${zone.title}`;
|
||||
default:
|
||||
return "请选择跳转链接";
|
||||
}
|
||||
},
|
||||
changeInfo(res) {
|
||||
let { info, index, zoneInfo } = res;
|
||||
info = { ...zoneInfo, ...info };
|
||||
// 改变热区并发送change通知
|
||||
Object.assign(this.zones[index], info);
|
||||
this.hasChange("changeInfo");
|
||||
this.$forceUpdate();
|
||||
},
|
||||
addItem(setting) {
|
||||
this.zones.push(setting);
|
||||
this.$emit("choose");
|
||||
// this.hasChange() 不应该发送通知,mouseup判定成功才应该发
|
||||
// this.$emit('add', setting)
|
||||
},
|
||||
eraseItem(index = this.zones.length - 1) {
|
||||
this.zones.splice(index, 1);
|
||||
this.$emit("erase", index);
|
||||
},
|
||||
isOverRange() {
|
||||
let { max, zones } = this;
|
||||
};
|
||||
image.onerror = function () {
|
||||
reject(new Error("error"));
|
||||
};
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
return max && zones.length > max;
|
||||
},
|
||||
overRange() {
|
||||
const index = this.zones.length - 1;
|
||||
function editZone(index) {
|
||||
zoneRefs.value[index].showModalFn(index);
|
||||
}
|
||||
|
||||
this.zones.splice(index, 1);
|
||||
this.$emit("overRange", index);
|
||||
},
|
||||
removeItem(index = this.zones.length - 1) {
|
||||
this.zones.splice(index, 1);
|
||||
this.hasChange("removeItem");
|
||||
this.$emit("remove", index);
|
||||
},
|
||||
changeItem(info, isAdd) {
|
||||
const index = this.zones.length - 1;
|
||||
// 改变热区并发送change通知
|
||||
Object.assign(this.zones[index], info);
|
||||
this.hasChange("changeItem");
|
||||
isAdd && this.$emit("add", this.zones[index]);
|
||||
},
|
||||
hasChange(from) {
|
||||
this.$emit("change", this.zones);
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
addItem,
|
||||
},
|
||||
components: {
|
||||
Zone,
|
||||
},
|
||||
};
|
||||
function delZone(index) {
|
||||
zoneRefs.value[index].delItem(index);
|
||||
}
|
||||
|
||||
function showZoneText(zone) {
|
||||
switch (zone.type) {
|
||||
case "goods":
|
||||
return `商品:${zone.goodsName}`;
|
||||
case "category":
|
||||
return `分类:${zone.name}`;
|
||||
case "shops":
|
||||
return `店铺:${zone.storeName}`;
|
||||
case "pages":
|
||||
return `文章:${zone.title}`;
|
||||
case "marketing":
|
||||
return `促销活动商品:${zone.goodsName}`;
|
||||
case "other":
|
||||
return `${zone.title}`;
|
||||
case "special":
|
||||
return `专题:${zone.title}`;
|
||||
default:
|
||||
return "请选择跳转链接";
|
||||
}
|
||||
}
|
||||
|
||||
function changeInfo(res) {
|
||||
let { info, index, zoneInfo } = res;
|
||||
info = { ...zoneInfo, ...info };
|
||||
// 改变热区并发送change通知
|
||||
Object.assign(zones.value[index], info);
|
||||
hasChange("changeInfo");
|
||||
instance.proxy.$forceUpdate();
|
||||
}
|
||||
|
||||
function addItem(setting) {
|
||||
zones.value.push(setting);
|
||||
emit("choose");
|
||||
// this.hasChange() 不应该发送通知,mouseup判定成功才应该发
|
||||
// this.$emit('add', setting)
|
||||
}
|
||||
|
||||
function eraseItem(index = zones.value.length - 1) {
|
||||
zones.value.splice(index, 1);
|
||||
emit("erase", index);
|
||||
}
|
||||
|
||||
function isOverRange() {
|
||||
return props.max && zones.value.length > props.max;
|
||||
}
|
||||
|
||||
function overRange() {
|
||||
const index = zones.value.length - 1;
|
||||
|
||||
zones.value.splice(index, 1);
|
||||
emit("overRange", index);
|
||||
}
|
||||
|
||||
function removeItem(index = zones.value.length - 1) {
|
||||
zones.value.splice(index, 1);
|
||||
hasChange("removeItem");
|
||||
emit("remove", index);
|
||||
}
|
||||
|
||||
function changeItem(info, isAdd) {
|
||||
const index = zones.value.length - 1;
|
||||
// 改变热区并发送change通知
|
||||
Object.assign(zones.value[index], info);
|
||||
hasChange("changeItem");
|
||||
isAdd && emit("add", zones.value[index]);
|
||||
}
|
||||
|
||||
function hasChange(from) {
|
||||
emit("change", zones.value);
|
||||
}
|
||||
|
||||
// v-add-item 指令通过 binding.instance 调用以下方法/读取 zones;
|
||||
// Zone 的 v-drag-item / v-change-size 指令通过 ctx.$parent.zones 读取 zones
|
||||
defineExpose({
|
||||
zones,
|
||||
addItem,
|
||||
eraseItem,
|
||||
isOverRange,
|
||||
overRange,
|
||||
changeItem,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -86,171 +86,177 @@
|
||||
@callback="callbackSelected"
|
||||
:isComponent="true"
|
||||
:initialize="picModelFlag"
|
||||
ref="ossManage"
|
||||
ref="ossManageRef"
|
||||
/>
|
||||
<template #footer />
|
||||
</el-dialog>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import changeSize from "../directives/changeSize";
|
||||
import dragItem from "../directives/dragItem";
|
||||
<script setup>
|
||||
import { ref, reactive, watch, nextTick, onMounted } from "vue";
|
||||
import vChangeSize from "../directives/changeSize";
|
||||
import vDragItem from "../directives/dragItem";
|
||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
||||
import { formatLinkType } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "Zone",
|
||||
components: {
|
||||
ossManage,
|
||||
},
|
||||
defineOptions({ name: "Zone" });
|
||||
|
||||
data() {
|
||||
return {
|
||||
zoneTop: "",
|
||||
zoneLeft: "",
|
||||
zoneWidth: "",
|
||||
zoneHeight: "",
|
||||
hideZone: false,
|
||||
tooSmall: false,
|
||||
showModal: false,
|
||||
picModelFlag: false,
|
||||
currentIndex: 0,
|
||||
currentShowIndex: -1,
|
||||
zoneForm: {
|
||||
img: "",
|
||||
link: "",
|
||||
type: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
props: ["index", "setting"],
|
||||
mounted() {
|
||||
this.setZoneInfo(this.setting);
|
||||
},
|
||||
methods: {
|
||||
setZoneInfo(val) {
|
||||
this.zoneTop = this.getZoneStyle(val.topPer);
|
||||
this.zoneLeft = this.getZoneStyle(val.leftPer);
|
||||
this.zoneWidth = this.getZoneStyle(val.widthPer);
|
||||
this.zoneHeight = this.getZoneStyle(val.heightPer);
|
||||
this.tooSmall = val.widthPer < 0.01 && val.heightPer < 0.01;
|
||||
this.zoneForm.link = val.link;
|
||||
this.settingZone(val);
|
||||
},
|
||||
handlehideZone(isHide = true) {
|
||||
if (this.hideZone === isHide) {
|
||||
return;
|
||||
}
|
||||
const props = defineProps(["index", "setting"]);
|
||||
const emit = defineEmits(["changeInfo", "delItem"]);
|
||||
|
||||
this.hideZone = isHide;
|
||||
},
|
||||
changeInfo(info = {}) {
|
||||
const { index } = this;
|
||||
const zoneTop = ref("");
|
||||
const zoneLeft = ref("");
|
||||
const zoneWidth = ref("");
|
||||
const zoneHeight = ref("");
|
||||
const hideZone = ref(false);
|
||||
const tooSmall = ref(false);
|
||||
const showModal = ref(false);
|
||||
const picModelFlag = ref(false);
|
||||
const currentIndex = ref(0);
|
||||
const currentShowIndex = ref(-1);
|
||||
const zoneForm = reactive({
|
||||
img: "",
|
||||
link: "",
|
||||
type: "",
|
||||
});
|
||||
|
||||
this.$emit("changeInfo", {
|
||||
info,
|
||||
index,
|
||||
zoneInfo: this.zoneForm,
|
||||
});
|
||||
},
|
||||
showModalFn(index) {
|
||||
this.showModal = true;
|
||||
this.currentIndex = index;
|
||||
},
|
||||
// 选择图片
|
||||
handleSelectImg() {
|
||||
this.picModelFlag = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ossManage) {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// 选择图片回调
|
||||
callbackSelected(item) {
|
||||
this.picModelFlag = false;
|
||||
this.zoneForm.img = item.url;
|
||||
},
|
||||
// 未在原 data 中声明、直接挂载在 this 上的遗留字段(仅赋值,未被读取)
|
||||
let selectedNav;
|
||||
|
||||
// 调起选择链接弹窗
|
||||
handleSelectLink(item, index) {
|
||||
if (item) this.selectedNav = item;
|
||||
this.$refs.liliDialog.open("link");
|
||||
},
|
||||
// 已选链接
|
||||
selectedLink(val) {
|
||||
this.zoneForm.link = this.$filters.formatLinkType(val);
|
||||
this.settingZone(val);
|
||||
this.changeInfo(this.zoneForm);
|
||||
},
|
||||
settingZone(val) {
|
||||
this.zoneForm.type = val.___type || val.type;
|
||||
this.zoneForm.title = val.title;
|
||||
if(val.pageType == "special"){
|
||||
this.zoneForm.id = val.id;
|
||||
this.zoneForm.pageType = val.pageType
|
||||
this.zoneForm.type = val.pageType
|
||||
this.zoneForm.title = val.name
|
||||
}
|
||||
switch (val.___type) {
|
||||
case "goods":
|
||||
this.zoneForm.id = val.id;
|
||||
this.zoneForm.goodsId = val.goodsId;
|
||||
this.zoneForm.goodsName = val.goodsName;
|
||||
break;
|
||||
case "category":
|
||||
this.zoneForm.id = val.allId;
|
||||
this.zoneForm.name = val.name;
|
||||
break;
|
||||
case "shops":
|
||||
this.zoneForm.id = val.id;
|
||||
this.zoneForm.storeName = val.storeName;
|
||||
break;
|
||||
case "pages":
|
||||
this.zoneForm.id = val.id;
|
||||
this.zoneForm.___path = val.___path;
|
||||
this.zoneForm.title = val.title;
|
||||
break;
|
||||
case "marketing":
|
||||
this.zoneForm.id = val.id;
|
||||
this.zoneForm.goodsId = val.goodsId;
|
||||
this.zoneForm.goodsName = val.goodsName;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
saveZone() {
|
||||
this.showModal = false;
|
||||
this.changeInfo(this.zoneForm);
|
||||
},
|
||||
cancelZone() {
|
||||
this.showModal = false;
|
||||
},
|
||||
delZone() {
|
||||
this.delItem(this.currentIndex);
|
||||
},
|
||||
delItem(index) {
|
||||
this.$emit("delItem", index);
|
||||
},
|
||||
getZoneStyle(val) {
|
||||
return `${(val || 0) * 100}%`;
|
||||
},
|
||||
},
|
||||
const liliDialog = ref(null);
|
||||
// 模板 ref 与 import 组件同名会互相覆盖,故改名 ossManageRef
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
watch: {
|
||||
setting: {
|
||||
handler: function (val) {
|
||||
this.setZoneInfo(val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
onMounted(() => {
|
||||
setZoneInfo(props.setting);
|
||||
});
|
||||
|
||||
function setZoneInfo(val) {
|
||||
zoneTop.value = getZoneStyle(val.topPer);
|
||||
zoneLeft.value = getZoneStyle(val.leftPer);
|
||||
zoneWidth.value = getZoneStyle(val.widthPer);
|
||||
zoneHeight.value = getZoneStyle(val.heightPer);
|
||||
tooSmall.value = val.widthPer < 0.01 && val.heightPer < 0.01;
|
||||
zoneForm.link = val.link;
|
||||
settingZone(val);
|
||||
}
|
||||
function handlehideZone(isHide = true) {
|
||||
if (hideZone.value === isHide) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideZone.value = isHide;
|
||||
}
|
||||
function changeInfo(info = {}) {
|
||||
const index = props.index;
|
||||
|
||||
emit("changeInfo", {
|
||||
info,
|
||||
index,
|
||||
zoneInfo: zoneForm,
|
||||
});
|
||||
}
|
||||
function showModalFn(index) {
|
||||
showModal.value = true;
|
||||
currentIndex.value = index;
|
||||
}
|
||||
// 选择图片
|
||||
function handleSelectImg() {
|
||||
picModelFlag.value = true;
|
||||
nextTick(() => {
|
||||
if (ossManageRef.value) {
|
||||
ossManageRef.value.selectImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 选择图片回调
|
||||
function callbackSelected(item) {
|
||||
picModelFlag.value = false;
|
||||
zoneForm.img = item.url;
|
||||
}
|
||||
|
||||
// 调起选择链接弹窗
|
||||
function handleSelectLink(item, index) {
|
||||
if (item) selectedNav = item;
|
||||
liliDialog.value.open("link");
|
||||
}
|
||||
// 已选链接
|
||||
function selectedLink(val) {
|
||||
zoneForm.link = formatLinkType(val);
|
||||
settingZone(val);
|
||||
changeInfo(zoneForm);
|
||||
}
|
||||
function settingZone(val) {
|
||||
zoneForm.type = val.___type || val.type;
|
||||
zoneForm.title = val.title;
|
||||
if(val.pageType == "special"){
|
||||
zoneForm.id = val.id;
|
||||
zoneForm.pageType = val.pageType
|
||||
zoneForm.type = val.pageType
|
||||
zoneForm.title = val.name
|
||||
}
|
||||
switch (val.___type) {
|
||||
case "goods":
|
||||
zoneForm.id = val.id;
|
||||
zoneForm.goodsId = val.goodsId;
|
||||
zoneForm.goodsName = val.goodsName;
|
||||
break;
|
||||
case "category":
|
||||
zoneForm.id = val.allId;
|
||||
zoneForm.name = val.name;
|
||||
break;
|
||||
case "shops":
|
||||
zoneForm.id = val.id;
|
||||
zoneForm.storeName = val.storeName;
|
||||
break;
|
||||
case "pages":
|
||||
zoneForm.id = val.id;
|
||||
zoneForm.___path = val.___path;
|
||||
zoneForm.title = val.title;
|
||||
break;
|
||||
case "marketing":
|
||||
zoneForm.id = val.id;
|
||||
zoneForm.goodsId = val.goodsId;
|
||||
zoneForm.goodsName = val.goodsName;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
function saveZone() {
|
||||
showModal.value = false;
|
||||
changeInfo(zoneForm);
|
||||
}
|
||||
function cancelZone() {
|
||||
showModal.value = false;
|
||||
}
|
||||
function delZone() {
|
||||
delItem(currentIndex.value);
|
||||
}
|
||||
function delItem(index) {
|
||||
emit("delItem", index);
|
||||
}
|
||||
function getZoneStyle(val) {
|
||||
return `${(val || 0) * 100}%`;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.setting,
|
||||
(val) => {
|
||||
setZoneInfo(val);
|
||||
},
|
||||
directives: {
|
||||
changeSize,
|
||||
dragItem,
|
||||
},
|
||||
};
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 外部访问:父组件 Hotzone.vue 通过 $refs 调用 showModalFn/delItem;
|
||||
// 自定义指令 changeSize/dragItem 通过 binding.instance 调用 handlehideZone/changeInfo
|
||||
defineExpose({
|
||||
showModalFn,
|
||||
delItem,
|
||||
handlehideZone,
|
||||
changeInfo,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
>
|
||||
<template v-if="flag && hotzoneRes">
|
||||
<hotzone
|
||||
ref="hotzone"
|
||||
ref="hotzoneRef"
|
||||
@change="changeHotzone"
|
||||
:zonesInit="hotzoneRes.zoneInfo"
|
||||
:image="hotzoneRes.img"
|
||||
@@ -22,47 +22,50 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import hotzone from "./components/Hotzone.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
hotzone,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
flag: false,
|
||||
hotzoneRes: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeHotzone(info) {
|
||||
if (this.hotzoneRes) {
|
||||
this.hotzoneRes.zoneInfo = info;
|
||||
}
|
||||
this.$emit("changeZone", info);
|
||||
},
|
||||
clickClose() {
|
||||
this.flag = false;
|
||||
this.hotzoneRes = null;
|
||||
this.$emit("closeFlag", false);
|
||||
},
|
||||
clickOK() {
|
||||
this.clickClose();
|
||||
},
|
||||
open(val) {
|
||||
this.hotzoneRes = val || null;
|
||||
if (this.hotzoneRes && !this.hotzoneRes.zoneInfo) {
|
||||
this.hotzoneRes.zoneInfo = [];
|
||||
}
|
||||
this.flag = true;
|
||||
},
|
||||
close() {
|
||||
this.flag = false;
|
||||
this.hotzoneRes = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
const emit = defineEmits(["changeZone", "closeFlag"]);
|
||||
|
||||
const flag = ref(false);
|
||||
const hotzoneRes = ref(null);
|
||||
|
||||
// hotzoneRef:模板 ref 与组件 import 同名,改名避免互相覆盖
|
||||
const hotzoneRef = ref(null);
|
||||
|
||||
function changeHotzone(info) {
|
||||
if (hotzoneRes.value) {
|
||||
hotzoneRes.value.zoneInfo = info;
|
||||
}
|
||||
emit("changeZone", info);
|
||||
}
|
||||
|
||||
function clickClose() {
|
||||
flag.value = false;
|
||||
hotzoneRes.value = null;
|
||||
emit("closeFlag", false);
|
||||
}
|
||||
|
||||
function clickOK() {
|
||||
clickClose();
|
||||
}
|
||||
|
||||
function open(val) {
|
||||
hotzoneRes.value = val || null;
|
||||
if (hotzoneRes.value && !hotzoneRes.value.zoneInfo) {
|
||||
hotzoneRes.value.zoneInfo = [];
|
||||
}
|
||||
flag.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
flag.value = false;
|
||||
hotzoneRes.value = null;
|
||||
}
|
||||
|
||||
// decorate.vue 通过 hotzoneRef.value.open(val) 调用
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-dialog) {
|
||||
|
||||
@@ -84,165 +84,165 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import * as API_Goods from "@/api/goods";
|
||||
import { goodsTypeLabel, goodsTypeTagType } from "@/constants/goodsType";
|
||||
export default {
|
||||
props: {
|
||||
selectedWay: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: "multiple",
|
||||
selectedList: [],
|
||||
skuList: [],
|
||||
total: 0,
|
||||
goodsParams: {
|
||||
pageNumber: 1,
|
||||
pageSize: 15,
|
||||
order: "desc",
|
||||
goodsName: "",
|
||||
sn: "",
|
||||
categoryPath: "",
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
sort: "createTime",
|
||||
},
|
||||
category: [],
|
||||
goodsData: [],
|
||||
empty: false,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
category(val) {
|
||||
this.goodsParams.categoryPath = val && val.length ? val[2] : "";
|
||||
},
|
||||
selectedWay: {
|
||||
handler(val) {
|
||||
this.selectedList = Array.isArray(val) ? val.slice() : [];
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
"goodsParams.categoryPath": {
|
||||
handler() {
|
||||
this.goodsData = [];
|
||||
this.goodsParams.pageNumber = 1;
|
||||
this.getQueryGoodsList();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
goodsTypeLabel,
|
||||
goodsTypeTagType,
|
||||
onSearchGoods() {
|
||||
this.goodsData = [];
|
||||
this.goodsParams.pageNumber = 1;
|
||||
this.getQueryGoodsList();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.goodsParams.pageNumber = v;
|
||||
this.getQueryGoodsList();
|
||||
},
|
||||
getQueryGoodsList() {
|
||||
this.loading = true;
|
||||
API_Goods.getGoodsSkuData(this.goodsParams)
|
||||
.then((res) => {
|
||||
this.initGoods(res);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
initGoods(res) {
|
||||
const records = res?.result?.records || [];
|
||||
if (records.length) {
|
||||
records.forEach((item) => {
|
||||
item.selected = false;
|
||||
item.___type = "goods";
|
||||
this.selectedList.forEach((e) => {
|
||||
if (e.id && e.id === item.id) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.total = res.result.total || 0;
|
||||
this.goodsData = records;
|
||||
this.empty = false;
|
||||
} else {
|
||||
this.goodsData = [];
|
||||
this.empty = true;
|
||||
}
|
||||
},
|
||||
emitSelected(list) {
|
||||
this.selectedList = list;
|
||||
this.$emit("selected", this.selectedList);
|
||||
},
|
||||
init() {
|
||||
API_Goods.getGoodsSkuData(this.goodsParams).then((res) => {
|
||||
this.initGoods(res);
|
||||
});
|
||||
API_Goods.getCategoryTree({ deleteFlag: false }).then((res) => {
|
||||
if (res.success) {
|
||||
this.deepGroup(res.result);
|
||||
}
|
||||
});
|
||||
},
|
||||
deepGroup(val) {
|
||||
val.forEach((item) => {
|
||||
let childWay = [];
|
||||
if (item.children) {
|
||||
item.children.forEach((child) => {
|
||||
if (child.children) {
|
||||
child.children.forEach((grandson, index, arr) => {
|
||||
arr[index] = {
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
childWay.push({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: child.children,
|
||||
});
|
||||
});
|
||||
}
|
||||
this.skuList.push({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: childWay,
|
||||
});
|
||||
});
|
||||
},
|
||||
checkedGoods(val) {
|
||||
if (this.type !== "multiple") {
|
||||
this.goodsData.forEach((item) => {
|
||||
item.selected = false;
|
||||
});
|
||||
val.selected = true;
|
||||
this.emitSelected([val]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!val.selected) {
|
||||
val.selected = true;
|
||||
this.emitSelected([...this.selectedList, val]);
|
||||
} else {
|
||||
val.selected = false;
|
||||
this.emitSelected(this.selectedList.filter((item) => item.id !== val.id));
|
||||
}
|
||||
},
|
||||
const props = defineProps({
|
||||
selectedWay: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const type = ref("multiple");
|
||||
const selectedList = ref([]);
|
||||
const skuList = ref([]);
|
||||
const total = ref(0);
|
||||
const goodsParams = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 15,
|
||||
order: "desc",
|
||||
goodsName: "",
|
||||
sn: "",
|
||||
categoryPath: "",
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
sort: "createTime",
|
||||
});
|
||||
const category = ref([]);
|
||||
const goodsData = ref([]);
|
||||
const empty = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
watch(category, (val) => {
|
||||
goodsParams.categoryPath = val && val.length ? val[2] : "";
|
||||
});
|
||||
watch(
|
||||
() => props.selectedWay,
|
||||
(val) => {
|
||||
selectedList.value = Array.isArray(val) ? val.slice() : [];
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
watch(
|
||||
() => goodsParams.categoryPath,
|
||||
() => {
|
||||
goodsData.value = [];
|
||||
goodsParams.pageNumber = 1;
|
||||
getQueryGoodsList();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function onSearchGoods() {
|
||||
goodsData.value = [];
|
||||
goodsParams.pageNumber = 1;
|
||||
getQueryGoodsList();
|
||||
}
|
||||
function changePageSize(v) {
|
||||
goodsParams.pageNumber = v;
|
||||
getQueryGoodsList();
|
||||
}
|
||||
function getQueryGoodsList() {
|
||||
loading.value = true;
|
||||
API_Goods.getGoodsSkuData(goodsParams)
|
||||
.then((res) => {
|
||||
initGoods(res);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function initGoods(res) {
|
||||
const records = res?.result?.records || [];
|
||||
if (records.length) {
|
||||
records.forEach((item) => {
|
||||
item.selected = false;
|
||||
item.___type = "goods";
|
||||
selectedList.value.forEach((e) => {
|
||||
if (e.id && e.id === item.id) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
total.value = res.result.total || 0;
|
||||
goodsData.value = records;
|
||||
empty.value = false;
|
||||
} else {
|
||||
goodsData.value = [];
|
||||
empty.value = true;
|
||||
}
|
||||
}
|
||||
function emitSelected(list) {
|
||||
selectedList.value = list;
|
||||
emit("selected", selectedList.value);
|
||||
}
|
||||
function init() {
|
||||
API_Goods.getGoodsSkuData(goodsParams).then((res) => {
|
||||
initGoods(res);
|
||||
});
|
||||
API_Goods.getCategoryTree({ deleteFlag: false }).then((res) => {
|
||||
if (res.success) {
|
||||
deepGroup(res.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
function deepGroup(val) {
|
||||
val.forEach((item) => {
|
||||
let childWay = [];
|
||||
if (item.children) {
|
||||
item.children.forEach((child) => {
|
||||
if (child.children) {
|
||||
child.children.forEach((grandson, index, arr) => {
|
||||
arr[index] = {
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
childWay.push({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: child.children,
|
||||
});
|
||||
});
|
||||
}
|
||||
skuList.value.push({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: childWay,
|
||||
});
|
||||
});
|
||||
}
|
||||
function checkedGoods(val) {
|
||||
if (type.value !== "multiple") {
|
||||
goodsData.value.forEach((item) => {
|
||||
item.selected = false;
|
||||
});
|
||||
val.selected = true;
|
||||
emitSelected([val]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!val.selected) {
|
||||
val.selected = true;
|
||||
emitSelected([...selectedList.value, val]);
|
||||
} else {
|
||||
val.selected = false;
|
||||
emitSelected(selectedList.value.filter((item) => item.id !== val.id));
|
||||
}
|
||||
}
|
||||
|
||||
// 父组件 lili-dialog/index.vue 通过 $refs.goodsDialog.type = "single" 切换单选;
|
||||
// expose 的 ref 外部读写等价于原 data 字段
|
||||
defineExpose({ type });
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
"
|
||||
v-if="goodsFlag"
|
||||
ref="goodsDialog"
|
||||
ref="goodsDialogRef"
|
||||
:selectedWay="goodsData"
|
||||
/>
|
||||
<linkDialog
|
||||
@@ -36,68 +36,82 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import linkDialog from "./link-dialog";
|
||||
export default {
|
||||
components: {
|
||||
goodsDialog,
|
||||
linkDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
goodsFlag: false, // 是否商品选择器
|
||||
goodsData: [], //选择的商品
|
||||
linkData: "", //选择的链接
|
||||
flag: false, // modal显隐
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clearGoodsSelected(){
|
||||
this.goodsData = []
|
||||
},
|
||||
// 关闭弹窗
|
||||
clickClose() {
|
||||
this.flag = false;
|
||||
this.goodsFlag = false;
|
||||
this.$emit("closeFlag", false);
|
||||
},
|
||||
// 单选商品
|
||||
singleGoods() {
|
||||
var timer = setInterval(() => {
|
||||
if (this.$refs.goodsDialog) {
|
||||
this.$refs.goodsDialog.type = "single";
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
// 点击确认
|
||||
clickOK() {
|
||||
if (this.goodsFlag) {
|
||||
this.$emit("selectedGoodsData", this.goodsData);
|
||||
} else {
|
||||
this.$emit("selectedLink", this.linkData);
|
||||
}
|
||||
this.clickClose();
|
||||
},
|
||||
// 打开组件方法
|
||||
open(type, mutiple) {
|
||||
this.flag = true;
|
||||
if (type == "goods") {
|
||||
this.goodsFlag = true;
|
||||
if (mutiple) {
|
||||
this.singleGoods();
|
||||
}
|
||||
} else {
|
||||
this.goodsFlag = false;
|
||||
}
|
||||
},
|
||||
// 关闭组件
|
||||
close() {
|
||||
this.flag = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const emit = defineEmits(["closeFlag", "selectedGoodsData", "selectedLink"]);
|
||||
|
||||
const goodsFlag = ref(false); // 是否商品选择器
|
||||
const goodsData = ref([]); //选择的商品
|
||||
const linkData = ref(""); //选择的链接
|
||||
const flag = ref(false); // modal显隐
|
||||
|
||||
// 模板 ref:原 ref="goodsDialog" 与 import 的组件同名,改名避免互相覆盖
|
||||
const goodsDialogRef = ref(null);
|
||||
|
||||
function clearGoodsSelected() {
|
||||
goodsData.value = [];
|
||||
}
|
||||
// 关闭弹窗
|
||||
function clickClose() {
|
||||
flag.value = false;
|
||||
goodsFlag.value = false;
|
||||
emit("closeFlag", false);
|
||||
}
|
||||
// 单选商品
|
||||
function singleGoods() {
|
||||
var timer = setInterval(() => {
|
||||
if (goodsDialogRef.value) {
|
||||
goodsDialogRef.value.type = "single";
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
// 点击确认
|
||||
function clickOK() {
|
||||
if (goodsFlag.value) {
|
||||
emit("selectedGoodsData", goodsData.value);
|
||||
} else {
|
||||
emit("selectedLink", linkData.value);
|
||||
}
|
||||
clickClose();
|
||||
}
|
||||
// 打开组件方法
|
||||
function open(type, mutiple) {
|
||||
flag.value = true;
|
||||
if (type == "goods") {
|
||||
goodsFlag.value = true;
|
||||
if (mutiple) {
|
||||
singleGoods();
|
||||
}
|
||||
} else {
|
||||
goodsFlag.value = false;
|
||||
}
|
||||
}
|
||||
// 关闭组件
|
||||
function close() {
|
||||
flag.value = false;
|
||||
}
|
||||
|
||||
// 外部通过 ref 访问的成员(grep manager/src 全量确认):
|
||||
// - open:page-decoration 各 modelList、modelForm/modelFormItem、hotzone/Zone、wap/advertising、alertAdvertising 等打开弹窗(goods/link)
|
||||
// - singleGoods:modelList/forYour.vue、mixModel.vue 直接调用
|
||||
// - clearGoodsSelected:wap/decorate.vue、modelList/goodsAndType.vue 调用
|
||||
// - goodsData:modelList/newGoodsSort.vue、notEnough.vue 回写已选商品
|
||||
// - goodsFlag:wap/decorate.vue 直接写入
|
||||
// - flag:wap/decorate.vue 直接写入 true 打开弹窗
|
||||
// - close:未被 grep 到外部调用,原 Options API 实例天然暴露,保守保留
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
singleGoods,
|
||||
clearGoodsSelected,
|
||||
goodsData,
|
||||
goodsFlag,
|
||||
flag,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-dialog__body) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:key="i"
|
||||
>
|
||||
<component
|
||||
ref="lili-component"
|
||||
ref="liliComponentRef"
|
||||
:is="templateWay[item.name]"
|
||||
@selected="
|
||||
(val) => {
|
||||
@@ -20,94 +20,85 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import wap from "./wap.js";
|
||||
<script setup>
|
||||
import { nextTick, onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import wapList from "./wap.js";
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import templateWay from "./template/index";
|
||||
export default {
|
||||
components: {
|
||||
goodsDialog
|
||||
|
||||
const emit = defineEmits(["selectedLink"]);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const changed = ref(""); // 变更模板
|
||||
const selected = ref(0); // 已选数据
|
||||
const selectedLink = ref(""); //选中的链接
|
||||
// wap 为共享的模块级数组,保持原 data 引用同一数组的行为
|
||||
const wap = ref(wapList); // tab标签
|
||||
const activeTab = ref(wapList[0]?.title || "");
|
||||
// v-for 内模板 ref 收集为数组;原名 lili-component 含连字符,改名并同步 script
|
||||
const liliComponentRef = ref([]);
|
||||
|
||||
watch(
|
||||
changed,
|
||||
(val) => {
|
||||
emit("selectedLink", val[0]); //因为是单选,所以直接返回第一个
|
||||
},
|
||||
setup() {
|
||||
return { templateWay };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
changed: "", // 变更模板
|
||||
selected: 0, // 已选数据
|
||||
selectedLink: "", //选中的链接
|
||||
wap, // tab标签
|
||||
activeTab: wap[0]?.title || "",
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
changed: {
|
||||
handler(val) {
|
||||
this.$emit("selectedLink", val[0]); //因为是单选,所以直接返回第一个
|
||||
},
|
||||
deep: true
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (liliComponentRef.value?.[0]) {
|
||||
liliComponentRef.value[0].type = "single"; //商品页面设置成为单选
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs["lili-component"][0].type = "single"; //商品页面设置成为单选
|
||||
});
|
||||
let url = window.location.href
|
||||
if(url.indexOf('/floorList/renovation') != -1){
|
||||
//此处去重防止移动楼层多次点击push 太多数据
|
||||
let obj = {};
|
||||
this.wap = this.wap.reduce((cur, next) => {
|
||||
//对象去重
|
||||
if (next.title != undefined) {
|
||||
obj[next.title]
|
||||
? ""
|
||||
: (obj[next.title] = true && cur.push(next));
|
||||
}
|
||||
return cur;
|
||||
}, []);
|
||||
if (this.$route.path !== '/floorList/main') {
|
||||
this.wap.forEach((items, indexs) => {
|
||||
if (items.title == '活动') {
|
||||
this.wap.splice(indexs, 1)
|
||||
}
|
||||
})
|
||||
});
|
||||
let url = window.location.href;
|
||||
if (url.indexOf("/floorList/renovation") != -1) {
|
||||
//此处去重防止移动楼层多次点击push 太多数据
|
||||
let obj = {};
|
||||
wap.value = wap.value.reduce((cur, next) => {
|
||||
//对象去重
|
||||
if (next.title != undefined) {
|
||||
obj[next.title]
|
||||
? ""
|
||||
: (obj[next.title] = true && cur.push(next));
|
||||
}
|
||||
}else{
|
||||
this.wap.push( {
|
||||
title: "活动",
|
||||
url: "3",
|
||||
name: "marketing"
|
||||
})
|
||||
let obj = {};
|
||||
this.wap = this.wap.reduce((cur, next) => {
|
||||
//对象去重
|
||||
if (next.title != undefined) {
|
||||
obj[next.title]
|
||||
? ""
|
||||
: (obj[next.title] = true && cur.push(next));
|
||||
}
|
||||
return cur;
|
||||
}, []);
|
||||
return cur;
|
||||
}, []);
|
||||
if (route.path !== "/floorList/main") {
|
||||
wap.value.forEach((items, indexs) => {
|
||||
if (items.title == "活动") {
|
||||
wap.value.splice(indexs, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.wap.forEach((item) => {
|
||||
if (item) {
|
||||
item.selected = false;
|
||||
}
|
||||
} else {
|
||||
wap.value.push({
|
||||
title: "活动",
|
||||
url: "3",
|
||||
name: "marketing",
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
// isVisible(item) {
|
||||
// const type = this.$route.query.pagetype;
|
||||
// if (type == "INDEX" && [ "discover"].includes(item.name)) {
|
||||
// return false;
|
||||
// }else if(type == "DISCOVER" && item.name == 'special'){
|
||||
// return false;
|
||||
// }else{
|
||||
// return true
|
||||
// }
|
||||
// },
|
||||
let obj = {};
|
||||
wap.value = wap.value.reduce((cur, next) => {
|
||||
//对象去重
|
||||
if (next.title != undefined) {
|
||||
obj[next.title]
|
||||
? ""
|
||||
: (obj[next.title] = true && cur.push(next));
|
||||
}
|
||||
return cur;
|
||||
}, []);
|
||||
}
|
||||
};
|
||||
wap.value.forEach((item) => {
|
||||
if (item) {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
|
||||
@@ -22,94 +22,94 @@
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getCategoryTree } from "@/api/goods.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
parentIndex: '', // 分类父级下标
|
||||
secondIndex: '', // 分类二级下标
|
||||
thirdIndex: '', // 分类三级下标
|
||||
categoryList: [], // 分类列表一级数据
|
||||
secondLevel: [], // 分类列表二级数据
|
||||
thirdLevel: [], // 分类列表三级数据
|
||||
selectedData: "", // 已选分类数据
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
loadCategoryFromCache() {
|
||||
try {
|
||||
const raw = localStorage.getItem("category");
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
applyCategoryList(category) {
|
||||
if (!Array.isArray(category)) {
|
||||
this.categoryList = [];
|
||||
return;
|
||||
}
|
||||
category.forEach((item) => {
|
||||
item.___type = "category";
|
||||
});
|
||||
this.categoryList = category;
|
||||
},
|
||||
// 点击一级
|
||||
handleClickChild(item, index) {
|
||||
this.parentIndex = index;
|
||||
this.secondLevel = item.children || [];
|
||||
item.___type = "category";
|
||||
item.allId = item.id;
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
this.secondIndex = '';
|
||||
this.thirdIndex = '';
|
||||
this.thirdLevel = []
|
||||
this.$emit("selected", [item]);
|
||||
// 点击第一级的时候默认显示第二级第一个
|
||||
// this.handleClickSecondChild(item.children, 0);
|
||||
},
|
||||
// 点击二级
|
||||
handleClickSecondChild(second, index) {
|
||||
second.___type = "category";
|
||||
second.allId = `${second.parentId},${second.id}`
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关;
|
||||
// decorate.vue 等仅通过 @selected → selectedLink 事件交互)
|
||||
const parentIndex = ref(''); // 分类父级下标
|
||||
const secondIndex = ref(''); // 分类二级下标
|
||||
const thirdIndex = ref(''); // 分类三级下标
|
||||
const categoryList = ref([]); // 分类列表一级数据
|
||||
const secondLevel = ref([]); // 分类列表二级数据
|
||||
const thirdLevel = ref([]); // 分类列表三级数据
|
||||
const selectedData = ref(""); // 已选分类数据
|
||||
|
||||
this.secondIndex = index;
|
||||
this.thirdLevel = second.children || [];
|
||||
this.thirdIndex = '';
|
||||
this.$emit("selected", [second]);
|
||||
// this.handleClickthirdChild(second.children[0], 0);
|
||||
},
|
||||
// 点击三级
|
||||
handleClickthirdChild(item, index) {
|
||||
item.___type = "category";
|
||||
item.allId = `${this.categoryList[this.parentIndex].id},${item.parentId},${item.id}`
|
||||
this.$emit("selected", [item]);
|
||||
this.thirdIndex = index;
|
||||
},
|
||||
init() {
|
||||
const cached = this.loadCategoryFromCache();
|
||||
if (cached) {
|
||||
this.applyCategoryList(cached);
|
||||
return;
|
||||
}
|
||||
getCategoryTree().then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
this.applyCategoryList(res.result);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
function loadCategoryFromCache() {
|
||||
try {
|
||||
const raw = localStorage.getItem("category");
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function applyCategoryList(category) {
|
||||
if (!Array.isArray(category)) {
|
||||
categoryList.value = [];
|
||||
return;
|
||||
}
|
||||
category.forEach((item) => {
|
||||
item.___type = "category";
|
||||
});
|
||||
categoryList.value = category;
|
||||
}
|
||||
// 点击一级
|
||||
function handleClickChild(item, index) {
|
||||
parentIndex.value = index;
|
||||
secondLevel.value = item.children || [];
|
||||
item.___type = "category";
|
||||
item.allId = item.id;
|
||||
|
||||
secondIndex.value = '';
|
||||
thirdIndex.value = '';
|
||||
thirdLevel.value = []
|
||||
emit("selected", [item]);
|
||||
// 点击第一级的时候默认显示第二级第一个
|
||||
// handleClickSecondChild(item.children, 0);
|
||||
}
|
||||
// 点击二级
|
||||
function handleClickSecondChild(second, index) {
|
||||
second.___type = "category";
|
||||
second.allId = `${second.parentId},${second.id}`
|
||||
|
||||
secondIndex.value = index;
|
||||
thirdLevel.value = second.children || [];
|
||||
thirdIndex.value = '';
|
||||
emit("selected", [second]);
|
||||
// handleClickthirdChild(second.children[0], 0);
|
||||
}
|
||||
// 点击三级
|
||||
function handleClickthirdChild(item, index) {
|
||||
item.___type = "category";
|
||||
item.allId = `${categoryList.value[parentIndex.value].id},${item.parentId},${item.id}`
|
||||
emit("selected", [item]);
|
||||
thirdIndex.value = index;
|
||||
}
|
||||
function init() {
|
||||
const cached = loadCategoryFromCache();
|
||||
if (cached) {
|
||||
applyCategoryList(cached);
|
||||
return;
|
||||
}
|
||||
getCategoryTree().then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
applyCategoryList(res.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
|
||||
@@ -68,99 +68,95 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { getAllPromotion } from "@/api/promotion";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
totals: "",
|
||||
loading: true,
|
||||
promotionList: "",
|
||||
selectedIndex: 0,
|
||||
promotions: "",
|
||||
index: 999,
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
showPromotionList: [],
|
||||
};
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const totals = ref("");
|
||||
const loading = ref(true);
|
||||
const promotionList = ref("");
|
||||
const selectedIndex = ref(0);
|
||||
const promotions = ref("");
|
||||
const index = ref(999);
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
const showPromotionList = ref([]);
|
||||
|
||||
const isSeckillMode = computed(() => {
|
||||
return promotions.value === "SECKILL";
|
||||
});
|
||||
|
||||
watch(
|
||||
params,
|
||||
() => {
|
||||
index.value = 999;
|
||||
typeOption(promotions.value) && typeOption(promotions.value).methodsed();
|
||||
},
|
||||
computed: {
|
||||
isSeckillMode() {
|
||||
return this.promotions === "SECKILL";
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
watch: {
|
||||
params: {
|
||||
handler() {
|
||||
this.index = 999;
|
||||
this.typeOption(this.promotions) && this.typeOption(this.promotions).methodsed();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
seckillStatusText(v) {
|
||||
if (v === "APPLY") return "申请";
|
||||
if (v === "PASS") return "通过";
|
||||
return "拒绝";
|
||||
},
|
||||
sortGoods(type) {
|
||||
this.loading = false;
|
||||
this.showPromotionList = this.promotionList[type];
|
||||
},
|
||||
typeOption(type) {
|
||||
switch (type) {
|
||||
case "FULL_DISCOUNT":
|
||||
return { title: "满减", methodsed: () => { this.showPromotionList = []; this.sortGoods("FULL_DISCOUNT"); } };
|
||||
case "PINTUAN":
|
||||
return { title: "拼团", methodsed: () => { this.showPromotionList = []; this.sortGoods("PINTUAN"); } };
|
||||
case "KANJIA":
|
||||
return { title: "砍价", methodsed: () => { this.showPromotionList = []; this.sortGoods("KANJIA"); } };
|
||||
case "SECKILL":
|
||||
return { title: "秒杀", methodsed: () => { this.showPromotionList = []; this.sortGoods("SECKILL"); } };
|
||||
case "POINTS_GOODS":
|
||||
return { title: "积分商品", methodsed: () => { this.showPromotionList = []; this.sortGoods("POINTS_GOODS"); } };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
selectedPromotion(val) {
|
||||
val.row.___type = "marketing";
|
||||
val.row.___promotion = this.promotions;
|
||||
this.$emit("selected", [val.row]);
|
||||
this.index = val.index;
|
||||
},
|
||||
async init() {
|
||||
const res = await getAllPromotion();
|
||||
if (res.success) {
|
||||
this.loading = false;
|
||||
this.getPromotion(res);
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
getPromotion(res) {
|
||||
if (res.result) {
|
||||
this.promotionList = res.result;
|
||||
delete this.promotionList.COUPON;
|
||||
Object.keys(res.result)[0] && this.typeOption(Object.keys(res.result)[0]).methodsed();
|
||||
this.promotions = Object.keys(res.result)[0];
|
||||
}
|
||||
},
|
||||
clickPromotion(val, i) {
|
||||
this.promotions = val;
|
||||
this.selectedIndex = i;
|
||||
this.params.pageNumber = 1;
|
||||
this.typeOption(val) && this.typeOption(val).methodsed(this.promotionList[val].id);
|
||||
},
|
||||
},
|
||||
};
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function seckillStatusText(v) {
|
||||
if (v === "APPLY") return "申请";
|
||||
if (v === "PASS") return "通过";
|
||||
return "拒绝";
|
||||
}
|
||||
function sortGoods(type) {
|
||||
loading.value = false;
|
||||
showPromotionList.value = promotionList.value[type];
|
||||
}
|
||||
function typeOption(type) {
|
||||
switch (type) {
|
||||
case "FULL_DISCOUNT":
|
||||
return { title: "满减", methodsed: () => { showPromotionList.value = []; sortGoods("FULL_DISCOUNT"); } };
|
||||
case "PINTUAN":
|
||||
return { title: "拼团", methodsed: () => { showPromotionList.value = []; sortGoods("PINTUAN"); } };
|
||||
case "KANJIA":
|
||||
return { title: "砍价", methodsed: () => { showPromotionList.value = []; sortGoods("KANJIA"); } };
|
||||
case "SECKILL":
|
||||
return { title: "秒杀", methodsed: () => { showPromotionList.value = []; sortGoods("SECKILL"); } };
|
||||
case "POINTS_GOODS":
|
||||
return { title: "积分商品", methodsed: () => { showPromotionList.value = []; sortGoods("POINTS_GOODS"); } };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
function selectedPromotion(val) {
|
||||
val.row.___type = "marketing";
|
||||
val.row.___promotion = promotions.value;
|
||||
emit("selected", [val.row]);
|
||||
index.value = val.index;
|
||||
}
|
||||
async function init() {
|
||||
const res = await getAllPromotion();
|
||||
if (res.success) {
|
||||
loading.value = false;
|
||||
getPromotion(res);
|
||||
} else {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
function getPromotion(res) {
|
||||
if (res.result) {
|
||||
promotionList.value = res.result;
|
||||
delete promotionList.value.COUPON;
|
||||
Object.keys(res.result)[0] && typeOption(Object.keys(res.result)[0]).methodsed();
|
||||
promotions.value = Object.keys(res.result)[0];
|
||||
}
|
||||
}
|
||||
function clickPromotion(val, i) {
|
||||
promotions.value = val;
|
||||
selectedIndex.value = i;
|
||||
params.value.pageNumber = 1;
|
||||
typeOption(val) && typeOption(val).methodsed(promotionList.value[val].id);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.link-text {
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { markRaw } from "vue";
|
||||
<script setup>
|
||||
import { markRaw, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
House,
|
||||
ShoppingCart,
|
||||
@@ -50,61 +51,61 @@ import {
|
||||
Link,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
linkList: [
|
||||
{ title: "首页", icon: markRaw(House), ___type: "home" },
|
||||
{ title: "购物车", icon: markRaw(ShoppingCart), ___type: "cart" },
|
||||
{ title: "收藏商品", icon: markRaw(Star), ___type: "collection" },
|
||||
{ title: "我的订单", icon: markRaw(Document), ___type: "order" },
|
||||
{ title: "个人中心", icon: markRaw(User), ___type: "user" },
|
||||
{ title: "拼团频道", icon: markRaw(Promotion), ___type: "group" },
|
||||
{ title: "秒杀频道", icon: markRaw(Promotion), ___type: "seckill" },
|
||||
{ title: "领券中心", icon: markRaw(PriceTag), ___type: "coupon" },
|
||||
{ title: "签到", icon: markRaw(Sunny), ___type: "sign" },
|
||||
{ title: "小程序直播", icon: markRaw(VideoCamera), ___type: "live" },
|
||||
{ title: "砍价", icon: markRaw(Share), ___type: "kanjia" },
|
||||
{ title: "积分商城", icon: markRaw(ShoppingBag), ___type: "point" },
|
||||
],
|
||||
linkItem: {
|
||||
title: "外部链接",
|
||||
icon: markRaw(Link),
|
||||
___type: "link",
|
||||
url: "",
|
||||
},
|
||||
linkVisible: true,
|
||||
selectedIndex: 9999999,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const urls = window.location.href;
|
||||
if (urls.indexOf("/floorList/renovation") != -1) {
|
||||
this.linkList = this.linkList.filter(
|
||||
(item) =>
|
||||
item.title !== "砍价" &&
|
||||
item.title !== "小程序直播" &&
|
||||
item.title !== "积分商城"
|
||||
);
|
||||
this.linkVisible = true;
|
||||
} else {
|
||||
this.linkVisible = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showLinkItem(item) {
|
||||
return (
|
||||
(item.title !== "拼团频道" && item.title !== "签到") ||
|
||||
this.$route.name !== "renovation"
|
||||
);
|
||||
},
|
||||
handleLink(val, index) {
|
||||
val = { ...val, ___type: "other" };
|
||||
this.selectedIndex = index;
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const linkList = ref([
|
||||
{ title: "首页", icon: markRaw(House), ___type: "home" },
|
||||
{ title: "购物车", icon: markRaw(ShoppingCart), ___type: "cart" },
|
||||
{ title: "收藏商品", icon: markRaw(Star), ___type: "collection" },
|
||||
{ title: "我的订单", icon: markRaw(Document), ___type: "order" },
|
||||
{ title: "个人中心", icon: markRaw(User), ___type: "user" },
|
||||
{ title: "拼团频道", icon: markRaw(Promotion), ___type: "group" },
|
||||
{ title: "秒杀频道", icon: markRaw(Promotion), ___type: "seckill" },
|
||||
{ title: "领券中心", icon: markRaw(PriceTag), ___type: "coupon" },
|
||||
{ title: "签到", icon: markRaw(Sunny), ___type: "sign" },
|
||||
{ title: "小程序直播", icon: markRaw(VideoCamera), ___type: "live" },
|
||||
{ title: "砍价", icon: markRaw(Share), ___type: "kanjia" },
|
||||
{ title: "积分商城", icon: markRaw(ShoppingBag), ___type: "point" },
|
||||
]);
|
||||
const linkItem = ref({
|
||||
title: "外部链接",
|
||||
icon: markRaw(Link),
|
||||
___type: "link",
|
||||
url: "",
|
||||
});
|
||||
const linkVisible = ref(true);
|
||||
const selectedIndex = ref(9999999);
|
||||
|
||||
// 原 created 钩子
|
||||
const urls = window.location.href;
|
||||
if (urls.indexOf("/floorList/renovation") != -1) {
|
||||
linkList.value = linkList.value.filter(
|
||||
(item) =>
|
||||
item.title !== "砍价" &&
|
||||
item.title !== "小程序直播" &&
|
||||
item.title !== "积分商城"
|
||||
);
|
||||
linkVisible.value = true;
|
||||
} else {
|
||||
linkVisible.value = false;
|
||||
}
|
||||
|
||||
function showLinkItem(item) {
|
||||
return (
|
||||
(item.title !== "拼团频道" && item.title !== "签到") ||
|
||||
route.name !== "renovation"
|
||||
);
|
||||
}
|
||||
function handleLink(val, index) {
|
||||
val = { ...val, ___type: "other" };
|
||||
selectedIndex.value = index;
|
||||
emit("selected", [val]);
|
||||
}
|
||||
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
|
||||
@@ -12,25 +12,20 @@
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import Article from "@/views/page/article-manage/articleList.vue";
|
||||
export default {
|
||||
components: {
|
||||
Article,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
callbackArticle(val) {
|
||||
val.___type = "pages";
|
||||
|
||||
val.___path = "/pages/passport/article";
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
function callbackArticle(val) {
|
||||
val.___type = "pages";
|
||||
|
||||
val.___path = "/pages/passport/article";
|
||||
|
||||
emit("selected", [val]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-card__body) {
|
||||
|
||||
@@ -56,53 +56,56 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, reactive } from "vue";
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
total: 0,
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 12,
|
||||
storeDisable: "OPEN",
|
||||
storeName: "",
|
||||
},
|
||||
shopsData: [],
|
||||
selected: 9999999999,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.shopsData = [];
|
||||
this.params.pageNumber = 1;
|
||||
this.init();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.params.pageNumber = v;
|
||||
this.init();
|
||||
},
|
||||
init() {
|
||||
this.loading = true;
|
||||
getShopListData(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
this.total = res.result.total;
|
||||
this.shopsData = res.result.records;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
clickShop(val, i) {
|
||||
this.selected = i;
|
||||
val = { ...val, ___type: "shops" };
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 12,
|
||||
storeDisable: "OPEN",
|
||||
storeName: "",
|
||||
});
|
||||
const shopsData = ref([]);
|
||||
const selected = ref(9999999999);
|
||||
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
|
||||
// created
|
||||
init();
|
||||
|
||||
function resetSearch() {
|
||||
shopsData.value = [];
|
||||
params.pageNumber = 1;
|
||||
init();
|
||||
}
|
||||
|
||||
function changePageSize(v) {
|
||||
params.pageNumber = v;
|
||||
init();
|
||||
}
|
||||
|
||||
function init() {
|
||||
loading.value = true;
|
||||
getShopListData(params).then((res) => {
|
||||
if (res.success) {
|
||||
total.value = res.result.total;
|
||||
shopsData.value = res.result.records;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function clickShop(val, i) {
|
||||
selected.value = i;
|
||||
val = { ...val, ___type: "shops" };
|
||||
emit("selected", [val]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
|
||||
@@ -34,56 +34,57 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getHomeList } from "@/api/other.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
index: 999,
|
||||
data: [],
|
||||
params: {
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
pageClientType: "H5",
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
pageType: "SPECIAL",
|
||||
},
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
selectRow(row, idx) {
|
||||
this.index = idx;
|
||||
const payload = { ...row, pageType: "special", ___type: "special" };
|
||||
this.$emit("selected", [payload]);
|
||||
},
|
||||
changePageNum(val) {
|
||||
this.params.pageNumber = val;
|
||||
this.init();
|
||||
},
|
||||
changePageSize(val) {
|
||||
this.params.pageNumber = 1;
|
||||
this.params.pageSize = val;
|
||||
this.init();
|
||||
},
|
||||
async init() {
|
||||
this.params.pageClientType = this.$route.name === "renovation" ? "PC" : "H5";
|
||||
const res = await getHomeList(this.params);
|
||||
if (res.success) {
|
||||
this.loading = false;
|
||||
this.data = res.result.records;
|
||||
this.total = res.result.total;
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(true);
|
||||
const index = ref(999);
|
||||
const data = ref([]);
|
||||
const params = reactive({
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
pageClientType: "H5",
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
pageType: "SPECIAL",
|
||||
});
|
||||
const total = ref(0);
|
||||
|
||||
function selectRow(row, idx) {
|
||||
index.value = idx;
|
||||
const payload = { ...row, pageType: "special", ___type: "special" };
|
||||
emit("selected", [payload]);
|
||||
}
|
||||
function changePageNum(val) {
|
||||
params.pageNumber = val;
|
||||
init();
|
||||
}
|
||||
function changePageSize(val) {
|
||||
params.pageNumber = 1;
|
||||
params.pageSize = val;
|
||||
init();
|
||||
}
|
||||
async function init() {
|
||||
params.pageClientType = route.name === "renovation" ? "PC" : "H5";
|
||||
const res = await getHomeList(params);
|
||||
if (res.success) {
|
||||
loading.value = false;
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
} else {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
img {
|
||||
|
||||
@@ -6,13 +6,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
name: "circleLoading",
|
||||
components: { Loading },
|
||||
};
|
||||
defineOptions({ name: "circleLoading" });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -6,23 +6,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "configDrawer",
|
||||
data() {
|
||||
return {
|
||||
drawer: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.drawer = true;
|
||||
},
|
||||
close() {
|
||||
this.drawer = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
defineOptions({ name: "configDrawer" });
|
||||
|
||||
const drawer = ref(false);
|
||||
|
||||
function open() {
|
||||
drawer.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
drawer.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -13,56 +13,55 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { initDepartment } from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "departmentChoose",
|
||||
data() {
|
||||
return {
|
||||
selectDep: [],
|
||||
department: [],
|
||||
cascaderProps: {
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initDepartmentData() {
|
||||
initDepartment().then((res) => {
|
||||
if (res.success) {
|
||||
const arr = res.result;
|
||||
this.filterData(arr);
|
||||
this.department = arr;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleChangeDep(value) {
|
||||
let departmentId = "";
|
||||
if (value && value.length > 0) {
|
||||
departmentId = value[value.length - 1];
|
||||
}
|
||||
this.$emit("on-change", departmentId);
|
||||
},
|
||||
clearSelect() {
|
||||
this.selectDep = [];
|
||||
},
|
||||
filterData(data) {
|
||||
data.forEach((e) => {
|
||||
e.value = e.id;
|
||||
e.label = e.title;
|
||||
if (e.children) {
|
||||
this.filterData(e.children);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDepartmentData();
|
||||
},
|
||||
};
|
||||
defineOptions({ name: "departmentChoose" });
|
||||
|
||||
const emit = defineEmits(["on-change"]);
|
||||
|
||||
const selectDep = ref([]);
|
||||
const department = ref([]);
|
||||
const cascaderProps = ref({
|
||||
value: "value",
|
||||
label: "label",
|
||||
children: "children",
|
||||
checkStrictly: true,
|
||||
emitPath: true,
|
||||
});
|
||||
|
||||
function initDepartmentData() {
|
||||
initDepartment().then((res) => {
|
||||
if (res.success) {
|
||||
const arr = res.result;
|
||||
filterData(arr);
|
||||
department.value = arr;
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleChangeDep(value) {
|
||||
let departmentId = "";
|
||||
if (value && value.length > 0) {
|
||||
departmentId = value[value.length - 1];
|
||||
}
|
||||
emit("on-change", departmentId);
|
||||
}
|
||||
function clearSelect() {
|
||||
selectDep.value = [];
|
||||
}
|
||||
function filterData(data) {
|
||||
data.forEach((e) => {
|
||||
e.value = e.id;
|
||||
e.label = e.title;
|
||||
if (e.children) {
|
||||
filterData(e.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initDepartmentData();
|
||||
|
||||
// userManage.vue 通过 depRef 引用本组件(当前未实际调用方法),作为公共组件 API 保留
|
||||
defineExpose({ clearSelect, selectDep });
|
||||
</script>
|
||||
|
||||
@@ -35,101 +35,100 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { initDepartment } from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "departmentTreeChoose",
|
||||
props: {
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "点击选择部门",
|
||||
},
|
||||
defineOptions({ name: "departmentTreeChoose" });
|
||||
|
||||
const props = defineProps({
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
depLoading: false,
|
||||
departmentTitle: "",
|
||||
searchKey: "",
|
||||
dataDep: [],
|
||||
cloneDep: [],
|
||||
departmentId: [],
|
||||
treeProps: {
|
||||
label: "title",
|
||||
children: "children",
|
||||
},
|
||||
};
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
methods: {
|
||||
initDepartmentData() {
|
||||
this.depLoading = true;
|
||||
initDepartment()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.dataDep = res.result;
|
||||
this.cloneDep = JSON.parse(JSON.stringify(this.dataDep));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.depLoading = false;
|
||||
});
|
||||
},
|
||||
searchDep() {
|
||||
if (this.searchKey) {
|
||||
this.dataDep = this.cloneDep.filter(
|
||||
(item) => item.title.indexOf(this.searchKey) > -1
|
||||
);
|
||||
} else {
|
||||
this.dataDep = JSON.parse(JSON.stringify(this.cloneDep));
|
||||
}
|
||||
},
|
||||
selectTree(node) {
|
||||
if (!node) {
|
||||
this.$emit("on-change", null);
|
||||
this.departmentId = "";
|
||||
this.departmentTitle = "";
|
||||
return;
|
||||
}
|
||||
this.departmentId = node.id;
|
||||
this.departmentTitle = node.title;
|
||||
this.$emit("on-change", {
|
||||
departmentId: this.departmentId,
|
||||
departmentTitle: this.departmentTitle,
|
||||
});
|
||||
},
|
||||
clearSelect() {
|
||||
this.departmentId = [];
|
||||
this.departmentTitle = "";
|
||||
this.initDepartmentData();
|
||||
if (this.multiple) {
|
||||
this.$emit("on-change", []);
|
||||
} else {
|
||||
this.$emit("on-change", "");
|
||||
}
|
||||
this.$emit("on-clear");
|
||||
},
|
||||
setData(ids, title) {
|
||||
this.departmentTitle = title;
|
||||
if (this.multiple) {
|
||||
this.departmentId = ids;
|
||||
} else {
|
||||
this.departmentId = [];
|
||||
this.departmentId.push(ids);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDepartmentData();
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "点击选择部门",
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["on-change", "on-clear"]);
|
||||
|
||||
const depLoading = ref(false);
|
||||
const departmentTitle = ref("");
|
||||
const searchKey = ref("");
|
||||
const dataDep = ref([]);
|
||||
const cloneDep = ref([]);
|
||||
const departmentId = ref([]);
|
||||
const treeProps = {
|
||||
label: "title",
|
||||
children: "children",
|
||||
};
|
||||
|
||||
function initDepartmentData() {
|
||||
depLoading.value = true;
|
||||
initDepartment()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
dataDep.value = res.result;
|
||||
cloneDep.value = JSON.parse(JSON.stringify(dataDep.value));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
depLoading.value = false;
|
||||
});
|
||||
}
|
||||
function searchDep() {
|
||||
if (searchKey.value) {
|
||||
dataDep.value = cloneDep.value.filter(
|
||||
(item) => item.title.indexOf(searchKey.value) > -1
|
||||
);
|
||||
} else {
|
||||
dataDep.value = JSON.parse(JSON.stringify(cloneDep.value));
|
||||
}
|
||||
}
|
||||
function selectTree(node) {
|
||||
if (!node) {
|
||||
emit("on-change", null);
|
||||
departmentId.value = "";
|
||||
departmentTitle.value = "";
|
||||
return;
|
||||
}
|
||||
departmentId.value = node.id;
|
||||
departmentTitle.value = node.title;
|
||||
emit("on-change", {
|
||||
departmentId: departmentId.value,
|
||||
departmentTitle: departmentTitle.value,
|
||||
});
|
||||
}
|
||||
function clearSelect() {
|
||||
departmentId.value = [];
|
||||
departmentTitle.value = "";
|
||||
initDepartmentData();
|
||||
if (props.multiple) {
|
||||
emit("on-change", []);
|
||||
} else {
|
||||
emit("on-change", "");
|
||||
}
|
||||
emit("on-clear");
|
||||
}
|
||||
function setData(ids, title) {
|
||||
departmentTitle.value = title;
|
||||
if (props.multiple) {
|
||||
departmentId.value = ids;
|
||||
} else {
|
||||
departmentId.value = [];
|
||||
departmentId.value.push(ids);
|
||||
}
|
||||
}
|
||||
|
||||
initDepartmentData();
|
||||
|
||||
// 父组件(userManage)通过 ref 调用 setData / clearSelect
|
||||
defineExpose({ setData, clearSelect });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -29,99 +29,105 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "setPassword",
|
||||
props: {
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
defineOptions({ name: "setPassword" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符",
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change"],
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.modelValue ?? this.value ?? "",
|
||||
tipStyle: "password-tip-none",
|
||||
strengthValue: 0,
|
||||
progressStatus: "",
|
||||
strength: "无",
|
||||
grade: 0,
|
||||
};
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
methods: {
|
||||
checkStrengthValue(v) {
|
||||
let grade = 0;
|
||||
if (/\d/.test(v)) grade++;
|
||||
if (/[a-z]/.test(v)) grade++;
|
||||
if (/[A-Z]/.test(v)) grade++;
|
||||
if (/\W/.test(v)) grade++;
|
||||
if (v.length >= 10) grade++;
|
||||
this.grade = grade;
|
||||
return grade;
|
||||
},
|
||||
strengthChange() {
|
||||
if (!this.currentValue) {
|
||||
this.tipStyle = "password-tip-none";
|
||||
this.strength = "无";
|
||||
this.strengthValue = 0;
|
||||
return;
|
||||
}
|
||||
const grade = this.checkStrengthValue(this.currentValue);
|
||||
if (grade <= 1) {
|
||||
this.progressStatus = "exception";
|
||||
this.tipStyle = "password-tip-weak";
|
||||
this.strength = "弱";
|
||||
this.strengthValue = 33;
|
||||
} else if (grade >= 2 && grade <= 4) {
|
||||
this.progressStatus = "";
|
||||
this.tipStyle = "password-tip-middle";
|
||||
this.strength = "中";
|
||||
this.strengthValue = 66;
|
||||
} else {
|
||||
this.progressStatus = "success";
|
||||
this.tipStyle = "password-tip-strong";
|
||||
this.strength = "强";
|
||||
this.strengthValue = 100;
|
||||
}
|
||||
},
|
||||
handleChange() {
|
||||
this.strengthChange();
|
||||
this.$emit("update:modelValue", this.currentValue);
|
||||
this.$emit("input", this.currentValue);
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) return;
|
||||
this.currentValue = value ?? "";
|
||||
this.strengthChange();
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
|
||||
const currentValue = ref(props.modelValue ?? props.value ?? "");
|
||||
const tipStyle = ref("password-tip-none");
|
||||
const strengthValue = ref(0);
|
||||
const progressStatus = ref("");
|
||||
const strength = ref("无");
|
||||
const grade = ref(0);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
|
||||
function checkStrengthValue(v) {
|
||||
// 局部变量原名 grade 与顶层 ref 同名,改名避免遮蔽
|
||||
let g = 0;
|
||||
if (/\d/.test(v)) g++;
|
||||
if (/[a-z]/.test(v)) g++;
|
||||
if (/[A-Z]/.test(v)) g++;
|
||||
if (/\W/.test(v)) g++;
|
||||
if (v.length >= 10) g++;
|
||||
grade.value = g;
|
||||
return g;
|
||||
}
|
||||
|
||||
function strengthChange() {
|
||||
if (!currentValue.value) {
|
||||
tipStyle.value = "password-tip-none";
|
||||
strength.value = "无";
|
||||
strengthValue.value = 0;
|
||||
return;
|
||||
}
|
||||
const grade = checkStrengthValue(currentValue.value);
|
||||
if (grade <= 1) {
|
||||
progressStatus.value = "exception";
|
||||
tipStyle.value = "password-tip-weak";
|
||||
strength.value = "弱";
|
||||
strengthValue.value = 33;
|
||||
} else if (grade >= 2 && grade <= 4) {
|
||||
progressStatus.value = "";
|
||||
tipStyle.value = "password-tip-middle";
|
||||
strength.value = "中";
|
||||
strengthValue.value = 66;
|
||||
} else {
|
||||
progressStatus.value = "success";
|
||||
tipStyle.value = "password-tip-strong";
|
||||
strength.value = "强";
|
||||
strengthValue.value = 100;
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange() {
|
||||
strengthChange();
|
||||
emit("update:modelValue", currentValue.value);
|
||||
emit("input", currentValue.value);
|
||||
emit("on-change", currentValue.value, grade.value, strength.value);
|
||||
}
|
||||
|
||||
function setCurrentValue(value) {
|
||||
if (value === currentValue.value) return;
|
||||
currentValue.value = value ?? "";
|
||||
strengthChange();
|
||||
emit("on-change", currentValue.value, grade.value, strength.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
@closed="resetPicModal"
|
||||
>
|
||||
<ossManage
|
||||
ref="ossManage"
|
||||
ref="ossManageRef"
|
||||
:is-component="true"
|
||||
:initialize="picModalFlag"
|
||||
:max-select="1"
|
||||
@@ -70,110 +70,112 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { nextTick, ref, useAttrs, watch } from "vue";
|
||||
import { View } from "@element-plus/icons-vue";
|
||||
import { uploadFile } from "@/api/index";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
||||
|
||||
export default {
|
||||
name: "uploadPicInput",
|
||||
components: {
|
||||
ossManage,
|
||||
View,
|
||||
defineOptions({ name: "uploadPicInput" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: {
|
||||
default: "default",
|
||||
type: String,
|
||||
},
|
||||
props: {
|
||||
modelValue: String,
|
||||
value: String,
|
||||
size: {
|
||||
default: "default",
|
||||
type: String,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "图片链接",
|
||||
},
|
||||
showInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxlength: Number,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "图片链接",
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change"],
|
||||
data() {
|
||||
return {
|
||||
accessToken: {},
|
||||
currentValue: this.modelValue ?? this.value ?? "",
|
||||
viewImage: false,
|
||||
uploadFileUrl: uploadFile,
|
||||
picModalFlag: false,
|
||||
selectedFormBtnName: "",
|
||||
picIndex: "",
|
||||
};
|
||||
showInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
methods: {
|
||||
handleCLickImg(val, index) {
|
||||
this.picModalFlag = true;
|
||||
this.selectedFormBtnName = val;
|
||||
this.picIndex = index;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ossManage) {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleOssCallback(item) {
|
||||
this.picModalFlag = false;
|
||||
if (!item?.url) return;
|
||||
this.currentValue = item.url;
|
||||
this.picIndex = "";
|
||||
this.emitValue(this.currentValue);
|
||||
},
|
||||
resetPicModal() {
|
||||
this.selectedFormBtnName = "";
|
||||
this.picIndex = "";
|
||||
},
|
||||
init() {
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
emitValue(val) {
|
||||
this.$emit("update:modelValue", val);
|
||||
this.$emit("input", val);
|
||||
this.$emit("on-change", val);
|
||||
},
|
||||
handleChange() {
|
||||
this.emitValue(this.currentValue);
|
||||
this.$attrs.rollback && this.$attrs.rollback();
|
||||
},
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) {
|
||||
return;
|
||||
}
|
||||
this.currentValue = value ?? "";
|
||||
this.emitValue(this.currentValue);
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
maxlength: Number,
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
const attrs = useAttrs();
|
||||
|
||||
const accessToken = ref({});
|
||||
const currentValue = ref(props.modelValue ?? props.value ?? "");
|
||||
const viewImage = ref(false);
|
||||
const uploadFileUrl = ref(uploadFile);
|
||||
const picModalFlag = ref(false);
|
||||
const selectedFormBtnName = ref("");
|
||||
const picIndex = ref("");
|
||||
|
||||
// 模板 ref:ossManage 与 import 组件同名,改名避免互相覆盖
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
function handleCLickImg(val, index) {
|
||||
picModalFlag.value = true;
|
||||
selectedFormBtnName.value = val;
|
||||
picIndex.value = index;
|
||||
nextTick(() => {
|
||||
if (ossManageRef.value) {
|
||||
ossManageRef.value.selectImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleOssCallback(item) {
|
||||
picModalFlag.value = false;
|
||||
if (!item?.url) return;
|
||||
currentValue.value = item.url;
|
||||
picIndex.value = "";
|
||||
emitValue(currentValue.value);
|
||||
}
|
||||
function resetPicModal() {
|
||||
selectedFormBtnName.value = "";
|
||||
picIndex.value = "";
|
||||
}
|
||||
function init() {
|
||||
accessToken.value = {
|
||||
accessToken: getStore("accessToken"),
|
||||
};
|
||||
}
|
||||
function emitValue(val) {
|
||||
emit("update:modelValue", val);
|
||||
emit("input", val);
|
||||
emit("on-change", val);
|
||||
}
|
||||
function handleChange() {
|
||||
emitValue(currentValue.value);
|
||||
attrs.rollback && attrs.rollback();
|
||||
}
|
||||
function setCurrentValue(value) {
|
||||
if (value === currentValue.value) {
|
||||
return;
|
||||
}
|
||||
currentValue.value = value ?? "";
|
||||
emitValue(currentValue.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setCurrentValue(val);
|
||||
}
|
||||
);
|
||||
|
||||
// 原 created 钩子
|
||||
init();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -75,167 +75,167 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { Camera, Delete, View } from "@element-plus/icons-vue";
|
||||
import { uploadFile } from "@/api/index";
|
||||
import { getStore } from "@/libs/storage";
|
||||
import { Message } from "@/utils/message";
|
||||
import vuedraggable from "vuedraggable";
|
||||
|
||||
export default {
|
||||
name: "uploadPicThumb",
|
||||
components: {
|
||||
vuedraggable,
|
||||
Camera,
|
||||
Delete,
|
||||
View,
|
||||
defineOptions({ name: "uploadPicThumb" });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: null },
|
||||
value: { type: null },
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
props: {
|
||||
modelValue: { type: null },
|
||||
value: { type: null },
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
emits: ["update:modelValue", "input", "on-change", "uploadchange"],
|
||||
data() {
|
||||
return {
|
||||
accessToken: {},
|
||||
uploadFileUrl: uploadFile,
|
||||
uploadList: [],
|
||||
viewImage: false,
|
||||
imgUrl: "",
|
||||
};
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
computed: {
|
||||
bindValue() {
|
||||
return this.modelValue !== undefined ? this.modelValue : this.value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
draggableItemKey(item) {
|
||||
return item.url || item.uid || item.name;
|
||||
},
|
||||
onEnd() {
|
||||
this.returnValue();
|
||||
},
|
||||
init() {
|
||||
this.setData(this.bindValue, true);
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
handleView(imgUrl) {
|
||||
this.imgUrl = imgUrl;
|
||||
this.viewImage = true;
|
||||
},
|
||||
handleRemove(file) {
|
||||
this.uploadList = this.uploadList.filter((i) => i.url !== file.url);
|
||||
this.returnValue();
|
||||
},
|
||||
handleSuccess(res, file) {
|
||||
if (res.success) {
|
||||
file.url = res.result;
|
||||
if (!this.multiple && this.uploadList.length > 0) {
|
||||
this.uploadList.splice(0, 1);
|
||||
}
|
||||
this.uploadList.push(file);
|
||||
this.returnValue();
|
||||
} else {
|
||||
this.$Message.error(res.message);
|
||||
}
|
||||
},
|
||||
handleError(error) {
|
||||
this.$Message.error(error.toString());
|
||||
},
|
||||
handleBeforeUpload(file) {
|
||||
const okType = ["image/jpeg", "image/png", "image/gif", "image/jpg"].includes(file.type);
|
||||
if (!okType) {
|
||||
this.$Message.warning("请选择 .jpg .jpeg .png .gif 格式图片");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 > 1024) {
|
||||
this.$Message.warning("所选文件大小过大,不能超过 1M");
|
||||
return false;
|
||||
}
|
||||
if (this.multiple && this.uploadList.length >= this.limit) {
|
||||
this.$Message.warning("最多只能上传" + this.limit + "张图片");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
emitValue(val) {
|
||||
this.$emit("update:modelValue", val);
|
||||
this.$emit("input", val);
|
||||
this.$emit("on-change", val);
|
||||
},
|
||||
returnValue() {
|
||||
if (!this.uploadList || this.uploadList.length < 1) {
|
||||
const empty = this.multiple ? [] : "";
|
||||
this.emitValue(empty);
|
||||
return;
|
||||
}
|
||||
if (!this.multiple) {
|
||||
this.emitValue(this.uploadList[0].url);
|
||||
} else {
|
||||
this.emitValue(this.uploadList.map((e) => e.url));
|
||||
}
|
||||
},
|
||||
setData(v, init) {
|
||||
if (typeof v == "string") {
|
||||
if (this.multiple) {
|
||||
this.$Message.warning("多张上传仅支持数组数据类型");
|
||||
return;
|
||||
}
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.uploadList = [{ url: v, status: "finished" }];
|
||||
this.$emit("uploadchange", v);
|
||||
this.emitValue(v);
|
||||
} else if (typeof v == "object" && v) {
|
||||
if (!this.multiple) {
|
||||
this.$Message.warning("单张上传仅支持字符串数据类型");
|
||||
return;
|
||||
}
|
||||
this.uploadList = [];
|
||||
const list = v.length > this.limit ? v.slice(0, this.limit) : v;
|
||||
if (v.length > this.limit) {
|
||||
this.$Message.warning("最多只能上传" + this.limit + "张图片");
|
||||
}
|
||||
list.forEach((e) => {
|
||||
this.uploadList.push({
|
||||
status: "finished",
|
||||
...(typeof e === "string" ? { url: e } : e),
|
||||
});
|
||||
});
|
||||
if (init) {
|
||||
this.emitValue(list);
|
||||
} else {
|
||||
this.$emit("on-change", list);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.setData(val);
|
||||
},
|
||||
value(val) {
|
||||
this.setData(val);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change", "uploadchange"]);
|
||||
|
||||
const accessToken = ref({});
|
||||
const uploadFileUrl = uploadFile;
|
||||
const uploadList = ref([]);
|
||||
const viewImage = ref(false);
|
||||
const imgUrl = ref("");
|
||||
|
||||
// el-upload 模板 ref
|
||||
const upload = ref(null);
|
||||
|
||||
const bindValue = computed(() => {
|
||||
return props.modelValue !== undefined ? props.modelValue : props.value;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
setData(val);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
setData(val);
|
||||
}
|
||||
);
|
||||
|
||||
function draggableItemKey(item) {
|
||||
return item.url || item.uid || item.name;
|
||||
}
|
||||
function onEnd() {
|
||||
returnValue();
|
||||
}
|
||||
function init() {
|
||||
setData(bindValue.value, true);
|
||||
accessToken.value = {
|
||||
accessToken: getStore("accessToken"),
|
||||
};
|
||||
}
|
||||
function handleView(url) {
|
||||
imgUrl.value = url;
|
||||
viewImage.value = true;
|
||||
}
|
||||
function handleRemove(file) {
|
||||
uploadList.value = uploadList.value.filter((i) => i.url !== file.url);
|
||||
returnValue();
|
||||
}
|
||||
function handleSuccess(res, file) {
|
||||
if (res.success) {
|
||||
file.url = res.result;
|
||||
if (!props.multiple && uploadList.value.length > 0) {
|
||||
uploadList.value.splice(0, 1);
|
||||
}
|
||||
uploadList.value.push(file);
|
||||
returnValue();
|
||||
} else {
|
||||
Message.error(res.message);
|
||||
}
|
||||
}
|
||||
function handleError(error) {
|
||||
Message.error(error.toString());
|
||||
}
|
||||
function handleBeforeUpload(file) {
|
||||
const okType = ["image/jpeg", "image/png", "image/gif", "image/jpg"].includes(file.type);
|
||||
if (!okType) {
|
||||
Message.warning("请选择 .jpg .jpeg .png .gif 格式图片");
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 > 1024) {
|
||||
Message.warning("所选文件大小过大,不能超过 1M");
|
||||
return false;
|
||||
}
|
||||
if (props.multiple && uploadList.value.length >= props.limit) {
|
||||
Message.warning("最多只能上传" + props.limit + "张图片");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function emitValue(val) {
|
||||
emit("update:modelValue", val);
|
||||
emit("input", val);
|
||||
emit("on-change", val);
|
||||
}
|
||||
function returnValue() {
|
||||
if (!uploadList.value || uploadList.value.length < 1) {
|
||||
const empty = props.multiple ? [] : "";
|
||||
emitValue(empty);
|
||||
return;
|
||||
}
|
||||
if (!props.multiple) {
|
||||
emitValue(uploadList.value[0].url);
|
||||
} else {
|
||||
emitValue(uploadList.value.map((e) => e.url));
|
||||
}
|
||||
}
|
||||
function setData(v, init) {
|
||||
if (typeof v == "string") {
|
||||
if (props.multiple) {
|
||||
Message.warning("多张上传仅支持数组数据类型");
|
||||
return;
|
||||
}
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
uploadList.value = [{ url: v, status: "finished" }];
|
||||
emit("uploadchange", v);
|
||||
emitValue(v);
|
||||
} else if (typeof v == "object" && v) {
|
||||
if (!props.multiple) {
|
||||
Message.warning("单张上传仅支持字符串数据类型");
|
||||
return;
|
||||
}
|
||||
uploadList.value = [];
|
||||
const list = v.length > props.limit ? v.slice(0, props.limit) : v;
|
||||
if (v.length > props.limit) {
|
||||
Message.warning("最多只能上传" + props.limit + "张图片");
|
||||
}
|
||||
list.forEach((e) => {
|
||||
uploadList.value.push({
|
||||
status: "finished",
|
||||
...(typeof e === "string" ? { url: e } : e),
|
||||
});
|
||||
});
|
||||
if (init) {
|
||||
emitValue(list);
|
||||
} else {
|
||||
emit("on-change", list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -19,132 +19,129 @@
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import AMapLoader from "@amap/amap-jsapi-loader";
|
||||
import { getRegion } from "@/api/common.js";
|
||||
import config from "@/config/index";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
name: "map",
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
showMap: false, // 地图显隐
|
||||
mapSearch: "", // 地图搜索
|
||||
map: null, // 初始化地图
|
||||
autoComplete: null, // 初始化搜索方法
|
||||
geocoder: null, // 初始化地理、坐标转化
|
||||
positionPicker: null, // 地图拖拽选点
|
||||
tips: [], //搜索关键字列表
|
||||
addrContent: {}, // 回显地址信息
|
||||
loading: false, // 加载状态
|
||||
defineOptions({ name: "map" });
|
||||
|
||||
const emit = defineEmits(["getAddress"]);
|
||||
|
||||
const showMap = ref(false); // 地图显隐
|
||||
const mapSearch = ref(""); // 地图搜索
|
||||
const map = ref(null); // 初始化地图
|
||||
const autoComplete = ref(null); // 初始化搜索方法
|
||||
const geocoder = ref(null); // 初始化地理、坐标转化
|
||||
const positionPicker = ref(null); // 地图拖拽选点
|
||||
const tips = ref([]); //搜索关键字列表
|
||||
const addrContent = ref({}); // 回显地址信息
|
||||
const loading = ref(false); // 加载状态
|
||||
|
||||
watch(mapSearch, (val) => {
|
||||
searchOfMap(val);
|
||||
});
|
||||
|
||||
function ok() {
|
||||
|
||||
if (addrContent.value && addrContent.value.regeocode) {
|
||||
const params = {
|
||||
cityCode: addrContent.value.regeocode.addressComponent.citycode,
|
||||
townName: addrContent.value.regeocode.addressComponent.township,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mapSearch: function (val) {
|
||||
this.searchOfMap(val);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
ok() {
|
||||
getRegion(params).then((res) => {
|
||||
if (res.success) {
|
||||
addrContent.value.addr = res.result.name.replace(/,/g, " ");
|
||||
addrContent.value.addrId = res.result.id;
|
||||
loading.value = false;
|
||||
|
||||
if (this.addrContent && this.addrContent.regeocode) {
|
||||
const params = {
|
||||
cityCode: this.addrContent.regeocode.addressComponent.citycode,
|
||||
townName: this.addrContent.regeocode.addressComponent.township,
|
||||
};
|
||||
getRegion(params).then((res) => {
|
||||
if (res.success) {
|
||||
this.addrContent.addr = res.result.name.replace(/,/g, " ");
|
||||
this.addrContent.addrId = res.result.id;
|
||||
this.loading = false;
|
||||
|
||||
this.$emit("getAddress", this.addrContent);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$Message.error('未获取到坐标信息!请查看高德API配置是否正确')
|
||||
emit("getAddress", addrContent.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Message.error('未获取到坐标信息!请查看高德API配置是否正确')
|
||||
}
|
||||
|
||||
}
|
||||
function init() {
|
||||
AMapLoader.load({
|
||||
key: config.aMapKey, // 申请好的Web端开发者Key,首次调用 load 时必填
|
||||
version: "", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
||||
plugins: [
|
||||
"AMap.ToolBar",
|
||||
"AMap.Autocomplete",
|
||||
"AMap.PlaceSearch",
|
||||
"AMap.Geolocation",
|
||||
"AMap.Geocoder",
|
||||
], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
|
||||
AMapUI: {
|
||||
// 是否加载 AMapUI,缺省不加载
|
||||
version: "1.1", // AMapUI 缺省 1.1
|
||||
plugins: ["misc/PositionPicker"], // 需要加载的 AMapUI ui插件
|
||||
},
|
||||
init() {
|
||||
AMapLoader.load({
|
||||
key: this.config.aMapKey, // 申请好的Web端开发者Key,首次调用 load 时必填
|
||||
version: "", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
||||
plugins: [
|
||||
"AMap.ToolBar",
|
||||
"AMap.Autocomplete",
|
||||
"AMap.PlaceSearch",
|
||||
"AMap.Geolocation",
|
||||
"AMap.Geocoder",
|
||||
], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
|
||||
AMapUI: {
|
||||
// 是否加载 AMapUI,缺省不加载
|
||||
version: "1.1", // AMapUI 缺省 1.1
|
||||
plugins: ["misc/PositionPicker"], // 需要加载的 AMapUI ui插件
|
||||
},
|
||||
})
|
||||
.then((AMap) => {
|
||||
let that = this;
|
||||
this.map = new AMap.Map("map-container", {
|
||||
zoom: 12,
|
||||
});
|
||||
that.map.addControl(new AMap.ToolBar());
|
||||
that.map.addControl(new AMap.Autocomplete());
|
||||
that.map.addControl(new AMap.PlaceSearch());
|
||||
that.map.addControl(new AMap.Geocoder());
|
||||
|
||||
// 实例化Autocomplete
|
||||
let autoOptions = {
|
||||
city: "全国",
|
||||
};
|
||||
that.autoComplete = new AMap.Autocomplete(autoOptions); // 搜索
|
||||
that.geocoder = new AMap.Geocoder(autoOptions);
|
||||
|
||||
that.positionPicker = new AMapUI.PositionPicker({
|
||||
// 拖拽选点
|
||||
mode: "dragMap",
|
||||
map: that.map,
|
||||
});
|
||||
that.positionPicker.start();
|
||||
/**
|
||||
*
|
||||
* 所有回显数据,都在positionResult里面
|
||||
* 需要字段可以查找
|
||||
*
|
||||
*/
|
||||
that.positionPicker.on("success", function (positionResult) {
|
||||
that.addrContent = positionResult;
|
||||
});
|
||||
})
|
||||
.catch((e) => { });
|
||||
},
|
||||
searchOfMap(val) {
|
||||
// 地图搜索
|
||||
let that = this;
|
||||
this.autoComplete.search(val, function (status, result) {
|
||||
// 搜索成功时,result即是对应的匹配数据
|
||||
if (status == "complete" && result.info == "OK") {
|
||||
that.tips = result.tips;
|
||||
} else {
|
||||
that.tips = [];
|
||||
}
|
||||
})
|
||||
.then((AMap) => {
|
||||
map.value = new AMap.Map("map-container", {
|
||||
zoom: 12,
|
||||
});
|
||||
},
|
||||
selectAddr(location) {
|
||||
// 选择坐标
|
||||
if (!location) {
|
||||
this.$Message.warning("请选择正确点位");
|
||||
return false;
|
||||
}
|
||||
const lnglat = [location.lng, location.lat];
|
||||
this.positionPicker.start(lnglat);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
map.value.addControl(new AMap.ToolBar());
|
||||
map.value.addControl(new AMap.Autocomplete());
|
||||
map.value.addControl(new AMap.PlaceSearch());
|
||||
map.value.addControl(new AMap.Geocoder());
|
||||
|
||||
// 实例化Autocomplete
|
||||
let autoOptions = {
|
||||
city: "全国",
|
||||
};
|
||||
autoComplete.value = new AMap.Autocomplete(autoOptions); // 搜索
|
||||
geocoder.value = new AMap.Geocoder(autoOptions);
|
||||
|
||||
positionPicker.value = new AMapUI.PositionPicker({
|
||||
// 拖拽选点
|
||||
mode: "dragMap",
|
||||
map: map.value,
|
||||
});
|
||||
positionPicker.value.start();
|
||||
/**
|
||||
*
|
||||
* 所有回显数据,都在positionResult里面
|
||||
* 需要字段可以查找
|
||||
*
|
||||
*/
|
||||
positionPicker.value.on("success", function (positionResult) {
|
||||
addrContent.value = positionResult;
|
||||
});
|
||||
})
|
||||
.catch((e) => { });
|
||||
}
|
||||
function searchOfMap(val) {
|
||||
// 地图搜索
|
||||
autoComplete.value.search(val, function (status, result) {
|
||||
// 搜索成功时,result即是对应的匹配数据
|
||||
if (status == "complete" && result.info == "OK") {
|
||||
tips.value = result.tips;
|
||||
} else {
|
||||
tips.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
function selectAddr(location) {
|
||||
// 选择坐标
|
||||
if (!location) {
|
||||
Message.warning("请选择正确点位");
|
||||
return false;
|
||||
}
|
||||
const lnglat = [location.lng, location.lat];
|
||||
positionPicker.value.start(lnglat);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
// 唯一引用方 multiple-map.vue 通过 ref 引用本组件但从不访问任何成员(仅监听 getAddress 事件),无需暴露成员
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
#map-container {
|
||||
|
||||
@@ -40,85 +40,87 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { aMapSwitch } from "@/config/index";
|
||||
import mapping from "@/components/map/index.vue";
|
||||
import * as API_Setup from "@/api/common.js";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
components: { mapping },
|
||||
data() {
|
||||
return {
|
||||
aMapSwitch,
|
||||
enableMap: false,
|
||||
mapDefault: "select",
|
||||
data: {
|
||||
province: [],
|
||||
city: [],
|
||||
area: [],
|
||||
street: [],
|
||||
},
|
||||
chiosend: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chiosend = new Array(4).fill("");
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.enableMap = true;
|
||||
this.init({ id: 0 }, "province");
|
||||
},
|
||||
changeMap(val) {
|
||||
this.mapDefault = val;
|
||||
},
|
||||
init(val, level = "province", index) {
|
||||
if (level == 0) {
|
||||
this.chiosend.splice(3, 1, val);
|
||||
} else {
|
||||
API_Setup.getChildRegion(val.id).then((res) => {
|
||||
if (res.result.length && val.id !== 0) {
|
||||
this.chiosend[index] = val;
|
||||
} else if (!res.result.length) {
|
||||
this.chiosend[index] = val;
|
||||
}
|
||||
this.data[level] = res.result;
|
||||
if (level == "city") {
|
||||
this.data.area = [];
|
||||
this.data.street = [];
|
||||
this.chiosend.splice(1, 3, "", "", "");
|
||||
}
|
||||
if (level == "area") {
|
||||
this.data.street = [];
|
||||
this.chiosend.splice(2, 2, "", "");
|
||||
}
|
||||
if (level == "street") {
|
||||
this.chiosend.splice(3, 1, "");
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["callback"]);
|
||||
|
||||
const enableMap = ref(false);
|
||||
const mapDefault = ref("select");
|
||||
const data = reactive({
|
||||
province: [],
|
||||
city: [],
|
||||
area: [],
|
||||
street: [],
|
||||
});
|
||||
const chiosend = ref([]);
|
||||
|
||||
// 模板 ref,对应高德地图子组件
|
||||
const map = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
chiosend.value = new Array(4).fill("");
|
||||
});
|
||||
|
||||
function open() {
|
||||
enableMap.value = true;
|
||||
init({ id: 0 }, "province");
|
||||
}
|
||||
function changeMap(val) {
|
||||
mapDefault.value = val;
|
||||
}
|
||||
function init(val, level = "province", index) {
|
||||
if (level == 0) {
|
||||
chiosend.value.splice(3, 1, val);
|
||||
} else {
|
||||
API_Setup.getChildRegion(val.id).then((res) => {
|
||||
if (res.result.length && val.id !== 0) {
|
||||
chiosend.value[index] = val;
|
||||
} else if (!res.result.length) {
|
||||
chiosend.value[index] = val;
|
||||
}
|
||||
},
|
||||
getAddress(center) {
|
||||
this.$emit("callback", {
|
||||
type: this.mapDefault,
|
||||
data: center,
|
||||
});
|
||||
this.enableMap = false;
|
||||
},
|
||||
finished() {
|
||||
if (!this.chiosend[0]) {
|
||||
this.$Message.error("请选择地址");
|
||||
return;
|
||||
data[level] = res.result;
|
||||
if (level == "city") {
|
||||
data.area = [];
|
||||
data.street = [];
|
||||
chiosend.value.splice(1, 3, "", "", "");
|
||||
}
|
||||
const params = this.chiosend.filter((item) => item !== "" && item.value !== "");
|
||||
this.enableMap = false;
|
||||
this.$emit("callback", {
|
||||
type: this.mapDefault,
|
||||
data: params,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
if (level == "area") {
|
||||
data.street = [];
|
||||
chiosend.value.splice(2, 2, "", "");
|
||||
}
|
||||
if (level == "street") {
|
||||
chiosend.value.splice(3, 1, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function getAddress(center) {
|
||||
emit("callback", {
|
||||
type: mapDefault.value,
|
||||
data: center,
|
||||
});
|
||||
enableMap.value = false;
|
||||
}
|
||||
function finished() {
|
||||
if (!chiosend.value[0]) {
|
||||
Message.error("请选择地址");
|
||||
return;
|
||||
}
|
||||
const params = chiosend.value.filter((item) => item !== "" && item.value !== "");
|
||||
enableMap.value = false;
|
||||
emit("callback", {
|
||||
type: mapDefault.value,
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
// 父组件(member/index、memberRecycle、memberDetail、orderDetail、shopOperation)通过 ref 调用 open() 打开弹窗
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -5,54 +5,56 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, getCurrentInstance } from "vue";
|
||||
import { unitPrice } from "@/utils/filters";
|
||||
|
||||
export default {
|
||||
name: "priceColorScheme",
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
validator(val) {
|
||||
return (
|
||||
val === null ||
|
||||
val === undefined ||
|
||||
typeof val === "number" ||
|
||||
typeof val === "string"
|
||||
);
|
||||
},
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: "¥",
|
||||
},
|
||||
dot: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
defineOptions({ name: "priceColorScheme" });
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
default: 0,
|
||||
validator(val) {
|
||||
return (
|
||||
val === null ||
|
||||
val === undefined ||
|
||||
typeof val === "number" ||
|
||||
typeof val === "string"
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
displayText() {
|
||||
const val = this.value;
|
||||
if (val === null || val === undefined || val === "" || val === "null") {
|
||||
return `${this.unit || "¥"}0.00`;
|
||||
}
|
||||
return unitPrice(val, this.unit);
|
||||
},
|
||||
priceStyle() {
|
||||
const resolvedColor = this.color || this.$mainColor || "";
|
||||
return resolvedColor
|
||||
? { color: resolvedColor, ...this.customStyle }
|
||||
: { ...this.customStyle };
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: "¥",
|
||||
},
|
||||
};
|
||||
dot: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
// $mainColor 为 main.js 注册的全局属性
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const displayText = computed(() => {
|
||||
const val = props.value;
|
||||
if (val === null || val === undefined || val === "" || val === "null") {
|
||||
return `${props.unit || "¥"}0.00`;
|
||||
}
|
||||
return unitPrice(val, props.unit);
|
||||
});
|
||||
const priceStyle = computed(() => {
|
||||
const resolvedColor = props.color || proxy.$mainColor || "";
|
||||
return resolvedColor
|
||||
? { color: resolvedColor, ...props.customStyle }
|
||||
: { ...props.customStyle };
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -10,140 +10,140 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { getChildRegion } from "@/api/common.js";
|
||||
|
||||
export default {
|
||||
props: ["addressId"],
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
addr: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
cascaderProps() {
|
||||
return {
|
||||
value: "value",
|
||||
label: "label",
|
||||
lazy: true,
|
||||
lazyLoad: this.loadData,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
change(val) {
|
||||
if (!val || !val.length) {
|
||||
this.$emit("selected", [[], []]);
|
||||
return;
|
||||
}
|
||||
const labels = this.resolveLabels(val);
|
||||
this.$emit("selected", [val, labels]);
|
||||
},
|
||||
resolveLabels(values) {
|
||||
const labels = [];
|
||||
let options = this.data;
|
||||
for (const v of values) {
|
||||
const node = options.find((item) => item.value === v);
|
||||
if (!node) break;
|
||||
labels.push(node.label);
|
||||
options = node.children || [];
|
||||
}
|
||||
return labels;
|
||||
},
|
||||
loadData(node, resolve) {
|
||||
const parentId = node.level === 0 ? 0 : node.value;
|
||||
getChildRegion(parentId).then((res) => {
|
||||
if (!res.result || res.result.length <= 0) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
const nodes = res.result.map((child) => {
|
||||
const isLeaf =
|
||||
child.level === "street" ||
|
||||
node.label === "香港特别行政区" ||
|
||||
child.name === "台湾省";
|
||||
return {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
leaf: isLeaf,
|
||||
};
|
||||
});
|
||||
resolve(nodes);
|
||||
});
|
||||
},
|
||||
async init() {
|
||||
const data = await getChildRegion(0);
|
||||
this.data = data.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
leaf: item.name === "台湾省",
|
||||
}));
|
||||
},
|
||||
async reviewData() {
|
||||
const addr = JSON.parse(JSON.stringify(this.addressId.split(",")));
|
||||
const length = addr.length;
|
||||
const root = await getChildRegion(0);
|
||||
const arr0 = root.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
leaf: item.name === "台湾省",
|
||||
children: [],
|
||||
}));
|
||||
const props = defineProps(["addressId"]);
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
if (length > 0) {
|
||||
const children = await getChildRegion(addr[0]);
|
||||
const arr1 = this.handleData(children.result);
|
||||
arr0.forEach((e) => {
|
||||
if (e.value === addr[0]) {
|
||||
e.children = arr1;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (length > 1) {
|
||||
let arr1 = arr0.find((e) => e.value === addr[0])?.children || [];
|
||||
const children = await getChildRegion(addr[1]);
|
||||
const arr2 = this.handleData(children.result);
|
||||
arr1.forEach((e) => {
|
||||
if (e.value === addr[1]) {
|
||||
e.children = arr2;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (length > 2) {
|
||||
const arr1 = arr0.find((e) => e.value === addr[0])?.children || [];
|
||||
const arr2 = arr1.find((e) => e.value === addr[1])?.children || [];
|
||||
const children = await getChildRegion(addr[2]);
|
||||
const arr3 = this.handleData(children.result);
|
||||
arr2.forEach((e) => {
|
||||
if (e.value === addr[2]) {
|
||||
e.children = arr3;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.data = arr0;
|
||||
this.addr = addr;
|
||||
},
|
||||
handleData(data) {
|
||||
return data.map((child) => ({
|
||||
const data = ref([]);
|
||||
const addr = ref([]);
|
||||
|
||||
const cascaderProps = computed(() => {
|
||||
return {
|
||||
value: "value",
|
||||
label: "label",
|
||||
lazy: true,
|
||||
lazyLoad: loadData,
|
||||
};
|
||||
});
|
||||
|
||||
function change(val) {
|
||||
if (!val || !val.length) {
|
||||
emit("selected", [[], []]);
|
||||
return;
|
||||
}
|
||||
const labels = resolveLabels(val);
|
||||
emit("selected", [val, labels]);
|
||||
}
|
||||
|
||||
function resolveLabels(values) {
|
||||
const labels = [];
|
||||
let options = data.value;
|
||||
for (const v of values) {
|
||||
const node = options.find((item) => item.value === v);
|
||||
if (!node) break;
|
||||
labels.push(node.label);
|
||||
options = node.children || [];
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
function loadData(node, resolve) {
|
||||
const parentId = node.level === 0 ? 0 : node.value;
|
||||
getChildRegion(parentId).then((res) => {
|
||||
if (!res.result || res.result.length <= 0) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
const nodes = res.result.map((child) => {
|
||||
const isLeaf =
|
||||
child.level === "street" ||
|
||||
node.label === "香港特别行政区" ||
|
||||
child.name === "台湾省";
|
||||
return {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
leaf: child.level === "street",
|
||||
children: child.level === "street" ? undefined : [],
|
||||
}));
|
||||
},
|
||||
leaf: isLeaf,
|
||||
};
|
||||
});
|
||||
resolve(nodes);
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const res = await getChildRegion(0);
|
||||
data.value = res.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
leaf: item.name === "台湾省",
|
||||
}));
|
||||
}
|
||||
|
||||
async function reviewData() {
|
||||
const addrArr = JSON.parse(JSON.stringify(props.addressId.split(",")));
|
||||
const length = addrArr.length;
|
||||
const root = await getChildRegion(0);
|
||||
const arr0 = root.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
leaf: item.name === "台湾省",
|
||||
children: [],
|
||||
}));
|
||||
|
||||
if (length > 0) {
|
||||
const children = await getChildRegion(addrArr[0]);
|
||||
const arr1 = handleData(children.result);
|
||||
arr0.forEach((e) => {
|
||||
if (e.value === addrArr[0]) {
|
||||
e.children = arr1;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (length > 1) {
|
||||
let arr1 = arr0.find((e) => e.value === addrArr[0])?.children || [];
|
||||
const children = await getChildRegion(addrArr[1]);
|
||||
const arr2 = handleData(children.result);
|
||||
arr1.forEach((e) => {
|
||||
if (e.value === addrArr[1]) {
|
||||
e.children = arr2;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (length > 2) {
|
||||
const arr1 = arr0.find((e) => e.value === addrArr[0])?.children || [];
|
||||
const arr2 = arr1.find((e) => e.value === addrArr[1])?.children || [];
|
||||
const children = await getChildRegion(addrArr[2]);
|
||||
const arr3 = handleData(children.result);
|
||||
arr2.forEach((e) => {
|
||||
if (e.value === addrArr[2]) {
|
||||
e.children = arr3;
|
||||
}
|
||||
});
|
||||
}
|
||||
data.value = arr0;
|
||||
addr.value = addrArr;
|
||||
}
|
||||
|
||||
function handleData(list) {
|
||||
return list.map((child) => ({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
leaf: child.level === "street",
|
||||
children: child.level === "street" ? undefined : [],
|
||||
}));
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.addressId,
|
||||
(v) => {
|
||||
if (v) {
|
||||
reviewData();
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
addressId: {
|
||||
handler(v) {
|
||||
if (v) {
|
||||
this.reviewData();
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -37,107 +37,106 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { Refresh, DArrowRight } from "@element-plus/icons-vue";
|
||||
import { getVerifyImg, postVerifyImg } from "./verify.js";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
export default {
|
||||
components: { Refresh, DArrowRight },
|
||||
props: {
|
||||
verifyType: {
|
||||
default: "LOGIN",
|
||||
type: String,
|
||||
},
|
||||
const props = defineProps({
|
||||
verifyType: {
|
||||
default: "LOGIN",
|
||||
type: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
type: "LOGIN",
|
||||
data: {
|
||||
backImage: "",
|
||||
slidingImage: "",
|
||||
originalHeight: 150,
|
||||
originalWidth: 300,
|
||||
sliderWidth: 60,
|
||||
sliderHeight: 60,
|
||||
},
|
||||
distance: 0,
|
||||
flag: false,
|
||||
downX: 0,
|
||||
bgColor: "#04ad11",
|
||||
verifyText: "拖动滑块解锁",
|
||||
};
|
||||
});
|
||||
const emit = defineEmits(["change"]);
|
||||
|
||||
const show = ref(false);
|
||||
const type = ref("LOGIN");
|
||||
const data = ref({
|
||||
backImage: "",
|
||||
slidingImage: "",
|
||||
originalHeight: 150,
|
||||
originalWidth: 300,
|
||||
sliderWidth: 60,
|
||||
sliderHeight: 60,
|
||||
});
|
||||
const distance = ref(0);
|
||||
const flag = ref(false);
|
||||
const downX = ref(0);
|
||||
const bgColor = ref("#04ad11");
|
||||
const verifyText = ref("拖动滑块解锁");
|
||||
|
||||
watch(
|
||||
() => props.verifyType,
|
||||
(v) => {
|
||||
type.value = v;
|
||||
},
|
||||
methods: {
|
||||
mouseDown(e) {
|
||||
this.downX = e.clientX;
|
||||
this.flag = true;
|
||||
},
|
||||
mouseMove(e) {
|
||||
if (this.flag) {
|
||||
const offset = e.clientX - this.downX;
|
||||
if (offset > this.data.originalWidth - 43) {
|
||||
this.distance = this.data.originalWidth - 43;
|
||||
} else if (offset < 0) {
|
||||
this.distance = 0;
|
||||
} else {
|
||||
this.distance = offset;
|
||||
}
|
||||
}
|
||||
},
|
||||
mouseUp() {
|
||||
if (!this.flag) return false;
|
||||
this.flag = false;
|
||||
const params = {
|
||||
verificationEnums: this.type,
|
||||
xPos: this.distance,
|
||||
};
|
||||
postVerifyImg(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
if (res.result) {
|
||||
this.bgColor = "green";
|
||||
this.verifyText = "解锁成功";
|
||||
this.$emit("change", { status: true, distance: this.distance });
|
||||
} else {
|
||||
this.bgColor = "red";
|
||||
this.verifyText = "解锁失败";
|
||||
setTimeout(() => this.init(), 1000);
|
||||
this.$emit("change", { status: false, distance: this.distance });
|
||||
}
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.init();
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.flag = false;
|
||||
this.downX = 0;
|
||||
this.distance = 0;
|
||||
this.bgColor = "#04ad11";
|
||||
this.verifyText = "拖动滑块解锁";
|
||||
getVerifyImg(this.type).then((res) => {
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function mouseDown(e) {
|
||||
downX.value = e.clientX;
|
||||
flag.value = true;
|
||||
}
|
||||
function mouseMove(e) {
|
||||
if (flag.value) {
|
||||
const offset = e.clientX - downX.value;
|
||||
if (offset > data.value.originalWidth - 43) {
|
||||
distance.value = data.value.originalWidth - 43;
|
||||
} else if (offset < 0) {
|
||||
distance.value = 0;
|
||||
} else {
|
||||
distance.value = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
function mouseUp() {
|
||||
if (!flag.value) return false;
|
||||
flag.value = false;
|
||||
const params = {
|
||||
verificationEnums: type.value,
|
||||
xPos: distance.value,
|
||||
};
|
||||
postVerifyImg(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
if (res.result) {
|
||||
this.data = res.result;
|
||||
this.show = true;
|
||||
bgColor.value = "green";
|
||||
verifyText.value = "解锁成功";
|
||||
emit("change", { status: true, distance: distance.value });
|
||||
} else {
|
||||
this.$Message.warning("请求失败请重试!");
|
||||
bgColor.value = "red";
|
||||
verifyText.value = "解锁失败";
|
||||
setTimeout(() => init(), 1000);
|
||||
emit("change", { status: false, distance: distance.value });
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
verifyType: {
|
||||
immediate: true,
|
||||
handler(v) {
|
||||
this.type = v;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
init();
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
flag.value = false;
|
||||
downX.value = 0;
|
||||
distance.value = 0;
|
||||
bgColor.value = "#04ad11";
|
||||
verifyText.value = "拖动滑块解锁";
|
||||
getVerifyImg(type.value).then((res) => {
|
||||
if (res.result) {
|
||||
data.value = res.result;
|
||||
show.value = true;
|
||||
} else {
|
||||
Message.warning("请求失败请重试!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// login.vue 通过 verifyRef.value.show = false 关闭、verifyRef.value.init() 打开
|
||||
defineExpose({ show, init });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import lazyLoading from "./lazyLoading.js";
|
||||
import lazyLoading from "./lazyLoading.js";
|
||||
import { getRouter } from "./router-holder";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
let util = {};
|
||||
@@ -176,22 +177,21 @@ util.oneOf = function(ele, targetArr) {
|
||||
};
|
||||
|
||||
// 打开新的页面
|
||||
util.openNewPage = function(vm, name, argu, query) {
|
||||
if (!vm.$store) {
|
||||
return;
|
||||
}
|
||||
let pageOpenedList = vm.$store.state.app.pageOpenedList;
|
||||
const currentPath = vm.$route ? vm.$route.path : "";
|
||||
util.openNewPage = function(name, argu, query) {
|
||||
const appStore = useAppStore();
|
||||
let pageOpenedList = appStore.pageOpenedList;
|
||||
const router = getRouter();
|
||||
const currentPath = router ? router.currentRoute.value.path : "";
|
||||
const openedIndex = util.findOpenedPageIndex(pageOpenedList, name, currentPath);
|
||||
if (openedIndex >= 0) {
|
||||
vm.$store.commit("pageOpenedList", {
|
||||
appStore.updateOpenedList({
|
||||
index: openedIndex,
|
||||
argu: argu,
|
||||
query: query
|
||||
});
|
||||
return;
|
||||
}
|
||||
let tag = vm.$store.state.app.tagsList.filter(item => {
|
||||
let tag = appStore.tagsList.filter(item => {
|
||||
if (item.children) {
|
||||
return name == item.children[0].name;
|
||||
} else {
|
||||
@@ -200,7 +200,7 @@ util.openNewPage = function(vm, name, argu, query) {
|
||||
});
|
||||
tag = tag[0];
|
||||
if (!tag) {
|
||||
tag = vm.$store.state.app.tagsList.find((item) => {
|
||||
tag = appStore.tagsList.find((item) => {
|
||||
const candidate = item.children ? item.children[0] : item;
|
||||
return util.isSameRoutePage(candidate, { name, path: currentPath });
|
||||
});
|
||||
@@ -217,7 +217,7 @@ util.openNewPage = function(vm, name, argu, query) {
|
||||
if (query) {
|
||||
tagObj.query = query;
|
||||
}
|
||||
vm.$store.commit("increateTag", tagObj);
|
||||
appStore.increateTag(tagObj);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -245,8 +245,9 @@ util.toDefaultPage = function(routers, name, route, next) {
|
||||
}
|
||||
};
|
||||
|
||||
util.initRouter = function(vm) {
|
||||
util.initRouter = function() {
|
||||
// 初始化路由
|
||||
const appStore = useAppStore();
|
||||
const constRoutes = [];
|
||||
const otherRoutes = [];
|
||||
|
||||
@@ -267,7 +268,7 @@ util.initRouter = function(vm) {
|
||||
// 未登录
|
||||
return;
|
||||
}
|
||||
if (!vm.$store.state.app.added) {
|
||||
if (!appStore.added) {
|
||||
// 第一次加载 读取数据
|
||||
// 加载菜单
|
||||
import("@/api/index").then(({ getCurrentPermissionList }) => {
|
||||
@@ -293,26 +294,25 @@ util.initRouter = function(vm) {
|
||||
}
|
||||
util.initAllMenuData(constRoutes, menuData);
|
||||
util.registerDynamicRoutes(menuData);
|
||||
vm.$store.commit(
|
||||
"updateAppRouter",
|
||||
appStore.updateAppRouter(
|
||||
constRoutes.filter((item) => item.children && item.children.length > 0)
|
||||
);
|
||||
util.initMenuData(vm, menuData);
|
||||
util.initMenuData(menuData);
|
||||
// 缓存数据 修改加载标识
|
||||
window.localStorage.setItem("menuData", JSON.stringify(menuData));
|
||||
vm.$store.commit("setAdded", true);
|
||||
appStore.setAdded(true);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 读取缓存数据
|
||||
let data = window.localStorage.getItem("menuData");
|
||||
if (!data) {
|
||||
vm.$store.commit("setAdded", false);
|
||||
appStore.setAdded(false);
|
||||
return;
|
||||
}
|
||||
let menuData = JSON.parse(data);
|
||||
util.registerDynamicRoutes(menuData);
|
||||
util.initMenuData(vm, menuData);
|
||||
util.initMenuData(menuData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -330,7 +330,8 @@ util.initAllMenuData = function(constRoutes, data) {
|
||||
};
|
||||
|
||||
// 生成菜单格式数据
|
||||
util.initMenuData = function(vm, data) {
|
||||
util.initMenuData = function(data) {
|
||||
const appStore = useAppStore();
|
||||
const menuRoutes = [];
|
||||
let menuData = data;
|
||||
// 顶部菜单 —— 首页固定置顶
|
||||
@@ -345,26 +346,26 @@ util.initMenuData = function(vm, data) {
|
||||
if (navList.length < 1) {
|
||||
return;
|
||||
}
|
||||
// 存入vuex
|
||||
vm.$store.commit("setNavList", navList);
|
||||
// 存入pinia
|
||||
appStore.setNavList(navList);
|
||||
let currNav = window.localStorage.getItem("currNav");
|
||||
if (currNav) {
|
||||
// 读取缓存title
|
||||
for (var item of navList) {
|
||||
if (item.name == currNav) {
|
||||
vm.$store.commit("setCurrNavTitle", item.title);
|
||||
appStore.setCurrNavTitle(item.title);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 默认首页
|
||||
currNav = 'home_index';
|
||||
vm.$store.commit("setCurrNavTitle", '首页');
|
||||
appStore.setCurrNavTitle('首页');
|
||||
}
|
||||
vm.$store.commit("setCurrNav", currNav);
|
||||
appStore.setCurrNav(currNav);
|
||||
// 首页不需要二级菜单
|
||||
if (currNav === 'home_index') {
|
||||
vm.$store.commit("updateMenulist", []);
|
||||
appStore.updateMenulist([]);
|
||||
return;
|
||||
}
|
||||
for (let item of menuData) {
|
||||
@@ -376,20 +377,19 @@ util.initMenuData = function(vm, data) {
|
||||
}
|
||||
util.initRouterNode(menuRoutes, menuData);
|
||||
// 刷新界面菜单
|
||||
vm.$store.commit(
|
||||
"updateMenulist",
|
||||
appStore.updateMenulist(
|
||||
menuRoutes.filter(item => item.children.length > 0)
|
||||
);
|
||||
|
||||
let tagsList = [];
|
||||
vm.$store.state.app.routers.map(item => {
|
||||
appStore.routers.map(item => {
|
||||
if (item.children.length <= 1) {
|
||||
tagsList.push(item.children[0]);
|
||||
} else {
|
||||
tagsList.push(...item.children);
|
||||
}
|
||||
});
|
||||
vm.$store.commit("setTagsList", tagsList);
|
||||
appStore.setTagsList(tagsList);
|
||||
};
|
||||
|
||||
// 生成路由节点
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import "core-js/stable";
|
||||
import "./styles/theme.less";
|
||||
import "@/styles/common.scss";
|
||||
import App from "./App.vue";
|
||||
import { router } from "./router/index";
|
||||
import store from "./store";
|
||||
import i18n from "@/locale";
|
||||
import { setupElementPlus } from "@/plugins/element";
|
||||
import { setupLegacyMessage } from "@/utils/message";
|
||||
@@ -46,7 +46,7 @@ setupLegacyMessage(app);
|
||||
installVueQr(app);
|
||||
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
app.use(createPinia());
|
||||
app.use(i18n);
|
||||
|
||||
app.component("liliDialog", liliDialog);
|
||||
|
||||
@@ -4,7 +4,6 @@ import "nprogress/nprogress.css";
|
||||
import Util from "../libs/util";
|
||||
import { setRouter } from "../libs/router-holder";
|
||||
import Cookies from "js-cookie";
|
||||
import store from "@/store";
|
||||
import { routers } from "./router";
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
@@ -43,7 +42,7 @@ router.beforeEach((to, from, next) => {
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
Util.openNewPage({ $store: store }, to.name, to.params, to.query);
|
||||
Util.openNewPage(to.name, to.params, to.query);
|
||||
NProgress.done();
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { createStore } from "vuex";
|
||||
import app from "./modules/app";
|
||||
import setting from "./modules/setting";
|
||||
import user from "./modules/user";
|
||||
import dict from "./modules/dict";
|
||||
|
||||
const store = createStore({
|
||||
state: {
|
||||
openStyleStore: "",
|
||||
openStoreId: "",
|
||||
notices: "",
|
||||
},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
modules: {
|
||||
app,
|
||||
user,
|
||||
setting,
|
||||
dict,
|
||||
},
|
||||
});
|
||||
|
||||
export default store;
|
||||
@@ -1,140 +0,0 @@
|
||||
import {
|
||||
otherRouter
|
||||
} from '@/router/router';
|
||||
import {
|
||||
router
|
||||
} from '@/router/index';
|
||||
import Util from '@/libs/util';
|
||||
import i18n from '@/locale';
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
styleStore: "", // 移动端楼层装修中选择风格存储
|
||||
added: false, // 加载路由标识
|
||||
navList: [], // 顶部菜单
|
||||
currNav: "", // 当前顶部菜单name
|
||||
currNavTitle: "", // 当前顶部菜单标题
|
||||
cachePage: [],
|
||||
lang: '',
|
||||
pageOpenedList: [{ // 打开过的页面,顶部历史菜单项
|
||||
title: '首页',
|
||||
path: '',
|
||||
name: 'home_index'
|
||||
}],
|
||||
hotWords:[], //今日热词数据
|
||||
// 面包屑数组 左侧菜单
|
||||
menuList: [],
|
||||
tagsList: [...otherRouter.children], //这块是面包屑的内容不能删除
|
||||
routers: [otherRouter],
|
||||
messageCount: 0,
|
||||
// 在这里定义你不想要缓存的页面的name属性值(参见路由配置router.js)
|
||||
dontCache: ['test', 'test']
|
||||
},
|
||||
mutations: {
|
||||
// 动态添加主界面路由,需要缓存
|
||||
updateAppRouter(state, routes) {
|
||||
state.routers.push(...routes);
|
||||
// 实际 addRoute 在 util.registerDynamicRoutes 中完成
|
||||
},
|
||||
updateDefaultRouter(state, routes) {
|
||||
// 兼容旧调用,404 由 registerDynamicRoutes 注册
|
||||
},
|
||||
setAdded(state, v) {
|
||||
state.added = v;
|
||||
},
|
||||
setNavList(state, list) {
|
||||
state.navList = list;
|
||||
},
|
||||
setCurrNav(state, v) {
|
||||
state.currNav = v;
|
||||
},
|
||||
setCurrNavTitle(state, v) {
|
||||
state.currNavTitle = v;
|
||||
},
|
||||
setTagsList(state, list) {
|
||||
state.tagsList.push(...list);
|
||||
},
|
||||
updateMenulist(state, routes) {
|
||||
state.menuList = routes;
|
||||
},
|
||||
closePage(state, name) {
|
||||
state.cachePage.forEach((item, index) => {
|
||||
if (item == name) {
|
||||
state.cachePage.splice(index, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
initCachePage(state) {
|
||||
if (localStorage.cachePage) {
|
||||
state.cachePage = JSON.parse(localStorage.cachePage);
|
||||
}
|
||||
},
|
||||
removeTag(state, name) {
|
||||
state.pageOpenedList.map((item, index) => {
|
||||
if (item.name == name) {
|
||||
state.pageOpenedList.splice(index, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
pageOpenedList(state, get) {
|
||||
let openedPage = state.pageOpenedList[get.index];
|
||||
if (get.argu) {
|
||||
openedPage.argu = get.argu;
|
||||
}
|
||||
if (get.query) {
|
||||
openedPage.query = get.query;
|
||||
}
|
||||
state.pageOpenedList.splice(get.index, 1, openedPage);
|
||||
localStorage.pageOpenedList = JSON.stringify(state.pageOpenedList);
|
||||
},
|
||||
clearAllTags(state) {
|
||||
state.pageOpenedList.splice(1);
|
||||
state.cachePage.length = 0;
|
||||
localStorage.pageOpenedList = JSON.stringify(state.pageOpenedList);
|
||||
},
|
||||
clearOtherTags(state, vm) {
|
||||
let currentName = vm.$route.name;
|
||||
let currentIndex = 0;
|
||||
state.pageOpenedList.forEach((item, index) => {
|
||||
if (item.name == currentName) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
if (currentIndex == 0) {
|
||||
state.pageOpenedList.splice(1);
|
||||
} else {
|
||||
state.pageOpenedList.splice(currentIndex + 1);
|
||||
state.pageOpenedList.splice(1, currentIndex - 1);
|
||||
}
|
||||
let newCachepage = state.cachePage.filter(item => {
|
||||
return item == currentName;
|
||||
});
|
||||
state.cachePage = newCachepage;
|
||||
localStorage.pageOpenedList = JSON.stringify(state.pageOpenedList);
|
||||
},
|
||||
setOpenedList(state) {
|
||||
state.pageOpenedList = localStorage.pageOpenedList ? JSON.parse(localStorage.pageOpenedList) : [otherRouter.children[0]];
|
||||
},
|
||||
setAvatarPath(state, path) {
|
||||
localStorage.avatorImgPath = path;
|
||||
},
|
||||
switchLang(state, lang) {
|
||||
state.lang = lang;
|
||||
localStorage.lang = lang;
|
||||
i18n.global.locale = lang;
|
||||
},
|
||||
setMessageCount(state, count) {
|
||||
state.messageCount = count;
|
||||
},
|
||||
increateTag(state, tagObj) {
|
||||
if (!Util.oneOf(tagObj.name, state.dontCache)) {
|
||||
state.cachePage.push(tagObj.name);
|
||||
localStorage.cachePage = JSON.stringify(state.cachePage);
|
||||
}
|
||||
state.pageOpenedList.push(tagObj);
|
||||
localStorage.pageOpenedList = JSON.stringify(state.pageOpenedList);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default app;
|
||||
@@ -1,26 +0,0 @@
|
||||
const dict = {
|
||||
state: {
|
||||
// 经常需要读取的数据字典
|
||||
sex: [],
|
||||
messageType: [],
|
||||
priority: [],
|
||||
leaveType: []
|
||||
},
|
||||
mutations: {
|
||||
// 设置值的改变方法
|
||||
setSex(state, list) {
|
||||
state.sex = list;
|
||||
},
|
||||
setMessageType(state, list) {
|
||||
state.messageType = list;
|
||||
},
|
||||
setPriority(state, list) {
|
||||
state.priority = list;
|
||||
},
|
||||
setLeaveType(state, list) {
|
||||
state.leaveType = list;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default dict;
|
||||
@@ -1,17 +0,0 @@
|
||||
import config from "@/config";
|
||||
|
||||
const { setting } = config;
|
||||
|
||||
const localSetting = window.localStorage.getItem('admin-setting')
|
||||
const settingData = {
|
||||
state: {
|
||||
setting:localSetting ? JSON.parse(localSetting) : setting,
|
||||
},
|
||||
mutations: {
|
||||
updateSetting(state, data) {
|
||||
state.setting = data;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default settingData;
|
||||
@@ -1,15 +0,0 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import util from '@/libs/util';
|
||||
|
||||
const user = {
|
||||
state: {},
|
||||
mutations: {
|
||||
logout () {
|
||||
util.clearDynamicRoutes();
|
||||
Cookies.remove('userInfoManager');
|
||||
localStorage.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
||||
146
manager/src/stores/app.js
Normal file
146
manager/src/stores/app.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { otherRouter } from "@/router/router";
|
||||
import i18n from "@/locale";
|
||||
|
||||
// 原 vuex app 模块 + 根 state(openStyleStore/openStoreId/notices)合并
|
||||
// mutation 全部转为同名 action;两个例外:
|
||||
// - pageOpenedList mutation 与 state 同名,改名 updateOpenedList
|
||||
// - clearOtherTags 原接收组件实例 vm,改为直接接收当前路由名 currentName
|
||||
export const useAppStore = defineStore("app", {
|
||||
state: () => ({
|
||||
// 原根 state
|
||||
openStyleStore: "",
|
||||
openStoreId: "",
|
||||
notices: "",
|
||||
// 原 app 模块
|
||||
styleStore: "", // 移动端楼层装修中选择风格存储
|
||||
added: false, // 加载路由标识
|
||||
navList: [], // 顶部菜单
|
||||
currNav: "", // 当前顶部菜单name
|
||||
currNavTitle: "", // 当前顶部菜单标题
|
||||
cachePage: [],
|
||||
lang: "",
|
||||
pageOpenedList: [
|
||||
{
|
||||
// 打开过的页面,顶部历史菜单项
|
||||
title: "首页",
|
||||
path: "",
|
||||
name: "home_index",
|
||||
},
|
||||
],
|
||||
hotWords: [], //今日热词数据
|
||||
// 面包屑数组 左侧菜单
|
||||
menuList: [],
|
||||
tagsList: [...otherRouter.children], //这块是面包屑的内容不能删除
|
||||
routers: [otherRouter],
|
||||
messageCount: 0,
|
||||
// 在这里定义你不想要缓存的页面的name属性值(参见路由配置router.js)
|
||||
dontCache: ["test", "test"],
|
||||
}),
|
||||
actions: {
|
||||
// 动态添加主界面路由,需要缓存
|
||||
updateAppRouter(routes) {
|
||||
this.routers.push(...routes);
|
||||
// 实际 addRoute 在 util.registerDynamicRoutes 中完成
|
||||
},
|
||||
updateDefaultRouter(routes) {
|
||||
// 兼容旧调用,404 由 registerDynamicRoutes 注册
|
||||
},
|
||||
setAdded(v) {
|
||||
this.added = v;
|
||||
},
|
||||
setNavList(list) {
|
||||
this.navList = list;
|
||||
},
|
||||
setCurrNav(v) {
|
||||
this.currNav = v;
|
||||
},
|
||||
setCurrNavTitle(v) {
|
||||
this.currNavTitle = v;
|
||||
},
|
||||
setTagsList(list) {
|
||||
this.tagsList.push(...list);
|
||||
},
|
||||
updateMenulist(routes) {
|
||||
this.menuList = routes;
|
||||
},
|
||||
closePage(name) {
|
||||
this.cachePage.forEach((item, index) => {
|
||||
if (item == name) {
|
||||
this.cachePage.splice(index, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
initCachePage() {
|
||||
if (localStorage.cachePage) {
|
||||
this.cachePage = JSON.parse(localStorage.cachePage);
|
||||
}
|
||||
},
|
||||
removeTag(name) {
|
||||
this.pageOpenedList.map((item, index) => {
|
||||
if (item.name == name) {
|
||||
this.pageOpenedList.splice(index, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateOpenedList(get) {
|
||||
let openedPage = this.pageOpenedList[get.index];
|
||||
if (get.argu) {
|
||||
openedPage.argu = get.argu;
|
||||
}
|
||||
if (get.query) {
|
||||
openedPage.query = get.query;
|
||||
}
|
||||
this.pageOpenedList.splice(get.index, 1, openedPage);
|
||||
localStorage.pageOpenedList = JSON.stringify(this.pageOpenedList);
|
||||
},
|
||||
clearAllTags() {
|
||||
this.pageOpenedList.splice(1);
|
||||
this.cachePage.length = 0;
|
||||
localStorage.pageOpenedList = JSON.stringify(this.pageOpenedList);
|
||||
},
|
||||
clearOtherTags(currentName) {
|
||||
let currentIndex = 0;
|
||||
this.pageOpenedList.forEach((item, index) => {
|
||||
if (item.name == currentName) {
|
||||
currentIndex = index;
|
||||
}
|
||||
});
|
||||
if (currentIndex == 0) {
|
||||
this.pageOpenedList.splice(1);
|
||||
} else {
|
||||
this.pageOpenedList.splice(currentIndex + 1);
|
||||
this.pageOpenedList.splice(1, currentIndex - 1);
|
||||
}
|
||||
let newCachepage = this.cachePage.filter((item) => {
|
||||
return item == currentName;
|
||||
});
|
||||
this.cachePage = newCachepage;
|
||||
localStorage.pageOpenedList = JSON.stringify(this.pageOpenedList);
|
||||
},
|
||||
setOpenedList() {
|
||||
this.pageOpenedList = localStorage.pageOpenedList
|
||||
? JSON.parse(localStorage.pageOpenedList)
|
||||
: [otherRouter.children[0]];
|
||||
},
|
||||
setAvatarPath(path) {
|
||||
localStorage.avatorImgPath = path;
|
||||
},
|
||||
switchLang(lang) {
|
||||
this.lang = lang;
|
||||
localStorage.lang = lang;
|
||||
i18n.global.locale = lang;
|
||||
},
|
||||
setMessageCount(count) {
|
||||
this.messageCount = count;
|
||||
},
|
||||
increateTag(tagObj) {
|
||||
if (this.dontCache.indexOf(tagObj.name) < 0) {
|
||||
this.cachePage.push(tagObj.name);
|
||||
localStorage.cachePage = JSON.stringify(this.cachePage);
|
||||
}
|
||||
this.pageOpenedList.push(tagObj);
|
||||
localStorage.pageOpenedList = JSON.stringify(this.pageOpenedList);
|
||||
},
|
||||
},
|
||||
});
|
||||
26
manager/src/stores/dict.js
Normal file
26
manager/src/stores/dict.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useDictStore = defineStore("dict", {
|
||||
state: () => ({
|
||||
// 经常需要读取的数据字典
|
||||
sex: [],
|
||||
messageType: [],
|
||||
priority: [],
|
||||
leaveType: [],
|
||||
}),
|
||||
actions: {
|
||||
// 设置值的改变方法
|
||||
setSex(list) {
|
||||
this.sex = list;
|
||||
},
|
||||
setMessageType(list) {
|
||||
this.messageType = list;
|
||||
},
|
||||
setPriority(list) {
|
||||
this.priority = list;
|
||||
},
|
||||
setLeaveType(list) {
|
||||
this.leaveType = list;
|
||||
},
|
||||
},
|
||||
});
|
||||
17
manager/src/stores/setting.js
Normal file
17
manager/src/stores/setting.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineStore } from "pinia";
|
||||
import config from "@/config";
|
||||
|
||||
const { setting } = config;
|
||||
|
||||
const localSetting = window.localStorage.getItem("admin-setting");
|
||||
|
||||
export const useSettingStore = defineStore("setting", {
|
||||
state: () => ({
|
||||
setting: localSetting ? JSON.parse(localSetting) : setting,
|
||||
}),
|
||||
actions: {
|
||||
updateSetting(data) {
|
||||
this.setting = data;
|
||||
},
|
||||
},
|
||||
});
|
||||
13
manager/src/stores/user.js
Normal file
13
manager/src/stores/user.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineStore } from "pinia";
|
||||
import Cookies from "js-cookie";
|
||||
import util from "@/libs/util";
|
||||
|
||||
export const useUserStore = defineStore("user", {
|
||||
actions: {
|
||||
logout() {
|
||||
util.clearDynamicRoutes();
|
||||
Cookies.remove("userInfoManager");
|
||||
localStorage.clear();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
359
manager/src/views/distribution/distributionCustomer.vue
Normal file
359
manager/src/views/distribution/distributionCustomer.vue
Normal 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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -17,20 +17,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Error403",
|
||||
methods: {
|
||||
backPage() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
goHome() {
|
||||
this.$router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({ name: "Error403" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backPage() {
|
||||
router.go(-1);
|
||||
}
|
||||
function goHome() {
|
||||
router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -17,20 +17,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Error404",
|
||||
methods: {
|
||||
backPage() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
goHome() {
|
||||
this.$router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({ name: "Error404" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backPage() {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -17,20 +17,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Error500",
|
||||
methods: {
|
||||
backPage() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
goHome() {
|
||||
this.$router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
defineOptions({ name: "Error500" });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backPage() {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
router.push({
|
||||
name: "home_index",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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组件样式
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user