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

23
im/src/stores/app.js Normal file
View File

@@ -0,0 +1,23 @@
import { defineStore } from "pinia";
import { defaultAvatar } from "@/assets/images";
import { useUserStore } from "./user";
// 原 vuex 根级别 state/mutation/getter
export const useAppStore = defineStore("app", {
state: () => ({
socketStatus: false,
website_name: process.env.VUE_APP_WEBSITE_NAME,
// 头像加载失败后的默认头像
defaultAvatar: "this.src='" + defaultAvatar + "'",
}),
getters: {
// 用户登录状态(跨模块读取 user store)
loginStatus: () => useUserStore().loginStatus,
},
actions: {
// 更新socket 连接状态
UPDATE_SOCKET_STATUS(status) {
this.socketStatus = status;
},
},
});

94
im/src/stores/dialogue.js Normal file
View File

@@ -0,0 +1,94 @@
import { defineStore } from "pinia";
// 原 vuex dialogue 模块
export const useDialogueStore = defineStore("dialogue", {
state: () => ({
// 对话来源[1:私聊;2:群聊]
talk_type: 0,
// 接收者ID
receiver_id: 0,
is_robot: 0,
// 聊天记录
records: [
{
id: "",
createTime: "",
toUser: "",
isRead: false,
messageType: "",
talkId: "",
text: "",
float: "",
},
],
webSocketWithOut: false, // ws 是否是掉线 无输出状态
// 对话索引(聊天对话的唯一索引)
index_name: null,
}),
actions: {
// 设置ws状态
SET_WS_STATUS(resource) {
this.webSocketWithOut = resource;
},
// 更新对话
UPDATE_DIALOGUE_MESSAGE(resource) {
this.records = [];
this.talk_type = parseInt(resource.talk_type);
this.receiver_id = parseInt(resource.receiver_id);
this.is_robot = parseInt(resource.is_robot);
/**
* receiver_id 就是 好友id
* 比如 a 和 b 聊天 receiver_id = b的id
*/
this.index_name = (resource.talk_type || 1) + "_" + resource.receiver_id;
},
// 数组头部压入对话记录
UNSHIFT_DIALOGUE(records) {
if (this.records.length > 0) {
this.records.unshift(...records);
} else {
this.records.push(...records);
}
},
// 推送对话记录
PUSH_DIALOGUE(record) {
record = { ...record, webSocketStatus: this.webSocketWithOut };
console.log("推送对话");
this.records.push(record);
},
// 更新对话记录
UPDATE_DIALOGUE(resource) {
for (let i in this.records) {
if (this.records[i].id === resource.id) {
Object.assign(this.records[i], resource);
break;
}
}
},
// 删除对话记录
DELETE_DIALOGUE(index) {
this.records.splice(index, 1);
},
BATCH_DELETE_DIALOGUE(ids) {
ids.forEach((record_id) => {
let index = this.records.findIndex((item) => item.id == record_id);
if (index >= 0) this.records.splice(index, 1);
});
},
// 数组头部压入对话记录
SET_DIALOGUE(records) {
this.records = records;
},
},
});

91
im/src/stores/emoticon.js Normal file
View File

@@ -0,0 +1,91 @@
import { defineStore } from "pinia";
import { ServeFindUserEmoticon, ServeUploadEmoticon } from "@/api/emoticon";
import { ServeCollectEmoticon } from "@/api/chat";
import { ElNotification } from "element-plus";
// 原 vuex emoticon 模块
export const useEmoticonStore = defineStore("emoticon", {
state: () => ({
items: [],
}),
actions: {
// 加载用户表情包
LOAD_USER_EMOTICON() {
// TODO 隐藏
return;
// eslint-disable-next-line no-unreachable
ServeFindUserEmoticon().then((res) => {
if (res.code == 200) {
const { collect_emoticon, sys_emoticon } = res.data;
this.items = this.items.slice(0, 2);
// 用户收藏的系统表情包
this.items[1].list = collect_emoticon;
// 用户添加的系统表情包
this.items.push(...sys_emoticon);
}
});
},
// 收藏用户表情包
SAVE_USER_EMOTICON(resoure) {
ServeCollectEmoticon({
record_id: resoure.record_id,
})
.then((res) => {
if (res.code == 200) {
ElNotification({
title: "收藏提示",
message: "表情包收藏成功...",
type: "success",
});
this.LOAD_USER_EMOTICON();
}
})
.catch(() => {
ElNotification({
title: "收藏提示",
message: "表情包收藏失败...",
type: "warning",
});
});
},
// 自定义上传用户表情包
UPLOAD_USER_EMOTICON(resoure) {
let fileData = new FormData();
fileData.append("emoticon", resoure.file);
ServeUploadEmoticon(fileData)
.then((res) => {
if (res.code == 200) {
this.items[1].list.push(res.data);
}
})
.catch(() => {
ElNotification({
message: "网络异常请稍后再试...",
type: "error",
duration: 3000,
});
});
},
// 添加系统表情包
APPEND_SYS_EMOTICON(resoure) {
this.items.push(resoure);
},
// 移除系统表情包
REMOVE_SYS_EMOTICON(resoure) {
for (let i in this.items) {
if (this.items[i].emoticon_id === resoure.emoticon_id) {
this.items.splice(i, 1);
break;
}
}
},
},
});

