feat: 更新依赖项版本,优化上传组件和路由配置,调整表单字段

This commit is contained in:
2026-01-12 16:04:48 +07:00
parent 07b11e5657
commit ac7882e99b
8 changed files with 47 additions and 94 deletions

View File

@@ -50,7 +50,7 @@
"@better-scroll/core": "2.5.1", "@better-scroll/core": "2.5.1",
"@elysiajs/eden": "^1.4.5", "@elysiajs/eden": "^1.4.5",
"@iconify/vue": "5.0.0", "@iconify/vue": "5.0.0",
"@riwa/api-types": "http://192.168.1.7:9527/api/riwa-eden-0.0.109.tgz", "@riwa/api-types": "http://192.168.1.7:9527/api/riwa-eden-0.0.126.tgz",
"@sa/axios": "workspace:*", "@sa/axios": "workspace:*",
"@sa/color": "workspace:*", "@sa/color": "workspace:*",
"@sa/hooks": "workspace:*", "@sa/hooks": "workspace:*",

12
pnpm-lock.yaml generated
View File

@@ -18,8 +18,8 @@ importers:
specifier: 5.0.0 specifier: 5.0.0
version: 5.0.0(vue@3.5.25(typescript@5.9.3)) version: 5.0.0(vue@3.5.25(typescript@5.9.3))
'@riwa/api-types': '@riwa/api-types':
specifier: http://192.168.1.7:9527/api/riwa-eden-0.0.109.tgz specifier: http://192.168.1.7:9527/api/riwa-eden-0.0.126.tgz
version: '@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.109.tgz(@elysiajs/eden@1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))' version: '@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.126.tgz(@elysiajs/eden@1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))'
'@sa/axios': '@sa/axios':
specifier: workspace:* specifier: workspace:*
version: link:packages/axios version: link:packages/axios
@@ -1230,9 +1230,9 @@ packages:
'@quansync/fs@0.1.6': '@quansync/fs@0.1.6':
resolution: {integrity: sha512-zoA8SqQO11qH9H8FCBR7NIbowYARIPmBz3nKjgAaOUDi/xPAAu1uAgebtV7KXHTc6CDZJVRZ1u4wIGvY5CWYaw==} resolution: {integrity: sha512-zoA8SqQO11qH9H8FCBR7NIbowYARIPmBz3nKjgAaOUDi/xPAAu1uAgebtV7KXHTc6CDZJVRZ1u4wIGvY5CWYaw==}
'@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.109.tgz': '@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.126.tgz':
resolution: {tarball: http://192.168.1.7:9527/api/riwa-eden-0.0.109.tgz} resolution: {tarball: http://192.168.1.7:9527/api/riwa-eden-0.0.126.tgz}
version: 0.0.109 version: 0.0.126
peerDependencies: peerDependencies:
'@elysiajs/eden': ^1.4.5 '@elysiajs/eden': ^1.4.5
@@ -5651,7 +5651,7 @@ snapshots:
dependencies: dependencies:
quansync: 0.3.0 quansync: 0.3.0
'@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.109.tgz(@elysiajs/eden@1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))': '@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.126.tgz(@elysiajs/eden@1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))':
dependencies: dependencies:
'@elysiajs/eden': 1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)) '@elysiajs/eden': 1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))

View File

