create-jnrs-template-vue 1.1.14 → 1.1.15
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/bin/create.mjs +55 -32
- package/jnrs-template-vue/auto-imports.d.ts +2 -0
- package/jnrs-template-vue/components.d.ts +1 -1
- package/jnrs-template-vue/package.json +4 -4
- package/jnrs-template-vue/src/api/common/index.ts +7 -3
- package/jnrs-template-vue/src/api/demos/index.ts +40 -25
- package/jnrs-template-vue/src/api/system/index.ts +3 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconArchive.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconAudio.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconCode.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconExcel.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconFile.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconFlash.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconGif.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconImage.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconMac.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconOfd.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconPdf.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconPpt.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconText.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconUnknown.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconVideo.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconWindows.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconWord.png +0 -0
- package/jnrs-template-vue/src/assets/images/fileIcon/iconWps.png +0 -0
- package/jnrs-template-vue/src/components/base/ImageView.vue +117 -40
- package/jnrs-template-vue/src/components/base/JnFileUpload.vue +433 -0
- package/jnrs-template-vue/src/components/base/PdfView.vue +106 -0
- package/jnrs-template-vue/src/components/common/JnDatetime.vue +37 -0
- package/jnrs-template-vue/src/components/common/JnDictTag.vue +70 -0
- package/jnrs-template-vue/src/components/common/JnEdit.vue +68 -0
- package/jnrs-template-vue/src/layout/SideMenu.vue +1 -0
- package/jnrs-template-vue/src/layout/TopHeader.vue +17 -5
- package/jnrs-template-vue/src/types/index.ts +32 -8
- package/jnrs-template-vue/src/utils/packages.ts +12 -7
- package/jnrs-template-vue/src/views/demos/crud/index.vue +180 -24
- package/jnrs-template-vue/src/views/demos/unitTest/RequestPage.vue +12 -16
- package/jnrs-template-vue/src/views/login/index.vue +13 -18
- package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +3 -8
- package/jnrs-template-vue/tsconfig.json +4 -1
- package/jnrs-template-vue/vite.config.ts +1 -1
- package/jnrs-template-vue/viteMockServe/file.ts +68 -0
- package/jnrs-template-vue/viteMockServe/fileSrc/mock-pdf.pdf +0 -0
- package/jnrs-template-vue/viteMockServe/fileSrc/mock-png-0.png +0 -0
- package/jnrs-template-vue/viteMockServe/fileSrc/mock-png-1.png +0 -0
- package/jnrs-template-vue/viteMockServe/index.ts +10 -8
- package/jnrs-template-vue/viteMockServe/json/dictRes.json +21 -0
- package/jnrs-template-vue/viteMockServe/json/loginRes_admin.json +157 -0
- package/jnrs-template-vue/viteMockServe/{loginRes_user.json → json/loginRes_user.json} +1 -1
- package/jnrs-template-vue/viteMockServe/{tableRes.json → json/tableRes.json} +143 -70
- package/jnrs-template-vue/viteMockServe/success.ts +8 -0
- package/package.json +1 -1
- package/jnrs-template-vue/viteMockServe/dictRes.json +0 -141
- package/jnrs-template-vue/viteMockServe/loginRes_admin.json +0 -713
- /package/jnrs-template-vue/viteMockServe/{detailsRes.json → json/detailsRes.json} +0 -0
- /package/jnrs-template-vue/viteMockServe/{dictItemRes.json → json/dictItemRes.json} +0 -0
- /package/jnrs-template-vue/viteMockServe/{roleRes.json → json/roleRes.json} +0 -0
package/bin/create.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from 'url'
|
|
2
2
|
import { dirname, join, relative } from 'path'
|
|
3
3
|
import { promises as fs, existsSync } from 'fs'
|
|
4
|
-
import { execSync
|
|
4
|
+
import { execSync } from 'child_process'
|
|
5
5
|
import minimist from 'minimist'
|
|
6
6
|
import prompts from 'prompts'
|
|
7
7
|
|
|
@@ -9,18 +9,21 @@ const __filename = fileURLToPath(import.meta.url)
|
|
|
9
9
|
const __dirname = dirname(__filename)
|
|
10
10
|
|
|
11
11
|
// 检测系统中可用的包管理器
|
|
12
|
-
function
|
|
12
|
+
function detectAvailablePackageManagers() {
|
|
13
|
+
const available = []
|
|
13
14
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
execSync('pnpm --version', { stdio: 'ignore' })
|
|
16
|
+
available.push('pnpm')
|
|
16
17
|
} catch {}
|
|
17
|
-
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
execSync('yarn --version', { stdio: 'ignore' })
|
|
20
|
+
available.push('yarn')
|
|
21
21
|
} catch {}
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
try {
|
|
23
|
+
execSync('npm --version', { stdio: 'ignore' })
|
|
24
|
+
available.push('npm')
|
|
25
|
+
} catch {}
|
|
26
|
+
return available.length ? available : ['npm']
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
function getInstallCommand(packageManager) {
|
|
@@ -35,13 +38,14 @@ function getInstallCommand(packageManager) {
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
function getRunCommand(packageManager, script) {
|
|
41
|
+
if (!packageManager || packageManager === 'skip') return `npm run ${script}` // fallback for skip
|
|
38
42
|
switch (packageManager) {
|
|
39
43
|
case 'pnpm':
|
|
40
|
-
return
|
|
41
|
-
case 'npm':
|
|
42
|
-
return `${packageManager} run ${script}`
|
|
44
|
+
return `pnpm ${script}`
|
|
43
45
|
case 'yarn':
|
|
44
46
|
return `yarn ${script}`
|
|
47
|
+
case 'npm':
|
|
48
|
+
return `npm run ${script}`
|
|
45
49
|
default:
|
|
46
50
|
return `npm run ${script}`
|
|
47
51
|
}
|
|
@@ -64,7 +68,6 @@ async function main() {
|
|
|
64
68
|
const argv = minimist(process.argv.slice(2), { string: ['_'] })
|
|
65
69
|
let targetDir = argv._[0]
|
|
66
70
|
|
|
67
|
-
// 交互式输入项目名
|
|
68
71
|
if (!targetDir) {
|
|
69
72
|
const { name } = await prompts({
|
|
70
73
|
type: 'text',
|
|
@@ -83,14 +86,12 @@ async function main() {
|
|
|
83
86
|
if (!name) process.exit(1)
|
|
84
87
|
targetDir = name
|
|
85
88
|
} else {
|
|
86
|
-
// 非交互模式:校验包名和目录
|
|
87
89
|
if (!isValidPackageName(targetDir)) {
|
|
88
90
|
console.error('❌ 无效的项目名称:', targetDir)
|
|
89
91
|
console.error(' 包名称必须有效(小写、无空格等)')
|
|
90
92
|
process.exit(1)
|
|
91
93
|
}
|
|
92
94
|
if (existsSync(join(process.cwd(), targetDir))) {
|
|
93
|
-
// ✅ 同步检查
|
|
94
95
|
console.error(`❌ 目录 "${targetDir}" 已存在!`)
|
|
95
96
|
process.exit(1)
|
|
96
97
|
}
|
|
@@ -108,23 +109,39 @@ async function main() {
|
|
|
108
109
|
pkg.name = toValidPackageName(targetDir)
|
|
109
110
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
112
|
+
// 检测可用包管理器
|
|
113
|
+
const availablePMs = detectAvailablePackageManagers()
|
|
114
|
+
|
|
115
|
+
// 构建选择项:可用包管理器 + 跳过选项
|
|
116
|
+
const pmChoices = [...availablePMs.map((pm) => ({ title: pm, value: pm })), { title: '跳过安装依赖', value: 'skip' }]
|
|
117
|
+
|
|
118
|
+
const { selected } = await prompts({
|
|
119
|
+
type: 'select',
|
|
120
|
+
name: 'selected',
|
|
121
|
+
message: '请选择包管理器来安装依赖:',
|
|
122
|
+
choices: pmChoices,
|
|
123
|
+
initial: 0
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (!selected) process.exit(1)
|
|
127
|
+
|
|
128
|
+
let packageManager = 'npm' // 默认用于生成 dev 命令
|
|
129
|
+
if (selected !== 'skip') {
|
|
130
|
+
packageManager = selected
|
|
131
|
+
console.log(`\n📦 使用 ${packageManager} 安装依赖项中...\n`)
|
|
132
|
+
const [cmd, ...args] = getInstallCommand(packageManager)
|
|
133
|
+
try {
|
|
134
|
+
execSync(`${cmd} ${args.join(' ')}`, {
|
|
135
|
+
cwd: root,
|
|
136
|
+
stdio: 'inherit',
|
|
137
|
+
env: { ...process.env, FORCE_COLOR: '1' }
|
|
138
|
+
})
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error(
|
|
141
|
+
`\n⚠️ 安装依赖失败。请手动运行:\n cd ${relative(process.cwd(), root)} && ${cmd} ${args.join(' ')}`
|
|
142
|
+
)
|
|
143
|
+
process.exit(1)
|
|
144
|
+
}
|
|
128
145
|
}
|
|
129
146
|
|
|
130
147
|
// 成功提示
|
|
@@ -133,6 +150,12 @@ async function main() {
|
|
|
133
150
|
|
|
134
151
|
console.log(`\n✅ 项目创建成功! 👌`)
|
|
135
152
|
console.log(`\n👉 cd ${relativePath}`)
|
|
153
|
+
if (selected === 'skip') {
|
|
154
|
+
const fallbackPM = availablePMs[0] || 'npm'
|
|
155
|
+
const [cmd, ...args] = getInstallCommand(fallbackPM)
|
|
156
|
+
console.log(` #👉 手动安装依赖:`)
|
|
157
|
+
console.log(` ${cmd} ${args.join(' ')}`)
|
|
158
|
+
}
|
|
136
159
|
console.log(` ${devCmd}\n`)
|
|
137
160
|
}
|
|
138
161
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// biome-ignore lint: disable
|
|
7
7
|
export {}
|
|
8
8
|
declare global {
|
|
9
|
+
const ElButton: typeof import('element-plus/es').ElButton
|
|
9
10
|
const ElMessage: typeof import('element-plus/es').ElMessage
|
|
10
11
|
const ElMessageBox: typeof import('element-plus/es').ElMessageBox
|
|
12
|
+
const ElSwitch: typeof import('element-plus/es').ElSwitch
|
|
11
13
|
}
|
|
@@ -20,6 +20,7 @@ declare module 'vue' {
|
|
|
20
20
|
ElCascader: typeof import('element-plus/es')['ElCascader']
|
|
21
21
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
|
22
22
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
|
23
|
+
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
|
23
24
|
ElDatePickerPanel: typeof import('element-plus/es')['ElDatePickerPanel']
|
|
24
25
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
|
25
26
|
ElForm: typeof import('element-plus/es')['ElForm']
|
|
@@ -34,7 +35,6 @@ declare module 'vue' {
|
|
|
34
35
|
ElOption: typeof import('element-plus/es')['ElOption']
|
|
35
36
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
|
36
37
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
|
37
|
-
ElProgress: typeof import('element-plus/es')['ElProgress']
|
|
38
38
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
39
39
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
|
40
40
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jnrs-template-vue",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "JNRS 信息化管理系统模板",
|
|
5
5
|
"author": "Talia-Tan",
|
|
6
6
|
"private": true,
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@element-plus/icons-vue": "^2.3.2",
|
|
22
|
-
"@jnrs/core": "1.1.
|
|
23
|
-
"@jnrs/shared": "1.1.
|
|
24
|
-
"@jnrs/vue-core": "1.1.
|
|
22
|
+
"@jnrs/core": "1.1.7",
|
|
23
|
+
"@jnrs/shared": "1.1.7",
|
|
24
|
+
"@jnrs/vue-core": "1.1.7",
|
|
25
25
|
"@vueuse/core": "^14.1.0",
|
|
26
26
|
"element-plus": "^2.11.9",
|
|
27
27
|
"pinia": "^3.0.4",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { request } from '@jnrs/vue-core'
|
|
2
|
-
// import { useFetch } from '@vueuse/core'
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
4
|
+
* axios 方式下载文件
|
|
6
5
|
* @param uniqueFileName 文件唯一名称(列表中一般是在 item.attachmentDocument/imageDocument.attachments[].uniqueFileName)
|
|
7
6
|
* @returns Blob
|
|
8
7
|
*/
|
|
@@ -10,10 +9,15 @@ export const FileApi = (uniqueFileName: string): Promise<Blob> => {
|
|
|
10
9
|
return request({
|
|
11
10
|
url: '/api/files/' + uniqueFileName,
|
|
12
11
|
method: 'get',
|
|
13
|
-
responseType: 'blob'
|
|
12
|
+
responseType: 'blob',
|
|
13
|
+
showErrorMsg: false
|
|
14
14
|
})
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Fetch 方式下载文件
|
|
19
|
+
*/
|
|
20
|
+
// import { useFetch } from '@vueuse/core'
|
|
17
21
|
// export const FileApi = (uniqueFileName: string) => {
|
|
18
22
|
// return useFetch(`/api/files/${uniqueFileName}`, {
|
|
19
23
|
// method: 'get',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { Document } from '@/types'
|
|
1
2
|
import { request } from '@jnrs/vue-core'
|
|
2
|
-
import type { ApiResponse } from '@/types'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* 404 错误
|
|
@@ -23,37 +23,21 @@ export const NoAuth = (showErrorMsg: boolean) => {
|
|
|
23
23
|
})
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface DetailsParams {
|
|
27
|
+
attachmentDocument: Document
|
|
28
|
+
imageDocument: Document
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
32
|
* 数据详情
|
|
28
33
|
*/
|
|
29
|
-
export const DetailsApi = (): Promise<
|
|
34
|
+
export const DetailsApi = (): Promise<DetailsParams> => {
|
|
30
35
|
return request({
|
|
31
36
|
url: '/details',
|
|
32
37
|
method: 'get'
|
|
33
38
|
})
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
/**
|
|
37
|
-
* 附件(文件)信息
|
|
38
|
-
*/
|
|
39
|
-
export interface Attachment {
|
|
40
|
-
id: number
|
|
41
|
-
documentId: number
|
|
42
|
-
fileName: string
|
|
43
|
-
uniqueFileName: string
|
|
44
|
-
fileType: string // MIME 类型
|
|
45
|
-
fileSize: number // 单位:字节(Bytes)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 文档容器(用于图片或附件)
|
|
50
|
-
*/
|
|
51
|
-
export interface Document {
|
|
52
|
-
id: number
|
|
53
|
-
description: string | null
|
|
54
|
-
attachments: Attachment[]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
41
|
/**
|
|
58
42
|
* 项目项
|
|
59
43
|
*/
|
|
@@ -64,9 +48,9 @@ export interface ProjectItem {
|
|
|
64
48
|
code: string
|
|
65
49
|
name: string
|
|
66
50
|
description: string
|
|
67
|
-
|
|
51
|
+
projectType?: '敏捷型' | '瀑布型' | '混合型'
|
|
68
52
|
manager: string
|
|
69
|
-
budget:
|
|
53
|
+
budget: number
|
|
70
54
|
plannedStartDate: string
|
|
71
55
|
plannedFinishDate: string
|
|
72
56
|
progress: string
|
|
@@ -75,6 +59,37 @@ export interface ProjectItem {
|
|
|
75
59
|
attachmentDocument?: Document // 普通文件类附件
|
|
76
60
|
}
|
|
77
61
|
|
|
62
|
+
/**
|
|
63
|
+
* 新增项目项
|
|
64
|
+
*/
|
|
65
|
+
type AddProjectOmitKeys =
|
|
66
|
+
| 'id'
|
|
67
|
+
| 'programCode'
|
|
68
|
+
| 'code'
|
|
69
|
+
| 'manager'
|
|
70
|
+
| 'plannedFinishDate'
|
|
71
|
+
| 'progress'
|
|
72
|
+
| 'status'
|
|
73
|
+
| 'imageDocument'
|
|
74
|
+
| 'attachmentDocument'
|
|
75
|
+
|
|
76
|
+
export type AddProjectItem = Omit<ProjectItem, AddProjectOmitKeys> & {
|
|
77
|
+
id?: string
|
|
78
|
+
newAttachmentFile: []
|
|
79
|
+
newImageFiles: []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 表单新增
|
|
84
|
+
*/
|
|
85
|
+
export const DemosFormApi = (data: FormData) => {
|
|
86
|
+
return request({
|
|
87
|
+
url: '/mock/demos/save',
|
|
88
|
+
method: 'post',
|
|
89
|
+
data
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
/**
|
|
79
94
|
* 数据详情
|
|
80
95
|
*/
|
|
@@ -16,10 +16,12 @@ interface LoginParams {
|
|
|
16
16
|
account: string
|
|
17
17
|
password: string
|
|
18
18
|
}
|
|
19
|
+
|
|
19
20
|
export interface LoginResult extends User {
|
|
20
21
|
token: string
|
|
21
22
|
dict: Dict
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
export const LoginApi = (data: LoginParams): Promise<LoginResult> => {
|
|
24
26
|
return request({
|
|
25
27
|
url: '/api/auth/login',
|
|
@@ -50,6 +52,7 @@ interface PasswordChange {
|
|
|
50
52
|
userId: number
|
|
51
53
|
password: string
|
|
52
54
|
}
|
|
55
|
+
|
|
53
56
|
export const PasswordChangeApi = (data: PasswordChange) => {
|
|
54
57
|
return request({
|
|
55
58
|
url: '/api/auth/change-password',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,58 +1,135 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import {
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FileItem } from '@jnrs/shared'
|
|
3
|
+
import type { Attachment } from '@/types'
|
|
4
|
+
import { ref, watch, onActivated, onDeactivated, onBeforeUnmount } from 'vue'
|
|
5
|
+
import { blobToUrl } from '@jnrs/shared'
|
|
6
|
+
import { debounce } from '@jnrs/shared/lodash'
|
|
3
7
|
import { FileApi } from '@/api/common'
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
const squareUrl = ref<string>('')
|
|
9
|
+
import { JnImageView } from '@jnrs/vue-core/components'
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
interface Props {
|
|
12
|
+
/**
|
|
13
|
+
* 要加载的文件列表 | 文件名唯一标识 uniqueFileName
|
|
14
|
+
*/
|
|
15
|
+
loadKeys: Attachment[] | string | undefined
|
|
16
|
+
}
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
async function loadImage() {
|
|
12
|
-
try {
|
|
13
|
-
const blob = await FileApi('20251216134412632-1376c8a7-1f31-451c-9eb4-944aaa03497b.png')
|
|
18
|
+
const { loadKeys } = defineProps<Props>()
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// 第一张显示的图片
|
|
21
|
+
const posterUrl = ref('')
|
|
22
|
+
|
|
23
|
+
// 存储每个 URL 对应的 URL 对象
|
|
24
|
+
const fileList = ref<FileItem[]>([])
|
|
25
|
+
|
|
26
|
+
// 存储每个 URL 对应的 revoke 函数用于副作用清理
|
|
27
|
+
const revokeFns = ref<(() => void)[]>([])
|
|
28
|
+
|
|
29
|
+
// 清理当前所有 Object URL
|
|
30
|
+
const clearUrls = () => {
|
|
31
|
+
revokeFns.value.forEach((revoke) => revoke())
|
|
32
|
+
revokeFns.value = []
|
|
33
|
+
fileList.value = []
|
|
34
|
+
posterUrl.value = ''
|
|
21
35
|
}
|
|
22
36
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
// 加载文件
|
|
38
|
+
const loadFilesRaw = async (keys: Props['loadKeys']) => {
|
|
39
|
+
clearUrls()
|
|
40
|
+
|
|
41
|
+
if (!keys) {
|
|
42
|
+
return
|
|
29
43
|
}
|
|
30
|
-
}
|
|
31
44
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
// 如果是字符串
|
|
46
|
+
if (typeof keys === 'string') {
|
|
47
|
+
try {
|
|
48
|
+
const blob = await FileApi(keys)
|
|
49
|
+
const { url, revoke } = blobToUrl(blob)
|
|
50
|
+
fileList.value = [{ src: url, fileName: keys }]
|
|
51
|
+
revokeFns.value = [revoke]
|
|
52
|
+
posterUrl.value = url
|
|
53
|
+
} catch (err) {
|
|
54
|
+
fileList.value = []
|
|
55
|
+
revokeFns.value = []
|
|
56
|
+
posterUrl.value = keys
|
|
57
|
+
console.warn('ImageView 组件异常', err)
|
|
58
|
+
}
|
|
59
|
+
return
|
|
38
60
|
}
|
|
39
|
-
}
|
|
40
61
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
// 如果是对象
|
|
63
|
+
if (keys !== null && typeof keys === 'object' && !Array.isArray(keys)) {
|
|
64
|
+
const { uniqueFileName, fileName } = keys
|
|
65
|
+
if (!uniqueFileName) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const blob = await FileApi(uniqueFileName)
|
|
70
|
+
const { url, revoke } = blobToUrl(blob)
|
|
71
|
+
|
|
72
|
+
fileList.value = [{ src: url, fileName: fileName || uniqueFileName }]
|
|
73
|
+
revokeFns.value = [revoke]
|
|
74
|
+
posterUrl.value = url
|
|
75
|
+
} catch (err) {
|
|
76
|
+
fileList.value = []
|
|
77
|
+
revokeFns.value = []
|
|
78
|
+
posterUrl.value = uniqueFileName
|
|
79
|
+
console.warn('ImageView 组件异常', err)
|
|
80
|
+
}
|
|
81
|
+
return
|
|
47
82
|
}
|
|
83
|
+
|
|
84
|
+
// 如果是数组
|
|
85
|
+
if (Array.isArray(keys) && keys.length > 0) {
|
|
86
|
+
const res = await Promise.all(
|
|
87
|
+
keys.map(async ({ uniqueFileName, fileName }) => {
|
|
88
|
+
if (!uniqueFileName) {
|
|
89
|
+
return { url: '', fileName: '', revoke: () => {} }
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const blob = await FileApi(uniqueFileName)
|
|
93
|
+
const { url, revoke } = blobToUrl(blob)
|
|
94
|
+
return { url, fileName, revoke }
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.warn('ImageView 组件异常', err)
|
|
97
|
+
return { url: uniqueFileName, fileName: '', revoke: () => {} }
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
)
|
|
101
|
+
fileList.value = res.map(({ url, fileName }) => ({ src: url, fileName }))
|
|
102
|
+
revokeFns.value = res.map((d) => d.revoke)
|
|
103
|
+
// 数组情况下默认显示第一张可用的图片
|
|
104
|
+
posterUrl.value = fileList.value.find((d) => d.fileName)?.src || ''
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const loadFiles = debounce(loadFilesRaw, 300)
|
|
109
|
+
|
|
110
|
+
watch(
|
|
111
|
+
() => loadKeys,
|
|
112
|
+
(nv) => {
|
|
113
|
+
loadFiles(nv)
|
|
114
|
+
},
|
|
115
|
+
{ deep: true, immediate: true }
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
onActivated(() => {
|
|
119
|
+
loadFiles(loadKeys)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// 副作用清理
|
|
123
|
+
onDeactivated(() => {
|
|
124
|
+
clearUrls()
|
|
48
125
|
})
|
|
49
126
|
|
|
50
|
-
//
|
|
51
|
-
|
|
127
|
+
// 副作用清理
|
|
128
|
+
onBeforeUnmount(() => {
|
|
129
|
+
clearUrls()
|
|
130
|
+
})
|
|
52
131
|
</script>
|
|
53
132
|
|
|
54
133
|
<template>
|
|
55
|
-
<
|
|
56
|
-
<!-- 可选:加载中状态 -->
|
|
57
|
-
<div v-else>加载中...</div>
|
|
134
|
+
<jn-image-view :src="posterUrl" :previewSrcList="fileList"></jn-image-view>
|
|
58
135
|
</template>
|