18
im/src/stores/note.js Normal file
View File

@@ -0,0 +1,18 @@
import { defineStore } from "pinia";
// 原 vuex note 模块
export const useNoteStore = defineStore("note", {
state: () => ({
tags: [],
class: [],
}),
actions: {
SET_NOTE_TAGS(tags) {
this.tags = tags;
},
PUSH_NOTE_TAG(tag) {
this.tags.unshift(tag);
},
},
});

65
im/src/stores/notify.js Normal file
View File

@@ -0,0 +1,65 @@
import { defineStore } from "pinia";
// 原 vuex notify 模块
export const useNotifyStore = defineStore("notify", {
state: () => ({
// 聊天消息未读数
unreadNum: 0,
// 好友申请未读数
applyNum: 0,
// 好友键盘事件监听
inputEvent: 0,
// 好友登录状态监听
friendStatus: {
// 登录状态[0:下线;1:在线;]
status: 0,
// 好友ID
friend_id: 0,
},
}),
actions: {
// 消息未读数自增
INCR_UNREAD_NUM() {
console.log("触发消息未读");
this.unreadNum++;
},
// 好友申请事件监听
INCR_APPLY_NUM() {
this.applyNum++;
},
// 设置消息未读数
SET_UNREAD_NUM(value) {
this.unreadNum = value;
},
// 好友申请事件监听
SET_APPLY_NUM(value) {
this.applyNum = value;
},
// 自增好友键盘输入事件
UPDATE_KEYBOARD_EVENT() {
this.inputEvent++;
},
// 更新好友登录状态
UPDATE_FRIEND_STATUS(value) {
this.friendStatus = value;
},
// 原 vuex action ACT_UPDATE_FRIEND_STATUS
ACT_UPDATE_FRIEND_STATUS(value) {
return new Promise((resolve) => {
setTimeout(() => {
this.UPDATE_FRIEND_STATUS(value);
resolve();
}, 0);
});
},
},
});

68
im/src/stores/settings.js Normal file
View File

@@ -0,0 +1,68 @@
import { defineStore } from "pinia";
import {
getToken,
getUserSettingCache,
setUserSettingCache,
} from "@/utils/auth";
// 模块加载时合并本地缓存的设置(与原 vuex settings 模块行为一致)
function initState() {
const state = {
// 主题模式 true:全屏模式 false:居中模式
themeMode: true,
// 主题模式为居中模式下,body 的背景图片
themeBagImg: "bag003",
// 主题颜色
themeColor: "",
// 消息提示音
notifyCueTone: true,
// 键盘输入事件消息推送开关
keyboardEventNotify: true,
};
if (getToken()) {
Object.assign(state, getUserSettingCache());
}
return state;
}
/**
* 用户相关设置
*/
export const useSettingsStore = defineStore("settings", {
state: initState,
actions: {
// 设置主题模式
SET_THEME_MODE(mode) {
this.themeMode = mode;
setUserSettingCache(this.$state);
},
// 设置主题的背景图片
SET_THEME_BAGIMG(bagName) {
this.themeBagImg = bagName;
setUserSettingCache(this.$state);
},
// 主题颜色
SET_THEME_COLOR(color) {
this.themeColor = color;
setUserSettingCache(this.$state);
},
// 设置消息提示音状态
SET_NOTIFY_CUE_TONE(isTrue) {
this.notifyCueTone = isTrue;
setUserSettingCache(this.$state);
},
// 设置键盘输入事件消息推送状态
SET_KEYBOARD_EVENT_NOTIFY(isTrue) {
this.keyboardEventNotify = isTrue;
setUserSettingCache(this.$state);
},
},
});

111
im/src/stores/talk.js Normal file
View File

