保存进度!

This commit is contained in:
2025-11-21 18:01:17 +08:00
parent d22b29f45d
commit 1e20eeb0d1
9 changed files with 259 additions and 47 deletions

View File

@@ -47,7 +47,7 @@ public class FileUpload
@RequestMapping( path = "/file-upload.do" ) @RequestMapping( path = "/file-upload.do" )
@ResponseBody @ResponseBody
public UploadFileResponse saveUploadFile( public UploadFileResponse saveUploadFile(
@RequestParam( "file-name" ) String fileName, @RequestParam( "fileName" ) String fileName,
@RequestParam( "files" ) MultipartFile file, @RequestParam( "files" ) MultipartFile file,
HttpServletRequest request HttpServletRequest request
) )

View File

@@ -14,3 +14,7 @@ VITE_APP_CORS_ORIGIN=http://localhost:3000
VITE_APP_TIMEOUT=5000 VITE_APP_TIMEOUT=5000
VITE_APP_RETRY_ATTEMPTS=3 VITE_APP_RETRY_ATTEMPTS=3
VITE_APP_CACHE_ENABLED=true VITE_APP_CACHE_ENABLED=true
#上传文件
VITE_APP_URL_UPLOAD_FILE=http://localhost:8080/RegulatoryManagementBackend/file/file-upload.do
VITE_APP_URL_MOVE_FILE=http://localhost:8080/RegulatoryManagementBackend/file/move-file.do

View File

@@ -14,11 +14,11 @@
}, },
"devDependencies": { "devDependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@stylistic/eslint-plugin": "^5.6.0", "@stylistic/eslint-plugin": "^5.6.1",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@typescript-eslint/eslint-plugin": "^8.47.0", "@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0", "@typescript-eslint/parser": "^8.47.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.2",
"@vue-office/docx": "^1.6.3", "@vue-office/docx": "^1.6.3",
"@vue-office/excel": "^1.7.14", "@vue-office/excel": "^1.7.14",
"@vue-office/pdf": "^2.0.10", "@vue-office/pdf": "^2.0.10",
@@ -28,9 +28,9 @@
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-vue": "^10.5.1", "eslint-plugin-vue": "^10.5.1",
"path": "^0.12.7", "path": "^0.12.7",
"sass": "^1.94.1", "sass": "^1.94.2",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.2.2", "vite": "^7.2.4",
"vue-demi": "^0.14.10", "vue-demi": "^0.14.10",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-pdf-embed": "^2.1.3", "vue-pdf-embed": "^2.1.3",
@@ -1391,9 +1391,9 @@
} }
}, },
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.29", "version": "1.0.0-beta.50",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",
"integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -1678,9 +1678,9 @@
] ]
}, },
"node_modules/@stylistic/eslint-plugin": { "node_modules/@stylistic/eslint-plugin": {
"version": "5.6.0", "version": "5.6.1",
"resolved": "https://registry.npmmirror.com/@stylistic/eslint-plugin/-/eslint-plugin-5.6.0.tgz", "resolved": "https://registry.npmmirror.com/@stylistic/eslint-plugin/-/eslint-plugin-5.6.1.tgz",
"integrity": "sha512-owEc4B8ME+O/xyZOkLVyLqPMsUgJXIM4XzCm5Vt3WvRXpyoOfYxgA+JkEiFqXPCI8+Nc2BzAT+KGAK7QleGs8Q==", "integrity": "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1998,13 +1998,13 @@
} }
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.2.tgz",
"integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", "integrity": "sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rolldown/pluginutils": "1.0.0-beta.29" "@rolldown/pluginutils": "1.0.0-beta.50"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
@@ -4069,9 +4069,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.94.1", "version": "1.94.2",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.94.1.tgz", "resolved": "https://registry.npmmirror.com/sass/-/sass-1.94.2.tgz",
"integrity": "sha512-/YVm5FRQaRlr3oNh2LLFYne1PdPlRZGyKnHh1sLleOqLcohTR4eUUvBjBIqkl1fEXd1MGOHgzJGJh+LgTtV4KQ==", "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -4278,9 +4278,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.2.2", "version": "7.2.4",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.2.2.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-7.2.4.tgz",
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,

View File

