@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.
Files changed (57) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +63 -0
  3. package/deppon.js +640 -0
  4. package/package.json +51 -0
  5. package/template/.env +12 -0
  6. package/template/.env.dev-local.example +64 -0
  7. package/template/.env.development.example +64 -0
  8. package/template/.env.example +1 -0
  9. package/template/.env.production.example +64 -0
  10. package/template/.env.test.example +64 -0
  11. package/template/.eslintignore +2 -0
  12. package/template/.eslintrc.cjs +14 -0
  13. package/template/.prettierrc.js +3 -0
  14. package/template/.vscode/settings.json +8 -0
  15. package/template/Dockerfile +5 -0
  16. package/template/README.md +149 -0
  17. package/template/commitlint.config.js +11 -0
  18. package/template/gitignore +8 -0
  19. package/template/index.html +18 -0
  20. package/template/nginx.conf +70 -0
  21. package/template/npmrc +2 -0
  22. package/template/package.json +49 -0
  23. package/template/preview-server.js +117 -0
  24. package/template/public/favicon.ico +0 -0
  25. package/template/public/logo.png +0 -0
  26. package/template/src/App.vue +123 -0
  27. package/template/src/api/index.ts +13 -0
  28. package/template/src/api/prefercenter.ts +23 -0
  29. package/template/src/api/product.ts +16 -0
  30. package/template/src/api/user.ts +41 -0
  31. package/template/src/components/ExpandableMessage.vue +340 -0
  32. package/template/src/components/PageLayout.vue +43 -0
  33. package/template/src/config/dictionaryConfig.ts +24 -0
  34. package/template/src/directives/permission.ts +162 -0
  35. package/template/src/layouts/BaseLayout.vue +687 -0
  36. package/template/src/main.ts +27 -0
  37. package/template/src/router/index.ts +179 -0
  38. package/template/src/router/route.ts +61 -0
  39. package/template/src/stores/menu.ts +334 -0
  40. package/template/src/stores/product.ts +155 -0
  41. package/template/src/stores/route.ts +79 -0
  42. package/template/src/stores/user.ts +145 -0
  43. package/template/src/styles/index.ts +29 -0
  44. package/template/src/types/dictionary.d.ts +24 -0
  45. package/template/src/types/vite-env.d.ts +119 -0
  46. package/template/src/utils/dictionary.ts +188 -0
  47. package/template/src/utils/errorAnalyzer.ts +217 -0
  48. package/template/src/utils/messageVNode.ts +15 -0
  49. package/template/src/utils/request.ts +293 -0
  50. package/template/src/views/error/401.vue +30 -0
  51. package/template/src/views/error/403.vue +30 -0
  52. package/template/src/views/error/404.vue +30 -0
  53. package/template/src/views/home/index.vue +25 -0
  54. package/template/tsconfig.json +27 -0
  55. package/template/vite.config.ts +243 -0
  56. package/template/yarnrc +3 -0
  57. package/template/yarnrc.yml +7 -0
