create-jnrs-vue 1.2.11

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/bin/create.mjs +221 -0
  4. package/bin/upgrade.mjs +40 -0
  5. package/jnrs-template-vue/.env.development +13 -0
  6. package/jnrs-template-vue/.env.production +4 -0
  7. package/jnrs-template-vue/.prettierrc.json +12 -0
  8. package/jnrs-template-vue/README.md +48 -0
  9. package/jnrs-template-vue/auto-imports.d.ts +17 -0
  10. package/jnrs-template-vue/components.d.ts +51 -0
  11. package/jnrs-template-vue/eslint.config.ts +40 -0
  12. package/jnrs-template-vue/index.html +13 -0
  13. package/jnrs-template-vue/package.json +55 -0
  14. package/jnrs-template-vue/public/favicon.ico +0 -0
  15. package/jnrs-template-vue/public/system/menu.json +137 -0
  16. package/jnrs-template-vue/src/App.vue +45 -0
  17. package/jnrs-template-vue/src/api/common/index.ts +28 -0
  18. package/jnrs-template-vue/src/api/demos/index.ts +155 -0
  19. package/jnrs-template-vue/src/api/request.ts +53 -0
  20. package/jnrs-template-vue/src/api/system/index.ts +107 -0
  21. package/jnrs-template-vue/src/api/user/index.ts +12 -0
  22. package/jnrs-template-vue/src/assets/fonts/.keep +0 -0
  23. package/jnrs-template-vue/src/assets/fonts/AlibabaPuHuiTi-Regular.woff2 +0 -0
  24. package/jnrs-template-vue/src/assets/fonts/AlimamaShuHeiTi-Bold.woff2 +0 -0
  25. package/jnrs-template-vue/src/assets/images/common/403.png +0 -0
  26. package/jnrs-template-vue/src/assets/images/common/404.png +0 -0
  27. package/jnrs-template-vue/src/assets/images/common/avatar.png +0 -0
  28. package/jnrs-template-vue/src/assets/images/common/jnrs-white.svg +1 -0
  29. package/jnrs-template-vue/src/assets/styles/animation.scss +0 -0
  30. package/jnrs-template-vue/src/assets/styles/common.scss +39 -0
  31. package/jnrs-template-vue/src/assets/styles/fonts.scss +27 -0
  32. package/jnrs-template-vue/src/assets/styles/index.scss +5 -0
  33. package/jnrs-template-vue/src/assets/styles/init.scss +41 -0
  34. package/jnrs-template-vue/src/assets/styles/root.scss +13 -0
  35. package/jnrs-template-vue/src/components/common/CardTable.vue +90 -0
  36. package/jnrs-template-vue/src/components/common/DictTag.vue +74 -0
  37. package/jnrs-template-vue/src/components/common/ImageView.vue +144 -0
  38. package/jnrs-template-vue/src/components/common/PdfView.vue +115 -0
  39. package/jnrs-template-vue/src/components/select/SelectManager.vue +26 -0
  40. package/jnrs-template-vue/src/directives/permissions.ts +28 -0
  41. package/jnrs-template-vue/src/layout/BlankLayout.vue +15 -0
  42. package/jnrs-template-vue/src/layout/RouterTabs /344/277/256/345/244/215/350/267/257/347/224/261/350/267/263/350/275/254/346/220/272/345/270/246/345/217/202/346/225/260/351/227/256/351/242/230.vue" +150 -0
  43. package/jnrs-template-vue/src/layout/RouterTabs.vue +142 -0
  44. package/jnrs-template-vue/src/layout/SideMenu.vue +208 -0
  45. package/jnrs-template-vue/src/layout/SideMenuItem.vue +38 -0
  46. package/jnrs-template-vue/src/layout/TopHeader.vue +184 -0
  47. package/jnrs-template-vue/src/layout/index.vue +71 -0
  48. package/jnrs-template-vue/src/locales/en.ts +14 -0
  49. package/jnrs-template-vue/src/locales/index.ts +23 -0
  50. package/jnrs-template-vue/src/locales/zhCn.ts +14 -0
  51. package/jnrs-template-vue/src/main.ts +31 -0
  52. package/jnrs-template-vue/src/router/index.ts +77 -0
  53. package/jnrs-template-vue/src/router/routes.ts +48 -0
  54. package/jnrs-template-vue/src/types/env.d.ts +12 -0
  55. package/jnrs-template-vue/src/types/index.ts +81 -0
  56. package/jnrs-template-vue/src/types/webSocket.ts +19 -0
  57. package/jnrs-template-vue/src/utils/file.ts +56 -0
  58. package/jnrs-template-vue/src/utils/packages.ts +116 -0
  59. package/jnrs-template-vue/src/utils/permissions.ts +16 -0
  60. package/jnrs-template-vue/src/views/common/403.vue +52 -0
  61. package/jnrs-template-vue/src/views/common/404.vue +52 -0
  62. package/jnrs-template-vue/src/views/demos/crud/index.vue +355 -0
  63. package/jnrs-template-vue/src/views/demos/simpleTable/index.vue +41 -0
  64. package/jnrs-template-vue/src/views/demos/unitTest/RequestPage.vue +137 -0
  65. package/jnrs-template-vue/src/views/demos/unitTest/index.vue +131 -0
  66. package/jnrs-template-vue/src/views/home/index.vue +9 -0
  67. package/jnrs-template-vue/src/views/lingshuSmart/editorPage.vue +9 -0
  68. package/jnrs-template-vue/src/views/login/index.vue +314 -0
  69. package/jnrs-template-vue/src/views/system/dict/index.vue +161 -0
  70. package/jnrs-template-vue/src/views/system/menu/index.vue +43 -0
  71. package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +108 -0
  72. package/jnrs-template-vue/src/views/system/mine/index.vue +83 -0
  73. package/jnrs-template-vue/src/views/system/mine/securitySettings.vue +105 -0
  74. package/jnrs-template-vue/src/views/system/role/editDialog.vue +94 -0
  75. package/jnrs-template-vue/src/views/system/role/index.vue +41 -0
  76. package/jnrs-template-vue/src/views/visual/index.vue +143 -0
  77. package/jnrs-template-vue/tsconfig.json +25 -0
  78. package/jnrs-template-vue/vite.config.ts +71 -0
  79. package/jnrs-template-vue/viteMockServe/fail.ts +38 -0
  80. package/jnrs-template-vue/viteMockServe/file/mock-pdf.pdf +0 -0
  81. package/jnrs-template-vue/viteMockServe/file/mock-png-0.png +0 -0
  82. package/jnrs-template-vue/viteMockServe/file/mock-png-1.png +0 -0
  83. package/jnrs-template-vue/viteMockServe/file.ts +67 -0
  84. package/jnrs-template-vue/viteMockServe/index.ts +87 -0
  85. package/jnrs-template-vue/viteMockServe/json/detailsRes.json +56 -0
  86. package/jnrs-template-vue/viteMockServe/json/dictItemRes.json +27 -0
  87. package/jnrs-template-vue/viteMockServe/json/dictRes.json +21 -0
  88. package/jnrs-template-vue/viteMockServe/json/loginRes_admin.json +157 -0
  89. package/jnrs-template-vue/viteMockServe/json/loginRes_user.json +713 -0
  90. package/jnrs-template-vue/viteMockServe/json/roleRes.json +37 -0
  91. package/jnrs-template-vue/viteMockServe/json/tableRes.json +390 -0
  92. package/jnrs-template-vue/viteMockServe/success.ts +39 -0
  93. package/package.json +41 -0
