mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-10 02:49:03 +08:00
refactor(layout): 重构商家后台布局与导航结构
- 移除多标签页内嵌模式,详情页固定在新窗口打开 - 重构顶部栏与侧边栏布局,Logo 移至顶部栏区域 - 新增未读消息角标,与消息页面实时同步数量 - 新增面包屑路径栏替代静态页面标题展示 - 删除页面配置抽屉和菜单搜索组件 - 将发货与退货地址设置拆分为独立页面(店铺信息、地址管理) - 增加 Logo 加载失败回退及 favicon 健壮性处理 - 注册店铺信息与地址管理页面的显式懒加载映射
This commit is contained in:
@@ -87,6 +87,11 @@ export const getShopInfo = () => {
|
|||||||
return getRequest(`/settings/storeSettings`)
|
return getRequest(`/settings/storeSettings`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取商家入驻详细信息
|
||||||
|
export const getShopEntryDetail = () => {
|
||||||
|
return getRequest(`/settings/storeSettings/storeDetail`)
|
||||||
|
}
|
||||||
|
|
||||||
// 保存商家详细信息
|
// 保存商家详细信息
|
||||||
export const saveShopInfo = (params) => {
|
export const saveShopInfo = (params) => {
|
||||||
return putRequest(`/settings/storeSettings`, params)
|
return putRequest(`/settings/storeSettings`, params)
|
||||||
@@ -127,5 +132,3 @@ export const editShipTemplate = (id, params, headers) => {
|
|||||||
export const editChecked = (logisticsId,params) => {
|
export const editChecked = (logisticsId,params) => {
|
||||||
return putRequest(`/other/logistics/${logisticsId}/updateStoreLogistics`,params)
|
return putRequest(`/other/logistics/${logisticsId}/updateStoreLogistics`,params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
mainColor: "#ff5c58", //主题色
|
mainColor: "#ff5c58", //主题色
|
||||||
setting: {
|
setting: {
|
||||||
isUseTabsRouter: false, // 多标签页内嵌模式默认关闭,用户可在页面配置中开启。
|
isUseTabsRouter: false, // 商家后台固定关闭多标签页内嵌模式,详情使用新页面展示。
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"setting": {
|
"setting": {
|
||||||
"isUseTabsRouter": true,
|
"isUseTabsRouter": false,
|
||||||
"showFooter": false
|
"showFooter": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
export default (url) =>()=>import(`@/views/${url}.vue`)
|
const explicitViews = {
|
||||||
|
"shop/shopInfo": () => import("@/views/shop/shopInfo.vue"),
|
||||||
|
"shop/addressManage": () => import("@/views/shop/addressManage.vue"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (url) => explicitViews[url] || (() => import(`@/views/${url}.vue`));
|
||||||
|
|||||||
@@ -489,6 +489,24 @@ export const result = [{
|
|||||||
component: "shop/shopSetting",
|
component: "shop/shopSetting",
|
||||||
children: null,
|
children: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "shopInfo",
|
||||||
|
level: 2,
|
||||||
|
type: 0,
|
||||||
|
title: "店铺信息",
|
||||||
|
path: "shopInfo",
|
||||||
|
component: "shop/shopInfo",
|
||||||
|
children: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "addressManage",
|
||||||
|
level: 2,
|
||||||
|
type: 0,
|
||||||
|
title: "地址管理",
|
||||||
|
path: "addressManage",
|
||||||
|
component: "shop/addressManage",
|
||||||
|
children: null,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "wapList",
|
name: "wapList",
|
||||||
level: 2,
|
level: 2,
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ export const useAppStore = defineStore("app", {
|
|||||||
routers: [otherRouter],
|
routers: [otherRouter],
|
||||||
tagsList: [...otherRouter.children],
|
tagsList: [...otherRouter.children],
|
||||||
refMenu: "",
|
refMenu: "",
|
||||||
|
unreadMessageCount: 0,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
|
/** 更新顶部未读消息数量。 */
|
||||||
|
setUnreadMessageCount(count) {
|
||||||
|
this.unreadMessageCount = Math.max(0, Number(count) || 0);
|
||||||
|
},
|
||||||
/** 清理账号绑定的路由与标签目录,保留静态页面,防止切换账号后复用旧菜单。 */
|
/** 清理账号绑定的路由与标签目录,保留静态页面,防止切换账号后复用旧菜单。 */
|
||||||
resetNavigationRoutes() {
|
resetNavigationRoutes() {
|
||||||
this.routers = [otherRouter];
|
this.routers = [otherRouter];
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ const localSetting = window.localStorage.getItem("setting");
|
|||||||
|
|
||||||
export const useSettingStore = defineStore("setting", {
|
export const useSettingStore = defineStore("setting", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
setting: localSetting ? JSON.parse(localSetting) : setting,
|
// 兼容已保存的其他偏好,旧浏览器中的多标签开启状态统一归为关闭。
|
||||||
|
setting: { ...setting, ...(localSetting ? JSON.parse(localSetting) : {}), isUseTabsRouter: false },
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
|
/** 更新其他页面偏好时,仍保持商家多标签模式关闭。 */
|
||||||
updateSetting(data) {
|
updateSetting(data) {
|
||||||
this.setting = data;
|
this.setting = { ...data, isUseTabsRouter: false };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const useUserStore = defineStore("user", {
|
|||||||
appStore.updateMenulist([]);
|
appStore.updateMenulist([]);
|
||||||
appStore.setCurrNav("");
|
appStore.setCurrNav("");
|
||||||
appStore.setAdded(false);
|
appStore.setAdded(false);
|
||||||
|
appStore.setUnreadMessageCount(0);
|
||||||
appStore.resetNavigationRoutes();
|
appStore.resetNavigationRoutes();
|
||||||
appStore.storeOpenedList = [{ title: "首页", path: "", name: "home_index" }];
|
appStore.storeOpenedList = [{ title: "首页", path: "", name: "home_index" }];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,21 +51,12 @@ export function enCode(v1) {
|
|||||||
|
|
||||||
import {router} from "@/router/index";
|
import {router} from "@/router/index";
|
||||||
/**
|
/**
|
||||||
* 自定义跳转
|
* 打开详情页面;商家后台固定关闭多标签页内嵌模式,不再读取旧浏览器偏好。
|
||||||
|
* @param push Vue Router 路由位置
|
||||||
*/
|
*/
|
||||||
export function customRouterPush(push){
|
export function customRouterPush(push){
|
||||||
const setting = window.localStorage.getItem('setting') ? JSON.parse(window.localStorage.getItem('setting')) : {};
|
const url = router.resolve(push);
|
||||||
|
window.open(url.href, '_blank');
|
||||||
if(setting.isUseTabsRouter){
|
|
||||||
router.push(push)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if(Object.keys(setting).length == 0){router.push(push)}
|
|
||||||
else{
|
|
||||||
let url = router.resolve(push);
|
|
||||||
window.open(url.href, '_blank');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,29 +5,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="sidebar-menu-con menu-bar">
|
<div class="sidebar-menu-con menu-bar">
|
||||||
<div class="logo-con">
|
|
||||||
<img alt="店铺 Logo" :src="storeSideLogo" key="max-logo" />
|
|
||||||
<span class="brand-caption">商家工作台</span>
|
|
||||||
</div>
|
|
||||||
<shrinkable-menu />
|
<shrinkable-menu />
|
||||||
</div>
|
</div>
|
||||||
<div class="main-header-con" :style="{ height: setting.isUseTabsRouter ? '106px' : '60px' }">
|
<div class="main-header-con">
|
||||||
|
<div class="logo-con">
|
||||||
|
<img alt="平台 Logo" :src="storeSideLogo" key="max-logo" @error="onLogoError" />
|
||||||
|
</div>
|
||||||
<div class="main-header">
|
<div class="main-header">
|
||||||
<div class="header-avator-con">
|
<div class="header-avator-con">
|
||||||
<div class="header-page-info">
|
|
||||||
<span class="workspace-label">商家后台</span>
|
|
||||||
<span class="header-divider">/</span>
|
|
||||||
<strong>{{ currentPageTitle }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="user-dropdown-menu-con">
|
<div class="user-dropdown-menu-con">
|
||||||
<el-row
|
<el-row
|
||||||
justify="end"
|
justify="end"
|
||||||
align="middle"
|
align="middle"
|
||||||
class="user-dropdown-innercon"
|
class="user-dropdown-innercon"
|
||||||
>
|
>
|
||||||
<menu-search />
|
|
||||||
<store-qrcode />
|
<store-qrcode />
|
||||||
<ul class="nav-list">
|
<ul class="nav-list">
|
||||||
|
<li class="nav-item">
|
||||||
|
<el-tooltip
|
||||||
|
:content="unreadMessageCount > 0 ? `有 ${unreadMessageCount} 条未读消息` : '暂无未读消息'"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="header-icon-button header-message-btn"
|
||||||
|
:aria-label="unreadMessageCount > 0 ? `有 ${unreadMessageCount} 条未读消息` : '消息中心'"
|
||||||
|
@click="openMessagePage"
|
||||||
|
>
|
||||||
|
<el-badge
|
||||||
|
:value="unreadMessageCount"
|
||||||
|
:max="99"
|
||||||
|
:hidden="unreadMessageCount <= 0"
|
||||||
|
class="header-message-badge"
|
||||||
|
>
|
||||||
|
<el-icon :size="18"><Bell /></el-icon>
|
||||||
|
</el-badge>
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<el-tooltip content="联系客服" placement="bottom">
|
<el-tooltip content="联系客服" placement="bottom">
|
||||||
<el-button type="warning" size="small" :loading="load" @click="im">
|
<el-button type="warning" size="small" :loading="load" @click="im">
|
||||||
@@ -36,13 +51,6 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<el-tooltip content="设置" placement="bottom">
|
|
||||||
<button type="button" class="header-icon-button" aria-label="页面设置" @click="handleClickSetting">
|
|
||||||
<el-icon :size="17"><Setting /></el-icon>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<el-dropdown trigger="hover" @command="handleClickUserDropdown">
|
<el-dropdown trigger="hover" @command="handleClickUserDropdown">
|
||||||
<div class="dropList">
|
<div class="dropList">
|
||||||
@@ -66,17 +74,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tags-con" v-if="setting.isUseTabsRouter">
|
|
||||||
<tags-page-opened :pageTagsList="pageTagsList" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="single-page-con">
|
||||||
class="single-page-con"
|
<div class="page-path-bar">
|
||||||
:style="{
|
<div class="header-page-info">
|
||||||
top: setting.isUseTabsRouter ? '106px' : '60px',
|
<template v-for="(part, index) in currentPathParts" :key="index">
|
||||||
height: setting.isUseTabsRouter ? 'calc(100% - 116px)' : 'calc(100% - 70px)',
|
<span v-if="index > 0" class="header-divider">/</span>
|
||||||
}"
|
<strong v-if="index === currentPathParts.length - 1">{{ part }}</strong>
|
||||||
>
|
<span v-else class="workspace-label">{{ part }}</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="single-page">
|
<div class="single-page">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<component :is="Component" v-if="Component" :key="$route.fullPath" />
|
<component :is="Component" v-if="Component" :key="$route.fullPath" />
|
||||||
@@ -84,52 +92,77 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<circleLoading class="loading-position" v-show="loading" />
|
<circleLoading class="loading-position" v-show="loading" />
|
||||||
<configDrawer ref="configRef" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { ChatDotRound, Setting, UserFilled } from "@element-plus/icons-vue";
|
import { Bell, ChatDotRound, UserFilled } from "@element-plus/icons-vue";
|
||||||
import shrinkableMenu from "./main-components/shrinkable-menu/shrinkable-menu.vue";
|
import shrinkableMenu from "./main-components/shrinkable-menu/shrinkable-menu.vue";
|
||||||
import tagsPageOpened from "./main-components/tags-page-opened.vue";
|
|
||||||
import circleLoading from "@/views/my-components/lili/circle-loading.vue";
|
import circleLoading from "@/views/my-components/lili/circle-loading.vue";
|
||||||
import configDrawer from "@/views/main-components/config-drawer.vue";
|
|
||||||
import storeQrcode from "@/views/main-components/store-qrcode.vue";
|
import storeQrcode from "@/views/main-components/store-qrcode.vue";
|
||||||
import menuSearch from "@/views/main-components/menu-search.vue";
|
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import util from "@/libs/util.js";
|
import util from "@/libs/util.js";
|
||||||
import { logout } from "@/api/index";
|
import { flattenNavigation, findNavigationItem } from "@/libs/seller-navigation";
|
||||||
|
import defaultLogo from "@/assets/logo.png";
|
||||||
|
import { loadSiteSettings } from "@/composables/useSiteSettings";
|
||||||
|
import { getMessageSendData, logout, userMsg } from "@/api/index";
|
||||||
import { getIMDetail } from "@/api/common";
|
import { getIMDetail } from "@/api/common";
|
||||||
import { userMsg } from "@/api/index";
|
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { getStore, setStore } from "@/libs/storage";
|
import { getStore, setStore } from "@/libs/storage";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { useSettingStore } from "@/stores/setting";
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const settingStore = useSettingStore();
|
|
||||||
|
|
||||||
const configRef = ref(null);
|
|
||||||
const sliceNum = ref(5);
|
const sliceNum = ref(5);
|
||||||
const userInfo = ref({});
|
const userInfo = ref({});
|
||||||
const storeSideLogo = ref("");
|
const storeSideLogo = ref(defaultLogo);
|
||||||
const IMLink = ref("");
|
const IMLink = ref("");
|
||||||
const load = ref(false);
|
const load = ref(false);
|
||||||
|
|
||||||
const setting = computed(() => settingStore.setting);
|
|
||||||
const loading = computed(() => appStore.loading);
|
const loading = computed(() => appStore.loading);
|
||||||
|
const unreadMessageCount = computed(() => appStore.unreadMessageCount);
|
||||||
const pageTagsList = computed(() => appStore.storeOpenedList);
|
const pageTagsList = computed(() => appStore.storeOpenedList);
|
||||||
const currentPageTitle = computed(() => route.meta.title || pageTagsList.value.find((item) => item.name === route.name)?.title || '工作台');
|
const currentPageTitle = computed(() => route.meta.title || pageTagsList.value.find((item) => item.name === route.name)?.title || '工作台');
|
||||||
|
const navigationItems = computed(() => flattenNavigation(appStore.navigationMenus || []));
|
||||||
|
const currentPathParts = computed(() => {
|
||||||
|
const matched = findNavigationItem(navigationItems.value, route.name);
|
||||||
|
const rawParts = matched
|
||||||
|
? [
|
||||||
|
matched.topTitle,
|
||||||
|
matched.groupTitle,
|
||||||
|
matched.title,
|
||||||
|
route.name !== matched.name ? currentPageTitle.value : "",
|
||||||
|
]
|
||||||
|
: ["商家后台", currentPageTitle.value];
|
||||||
|
return rawParts.filter((part, idx, arr) => part && part !== arr[idx - 1]);
|
||||||
|
});
|
||||||
const lang = computed(() => appStore.lang);
|
const lang = computed(() => appStore.lang);
|
||||||
|
|
||||||
function handleClickSetting() {
|
/**
|
||||||
configRef.value.open();
|
* 跳转至系统消息页面。
|
||||||
|
*/
|
||||||
|
function openMessagePage() {
|
||||||
|
util.openNewPage("message_index");
|
||||||
|
router.push({ name: "message_index" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取当前店铺未读消息总数,更新右上角消息角标。
|
||||||
|
*/
|
||||||
|
function loadUnreadMessageCount() {
|
||||||
|
getMessageSendData({ status: "UN_READY", pageNumber: 1, pageSize: 1 })
|
||||||
|
.then((res) => {
|
||||||
|
if (res && res.success && res.result) {
|
||||||
|
appStore.setUnreadMessageCount(res.result.total);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function im() {
|
async function im() {
|
||||||
@@ -152,23 +185,67 @@ async function getIMDetailMethods() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新页面站点图标。
|
||||||
|
* @param {string} href 图标地址
|
||||||
|
*/
|
||||||
|
function applyFavicon(href) {
|
||||||
|
if (!href || href === "undefined" || href === "null") return;
|
||||||
|
const link =
|
||||||
|
document.querySelector("link[rel*='icon']") ||
|
||||||
|
document.createElement("link");
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
link.href = href;
|
||||||
|
link.rel = "shortcut icon";
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台 Logo 加载失败时回退为默认 Logo。
|
||||||
|
* @param {Event} e 图片加载错误事件
|
||||||
|
*/
|
||||||
|
function onLogoError(e) {
|
||||||
|
if (e?.target && e.target.src !== defaultLogo) {
|
||||||
|
e.target.src = defaultLogo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
const pathArr = util.setCurrentPath(route.name);
|
const pathArr = util.setCurrentPath(route.name);
|
||||||
if (pathArr.length >= 2) {
|
if (pathArr.length >= 2) {
|
||||||
appStore.addOpenSubmenu(pathArr[1].name);
|
appStore.addOpenSubmenu(pathArr[1].name);
|
||||||
}
|
}
|
||||||
storeSideLogo.value = localStorage.getItem("sellerlogoImg");
|
const cachedLogo = localStorage.getItem("sellerlogoImg");
|
||||||
|
storeSideLogo.value =
|
||||||
|
cachedLogo && cachedLogo !== "undefined" && cachedLogo !== "null"
|
||||||
|
? cachedLogo
|
||||||
|
: defaultLogo;
|
||||||
util.title(currentPageTitle.value);
|
util.title(currentPageTitle.value);
|
||||||
const link =
|
applyFavicon(localStorage.getItem("sellerIconImg"));
|
||||||
document.querySelector("link[rel*='icon']") ||
|
|
||||||
document.createElement("link");
|
loadSiteSettings()
|
||||||
link.type = "image/x-icon";
|
.then((data) => {
|
||||||
link.href = localStorage.getItem("sellerIconImg");
|
if (data) {
|
||||||
link.rel = "shortcut icon";
|
const logo = data.storeSideLogo || data.domainLogo || "";
|
||||||
document.getElementsByTagName("head")[0].appendChild(link);
|
const icon = data.storeSideIcon || data.domainIcon || "";
|
||||||
|
if (logo) {
|
||||||
|
localStorage.setItem("sellerlogoImg", logo);
|
||||||
|
storeSideLogo.value = logo;
|
||||||
|
}
|
||||||
|
if (icon) {
|
||||||
|
localStorage.setItem("sellerIconImg", icon);
|
||||||
|
applyFavicon(icon);
|
||||||
|
}
|
||||||
|
if (data.siteName) {
|
||||||
|
localStorage.setItem("sellersiteName", data.siteName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
userInfo.value = JSON.parse(Cookies.get("userInfoSeller"));
|
userInfo.value = JSON.parse(Cookies.get("userInfoSeller"));
|
||||||
checkTag(route.name);
|
checkTag(route.name);
|
||||||
|
loadUnreadMessageCount();
|
||||||
|
|
||||||
const currWidth = document.body.clientWidth;
|
const currWidth = document.body.clientWidth;
|
||||||
if (currWidth <= 1200) {
|
if (currWidth <= 1200) {
|
||||||
@@ -214,6 +291,7 @@ watch(
|
|||||||
}
|
}
|
||||||
checkTag(name);
|
checkTag(name);
|
||||||
util.title(currentPageTitle.value);
|
util.title(currentPageTitle.value);
|
||||||
|
loadUnreadMessageCount();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-drawer v-model="drawer" title="页面配置" size="300px">
|
|
||||||
<h3>内容设置</h3>
|
|
||||||
<div class="config-item flex flex-a-c flex-j-sb">
|
|
||||||
<div>
|
|
||||||
<el-tooltip
|
|
||||||
placement="bottom-end"
|
|
||||||
content="关闭之后部分页面点击'查看''详情'等按钮将跳到新页面展示"
|
|
||||||
>
|
|
||||||
<div>多标签Tab页内嵌模式</div>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
<el-switch v-model="setting.isUseTabsRouter" />
|
|
||||||
</div>
|
|
||||||
</el-drawer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, ref, watch } from "vue";
|
|
||||||
import { useSettingStore } from "@/stores/setting";
|
|
||||||
import { setStore } from "@/libs/storage";
|
|
||||||
|
|
||||||
defineOptions({ name: "configDrawer" });
|
|
||||||
|
|
||||||
const settingStore = useSettingStore();
|
|
||||||
|
|
||||||
const drawer = ref(false);
|
|
||||||
|
|
||||||
const setting = computed(() => settingStore.setting);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
setting,
|
|
||||||
(val) => {
|
|
||||||
setStore("setting", val);
|
|
||||||
settingStore.updateSetting(val);
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
drawer.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
drawer.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({ open, close });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
* {
|
|
||||||
color: #333 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 10px 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-item {
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-row class="header">
|
<el-row class="header">
|
||||||
<img alt="店铺 Logo" class="logo" :src="storeSideLogo" />
|
<img alt="平台 Logo" class="logo" :src="storeSideLogo" />
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -16,7 +16,7 @@ function init() {
|
|||||||
getSite();
|
getSite();
|
||||||
}
|
}
|
||||||
function applyFavicon(href) {
|
function applyFavicon(href) {
|
||||||
if (!href) return;
|
if (!href || href === "undefined" || href === "null") return;
|
||||||
const link =
|
const link =
|
||||||
document.querySelector("link[rel*='icon']") ||
|
document.querySelector("link[rel*='icon']") ||
|
||||||
document.createElement("link");
|
document.createElement("link");
|
||||||
@@ -29,16 +29,18 @@ function getSite() {
|
|||||||
loadSiteSettings()
|
loadSiteSettings()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const logo = data.storeSideLogo || data.domainLogo || "";
|
||||||
|
const icon = data.storeSideIcon || data.domainIcon || "";
|
||||||
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 || "");
|
||||||
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
|
if (logo) localStorage.setItem("sellerlogoImg", logo);
|
||||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
if (icon) localStorage.setItem("sellerIconImg", icon);
|
||||||
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;
|
storeSideLogo.value = logo || storeSideLogo.value;
|
||||||
if (data.siteName) window.document.title = data.siteName;
|
if (data.siteName) window.document.title = data.siteName;
|
||||||
applyFavicon(data.storeSideIcon);
|
applyFavicon(icon);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
<!--
|
|
||||||
商家功能搜索:仅检索当前账号已授权且已注册的菜单,支持键盘选择。
|
|
||||||
@author Bulbasaur
|
|
||||||
@since 2026-11-08
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<button type="button" class="menu-search-trigger" aria-label="搜索功能菜单" @click="openSearch">
|
|
||||||
<el-icon :size="17"><Search /></el-icon><span>搜索功能菜单</span><kbd>/</kbd>
|
|
||||||
</button>
|
|
||||||
<el-dialog v-model="visible" class="seller-menu-search" title="搜索功能" width="560px" align-center
|
|
||||||
@opened="focusInput" @closed="keyword = ''">
|
|
||||||
<el-input ref="inputRef" v-model="keyword" placeholder="搜索菜单,例如:商品、订单、优惠券" size="large" clearable
|
|
||||||
aria-label="搜索菜单" @keydown.down.prevent="moveSelection(1)" @keydown.up.prevent="moveSelection(-1)"
|
|
||||||
@keydown.enter.prevent="navigate(results[selected])">
|
|
||||||
<template #prefix><el-icon><Search /></el-icon></template>
|
|
||||||
</el-input>
|
|
||||||
<div class="search-summary">{{ keyword.trim() ? `找到 ${results.length} 个功能` : '全部可用功能' }}</div>
|
|
||||||
<div ref="resultsRef" class="search-results">
|
|
||||||
<button v-for="(item, index) in results" :key="`${item.topName}-${item.name}`" type="button"
|
|
||||||
class="search-result" :class="{ selected: selected === index }" @click="navigate(item)" @mouseenter="selected = index">
|
|
||||||
<div><strong>{{ item.title }}</strong><span>{{ item.topTitle }} / {{ item.groupTitle }}</span></div>
|
|
||||||
<el-icon><ArrowRight /></el-icon>
|
|
||||||
</button>
|
|
||||||
<el-empty v-if="!results.length" :image-size="72" description="没有匹配的功能,试试其他关键词" />
|
|
||||||
</div>
|
|
||||||
<template #footer><div class="search-help"><span>↑ ↓ 选择</span><span>Enter 打开</span><span>Esc 关闭</span></div></template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { Search, ArrowRight } from "@element-plus/icons-vue";
|
|
||||||
import { useAppStore } from "@/stores/app";
|
|
||||||
import { flattenNavigation, searchNavigation } from "@/libs/seller-navigation";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const visible = ref(false);
|
|
||||||
const keyword = ref("");
|
|
||||||
const selected = ref(0);
|
|
||||||
const inputRef = ref(null);
|
|
||||||
const resultsRef = ref(null);
|
|
||||||
const items = computed(() => flattenNavigation(appStore.navigationMenus).filter((item) => router.hasRoute(item.name)));
|
|
||||||
const results = computed(() => searchNavigation(items.value, keyword.value));
|
|
||||||
|
|
||||||
/** 每次打开清空上次关键词,避免残留搜索状态。 */
|
|
||||||
function openSearch() { keyword.value = ""; selected.value = 0; visible.value = true; }
|
|
||||||
/** 等弹层完成动画后聚焦输入框。 */
|
|
||||||
function focusInput() { inputRef.value?.focus(); }
|
|
||||||
/** 键盘移动候选项并滚动到可见区域;空列表不产生无效索引。 */
|
|
||||||
async function moveSelection(step) {
|
|
||||||
if (!results.value.length) return;
|
|
||||||
selected.value = (selected.value + step + results.value.length) % results.value.length;
|
|
||||||
await nextTick();
|
|
||||||
resultsRef.value?.children[selected.value]?.scrollIntoView({ block: "nearest" });
|
|
||||||
}
|
|
||||||
/** 只允许打开当前权限搜索结果;清空关闭的弹层避免遮挡业务页面。 */
|
|
||||||
function navigate(item) {
|
|
||||||
if (!item || !items.value.some((entry) => entry.name === item.name)) return;
|
|
||||||
visible.value = false;
|
|
||||||
router.push({ name: item.name });
|
|
||||||
}
|
|
||||||
/** 仅在非编辑区域按 / 唤起搜索,不截获输入框、富文本和其他弹窗内的按键。 */
|
|
||||||
function handleShortcut(event) {
|
|
||||||
if (event.key !== "/" || event.ctrlKey || event.metaKey || event.altKey || visible.value ||
|
|
||||||
event.target.closest?.('input, textarea, select, [contenteditable="true"], [role="dialog"]')) return;
|
|
||||||
event.preventDefault();
|
|
||||||
openSearch();
|
|
||||||
}
|
|
||||||
watch(results, () => { selected.value = 0; });
|
|
||||||
watch(() => appStore.navigationMenus, () => { visible.value = false; keyword.value = ""; });
|
|
||||||
onMounted(() => window.addEventListener("keydown", handleShortcut));
|
|
||||||
onBeforeUnmount(() => window.removeEventListener("keydown", handleShortcut));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.menu-search-trigger { width: 240px; height: 34px; display: flex; align-items: center; gap: 9px; padding: 0 12px; border: 1px solid #eceef3; border-radius: 7px; background: #f7f8fb; color: #969cab; font: inherit; font-size: 12px; cursor: pointer;
|
|
||||||
&:hover { border-color: #c5d2ff; color: #315efb; }
|
|
||||||
&:focus-visible { outline: 2px solid #315efb; }
|
|
||||||
kbd { margin-left: auto; padding: 0 5px; border: 1px solid #e0e4ed; border-radius: 3px; background: #fff; font-family: inherit; }
|
|
||||||
}
|
|
||||||
.search-summary { padding: 18px 2px 8px; font-size: 12px; color: #969cab; }
|
|
||||||
.search-results { max-height: 380px; overflow-y: auto; }
|
|
||||||
.search-result { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 12px; margin: 3px 0; border: 0; border-radius: 7px; background: #fff; text-align: left; cursor: pointer; color: #858c9c;
|
|
||||||
strong { display: block; font-size: 14px; font-weight: 500; color: #303749; }
|
|
||||||
span { display: block; margin-top: 5px; font-size: 12px; }
|
|
||||||
&.selected, &:focus-visible { background: #eef2ff; strong { color: #315efb; } }
|
|
||||||
}
|
|
||||||
.search-help { display: flex; gap: 20px; color: #969cab; font-size: 12px; }
|
|
||||||
@media (max-width: 1100px) { .menu-search-trigger { width: 180px; } }
|
|
||||||
</style>
|
|
||||||
@@ -78,7 +78,7 @@ watch([() => route.fullPath, () => appStore.navigationMenus], syncRouteMenu, { i
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 隐藏滚动条但保留滚轮、触控和键盘滚动,确保下方栏目仍可访问。
|
// 隐藏滚动条但保留滚轮、触控和键盘滚动,确保下方栏目仍可访问。
|
||||||
.seller-navigation { height: calc(100% - 64px); box-sizing: border-box; padding: 18px 14px 32px; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; background: #fff;
|
.seller-navigation { height: 100%; box-sizing: border-box; padding: 18px 14px 32px; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; background: #fff;
|
||||||
&::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
&::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
||||||
}
|
}
|
||||||
.nav-heading { width: 100%; display: flex; align-items: center; gap: 13px; min-height: 46px; padding: 11px 14px; border: 0; border-radius: 6px; background: transparent; color: #242832; text-align: left; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
|
.nav-heading { width: 100%; display: flex; align-items: center; gap: 13px; min-height: 46px; padding: 11px 14px; border: 0; border-radius: 6px; background: transparent; color: #242832; text-align: left; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
|
||||||
|
|||||||
@@ -24,15 +24,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-menu-con {
|
.sidebar-menu-con {
|
||||||
height: 100%;
|
height: calc(100% - 60px);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 60px;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 21;
|
z-index: 21;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
transition: width 0.3s; // background: rgb(73, 80, 96)
|
transition: width 0.3s; // background: rgb(73, 80, 96)
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-right: 1px solid #e9ecf2;
|
border-right: 1px solid #e4e7ed;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,12 +53,15 @@
|
|||||||
&-header-con {
|
&-header-con {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: block;
|
display: flex;
|
||||||
padding-left: 300px;
|
align-items: stretch;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #fff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 106px;
|
height: 60px;
|
||||||
z-index: 20;
|
z-index: 22;
|
||||||
border-bottom: 1px solid #e9ecf2;
|
border-bottom: 1px solid #e4e7ed;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transition: padding 0.3s;
|
transition: padding 0.3s;
|
||||||
}
|
}
|
||||||
@@ -125,10 +128,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 60px;
|
height: 100%;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
border-bottom: 1px solid #e9ecf2;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
@@ -158,7 +161,7 @@
|
|||||||
|
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
|
||||||
@@ -233,7 +236,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 300px;
|
left: 300px;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 106px;
|
top: 60px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -262,19 +265,21 @@
|
|||||||
transition: transform 0.3s;
|
transition: transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-con {
|
.main .logo-con {
|
||||||
width: 100%;
|
flex: 0 0 300px;
|
||||||
height: 64px;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 12px 16px;
|
padding: 8px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
justify-content: flex-start;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-bottom: 1px solid #e9ecf2;
|
|
||||||
img {
|
img {
|
||||||
max-height: 36px;
|
display: block;
|
||||||
max-width: 48px;
|
max-height: 44px;
|
||||||
|
max-width: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,6 +301,7 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
@@ -334,8 +340,19 @@
|
|||||||
|
|
||||||
// 导航与顶部栏的样式局限于商家外层布局,不覆盖业务页面的组件主题。
|
// 导航与顶部栏的样式局限于商家外层布局,不覆盖业务页面的组件主题。
|
||||||
.main {
|
.main {
|
||||||
.brand-caption { color: #252b3a; font-size: 15px; font-weight: 600; white-space: nowrap; }
|
.page-path-bar {
|
||||||
.header-page-info { display: flex; align-items: center; gap: 12px; min-width: 0; font-size: 13px;
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
.header-page-info { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 13px;
|
||||||
strong { color: #303749; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
strong { color: #303749; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
}
|
}
|
||||||
.workspace-label { color: #969cab; white-space: nowrap; }
|
.workspace-label { color: #969cab; white-space: nowrap; }
|
||||||
@@ -347,13 +364,20 @@
|
|||||||
&:hover { background: #f5f7fb; color: #315efb; }
|
&:hover { background: #f5f7fb; color: #315efb; }
|
||||||
&:focus-visible { outline: 2px solid #315efb; }
|
&:focus-visible { outline: 2px solid #315efb; }
|
||||||
}
|
}
|
||||||
|
.header-message-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
.single-page-con .single-page { margin: 16px; }
|
.single-page-con .single-page { margin: 16px; }
|
||||||
}
|
}
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.main {
|
.main {
|
||||||
.header-avator-con { padding: 0 16px; gap: 12px; }
|
.header-avator-con { padding: 0 16px; gap: 12px; }
|
||||||
|
.page-path-bar { padding: 8px 14px; }
|
||||||
.user-dropdown-innercon { gap: 10px; }
|
.user-dropdown-innercon { gap: 10px; }
|
||||||
.workspace-label, .header-divider, .header-store-name { display: none; }
|
.header-store-name { display: none; }
|
||||||
.single-page-con .single-page { margin: 12px; }
|
.single-page-con .single-page { margin: 12px; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,10 +84,12 @@ import { computed, onMounted, reactive, ref, watch } from "vue";
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { Bell, ChatDotRound, Clock, Delete } from "@element-plus/icons-vue";
|
import { Bell, ChatDotRound, Clock, Delete } from "@element-plus/icons-vue";
|
||||||
import * as API_Index from "@/api/index";
|
import * as API_Index from "@/api/index";
|
||||||
|
import { useAppStore } from "@/stores/app";
|
||||||
|
|
||||||
defineOptions({ name: "message_index" });
|
defineOptions({ name: "message_index" });
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const mesTypeList = ref([
|
const mesTypeList = ref([
|
||||||
@@ -153,7 +155,10 @@ function refreshMessage() {
|
|||||||
}
|
}
|
||||||
function refreshUnreadCount() {
|
function refreshUnreadCount() {
|
||||||
API_Index.getAllMessage(params).then((res) => {
|
API_Index.getAllMessage(params).then((res) => {
|
||||||
if (res.success) unReadCount.value = res.result.UN_READY.total;
|
if (res.success) {
|
||||||
|
unReadCount.value = res.result.UN_READY.total;
|
||||||
|
appStore.setUnreadMessageCount(unReadCount.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getAll() {
|
function getAll() {
|
||||||
@@ -161,6 +166,7 @@ function getAll() {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
unReadCount.value = res.result.UN_READY.total;
|
unReadCount.value = res.result.UN_READY.total;
|
||||||
|
appStore.setUnreadMessageCount(unReadCount.value);
|
||||||
currentMesList.value = res.result.UN_READY.records;
|
currentMesList.value = res.result.UN_READY.records;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
312
seller/src/views/shop/addressManage.vue
Normal file
312
seller/src/views/shop/addressManage.vue
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
<!--
|
||||||
|
商家端地址管理页面:集中维护店铺发货地址与退货(售后)地址。
|
||||||
|
@author Bulbasaur
|
||||||
|
@since 2026-11-09
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<el-card>
|
||||||
|
<div class="setting-page">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
label-width="150px"
|
||||||
|
label-position="right"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRules"
|
||||||
|
>
|
||||||
|
<div class="setting-section">
|
||||||
|
<div class="section-title">发货地址</div>
|
||||||
|
<el-form-item label="发货人姓名" prop="salesConsignorName">
|
||||||
|
<el-input
|
||||||
|
v-model="form.salesConsignorName"
|
||||||
|
maxlength="11"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入发货人姓名"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="发货人手机号" prop="salesConsignorMobile">
|
||||||
|
<el-input
|
||||||
|
v-model="form.salesConsignorMobile"
|
||||||
|
maxlength="11"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入发货人手机号"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="地址" prop="salesConsignorAddressId">
|
||||||
|
<span v-if="form.salesConsignorAddressPath" class="address-text">
|
||||||
|
{{ form.salesConsignorAddressPath }}
|
||||||
|
</span>
|
||||||
|
<el-button @click="handleChangeAddress('addressGoods')">选择</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="详细地址" prop="salesConsignorDetail">
|
||||||
|
<el-input
|
||||||
|
v-model="form.salesConsignorDetail"
|
||||||
|
clearable
|
||||||
|
maxlength="50"
|
||||||
|
placeholder="请输入详细地址"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-section">
|
||||||
|
<div class="section-title">退货地址</div>
|
||||||
|
<el-form-item label="收货人" prop="salesConsigneeName">
|
||||||
|
<el-input
|
||||||
|
v-model="form.salesConsigneeName"
|
||||||
|
maxlength="11"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入收货人姓名"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="收货人电话" prop="salesConsigneeMobile">
|
||||||
|
<el-input
|
||||||
|
v-model="form.salesConsigneeMobile"
|
||||||
|
maxlength="11"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入收货人电话"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="售后地址" prop="salesConsigneeAddressId">
|
||||||
|
<span v-if="form.salesConsigneeAddressPath" class="address-text">
|
||||||
|
{{ form.salesConsigneeAddressPath }}
|
||||||
|
</span>
|
||||||
|
<el-button @click="handleChangeAddress('salesConsigneeAddressPath')">选择</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="详细地址" prop="salesConsigneeDetail">
|
||||||
|
<el-input
|
||||||
|
v-model="form.salesConsigneeDetail"
|
||||||
|
clearable
|
||||||
|
maxlength="50"
|
||||||
|
placeholder="请输入详细地址"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="submit-bar">
|
||||||
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<multipleMap ref="mapRef" @callback="callbackAddress" />
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import * as API_Shop from "@/api/shops";
|
||||||
|
import multipleMap from "@/views/my-components/map/multiple-map";
|
||||||
|
import * as RegExp from "@/libs/RegExp.js";
|
||||||
|
import { Message } from "@/utils/message";
|
||||||
|
|
||||||
|
defineOptions({ name: "addressManage" });
|
||||||
|
|
||||||
|
const currentKey = ref("");
|
||||||
|
const submitLoading = ref(false);
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
salesConsignorName: "",
|
||||||
|
salesConsignorMobile: "",
|
||||||
|
salesConsignorAddressId: "",
|
||||||
|
salesConsignorAddressPath: "",
|
||||||
|
salesConsignorDetail: "",
|
||||||
|
salesConsigneeName: "",
|
||||||
|
salesConsigneeMobile: "",
|
||||||
|
salesConsigneeAddressId: "",
|
||||||
|
salesConsigneeAddressPath: "",
|
||||||
|
salesConsigneeDetail: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRules = {
|
||||||
|
salesConsignorName: [
|
||||||
|
{ required: true, message: "请输入发货人姓名", trigger: "blur" },
|
||||||
|
],
|
||||||
|
salesConsignorMobile: [
|
||||||
|
{ required: true, message: "手机号不能为空", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: RegExp.mobile,
|
||||||
|
trigger: "blur",
|
||||||
|
message: "请输入正确的手机号",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
salesConsignorDetail: [
|
||||||
|
{ required: true, message: "请输入详细地址", trigger: "blur" },
|
||||||
|
],
|
||||||
|
salesConsigneeName: [
|
||||||
|
{ required: true, message: "请输入收货人", trigger: "blur" },
|
||||||
|
],
|
||||||
|
salesConsigneeMobile: [
|
||||||
|
{ required: true, message: "手机号不能为空", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: RegExp.mobile,
|
||||||
|
trigger: "blur",
|
||||||
|
message: "请输入正确的手机号",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
salesConsigneeDetail: [
|
||||||
|
{ required: true, message: "请输入详细地址", trigger: "blur" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const formRef = ref(null);
|
||||||
|
const mapRef = ref(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收地图/行政区选择器回传的地址数据。
|
||||||
|
* @param {object} val 地址选择结果
|
||||||
|
*/
|
||||||
|
function callbackAddress(val) {
|
||||||
|
if (val.type === "select") {
|
||||||
|
const paths = val.data.map((item) => item.name).join(",");
|
||||||
|
const ids = val.data.map((item) => item.id).join(",");
|
||||||
|
|
||||||
|
if (currentKey.value === "addressGoods") {
|
||||||
|
form.value.salesConsignorAddressPath = paths;
|
||||||
|
form.value.salesConsignorAddressId = ids;
|
||||||
|
}
|
||||||
|
if (currentKey.value === "salesConsigneeAddressPath") {
|
||||||
|
form.value.salesConsigneeAddressPath = paths;
|
||||||
|
form.value.salesConsigneeAddressId = ids;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentKey.value === "addressGoods") {
|
||||||
|
form.value.salesConsignorAddressPath = val.data.addr;
|
||||||
|
form.value.salesConsignorAddressId = val.data.addrId;
|
||||||
|
}
|
||||||
|
if (currentKey.value === "salesConsigneeAddressPath") {
|
||||||
|
form.value.salesConsigneeAddressPath = val.data.addr;
|
||||||
|
form.value.salesConsigneeAddressId = val.data.addrId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开地址选择弹窗。
|
||||||
|
* @param {string} key 当前编辑的地址类型标识
|
||||||
|
*/
|
||||||
|
function handleChangeAddress(key) {
|
||||||
|
currentKey.value = key;
|
||||||
|
mapRef.value.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取店铺发货地址配置。
|
||||||
|
*/
|
||||||
|
function getDeliverAddress() {
|
||||||
|
API_Shop.getDeliverAddress().then((res) => {
|
||||||
|
if (res.success && res.result) {
|
||||||
|
form.value.salesConsignorName = res.result.salesConsignorName || "";
|
||||||
|
form.value.salesConsignorMobile = res.result.salesConsignorMobile || "";
|
||||||
|
form.value.salesConsignorAddressId = res.result.salesConsignorAddressId || "";
|
||||||
|
form.value.salesConsignorAddressPath = res.result.salesConsignorAddressPath || "";
|
||||||
|
form.value.salesConsignorDetail = res.result.salesConsignorDetail || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取商家退货地址配置。
|
||||||
|
*/
|
||||||
|
function getRefundGoodsAddress() {
|
||||||
|
API_Shop.getRefundGoodsAddress().then((res) => {
|
||||||
|
if (res.success && res.result) {
|
||||||
|
form.value.salesConsigneeName = res.result.salesConsigneeName || "";
|
||||||
|
form.value.salesConsigneeMobile = res.result.salesConsigneeMobile || "";
|
||||||
|
form.value.salesConsigneeAddressId = res.result.salesConsigneeAddressId || "";
|
||||||
|
form.value.salesConsigneeAddressPath = res.result.salesConsigneeAddressPath || "";
|
||||||
|
form.value.salesConsigneeDetail = res.result.salesConsigneeDetail || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一校验并保存发货地址与退货地址配置。
|
||||||
|
*/
|
||||||
|
function handleSubmit() {
|
||||||
|
formRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
submitLoading.value = true;
|
||||||
|
try {
|
||||||
|
const deliverParams = {
|
||||||
|
salesConsignorName: form.value.salesConsignorName,
|
||||||
|
salesConsignorMobile: form.value.salesConsignorMobile,
|
||||||
|
salesConsignorAddressId: form.value.salesConsignorAddressId,
|
||||||
|
salesConsignorAddressPath: form.value.salesConsignorAddressPath,
|
||||||
|
salesConsignorDetail: form.value.salesConsignorDetail,
|
||||||
|
};
|
||||||
|
const refundParams = {
|
||||||
|
salesConsigneeName: form.value.salesConsigneeName,
|
||||||
|
salesConsigneeMobile: form.value.salesConsigneeMobile,
|
||||||
|
salesConsigneeAddressId: form.value.salesConsigneeAddressId,
|
||||||
|
salesConsigneeAddressPath: form.value.salesConsigneeAddressPath,
|
||||||
|
salesConsigneeDetail: form.value.salesConsigneeDetail,
|
||||||
|
};
|
||||||
|
const [deliverRes, refundRes] = await Promise.all([
|
||||||
|
API_Shop.editDeliverAddress(deliverParams),
|
||||||
|
API_Shop.saveRefundGoodsAddress(refundParams),
|
||||||
|
]);
|
||||||
|
if (deliverRes?.success && refundRes?.success) {
|
||||||
|
Message.success("保存成功");
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
getDeliverAddress();
|
||||||
|
getRefundGoodsAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.setting-page {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-section {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px 24px 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input) {
|
||||||
|
width: 400px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-text {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-bar {
|
||||||
|
text-align: center;
|
||||||
|
margin: 24px 0 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
294
seller/src/views/shop/shopInfo.vue
Normal file
294
seller/src/views/shop/shopInfo.vue
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
<!--
|
||||||
|
店铺信息页面:展示商家入驻时填写的企业及联系人信息、营业执照与法人信息、财务结算信息、店铺及经营信息。
|
||||||
|
@author Bulbasaur
|
||||||
|
@since 2026-11-09
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="shop-info-page">
|
||||||
|
<el-card v-loading="loading" shadow="never" class="info-card">
|
||||||
|
<div class="section-block">
|
||||||
|
<div class="section-title">店铺及经营信息</div>
|
||||||
|
<el-descriptions :column="2" border class="shop-desc">
|
||||||
|
<el-descriptions-item label="店铺名称">
|
||||||
|
{{ blankFilter(storeInfo.storeName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="商家账号">
|
||||||
|
{{ blankFilter(storeInfo.memberName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺类型">
|
||||||
|
{{ storeInfo.selfOperated == 1 || storeInfo.selfOperated === true ? "自营" : "非自营" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺状态">
|
||||||
|
{{ storeStatusText(storeInfo.storeDisable) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="开店时间">
|
||||||
|
{{ blankFilter(storeInfo.createTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺定位">
|
||||||
|
{{ blankFilter(storeInfo.storeCenter) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺所在地">
|
||||||
|
{{ blankFilter(storeInfo.storeAddressPath) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺详细地址">
|
||||||
|
{{ blankFilter(storeInfo.storeAddressDetail) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺LOGO">
|
||||||
|
<el-image
|
||||||
|
v-if="isValid(storeInfo.storeLogo)"
|
||||||
|
:src="storeInfo.storeLogo"
|
||||||
|
:preview-src-list="[storeInfo.storeLogo]"
|
||||||
|
fit="cover"
|
||||||
|
preview-teleported
|
||||||
|
class="cert-img"
|
||||||
|
/>
|
||||||
|
<span v-else>暂未上传</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺经营类目">
|
||||||
|
{{ categories.length ? categories.map((item) => item.name).join("、") : "暂未配置" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺简介" :span="2">
|
||||||
|
{{ blankFilter(storeInfo.storeDesc) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-block">
|
||||||
|
<div class="section-title">企业及联系人信息</div>
|
||||||
|
<el-descriptions :column="2" border class="shop-desc">
|
||||||
|
<el-descriptions-item label="公司名称">
|
||||||
|
{{ blankFilter(storeInfo.companyName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="公司电话">
|
||||||
|
{{ blankFilter(storeInfo.companyPhone) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="公司所在地">
|
||||||
|
{{ blankFilter(storeInfo.companyAddressPath) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="公司详细地址">
|
||||||
|
{{ blankFilter(storeInfo.companyAddress) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="员工总数">
|
||||||
|
{{ isValid(storeInfo.employeeNum) ? `${storeInfo.employeeNum} 人` : "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="注册资金">
|
||||||
|
{{ isValid(storeInfo.registeredCapital) ? `${storeInfo.registeredCapital} 万元` : "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系人姓名">
|
||||||
|
{{ blankFilter(storeInfo.linkName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系人电话">
|
||||||
|
{{ blankFilter(storeInfo.linkPhone) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="电子邮箱" :span="2">
|
||||||
|
{{ blankFilter(storeInfo.companyEmail) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-block">
|
||||||
|
<div class="section-title">营业执照与法人信息</div>
|
||||||
|
<el-descriptions :column="2" border class="shop-desc">
|
||||||
|
<el-descriptions-item label="营业执照号">
|
||||||
|
{{ blankFilter(storeInfo.licenseNum) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="法人姓名">
|
||||||
|
{{ blankFilter(storeInfo.legalName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="法人证件号" :span="2">
|
||||||
|
{{ blankFilter(storeInfo.legalId) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="法定经营范围" :span="2">
|
||||||
|
{{ blankFilter(storeInfo.scope) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="营业执照电子版">
|
||||||
|
<el-image
|
||||||
|
v-if="isValid(storeInfo.licencePhoto)"
|
||||||
|
:src="storeInfo.licencePhoto"
|
||||||
|
:preview-src-list="[storeInfo.licencePhoto]"
|
||||||
|
fit="cover"
|
||||||
|
preview-teleported
|
||||||
|
class="cert-img"
|
||||||
|
/>
|
||||||
|
<span v-else>暂未上传</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="法人证件电子版">
|
||||||
|
<template v-if="legalPhotoList.length">
|
||||||
|
<el-image
|
||||||
|
v-for="(img, idx) in legalPhotoList"
|
||||||
|
:key="idx"
|
||||||
|
:src="img"
|
||||||
|
:preview-src-list="legalPhotoList"
|
||||||
|
:initial-index="idx"
|
||||||
|
fit="cover"
|
||||||
|
preview-teleported
|
||||||
|
class="cert-img"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span v-else>暂未上传</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-block">
|
||||||
|
<div class="section-title">财务结算信息</div>
|
||||||
|
<el-descriptions :column="2" border class="shop-desc">
|
||||||
|
<el-descriptions-item label="银行开户名">
|
||||||
|
{{ blankFilter(storeInfo.settlementBankAccountName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="银行账号">
|
||||||
|
{{ blankFilter(storeInfo.settlementBankAccountNum) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="开户银行支行名称">
|
||||||
|
{{ blankFilter(storeInfo.settlementBankBranchName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="支行联行号">
|
||||||
|
{{ blankFilter(storeInfo.settlementBankJointName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="结算日" :span="2">
|
||||||
|
<span v-if="settlementCycles.length">每月 {{ settlementCycles.join("、") }} 日</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import * as API_Shop from "@/api/shops";
|
||||||
|
import { getGoodsCategoryAll } from "@/api/goods";
|
||||||
|
|
||||||
|
defineOptions({ name: "shopInfo" });
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const storeInfo = ref({});
|
||||||
|
const categories = ref([]);
|
||||||
|
|
||||||
|
const isValid = (v) => v !== null && v !== undefined && v !== "" && v !== "null";
|
||||||
|
|
||||||
|
function blankFilter(v) {
|
||||||
|
return isValid(v) ? v : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeStatusText(status) {
|
||||||
|
const map = {
|
||||||
|
OPEN: "营业中",
|
||||||
|
CLOSED: "已关闭",
|
||||||
|
APPLY: "申请开店中",
|
||||||
|
APPLYING: "待平台审核",
|
||||||
|
REFUSED: "审核未通过",
|
||||||
|
};
|
||||||
|
return map[status] || status || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
const legalPhotoList = computed(() => {
|
||||||
|
if (!isValid(storeInfo.value.legalPhoto)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(storeInfo.value.legalPhoto)) {
|
||||||
|
return storeInfo.value.legalPhoto.filter(isValid);
|
||||||
|
}
|
||||||
|
return String(storeInfo.value.legalPhoto)
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(isValid);
|
||||||
|
});
|
||||||
|
|
||||||
|
const settlementCycles = computed(() => {
|
||||||
|
if (!isValid(storeInfo.value.settlementCycle)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return String(storeInfo.value.settlementCycle)
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const [baseRes, detailRes, catRes] = await Promise.allSettled([
|
||||||
|
API_Shop.getShopInfo(),
|
||||||
|
API_Shop.getShopEntryDetail(),
|
||||||
|
getGoodsCategoryAll(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const merged = {};
|
||||||
|
if (baseRes.status === "fulfilled" && baseRes.value?.success && baseRes.value.result) {
|
||||||
|
Object.assign(merged, baseRes.value.result);
|
||||||
|
}
|
||||||
|
if (detailRes.status === "fulfilled" && detailRes.value?.success && detailRes.value.result) {
|
||||||
|
Object.assign(merged, detailRes.value.result);
|
||||||
|
}
|
||||||
|
storeInfo.value = merged;
|
||||||
|
|
||||||
|
if (catRes.status === "fulfilled" && catRes.value?.success && Array.isArray(catRes.value.result)) {
|
||||||
|
categories.value = catRes.value.result;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.shop-info-page {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-block + .section-block {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--el-color-primary);
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-desc {
|
||||||
|
:deep(.el-descriptions__label) {
|
||||||
|
width: 160px;
|
||||||
|
color: #606266;
|
||||||
|
background: #fafbfc;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-descriptions__content) {
|
||||||
|
color: #303133;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-descriptions__cell) {
|
||||||
|
padding: 12px 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-img {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
margin: 0 10px 4px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<el-card style="margin-left: 10px">
|
<el-card style="margin-left: 10px">
|
||||||
<el-tabs v-model="type" @tab-click="handleClickType">
|
<el-tabs v-model="type">
|
||||||
<el-tab-pane label="基本信息" name="INFO">
|
<el-tab-pane label="基本信息" name="INFO">
|
||||||
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
|
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
|
||||||
<el-form-item label="店铺名称">
|
<el-form-item label="店铺名称">
|
||||||
@@ -34,85 +34,6 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="发货地址">
|
|
||||||
<el-form ref="addressGoodsRef" :model="addressGoods" label-width="120px" :rules="addressGoodsValidate" class="shop-setting-form">
|
|
||||||
<el-form-item label="发货人姓名" prop="salesConsignorName">
|
|
||||||
<el-input v-model="addressGoods.salesConsignorName" maxlength="11" clearable style="width: 20%">
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="发货人手机号" prop="salesConsignorMobile">
|
|
||||||
<el-input v-model="addressGoods.salesConsignorMobile" maxlength="11" clearable style="width: 20%">
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="地址" prop="salesConsignorAddressId">
|
|
||||||
<span>{{ addressGoods.salesConsignorAddressPath }}</span>
|
|
||||||
<el-button style="margin-left: 10px;" @click="handleChangeAddress('addressGoods')">选择</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- <el-form-item label="地址名称" prop="salesConsignorAddressPath">
|
|
||||||
<el-input
|
|
||||||
v-model="addressGoods.salesConsignorAddressPath"
|
|
||||||
clearable
|
|
||||||
style="width: 20%"
|
|
||||||
>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item> -->
|
|
||||||
<el-form-item label="详细地址" prop="salesConsignorDetail">
|
|
||||||
<el-input v-model="addressGoods.salesConsignorDetail" clearable style="width: 20%">
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-button @click="setAddressGoods" type="primary" style="margin-left: 8px">确认
|
|
||||||
</el-button>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="退货地址" name="REFUND_GOODS_ADDRESS">
|
|
||||||
<el-form ref="addressFormRef" :model="addressForm" label-width="100px" :rules="afterFormValidate">
|
|
||||||
<el-form-item label="收货人" prop="salesConsigneeName">
|
|
||||||
<el-input v-model="addressForm.salesConsigneeName" maxlength="11" clearable style="width: 20%" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="收货人电话" prop="salesConsigneeMobile">
|
|
||||||
<el-input v-model="addressForm.salesConsigneeMobile" maxlength="11" style="width: 20%" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="售后地址">
|
|
||||||
<span>{{ addressForm.salesConsigneeAddressPath }}</span>
|
|
||||||
<el-button style="margin-left: 10px;" @click="handleChangeAddress('salesConsigneeAddressPath')">选择</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="详细地址" prop="salesConsigneeDetail">
|
|
||||||
<el-input v-model="addressForm.salesConsigneeDetail" clearable style="width: 20%" maxlength="50" />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item>
|
|
||||||
<el-button @click="afterHandleSubmit" :loading="submitLoading" type="primary" style="margin-right: 5px">修改
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
<!-- Udesk 坐席功能 后续维护 -->
|
|
||||||
<!-- <el-tab-pane label="客服设置" name="UDESK">
|
|
||||||
<el-form
|
|
||||||
ref="udeskForm"
|
|
||||||
:model="udeskForm"
|
|
||||||
label-width="100px"
|
|
||||||
:rules="udeskFormValidate"
|
|
||||||
>
|
|
||||||
<el-form-item label="坐席id" prop="merchantEuid">
|
|
||||||
<el-input
|
|
||||||
v-model="udeskForm.merchantEuid"
|
|
||||||
maxlength="30"
|
|
||||||
clearable
|
|
||||||
style="width: 20%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button
|
|
||||||
@click="merchantSubmit"
|
|
||||||
:loading="submitLoading"
|
|
||||||
type="primary"
|
|
||||||
style="margin-right: 5px"
|
|
||||||
>修改
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane> -->
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
@@ -126,53 +47,23 @@ import * as API_Shop from "@/api/shops";
|
|||||||
import { validateMobile } from "@/libs/validate";
|
import { validateMobile } from "@/libs/validate";
|
||||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||||
import multipleMap from "@/views/my-components/map/multiple-map";
|
import multipleMap from "@/views/my-components/map/multiple-map";
|
||||||
import * as RegExp from "@/libs/RegExp.js";
|
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
|
|
||||||
defineOptions({ name: "shopSetting" });
|
defineOptions({ name: "shopSetting" });
|
||||||
|
|
||||||
const currentKey = ref(""); //存储当前点击地址的key
|
const currentKey = ref(""); // 存储当前点击地址的key
|
||||||
const type = ref("INFO");
|
const type = ref("INFO");
|
||||||
|
|
||||||
const storeName = ref(""); //店铺名称
|
const storeName = ref(""); // 店铺名称
|
||||||
|
|
||||||
const addressForm = ref({
|
|
||||||
// 退货地址
|
|
||||||
salesConsigneeName: "", // 收货人姓名
|
|
||||||
salesConsigneeMobile: "", // 收货人电话
|
|
||||||
salesConsigneeAddressId: "", // 售后地址id,逗号分割
|
|
||||||
salesConsigneeAddressPath: "", // 售后地址,逗号分割
|
|
||||||
salesConsigneeDetail: "", // 详细地址
|
|
||||||
});
|
|
||||||
//im form
|
|
||||||
const udeskForm = ref({
|
|
||||||
merchantEuid: "",
|
|
||||||
});
|
|
||||||
const afterFormValidate = {
|
|
||||||
salesConsigneeMobile: [
|
|
||||||
{ required: true, message: "手机号不能为空", trigger: "blur" },
|
|
||||||
{
|
|
||||||
pattern: RegExp.mobile,
|
|
||||||
trigger: "blur",
|
|
||||||
message: "请输入正确的手机号",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
salesConsigneeName: [
|
|
||||||
{ required: true, message: "请输入收货人", trigger: "blur" },
|
|
||||||
],
|
|
||||||
salesConsigneeDetail: [
|
|
||||||
{ required: true, message: "请输入详细地址", trigger: "blur" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
// 添加或编辑表单对象初始化数据
|
|
||||||
storeAddressPath: "", // 店铺地址中文
|
storeAddressPath: "", // 店铺地址中文
|
||||||
storeCenter: "", // 经度 + 纬度
|
storeCenter: "", // 经度 + 纬度
|
||||||
longitude: "", //经度
|
longitude: "", // 经度
|
||||||
latitude: "", //纬度
|
latitude: "", // 纬度
|
||||||
storeAddressDetail: "", //详细地址
|
storeAddressDetail: "", // 详细地址
|
||||||
storeAddressIdPath: "", //地址
|
storeAddressIdPath: "", // 地址
|
||||||
storeDesc: "", // 店铺描述
|
storeDesc: "", // 店铺描述
|
||||||
pageShow: false,
|
pageShow: false,
|
||||||
selfPickFlag: false,
|
selfPickFlag: false,
|
||||||
@@ -220,134 +111,69 @@ const formValidate = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const submitLoading = ref(false); // 添加或编辑提交状态
|
const submitLoading = ref(false);
|
||||||
//发货地址
|
|
||||||
const addressGoods = ref({
|
|
||||||
salesConsignorName: " ",
|
|
||||||
salesConsignorMobile: " ",
|
|
||||||
salesConsignorAddressId: " ",
|
|
||||||
salesConsignorAddressPath: " ",
|
|
||||||
salesConsignorDetail: " ",
|
|
||||||
});
|
|
||||||
|
|
||||||
const addressGoodsValidate = {
|
|
||||||
salesConsignorName: [
|
|
||||||
{ required: true, message: "请输入发货人姓名", trigger: "blur" },
|
|
||||||
],
|
|
||||||
salesConsignorMobile: [
|
|
||||||
{ required: true, message: "手机号不能为空", trigger: "blur" },
|
|
||||||
{
|
|
||||||
pattern: RegExp.mobile,
|
|
||||||
trigger: "blur",
|
|
||||||
message: "请输入正确的手机号",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
salesConsignorDetail: [
|
|
||||||
{ required: true, message: "请输入详细地址", trigger: "blur" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
const addressGoodsRef = ref(null);
|
|
||||||
const addressFormRef = ref(null);
|
|
||||||
const mapRef = ref(null);
|
const mapRef = ref(null);
|
||||||
|
|
||||||
// 回调地址
|
// 回调地址
|
||||||
function callbackAddress(val) {
|
function callbackAddress(val) {
|
||||||
if (val.type === 'select') {
|
if (val.type === "select") {
|
||||||
const paths = val.data.map(item => item.name).join(',')
|
const paths = val.data.map((item) => item.name).join(",");
|
||||||
|
const ids = val.data.map((item) => item.id).join(",");
|
||||||
|
|
||||||
const ids = val.data.map(item => item.id).join(',')
|
if (currentKey.value === "storeAddress") {
|
||||||
|
form.value.storeAddressPath = paths;
|
||||||
if (currentKey.value === 'storeAddress') {
|
form.value.storeAddressIdPath = ids;
|
||||||
form.value.storeAddressPath = paths
|
form.value.storeCenter = val.data[val.data.length - 1].center;
|
||||||
form.value.storeAddressIdPath = ids
|
|
||||||
form.value.storeCenter = val.data[val.data.length - 1].center
|
|
||||||
}
|
}
|
||||||
if (currentKey.value === 'addressGoods') {
|
} else {
|
||||||
addressGoods.value.salesConsignorAddressPath = paths
|
if (
|
||||||
addressGoods.value.salesConsignorAddressId = ids
|
currentKey.value === "storeAddress" ||
|
||||||
}
|
currentKey.value === "storeAddressstoreAddress"
|
||||||
if (currentKey.value === 'salesConsigneeAddressPath') {
|
) {
|
||||||
addressForm.value.salesConsigneeAddressPath = paths
|
|
||||||
addressForm.value.salesConsigneeAddressId = ids
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (currentKey.value === 'storeAddressstoreAddress') {
|
|
||||||
form.value.storeAddressPath = val.data.addr;
|
form.value.storeAddressPath = val.data.addr;
|
||||||
form.value.storeAddressIdPath = val.data.addrId;
|
form.value.storeAddressIdPath = val.data.addrId;
|
||||||
form.value.storeCenter = val.data.position.lng + "," + val.data.position.lat;
|
form.value.storeCenter =
|
||||||
}
|
val.data.position.lng + "," + val.data.position.lat;
|
||||||
if (currentKey.value === 'addressGoods') {
|
|
||||||
addressGoods.value.salesConsignorAddressPath = val.data.addr;
|
|
||||||
addressGoods.value.salesConsignorAddressId = val.data.addrId;
|
|
||||||
}
|
|
||||||
if (currentKey.value === 'salesConsigneeAddressPath') {
|
|
||||||
addressForm.value.salesConsigneeAddressPath = val.data.addr;
|
|
||||||
addressForm.value.salesConsigneeAddressId = val.data.addrId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改地址
|
// 修改地址
|
||||||
function handleChangeAddress(key) {
|
function handleChangeAddress(key) {
|
||||||
currentKey.value = key
|
currentKey.value = key;
|
||||||
mapRef.value.open();
|
mapRef.value.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
function init() {
|
function init() {
|
||||||
getShopInfo();
|
getShopInfo();
|
||||||
getDeliverAddress()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取店铺信息
|
||||||
function setAddressGoods() {
|
|
||||||
addressGoodsRef.value.validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
API_Shop.editDeliverAddress(addressGoods.value).then(res => {
|
|
||||||
if (res.success) {
|
|
||||||
Message.success("修改成功")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//获取店铺信息
|
|
||||||
function getShopInfo() {
|
function getShopInfo() {
|
||||||
API_Shop.getShopInfo().then((res) => {
|
API_Shop.getShopInfo().then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
form.value = res.result;
|
form.value = res.result;
|
||||||
|
|
||||||
storeName.value = res.result.storeName;
|
storeName.value = res.result.storeName;
|
||||||
form.value.storeCenter = res.result.storeCenter;
|
form.value.storeCenter = res.result.storeCenter;
|
||||||
Cookies.set("userInfoSeller", JSON.stringify(res.result));
|
Cookies.set("userInfoSeller", JSON.stringify(res.result));
|
||||||
if (res.result.merchantEuid) {
|
|
||||||
//赋予坐席id
|
|
||||||
udeskForm.value.merchantEuid = res.result.merchantEuid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function pageShow(type) {
|
|
||||||
form.value.pageShow = type
|
function pageShow(val) {
|
||||||
}
|
form.value.pageShow = val;
|
||||||
function changeSelfPickFlag(item) {
|
|
||||||
if (item) {
|
|
||||||
form.value.selfPickFlag = item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getDeliverAddress() {
|
|
||||||
API_Shop.getDeliverAddress().then(res => {
|
|
||||||
if (res.success) {
|
|
||||||
if (res.result != '' && res.result != null) {
|
|
||||||
addressGoods.value = res.result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//提交保存
|
function changeSelfPickFlag(item) {
|
||||||
|
if (item) {
|
||||||
|
form.value.selfPickFlag = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交保存
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
formRef.value.validate((valid) => {
|
formRef.value.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
@@ -363,42 +189,6 @@ function handleSubmit() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//tab切换
|
|
||||||
function handleClickType(pane) {
|
|
||||||
const name = pane.paneName;
|
|
||||||
//退款
|
|
||||||
if (name === "INFO") {
|
|
||||||
getShopInfo();
|
|
||||||
}
|
|
||||||
//退货
|
|
||||||
if (name === "REFUND_GOODS_ADDRESS") {
|
|
||||||
getRefundGoodsAddress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//获取商家退货地址
|
|
||||||
function getRefundGoodsAddress() {
|
|
||||||
API_Shop.getRefundGoodsAddress().then((res) => {
|
|
||||||
if (res.result != null) {
|
|
||||||
addressForm.value = res.result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//提交保存
|
|
||||||
function afterHandleSubmit() {
|
|
||||||
addressFormRef.value.validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
submitLoading.value = true;
|
|
||||||
API_Shop.saveRefundGoodsAddress(addressForm.value).then((res) => {
|
|
||||||
submitLoading.value = false;
|
|
||||||
if (res.success) {
|
|
||||||
Message.success("修改成功");
|
|
||||||
getRefundGoodsAddress();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
@@ -408,15 +198,4 @@ onMounted(() => {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-setting-form {
|
|
||||||
:deep(.el-form-item) {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-form-item__label) {
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user