@@ -1,7 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui'; import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui';
import { type UploadFetchOptions, getS3FileMetadata, uploadToS3 } from '@/utils/aws/s3'; import { client, safeClient } from '@/service/api';
import { type UploadFetchOptions, uploadToS3 } from '@/utils/aws/s3';
defineOptions({ name: 'UploadS3' }); defineOptions({ name: 'UploadS3' });
@@ -34,39 +35,24 @@ const emit = defineEmits<{
const fileList = ref<UploadFileInfo[]>([]); const fileList = ref<UploadFileInfo[]>([]);
const loading = ref(false); const loading = ref(false);
// 初始化文件列表
async function initFileList() { async function initFileList() {
const fileIds = props.modelValue.filter(id => id && id.trim() !== ''); const fileIds = props.modelValue.filter(id => id && id.trim() !== '');
if (!props.modelValue || fileIds.length === 0) { if (!props.modelValue || fileIds.length === 0) {
fileList.value = []; fileList.value = [];
return; return;
} }
loading.value = true; loading.value = true;
try { try {
const fileInfoPromises = props.modelValue.map(async fileId => { const { data } = await safeClient(client.api.file_storage.access_urls.post({ fileIds }));
try {
const metadata = await getS3FileMetadata(fileId);
return {
id: fileId,
name: metadata.fileName || fileId,
status: 'finished' as const,
url: fileId,
file: null as any
};
} catch (error) {
return {
id: fileId,
name: fileId,
status: 'error' as const,
url: fileId,
file: null as any
};
}
});
fileList.value = await Promise.all(fileInfoPromises); fileList.value =
data.value?.map(item => ({
id: item.id,
name: item.fileName || item.id,
status: 'finished' as const,
url: item.url,
file: null as any
})) || [];
} catch (error) { } catch (error) {
window.$message?.error('加载文件列表失败'); window.$message?.error('加载文件列表失败');
} finally { } finally {
@@ -74,11 +60,9 @@ async function initFileList() {
} }
} }
// 监听 modelValue 变化
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal, oldVal) => { (newVal, oldVal) => {
// 只在外部变化时重新加载(避免内部上传/删除时重复加载)
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
initFileList(); initFileList();
} }
@@ -86,15 +70,12 @@ watch(
{ deep: true } { deep: true }
); );
// 组件挂载时初始化
onMounted(() => { onMounted(() => {
initFileList(); initFileList();
}); });
// 自定义上传请求
async function handleCustomRequest({ file, onProgress, onFinish, onError }: UploadCustomRequestOptions) { async function handleCustomRequest({ file, onProgress, onFinish, onError }: UploadCustomRequestOptions) {
try { try {
// 验证文件大小
const fileSizeMB = file.file!.size / 1024 / 1024; const fileSizeMB = file.file!.size / 1024 / 1024;
if (fileSizeMB > props.maxSize) { if (fileSizeMB > props.maxSize) {
window.$message?.error(`文件大小不能超过 ${props.maxSize}MB`); window.$message?.error(`文件大小不能超过 ${props.maxSize}MB`);
@@ -102,14 +83,12 @@ async function handleCustomRequest({ file, onProgress, onFinish, onError }: Uplo
return; return;
} }
// 验证文件数量
if (fileList.value.length > props.maxFiles) { if (fileList.value.length > props.maxFiles) {
window.$message?.error(`最多只能上传 ${props.maxFiles} 个文件`); window.$message?.error(`最多只能上传 ${props.maxFiles} 个文件`);
onError(); onError();
return; return;
} }
// 上传到 S3
const options = { const options = {
fileName: file.name, fileName: file.name,
fileSize: file.file?.size, fileSize: file.file?.size,
@@ -124,38 +103,30 @@ async function handleCustomRequest({ file, onProgress, onFinish, onError }: Uplo
} }
}); });
// 添加到文件列表 // 直接修改 file 对象的 idNaiveUI 会自动同步到 fileList
fileList.value.push({ file.id = fileId;
id: fileId, file.status = 'finished';
name: file.name, file.url = fileId;
status: 'finished',
url: fileId,
file: null as any
});
// 更新文件 ID 到 modelValue
const newFileIds = [...props.modelValue, fileId]; const newFileIds = [...props.modelValue, fileId];
emit('update:modelValue', newFileIds); emit('update:modelValue', newFileIds);
onFinish(); onFinish();
window.$message?.success(`文件 ${file.name} 上传成功`); window.$message?.success(`文件 ${file.name} 上传成功`);
} catch (error: any) { } catch (error: any) {
window.$message?.error(error.message || '上传失败');
onError(); onError();
} }
} }
// 删除文件
function handleRemove({ file }: { file: UploadFileInfo }) { function handleRemove({ file }: { file: UploadFileInfo }) {
// 从文件列表中移除 // 只删除上传成功的文件(有有效 id 的文件)
fileList.value = fileList.value.filter(f => f.id !== file.id); if (file.id && typeof file.id === 'string' && props.modelValue.includes(file.id)) {
// 更新 modelValue const newFileIds = props.modelValue.filter(id => id !== file.id);
const newFileIds = props.modelValue.filter(id => id !== file.id); emit('update:modelValue', newFileIds);
emit('update:modelValue', newFileIds); }
return true; return true;
} }
// 上传前验证
function beforeUpload({ file }: { file: UploadFileInfo }) { function beforeUpload({ file }: { file: UploadFileInfo }) {
const fileSizeMB = file.file!.size / 1024 / 1024; const fileSizeMB = file.file!.size / 1024 / 1024;
if (fileSizeMB > props.maxSize) { if (fileSizeMB > props.maxSize) {

View File

@@ -116,16 +116,6 @@ export const generatedRoutes: GeneratedRoute[] = [
hideInMenu: true hideInMenu: true
} }
}, },
{
name: 'notification',
path: '/notification',
component: 'layout.base$view.notification',
meta: {
title: 'notification',
i18nKey: 'route.notification',
order: 7
}
},
{ {
name: 'news', name: 'news',
path: '/news', path: '/news',
@@ -136,6 +126,16 @@ export const generatedRoutes: GeneratedRoute[] = [
order: 8 order: 8
} }
}, },
{
name: 'notification',
path: '/notification',
component: 'layout.base$view.notification',
meta: {
title: 'notification',
i18nKey: 'route.notification',
order: 7
}
},
{ {
name: 'rwa', name: 'rwa',
path: '/rwa', path: '/rwa',

View File

@@ -25,10 +25,8 @@ declare module 'vue' {
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default'] IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default'] IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default'] IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
IconIcRoundEdit: typeof import('~icons/ic/round-edit')['default']
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default'] IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
IconIcRoundUpload: typeof import('~icons/ic/round-upload')['default'] IconIcRoundUpload: typeof import('~icons/ic/round-upload')['default']
IconIcRoundVisibility: typeof import('~icons/ic/round-visibility')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default'] IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default'] IconLocalLogo: typeof import('~icons/local/logo')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
@@ -86,7 +84,6 @@ declare module 'vue' {
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab'] NTab: typeof import('naive-ui')['NTab']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NText: typeof import('naive-ui')['NText']
NThing: typeof import('naive-ui')['NThing'] NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip'] NTooltip: typeof import('naive-ui')['NTooltip']
NUpload: typeof import('naive-ui')['NUpload'] NUpload: typeof import('naive-ui')['NUpload']
@@ -123,10 +120,8 @@ declare global {
const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default'] const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default'] const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
const IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default'] const IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
const IconIcRoundEdit: typeof import('~icons/ic/round-edit')['default']
const IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default'] const IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
const IconIcRoundUpload: typeof import('~icons/ic/round-upload')['default'] const IconIcRoundUpload: typeof import('~icons/ic/round-upload')['default']
const IconIcRoundVisibility: typeof import('~icons/ic/round-visibility')['default']
const IconLocalBanner: typeof import('~icons/local/banner')['default'] const IconLocalBanner: typeof import('~icons/local/banner')['default']
const IconLocalLogo: typeof import('~icons/local/logo')['default'] const IconLocalLogo: typeof import('~icons/local/logo')['default']
const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
@@ -184,7 +179,6 @@ declare global {
const NSwitch: typeof import('naive-ui')['NSwitch'] const NSwitch: typeof import('naive-ui')['NSwitch']
const NTab: typeof import('naive-ui')['NTab'] const NTab: typeof import('naive-ui')['NTab']
const NTabs: typeof import('naive-ui')['NTabs'] const NTabs: typeof import('naive-ui')['NTabs']
const NText: typeof import('naive-ui')['NText']
const NThing: typeof import('naive-ui')['NThing'] const NThing: typeof import('naive-ui')['NThing']
const NTooltip: typeof import('naive-ui')['NTooltip'] const NTooltip: typeof import('naive-ui')['NTooltip']
const NUpload: typeof import('naive-ui')['NUpload'] const NUpload: typeof import('naive-ui')['NUpload']

View File

@@ -12,16 +12,16 @@ export type UploadFetchOptions = CommonType.TreatyBody<typeof client.api.file_st
export async function uploadToS3(file: File, options: UploadOptions) { export async function uploadToS3(file: File, options: UploadOptions) {
const { onProgress, signal, fetchOptions } = options; const { onProgress, signal, fetchOptions } = options;
// 1. 获取预签名 URL const { data } = await safeClient(
const { data, error } = await safeClient(
client.api.file_storage.upload_url.post({ client.api.file_storage.upload_url.post({
...fetchOptions ...fetchOptions
}) })
); );
if (error.value || !data.value) { if (!data.value) {
throw new Error('获取上传 URL 失败'); throw new Error('获取上传 URL 失败');
} }
const { fileId, uploadUrl, method, headers } = toRefs(data.value); const { fileId, uploadUrl, method, headers } = toRefs(data.value);
// 2. 上传文件到 S3 // 2. 上传文件到 S3
@@ -77,18 +77,6 @@ export async function uploadToS3(file: File, options: UploadOptions) {
}); });
} }
export async function getS3FileUrl(fileId: string): Promise<string> {
const { data, error } = await safeClient(
client.api.file_storage.download_url.post({
fileId
})
);
if (error.value || !data.value) {
throw new Error('获取文件下载 URL 失败');
}
return data.value.downloadUrl;
}
export async function getS3FileMetadata(fileId: string) { export async function getS3FileMetadata(fileId: string) {
const { data, error } = await safeClient(client.api.file_storage({ id: fileId }).get()); const { data, error } = await safeClient(client.api.file_storage({ id: fileId }).get());
if (error.value || !data.value) { if (error.value || !data.value) {

View File

@@ -27,7 +27,7 @@ const form = ref<Body>({
description: '', description: '',
estimatedValue: '1', estimatedValue: '1',
totalSupplyLimit: '100', totalSupplyLimit: '100',
proofDocuments: '' proofDocumentIds: []
}); });
const rules: FormRules = { const rules: FormRules = {
@@ -129,12 +129,12 @@ function handleCreateDraftAndSubmit() {
</NFormItem> </NFormItem>
<NFormItem path="proofDocuments" label="资产证明 "> <NFormItem path="proofDocuments" label="资产证明 ">
<Upload <Upload
:model-value="form.proofDocuments?.split(',') || []" :model-value="form.proofDocumentIds || []"
:fetch-options="{ :fetch-options="{
businessType: 'rwa_proof' businessType: 'rwa_proof'
}" }"
accept="application/pdf,image/*,.doc,.docx" accept="application/pdf,image/*,.doc,.docx"
@update:model-value="val => (form.proofDocuments = val.join(','))" @update:model-value="val => (form.proofDocumentIds = val)"
/> />
</NFormItem> </NFormItem>
<NSpace justify="end"> <NSpace justify="end">

View File

@@ -33,7 +33,7 @@ const form = ref<Body>({
description: props.data.description, description: props.data.description,
estimatedValue: props.data.estimatedValue, estimatedValue: props.data.estimatedValue,
totalSupplyLimit: props.data.totalSupplyLimit, totalSupplyLimit: props.data.totalSupplyLimit,
proofDocuments: props.data.proofDocuments proofDocumentIds: props.data.proofDocumentIds
}); });
const rules: FormRules = { const rules: FormRules = {
@@ -122,14 +122,14 @@ function handleSubmit() {
<NFormItem path="description" label="产品描述"> <NFormItem path="description" label="产品描述">
<NInput v-model:value="form.description" type="textarea" /> <NInput v-model:value="form.description" type="textarea" />
</NFormItem> </NFormItem>
<NFormItem path="proofDocuments" label="资产证明 "> <NFormItem path="proofDocumentIds" label="资产证明 ">
<Upload <Upload
:model-value="form.proofDocuments?.split(',') || []" :model-value="form.proofDocumentIds || []"
:fetch-options="{ :fetch-options="{
businessType: 'rwa_proof' businessType: 'rwa_proof'
}" }"
accept="application/pdf,image/*,.doc,.docx" accept="application/pdf,image/*,.doc,.docx"
@update:model-value="val => (form.proofDocuments = val.join(','))" @update:model-value="val => (form.proofDocumentIds = val)"
/> />
</NFormItem> </NFormItem>
<NSpace justify="end"> <NSpace justify="end">