@@ -0,0 +1,83 @@
1
+ <script setup lang="ts">
2
+ import BaseInfo from './baseInfo.vue'
3
+ import SecuritySettings from './securitySettings.vue'
4
+ import { ref, computed, watch } from 'vue'
5
+ import { useRoute } from '@jnrs/vue-core/router'
6
+ import type { Component } from 'vue'
7
+
8
+ type TabName = 'BaseInfo' | 'SecuritySettings'
9
+
10
+ const componentsMap = {
11
+ BaseInfo,
12
+ SecuritySettings
13
+ } as const satisfies Record<TabName, Component>
14
+
15
+ const tabList = ref<Array<{ label: string; name: TabName }>>([
16
+ { label: '基本信息', name: 'BaseInfo' },
17
+ { label: '安全设置', name: 'SecuritySettings' }
18
+ ])
19
+
20
+ const actTabName = ref<TabName>('BaseInfo')
21
+
22
+ const actTabLabel = computed(() => {
23
+ return tabList.value.find((d) => d.name === actTabName.value)?.label
24
+ })
25
+
26
+ const route = useRoute()
27
+
28
+ watch(
29
+ () => route.query.tab,
30
+ (val) => {
31
+ if (typeof val === 'string' && val in componentsMap) {
32
+ actTabName.value = val as TabName
33
+ } else {
34
+ actTabName.value = tabList.value[0]?.name || 'BaseInfo'
35
+ }
36
+ },
37
+ { immediate: true }
38
+ )
39
+ </script>
40
+
41
+ <template>
42
+ <el-card class="card" shadow="never">
43
+ <div class="card_container">
44
+ <div class="left">
45
+ <el-tabs v-model="actTabName" tab-position="left">
46
+ <el-tab-pane
47
+ v-for="item in tabList"
48
+ :key="item.name"
49
+ :label="item.label"
50
+ :name="item.name"
51
+ />
52
+ </el-tabs>
53
+ </div>
54
+ <div class="right">
55
+ <h1>{{ actTabLabel }}</h1>
56
+ <component
57
+ class="component animation15"
58
+ style="animation-duration: 0.8s; animation-delay: 0s"
59
+ :is="componentsMap[actTabName]"
60
+ />
61
+ </div>
62
+ </div>
63
+ </el-card>
64
+ </template>
65
+
66
+ <style lang="scss" scoped>
67
+ .card {
68
+ .card_container {
69
+ display: flex;
70
+ width: 100%;
71
+ }
72
+ }
73
+ .left {
74
+ min-width: 150px;
75
+ min-height: 300px;
76
+ }
77
+ .right {
78
+ flex: 1;
79
+ h1 {
80
+ margin-bottom: 30px;
81
+ }
82
+ }
83
+ </style>
@@ -0,0 +1,105 @@
1
+ <script setup lang="ts">
2
+ import { reactive, ref } from 'vue'
3
+ import { PasswordChangeApi } from '@/api/system'
4
+ import { useAuthStore } from '@jnrs/vue-core/pinia'
5
+ import { handleRouter } from '@jnrs/vue-core/router'
6
+ import { isWeakPwd } from '@jnrs/shared/validator'
7
+ import type { FormInstance, FormItemRule } from 'element-plus'
8
+ import { ElMessage } from 'element-plus'
9
+ import { LogoutApi } from '@/api/system'
10
+
11
+ const { userInfo, clearAuth } = useAuthStore()
12
+ const loading = ref(false)
13
+ const formRef = ref<FormInstance>()
14
+ const ruleForm = ref({
15
+ originPassword: '',
16
+ password: '',
17
+ repassword: ''
18
+ })
19
+ const rules = reactive({
20
+ originPassword: [{ required: true, message: '请输入', trigger: 'change' }],
21
+ password: [
22
+ { required: true, message: '请输入', trigger: 'change' },
23
+ {
24
+ validator: isWeakPwd,
25
+ trigger: 'change'
26
+ }
27
+ ],
28
+ repassword: [
29
+ { required: true, message: '请输入', trigger: 'change' },
30
+ {
31
+ validator: (rule: FormItemRule, value: string, callback: (error?: string) => void) => {
32
+ if (value && value !== ruleForm.value.password) {
33
+ callback('两次密码不一致!')
34
+ } else {
35
+ callback()
36
+ }
37
+ },
38
+ required: false,
39
+ trigger: 'change'
40
+ }
41
+ ]
42
+ })
43
+
44
+ const submitForm = async (formEl: FormInstance | undefined) => {
45
+ if (!formEl) return
46
+ try {
47
+ const valid = await formEl.validate()
48
+ if (valid && userInfo?.id) {
49
+ loading.value = true
50
+ await PasswordChangeApi({
51
+ userId: userInfo.id,
52
+ password: ruleForm.value.password
53
+ })
54
+ ElMessage.success('密码修改成功,请重新登录系统')
55
+ await LogoutApi()
56
+ await clearAuth()
57
+ handleRouter({ name: 'Login' }, 'replace')
58
+ }
59
+ } catch {
60
+ } finally {
61
+ loading.value = false
62
+ }
63
+ }
64
+ </script>
65
+
66
+ <template>
67
+ <el-form ref="formRef" :model="ruleForm" :rules="rules" label-width="auto" v-loading="loading">
68
+ <el-form-item label="当前密码" prop="originPassword">
69
+ <el-input
70
+ v-model.trim="ruleForm.originPassword"
71
+ type="password"
72
+ autocomplete="off"
73
+ show-password
74
+ style="width: 200px"
75
+ />
76
+ </el-form-item>
77
+ <el-form-item label="新密码" prop="password">
78
+ <el-input
79
+ v-model.trim="ruleForm.password"
80
+ type="password"
81
+ autocomplete="off"
82
+ show-password
83
+ style="width: 200px"
84
+ />
85
+ </el-form-item>
86
+ <el-form-item label="确认密码" prop="repassword">
87
+ <el-input
88
+ v-model.trim="ruleForm.repassword"
89
+ type="repassword"
90
+ autocomplete="off"
91
+ show-password
92
+ style="width: 200px"
93
+ />
94
+ </el-form-item>
95
+ <el-form-item>
96
+ <el-button class="form_submit" type="primary" @click="submitForm(formRef)">保存修改</el-button>
97
+ </el-form-item>
98
+ </el-form>
99
+ </template>
100
+
101
+ <style lang="scss" scoped>
102
+ .form_submit {
103
+ margin-top: 30px;
104
+ }
105
+ </style>
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import type { FormInstance } from 'element-plus'
3
+ import { ref, nextTick } from 'vue'
4
+ defineOptions({
5
+ name: 'editDialog'
6
+ })
7
+
8
+ const loading = ref(false)
9
+ const dialogVisible = ref(false)
10
+ const ruleFormRef = ref<FormInstance>()
11
+ const ruleForm = ref({
12
+ id: '',
13
+ name: '',
14
+ enname: '',
15
+ remark: '',
16
+ usable: '1'
17
+ })
18
+ const rules = ref({
19
+ name: { required: true, message: '请输入角色名称', trigger: 'change' },
20
+ enname: { required: true, message: '请输入英文名称', trigger: 'change' },
21
+ usable: { required: true, message: '请选择是否可用', trigger: 'change' }
22
+ })
23
+
24
+ const toggleVisible = () => {
25
+ dialogVisible.value = !dialogVisible.value
26
+ nextTick(() => {
27
+ ruleFormRef.value?.resetFields()
28
+ })
29
+ }
30
+
31
+ const submitForm = () => {
32
+ if (!ruleFormRef.value) return
33
+ ruleFormRef.value.validate(async (valid) => {
34
+ if (!valid) return
35
+ })
36
+ }
37
+
38
+ defineExpose({
39
+ toggleVisible
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <el-dialog
45
+ v-model="dialogVisible"
46
+ width="600"
47
+ :append-to-body="true"
48
+ :close-on-click-modal="true"
49
+ :close-on-press-escape="true"
50
+ >
51
+ <template #header>
52
+ <h3>{{ ruleForm.id ? '修改' : '添加' }}角色</h3>
53
+ </template>
54
+ <el-form
55
+ ref="ruleFormRef"
56
+ :model="ruleForm"
57
+ :rules="rules"
58
+ label-width="auto"
59
+ v-loading="loading"
60
+ style="width: 80%; margin: 0 auto"
61
+ >
62
+ <el-form-item prop="id" v-show="false">
63
+ <el-input v-model="ruleForm.id" />
64
+ </el-form-item>
65
+ <el-form-item label="角色名称" prop="name">
66
+ <el-input v-model="ruleForm.name" maxlength="20" />
67
+ </el-form-item>
68
+ <el-form-item label="英文名称" prop="enname">
69
+ <el-input v-model="ruleForm.enname" maxlength="20" />
70
+ </el-form-item>
71
+ <el-form-item label="说明" prop="remark">
72
+ <el-input v-model="ruleForm.remark" maxlength="200" type="textarea" :rows="5" />
73
+ </el-form-item>
74
+ <el-form-item label="是否可用" prop="usable">
75
+ <el-switch
76
+ v-model="ruleForm.usable"
77
+ inline-prompt
78
+ active-text="是"
79
+ inactive-text="否"
80
+ :active-value="true"
81
+ :inactive-value="false"
82
+ width="50"
83
+ style="--el-switch-on-color: #67c23a; --el-switch-off-color: #f56c6c"
84
+ />
85
+ </el-form-item>
86
+ </el-form>
87
+ <template #footer>
88
+ <div class="dialog-footer">
89
+ <el-button @click="toggleVisible()" :loading="loading">取消</el-button>
90
+ <el-button type="primary" @click="submitForm()" :loading="loading">提交</el-button>
91
+ </div>
92
+ </template>
93
+ </el-dialog>
94
+ </template>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from 'vue'
3
+ import { RoleApi } from '@/api/system'
4
+ import { JnTable } from '@jnrs/vue-core/components'
5
+
6
+ const loading = ref(false)
7
+ const tableData = ref()
8
+
9
+ const handleRoleApi = async () => {
10
+ try {
11
+ const res = await RoleApi()
12
+ tableData.value = res
13
+ } catch {}
14
+ }
15
+
16
+ onMounted(() => {
17
+ handleRoleApi()
18
+ })
19
+ </script>
20
+
21
+ <template>
22
+ <el-card v-loading="loading">
23
+ <template #header>
24
+ <span>角色管理</span>
25
+ </template>
26
+
27
+ <JnTable :data="tableData">
28
+ <el-table-column prop="label" label="角色名称" width="200" align="center" />
29
+ <el-table-column prop="value" label="值" width="200" align="center">
30
+ <template #default="{ row }">
31
+ {{ row.value + ' [' + typeof row.value + ']' }}
32
+ </template>
33
+ </el-table-column>
34
+ <el-table-column prop="permissions" label="可用权限" header-align="center">
35
+ <template #default="{ row }">
36
+ {{ row.permissions }}
37
+ </template>
38
+ </el-table-column>
39
+ </JnTable>
40
+ </el-card>
41
+ </template>
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ import type { MsgIdMessage, TypeDataMessage } from '@/types/webSocket'
3
+ import { isMsgIdMessage, isTypeDataMessage } from '@/types/webSocket'
4
+ import { storeToRefs } from 'pinia'
5
+ import { useWebSocket } from '@jnrs/vue-core/composables'
6
+ import { useRouter } from '@jnrs/vue-core/router'
7
+ import { useSystemStore } from '@jnrs/vue-core/pinia'
8
+
9
+ const router = useRouter()
10
+ const systemStore = useSystemStore()
11
+ const { documentFullscreen } = storeToRefs(systemStore)
12
+ const { toggleFullScreen } = systemStore
13
+
14
+ const { isLoading, wsStateInfo } = useWebSocket({
15
+ url: '/ws/monitor',
16
+ connectionTimeoutDuration: 3_000, // 修改仅为测试
17
+ onMessage: (rawMessage: unknown) => {
18
+ if (isMsgIdMessage(rawMessage)) {
19
+ const msg = rawMessage as MsgIdMessage
20
+ if (msg.msgId && msg.d && typeof msg.d === 'object') {
21
+ // console.log('接收 msgId 数据:', msg)
22
+ }
23
+ } else if (isTypeDataMessage(rawMessage)) {
24
+ const msg = rawMessage as TypeDataMessage
25
+ if (msg.type && msg.data && typeof msg.data === 'object') {
26
+ // console.log('接收 type 数据:', msg)
27
+ }
28
+ }
29
+ }
30
+ })
31
+ </script>
32
+
33
+ <template>
34
+ <div
35
+ class="visual"
36
+ v-loading="isLoading"
37
+ element-loading-background="rgba(0,0,0,.5)"
38
+ element-loading-text="网络加载中..."
39
+ >
40
+ <div class="visual_head">
41
+ <div class="visual_head_left">
42
+ <div class="visual_title">
43
+ <b>数字孪生看板</b>
44
+ <span>{{ wsStateInfo }}</span>
45
+ </div>
46
+ </div>
47
+ <div class="visual_head_right">
48
+ <span class="btn" @click="toggleFullScreen">
49
+ {{ !documentFullscreen ? '启用全屏' : '退出全屏' }}
50
+ </span>
51
+ <span class="btn" @click="router.push('/')">返回首页</span>
52
+ </div>
53
+ </div>
54
+ <div class="visual_canvas">
55
+ <i>canvas</i>
56
+ </div>
57
+ </div>
58
+ </template>
59
+
60
+ <style lang="scss" scoped>
61
+ @use 'sass:math';
62
+ @function px2vw($px) {
63
+ @return math.div($px, 1920) * 100vw;
64
+ }
65
+
66
+ .visual {
67
+ position: relative;
68
+ width: 100%;
69
+ height: 100%;
70
+ font-size: px2vw(18);
71
+ overflow: hidden;
72
+ user-select: none;
73
+
74
+ .visual_head {
75
+ position: absolute;
76
+ z-index: 10;
77
+ display: flex;
78
+ justify-content: space-between;
79
+ align-items: top;
80
+ width: 100%;
81
+ padding: px2vw(10);
82
+ color: #fff;
83
+
84
+ .visual_title {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ font-size: px2vw(24);
88
+ height: px2vw(30);
89
+ background: #000;
90
+ border: px2vw(2) solid #f2f2f2;
91
+ margin: 0 0 px2vw(5) 0;
92
+ overflow: hidden;
93
+
94
+ b {
95
+ display: inline-block;
96
+ padding: 0 px2vw(5);
97
+ font-size: px2vw(24);
98
+ background: #f2f2f2;
99
+ color: #000;
100
+ }
101
+
102
+ span {
103
+ padding: 0 px2vw(8);
104
+ color: #f2f2f2;
105
+ box-sizing: border-box;
106
+ }
107
+ }
108
+
109
+ .visual_head_right {
110
+ .btn {
111
+ align-items: center;
112
+ padding: 0 px2vw(8);
113
+ color: #f2f2f2;
114
+ background: var(--jnrs-color-primary);
115
+ border-radius: px2vw(2);
116
+ margin: 0 px2vw(2);
117
+ transition: all 0.25s ease;
118
+ cursor: pointer;
119
+
120
+ &:hover {
121
+ filter: brightness(1.3);
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ .visual_canvas {
128
+ position: relative;
129
+ width: 100%;
130
+ height: 100%;
131
+
132
+ i {
133
+ position: absolute;
134
+ top: 50%;
135
+ left: 50%;
136
+ transform: translate(-50%, -50%);
137
+ font-size: px2vw(100);
138
+ color: #fff;
139
+ opacity: 0.5;
140
+ }
141
+ }
142
+ }
143
+ </style>
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "resolveJsonModule": true,
13
+ "verbatimModuleSyntax": true, // 不做任何自动重写
14
+ "isolatedModules": true, // 不依赖全局类型分析
15
+ "noEmit": true, // 根目录只做类型检查
16
+ "removeComments": true, // 移除注释
17
+ "types": ["node"],
18
+ "baseUrl": ".",
19
+ "paths": {
20
+ "@/*": ["src/*"]
21
+ }
22
+ },
23
+ "include": ["src/**/*", "vite.config.ts", "../../../packages/vue-core/src/composables/useMouseSelection.ts"],
24
+ "exclude": ["node_modules", "dist"]
25
+ }
@@ -0,0 +1,71 @@
1
+ import { fileURLToPath, URL } from 'node:url'
2
+ import { defineConfig, loadEnv } from 'vite'
3
+ import vue from '@vitejs/plugin-vue'
4
+ import AutoImport from 'unplugin-auto-import/vite'
5
+ import Components from 'unplugin-vue-components/vite'
6
+ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
7
+ import compression from 'vite-plugin-compression'
8
+ import { viteMockServe } from 'vite-plugin-mock'
9
+ import vueDevTools from 'vite-plugin-vue-devtools'
10
+
11
+ const path = (url: string) => fileURLToPath(new URL(url, import.meta.url))
12
+ const config = loadEnv('development', './')
13
+ const isMock = config.VITE_USE_MOCK === 'true'
14
+
15
+ export default defineConfig({
16
+ plugins: [
17
+ vue(),
18
+ vueDevTools(),
19
+ AutoImport({
20
+ resolvers: [ElementPlusResolver()]
21
+ }),
22
+ Components({
23
+ dirs: [], // 禁用本地组件自动导入
24
+ resolvers: [ElementPlusResolver()]
25
+ }),
26
+ compression({
27
+ verbose: false,
28
+ disable: false,
29
+ threshold: 8192,
30
+ algorithm: 'gzip',
31
+ ext: '.gz',
32
+ deleteOriginFile: false
33
+ }),
34
+ viteMockServe({
35
+ mockPath: 'viteMockServe', // mock 文件目录
36
+ watchFiles: true, // mock 文件热更新
37
+ enable: isMock, // 是否开启
38
+ logger: false // 是否在终端显示请求日志
39
+ })
40
+ ],
41
+ resolve: {
42
+ alias: {
43
+ '@': path('./src')
44
+ }
45
+ },
46
+ server: {
47
+ host: config.VITE_APP_HOST || true,
48
+ port: Number(config.VITE_APP_PORT),
49
+ open: true,
50
+ proxy: {
51
+ '/api': {
52
+ target: 'http://' + config.VITE_BASE_URL,
53
+ changeOrigin: true,
54
+ rewrite: (path: string) => path.replace(/^\/api/, '')
55
+ },
56
+ '/ws': {
57
+ target: 'ws://' + config.VITE_BASE_URL,
58
+ changeOrigin: true
59
+ }
60
+ }
61
+ },
62
+ build: {
63
+ rollupOptions: {
64
+ output: {
65
+ manualChunks: {
66
+ '@jnrs/shared': ['@jnrs/shared']
67
+ }
68
+ }
69
+ }
70
+ }
71
+ })
@@ -0,0 +1,38 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http'
2
+
3
+ const res_fail = {
4
+ code: 1,
5
+ msg: '操作失败'
6
+ }
7
+
8
+ export default [
9
+ // 404
10
+ {
11
+ url: '/mock/notFound',
12
+ method: 'get',
13
+ rawResponse: async (req: IncomingMessage, res: ServerResponse) => {
14
+ res.statusCode = 404
15
+ res.end('File not found')
16
+ return
17
+ }
18
+ },
19
+ // 权限不足
20
+ {
21
+ url: '/mock/auth/no',
22
+ method: 'post',
23
+ response: () => {
24
+ return {
25
+ code: 3,
26
+ msg: '暂无权限'
27
+ }
28
+ }
29
+ },
30
+ // 操作失败
31
+ {
32
+ url: '/mock/auth/fail',
33
+ method: 'post',
34
+ response: () => {
35
+ return res_fail
36
+ }
37
+ }
38
+ ]
@@ -0,0 +1,67 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http'
2
+
3
+ const MIME_TYPES: Record<string, string> = {
4
+ '.png': 'image/png',
5
+ '.jpg': 'image/jpeg',
6
+ '.jpeg': 'image/jpeg',
7
+ '.gif': 'image/gif',
8
+ '.svg': 'image/svg+xml',
9
+ '.webp': 'image/webp',
10
+ '.pdf': 'application/pdf',
11
+ '.txt': 'text/plain; charset=utf-8',
12
+ '.json': 'application/json',
13
+ '.xml': 'application/xml',
14
+ '.zip': 'application/zip',
15
+ '.mp4': 'video/mp4',
16
+ '.mp3': 'audio/mpeg'
17
+ }
18
+
19
+ export default [
20
+ {
21
+ url: /\/mock\/api\/files\/[\w.-]+/,
22
+ method: 'get',
23
+ rawResponse: async (req: IncomingMessage, res: ServerResponse) => {
24
+ if (!req.url) {
25
+ res.statusCode = 400
26
+ res.end('Bad Request')
27
+ return
28
+ }
29
+
30
+ const pathParts = req.url.split('/')
31
+ const fileName = decodeURIComponent(pathParts[4])
32
+
33
+ try {
34
+ const fs = await import('fs').then((m) => m.default || m)
35
+ const path = await import('path').then((m) => m.default || m)
36
+ const filePath = path.resolve(process.cwd(), `viteMockServe/file/${fileName}`)
37
+ const fileDir = path.resolve(process.cwd(), 'viteMockServe/file')
38
+
39
+ if (!filePath.startsWith(fileDir + path.sep) && filePath !== fileDir) {
40
+ res.statusCode = 403
41
+ res.end('Forbidden')
42
+ return
43
+ }
44
+
45
+ if (!fs.existsSync(filePath)) {
46
+ res.statusCode = 404
47
+ res.end('File not found')
48
+ return
49
+ }
50
+
51
+ const buffer = fs.readFileSync(filePath)
52
+ const ext = path.extname(fileName).toLowerCase()
53
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream'
54
+
55
+ res.setHeader('Content-Type', contentType)
56
+ res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`)
57
+
58
+ res.statusCode = 200
59
+ res.end(buffer)
60
+ } catch (error) {
61
+ console.error('Mock file serve error:', error)
62
+ res.statusCode = 500
63
+ res.end('Internal Server Error')
64
+ }
65
+ }
66
+ }
67
+ ]