@@ -15,11 +15,11 @@
}, },
"devDependencies": { "devDependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@stylistic/eslint-plugin": "^5.6.0", "@stylistic/eslint-plugin": "^5.6.1",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@typescript-eslint/eslint-plugin": "^8.47.0", "@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0", "@typescript-eslint/parser": "^8.47.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.2",
"@vue-office/docx": "^1.6.3", "@vue-office/docx": "^1.6.3",
"@vue-office/excel": "^1.7.14", "@vue-office/excel": "^1.7.14",
"@vue-office/pdf": "^2.0.10", "@vue-office/pdf": "^2.0.10",
@@ -29,9 +29,9 @@
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-vue": "^10.5.1", "eslint-plugin-vue": "^10.5.1",
"path": "^0.12.7", "path": "^0.12.7",
"sass": "^1.94.1", "sass": "^1.94.2",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.2.2", "vite": "^7.2.4",
"vue-demi": "^0.14.10", "vue-demi": "^0.14.10",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-pdf-embed": "^2.1.3", "vue-pdf-embed": "^2.1.3",

View File

@@ -0,0 +1,28 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-21 10:19:11
* @LastEditors: Kane Wang
* @LastModified: 2025-11-21 10:36:19
* @FilePath: src/types/upload_file.ts
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
interface UploadFileResponse
{
success: boolean;
message: string;
fileList: UploadedFile[];
}
interface UploadedFile
{
fileName: string;
localFilePath: string;
}
export {
type UploadedFile,
type UploadFileResponse
};

View File

@@ -0,0 +1,18 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-21 09:39:59
* @LastEditors: Kane Wang
* @LastModified: 2025-11-21 09:40:00
* @FilePath: src/utils/config.ts
* @Description: 保存应用的配置参数。
*
* Copyright (c) 2025 by Kane All rights reserved
*/
const API_URL= {
URL_UPLOAD_FILE: import.meta.env.VITE_APP_URL_UPLOAD_FILE,
URL_MOVE_FILE: import.meta.env.VITE_APP_URL_MOVE_FILE,
};
console.log( API_URL );
export { API_URL };

View File

@@ -2,9 +2,55 @@
* @Author: Kane Wang <wangkane@qq.com> * @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-23 16:52:10 * @Date: 2025-10-23 16:52:10
* @LastEditors: Kane Wang * @LastEditors: Kane Wang
* @LastModified: 2025-10-23 17:04:54 * @LastModified: 2025-11-21 11:17:40
* @FilePath: src/utils/utils.ts * @FilePath: src/utils/utils.ts
* @Description: * @Description: 提供 一些功能性的函数
* *
* Copyright (c) 2025 by Kane All rights reserved * Copyright (c) 2025 by Kane All rights reserved
*/ */
/**
* 取文件路径末尾的扩展名作为文件类型
* @param filePath 文件路径
* @returns 文件类型字符串
*/
function getFileType( filePath: string ): string
{
let type = "未知类型";
if ( filePath == null || filePath.length == 0 )
{
return type;
}
const startIndex = filePath.lastIndexOf( "." );
const fileType = filePath.slice( startIndex + 1 ).toUpperCase();
// ignore-eslint-next-line
switch( fileType )
{
case "DOCX":
type = "WPS文档";
break;
case "XLSX":
type = "WPS表格";
break;
case "PDF":
type = "PDF文档";
break;
case "JPG":
case "PNG":
case "BMP":
case "GIF":
type = "图片文件";
break;
default:
type = "未知文件类型";
}
// type = fileType.length != 0 ? fileType + "文件" : "未知类型";
return type;
}
export { getFileType };

View File

@@ -12,7 +12,7 @@ Copyright © CPIC All rights reserved
<span>名称</span> <span>名称</span>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<el-input style="text-align:center;" /> <el-input v-model.trim.lazy="ui.newRegulatory.regulatory_name" style="text-align:center;" />
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
@@ -20,13 +20,13 @@ Copyright © CPIC All rights reserved
<span>部门</span> <span>部门</span>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<el-input /> <el-input v-model.trim="ui.newRegulatory.department_name" />
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<span>发布修订年份</span> <span>发布修订年份</span>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<el-input /> <el-input v-model.lazy.number.trim="ui.newRegulatory.release_year" />
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
@@ -34,7 +34,7 @@ Copyright © CPIC All rights reserved
<span>备注</span> <span>备注</span>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<el-input type="textarea" :rows="3" /> <el-input v-model.lazy.trim="ui.newRegulatory.comment" type="textarea" :rows="3" />
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
@@ -43,9 +43,6 @@ Copyright © CPIC All rights reserved
<el-button type="primary" icon="document" @click="showUploadFileDialog"> <el-button type="primary" icon="document" @click="showUploadFileDialog">
新增文档 新增文档
</el-button> </el-button>
<el-button type="primary" icon="document">
新增文档
</el-button>
</div> </div>
</el-col> </el-col>
<el-col :span="5" /> <el-col :span="5" />
@@ -63,21 +60,27 @@ Copyright © CPIC All rights reserved
border border
:head-cell-style="headerCellStyle" :head-cell-style="headerCellStyle"
empty-text="请上传文件" empty-text="请上传文件"
:data="ui.newRegulatory.regulatory_files"
> >
<el-table-column label="文件名" align="center" width="200px"> <el-table-column label="文件名" align="center">
<template #default="file"> <template #default="file">
<span>{{ file.row.filename }}</span> <span>{{ file.row.regulatory_file_name }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="文件类型" align="center"> <el-table-column label="文件类型" align="center" width="200px">
<template #default="file"> <template #default="file">
<span>{{ file.row.filename }}</span> <span>{{ file.row.file_type }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="200px"> <el-table-column label="操作" align="center" width="200px">
<el-button type="primary" icon="search"> <template #default="file">
查看 <el-button type="primary" icon="search" circle />
</el-button> <el-button
type="danger" icon="delete"
circle
@click="onDeleteUploadedFile(file.row)"
/>
</template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="upload-dialog-wrapper"> <div class="upload-dialog-wrapper">
@@ -108,18 +111,41 @@ Copyright © CPIC All rights reserved
</template> </template>
<script lang="ts"> <script lang="ts">
import {reactive, ref} from "vue"; import {reactive, ref} from "vue";
import { type UploadProps, type UploadFile, type UploadFiles, ElMessage, ElMessageBox } from "element-plus";
import {type RegulatoryData, type RegulatoryFile} from "@/types/regulatory/regulatory.ts";
import { type UploadedFile, type UploadFileResponse } from "@/types/upload_file.ts";
import {API_URL} from "@/utils/config.ts";
import { getFileType } from "@/utils/utils";
interface UI{
showUI: boolean;
showUploadDialog: boolean;
urlFileUpload: string;
uploadParameters: {
fileName: string;
};
newRegulatory: RegulatoryData;
showFileList: boolean;
};
export default { export default {
name: "NewRegulatory", name: "NewRegulatory",
components: {}, components: {},
setup() setup()
{ {
const ui = reactive({ const ui: UI = reactive({
showUI: true, showUI: true,
showUploadDialog: false, showUploadDialog: false,
urlFileUpload: "", urlFileUpload: API_URL.URL_UPLOAD_FILE,
uploadParameters: { uploadParameters: {
"file-name": "1234", fileName: "1234",
},
newRegulatory: {
department_name: "",
release_year: "",
regulatory_name: "",
comment: "",
regulatory_files: [],
}, },
showFileList: false, showFileList: false,
}); });
@@ -133,19 +159,78 @@ export default {
textAlign: "center", textAlign: "center",
}); });
const onUploadSuccess = ()=> {};
const showUploadFileDialog = (): void => const showUploadFileDialog = (): void =>
{ {
ui.showUploadDialog = true; ui.showUploadDialog = true;
}; };
/*表格操作相关 */
const onDeleteUploadedFile = ( rowId: any ): void =>
{
console.log( "点击的rowid", rowId );
};
const onUploadSuccess: UploadProps["onSuccess"] = ( response: UploadFileResponse, uploadFile: UploadFile, uploadFiles: UploadFiles ): void =>
{
console.log( "测试上传制度文件结果:", response );
ui.showUploadDialog = false;
// 先判断成功标志位
if ( response.success )
{
// 成功,把文件写入清单
if ( response.fileList === null || response.fileList.length === 0 )
{
// 上传文件路径有问题,提示一下
ElMessageBox.confirm(
"上传文件的保存路径有误,请联系开发人员。",
"上传文件错误",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then((): void => {})
.catch((): void => {});
}
const uploadedFile: RegulatoryFile = {
regulatory_file_name: response.fileList[0]?.fileName ?? "",
local_file_path: response.fileList[0]?.localFilePath ?? "",
file_type: getFileType( response.fileList[0]?.localFilePath ),
file_url: "",
};
ui.newRegulatory.regulatory_files?.push( uploadedFile );
console.log( "文件列表", ui.newRegulatory.regulatory_files );
}
else
{
// 失败了,提示一下
ElMessageBox.confirm(
response.message,
"上传文件错误",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then((): void => {})
.catch((): void => {});
}
};
return { return {
ui, ui,
headerCellStyle, headerCellStyle,
cellStyle, cellStyle,
onUploadSuccess, onUploadSuccess,
showUploadFileDialog, showUploadFileDialog,
onDeleteUploadedFile,
}; };
}, },
}; };

View File

@@ -129,3 +129,34 @@ const onUploadSuccess: UploadProps["onSuccess"] = ( response: FileUploadResponse
}; };
``` ```
### typescript 的安全链式调用 和 强制链式调用
在链式调用时,在成员访问操作符前加上?,表示安全链式调用;加上!表示强制链式调用。
如果前的属性存在则正常调用否则返回null。
```typescript
// 这里 Error对象定义的stack是可选参数如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
return new Error().stack.split('\n');
// 我们可以添加?操作符当stack属性存在时调用 stack.split。
// 若stack不存在则返回空
return new Error().stack?.split('\n');
// 以上代码等同以下代码, 感谢 @dingyanhe 的监督
const err = new Error();
return err.stack && err.stack.split('\n');
```
强制链式调用表示!前的属性一定会存在。
```typescript
// 这里 Error对象定义的stack是可选参数如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
new Error().stack.split('\n');
// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');
```