@@ -0,0 +1,111 @@
import { defineStore } from "pinia";
import { getSort, getMutipSort } from "@/utils/functions";
// 原 vuex talks 模块
export const useTalkStore = defineStore("talks", {
state: () => ({
// 用户对话列表
items: [],
// 最后一条消息
unreadMessage: {
num: 0,
nickname: "未知",
content: "...",
},
// 对话列表重载状态
heavyLoad: false,
}),
getters: {
// 过滤所有置顶对话列表
topItems: (state) => {
return state.items.filter((item) => item.is_top == 1);
},
// 原 vuex getter,注意:sort 会原地修改 items 数组(历史行为,原样保留)
talkItems: (state) => {
return state.items.sort(
getMutipSort([getSort((a, b) => a.lastTalkTime > b.lastTalkTime)])
);
},
// 消息未读数总计
unreadNum: (state) => {
return state.items.reduce((total, item) => {
return total + parseInt(item.unread);
}, 0);
},
talkNum: (state) => state.items.length,
},
actions: {
// 设置对话列表
SET_TALK_ITEMS(resource) {
this.items = resource.items;
},
// 更新对话节点
UPDATE_TALK_ITEM(resource) {
let index = this.items.findIndex(
(item) => item.userId === resource.index_name.split("_")[1]
);
if (index >= 0) {
Object.assign(this.items[index], resource);
}
},
// 新增对话节点
PUSH_TALK_ITEM(resource) {
this.items.push(resource);
},
// 移除对话节点
REMOVE_TALK_ITEM(index_name) {
for (let i in this.items) {
if (this.items[i].index_name === index_name) {
this.items.splice(i, 1);
break;
}
}
},
// 更新对话消息
UPDATE_TALK_MESSAGE(resource) {
let enableGetTalkList = true;
this.items.forEach((item) => {
if (item.userId == resource.index_name.split("_")[1]) {
item.unread++;
item.msg_text = resource.msg_text;
item.lastTalkTime = resource.updated_at;
item.lastTalkMessage = resource.msg_text;
item.updated_at = resource.updated_at;
enableGetTalkList = false;
}
});
// 循环如果当前用户不在对话记录列表中 就重新请求对话列表接口
// 原代码此处为 this.commit('getTalkList'),目标 action 早已被原作者注释删除,
// vuex 对未知 mutation 仅告警不中断,此处保留为空操作
if (enableGetTalkList) {
/* noop */
}
},
// 触发对话列表重新加载
TRIGGER_TALK_ITEMS_LOAD(status = false) {
this.heavyLoad = status;
},
SET_TLAK_UNREAD_MESSAGE(resource) {
this.unreadMessage.num++;
this.unreadMessage.nickname = resource.nickname;
this.unreadMessage.content = resource.content;
},
// 清除最后一条未读消息
CLEAR_TLAK_UNREAD_MESSAGE() {
this.unreadMessage = {
num: 0,
nickname: "未知",
content: "...",
};
},
},
});

68
im/src/stores/user.js Normal file
View File

@@ -0,0 +1,68 @@
import { defineStore } from "pinia";
import { setUserInfo, getUserInfo, getToken } from "@/utils/auth";
import { defaultAvatar, defaultUserBanner } from "@/assets/images";
// 判断用户是否登录(模块加载时初始化,与原 vuex user 模块行为一致)
function initState() {
const state = {
// 用户ID
id: 0,
// 用户昵称
name: "",
// 个性头像
face: defaultAvatar,
// 名片背景
visitCardBag: defaultUserBanner,
// 当前登录状态
loginStatus: false,
toUser: "",
};
if (getToken()) {
let userInfo = getUserInfo();
state.name = userInfo.name;
state.id = userInfo.id;
// 原代码如此:state.avatar 并不存在(恒为 undefined)
state.face = userInfo.face ? userInfo.face : state.avatar;
state.loginStatus = true;
}
return state;
}
export const useUserStore = defineStore("user", {
state: initState,
actions: {
// 用户退出登录
USER_LOGOUT() {
this.id = 0;
this.face = "";
this.name = "";
this.loginStatus = false;
},
// 设置用户登录状态
UPDATE_LOGIN_STATUS() {
this.loginStatus = true;
},
// 更新用户信息
UPDATE_USER_INFO(data) {
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(this.$state, key)) {
this[key] = data[key];
}
}
// 保存用户信息到缓存
setUserInfo({
id: this.id,
face: this.face,
name: this.name,
});
},
// 退出登录处理操作(原 vuex action ACT_USER_LOGOUT)
ACT_USER_LOGOUT() {
this.USER_LOGOUT();
},
},
});