@deppon/create-deppon-app 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +63 -0
- package/deppon.js +640 -0
- package/package.json +51 -0
- package/template/.env +12 -0
- package/template/.env.dev-local.example +64 -0
- package/template/.env.development.example +64 -0
- package/template/.env.example +1 -0
- package/template/.env.production.example +64 -0
- package/template/.env.test.example +64 -0
- package/template/.eslintignore +2 -0
- package/template/.eslintrc.cjs +14 -0
- package/template/.prettierrc.js +3 -0
- package/template/.vscode/settings.json +8 -0
- package/template/Dockerfile +5 -0
- package/template/README.md +149 -0
- package/template/commitlint.config.js +11 -0
- package/template/gitignore +8 -0
- package/template/index.html +18 -0
- package/template/nginx.conf +70 -0
- package/template/npmrc +2 -0
- package/template/package.json +49 -0
- package/template/preview-server.js +117 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/logo.png +0 -0
- package/template/src/App.vue +123 -0
- package/template/src/api/index.ts +13 -0
- package/template/src/api/prefercenter.ts +23 -0
- package/template/src/api/product.ts +16 -0
- package/template/src/api/user.ts +41 -0
- package/template/src/components/ExpandableMessage.vue +340 -0
- package/template/src/components/PageLayout.vue +43 -0
- package/template/src/config/dictionaryConfig.ts +24 -0
- package/template/src/directives/permission.ts +162 -0
- package/template/src/layouts/BaseLayout.vue +687 -0
- package/template/src/main.ts +27 -0
- package/template/src/router/index.ts +179 -0
- package/template/src/router/route.ts +61 -0
- package/template/src/stores/menu.ts +334 -0
- package/template/src/stores/product.ts +155 -0
- package/template/src/stores/route.ts +79 -0
- package/template/src/stores/user.ts +145 -0
- package/template/src/styles/index.ts +29 -0
- package/template/src/types/dictionary.d.ts +24 -0
- package/template/src/types/vite-env.d.ts +119 -0
- package/template/src/utils/dictionary.ts +188 -0
- package/template/src/utils/errorAnalyzer.ts +217 -0
- package/template/src/utils/messageVNode.ts +15 -0
- package/template/src/utils/request.ts +293 -0
- package/template/src/views/error/401.vue +30 -0
- package/template/src/views/error/403.vue +30 -0
- package/template/src/views/error/404.vue +30 -0
- package/template/src/views/home/index.vue +25 -0
- package/template/tsconfig.json +27 -0
- package/template/vite.config.ts +243 -0
- package/template/yarnrc +3 -0
- package/template/yarnrc.yml +7 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 前端字典配置
|
|
3
|
+
* 用于存储和管理前端字典数据,支持根据 key 动态获取字典
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DictionaryItem, DictionaryConfig } from '@/types/dictionary';
|
|
7
|
+
|
|
8
|
+
// 重新导出类型,保持向后兼容
|
|
9
|
+
export type { DictionaryItem, DictionaryConfig };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 从配置文件导入字典配置
|
|
13
|
+
*/
|
|
14
|
+
import { dictionaryConfig as configData } from '@/config/dictionaryConfig';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 前端字典配置数据
|
|
18
|
+
* 配置数据从 @/config/dictionaryConfig 导入
|
|
19
|
+
* 可以通过 setDictionary、setDictionaries 等方法动态修改
|
|
20
|
+
*/
|
|
21
|
+
const dictionaryConfig: DictionaryConfig = { ...configData };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 根据 key 获取字典列表
|
|
25
|
+
* @param key - 字典 key
|
|
26
|
+
* @returns 返回字典项数组,如果不存在则返回空数组
|
|
27
|
+
*/
|
|
28
|
+
export const getDictionary = (key: string): DictionaryItem[] => {
|
|
29
|
+
return dictionaryConfig[key] || [];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 根据 key 获取字典选项(用于下拉框等组件)
|
|
34
|
+
* @param key - 字典 key
|
|
35
|
+
* @param filterDisabled - 是否过滤禁用的项(默认 true)
|
|
36
|
+
* @returns 返回选项数组,格式为 { label, value, ... }
|
|
37
|
+
*/
|
|
38
|
+
export const getDictionaryOptions = (
|
|
39
|
+
key: string,
|
|
40
|
+
filterDisabled: boolean = true
|
|
41
|
+
): Array<{ label: string; value: string; [key: string]: any }> => {
|
|
42
|
+
const dictList = getDictionary(key);
|
|
43
|
+
let result = dictList.map(item => ({
|
|
44
|
+
label: item.label,
|
|
45
|
+
value: item.code,
|
|
46
|
+
...item,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// 过滤禁用的项
|
|
50
|
+
if (filterDisabled) {
|
|
51
|
+
result = result.filter(item => !item.disabled);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 按 sort 排序
|
|
55
|
+
result.sort((a, b) => {
|
|
56
|
+
const sortA = a.sort ?? 999;
|
|
57
|
+
const sortB = b.sort ?? 999;
|
|
58
|
+
return sortA - sortB;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 根据 key 和 code 获取字典项
|
|
66
|
+
* @param key - 字典 key
|
|
67
|
+
* @param code - 字典编码
|
|
68
|
+
* @returns 返回字典项,如果不存在则返回 undefined
|
|
69
|
+
*/
|
|
70
|
+
export const getDictionaryItem = (key: string, code: string): DictionaryItem | undefined => {
|
|
71
|
+
const dictList = getDictionary(key);
|
|
72
|
+
return dictList.find(item => item.code === code);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 根据 key 和 code 获取字典标签
|
|
77
|
+
* @param key - 字典 key
|
|
78
|
+
* @param code - 字典编码
|
|
79
|
+
* @param defaultValue - 默认值(如果找不到对应的字典项)
|
|
80
|
+
* @returns 返回字典标签,如果不存在则返回默认值或 code
|
|
81
|
+
*/
|
|
82
|
+
export const getDictionaryLabel = (
|
|
83
|
+
key: string,
|
|
84
|
+
code: string,
|
|
85
|
+
defaultValue?: string
|
|
86
|
+
): string => {
|
|
87
|
+
const item = getDictionaryItem(key, code);
|
|
88
|
+
return item?.label || defaultValue || code;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 设置字典配置
|
|
93
|
+
* @param key - 字典 key
|
|
94
|
+
* @param dictList - 字典项数组
|
|
95
|
+
*/
|
|
96
|
+
export const setDictionary = (key: string, dictList: DictionaryItem[]): void => {
|
|
97
|
+
dictionaryConfig[key] = dictList;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 批量设置字典配置
|
|
102
|
+
* @param config - 字典配置对象
|
|
103
|
+
*/
|
|
104
|
+
export const setDictionaries = (config: DictionaryConfig): void => {
|
|
105
|
+
Object.assign(dictionaryConfig, config);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 添加字典项
|
|
110
|
+
* @param key - 字典 key
|
|
111
|
+
* @param item - 字典项
|
|
112
|
+
*/
|
|
113
|
+
export const addDictionaryItem = (key: string, item: DictionaryItem): void => {
|
|
114
|
+
if (!dictionaryConfig[key]) {
|
|
115
|
+
dictionaryConfig[key] = [];
|
|
116
|
+
}
|
|
117
|
+
// 检查是否已存在相同的 code
|
|
118
|
+
const index = dictionaryConfig[key].findIndex(d => d.code === item.code);
|
|
119
|
+
if (index >= 0) {
|
|
120
|
+
// 如果存在,则更新
|
|
121
|
+
dictionaryConfig[key][index] = item;
|
|
122
|
+
} else {
|
|
123
|
+
// 如果不存在,则添加
|
|
124
|
+
dictionaryConfig[key].push(item);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 移除字典项
|
|
130
|
+
* @param key - 字典 key
|
|
131
|
+
* @param code - 字典编码
|
|
132
|
+
*/
|
|
133
|
+
export const removeDictionaryItem = (key: string, code: string): void => {
|
|
134
|
+
if (!dictionaryConfig[key]) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
dictionaryConfig[key] = dictionaryConfig[key].filter(item => item.code !== code);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 移除整个字典
|
|
142
|
+
* @param key - 字典 key
|
|
143
|
+
*/
|
|
144
|
+
export const removeDictionary = (key: string): void => {
|
|
145
|
+
delete dictionaryConfig[key];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 获取所有字典 key
|
|
150
|
+
* @returns 返回所有字典 key 数组
|
|
151
|
+
*/
|
|
152
|
+
export const getAllDictionaryKeys = (): string[] => {
|
|
153
|
+
return Object.keys(dictionaryConfig);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 检查字典是否存在
|
|
158
|
+
* @param key - 字典 key
|
|
159
|
+
* @returns 返回是否存在
|
|
160
|
+
*/
|
|
161
|
+
export const hasDictionary = (key: string): boolean => {
|
|
162
|
+
return key in dictionaryConfig && Array.isArray(dictionaryConfig[key]) && dictionaryConfig[key].length > 0;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 清空所有字典配置
|
|
167
|
+
*/
|
|
168
|
+
export const clearAllDictionaries = (): void => {
|
|
169
|
+
Object.keys(dictionaryConfig).forEach(key => {
|
|
170
|
+
delete dictionaryConfig[key];
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 导出字典配置(用于备份或调试)
|
|
176
|
+
* @returns 返回当前所有字典配置
|
|
177
|
+
*/
|
|
178
|
+
export const exportDictionaryConfig = (): DictionaryConfig => {
|
|
179
|
+
return JSON.parse(JSON.stringify(dictionaryConfig));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 导入字典配置(用于恢复或初始化)
|
|
184
|
+
* @param config - 字典配置对象
|
|
185
|
+
*/
|
|
186
|
+
export const importDictionaryConfig = (config: DictionaryConfig): void => {
|
|
187
|
+
Object.assign(dictionaryConfig, config);
|
|
188
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误信息分析工具类
|
|
3
|
+
* 用于分析和序列化来自 resInterceptor.js 的错误对象
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 安全的序列化函数,处理循环引用和不可序列化的值
|
|
8
|
+
* @param obj - 要序列化的对象
|
|
9
|
+
* @param space - JSON 格式化的缩进空格数
|
|
10
|
+
* @returns 序列化后的 JSON 字符串
|
|
11
|
+
*/
|
|
12
|
+
export function safeStringify(obj: any, space: number = 2): string {
|
|
13
|
+
const seen = new WeakSet();
|
|
14
|
+
return JSON.stringify(
|
|
15
|
+
obj,
|
|
16
|
+
(key, value) => {
|
|
17
|
+
// 跳过函数
|
|
18
|
+
if (typeof value === 'function') {
|
|
19
|
+
return '[Function]';
|
|
20
|
+
}
|
|
21
|
+
// 跳过 undefined(返回 undefined 会从结果中移除该属性)
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
// 处理循环引用
|
|
26
|
+
if (typeof value === 'object' && value !== null) {
|
|
27
|
+
if (seen.has(value)) {
|
|
28
|
+
return '[Circular]';
|
|
29
|
+
}
|
|
30
|
+
seen.add(value);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
},
|
|
34
|
+
space
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 兼容的复制函数,支持多种复制方式
|
|
40
|
+
* @param text - 要复制的文本
|
|
41
|
+
* @returns Promise<void>
|
|
42
|
+
*/
|
|
43
|
+
export async function copyToClipboard(text: string): Promise<void> {
|
|
44
|
+
// 方法1: 使用 Clipboard API(现代浏览器)
|
|
45
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
46
|
+
try {
|
|
47
|
+
await navigator.clipboard.writeText(text);
|
|
48
|
+
return;
|
|
49
|
+
} catch (clipboardErr) {
|
|
50
|
+
console.warn('Clipboard API 失败,尝试备用方法:', clipboardErr);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 方法2: 使用 document.execCommand(兼容旧浏览器)
|
|
55
|
+
const textarea = document.createElement('textarea');
|
|
56
|
+
textarea.value = text;
|
|
57
|
+
textarea.style.position = 'fixed';
|
|
58
|
+
textarea.style.left = '-9999px';
|
|
59
|
+
textarea.style.top = '-9999px';
|
|
60
|
+
document.body.appendChild(textarea);
|
|
61
|
+
textarea.select();
|
|
62
|
+
textarea.setSelectionRange(0, text.length);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const success = document.execCommand('copy');
|
|
66
|
+
document.body.removeChild(textarea);
|
|
67
|
+
if (!success) {
|
|
68
|
+
throw new Error('execCommand 复制失败');
|
|
69
|
+
}
|
|
70
|
+
} catch (execErr) {
|
|
71
|
+
document.body.removeChild(textarea);
|
|
72
|
+
throw execErr;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 错误对象结构(根据 resInterceptor.js)
|
|
78
|
+
*/
|
|
79
|
+
export interface ErrorInfo {
|
|
80
|
+
// 响应信息
|
|
81
|
+
status?: number;
|
|
82
|
+
statusText?: string;
|
|
83
|
+
response?: any;
|
|
84
|
+
// 请求信息
|
|
85
|
+
request?: {
|
|
86
|
+
url?: string;
|
|
87
|
+
method?: string;
|
|
88
|
+
params?: any;
|
|
89
|
+
data?: any;
|
|
90
|
+
headers?: any;
|
|
91
|
+
timeout?: number;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 分析错误对象,提取关键信息
|
|
97
|
+
* @param error - 错误对象(来自 resInterceptor.js)
|
|
98
|
+
* @returns 分析后的错误信息对象
|
|
99
|
+
*/
|
|
100
|
+
export function analyzeError(error: any): {
|
|
101
|
+
errorMessage: string;
|
|
102
|
+
status: number | undefined;
|
|
103
|
+
statusText: string | undefined;
|
|
104
|
+
requestUrl: string | undefined;
|
|
105
|
+
requestMethod: string | undefined;
|
|
106
|
+
hasRequestData: boolean;
|
|
107
|
+
hasResponseData: boolean;
|
|
108
|
+
errorKeys: string[];
|
|
109
|
+
} {
|
|
110
|
+
return {
|
|
111
|
+
errorMessage: error?.response?.message || error?.statusText || '未知错误',
|
|
112
|
+
status: error?.status,
|
|
113
|
+
statusText: error?.statusText,
|
|
114
|
+
requestUrl: error?.request?.url || error?.config?.url,
|
|
115
|
+
requestMethod: error?.request?.method || error?.config?.method,
|
|
116
|
+
hasRequestData: !!(error?.request?.data || error?.config?.data),
|
|
117
|
+
hasResponseData: !!error?.response,
|
|
118
|
+
errorKeys: Object.keys(error || {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 按照 resInterceptor.js 的结构组织错误信息
|
|
124
|
+
* @param error - 错误对象(来自 resInterceptor.js)
|
|
125
|
+
* @returns 按照 resInterceptor.js 结构组织的错误信息对象
|
|
126
|
+
*/
|
|
127
|
+
export function formatErrorInfo(error: any): ErrorInfo {
|
|
128
|
+
// 如果错误对象已经是 resInterceptor.js 的结构,直接使用
|
|
129
|
+
if (error?.status !== undefined && error?.request !== undefined) {
|
|
130
|
+
// 确保 request 对象存在且包含必要的字段
|
|
131
|
+
const request = error.request || {};
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
// 只返回 error 中存在的字段,不强制添加 status 和 statusText
|
|
135
|
+
...(error.status !== undefined && { status: error.status }),
|
|
136
|
+
...(error.statusText !== undefined && { statusText: error.statusText }),
|
|
137
|
+
response: error.response,
|
|
138
|
+
request: {
|
|
139
|
+
url: request.url || '',
|
|
140
|
+
method: request.method || 'GET',
|
|
141
|
+
params: request.params || {},
|
|
142
|
+
data: request.data || {},
|
|
143
|
+
headers: request.headers || {},
|
|
144
|
+
timeout: request.timeout,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 否则,从 error.response 和 error.config 中提取信息(兼容 axios 原始错误)
|
|
150
|
+
const res = error?.response || error;
|
|
151
|
+
|
|
152
|
+
// 获取完整的请求 headers(优先使用 _requestHeaders)
|
|
153
|
+
const requestHeaders = res?.config?._requestHeaders || res?.config?.headers || {};
|
|
154
|
+
|
|
155
|
+
// 清理 headers,移除 Axios 内部使用的字段
|
|
156
|
+
const cleanedHeaders: any = {};
|
|
157
|
+
const excludeKeys = ['common', 'delete', 'get', 'head', 'post', 'put', 'patch'];
|
|
158
|
+
|
|
159
|
+
for (const key in requestHeaders) {
|
|
160
|
+
if (!excludeKeys.includes(key)) {
|
|
161
|
+
cleanedHeaders[key] = requestHeaders[key];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
// 只返回存在的字段,不强制添加 status 和 statusText
|
|
167
|
+
...(res?.status !== undefined && { status: res.status }),
|
|
168
|
+
...(res?.statusText !== undefined && { statusText: res.statusText }),
|
|
169
|
+
response: res?.data,
|
|
170
|
+
// 请求信息(从 config 中获取,包含完整的 headers)
|
|
171
|
+
request: {
|
|
172
|
+
url: res?.config?.url || res?.config?.baseURL || '',
|
|
173
|
+
method: res?.config?.method?.toUpperCase() || 'GET',
|
|
174
|
+
params: res?.config?.params || {},
|
|
175
|
+
data: res?.config?.data || {},
|
|
176
|
+
headers: cleanedHeaders,
|
|
177
|
+
timeout: res?.config?.timeout,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 序列化错误对象为可复制的 JSON 字符串
|
|
184
|
+
* 按照 resInterceptor.js 的结构组织错误信息
|
|
185
|
+
* @param error - 错误对象(来自 resInterceptor.js)
|
|
186
|
+
* @returns 序列化后的 JSON 字符串
|
|
187
|
+
*/
|
|
188
|
+
export function serializeError(error: any): string {
|
|
189
|
+
try {
|
|
190
|
+
const errorInfo = formatErrorInfo(error);
|
|
191
|
+
return safeStringify(errorInfo, 2);
|
|
192
|
+
} catch (stringifyErr) {
|
|
193
|
+
console.error('序列化错误失败:', stringifyErr);
|
|
194
|
+
// 如果序列化失败,返回简单的错误信息
|
|
195
|
+
return JSON.stringify({
|
|
196
|
+
error: '序列化失败',
|
|
197
|
+
message: error?.message || String(error),
|
|
198
|
+
timestamp: new Date().toISOString(),
|
|
199
|
+
}, null, 2);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 复制错误信息到剪贴板
|
|
205
|
+
* @param error - 错误对象(来自 resInterceptor.js)或错误消息字符串
|
|
206
|
+
* @returns Promise<void>
|
|
207
|
+
*/
|
|
208
|
+
export async function copyErrorToClipboard(error: any): Promise<void> {
|
|
209
|
+
// 如果传入的是字符串,直接复制字符串
|
|
210
|
+
if (typeof error === 'string') {
|
|
211
|
+
await copyToClipboard(error);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// 如果是对象,序列化后复制
|
|
215
|
+
const errorText = serializeError(error);
|
|
216
|
+
await copyToClipboard(errorText);
|
|
217
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { h } from 'vue';
|
|
2
|
+
import ExpandableMessage from '@/components/ExpandableMessage.vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 创建可展开/收缩的错误消息 vnode
|
|
6
|
+
* @param errorMessage - 错误消息文本
|
|
7
|
+
* @param maxLines - 默认显示的最大行数,超出后显示展开按钮,默认 3
|
|
8
|
+
* @returns 返回一个 vnode
|
|
9
|
+
*/
|
|
10
|
+
export const createExpandableMessageVNode = (errorMessage: string, maxLines: number = 3) => {
|
|
11
|
+
return h(ExpandableMessage, {
|
|
12
|
+
message: errorMessage,
|
|
13
|
+
maxLines: maxLines
|
|
14
|
+
});
|
|
15
|
+
};
|