fix: 优化工作人员重复信息提示
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
"contract:check": "node scripts/check-backend-contract.mjs",
|
||||
"resource-pages:check": "node scripts/check-resource-pages.mjs",
|
||||
"account-roles:check": "node scripts/check-account-role-presentation.mjs",
|
||||
"avatar-retry:check": "node scripts/check-avatar-upload-cache.mjs",
|
||||
"audit:platform": "node scripts/check-backend-contract.mjs",
|
||||
"lint": "biome lint .",
|
||||
"lint:fix": "biome lint --write .",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 功能:验证头像上传成功后的保存重试会复用受控资源地址。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { transformWithOxc } from 'vite';
|
||||
|
||||
const sourceURL = new URL(
|
||||
'../src/views/resource/avatar-upload-cache.ts',
|
||||
import.meta.url,
|
||||
);
|
||||
const source = await readFile(sourceURL, 'utf8');
|
||||
const transformed = await transformWithOxc(source, sourceURL.pathname);
|
||||
const moduleURL = `data:text/javascript;base64,${Buffer.from(transformed.code).toString('base64')}`;
|
||||
const { createAvatarUploadCache } = await import(moduleURL);
|
||||
|
||||
let uploadCount = 0;
|
||||
const cache = createAvatarUploadCache(async () => {
|
||||
uploadCount += 1;
|
||||
return { uri: `/uploads/avatars/test-${uploadCount}.png` };
|
||||
});
|
||||
const file = { name: 'avatar.png', size: 128, lastModified: 1 };
|
||||
|
||||
const firstURI = await cache.resolve(file);
|
||||
const retryURI = await cache.resolve(file);
|
||||
assert.equal(firstURI, '/uploads/avatars/test-1.png');
|
||||
assert.equal(retryURI, firstURI);
|
||||
assert.equal(uploadCount, 1, '保存失败后重试不应重复上传同一头像');
|
||||
|
||||
cache.reset();
|
||||
const replacedURI = await cache.resolve(file);
|
||||
assert.equal(replacedURI, '/uploads/avatars/test-2.png');
|
||||
assert.equal(uploadCount, 2, '重置缓存后应重新上传头像');
|
||||
|
||||
let failedCount = 0;
|
||||
const retryableCache = createAvatarUploadCache(async () => {
|
||||
failedCount += 1;
|
||||
if (failedCount === 1) throw new Error('临时上传失败');
|
||||
return { uri: '/uploads/avatars/retry.png' };
|
||||
});
|
||||
await assert.rejects(() => retryableCache.resolve(file), /临时上传失败/);
|
||||
assert.equal(await retryableCache.resolve(file), '/uploads/avatars/retry.png');
|
||||
assert.equal(failedCount, 2, '上传失败后应允许重新上传');
|
||||
|
||||
console.log('头像失败重试缓存检查通过');
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 功能:缓存一次资源保存流程中已经完成的头像上传结果。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
|
||||
export type AvatarUpload = (file: File) => Promise<{ uri: string }>;
|
||||
|
||||
/** 创建头像上传缓存;选择新文件或清除头像时由调用方主动重置。 */
|
||||
export function createAvatarUploadCache(upload: AvatarUpload) {
|
||||
let cachedFile: File | undefined;
|
||||
let cachedURI: Promise<string> | undefined;
|
||||
|
||||
/** 返回当前文件的受控资源地址;同一文件重试时复用首次成功结果。 */
|
||||
async function resolve(file: File) {
|
||||
if (cachedFile !== file || !cachedURI) {
|
||||
cachedFile = file;
|
||||
cachedURI = upload(file)
|
||||
.then((result) => result.uri)
|
||||
.catch((error) => {
|
||||
// 上传本身失败时清空缓存,允许用户直接重试。
|
||||
if (cachedFile === file) cachedURI = undefined;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
return cachedURI;
|
||||
}
|
||||
|
||||
/** 清空已上传地址,确保新选择的文件不会错误复用旧头像。 */
|
||||
function reset() {
|
||||
cachedFile = undefined;
|
||||
cachedURI = undefined;
|
||||
}
|
||||
|
||||
return { resolve, reset };
|
||||
}
|
||||
@@ -6,12 +6,14 @@ import { Message } from '@arco-design/web-vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { avatarApi } from '@/api/avatar';
|
||||
import { DEFAULT_USER_AVATAR } from '@/constants/avatar';
|
||||
import { createAvatarUploadCache } from './avatar-upload-cache';
|
||||
|
||||
export function useResourceAvatar() {
|
||||
const url = ref(DEFAULT_USER_AVATAR);
|
||||
const file = ref<File>();
|
||||
const cleared = ref(false);
|
||||
const canClear = ref(false);
|
||||
const uploadCache = createAvatarUploadCache(avatarApi.upload);
|
||||
let objectURL = '';
|
||||
|
||||
function revoke() {
|
||||
@@ -22,6 +24,7 @@ export function useResourceAvatar() {
|
||||
/** 加载现有受保护头像,记录没有头像时继续使用默认图。 */
|
||||
async function load(resource: string, identity: string) {
|
||||
revoke();
|
||||
uploadCache.reset();
|
||||
url.value = DEFAULT_USER_AVATAR;
|
||||
canClear.value = false;
|
||||
file.value = undefined;
|
||||
@@ -45,6 +48,7 @@ export function useResourceAvatar() {
|
||||
return;
|
||||
}
|
||||
revoke();
|
||||
uploadCache.reset();
|
||||
objectURL = URL.createObjectURL(next);
|
||||
url.value = objectURL;
|
||||
file.value = next;
|
||||
@@ -55,6 +59,7 @@ export function useResourceAvatar() {
|
||||
/** 标记清除头像,真正写入空值发生在保存资料时。 */
|
||||
function clear() {
|
||||
revoke();
|
||||
uploadCache.reset();
|
||||
url.value = DEFAULT_USER_AVATAR;
|
||||
file.value = undefined;
|
||||
cleared.value = true;
|
||||
@@ -63,7 +68,7 @@ export function useResourceAvatar() {
|
||||
|
||||
/** 将头像变化写入资源更新载荷。 */
|
||||
async function applyToPayload(payload: Record<string, unknown>) {
|
||||
if (file.value) payload.avatar = (await avatarApi.upload(file.value)).uri;
|
||||
if (file.value) payload.avatar = await uploadCache.resolve(file.value);
|
||||
else if (cleared.value) payload.avatar = '';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user