@@ -0,0 +1,293 @@
1
+ import request from '@deppon/deppon-request';
2
+ import { ElMessageBox, ElMessage } from '@deppon/deppon-ui';
3
+ import { copyErrorToClipboard } from './errorAnalyzer';
4
+ import { createExpandableMessageVNode } from './messageVNode';
5
+
6
+ /**
7
+ * 从环境变量获取 API 地址
8
+ * @param key - 环境变量键名
9
+ * @returns API 地址,如果不存在则返回空字符串
10
+ */
11
+ const getApiUrl = (key: string): string => {
12
+ const env = import.meta.env as Record<string, string | undefined>;
13
+ return env[key] ?? '';
14
+ };
15
+
16
+ /**
17
+ * 检查 URL 是否应该使用代理
18
+ * @param url - 请求的 URL 路径(此参数保留以保持接口一致性,但当前不检查具体路径)
19
+ * @returns 是否应该使用代理
20
+ *
21
+ * 说明:
22
+ * - 仅在开发服务器运行时(import.meta.env.DEV === true)使用代理
23
+ * - 构建后的代码(即使是 development mode)也会使用完整的 API URL
24
+ * - 在开发环境中,所有相对路径都使用代理,代理配置在 vite.config.ts 中管理
25
+ * - 这样本地开发时使用代理避免 CORS,测试服务器构建后直接请求 API
26
+ * - 添加新的 API 路径时,只需在 vite.config.ts 中配置代理,无需修改此文件
27
+ */
28
+ const shouldUseProxy = (url: string): boolean => {
29
+ // 只有在开发服务器运行时才使用代理
30
+ // import.meta.env.DEV 在 dev server 运行时为 true,构建后为 false
31
+ return import.meta.env.DEV;
32
+ };
33
+
34
+ /**
35
+ * 根据 URL 路径自动识别应该使用的 API 地址
36
+ * @param url - 请求的 URL 路径
37
+ */
38
+ const getBaseURLByUrl = (url: string): string => {
39
+ // 如果 URL 已经是完整地址,直接返回空(不添加 baseURL)
40
+ if (url.startsWith('http')) {
41
+ return '';
42
+ }
43
+
44
+ // 开发环境中,如果 URL 会被代理处理,则不添加 baseURL
45
+ if (shouldUseProxy(url)) {
46
+ return '';
47
+ }
48
+
49
+ // 根据 URL 路径特征判断使用哪个系统的哪个 API 地址
50
+ // prefercenter 系统路径特征
51
+ if (url.includes('/cmc-prefer-web/')) {
52
+ return getApiUrl('DP_API_HOST');
53
+ }
54
+
55
+ // UAP 用户信息服务
56
+ if (url.includes('/uap-index-service/')) {
57
+ return getApiUrl('DP_UAP_API_HOST') || '';
58
+ }
59
+
60
+ // 默认使用主业务 API 地址
61
+ return getApiUrl('DP_API_HOST');
62
+ };
63
+
64
+ // 包装请求函数,自动添加 baseURL
65
+ const requestWithBaseURL = async (options: any) => {
66
+ // 检查 Content-Type 是否为 multipart/form-data
67
+ const contentType = options.headers?.['Content-Type'] || options.headers?.['content-type'];
68
+ const isMultipartFormData = contentType && contentType.includes('multipart/form-data');
69
+
70
+ // 过滤掉空参数(处理 params 和 data),但 multipart/form-data 不处理
71
+ if (!isMultipartFormData) {
72
+ if (options.params && typeof options.params === 'object') {
73
+ options.params = filterEmptyParams(options.params);
74
+ }
75
+ if (options.data && typeof options.data === 'object') {
76
+ options.data = filterEmptyParams(options.data);
77
+ }
78
+ }
79
+
80
+ const baseURL = getBaseURLByUrl(options.url ?? '');
81
+ // 如果 URL 不是以 http 开头,且配置了 baseURL,则添加 baseURL
82
+ if (options.url && !options.url.startsWith('http') && baseURL) {
83
+ options.url = `${baseURL}${options.url}`;
84
+ }
85
+
86
+ try {
87
+ const response = await request(options);
88
+ // 统一处理 success: false 的情况
89
+ if (response && response.success === false) {
90
+ // 优先使用 errMessage,确保是字符串类型
91
+ let errorMessage: string = '';
92
+ if (response?.errMessage) {
93
+ errorMessage = typeof response.errMessage === 'string' ? response.errMessage : String(response.errMessage);
94
+ } else {
95
+ const fallbackMessage = response?.data || response?.message;
96
+ errorMessage = typeof fallbackMessage === 'string' ? fallbackMessage : String(fallbackMessage || '请求失败');
97
+ }
98
+ // 使用可展开/收缩的消息 vnode
99
+ const messageVNode = createExpandableMessageVNode(errorMessage, 5);
100
+
101
+ // 使用 ElMessageBox 显示错误信息
102
+ ElMessageBox({
103
+ title: '提示',
104
+ message: messageVNode,
105
+ confirmButtonText: '知道了',
106
+ type: 'warning',
107
+ draggable: true,
108
+ dangerouslyUseHTMLString: false,
109
+ })
110
+ .then(() => {
111
+ // 用户点击确定后的处理
112
+ })
113
+ .catch(() => {
114
+ // 用户关闭对话框
115
+ });
116
+ // 使用 warn 记录
117
+ console.error('Request success: false:', response);
118
+ // 不抛出错误,直接返回,让调用方知道请求失败但不影响后续流程
119
+ }
120
+ return response;
121
+ } catch (error: any) {
122
+ // 使用 error 记录
123
+ console.error('Request error:', error);
124
+
125
+ // 统一处理错误消息,安全地拼接 message 和 errMessage
126
+ let errorMessage = '请求失败';
127
+ if (error?.data) {
128
+ const data = error.data;
129
+ const message = data.message || '';
130
+ const errMessage = data.errMessage || '';
131
+ if (message || errMessage) {
132
+ errorMessage = message + (errMessage ? (message ? ':' : '') + errMessage : '');
133
+ } else if (typeof data === 'string') {
134
+ errorMessage = data;
135
+ }
136
+ } else if (error?.message) {
137
+ errorMessage = error.message;
138
+ }
139
+
140
+ // success: false 的情况下不显示复制错误信息按钮
141
+ const showCancelButton = !error?.isSuccessFalse;
142
+
143
+ // 统一处理错误消息,格式化显示错误信息
144
+ let finalMessage = String(errorMessage);
145
+ const responseData = error?.response?.data || error?.data || {};
146
+ const status = error?.response?.status || error?.status || responseData.status || '';
147
+ const url = error?.response?.config?.url || error?.config?.url || responseData.path || '';
148
+
149
+ // 格式化错误信息
150
+ const errorParts: string[] = [];
151
+
152
+ // 错误类型
153
+ if (responseData.error) {
154
+ // 将错误类型转换为中文
155
+ const errorTypeMap: Record<string, string> = {
156
+ 'Internal Server Error': '服务异常',
157
+ 'Bad Request': '请求错误',
158
+ 'Unauthorized': '未授权',
159
+ 'Forbidden': '禁止访问',
160
+ 'Not Found': '未找到',
161
+ 'Method Not Allowed': '方法不允许',
162
+ 'Conflict': '冲突',
163
+ 'Unprocessable Entity': '无法处理的实体',
164
+ 'Too Many Requests': '请求过多',
165
+ 'Gateway Timeout': '网关超时',
166
+ 'Service Unavailable': '服务不可用',
167
+ 'Bad Gateway': '网关错误',
168
+ };
169
+ const errorType = errorTypeMap[responseData.error] || '服务异常';
170
+ errorParts.push(`错误类型:${errorType}`);
171
+ } else if (status === 502) {
172
+ // 处理 502 状态码错误
173
+ errorParts.push(`错误类型:网关错误`);
174
+ }
175
+
176
+ // 错误消息
177
+ const message = responseData.message || '';
178
+ const errMessage = responseData.errMessage || '';
179
+ if (message || errMessage) {
180
+ const msg = message + (errMessage ? (message ? ':' : '') + errMessage : '');
181
+ if (msg) {
182
+ errorParts.push(`错误信息:${msg}`);
183
+ }
184
+ } else if (finalMessage && finalMessage !== '请求失败') {
185
+ // 处理常见的 HTTP 错误消息,转换为更友好的中文提示
186
+ let displayMessage = finalMessage;
187
+ if (status === 502) {
188
+ if (finalMessage.includes('502') || finalMessage.includes('Bad Gateway')) {
189
+ displayMessage = '服务器收到无效响应,请稍后重试';
190
+ }
191
+ }
192
+ errorParts.push(`错误信息:${displayMessage}`);
193
+ }
194
+
195
+ // 接口路径
196
+ if (url) {
197
+ errorParts.push(`接口路径:${url}`);
198
+ }
199
+
200
+ // 状态码
201
+ if (status) {
202
+ errorParts.push(`状态码:${status}`);
203
+ }
204
+
205
+ // 时间戳
206
+ if (responseData.timestamp) {
207
+ const date = new Date(responseData.timestamp);
208
+ const formattedTime = date.toLocaleString('zh-CN', {
209
+ year: 'numeric',
210
+ month: '2-digit',
211
+ day: '2-digit',
212
+ hour: '2-digit',
213
+ minute: '2-digit',
214
+ second: '2-digit'
215
+ });
216
+ errorParts.push(`时间:${formattedTime}`);
217
+ }
218
+
219
+ // 组合最终消息,使用 <br> 标签换行
220
+ if (errorParts.length > 0) {
221
+ finalMessage = errorParts.join('<br>');
222
+ }
223
+
224
+ // 使用可展开/收缩的消息 vnode
225
+ const messageVNode = createExpandableMessageVNode(finalMessage, 5);
226
+
227
+ ElMessageBox({
228
+ title: '提示',
229
+ message: messageVNode,
230
+ confirmButtonText: '知道了',
231
+ cancelButtonText: '复制错误信息',
232
+ showCancelButton: showCancelButton,
233
+ distinguishCancelAndClose: true, // 区分取消按钮和关闭按钮
234
+ type: 'error',
235
+ draggable: true,
236
+ dangerouslyUseHTMLString: false, // 使用 vnode,不使用 HTML
237
+ })
238
+ .then(() => {
239
+ // 用户点击确定后的处理
240
+ })
241
+ .catch(async (action: string) => {
242
+ // 只有当用户点击"复制错误信息"按钮时才执行复制操作
243
+ // action === 'cancel' 表示点击了取消按钮(复制错误信息)
244
+ // action === 'close' 表示点击了关闭按钮(x)
245
+ if (action === 'cancel') {
246
+ try {
247
+ await copyErrorToClipboard(error);
248
+ ElMessage.success('完整错误信息已复制到剪贴板,可发送给开发人员');
249
+ } catch (err: any) {
250
+ console.error('复制错误信息失败:', err);
251
+ ElMessage.error(`复制失败: ${err?.message || '未知错误'},请手动复制控制台中的错误信息`);
252
+ }
253
+ }
254
+ // 如果 action === 'close',不做任何处理,只是关闭对话框
255
+ });
256
+
257
+ throw error; // 重新抛出错误,让调用方知道请求失败
258
+ }
259
+ };
260
+
261
+ /**
262
+ * 过滤掉空参数
263
+ * @param params - 参数对象
264
+ * @returns 过滤后的参数对象(只包含有效值)
265
+ */
266
+ export const filterEmptyParams = (params: Record<string, any> | any[]): Record<string, any> | any[] => {
267
+ // 如果是数组,直接返回(不处理数组)
268
+ if (Array.isArray(params)) {
269
+ return params;
270
+ }
271
+
272
+ if (!params || typeof params !== 'object') {
273
+ return {};
274
+ }
275
+
276
+ return Object.keys(params).reduce((acc: any, key: string) => {
277
+ const value = params[key];
278
+ // 检查值是否有效(不为空、null、undefined、空字符串、空数组)
279
+ const isValid =
280
+ value !== null &&
281
+ value !== undefined &&
282
+ value !== '' &&
283
+ !(Array.isArray(value) && value.length === 0) &&
284
+ !(typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length === 0);
285
+
286
+ if (isValid) {
287
+ acc[key] = value;
288
+ }
289
+ return acc;
290
+ }, {});
291
+ };
292
+
293
+ export default requestWithBaseURL;
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div class="error-page">
3
+ <h1>401</h1>
4
+ <p>未授权,请先登录</p>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ // 401 未授权页面
10
+ </script>
11
+
12
+ <style scoped lang="less">
13
+ .error-page {
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ justify-content: center;
18
+ height: 100vh;
19
+ h1 {
20
+ font-size: 72px;
21
+ color: #1957ff;
22
+ margin-bottom: 20px;
23
+ }
24
+ p {
25
+ color: #666;
26
+ font-size: 18px;
27
+ }
28
+ }
29
+ </style>
30
+
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div class="error-page">
3
+ <h1>403</h1>
4
+ <p>禁止访问</p>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ // 403 禁止访问页面
10
+ </script>
11
+
12
+ <style scoped lang="less">
13
+ .error-page {
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ justify-content: center;
18
+ height: 100vh;
19
+ h1 {
20
+ font-size: 72px;
21
+ color: #1957ff;
22
+ margin-bottom: 20px;
23
+ }
24
+ p {
25
+ color: #666;
26
+ font-size: 18px;
27
+ }
28
+ }
29
+ </style>
30
+
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div class="error-page">
3
+ <h1>404</h1>
4
+ <p>页面未找到</p>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ // 404 错误页面
10
+ </script>
11
+
12
+ <style scoped lang="less">
13
+ .error-page {
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ justify-content: center;
18
+ height: 100vh;
19
+ h1 {
20
+ font-size: 72px;
21
+ color: #1957ff;
22
+ margin-bottom: 20px;
23
+ }
24
+ p {
25
+ color: #666;
26
+ font-size: 18px;
27
+ }
28
+ }
29
+ </style>
30
+
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div class="home">
3
+ <h1>欢迎使用德邦前端脚手架</h1>
4
+ <p>基于 Vue 3 + TypeScript + Vite</p>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ // 首页组件
10
+ </script>
11
+
12
+ <style scoped lang="less">
13
+ .home {
14
+ padding: 20px;
15
+ text-align: center;
16
+ h1 {
17
+ color: #1957ff;
18
+ margin-bottom: 10px;
19
+ }
20
+ p {
21
+ color: #666;
22
+ }
23
+ }
24
+ </style>
25
+
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+ "module": "esnext",
5
+ "target": "es5",
6
+ "lib": ["es6", "dom", "dom.iterable"],
7
+ "allowJs": true,
8
+ "moduleResolution": "node",
9
+ "noImplicitThis": true,
10
+ "strictNullChecks": false,
11
+ "allowSyntheticDefaultImports": true,
12
+ "skipLibCheck": true,
13
+ "experimentalDecorators": true,
14
+ "emitDecoratorMetadata": true,
15
+ "allowUnreachableCode": true,
16
+ "noImplicitAny": false,
17
+ "jsx": "preserve",
18
+ "baseUrl": "./",
19
+ "paths": {
20
+ "@/*": ["src/*"],
21
+ "@deppon/deppon-ui/icons-vue": ["node_modules/@deppon/deppon-ui/es/icons-vue"]
22
+ },
23
+ "types": ["node", "vite/client"]
24
+ },
25
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/types/**/*.d.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,243 @@
1
+ import { defineConfig, loadEnv } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+ import legacy from '@vitejs/plugin-legacy';
4
+ import { resolve } from 'path';
5
+
6
+ // 跨平台兼容:获取项目根目录
7
+ const rootDir = process.cwd();
8
+
9
+ // 修复 @deppon/deppon-template 包中的导入路径问题
10
+ const fixDepponTemplateImports = () => {
11
+ return {
12
+ name: 'fix-deppon-template-imports',
13
+ enforce: 'pre' as const,
14
+ transform(code: string, id: string) {
15
+ // 在 @deppon/deppon-template 包的文件中,修复 _virtual 导入路径
16
+ if (id.includes('@deppon/deppon-template') && id.endsWith('.js')) {
17
+ // 将 'import "_virtual/...' 替换为 'import "./_virtual/...'
18
+ const fixedCode = code.replace(/import\s+['"]_virtual\//g, match =>
19
+ match.replace('_virtual/', './_virtual/'),
20
+ );
21
+ if (fixedCode !== code) {
22
+ return { code: fixedCode, map: null };
23
+ }
24
+ }
25
+ return null;
26
+ },
27
+ resolveId(id: string, importer?: string) {
28
+ // 修复 _virtual 路径问题:将 '_virtual/...' 转换为 './_virtual/...'
29
+ if (id === '_virtual/_rollup-plugin-inject-process-env.js' && importer) {
30
+ // 处理 pnpm 的路径结构
31
+ const templateMatch = importer.match(/(.*@deppon[\/\\]deppon-template[^\/\\]*[\/\\]es)/);
32
+ if (templateMatch) {
33
+ const templateEsPath = templateMatch[1].replace(/\\/g, '/');
34
+ const virtualPath = resolve(templateEsPath, `./${id}`).replace(/\\/g, '/');
35
+ return virtualPath;
36
+ }
37
+ // 如果是在 @deppon/deppon-template 包内,直接使用相对路径
38
+ if (importer.includes('@deppon/deppon-template')) {
39
+ const fileDir = importer.substring(
40
+ 0,
41
+ Math.max(importer.lastIndexOf('/'), importer.lastIndexOf('\\')),
42
+ );
43
+ return resolve(fileDir, `./${id}`);
44
+ }
45
+ }
46
+ return null;
47
+ },
48
+ };
49
+ };
50
+
51
+ // 自定义显示域名的插件(仅用于显示,不影响实际绑定)
52
+ const customDomainDisplay = (domain?: string) => {
53
+ return {
54
+ name: 'custom-domain-display',
55
+ configureServer(server: any) {
56
+ if (domain) {
57
+ const originalPrintUrls = server.printUrls;
58
+ server.printUrls = () => {
59
+ const port = server.config.server?.port || 3100;
60
+ const protocol = server.config.server?.https ? 'https' : 'http';
61
+ const localUrl = `${protocol}://${domain}:${port}/`;
62
+ const networkUrls = server.resolvedUrls;
63
+ const networkUrl = networkUrls?.network?.[0] || '';
64
+ const blueColor = '\u001b[34m'; // 蓝色
65
+ const resetColor = '\u001b[0m'; // 重置颜色
66
+ // 使用 OSC 8 协议让终端识别为可点击链接(支持 iTerm2、Windows Terminal 等)
67
+ const clickableUrl = `\u001b]8;;${localUrl}\u001b\\${blueColor}${localUrl}${resetColor}\u001b]8;;\u001b\\`;
68
+ console.log(`\n ➜ Network: ${clickableUrl}`);
69
+ };
70
+ }
71
+ },
72
+ };
73
+ };
74
+
75
+ // 生产环境移除 console 和 debugger
76
+ const removeConsole = (buildMode: string) => {
77
+ return {
78
+ name: 'remove-console',
79
+ enforce: 'post' as const,
80
+ apply: 'build' as const,
81
+ transform(code: string, id: string) {
82
+ // 只处理 JS/TS 文件,排除 node_modules
83
+ if (buildMode === 'production' && !id.includes('node_modules') && /\.(js|ts|vue)$/.test(id)) {
84
+ return {
85
+ code: code
86
+ .replace(
87
+ /console\.(log|debug|info|warn|error|trace|table|group|groupEnd|time|timeEnd)\([^)]*\);?/g,
88
+ '',
89
+ )
90
+ .replace(/debugger;?/g, ''),
91
+ map: null,
92
+ };
93
+ }
94
+ return null;
95
+ },
96
+ };
97
+ };
98
+
99
+ export default defineConfig(({ mode }) => {
100
+ // 加载环境变量,使用空字符串作为前缀以加载所有变量(包括 DP_ 前缀)
101
+ const env = loadEnv(mode, process.cwd(), '');
102
+
103
+ // 构建代理配置
104
+ const getProxyConfig = () => {
105
+ const proxyConfig: Record<string, any> = {};
106
+
107
+ // 原有的 /api 代理配置
108
+ if (env.DP_API_URL) {
109
+ proxyConfig['/api'] = {
110
+ target: env.DP_API_URL,
111
+ changeOrigin: true,
112
+ rewrite: (path: string) => path.replace(/^\/api/, ''),
113
+ };
114
+ }
115
+
116
+ // prefercenter 系统代理 - /cmc-prefer-web/
117
+ // 在开发模式和测试模式下启用代理
118
+ if (env.DP_API_HOST && (mode === 'development' || mode === 'test' || mode === 'dev-local')) {
119
+ const apiHost = env.DP_API_HOST.replace(/\/$/, '');
120
+ proxyConfig['/cmc-prefer-web'] = {
121
+ target: apiHost,
122
+ changeOrigin: true,
123
+ secure: false,
124
+ };
125
+ }
126
+
127
+ // UAP 用户信息服务代理 - /uap-index-service/
128
+ if (env.DP_UAP_API_HOST && (mode === 'development' || mode === 'test' || mode === 'dev-local')) {
129
+ const uapHost = env.DP_UAP_API_HOST.replace(/\/$/, '').replace(/\/uap-index-service.*$/, '');
130
+ proxyConfig['/uap-index-service'] = {
131
+ target: uapHost || 'http://uapsit.deppontest.com',
132
+ changeOrigin: true,
133
+ secure: false,
134
+ };
135
+ }
136
+
137
+ return Object.keys(proxyConfig).length > 0 ? proxyConfig : undefined;
138
+ };
139
+
140
+ return {
141
+ plugins: [
142
+ vue(),
143
+ fixDepponTemplateImports(),
144
+ // 自定义域名显示插件(仅用于显示,不影响实际绑定)
145
+ customDomainDisplay(env.DP_DOMAIN),
146
+ // 生产环境移除 console 和 debugger
147
+ removeConsole(mode),
148
+ // Legacy 插件:支持旧版浏览器(包括 Firefox 63.0)
149
+ legacy({
150
+ targets: ['Firefox >= 60', 'Chrome >= 60', 'Safari >= 10', 'Edge >= 79'],
151
+ // 现代浏览器不需要 legacy 代码
152
+ modernPolyfills: false,
153
+ // 添加必要的 polyfill
154
+ additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
155
+ }),
156
+ ],
157
+ resolve: {
158
+ alias: {
159
+ '@': resolve(rootDir, 'src'),
160
+ // 修复 @deppon/deppon-template 包中的 _virtual 路径问题
161
+ '_virtual/_rollup-plugin-inject-process-env.js': resolve(
162
+ rootDir,
163
+ 'node_modules/@deppon/deppon-template/es/_virtual/_rollup-plugin-inject-process-env.js',
164
+ ),
165
+ },
166
+ },
167
+ css: {
168
+ preprocessorOptions: {
169
+ less: {
170
+ // 支持在 less 中使用 JavaScript
171
+ javascriptEnabled: true,
172
+ // 配置 Less 的路径选项,确保能正确解析 node_modules 中的 @import
173
+ paths: [resolve(rootDir, 'node_modules'), resolve(rootDir, 'src')],
174
+ // Less 配置选项:确保在构建时正确处理 @import
175
+ modifyVars: {},
176
+ },
177
+ },
178
+ // 确保处理 node_modules 中的 CSS/Less 文件
179
+ postcss: undefined,
180
+ // 根据环境变量配置是否启用 source map
181
+ devSourcemap: env.DP_SOURCEMAP === 'true',
182
+ // 构建时确保处理所有 CSS/Less 文件,包括 node_modules
183
+ modules: false,
184
+ },
185
+ server: {
186
+ port: Number(env.DP_PORT) || 3100,
187
+ // 实际绑定到 0.0.0.0(允许所有网络接口访问)
188
+ // 显示的自定义域名通过 customDomainDisplay 插件处理
189
+ host: env.DP_HOST || '0.0.0.0',
190
+ // 如果设置了 DP_DOMAIN,自动打开时使用自定义域名;否则使用默认行为
191
+ open:
192
+ env.DP_OPEN === 'true'
193
+ ? env.DP_DOMAIN
194
+ ? `http://${env.DP_DOMAIN}:${Number(env.DP_PORT) || 3100}/`
195
+ : true
196
+ : false,
197
+ proxy: getProxyConfig(),
198
+ allowedHosts: env.DP_ALLOWED_HOSTS?.split(',') || [],
199
+ // allowedHosts 在 Vite 中通过 host 配置实现
200
+ },
201
+ base: env.DP_PUBLIC_PATH || '/',
202
+ // 配置环境变量前缀,同时支持 VITE_ 和 DP_ 前缀
203
+ envPrefix: ['VITE_', 'DP_'],
204
+ preview: {
205
+ // 预览服务器配置
206
+ port: 4173,
207
+ host: '0.0.0.0',
208
+ // 预览服务器会自动处理 SPA 路由回退
209
+ strictPort: false,
210
+ cors: true,
211
+ },
212
+ build: {
213
+ outDir: 'dist',
214
+ // 根据环境变量配置是否生成 source map
215
+ sourcemap: env.DP_SOURCEMAP === 'true',
216
+ // 启用代码压缩(生产环境使用 esbuild,速度快)
217
+ minify: mode === 'production' ? 'esbuild' : false,
218
+ // 启用 CSS 压缩(生产环境)
219
+ cssMinify: mode === 'production',
220
+ // 配置资源内联阈值,设置为 0 表示不内联任何资源,所有图片都会作为独立文件打包
221
+ assetsInlineLimit: 0,
222
+ // CSS 代码分割配置,确保样式文件被正确处理
223
+ cssCodeSplit: true,
224
+ // 确保 Rollup 正确处理 Less 文件的依赖关系
225
+ rollupOptions: {
226
+ output: {
227
+ // 确保 CSS 文件被正确处理
228
+ assetFileNames: assetInfo => {
229
+ if (assetInfo.name && assetInfo.name.endsWith('.css')) {
230
+ return 'assets/css/[name]-[hash][extname]';
231
+ }
232
+ return 'assets/[name]-[hash][extname]';
233
+ },
234
+ // 压缩输出
235
+ compact: mode === 'production',
236
+ },
237
+ },
238
+ // 构建时移除 console 和 debugger(生产环境)
239
+ chunkSizeWarningLimit: 1000,
240
+ },
241
+ };
242
+ });
243
+
@@ -0,0 +1,3 @@
1
+ registry "https://registry.npmjs.org/"
2
+ "@deppon:registry" "https://registry.npmjs.org/"
3
+ network-timeout 300000