mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-13 20:38:54 +08:00
refactor(site-settings): 统一站点配置管理并清理废弃页面
- 新增 useSiteSettings composable 替代各端硬编码的配置引用 - 移除 memberRecycle 和 fictitiousOrderList 废弃页面并增加缓存过滤 - 版权信息改为通过接口动态获取,后台新增版权归属配置字段 - 全局禁止 textarea 拖拽缩放并统一三端样式规则 - 会员列表增加状态 tab 筛选及启用/禁用双向切换功能 - 删除未使用的 getSpecificationList API 方法
This commit is contained in:
18
README.md
18
README.md
@@ -162,6 +162,8 @@ lilishop-ui
|
|||||||
|
|
||||||
## 8. 升级计划
|
## 8. 升级计划
|
||||||
|
|
||||||
|
> 更新日期:2026-11-03,依据各端近期提交及当前代码实现同步。
|
||||||
|
|
||||||
### ✅ 已完成特性
|
### ✅ 已完成特性
|
||||||
| 功能模块 | 功能模块 |
|
| 功能模块 | 功能模块 |
|
||||||
| :--- | :--- |
|
| :--- | :--- |
|
||||||
@@ -170,17 +172,21 @@ lilishop-ui
|
|||||||
| ✅ 置顶评论 | ✅ 客户等级体系 |
|
| ✅ 置顶评论 | ✅ 客户等级体系 |
|
||||||
| ✅ 购物卡 | ✅ 腾讯云-云直播 |
|
| ✅ 购物卡 | ✅ 腾讯云-云直播 |
|
||||||
| ✅ 卡密商品 | ✅ 二级分销 |
|
| ✅ 卡密商品 | ✅ 二级分销 |
|
||||||
| ✅ 定时任务管理 | ✅ 预约服务商品 |
|
| ✅ 定时任务管理(数据库配置驱动) | ✅ 预约服务商品 |
|
||||||
| ✅ 主题色管理 | ✅ 新增无需配送发货方式 |
|
| ✅ 主题色管理 | ✅ 新增无需配送发货方式 |
|
||||||
| ✅ 多维统计(营业/商品/会员/积分/储值/营销/分销) | ✅ 经营报表(店铺业绩/商品同比环比/订单明细/销售汇总) |
|
| ✅ 多维统计(营业/商品/会员/积分/储值/营销/分销) | ✅ 经营报表(店铺业绩/商品同比环比/订单明细/销售汇总) |
|
||||||
| ✅ 分类页多模板设置 | ✅ 移动端楼层装修组件库 |
|
| ✅ 分类页多模板设置 | ✅ 移动端楼层装修组件库 |
|
||||||
| ✅ PC端楼层装修重构 | ✅ 全站统一顶栏 |
|
| ✅ PC端楼层装修重构(自适应高度 / 可视化热区绘制) | ✅ 全站统一顶栏 / 商品与店铺双模式搜索 |
|
||||||
| ✅ PC端店铺独立列表 | ✅ 敏感词场景化过滤 |
|
| ✅ PC端店铺独立列表 | ✅ 敏感词场景化过滤与分组开关 |
|
||||||
|
| ✅ 店铺结算汇总 / 结算台账 | ✅ 支付方式汇总 / 钱包流水 |
|
||||||
|
| ✅ 平台经营报表 / 商家周期财务汇总 | ✅ 财务报表与流水 Excel 导出 / 结算单批量下载 |
|
||||||
|
| ✅ 会员消息四渠道异步通知(站内信 / 短信 / 公众号 / 小程序) | ✅ 消息模板多渠道矩阵管理(模板配置 / 渠道开关) |
|
||||||
|
| ✅ 平台订单详情优化 / 标准 A4 出库发货单打印 | ✅ 售后详情展示优化 / 移动端售后入口整合至订单列表 |
|
||||||
|
| ✅ 商品详情视频内嵌相册 | ✅ 评价管理增强(回复状态 / 图片筛选 / 评价分级) |
|
||||||
|
| ✅ PC会员中心订单快捷入口 / 资产卡片 | ✅ 平台表单抽屉化 / 文章独立编辑页 |
|
||||||
|
|
||||||
### 🔜 近期规划中
|
### 🔜 近期规划中
|
||||||
- 🔜 店铺结算汇总 & 结算台账
|
|
||||||
- 🔜 支付方式汇总 & 钱包流水 & 财务汇总
|
|
||||||
- 🔜 微信小店对接
|
- 🔜 微信小店对接
|
||||||
- 🔜 抖店对接
|
- 🔜 抖店对接
|
||||||
- 🔜 微信小程序原生重构与体验对齐
|
- 🔜 微信小程序原生重构与体验对齐(持续完善;已接入预约交易、分类多模板与首页楼层装修)
|
||||||
- 🔜 OpenAPI 开放平台与开发者中心
|
- 🔜 OpenAPI 开放平台与开发者中心
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/* 全局禁止多行输入框拖动缩放,覆盖组件生成的内联 resize 样式。 */
|
||||||
|
.el-textarea__inner {
|
||||||
|
resize: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--el-color-primary: var(--theme-color, #ff3c2a);
|
--el-color-primary: var(--theme-color, #ff3c2a);
|
||||||
--el-color-danger: var(--theme-color, #ff3c2a);
|
--el-color-danger: var(--theme-color, #ff3c2a);
|
||||||
|
|||||||
@@ -43,25 +43,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="information footer-bottmom">
|
<div class="information footer-bottmom">
|
||||||
<div class="icp">
|
<div class="icp">
|
||||||
<li v-if="config.icpCard">
|
<li v-if="icp" class="footer-bottmom">
|
||||||
<a :href='"https://tsm.miit.gov.cn/dxxzsp/xkz/xkzgl/resource/qiyesearch.jsp?num="+config.icpCard+"&type=xuke"' target="_blank">
|
|
||||||
{{config.icpCard}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li v-if="config.icpMessage" class="footer-bottmom">
|
|
||||||
<a href="https://beian.miit.gov.cn/" target="_blank">
|
<a href="https://beian.miit.gov.cn/" target="_blank">
|
||||||
{{config.icpMessage}}
|
{{icp}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<a class="flex " :href="config.company.href">
|
<a v-if="companyName" class="flex " :href="companyUrl || undefined">
|
||||||
<img class="zhizhao" src="@/assets/images/zhizhao.jpg" mode="" />{{config.company.name}}
|
<img class="zhizhao" src="@/assets/images/zhizhao.jpg" mode="" />{{companyName}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<div class="copyright">
|
<div class="copyright">
|
||||||
<p>Copyright © {{year}} {{config.title}}</p>
|
<p>Copyright © {{year}} {{copyrightOwner}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -71,11 +66,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import config from '@/config'
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
defineOptions({ name: 'Footer' })
|
defineOptions({ name: 'Footer' })
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { companyName, companyUrl, icp, copyrightOwner } = useSiteSettings()
|
||||||
|
|
||||||
const guideArr = ref([
|
const guideArr = ref([
|
||||||
// 导航链接
|
// 导航链接
|
||||||
|
|||||||
33
buyer/src/composables/useSiteSettings.js
Normal file
33
buyer/src/composables/useSiteSettings.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { getBaseSite } from "@/api/common.js";
|
||||||
|
import config from "@/config";
|
||||||
|
|
||||||
|
// 同页的页头、页脚共享请求;每次重新进入页面时刷新,避免旧缓存覆盖后台设置。
|
||||||
|
const settings = ref(null);
|
||||||
|
let pendingRequest;
|
||||||
|
|
||||||
|
export function loadSiteSettings() {
|
||||||
|
if (!pendingRequest) {
|
||||||
|
pendingRequest = getBaseSite()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success && res.result?.settingValue) {
|
||||||
|
settings.value = JSON.parse(res.result.settingValue);
|
||||||
|
}
|
||||||
|
return settings.value;
|
||||||
|
})
|
||||||
|
.catch(() => settings.value)
|
||||||
|
.finally(() => { pendingRequest = null; });
|
||||||
|
}
|
||||||
|
return pendingRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSiteSettings() {
|
||||||
|
onMounted(loadSiteSettings);
|
||||||
|
const siteName = computed(() => settings.value?.siteName || config.title);
|
||||||
|
const companyName = computed(() => settings.value ? settings.value.companyName || "" : config.company?.name || "");
|
||||||
|
const companyUrl = computed(() => settings.value ? settings.value.companyUrl || "" : config.company?.href || "");
|
||||||
|
const website = computed(() => settings.value ? settings.value.website || "" : config.website || "");
|
||||||
|
const icp = computed(() => settings.value ? settings.value.icp || "" : config.icpMessage || "");
|
||||||
|
const copyrightOwner = computed(() => settings.value?.copyrightOwner?.trim() || siteName.value);
|
||||||
|
return { settings, siteName, companyName, companyUrl, website, icp, copyrightOwner };
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
resize: none;
|
resize: none !important;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ function finished() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 父组件(member/index、memberRecycle、memberDetail、orderDetail、shopOperation)通过 ref 调用 open() 打开弹窗
|
// 父组件(member/index、memberDetail、orderDetail、shopOperation)通过 ref 调用 open() 打开弹窗
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
33
manager/src/composables/useSiteSettings.js
Normal file
33
manager/src/composables/useSiteSettings.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { getBaseSite } from "@/api/common.js";
|
||||||
|
import config from "@/config";
|
||||||
|
|
||||||
|
// 同页的页头、页脚共享请求;每次重新进入页面时刷新,避免旧缓存覆盖后台设置。
|
||||||
|
const settings = ref(null);
|
||||||
|
let pendingRequest;
|
||||||
|
|
||||||
|
export function loadSiteSettings() {
|
||||||
|
if (!pendingRequest) {
|
||||||
|
pendingRequest = getBaseSite()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success && res.result?.settingValue) {
|
||||||
|
settings.value = JSON.parse(res.result.settingValue);
|
||||||
|
}
|
||||||
|
return settings.value;
|
||||||
|
})
|
||||||
|
.catch(() => settings.value)
|
||||||
|
.finally(() => { pendingRequest = null; });
|
||||||
|
}
|
||||||
|
return pendingRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSiteSettings() {
|
||||||
|
onMounted(loadSiteSettings);
|
||||||
|
const siteName = computed(() => settings.value?.siteName || config.title);
|
||||||
|
const companyName = computed(() => settings.value ? settings.value.companyName || "" : config.company?.name || "");
|
||||||
|
const companyUrl = computed(() => settings.value ? settings.value.companyUrl || "" : config.company?.href || "");
|
||||||
|
const website = computed(() => settings.value ? settings.value.website || "" : config.website || "");
|
||||||
|
const icp = computed(() => settings.value ? settings.value.icp || "" : config.icpMessage || "");
|
||||||
|
const copyrightOwner = computed(() => settings.value?.copyrightOwner?.trim() || siteName.value);
|
||||||
|
return { settings, siteName, companyName, companyUrl, website, icp, copyrightOwner };
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ let util = {};
|
|||||||
/** 已弃用、前端未实现或已移除的菜单 frontRoute,注册路由时跳过 */
|
/** 已弃用、前端未实现或已移除的菜单 frontRoute,注册路由时跳过 */
|
||||||
const DEPRECATED_FRONT_ROUTES = new Set([
|
const DEPRECATED_FRONT_ROUTES = new Set([
|
||||||
"goods/goods-manage/spec",
|
"goods/goods-manage/spec",
|
||||||
|
"member/list/memberRecycle",
|
||||||
|
"order/order/fictitiousOrderList",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function isDeprecatedMenu(menu) {
|
function isDeprecatedMenu(menu) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { defineStore } from "pinia";
|
|||||||
import { otherRouter } from "@/router/router";
|
import { otherRouter } from "@/router/router";
|
||||||
import i18n from "@/locale";
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
const removedPageNames = new Set(["memberRecycle", "fictitiousOrderList"]);
|
||||||
|
|
||||||
// 原 vuex app 模块 + 根 state(openStyleStore/openStoreId/notices)合并
|
// 原 vuex app 模块 + 根 state(openStyleStore/openStoreId/notices)合并
|
||||||
// mutation 全部转为同名 action;两个例外:
|
// mutation 全部转为同名 action;两个例外:
|
||||||
// - pageOpenedList mutation 与 state 同名,改名 updateOpenedList
|
// - pageOpenedList mutation 与 state 同名,改名 updateOpenedList
|
||||||
@@ -73,7 +75,8 @@ export const useAppStore = defineStore("app", {
|
|||||||
},
|
},
|
||||||
initCachePage() {
|
initCachePage() {
|
||||||
if (localStorage.cachePage) {
|
if (localStorage.cachePage) {
|
||||||
this.cachePage = JSON.parse(localStorage.cachePage);
|
this.cachePage = JSON.parse(localStorage.cachePage).filter((name) => !removedPageNames.has(name));
|
||||||
|
localStorage.cachePage = JSON.stringify(this.cachePage);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeTag(name) {
|
removeTag(name) {
|
||||||
@@ -120,8 +123,9 @@ export const useAppStore = defineStore("app", {
|
|||||||
},
|
},
|
||||||
setOpenedList() {
|
setOpenedList() {
|
||||||
this.pageOpenedList = localStorage.pageOpenedList
|
this.pageOpenedList = localStorage.pageOpenedList
|
||||||
? JSON.parse(localStorage.pageOpenedList)
|
? JSON.parse(localStorage.pageOpenedList).filter((page) => !removedPageNames.has(page.name))
|
||||||
: [otherRouter.children[0]];
|
: [otherRouter.children[0]];
|
||||||
|
localStorage.pageOpenedList = JSON.stringify(this.pageOpenedList);
|
||||||
},
|
},
|
||||||
setAvatarPath(path) {
|
setAvatarPath(path) {
|
||||||
localStorage.avatorImgPath = path;
|
localStorage.avatorImgPath = path;
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/* 全局禁止多行输入框拖动缩放,覆盖组件生成的内联 resize 样式。 */
|
||||||
|
.el-textarea__inner {
|
||||||
|
resize: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Element Plus 主题覆盖(对齐原 iView 主色) */
|
/* Element Plus 主题覆盖(对齐原 iView 主色) */
|
||||||
:root {
|
:root {
|
||||||
// --el-color-primary: #ff5c58;
|
// --el-color-primary: #ff5c58;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<li>多店铺管理与经营数据分析</li>
|
<li>多店铺管理与经营数据分析</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p class="brand-copyright">Copyright © {{ year }} Lilishop</p>
|
<p class="brand-copyright">Copyright © {{ year }} {{ copyrightOwner }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login-panel" @keyup.enter="submitLogin">
|
<div class="login-panel" @keyup.enter="submitLogin">
|
||||||
@@ -94,11 +94,13 @@ import verify from "@/components/verify";
|
|||||||
import { setStore } from "@/libs/storage";
|
import { setStore } from "@/libs/storage";
|
||||||
import { md5 } from "@/utils/md5.js";
|
import { md5 } from "@/utils/md5.js";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
|
import { useSiteSettings } from "@/composables/useSiteSettings";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const year = ref(new Date().getFullYear());
|
const year = ref(new Date().getFullYear());
|
||||||
|
const { copyrightOwner } = useSiteSettings();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
username: "",
|
username: "",
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="foot">
|
<div class="foot">
|
||||||
<el-row justify="space-around" class="help">
|
<el-row v-if="website" justify="space-around" class="help">
|
||||||
<a class="item" :href="website" target="_blank">帮助</a>
|
<a class="item" :href="website" target="_blank">帮助</a>
|
||||||
<a class="item" :href="website" target="_blank">隐私</a>
|
<a class="item" :href="website" target="_blank">隐私</a>
|
||||||
<a class="item" :href="website" target="_blank">条款</a>
|
<a class="item" :href="website" target="_blank">条款</a>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row justify="center" class="copyright">
|
<el-row justify="center" class="copyright">
|
||||||
Copyright © {{ year }} - Present
|
Copyright © {{ year }}
|
||||||
<a :href="website" class="href" target="_blank" style="margin: 0 5px">{{ config.title }}</a>
|
<a :href="website || undefined" class="href" target="_blank" style="margin: 0 5px">{{ copyrightOwner }}</a>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import config from "@/config/index";
|
import { useSiteSettings } from "@/composables/useSiteSettings";
|
||||||
|
|
||||||
const year = ref(new Date().getFullYear());
|
const year = ref(new Date().getFullYear());
|
||||||
// 官网地址优先取接口下发缓存,兜底用本地配置
|
const { website, copyrightOwner } = useSiteSettings();
|
||||||
const website = ref(localStorage.getItem("website") || config.website);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { getBaseSite } from "@/api/common.js";
|
import { loadSiteSettings } from "@/composables/useSiteSettings";
|
||||||
|
|
||||||
const domainLogo = ref("");
|
const domainLogo = ref("");
|
||||||
|
|
||||||
@@ -24,11 +24,9 @@ function applyFavicon(href) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSite() {
|
function getSite() {
|
||||||
getBaseSite().then((res) => {
|
loadSiteSettings().then((data) => {
|
||||||
if (res.success && res.result && res.result.settingValue) {
|
if (data) {
|
||||||
const { domainLogo: logo, domainIcon, siteName, website } = JSON.parse(
|
const { domainLogo: logo, domainIcon, siteName, website } = data;
|
||||||
res.result.settingValue
|
|
||||||
);
|
|
||||||
domainLogo.value = logo || "";
|
domainLogo.value = logo || "";
|
||||||
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
||||||
localStorage.setItem("icontitle_expiration_time", expirationTime);
|
localStorage.setItem("icontitle_expiration_time", expirationTime);
|
||||||
|
|||||||
@@ -48,6 +48,11 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card>
|
<el-card>
|
||||||
|
<el-tabs v-if="!selectedMember" v-model="memberStatus" @tab-change="handleStatusChange">
|
||||||
|
<el-tab-pane label="全部" name="ALL" />
|
||||||
|
<el-tab-pane label="启用" name="OPEN" />
|
||||||
|
<el-tab-pane label="禁用" name="CLOSE" />
|
||||||
|
</el-tabs>
|
||||||
<div v-if="!selectedMember" class="operation padding-row">
|
<div v-if="!selectedMember" class="operation padding-row">
|
||||||
<el-button type="primary" @click="addMember">添加会员</el-button>
|
<el-button type="primary" @click="addMember">添加会员</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -88,6 +93,11 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="username" label="客户名称" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="username" label="客户名称" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column prop="nickName" label="会员昵称" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="nickName" label="会员昵称" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column v-if="!selectedMember" label="状态" width="90" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.disabled ? 'success' : 'info'">{{ row.disabled ? '启用' : '禁用' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="联系方式" min-width="130">
|
<el-table-column label="联系方式" min-width="130">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row">{{ row.mobile || "" }}</span>
|
<span v-if="row">{{ row.mobile || "" }}</span>
|
||||||
@@ -142,7 +152,7 @@
|
|||||||
<el-dropdown-item command="edit">编辑会员</el-dropdown-item>
|
<el-dropdown-item command="edit">编辑会员</el-dropdown-item>
|
||||||
<el-dropdown-item command="increaseWallet">增加余额</el-dropdown-item>
|
<el-dropdown-item command="increaseWallet">增加余额</el-dropdown-item>
|
||||||
<el-dropdown-item command="updatePoint">修改积分</el-dropdown-item>
|
<el-dropdown-item command="updatePoint">修改积分</el-dropdown-item>
|
||||||
<el-dropdown-item command="disabled">禁用会员</el-dropdown-item>
|
<el-dropdown-item command="disabled">{{ row.disabled ? '禁用会员' : '启用会员' }}</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@@ -359,6 +369,7 @@ const addMemberForm = ref({
|
|||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
});
|
});
|
||||||
|
const memberStatus = ref("ALL");
|
||||||
const searchForm = ref({
|
const searchForm = ref({
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
@@ -366,7 +377,7 @@ const searchForm = ref({
|
|||||||
id: "",
|
id: "",
|
||||||
username: "",
|
username: "",
|
||||||
mobile: "",
|
mobile: "",
|
||||||
disabled: "OPEN",
|
disabled: props.selectedMember ? "OPEN" : "",
|
||||||
groupId: "",
|
groupId: "",
|
||||||
});
|
});
|
||||||
const picModelFlag = ref(false);
|
const picModelFlag = ref(false);
|
||||||
@@ -586,6 +597,11 @@ function handleSearch() {
|
|||||||
searchForm.value.pageSize = 20;
|
searchForm.value.pageSize = 20;
|
||||||
getData();
|
getData();
|
||||||
}
|
}
|
||||||
|
function handleStatusChange(status) {
|
||||||
|
searchForm.value.disabled = status === "ALL" ? "" : status;
|
||||||
|
searchForm.value.pageNumber = 1;
|
||||||
|
getData();
|
||||||
|
}
|
||||||
function editPerm(val) {
|
function editPerm(val) {
|
||||||
descTitle.value = `查看用户 ${val.username}`;
|
descTitle.value = `查看用户 ${val.username}`;
|
||||||
descFlag.value = true;
|
descFlag.value = true;
|
||||||
@@ -609,19 +625,26 @@ function getMemberInfo(id) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let memberListRequestId = 0;
|
||||||
function getData() {
|
function getData() {
|
||||||
|
const requestId = ++memberListRequestId;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
if (!props.selectedMember) {
|
if (!props.selectedMember) {
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
API_Member.getMemberListData(searchForm.value).then((res) => {
|
API_Member.getMemberListData({ ...searchForm.value })
|
||||||
if (res.result.records) {
|
.then((res) => {
|
||||||
loading.value = false;
|
if (requestId !== memberListRequestId) return;
|
||||||
init(res.result.records);
|
if (res.success && res.result) {
|
||||||
total.value = res.result.total;
|
init(res.result.records || []);
|
||||||
|
total.value = res.result.total || 0;
|
||||||
syncTableSelection();
|
syncTableSelection();
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
if (requestId === memberListRequestId) loading.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function openPicSelector() {
|
function openPicSelector() {
|
||||||
@@ -666,14 +689,15 @@ function detail(row) {
|
|||||||
customRouterPush({ name: "member-detail", query: { id: row.id } });
|
customRouterPush({ name: "member-detail", query: { id: row.id } });
|
||||||
}
|
}
|
||||||
function disabled(v) {
|
function disabled(v) {
|
||||||
|
const action = v.disabled ? "禁用" : "启用";
|
||||||
let params = {
|
let params = {
|
||||||
memberIds: [v.id],
|
memberIds: [v.id],
|
||||||
disabled: false,
|
disabled: !v.disabled,
|
||||||
};
|
};
|
||||||
ElMessageBox.confirm("确认禁用此会员?", "提示", { type: "warning" }).then(() => {
|
ElMessageBox.confirm(`确认${action}此会员?`, "提示", { type: "warning" }).then(() => {
|
||||||
API_Member.updateMemberStatus(params).then((res) => {
|
API_Member.updateMemberStatus(params).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success("禁用成功");
|
ElMessage.success(`${action}成功`);
|
||||||
getData();
|
getData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,451 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="member-recycle-page search">
|
|
||||||
<!-- 顶部筛选条件表单 -->
|
|
||||||
<el-card shadow="never" class="filter-card">
|
|
||||||
<el-form
|
|
||||||
ref="searchFormRef"
|
|
||||||
:model="searchForm"
|
|
||||||
inline
|
|
||||||
label-width="76px"
|
|
||||||
class="search-form"
|
|
||||||
@keyup.enter="handleSearch"
|
|
||||||
>
|
|
||||||
<el-form-item label="客户ID" prop="id">
|
|
||||||
<el-input
|
|
||||||
v-model="searchForm.id"
|
|
||||||
placeholder="请输入客户ID"
|
|
||||||
clearable
|
|
||||||
style="width: 200px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="客户名称" prop="username">
|
|
||||||
<el-input
|
|
||||||
v-model="searchForm.username"
|
|
||||||
placeholder="请输入客户名称"
|
|
||||||
clearable
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="联系方式" prop="mobile">
|
|
||||||
<el-input
|
|
||||||
v-model="searchForm.mobile"
|
|
||||||
placeholder="请输入手机联系方式"
|
|
||||||
clearable
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item class="form-actions">
|
|
||||||
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
|
||||||
<el-button @click="handleReset">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 回收站会员表格卡片 -->
|
|
||||||
<el-card shadow="never" class="table-card mt_10">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header-bar">
|
|
||||||
<div class="header-title">
|
|
||||||
<span>会员回收站列表</span>
|
|
||||||
</div>
|
|
||||||
<div class="header-actions">
|
|
||||||
<el-button size="small" @click="getData">刷新</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
ref="table"
|
|
||||||
v-loading="loading"
|
|
||||||
border
|
|
||||||
stripe
|
|
||||||
:data="data"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<!-- 客户ID -->
|
|
||||||
<el-table-column prop="id" label="客户ID" min-width="190" show-overflow-tooltip />
|
|
||||||
|
|
||||||
<!-- 客户名称 -->
|
|
||||||
<el-table-column prop="username" label="客户名称" min-width="150" show-overflow-tooltip />
|
|
||||||
|
|
||||||
<!-- 昵称 -->
|
|
||||||
<el-table-column prop="nickName" label="昵称" min-width="140" show-overflow-tooltip />
|
|
||||||
|
|
||||||
<!-- 联系方式 -->
|
|
||||||
<el-table-column label="联系方式" min-width="140">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row && row.mobile ? row.mobile : "-" }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<!-- 积分数量 -->
|
|
||||||
<el-table-column label="积分数量" width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row ? (row.point == void 0 ? "0" : row.point) : "0" }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<!-- 注册时间 -->
|
|
||||||
<el-table-column prop="createTime" label="注册时间" min-width="180" align="center" />
|
|
||||||
|
|
||||||
<!-- 操作栏 -->
|
|
||||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<template v-if="row">
|
|
||||||
<template v-if="selectedMember">
|
|
||||||
<a class="link-text" @click="callback(row)">
|
|
||||||
{{ row.___selected ? "已选择" : "选择" }}
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a class="link-text" @click="detail(row)">查看</a>
|
|
||||||
<span class="op-split">|</span>
|
|
||||||
<a class="link-text" @click="enable(row)">启用</a>
|
|
||||||
<span class="op-split">|</span>
|
|
||||||
<a class="link-text" @click="editPerm(row)">编辑</a>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 底部分页 -->
|
|
||||||
<div class="pagination-wrap mt_10">
|
|
||||||
<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"
|
|
||||||
size="small"
|
|
||||||
@current-change="changePage"
|
|
||||||
@size-change="changePageSize"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 编辑会员弹窗 -->
|
|
||||||
<el-dialog
|
|
||||||
v-model="descFlag"
|
|
||||||
:title="descTitle"
|
|
||||||
width="540px"
|
|
||||||
destroy-on-close
|
|
||||||
class="edit-member-dialog"
|
|
||||||
>
|
|
||||||
<el-form ref="formValidateRef" :model="formValidate" :rules="ruleValidate" label-width="90px">
|
|
||||||
<el-form-item label="客户ID">
|
|
||||||
<span>{{ formValidate.id }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="头像">
|
|
||||||
<div class="avatar-edit-box">
|
|
||||||
<el-avatar :size="50" :src="formValidate.face || undefined" class="dialog-avatar">
|
|
||||||
{{ (formValidate.username || "用")[0] }}
|
|
||||||
</el-avatar>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
class="upload-btn"
|
|
||||||
@click="openPicSelector"
|
|
||||||
>
|
|
||||||
修改头像
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="客户名称" prop="name">
|
|
||||||
<el-input v-model="formValidate.username" style="width: 260px" disabled />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="用户昵称" prop="name">
|
|
||||||
<el-input v-model="formValidate.nickName" placeholder="请输入用户昵称" style="width: 260px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="性别" prop="sex">
|
|
||||||
<el-radio-group v-model="formValidate.sex">
|
|
||||||
<el-radio-button :value="1">男</el-radio-button>
|
|
||||||
<el-radio-button :value="0">女</el-radio-button>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="重置密码" prop="password">
|
|
||||||
<el-input
|
|
||||||
v-model="formValidate.newPassword"
|
|
||||||
type="password"
|
|
||||||
show-password
|
|
||||||
placeholder="留空则保持原密码"
|
|
||||||
style="width: 260px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="生日" prop="birthday">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="formValidate.birthday"
|
|
||||||
type="date"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
placeholder="请选择生日"
|
|
||||||
style="width: 260px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="所在地" prop="mail">
|
|
||||||
<div class="region-box">
|
|
||||||
<span class="region-text">{{ formValidate.region || "暂无地址" }}</span>
|
|
||||||
<el-button size="small" @click="$refs.map.open()">选择地区</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="descFlag = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="handleSubmitModal">确定</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog v-model="picModelFlag" width="1200px" title="选择头像图片">
|
|
||||||
<ossManage
|
|
||||||
ref="ossManageRef"
|
|
||||||
@callback="callbackSelected"
|
|
||||||
:isComponent="true"
|
|
||||||
:initialize="picModelFlag"
|
|
||||||
/>
|
|
||||||
</el-dialog>
|
|
||||||
<multipleMap ref="map" @callback="selectedRegion" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, nextTick, onMounted } from "vue";
|
|
||||||
import * as API_Member from "@/api/member.js";
|
|
||||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
|
||||||
import multipleMap from "@/components/map/multiple-map";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { customRouterPush } from "@/utils/filters";
|
|
||||||
import { md5 } from "@/utils/md5.js";
|
|
||||||
|
|
||||||
defineOptions({ name: "memberRecycle" });
|
|
||||||
|
|
||||||
const emit = defineEmits(["callback"]);
|
|
||||||
|
|
||||||
const searchFormRef = ref(null);
|
|
||||||
const table = ref(null);
|
|
||||||
const formValidateRef = ref(null);
|
|
||||||
const ossManageRef = ref(null);
|
|
||||||
|
|
||||||
const selectedMember = ref(false);
|
|
||||||
const descTitle = ref("");
|
|
||||||
const descFlag = ref(false);
|
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
const searchForm = ref({
|
|
||||||
pageNumber: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
order: "desc",
|
|
||||||
id: "",
|
|
||||||
username: "",
|
|
||||||
mobile: "",
|
|
||||||
disabled: "CLOSE",
|
|
||||||
});
|
|
||||||
|
|
||||||
const picModelFlag = ref(false);
|
|
||||||
const formValidate = ref({});
|
|
||||||
const ruleValidate = ref({});
|
|
||||||
const data = ref([]);
|
|
||||||
const total = ref(0);
|
|
||||||
|
|
||||||
function callback(val) {
|
|
||||||
emit("callback", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function changePage() {
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function changePageSize() {
|
|
||||||
searchForm.value.pageNumber = 1;
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSearch() {
|
|
||||||
searchForm.value.pageNumber = 1;
|
|
||||||
searchForm.value.pageSize = 20;
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleReset() {
|
|
||||||
searchForm.value.pageNumber = 1;
|
|
||||||
searchForm.value.id = "";
|
|
||||||
searchForm.value.username = "";
|
|
||||||
searchForm.value.mobile = "";
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
function editPerm(val) {
|
|
||||||
descTitle.value = `编辑停用客户 - ${val.username || "客户"}`;
|
|
||||||
descFlag.value = true;
|
|
||||||
getMemberInfo(val.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMemberInfo(id) {
|
|
||||||
API_Member.getMemberInfoData(id).then((res) => {
|
|
||||||
if (res.result) {
|
|
||||||
formValidate.value = res.result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getData() {
|
|
||||||
loading.value = true;
|
|
||||||
API_Member.getMemberListData(searchForm.value)
|
|
||||||
.then((res) => {
|
|
||||||
loading.value = false;
|
|
||||||
if (res.success && res.result) {
|
|
||||||
data.value = res.result.records || [];
|
|
||||||
total.value = res.result.total || 0;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openPicSelector() {
|
|
||||||
picModelFlag.value = true;
|
|
||||||
nextTick(() => {
|
|
||||||
if (ossManageRef.value) {
|
|
||||||
ossManageRef.value.selectImage = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function callbackSelected(val) {
|
|
||||||
picModelFlag.value = false;
|
|
||||||
formValidate.value.face = val.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectedRegion(val) {
|
|
||||||
if (val.type === "select") {
|
|
||||||
const paths = val.data.map((item) => item.name).join(",");
|
|
||||||
const ids = val.data.map((item) => item.id).join(",");
|
|
||||||
formValidate.value.region = paths;
|
|
||||||
formValidate.value.regionId = ids;
|
|
||||||
} else {
|
|
||||||
formValidate.value.region = val.data.addr;
|
|
||||||
formValidate.value.regionId = val.data.addrId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function detail(row) {
|
|
||||||
customRouterPush({ name: "member-detail", query: { id: row.id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
function enable(v) {
|
|
||||||
let params = {
|
|
||||||
memberIds: [v.id],
|
|
||||||
disabled: true,
|
|
||||||
};
|
|
||||||
ElMessageBox.confirm(`确定启用客户 "${v.username || v.nickName || "该客户"}" 吗?`, "提示", {
|
|
||||||
type: "warning",
|
|
||||||
confirmButtonText: "确定启用",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
API_Member.updateMemberStatus(params).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
ElMessage.success("启用成功");
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmitModal() {
|
|
||||||
const { nickName, sex, username, face, newPassword, id, regionId, region } = formValidate.value;
|
|
||||||
let time = new Date(formValidate.value.birthday);
|
|
||||||
let birthday = time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
|
|
||||||
let submit = {
|
|
||||||
regionId: regionId,
|
|
||||||
region: region,
|
|
||||||
nickName,
|
|
||||||
sex,
|
|
||||||
birthday,
|
|
||||||
face: face || "",
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
if (newPassword) {
|
|
||||||
submit.password = md5(newPassword);
|
|
||||||
}
|
|
||||||
API_Member.updateMember(submit).then((res) => {
|
|
||||||
if (res.result) {
|
|
||||||
ElMessage.success("修改成功!");
|
|
||||||
descFlag.value = false;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.member-recycle-page {
|
|
||||||
.filter-card {
|
|
||||||
.search-form {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
:deep(.el-form-item) {
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrap {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-edit-box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.dialog-avatar {
|
|
||||||
background-color: #f56c6c;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.region-box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.region-text {
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="search">
|
|
||||||
<el-card>
|
|
||||||
<el-form
|
|
||||||
ref="searchFormRef"
|
|
||||||
:model="searchForm"
|
|
||||||
inline
|
|
||||||
label-width="70px"
|
|
||||||
class="search-form"
|
|
||||||
@keyup.enter="handleSearch"
|
|
||||||
>
|
|
||||||
<el-form-item label="订单号" prop="orderSn">
|
|
||||||
<el-input
|
|
||||||
v-model="searchForm.orderSn"
|
|
||||||
placeholder="请输入订单号"
|
|
||||||
clearable
|
|
||||||
style="width: 240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="会员名称" prop="buyerName">
|
|
||||||
<el-input
|
|
||||||
v-model="searchForm.buyerName"
|
|
||||||
placeholder="请输入会员名称"
|
|
||||||
clearable
|
|
||||||
style="width: 240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="下单时间">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="selectDate"
|
|
||||||
type="datetimerange"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
clearable
|
|
||||||
start-placeholder="开始时间"
|
|
||||||
end-placeholder="结束时间"
|
|
||||||
placeholder="选择起始时间"
|
|
||||||
style="width: 240px"
|
|
||||||
@change="selectDateRange"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card>
|
|
||||||
<div class="order-tab">
|
|
||||||
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
|
||||||
<el-tab-pane
|
|
||||||
v-for="(item, index) in orderStatus"
|
|
||||||
:key="index"
|
|
||||||
:label="item.title"
|
|
||||||
:name="item.value"
|
|
||||||
/>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
ref="tableRef"
|
|
||||||
v-loading="loading"
|
|
||||||
border
|
|
||||||
:data="data"
|
|
||||||
class="mt_10"
|
|
||||||
style="width: 100%"
|
|
||||||
@sort-change="changeSort"
|
|
||||||
>
|
|
||||||
<el-table-column prop="sn" label="订单号" min-width="230" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="createTime" label="下单时间" width="200" />
|
|
||||||
<el-table-column label="订单来源" width="95">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row">
|
|
||||||
<span v-if="row.clientType == 'H5'">移动端</span>
|
|
||||||
<span v-else-if="row.clientType == 'PC'">PC端</span>
|
|
||||||
<span v-else-if="row.clientType == 'WECHAT_MP'">小程序端</span>
|
|
||||||
<span v-else-if="row.clientType == 'APP'">移动应用端</span>
|
|
||||||
<span v-else>{{ row.clientType }}</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="memberName" label="买家名称" width="130" />
|
|
||||||
<el-table-column label="订单金额" prop="flowPrice" min-width="120" sortable="custom">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<priceColorScheme v-if="row" :value="row.flowPrice" :color="$mainColor" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="订单状态" width="95">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<template v-if="row">
|
|
||||||
<el-tag v-if="row.orderStatus == 'UNPAID'" type="danger">未付款</el-tag>
|
|
||||||
<el-tag v-else-if="row.orderStatus == 'PAID'" type="primary">已付款</el-tag>
|
|
||||||
<el-tag v-else-if="row.orderStatus == 'COMPLETED'" type="success">已完成</el-tag>
|
|
||||||
<el-tag v-else-if="row.orderStatus == 'TAKE'" type="warning">待核验</el-tag>
|
|
||||||
<el-tag v-else-if="row.orderStatus == 'CANCELLED'" type="info">已关闭</el-tag>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="200" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<template v-if="row">
|
|
||||||
<a
|
|
||||||
class="link-text"
|
|
||||||
:class="{ disabled: row.orderStatus != 'UNPAID' }"
|
|
||||||
@click="row.orderStatus == 'UNPAID' && confirmPrice(row)"
|
|
||||||
>收款</a>
|
|
||||||
<span class="op-split">|</span>
|
|
||||||
<a class="link-text" @click="detail(row)">查看</a>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<div class="mt_10" 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"
|
|
||||||
size="small"
|
|
||||||
@current-change="changePage"
|
|
||||||
@size-change="changePageSize"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { onMounted, reactive, ref } from "vue";
|
|
||||||
import * as API_Order from "@/api/order";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { customRouterPush } from "@/utils/filters";
|
|
||||||
|
|
||||||
defineOptions({ name: "fictitiousOrderList" });
|
|
||||||
|
|
||||||
const searchFormRef = ref(null);
|
|
||||||
const tableRef = ref(null);
|
|
||||||
|
|
||||||
const loading = ref(true);
|
|
||||||
const searchForm = reactive({
|
|
||||||
pageNumber: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
sort: "",
|
|
||||||
order: "",
|
|
||||||
startDate: "",
|
|
||||||
endDate: "",
|
|
||||||
orderType: "VIRTUAL",
|
|
||||||
orderSn: "",
|
|
||||||
buyerName: "",
|
|
||||||
orderStatus: "",
|
|
||||||
});
|
|
||||||
const selectDate = ref(null);
|
|
||||||
const data = ref([]);
|
|
||||||
const total = ref(0);
|
|
||||||
const orderStatus = ref([
|
|
||||||
{ title: "全部", value: "" },
|
|
||||||
{ title: "未付款", value: "UNPAID" },
|
|
||||||
{ title: "已付款", value: "PAID" },
|
|
||||||
{ title: "待核验", value: "TAKE" },
|
|
||||||
{ title: "已完成", value: "COMPLETED" },
|
|
||||||
{ title: "已关闭", value: "CANCELLED" },
|
|
||||||
]);
|
|
||||||
const currentStatus = ref("");
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
function changePage() {
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
function changePageSize() {
|
|
||||||
searchForm.pageNumber = 1;
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
function handleSearch() {
|
|
||||||
searchForm.pageNumber = 1;
|
|
||||||
searchForm.pageSize = 20;
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
function changeSort(e) {
|
|
||||||
searchForm.sort = e.prop;
|
|
||||||
searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
function selectDateRange(v) {
|
|
||||||
if (v) {
|
|
||||||
searchForm.startDate = v[0];
|
|
||||||
searchForm.endDate = v[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getDataList() {
|
|
||||||
loading.value = true;
|
|
||||||
API_Order.getOrderList(searchForm).then((res) => {
|
|
||||||
loading.value = false;
|
|
||||||
if (res.success) {
|
|
||||||
data.value = res.result.records;
|
|
||||||
total.value = res.result.total;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function confirmPrice(v) {
|
|
||||||
ElMessageBox.confirm("您确定要收款吗?", "确认收款", { type: "warning" }).then(() => {
|
|
||||||
API_Order.orderPay(v.sn).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
ElMessage.success("收款成功");
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
|
||||||
function detail(v) {
|
|
||||||
let sn = v.sn;
|
|
||||||
customRouterPush({
|
|
||||||
name: "order-detail",
|
|
||||||
query: { sn: sn, orderType: v.orderType },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function onStatusTabClick(tab) {
|
|
||||||
const item = tab.paneName;
|
|
||||||
currentStatus.value = item;
|
|
||||||
searchForm.orderStatus = item;
|
|
||||||
getDataList();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.order-tab {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="order-detail-page">
|
<div class="order-detail-page">
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<h1>订单详情</h1>
|
||||||
|
<p>查看订单信息、商品明细与履约进度</p>
|
||||||
|
</div>
|
||||||
|
<el-button class="back-btn" :icon="ArrowLeft" @click="goBack">返回列表</el-button>
|
||||||
|
</div>
|
||||||
<!-- 顶部一体化状态与履约进度卡片 -->
|
<!-- 顶部一体化状态与履约进度卡片 -->
|
||||||
<el-card shadow="never" class="top-overview-card">
|
<el-card shadow="never" class="top-overview-card">
|
||||||
<!-- 第一层:返回导航 + 核心状态/单号 + 操作按钮组 -->
|
<!-- 第一层:返回导航 + 核心状态/单号 + 操作按钮组 -->
|
||||||
<div class="overview-header">
|
<div class="overview-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<el-button link class="back-btn" @click="goBack">
|
|
||||||
<el-icon><ArrowLeft /></el-icon>
|
|
||||||
<span>返回列表</span>
|
|
||||||
</el-button>
|
|
||||||
<div class="header-divider"></div>
|
|
||||||
<el-tag
|
<el-tag
|
||||||
:type="getOrderStatusTagType(orderInfo.order.orderStatus)"
|
:type="getOrderStatusTagType(orderInfo.order.orderStatus)"
|
||||||
effect="dark"
|
effect="light"
|
||||||
class="main-status-tag"
|
class="main-status-tag"
|
||||||
>
|
>
|
||||||
{{ orderInfo.orderStatusValue || "加载中..." }}
|
{{ orderInfo.orderStatusValue || "加载中..." }}
|
||||||
@@ -26,6 +28,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
:icon="CopyDocument"
|
:icon="CopyDocument"
|
||||||
class="copy-icon-btn"
|
class="copy-icon-btn"
|
||||||
|
aria-label="复制订单编号"
|
||||||
@click="copyText(orderInfo.order.sn || sn, '订单编号')"
|
@click="copyText(orderInfo.order.sn || sn, '订单编号')"
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -207,6 +210,7 @@
|
|||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
:icon="CopyDocument"
|
:icon="CopyDocument"
|
||||||
|
aria-label="复制手机号"
|
||||||
@click="copyText(orderInfo.order.consigneeMobile, '手机号')"
|
@click="copyText(orderInfo.order.consigneeMobile, '手机号')"
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -349,7 +353,6 @@
|
|||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
border
|
|
||||||
:data="data"
|
:data="data"
|
||||||
class="goods-table"
|
class="goods-table"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@@ -517,7 +520,7 @@
|
|||||||
<el-dialog v-model="orderCancelModal" title="取消订单" width="480px" destroy-on-close>
|
<el-dialog v-model="orderCancelModal" title="取消订单" width="480px" destroy-on-close>
|
||||||
<el-form ref="orderCancelFormRef" :model="orderCancelForm" label-width="90px" :rules="orderCancelValidate">
|
<el-form ref="orderCancelFormRef" :model="orderCancelForm" label-width="90px" :rules="orderCancelValidate">
|
||||||
<el-form-item label="取消原因" prop="reason">
|
<el-form-item label="取消原因" prop="reason">
|
||||||
<el-input v-model="orderCancelForm.reason" type="textarea" :rows="3" placeholder="请输入取消订单的具体原因" />
|
<el-input v-model="orderCancelForm.reason" type="textarea" :rows="3" resize="none" placeholder="请输入取消订单的具体原因" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -1403,510 +1406,229 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.order-detail-page {
|
.order-detail-page {
|
||||||
|
--detail-ink: #1e293b;
|
||||||
|
--detail-muted: #748196;
|
||||||
|
--detail-line: #e8edf4;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 28px;
|
max-width: 1680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 8px 12px 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--detail-ink);
|
||||||
|
|
||||||
// 顶部一体化状态与履约步骤卡片
|
.page-heading {
|
||||||
.top-overview-card {
|
display: flex;
|
||||||
border-radius: 10px;
|
align-items: center;
|
||||||
border: 1px solid #e2e8f0;
|
justify-content: space-between;
|
||||||
background: #fff;
|
gap: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 22px;
|
||||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.03);
|
h1 { margin: 0; font-size: 24px; font-weight: 650; line-height: 34px; letter-spacing: .5px; }
|
||||||
|
p { margin: 5px 0 0; color: var(--detail-muted); font-size: 13px; line-height: 22px; }
|
||||||
:deep(.el-card__body) {
|
.back-btn { border-radius: 8px; color: #526078; }
|
||||||
padding: 16px 22px 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-overview-card, .section-card {
|
||||||
|
border: 1px solid var(--detail-line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 3px 12px rgba(31, 51, 82, .025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-overview-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: linear-gradient(115deg, #f5f9ff 0, #fff 58%);
|
||||||
|
:deep(.el-card__body) { padding: 24px 26px; }
|
||||||
|
}
|
||||||
.overview-header {
|
.overview-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 14px;
|
gap: 20px;
|
||||||
padding-bottom: 16px;
|
}
|
||||||
border-bottom: 1px solid #f1f5f9;
|
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
.back-btn {
|
.main-status-tag { height: 34px; padding: 0 14px; border-radius: 7px; font-size: 15px; font-weight: 600; }
|
||||||
font-size: 14px;
|
.sn-title-group { display: flex; align-items: center; min-width: 0; font-size: 13px; }
|
||||||
font-weight: 600;
|
.sn-label { color: var(--detail-muted); white-space: nowrap; }
|
||||||
color: #475569;
|
.sn-value { color: #22324b; font-size: 16px; font-weight: 650; overflow-wrap: anywhere; }
|
||||||
padding: 4px 2px;
|
.copy-icon-btn { margin-left: 4px; }
|
||||||
|
.meta-tags { display: flex; gap: 6px; }
|
||||||
&:hover {
|
.meta-tags .el-tag { background: #fff; border-color: #e1e8f0; color: #748196; border-radius: 4px; }
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-divider {
|
|
||||||
width: 1px;
|
|
||||||
height: 16px;
|
|
||||||
background: #cbd5e1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-status-tag {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0 12px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sn-title-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.sn-label {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sn-value {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #0f172a;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-icon-btn {
|
|
||||||
margin-left: 4px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-tags {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
gap: 8px;
|
||||||
|
.el-button { margin: 0; border-radius: 7px; font-size: 13px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 紧凑型横向履约进度条
|
|
||||||
.fulfillment-stepper {
|
.fulfillment-stepper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
margin-top: 24px;
|
||||||
margin-top: 16px;
|
padding: 24px 8px 2px;
|
||||||
padding: 14px 24px;
|
border-top: 1px solid #e5edf8;
|
||||||
background: #f8fafc;
|
|
||||||
border: 1px solid #f1f5f9;
|
|
||||||
border-radius: 8px;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
.step-item { display: flex; align-items: center; flex: 1; min-width: 0; }
|
||||||
.step-item {
|
.step-item:last-child { flex: 0 0 auto; }
|
||||||
display: flex;
|
.step-node { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-node {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-circle {
|
.step-circle {
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 14px;
|
width: 34px;
|
||||||
font-weight: 700;
|
height: 34px;
|
||||||
border: 2px solid #cbd5e1;
|
box-sizing: border-box;
|
||||||
background: #fff;
|
|
||||||
color: #94a3b8;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
border-radius: 50%;
|
||||||
|
border: 1px solid #dce3ed;
|
||||||
.step-text {
|
background: #f7f9fc;
|
||||||
display: flex;
|
color: #8b98ab;
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
|
|
||||||
.step-title {
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #64748b;
|
}
|
||||||
line-height: 1.3;
|
.step-title { color: #8290a4; font-size: 14px; font-weight: 600; line-height: 22px; }
|
||||||
|
.step-desc { margin-top: 4px; color: #8b98ab; font-size: 12px; line-height: 18px; font-variant-numeric: tabular-nums; }
|
||||||
|
.step-connector { flex: 1; height: 2px; min-width: 16px; margin: 0 24px; background: #e8edf4; }
|
||||||
|
.is-done {
|
||||||
|
.step-circle { color: #20a684; background: #eaf8f2; border-color: #c7eadd; }
|
||||||
|
.step-title { color: #45566b; }
|
||||||
|
.step-connector { background: #b9e3d5; }
|
||||||
|
}
|
||||||
|
.is-active {
|
||||||
|
.step-circle { background: #3875ed; border-color: #3875ed; color: #fff; box-shadow: 0 0 0 5px #eaf1ff; }
|
||||||
|
.step-title, .step-desc { color: #326bdb; }
|
||||||
|
}
|
||||||
|
.is-cancelled .step-circle { color: #a3adbb; background: #f1f4f8; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-desc {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #94a3b8;
|
|
||||||
line-height: 1.3;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-connector {
|
|
||||||
flex: 1;
|
|
||||||
height: 2px;
|
|
||||||
background: #e2e8f0;
|
|
||||||
margin: 0 20px;
|
|
||||||
border-radius: 2px;
|
|
||||||
min-width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 已完成状态
|
|
||||||
&.is-done {
|
|
||||||
.step-circle {
|
|
||||||
border-color: #10b981;
|
|
||||||
background: #ecfdf5;
|
|
||||||
color: #10b981;
|
|
||||||
}
|
|
||||||
.step-text .step-title {
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
.step-text .step-desc {
|
|
||||||
color: #10b981;
|
|
||||||
}
|
|
||||||
.step-connector {
|
|
||||||
background: #10b981;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当前进行中状态
|
|
||||||
&.is-active {
|
|
||||||
.step-circle {
|
|
||||||
border-color: #3b82f6;
|
|
||||||
background: #3b82f6;
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.14);
|
|
||||||
}
|
|
||||||
.step-text .step-title {
|
|
||||||
color: #1e293b;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.step-text .step-desc {
|
|
||||||
color: #3b82f6;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 已取消状态
|
|
||||||
&.is-cancelled {
|
|
||||||
.step-circle {
|
|
||||||
border-color: #e2e8f0;
|
|
||||||
background: #f1f5f9;
|
|
||||||
color: #cbd5e1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 中部三列等高卡片网格
|
|
||||||
.info-grid-row {
|
.info-grid-row {
|
||||||
margin-bottom: 16px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
row-gap: 16px;
|
||||||
:deep(.el-col) {
|
margin-bottom: 20px;
|
||||||
display: flex;
|
:deep(.el-col) { display: flex; }
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.section-card {
|
.section-card {
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.03);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
&.equal-card { display: flex; flex-direction: column; height: 100%; }
|
||||||
&.equal-card {
|
&.equal-card :deep(.el-card__body) { flex: 1; }
|
||||||
display: flex;
|
:deep(.el-card__header) { padding: 18px 22px; background: #fff; border-bottom: 1px solid var(--detail-line); }
|
||||||
flex-direction: column;
|
:deep(.el-card__body) { padding: 20px 22px; }
|
||||||
height: 100%;
|
.card-title, .flex-title { display: flex; align-items: center; gap: 10px; }
|
||||||
|
.card-title { min-height: 24px; color: #25354b; font-size: 15px; font-weight: 600; }
|
||||||
:deep(.el-card__body) {
|
.card-title > .el-icon, .flex-title > .el-icon {
|
||||||
flex: 1;
|
box-sizing: content-box;
|
||||||
}
|
padding: 6px;
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-card__header) {
|
|
||||||
padding: 13px 18px;
|
|
||||||
border-bottom: 1px solid #f1f5f9;
|
|
||||||
background: #fafbfc;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
padding: 16px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1e293b;
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #ff6a00;
|
color: #4c7bdd;
|
||||||
|
background: #eff4ff;
|
||||||
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
|
.justify-between { justify-content: space-between; }
|
||||||
&.justify-between {
|
.count-badge { margin-left: 4px; font-size: 12px; font-weight: 400; color: #748196; border: 0; background: #f3f5f9; }
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.count-badge {
|
|
||||||
margin-left: 4px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 水平对齐键值对列表
|
|
||||||
.kv-list {
|
.kv-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 17px;
|
||||||
|
.kv-row { display: flex; align-items: flex-start; gap: 12px; font-size: 13px; line-height: 22px; }
|
||||||
.kv-row {
|
.kv-label { width: 76px; flex-shrink: 0; color: var(--detail-muted); }
|
||||||
display: flex;
|
.kv-val { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
||||||
align-items: flex-start;
|
.address-val { line-height: 23px; }
|
||||||
font-size: 13px;
|
.flex-col { flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||||
line-height: 20px;
|
.member-name-text { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.sub-pay-method, .f-label { font-size: 12px; color: var(--detail-muted); }
|
||||||
.kv-label {
|
.form-ans-line { font-size: 13px; line-height: 22px; }
|
||||||
width: 82px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: #64748b;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.kv-val {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
color: #1e293b;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
&.address-val {
|
|
||||||
line-height: 20px;
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.flex-col {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-name-text {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-pay-method {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-ans-line {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #334155;
|
|
||||||
line-height: 20px;
|
|
||||||
|
|
||||||
.f-label {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 商品表格与底部结算卡
|
|
||||||
.goods-section-card {
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
.goods-table {
|
.goods-table {
|
||||||
:deep(th.el-table__cell) {
|
--el-table-border-color: #edf1f6;
|
||||||
background-color: #f8fafc;
|
--el-table-header-bg-color: #f7f9fc;
|
||||||
color: #475569;
|
--el-table-row-hover-bg-color: #f8faff;
|
||||||
font-weight: 600;
|
color: #43516a;
|
||||||
height: 44px;
|
:deep(th.el-table__cell) { height: 46px; background: #f7f9fc; color: #6b788d; font-size: 12px; font-weight: 500; }
|
||||||
|
:deep(td.el-table__cell) { padding: 18px 0; }
|
||||||
|
:deep(.cell) { padding-left: 16px; padding-right: 16px; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 商品列表单元格
|
|
||||||
.goods-item-cell {
|
.goods-item-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
padding: 4px 0;
|
.goods-thumb { width: 68px; height: 68px; border-radius: 8px; border: 1px solid #e8edf4; flex-shrink: 0; }
|
||||||
|
.goods-info { flex: 1; min-width: 0; }
|
||||||
.goods-thumb {
|
.goods-name { color: #26364d; font-size: 14px; font-weight: 600; line-height: 22px; overflow-wrap: anywhere; }
|
||||||
width: 56px;
|
.goods-specs { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; }
|
||||||
height: 56px;
|
.spec-tag { font-size: 12px; color: #7b8799; background: #f4f6f9; padding: 2px 8px; border-radius: 4px; }
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
.sub-header-title { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; font-weight: 600; }
|
||||||
.goods-info {
|
.sub-header-title .el-icon { color: #4c7bdd; }
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
.goods-name {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1e293b;
|
|
||||||
line-height: 18px;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goods-specs {
|
|
||||||
margin-top: 6px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.spec-tag {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #64748b;
|
|
||||||
background: #f1f5f9;
|
|
||||||
padding: 1px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-header-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1e293b;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
color: #ff6a00;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 商品清单底部金额汇总排版
|
|
||||||
.goods-summary-footer {
|
.goods-summary-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 16px;
|
margin-top: 24px;
|
||||||
|
.summary-rows { width: 360px; max-width: 100%; padding: 20px 24px; box-sizing: border-box; background: #f7f9fc; border: 1px solid #edf1f7; border-radius: 10px; display: flex; flex-direction: column; gap: 12px; }
|
||||||
.summary-rows {
|
.summary-item { display: flex; align-items: center; justify-content: space-between; gap: 16px; font-size: 13px; }
|
||||||
width: 340px;
|
.label { color: var(--detail-muted); }
|
||||||
background: #f8fafc;
|
.value { font-weight: 500; }
|
||||||
border: 1px solid #f1f5f9;
|
.summary-divider { height: 1px; background: #e1e7f0; margin: 4px 0; }
|
||||||
border-radius: 8px;
|
.final-total { align-items: baseline; }
|
||||||
padding: 14px 20px;
|
.final-total .label { color: #334155; font-size: 14px; font-weight: 600; }
|
||||||
display: flex;
|
.final-price { font-size: 26px; font-weight: 650; white-space: nowrap; letter-spacing: -.5px; }
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.summary-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
}
|
||||||
|
.addr-select-line { display: flex; align-items: center; gap: 10px; }
|
||||||
.value {
|
.text-danger { color: #e45454; }
|
||||||
color: #1e293b;
|
.text-success { color: #20a684; }
|
||||||
font-weight: 500;
|
.text-warning { color: #d9972b; }
|
||||||
}
|
.text-primary { color: #3875ed; }
|
||||||
|
.text-gray { color: #8b98ab; }
|
||||||
&.final-total {
|
.text-dark { color: #1e293b; }
|
||||||
padding-top: 4px;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.final-price {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-divider {
|
|
||||||
height: 1px;
|
|
||||||
background: #e2e8f0;
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.addr-select-line {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-danger { color: #ef4444; }
|
|
||||||
.text-success { color: #10b981; }
|
|
||||||
.text-warning { color: #f59e0b; }
|
|
||||||
.text-primary { color: #2563eb; }
|
|
||||||
.text-gray { color: #94a3b8; }
|
|
||||||
.text-dark { color: #0f172a; }
|
|
||||||
.font-bold { font-weight: 600; }
|
.font-bold { font-weight: 600; }
|
||||||
.font-mono { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; }
|
.font-mono { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; font-variant-numeric: tabular-nums; }
|
||||||
.mt_16 { margin-top: 16px; }
|
.mt_16 { margin-top: 16px; }
|
||||||
.mb_10 { margin-bottom: 10px; }
|
.mb_10 { margin-bottom: 10px; }
|
||||||
.pr_12 { padding-right: 12px; }
|
.pr_12 { padding-right: 12px; }
|
||||||
|
|
||||||
|
@media (max-width: 1440px) {
|
||||||
|
padding: 6px 4px 28px;
|
||||||
|
.overview-header { align-items: flex-start; }
|
||||||
|
.header-actions { width: 100%; padding-top: 16px; border-top: 1px solid #e5edf8; }
|
||||||
|
.fulfillment-stepper { margin-top: 20px; }
|
||||||
|
.section-card :deep(.el-card__header), .section-card :deep(.el-card__body) { padding-left: 18px; padding-right: 18px; }
|
||||||
|
.kv-list .kv-row { gap: 8px; }
|
||||||
|
.kv-list .kv-label { width: 70px; }
|
||||||
|
.fulfillment-stepper .step-connector { margin: 0 16px; }
|
||||||
|
}
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.info-grid-row :deep(.el-col) { width: 100%; max-width: 100%; flex: 0 0 100%; }
|
||||||
|
.fulfillment-stepper .step-node { flex-direction: column; align-items: flex-start; gap: 10px; }
|
||||||
|
.fulfillment-stepper .step-item { align-items: flex-start; }
|
||||||
|
.fulfillment-stepper .step-connector { margin-top: 17px; }
|
||||||
|
.fulfillment-stepper .step-desc { max-width: 120px; }
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
padding: 4px 0 20px;
|
||||||
|
.page-heading { margin-bottom: 16px; }
|
||||||
|
.page-heading h1 { font-size: 21px; }
|
||||||
|
.page-heading p { max-width: 190px; }
|
||||||
|
.top-overview-card :deep(.el-card__body) { padding: 18px 16px; }
|
||||||
|
.header-left { gap: 10px; }
|
||||||
|
.header-left .sn-title-group { width: 100%; }
|
||||||
|
.header-left .sn-value { font-size: 13px; }
|
||||||
|
.fulfillment-stepper { align-items: stretch; flex-direction: column; gap: 20px; padding: 20px 4px 0; }
|
||||||
|
.fulfillment-stepper .step-item { flex: auto; }
|
||||||
|
.fulfillment-stepper .step-node { flex-direction: row; align-items: center; }
|
||||||
|
.fulfillment-stepper .step-desc { max-width: none; }
|
||||||
|
.fulfillment-stepper .step-connector { display: none; }
|
||||||
|
.goods-summary-footer .summary-rows { width: 100%; padding: 18px; }
|
||||||
|
.goods-summary-footer .final-price { font-size: 23px; }
|
||||||
|
.section-card .flex-title { flex-wrap: wrap; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,10 @@
|
|||||||
<el-form-item label="ICP备案号" prop="icp">
|
<el-form-item label="ICP备案号" prop="icp">
|
||||||
<el-input v-model="formValidate.icp" />
|
<el-input v-model="formValidate.icp" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="版权归属" prop="copyrightOwner">
|
||||||
|
<el-input v-model="formValidate.copyrightOwner" placeholder="留空时使用商城名称" maxlength="100" />
|
||||||
|
<span class="field-desc">仅填写版权归属名称,Copyright © 和当前年份由系统自动显示</span>
|
||||||
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="submit-bar">
|
<div class="submit-bar">
|
||||||
@@ -91,6 +95,7 @@ const formValidate = ref({
|
|||||||
buyerSideLogo: "",
|
buyerSideLogo: "",
|
||||||
companyName: "",
|
companyName: "",
|
||||||
companyUrl: "",
|
companyUrl: "",
|
||||||
|
copyrightOwner: "",
|
||||||
domainLogo: "",
|
domainLogo: "",
|
||||||
icp: "",
|
icp: "",
|
||||||
storeSideLogo: "",
|
storeSideLogo: "",
|
||||||
@@ -145,8 +150,9 @@ function setupSetting() {
|
|||||||
}
|
}
|
||||||
function init() {
|
function init() {
|
||||||
result.value = JSON.parse(props.res);
|
result.value = JSON.parse(props.res);
|
||||||
formValidate.value = { ...result.value };
|
formValidate.value = { copyrightOwner: "", ...result.value };
|
||||||
Object.keys(result.value).forEach((item) => {
|
Object.keys(result.value).forEach((item) => {
|
||||||
|
if (item === "copyrightOwner") return;
|
||||||
ruleValidate[item] = [
|
ruleValidate[item] = [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -69,10 +69,6 @@ export const saveCategoryBrand = (category_id, params) => {
|
|||||||
export const getBrandListData = params => {
|
export const getBrandListData = params => {
|
||||||
return getRequest("/goods/brand/all", params);
|
return getRequest("/goods/brand/all", params);
|
||||||
};
|
};
|
||||||
// 获取所有可用规格
|
|
||||||
export const getSpecificationList = params => {
|
|
||||||
return getRequest("/goods/spec/all", params);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取店铺分类列表数据
|
// 获取店铺分类列表数据
|
||||||
export const getAllList = id => {
|
export const getAllList = id => {
|
||||||
|
|||||||
33
seller/src/composables/useSiteSettings.js
Normal file
33
seller/src/composables/useSiteSettings.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { getBaseSite } from "@/api/common.js";
|
||||||
|
import config from "@/config";
|
||||||
|
|
||||||
|
// 同页的页头、页脚共享请求;每次重新进入页面时刷新,避免旧缓存覆盖后台设置。
|
||||||
|
const settings = ref(null);
|
||||||
|
let pendingRequest;
|
||||||
|
|
||||||
|
export function loadSiteSettings() {
|
||||||
|
if (!pendingRequest) {
|
||||||
|
pendingRequest = getBaseSite()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success && res.result?.settingValue) {
|
||||||
|
settings.value = JSON.parse(res.result.settingValue);
|
||||||
|
}
|
||||||
|
return settings.value;
|
||||||
|
})
|
||||||
|
.catch(() => settings.value)
|
||||||
|
.finally(() => { pendingRequest = null; });
|
||||||
|
}
|
||||||
|
return pendingRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSiteSettings() {
|
||||||
|
onMounted(loadSiteSettings);
|
||||||
|
const siteName = computed(() => settings.value?.siteName || config.title);
|
||||||
|
const companyName = computed(() => settings.value ? settings.value.companyName || "" : config.company?.name || "");
|
||||||
|
const companyUrl = computed(() => settings.value ? settings.value.companyUrl || "" : config.company?.href || "");
|
||||||
|
const website = computed(() => settings.value ? settings.value.website || "" : config.website || "");
|
||||||
|
const icp = computed(() => settings.value ? settings.value.icp || "" : config.icpMessage || "");
|
||||||
|
const copyrightOwner = computed(() => settings.value?.copyrightOwner?.trim() || siteName.value);
|
||||||
|
return { settings, siteName, companyName, companyUrl, website, icp, copyrightOwner };
|
||||||
|
}
|
||||||
@@ -113,9 +113,9 @@
|
|||||||
z-index: 4000 !important;
|
z-index: 4000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 全局禁止 textarea 拖拽改变大小 */
|
/* 全局禁止多行输入框拖动缩放,覆盖组件生成的内联 resize 样式。 */
|
||||||
.el-textarea__inner {
|
.el-textarea__inner {
|
||||||
resize: none;
|
resize: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 统一空状态:表格 / 树 / 列表无数据时的展示(未自定义 #empty 插槽的页面均生效) */
|
/* 统一空状态:表格 / 树 / 列表无数据时的展示(未自定义 #empty 插槽的页面均生效) */
|
||||||
|
|||||||
@@ -99,15 +99,15 @@
|
|||||||
<div class="login-btn"><a @click="$router.push('login')">前往登录</a></div>
|
<div class="login-btn"><a @click="$router.push('login')">前往登录</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="foot">
|
<div class="foot">
|
||||||
<el-row justify="space-around" class="help">
|
<el-row v-if="website" justify="space-around" class="help">
|
||||||
<a class="item" href="https://pickmall.cn/" target="_blank">帮助</a>
|
<a class="item" :href="website" target="_blank">帮助</a>
|
||||||
<a class="item" href="https://pickmall.cn/" target="_blank">隐私</a>
|
<a class="item" :href="website" target="_blank">隐私</a>
|
||||||
<a class="item" href="https://pickmall.cn/" target="_blank">条款</a>
|
<a class="item" :href="website" target="_blank">条款</a>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row justify="center" class="copyright">
|
<el-row justify="center" class="copyright">
|
||||||
Copyright © {{ year }} - Present
|
Copyright © {{ year }}
|
||||||
<a href="https://pickmall.cn/" target="_blank" style="margin: 0 5px">{{
|
<a :href="website || undefined" target="_blank" style="margin: 0 5px">{{
|
||||||
siteName
|
copyrightOwner
|
||||||
}}</a>
|
}}</a>
|
||||||
版权所有
|
版权所有
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -124,7 +124,7 @@ import { md5 } from "@/utils/md5.js";
|
|||||||
import * as RegExp from "@/utils/regular.js";
|
import * as RegExp from "@/utils/regular.js";
|
||||||
import * as apiLogin from "@/api/index.js";
|
import * as apiLogin from "@/api/index.js";
|
||||||
import { sendSms } from "@/api/common.js";
|
import { sendSms } from "@/api/common.js";
|
||||||
import util from "@/libs/util.js";
|
import { useSiteSettings } from "@/composables/useSiteSettings";
|
||||||
import verify from "@/views/my-components/verify";
|
import verify from "@/views/my-components/verify";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
|
|
||||||
@@ -146,8 +146,7 @@ const form = reactive({
|
|||||||
oncePasd: "",
|
oncePasd: "",
|
||||||
});
|
});
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
// 站点名优先取接口下发缓存,兜底用本地配置
|
const { website, copyrightOwner } = useSiteSettings();
|
||||||
const siteName = util.getSiteName();
|
|
||||||
const step = ref(0);
|
const step = ref(0);
|
||||||
const ruleInline = {
|
const ruleInline = {
|
||||||
mobile: [
|
mobile: [
|
||||||
|
|||||||
@@ -2,46 +2,30 @@
|
|||||||
<div class="foot">
|
<div class="foot">
|
||||||
<div class="information footer-bottom">
|
<div class="information footer-bottom">
|
||||||
<div class="icp">
|
<div class="icp">
|
||||||
<li v-if="config.icpCard">
|
<li v-if="icp" class="footer-bottom">
|
||||||
<a
|
|
||||||
:href="
|
|
||||||
'https://tsm.miit.gov.cn/dxxzsp/xkz/xkzgl/resource/qiyesearch.jsp?num=' +
|
|
||||||
config.icpCard +
|
|
||||||
'&type=xuke'
|
|
||||||
"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ config.icpCard }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li v-if="config.icpMessage" class="footer-bottom">
|
|
||||||
<a href="https://beian.miit.gov.cn/" target="_blank">
|
<a href="https://beian.miit.gov.cn/" target="_blank">
|
||||||
{{ config.icpMessage }}
|
{{ icp }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<a class="company-link" :href="companyUrl" target="_blank">
|
<a v-if="companyName" class="company-link" :href="companyUrl || undefined" target="_blank">
|
||||||
<img class="zhizhao" :src="zhizhaoImg" alt="" />
|
<img class="zhizhao" :src="zhizhaoImg" alt="" />
|
||||||
{{ companyName }}
|
{{ companyName }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="copyright">
|
<div class="copyright">
|
||||||
<p>Copyright © {{ year }} {{ siteName }}</p>
|
<p>Copyright © {{ year }} {{ copyrightOwner }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import config from "@/config/index";
|
import { useSiteSettings } from "@/composables/useSiteSettings";
|
||||||
import util from "@/libs/util.js";
|
|
||||||
import zhizhaoImg from "@/assets/images/zhizhao.jpg";
|
import zhizhaoImg from "@/assets/images/zhizhao.jpg";
|
||||||
|
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
// 站点名/公司信息优先取接口下发缓存,兜底用本地配置
|
const { companyName, companyUrl, icp, copyrightOwner } = useSiteSettings();
|
||||||
const siteName = util.getSiteName();
|
|
||||||
const companyName = localStorage.getItem("sellerCompanyName") || config.company.name;
|
|
||||||
const companyUrl = localStorage.getItem("sellerCompanyUrl") || config.company.href;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -8,27 +8,15 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { getBaseSite } from "@/api/common.js";
|
import { loadSiteSettings } from "@/composables/useSiteSettings";
|
||||||
|
|
||||||
const storeSideLogo = ref(require("@/assets/logo-lilishop.png"));
|
const storeSideLogo = ref(require("@/assets/logo-lilishop.png"));
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (
|
|
||||||
!localStorage.getItem("sellersiteName") ||
|
|
||||||
!localStorage.getItem("sellerlogoImg") ||
|
|
||||||
!localStorage.getItem("sellerIconImg") ||
|
|
||||||
!localStorage.getItem("seller_expiration_time")
|
|
||||||
) {
|
|
||||||
getSite();
|
getSite();
|
||||||
} else if (new Date() > localStorage.getItem("seller_expiration_time")) {
|
|
||||||
getSite();
|
|
||||||
} else {
|
|
||||||
storeSideLogo.value = localStorage.getItem("sellerlogoImg");
|
|
||||||
window.document.title = localStorage.getItem("sellersiteName");
|
|
||||||
applyFavicon(localStorage.getItem("sellerIconImg"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function applyFavicon(href) {
|
function applyFavicon(href) {
|
||||||
|
if (!href) return;
|
||||||
const link =
|
const link =
|
||||||
document.querySelector("link[rel*='icon']") ||
|
document.querySelector("link[rel*='icon']") ||
|
||||||
document.createElement("link");
|
document.createElement("link");
|
||||||
@@ -38,10 +26,9 @@ function applyFavicon(href) {
|
|||||||
document.getElementsByTagName("head")[0].appendChild(link);
|
document.getElementsByTagName("head")[0].appendChild(link);
|
||||||
}
|
}
|
||||||
function getSite() {
|
function getSite() {
|
||||||
getBaseSite()
|
loadSiteSettings()
|
||||||
.then((res) => {
|
.then((data) => {
|
||||||
if (res.success && res.result.settingValue) {
|
if (data) {
|
||||||
const data = JSON.parse(res.result.settingValue);
|
|
||||||
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
||||||
localStorage.setItem("seller_expiration_time", expirationTime);
|
localStorage.setItem("seller_expiration_time", expirationTime);
|
||||||
localStorage.setItem("sellersiteName", data.siteName);
|
localStorage.setItem("sellersiteName", data.siteName);
|
||||||
@@ -49,8 +36,8 @@ function getSite() {
|
|||||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
||||||
localStorage.setItem("sellerCompanyName", data.companyName || "");
|
localStorage.setItem("sellerCompanyName", data.companyName || "");
|
||||||
localStorage.setItem("sellerCompanyUrl", data.companyUrl || "");
|
localStorage.setItem("sellerCompanyUrl", data.companyUrl || "");
|
||||||
storeSideLogo.value = data.storeSideLogo;
|
storeSideLogo.value = data.storeSideLogo || storeSideLogo.value;
|
||||||
window.document.title = data.siteName;
|
if (data.siteName) window.document.title = data.siteName;
|
||||||
applyFavicon(data.storeSideIcon);
|
applyFavicon(data.storeSideIcon);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user