verce-vue-test 0.0.26 → 0.0.27
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/mini2.0-main/package-lock.json +21 -0
- package/mini2.0-main/package.json +2 -0
- package/mini2.0-main/plugins/bump-version.ts +11 -4
- package/mini2.0-main/src/config/plugin.properties.test +2 -2
- package/mini2.0-main/src/main.ts +2 -1
- package/mini2.0-main/src/router/index.ts +2 -2
- package/mini2.0-main/src/stores/user.ts +1 -1
- package/mini2.0-main/vite.config.ts +1 -0
- package/package.json +1 -1
- package/src/App.vue +4 -0
- package/src/components/ElButtonPro.vue +215 -0
- package/src/components/ElUploadButton.vue +261 -0
- package/src/components/NumberRange.vue +262 -0
- package/src/router/index.ts +6 -0
- package/src/views/FundCockpitView.vue +1064 -0
- package/src/views/PlaceholderView.vue +387 -15
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@iconify/vue": "^5.0.1",
|
|
12
12
|
"@vueuse/core": "^14.3.0",
|
|
13
|
+
"adm-zip": "^0.5.17",
|
|
13
14
|
"axios": "^1.16.1",
|
|
14
15
|
"clipboard": "^2.0.11",
|
|
15
16
|
"crypto-js": "^4.2.0",
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@tsconfig/node24": "^24.0.4",
|
|
29
|
+
"@types/adm-zip": "^0.5.8",
|
|
28
30
|
"@types/node": "^24.12.2",
|
|
29
31
|
"@vitejs/plugin-vue": "^6.0.6",
|
|
30
32
|
"@vue/eslint-config-typescript": "^14.7.0",
|
|
@@ -1463,6 +1465,16 @@
|
|
|
1463
1465
|
"tslib": "^2.4.0"
|
|
1464
1466
|
}
|
|
1465
1467
|
},
|
|
1468
|
+
"node_modules/@types/adm-zip": {
|
|
1469
|
+
"version": "0.5.8",
|
|
1470
|
+
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.8.tgz",
|
|
1471
|
+
"integrity": "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==",
|
|
1472
|
+
"dev": true,
|
|
1473
|
+
"license": "MIT",
|
|
1474
|
+
"dependencies": {
|
|
1475
|
+
"@types/node": "*"
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1466
1478
|
"node_modules/@types/esrecurse": {
|
|
1467
1479
|
"version": "4.3.1",
|
|
1468
1480
|
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
|
@@ -2198,6 +2210,15 @@
|
|
|
2198
2210
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
2199
2211
|
}
|
|
2200
2212
|
},
|
|
2213
|
+
"node_modules/adm-zip": {
|
|
2214
|
+
"version": "0.5.17",
|
|
2215
|
+
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz",
|
|
2216
|
+
"integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==",
|
|
2217
|
+
"license": "MIT",
|
|
2218
|
+
"engines": {
|
|
2219
|
+
"node": ">=12.0"
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2201
2222
|
"node_modules/agent-base": {
|
|
2202
2223
|
"version": "6.0.2",
|
|
2203
2224
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@iconify/vue": "^5.0.1",
|
|
24
24
|
"@vueuse/core": "^14.3.0",
|
|
25
|
+
"adm-zip": "^0.5.17",
|
|
25
26
|
"axios": "^1.16.1",
|
|
26
27
|
"clipboard": "^2.0.11",
|
|
27
28
|
"crypto-js": "^4.2.0",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@tsconfig/node24": "^24.0.4",
|
|
41
|
+
"@types/adm-zip": "^0.5.8",
|
|
40
42
|
"@types/node": "^24.12.2",
|
|
41
43
|
"@vitejs/plugin-vue": "^6.0.6",
|
|
42
44
|
"@vue/eslint-config-typescript": "^14.7.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, cpSync } from 'node:fs'
|
|
1
|
+
import { readFileSync, writeFileSync, cpSync, rmSync } from 'node:fs'
|
|
2
2
|
import { resolve, basename } from 'node:path'
|
|
3
|
-
import { execSync } from 'node:child_process'
|
|
4
3
|
import type { Plugin } from 'vite'
|
|
4
|
+
import AdmZip from 'adm-zip'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 打包时自增版本号、复制配置文件、压缩 dist 为 zip
|
|
@@ -40,10 +40,17 @@ export function bumpVersion(mode: string): Plugin {
|
|
|
40
40
|
|
|
41
41
|
console.log(`[plugin.properties] version: ${oldCode} → ${newCode} (${versionName})`)
|
|
42
42
|
|
|
43
|
-
// 压缩 dist 为 zip
|
|
43
|
+
// 压缩 dist 为 zip(使用 adm-zip,跨平台兼容)
|
|
44
44
|
const distDir = resolve(outDir, '..')
|
|
45
45
|
const zipName = `dist_${newCode}.zip`
|
|
46
|
-
|
|
46
|
+
const zipPath = resolve(distDir, zipName)
|
|
47
|
+
const zip = new AdmZip()
|
|
48
|
+
|
|
49
|
+
zip.addLocalFolder(resolve(distDir, 'www'), 'www')
|
|
50
|
+
zip.addLocalFile(resolve(distDir, 'plugin.properties'))
|
|
51
|
+
|
|
52
|
+
rmSync(zipPath, { force: true })
|
|
53
|
+
zip.writeZip(zipPath)
|
|
47
54
|
console.log(`[zip] 已生成 ${basename(distDir)}/${zipName}`)
|
|
48
55
|
},
|
|
49
56
|
}
|
package/mini2.0-main/src/main.ts
CHANGED
|
@@ -5,8 +5,9 @@ import 'vant/lib/index.css' // Vant 基础样式,必须引入否则组件无
|
|
|
5
5
|
|
|
6
6
|
import App from './App.vue'
|
|
7
7
|
import router from './router'
|
|
8
|
+
import { isNativeApp } from '@/core/mxApi'
|
|
8
9
|
|
|
9
|
-
if (import.meta.env.VITE_APP_ENV
|
|
10
|
+
if (import.meta.env.VITE_APP_ENV === 'test' && isNativeApp()) {
|
|
10
11
|
import('vconsole').then(({ default: VConsole }) => new VConsole())
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createRouter,
|
|
1
|
+
import { createRouter, createWebHashHistory } from 'vue-router'
|
|
2
2
|
import { useUserStore } from '@/stores/user'
|
|
3
3
|
|
|
4
4
|
const router = createRouter({
|
|
5
|
-
history:
|
|
5
|
+
history: createWebHashHistory(),
|
|
6
6
|
routes: [
|
|
7
7
|
{
|
|
8
8
|
path: '/',
|
|
@@ -13,7 +13,7 @@ export const useUserStore = defineStore(
|
|
|
13
13
|
// -------------------------------- State --------------------------------
|
|
14
14
|
|
|
15
15
|
/** 登录凭证,登录成功后由后端返回,后续请求通过请求头携带 */
|
|
16
|
-
const token = ref('')
|
|
16
|
+
const token = ref('7')
|
|
17
17
|
|
|
18
18
|
// ------------------------------ Actions --------------------------------
|
|
19
19
|
|
package/package.json
CHANGED
package/src/App.vue
CHANGED
|
@@ -89,6 +89,10 @@ function toggleCollapse() {
|
|
|
89
89
|
<el-icon><TrendCharts /></el-icon>
|
|
90
90
|
<span>总行管理员首页</span>
|
|
91
91
|
</el-menu-item>
|
|
92
|
+
<el-menu-item index="/fund-cockpit">
|
|
93
|
+
<el-icon><TrendCharts /></el-icon>
|
|
94
|
+
<span>资金驾驶舱</span>
|
|
95
|
+
</el-menu-item>
|
|
92
96
|
<el-menu-item index="/notice-announcement">
|
|
93
97
|
<el-icon><Bell /></el-icon>
|
|
94
98
|
<span>通知公告</span>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, useAttrs } from 'vue'
|
|
3
|
+
import { ElMessageBox, ElMessage } from 'element-plus'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 增强版按钮组件
|
|
7
|
+
* 基于 el-button,提供权限控制、防抖、确认对话框等功能
|
|
8
|
+
*/
|
|
9
|
+
defineOptions({
|
|
10
|
+
name: 'ElButtonPro',
|
|
11
|
+
inheritAttrs: false,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const attrs = useAttrs()
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(
|
|
17
|
+
defineProps<{
|
|
18
|
+
/** 权限码,用于权限控制 */
|
|
19
|
+
permission?: string | string[]
|
|
20
|
+
/** 是否启用防抖,单位毫秒 */
|
|
21
|
+
debounce?: number
|
|
22
|
+
/** 是否启用节流,单位毫秒 */
|
|
23
|
+
throttle?: number
|
|
24
|
+
/** 点击前是否显示确认对话框 */
|
|
25
|
+
confirm?: boolean | string
|
|
26
|
+
/** 确认对话框标题 */
|
|
27
|
+
confirmTitle?: string
|
|
28
|
+
/** 确认对话框类型 */
|
|
29
|
+
confirmType?: 'warning' | 'info' | 'success' | 'error'
|
|
30
|
+
/** 是否显示取消按钮 */
|
|
31
|
+
showCancelButton?: boolean
|
|
32
|
+
/** 取消按钮文本 */
|
|
33
|
+
cancelButtonText?: string
|
|
34
|
+
/** 确认按钮文本 */
|
|
35
|
+
confirmButtonText?: string
|
|
36
|
+
/** 确认按钮类型 */
|
|
37
|
+
confirmButtonType?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
|
|
38
|
+
/** 是否显示成功提示 */
|
|
39
|
+
showSuccessMessage?: boolean | string
|
|
40
|
+
/** 成功提示文本 */
|
|
41
|
+
successMessage?: string
|
|
42
|
+
/** 是否显示失败提示 */
|
|
43
|
+
showErrorMessage?: boolean
|
|
44
|
+
/** 失败提示文本 */
|
|
45
|
+
errorMessage?: string
|
|
46
|
+
/** 是否自动处理错误 */
|
|
47
|
+
autoHandleError?: boolean
|
|
48
|
+
}>(),
|
|
49
|
+
{
|
|
50
|
+
debounce: 0,
|
|
51
|
+
throttle: 0,
|
|
52
|
+
confirm: false,
|
|
53
|
+
confirmTitle: '提示',
|
|
54
|
+
confirmType: 'warning',
|
|
55
|
+
showCancelButton: true,
|
|
56
|
+
cancelButtonText: '取消',
|
|
57
|
+
confirmButtonText: '确定',
|
|
58
|
+
confirmButtonType: 'primary',
|
|
59
|
+
showSuccessMessage: false,
|
|
60
|
+
successMessage: '操作成功',
|
|
61
|
+
showErrorMessage: true,
|
|
62
|
+
errorMessage: '操作失败',
|
|
63
|
+
autoHandleError: true,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const emit = defineEmits<{
|
|
68
|
+
/** 点击事件 */
|
|
69
|
+
'click': [event: MouseEvent]
|
|
70
|
+
/** 确认后点击事件 */
|
|
71
|
+
'confirm': [event: MouseEvent]
|
|
72
|
+
/** 成功事件 */
|
|
73
|
+
'success': [data?: any]
|
|
74
|
+
/** 失败事件 */
|
|
75
|
+
'error': [error: Error]
|
|
76
|
+
}>()
|
|
77
|
+
|
|
78
|
+
/** 内部加载状态 */
|
|
79
|
+
const internalLoading = ref(false)
|
|
80
|
+
|
|
81
|
+
/** 从 attrs 中提取 disabled */
|
|
82
|
+
const disabled = computed(() => attrs.disabled as boolean | undefined)
|
|
83
|
+
|
|
84
|
+
/** 从 attrs 中提取外部 loading */
|
|
85
|
+
const externalLoading = computed(() => attrs.loading as boolean | undefined)
|
|
86
|
+
|
|
87
|
+
/** 最终的加载状态(合并外部 loading 和内部 loading) */
|
|
88
|
+
const finalLoading = computed(() => externalLoading.value || internalLoading.value)
|
|
89
|
+
|
|
90
|
+
/** 过滤后的 attrs(移除 loading 和 disabled) */
|
|
91
|
+
const filteredAttrs = computed(() => {
|
|
92
|
+
const { loading, disabled: _, ...rest } = attrs
|
|
93
|
+
return rest
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
/** 防抖/节流定时器 */
|
|
97
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
98
|
+
let lastTime = 0
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 处理点击事件
|
|
102
|
+
*/
|
|
103
|
+
const handleClick = async (event: MouseEvent) => {
|
|
104
|
+
// 如果按钮被禁用或正在加载,不处理点击
|
|
105
|
+
if (disabled.value || finalLoading.value) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 防抖处理
|
|
110
|
+
if (props.debounce > 0) {
|
|
111
|
+
if (timer) {
|
|
112
|
+
clearTimeout(timer)
|
|
113
|
+
}
|
|
114
|
+
timer = setTimeout(() => {
|
|
115
|
+
handleConfirmClick(event)
|
|
116
|
+
}, props.debounce)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 节流处理
|
|
121
|
+
if (props.throttle > 0) {
|
|
122
|
+
const now = Date.now()
|
|
123
|
+
if (now - lastTime < props.throttle) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
lastTime = now
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await handleConfirmClick(event)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 处理确认后的点击事件
|
|
134
|
+
*/
|
|
135
|
+
const handleConfirmClick = async (event: MouseEvent) => {
|
|
136
|
+
// 如果需要确认对话框
|
|
137
|
+
if (props.confirm) {
|
|
138
|
+
try {
|
|
139
|
+
const confirmText = typeof props.confirm === 'string' ? props.confirm : '确定要执行此操作吗?'
|
|
140
|
+
await ElMessageBox.confirm(confirmText, props.confirmTitle, {
|
|
141
|
+
confirmButtonText: props.confirmButtonText,
|
|
142
|
+
cancelButtonText: props.cancelButtonText,
|
|
143
|
+
type: props.confirmType,
|
|
144
|
+
showCancelButton: props.showCancelButton,
|
|
145
|
+
confirmButtonClass: `el-button--${props.confirmButtonType}`,
|
|
146
|
+
})
|
|
147
|
+
} catch {
|
|
148
|
+
// 用户取消操作
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 触发点击事件
|
|
154
|
+
emit('click', event)
|
|
155
|
+
emit('confirm', event)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 执行异步操作
|
|
160
|
+
*/
|
|
161
|
+
const execute = async <T = any>(fn: () => Promise<T>): Promise<T | undefined> => {
|
|
162
|
+
if (finalLoading.value) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
internalLoading.value = true
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = await fn()
|
|
170
|
+
|
|
171
|
+
// 显示成功提示
|
|
172
|
+
if (props.showSuccessMessage) {
|
|
173
|
+
const message = typeof props.showSuccessMessage === 'string' ? props.showSuccessMessage : props.successMessage
|
|
174
|
+
ElMessage.success(message)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
emit('success', result)
|
|
178
|
+
return result
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
181
|
+
|
|
182
|
+
// 显示失败提示
|
|
183
|
+
if (props.showErrorMessage) {
|
|
184
|
+
const message = props.errorMessage || err.message
|
|
185
|
+
ElMessage.error(message)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
emit('error', err)
|
|
189
|
+
|
|
190
|
+
if (!props.autoHandleError) {
|
|
191
|
+
throw err
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
internalLoading.value = false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 暴露方法给父组件
|
|
200
|
+
*/
|
|
201
|
+
defineExpose({
|
|
202
|
+
execute,
|
|
203
|
+
loading: finalLoading,
|
|
204
|
+
})
|
|
205
|
+
</script>
|
|
206
|
+
|
|
207
|
+
<template>
|
|
208
|
+
<el-button
|
|
209
|
+
:loading="finalLoading"
|
|
210
|
+
v-bind="filteredAttrs"
|
|
211
|
+
@click="handleClick"
|
|
212
|
+
>
|
|
213
|
+
<slot />
|
|
214
|
+
</el-button>
|
|
215
|
+
</template>
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 基于 Element Plus 的上传按钮组件
|
|
7
|
+
* 使用 el-button 作为按钮样式,封装 el-upload 功能
|
|
8
|
+
*/
|
|
9
|
+
defineOptions({
|
|
10
|
+
name: 'ElUploadButton',
|
|
11
|
+
inheritAttrs: false,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
/** 按钮文本 */
|
|
17
|
+
text?: string
|
|
18
|
+
/** 按钮类型 */
|
|
19
|
+
type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
|
|
20
|
+
/** 按钮大小 */
|
|
21
|
+
size?: 'large' | 'default' | 'small'
|
|
22
|
+
/** 是否禁用 */
|
|
23
|
+
disabled?: boolean
|
|
24
|
+
/** 是否显示为加载状态 */
|
|
25
|
+
loading?: boolean
|
|
26
|
+
/** 是否为朴素按钮 */
|
|
27
|
+
plain?: boolean
|
|
28
|
+
/** 是否为圆角按钮 */
|
|
29
|
+
round?: boolean
|
|
30
|
+
/** 是否为圆形按钮 */
|
|
31
|
+
circle?: boolean
|
|
32
|
+
/** 按钮图标 */
|
|
33
|
+
icon?: any
|
|
34
|
+
/** 上传的地址 */
|
|
35
|
+
action?: string
|
|
36
|
+
/** 上传的文件字段名 */
|
|
37
|
+
name?: string
|
|
38
|
+
/** 上传时附带的额外参数 */
|
|
39
|
+
data?: Record<string, any>
|
|
40
|
+
/** 上传请求的 headers */
|
|
41
|
+
headers?: Record<string, any>
|
|
42
|
+
/** 是否支持多选文件 */
|
|
43
|
+
multiple?: boolean
|
|
44
|
+
/** 是否禁用拖拽上传 */
|
|
45
|
+
drag?: boolean
|
|
46
|
+
/** 接受上传的文件类型 */
|
|
47
|
+
accept?: string
|
|
48
|
+
/** 文件大小限制,单位为字节 */
|
|
49
|
+
maxSize?: number
|
|
50
|
+
/** 最大上传文件数 */
|
|
51
|
+
limit?: number
|
|
52
|
+
/** 是否自动上传 */
|
|
53
|
+
autoUpload?: boolean
|
|
54
|
+
/** 是否显示文件列表 */
|
|
55
|
+
showFileList?: boolean
|
|
56
|
+
/** 是否启用上传进度条 */
|
|
57
|
+
withCredentials?: boolean
|
|
58
|
+
/** 上传之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传 */
|
|
59
|
+
beforeUpload?: (file: UploadRawFile) => boolean | Promise<boolean>
|
|
60
|
+
/** 文件上传成功时的钩子 */
|
|
61
|
+
onSuccess?: (response: any, file: UploadUserFile, fileList: UploadUserFile[]) => void
|
|
62
|
+
/** 文件上传失败时的钩子 */
|
|
63
|
+
onError?: (error: Error, file: UploadUserFile, fileList: UploadUserFile[]) => void
|
|
64
|
+
/** 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 */
|
|
65
|
+
onChange?: (file: UploadUserFile, fileList: UploadUserFile[]) => void
|
|
66
|
+
/** 文件列表移除文件时的钩子 */
|
|
67
|
+
onRemove?: (file: UploadUserFile, fileList: UploadUserFile[]) => void
|
|
68
|
+
/** 文件超出限制时的钩子 */
|
|
69
|
+
onExceed?: (files: File[], fileList: UploadUserFile[]) => void
|
|
70
|
+
/** 文件上传进度时的钩子 */
|
|
71
|
+
onProgress?: (event: any, file: UploadUserFile, fileList: UploadUserFile[]) => void
|
|
72
|
+
}>(),
|
|
73
|
+
{
|
|
74
|
+
text: '上传文件',
|
|
75
|
+
type: 'primary',
|
|
76
|
+
size: 'default',
|
|
77
|
+
disabled: false,
|
|
78
|
+
loading: false,
|
|
79
|
+
plain: false,
|
|
80
|
+
round: false,
|
|
81
|
+
circle: false,
|
|
82
|
+
name: 'file',
|
|
83
|
+
multiple: false,
|
|
84
|
+
drag: false,
|
|
85
|
+
autoUpload: true,
|
|
86
|
+
showFileList: false,
|
|
87
|
+
withCredentials: false,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
const emit = defineEmits<{
|
|
92
|
+
/** 上传成功事件 */
|
|
93
|
+
'update:modelValue': [fileList: UploadUserFile[]]
|
|
94
|
+
/** 文件状态改变事件 */
|
|
95
|
+
'change': [file: UploadUserFile, fileList: UploadUserFile[]]
|
|
96
|
+
/** 上传成功事件 */
|
|
97
|
+
'success': [response: any, file: UploadUserFile, fileList: UploadUserFile[]]
|
|
98
|
+
/** 上传失败事件 */
|
|
99
|
+
'error': [error: Error, file: UploadUserFile, fileList: UploadUserFile[]]
|
|
100
|
+
/** 文件移除事件 */
|
|
101
|
+
'remove': [file: UploadUserFile, fileList: UploadUserFile[]]
|
|
102
|
+
/** 文件超出限制事件 */
|
|
103
|
+
'exceed': [files: File[], fileList: UploadUserFile[]]
|
|
104
|
+
/** 上传进度事件 */
|
|
105
|
+
'progress': [event: any, file: UploadUserFile, fileList: UploadUserFile[]]
|
|
106
|
+
}>()
|
|
107
|
+
|
|
108
|
+
/** 上传组件实例引用 */
|
|
109
|
+
const uploadRef = ref<UploadInstance>()
|
|
110
|
+
|
|
111
|
+
/** 文件列表 */
|
|
112
|
+
const fileList = ref<UploadUserFile[]>([])
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 上传之前的钩子
|
|
116
|
+
* 检查文件大小是否超过限制
|
|
117
|
+
*/
|
|
118
|
+
const handleBeforeUpload: UploadProps['beforeUpload'] = (file) => {
|
|
119
|
+
if (props.maxSize && file.size > props.maxSize) {
|
|
120
|
+
const maxSizeMB = (props.maxSize / 1024 / 1024).toFixed(2)
|
|
121
|
+
console.error(`文件大小不能超过 ${maxSizeMB}MB`)
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
return props.beforeUpload?.(file) ?? true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 文件上传成功时的钩子
|
|
129
|
+
*/
|
|
130
|
+
const handleSuccess: UploadProps['onSuccess'] = (response, file, uploadFileList) => {
|
|
131
|
+
props.onSuccess?.(response, file, uploadFileList)
|
|
132
|
+
emit('success', response, file, uploadFileList)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 文件上传失败时的钩子
|
|
137
|
+
*/
|
|
138
|
+
const handleError: UploadProps['onError'] = (error, file, uploadFileList) => {
|
|
139
|
+
props.onError?.(error, file, uploadFileList)
|
|
140
|
+
emit('error', error, file, uploadFileList)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 文件状态改变时的钩子
|
|
145
|
+
*/
|
|
146
|
+
const handleChange: UploadProps['onChange'] = (file, uploadFileList) => {
|
|
147
|
+
fileList.value = uploadFileList
|
|
148
|
+
props.onChange?.(file, uploadFileList)
|
|
149
|
+
emit('change', file, uploadFileList)
|
|
150
|
+
emit('update:modelValue', uploadFileList)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 文件列表移除文件时的钩子
|
|
155
|
+
*/
|
|
156
|
+
const handleRemove: UploadProps['onRemove'] = (file, uploadFileList) => {
|
|
157
|
+
fileList.value = uploadFileList
|
|
158
|
+
props.onRemove?.(file, uploadFileList)
|
|
159
|
+
emit('remove', file, uploadFileList)
|
|
160
|
+
emit('update:modelValue', uploadFileList)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 文件超出限制时的钩子
|
|
165
|
+
*/
|
|
166
|
+
const handleExceed: UploadProps['onExceed'] = (files, uploadFileList) => {
|
|
167
|
+
props.onExceed?.(files, uploadFileList)
|
|
168
|
+
emit('exceed', files, uploadFileList)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 文件上传进度时的钩子
|
|
173
|
+
*/
|
|
174
|
+
const handleProgress: UploadProps['onProgress'] = (event, file, uploadFileList) => {
|
|
175
|
+
props.onProgress?.(event, file, uploadFileList)
|
|
176
|
+
emit('progress', event, file, uploadFileList)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 手动上传文件
|
|
181
|
+
*/
|
|
182
|
+
const submit = () => {
|
|
183
|
+
uploadRef.value?.submit()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 清空文件列表
|
|
188
|
+
*/
|
|
189
|
+
const clearFiles = () => {
|
|
190
|
+
uploadRef.value?.clearFiles()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 取消上传请求
|
|
195
|
+
*/
|
|
196
|
+
const abort = (file?: UploadUserFile) => {
|
|
197
|
+
if (file) {
|
|
198
|
+
uploadRef.value?.abort(file as any)
|
|
199
|
+
} else {
|
|
200
|
+
uploadRef.value?.abort()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 暴露方法给父组件
|
|
206
|
+
*/
|
|
207
|
+
defineExpose({
|
|
208
|
+
submit,
|
|
209
|
+
clearFiles,
|
|
210
|
+
abort,
|
|
211
|
+
uploadRef,
|
|
212
|
+
})
|
|
213
|
+
</script>
|
|
214
|
+
|
|
215
|
+
<template>
|
|
216
|
+
<el-upload
|
|
217
|
+
ref="uploadRef"
|
|
218
|
+
:action="action"
|
|
219
|
+
:name="name"
|
|
220
|
+
:data="data"
|
|
221
|
+
:headers="headers"
|
|
222
|
+
:multiple="multiple"
|
|
223
|
+
:drag="drag"
|
|
224
|
+
:accept="accept"
|
|
225
|
+
:limit="limit"
|
|
226
|
+
:auto-upload="autoUpload"
|
|
227
|
+
:show-file-list="showFileList"
|
|
228
|
+
:with-credentials="withCredentials"
|
|
229
|
+
:before-upload="handleBeforeUpload"
|
|
230
|
+
:on-success="handleSuccess"
|
|
231
|
+
:on-error="handleError"
|
|
232
|
+
:on-change="handleChange"
|
|
233
|
+
:on-remove="handleRemove"
|
|
234
|
+
:on-exceed="handleExceed"
|
|
235
|
+
:on-progress="handleProgress"
|
|
236
|
+
:file-list="fileList"
|
|
237
|
+
v-bind="$attrs"
|
|
238
|
+
>
|
|
239
|
+
<el-button
|
|
240
|
+
:type="type"
|
|
241
|
+
:size="size"
|
|
242
|
+
:disabled="disabled"
|
|
243
|
+
:loading="loading"
|
|
244
|
+
:plain="plain"
|
|
245
|
+
:round="round"
|
|
246
|
+
:circle="circle"
|
|
247
|
+
:icon="icon"
|
|
248
|
+
>
|
|
249
|
+
<slot v-if="$slots.default" />
|
|
250
|
+
<template v-else>{{ text }}</template>
|
|
251
|
+
</el-button>
|
|
252
|
+
|
|
253
|
+
<template v-if="$slots.tip" #tip>
|
|
254
|
+
<slot name="tip" />
|
|
255
|
+
</template>
|
|
256
|
+
|
|
257
|
+
<template v-if="$slots.file" #file="{ file }">
|
|
258
|
+
<slot name="file" :file="file" />
|
|
259
|
+
</template>
|
|
260
|
+
</el-upload>
|
|
261
|
+
</template>
|