refactor: 四端完成 Options API → script setup + vuex → pinia 迁移

- buyer(95) / seller(169) / im(53) / manager(280) 共 597 个组件全部从 Options API 转换为 <script setup>
- 四端 vuex 全部移除,状态管理统一迁移至 pinia 4(stores/ 目录)
- util.js 等基础设施去除 vm 首参依赖;全局属性($Message/$filters 等)在 script 内改为具名导入
- 被外部 ref 访问的公共组件均已补齐 defineExpose(清单见各端 MIGRATION-VUE3.md)
- 各端 eslint 启用 vue/component-api-style 防止新代码回退 Options API
- 四端 yarn build 全部通过;迁移约定与注意事项已更新至各端 MIGRATION-VUE3.md
This commit is contained in:
lifenlong
2026-10-13 07:06:59 +08:00
parent 870bacdd8c
commit 88fc1b0b2a
674 changed files with 71220 additions and 69943 deletions

View File

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

View File

@@ -6,50 +6,46 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { getBaseSite } from "@/api/common.js";
export default {
data() {
return {
domainLogo: "",
};
},
methods: {
applyFavicon(href) {
if (!href) return;
let link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = href;
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
},
getSite() {
getBaseSite().then((res) => {
if (res.success && res.result && res.result.settingValue) {
const { domainLogo, domainIcon, siteName } = JSON.parse(
res.result.settingValue
);
this.domainLogo = domainLogo || "";
const expirationTime = new Date().setHours(new Date().getHours() + 1);
localStorage.setItem("icontitle_expiration_time", expirationTime);
localStorage.setItem("icon", domainLogo || "");
localStorage.setItem("domainIcon", domainIcon || "");
localStorage.setItem("title", siteName || "");
this.applyFavicon(domainIcon || domainLogo);
if (siteName) {
window.document.title = siteName + " - 运营后台";
}
}
});
},
},
mounted() {
this.getSite();
},
};
const domainLogo = ref("");
function applyFavicon(href) {
if (!href) return;
let link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = href;
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
}
function getSite() {
getBaseSite().then((res) => {
if (res.success && res.result && res.result.settingValue) {
const { domainLogo: logo, domainIcon, siteName } = JSON.parse(
res.result.settingValue
);
domainLogo.value = logo || "";
const expirationTime = new Date().setHours(new Date().getHours() + 1);
localStorage.setItem("icontitle_expiration_time", expirationTime);
localStorage.setItem("icon", logo || "");
localStorage.setItem("domainIcon", domainIcon || "");
localStorage.setItem("title", siteName || "");
applyFavicon(domainIcon || logo);
if (siteName) {
window.document.title = siteName + " - 运营后台";
}
}
});
}
onMounted(() => {
getSite();
});
</script>
<style lang="scss" scoped>

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,160 +41,179 @@
</div>
</template>
<script>
<script setup>
import { ref, watch, onBeforeUnmount } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { useAppStore } from "@/stores/app";
import util from "@/libs/util.js";
export default {
name: "tagsPageOpened",
props: {
pageTagsList: Array,
beforePush: {
type: Function,
default: () => true,
},
},
data() {
return {
currentPageName: this.$route.name,
tagBodyLeft: 0,
visible: false,
contextMenuLeft: 0,
contextMenuTop: 0,
actionList: {
others: "关闭其他",
clearAll: "关闭所有",
},
};
},
methods: {
isActive(item) {
const target = item.children ? item.children[0] : item;
return util.isSameRoutePage(target, {
name: this.currentPageName,
path: this.$route.path,
});
},
tagType(item) {
return this.isActive(item) ? "primary" : "info";
},
itemTitle(item) {
if (typeof item.title === "object") {
return this.$t(item.title.i18n);
}
return item.title;
},
closePage(event, name) {
let pageOpenedList = this.$store.state.app.pageOpenedList;
let lastPageObj = pageOpenedList[0];
if (this.currentPageName === name) {
const len = pageOpenedList.length;
for (let i = 1; i < len; i++) {
if (pageOpenedList[i].name === name) {
lastPageObj =
i < len - 1 ? pageOpenedList[i + 1] : pageOpenedList[i - 1];
break;
}
}
} else if (event && event.target) {
const tagWidth = event.target.parentNode?.offsetWidth || 0;
this.tagBodyLeft = Math.min(this.tagBodyLeft + tagWidth, 0);
}
this.$store.commit("removeTag", name);
this.$store.commit("closePage", name);
pageOpenedList = this.$store.state.app.pageOpenedList;
localStorage.pageOpenedList = JSON.stringify(pageOpenedList);
if (this.currentPageName === name) {
this.linkTo(lastPageObj);
}
},
linkTo(item) {
const target = item.children ? item.children[0] : item;
if (util.isSameRoutePage(target, this.$route)) return;
if (!this.beforePush(item)) return;
defineOptions({ name: "tagsPageOpened" });
const routerObj = { name: item.name };
if (item.argu) routerObj.params = item.argu;
if (item.query) routerObj.query = item.query;
const props = defineProps({
pageTagsList: Array,
beforePush: {
type: Function,
default: () => true,
},
});
if (item.name && this.$router.hasRoute(item.name)) {
this.$router.push(routerObj);
return;
}
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const appStore = useAppStore();
const path = target.path || item.path;
if (path) {
this.$router.push({
path: path.startsWith("/") ? path : `/${path}`,
query: item.query,
});
}
},
handlescroll(e) {
const type = e.type;
let delta = 0;
if (type === "DOMMouseScroll" || type === "mousewheel") {
delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40;
}
let left = 0;
if (delta > 0) {
left = Math.min(0, this.tagBodyLeft + delta);
} else if (
this.$refs.scrollCon.offsetWidth - 100 <
this.$refs.scrollBody.offsetWidth
) {
if (
this.tagBodyLeft <
-(this.$refs.scrollBody.offsetWidth - this.$refs.scrollCon.offsetWidth + 100)
) {
left = this.tagBodyLeft;
} else {
left = Math.max(
this.tagBodyLeft + delta,
this.$refs.scrollCon.offsetWidth -
this.$refs.scrollBody.offsetWidth -
100
);
}
} else {
this.tagBodyLeft = 0;
}
this.tagBodyLeft = left;
},
handleTagsOption(type) {
if (type === "clearAll") {
this.$store.commit("clearAllTags");
this.$router.push({ name: "home_index" });
} else {
this.$store.commit("clearOtherTags", this);
}
this.tagBodyLeft = 0;
},
contextMenu(item, e) {
this.visible = true;
const offsetLeft = this.$el.getBoundingClientRect().left;
this.contextMenuLeft = e.clientX - offsetLeft + 10;
this.contextMenuTop = e.clientY - 64;
},
closeMenu() {
this.visible = false;
},
},
watch: {
$route(to) {
this.currentPageName = to.name;
},
visible(value) {
if (value) {
document.body.addEventListener("click", this.closeMenu);
} else {
document.body.removeEventListener("click", this.closeMenu);
}
},
},
beforeUnmount() {
document.body.removeEventListener("click", this.closeMenu);
},
const currentPageName = ref(route.name);
const tagBodyLeft = ref(0);
const visible = ref(false);
const contextMenuLeft = ref(0);
const contextMenuTop = ref(0);
const actionList = {
others: "关闭其他",
clearAll: "关闭所有",
};
const scrollCon = ref(null);
const scrollBody = ref(null);
const isActive = (item) => {
const target = item.children ? item.children[0] : item;
return util.isSameRoutePage(target, {
name: currentPageName.value,
path: route.path,
});
};
const tagType = (item) => {
return isActive(item) ? "primary" : "info";
};
const itemTitle = (item) => {
if (typeof item.title === "object") {
return t(item.title.i18n);
}
return item.title;
};
const closePage = (event, name) => {
let pageOpenedList = appStore.pageOpenedList;
let lastPageObj = pageOpenedList[0];
if (currentPageName.value === name) {
const len = pageOpenedList.length;
for (let i = 1; i < len; i++) {
if (pageOpenedList[i].name === name) {
lastPageObj =
i < len - 1 ? pageOpenedList[i + 1] : pageOpenedList[i - 1];
break;
}
}
} else if (event && event.target) {
const tagWidth = event.target.parentNode?.offsetWidth || 0;
tagBodyLeft.value = Math.min(tagBodyLeft.value + tagWidth, 0);
}
appStore.removeTag(name);
appStore.closePage(name);
pageOpenedList = appStore.pageOpenedList;
localStorage.pageOpenedList = JSON.stringify(pageOpenedList);
if (currentPageName.value === name) {
linkTo(lastPageObj);
}
};
const linkTo = (item) => {
const target = item.children ? item.children[0] : item;
if (util.isSameRoutePage(target, route)) return;
if (!props.beforePush(item)) return;
const routerObj = { name: item.name };
if (item.argu) routerObj.params = item.argu;
if (item.query) routerObj.query = item.query;
if (item.name && router.hasRoute(item.name)) {
router.push(routerObj);
return;
}
const path = target.path || item.path;
if (path) {
router.push({
path: path.startsWith("/") ? path : `/${path}`,
query: item.query,
});
}
};
const handlescroll = (e) => {
const type = e.type;
let delta = 0;
if (type === "DOMMouseScroll" || type === "mousewheel") {
delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40;
}
let left = 0;
if (delta > 0) {
left = Math.min(0, tagBodyLeft.value + delta);
} else if (
scrollCon.value.offsetWidth - 100 <
scrollBody.value.offsetWidth
) {
if (
tagBodyLeft.value <
-(scrollBody.value.offsetWidth - scrollCon.value.offsetWidth + 100)
) {
left = tagBodyLeft.value;
} else {
left = Math.max(
tagBodyLeft.value + delta,
scrollCon.value.offsetWidth -
scrollBody.value.offsetWidth -
100
);
}
} else {
tagBodyLeft.value = 0;
}
tagBodyLeft.value = left;
};
const handleTagsOption = (type) => {
if (type === "clearAll") {
appStore.clearAllTags();
router.push({ name: "home_index" });
} else {
appStore.clearOtherTags(route.name);
}
tagBodyLeft.value = 0;
};
const contextMenu = (item, e) => {
visible.value = true;
const offsetLeft = scrollCon.value.getBoundingClientRect().left;
contextMenuLeft.value = e.clientX - offsetLeft + 10;
contextMenuTop.value = e.clientY - 64;
};
const closeMenu = () => {
visible.value = false;
};
watch(
() => route.name,
(name) => {
currentPageName.value = name;
}
);
watch(visible, (value) => {
if (value) {
document.body.addEventListener("click", closeMenu);
} else {
document.body.removeEventListener("click", closeMenu);
}
});
onBeforeUnmount(() => {
document.body.removeEventListener("click", closeMenu);
});
</script>
<style lang="scss">