@@ -9,6 +9,7 @@ import { formatAssetStatus, formatAssetVisibility, formatDateTime, formatFileSiz
99
1010type AssetStatus = (typeof assetStatusValues )[number ];
1111type AssetVisibility = ' public' | ' private' ;
12+ type AssetVisibilityFilter = ' all' | AssetVisibility ;
1213
1314interface AssetFormState {
1415 storageProvider: string ;
@@ -29,7 +30,6 @@ interface AssetFormState {
2930
3031interface UploadFormState {
3132 folder: string ;
32- visibility: AssetVisibility ;
3333 altText: string ;
3434 assetType: string ;
3535}
@@ -42,7 +42,7 @@ interface UploadQueueItem {
4242
4343type AssetWorkspaceMode = ' upload' | ' edit' ;
4444
45- const visibilityOptions : AssetVisibility [] = [' public' , ' private' ];
45+ const filterVisibilityOptions : AssetVisibilityFilter [] = [' all ' , ' public' , ' private' ];
4646
4747const createBlankForm = (bucket = ' rebase-media' ): AssetFormState => ({
4848 storageProvider: ' r2' ,
@@ -63,7 +63,6 @@ const createBlankForm = (bucket = 'rebase-media'): AssetFormState => ({
6363
6464const createUploadForm = (): UploadFormState => ({
6565 folder: ' media' ,
66- visibility: ' public' ,
6766 altText: ' ' ,
6867 assetType: ' ' ,
6968});
@@ -92,7 +91,7 @@ const upload = reactive<UploadFormState>(createUploadForm());
9291const filters = reactive ({
9392 query: ' ' ,
9493 status: ' all' ,
95- visibility: ' all' ,
94+ visibility: ' all' as AssetVisibilityFilter ,
9695});
9796
9897const selectedAsset = computed (() => rows .value .find ((row ) => row .id === selectedAssetId .value ) ?? null );
@@ -151,6 +150,13 @@ const uploadStatusMessage = computed(() => {
151150 return uploadConfig .value .message ;
152151 }
153152});
153+ const uploadMimeSummary = computed (() => {
154+ const values = uploadConfig .value ?.acceptedMimeTypes ?? [];
155+ return values .length > 0 ? values .join (' 、' ) : ' 尚未配置' ;
156+ });
157+ const uploadLimitSummary = computed (() => (uploadConfig .value ? formatFileSize (uploadConfig .value .maxUploadBytes ) : ' 尚未配置' ));
158+ const legacyPrivateVisibility = computed (() => (selectedAsset .value ?.visibility ?? form .visibility ) === ' private' );
159+ const editorVisibilityOptions = computed <AssetVisibility []>(() => (legacyPrivateVisibility .value ? [' private' , ' public' ] : [' public' ]));
154160const uploadProgressMessage = computed (() => {
155161 if (! uploading .value || ! uploadProgressName .value ) {
156162 return ' ' ;
@@ -379,15 +385,32 @@ const loadPage = async (nextSelectedId: string | null = selectedAssetId.value, p
379385
380386const onFilePicked = (event : Event ) => {
381387 const input = event .target as HTMLInputElement ;
382- setUploadFiles (Array .from (input .files ?? []));
388+ const files = Array .from (input .files ?? []);
389+ const acceptedFiles: File [] = [];
390+ const rejections: string [] = [];
391+
392+ for (const file of files ) {
393+ const failure = validateSelectedUploadFile (file );
394+ if (failure ) {
395+ rejections .push (` ${file .name }:${failure } ` );
396+ continue ;
397+ }
398+
399+ acceptedFiles .push (file );
400+ }
401+
402+ setUploadFiles (acceptedFiles );
403+ if (rejections .length > 0 ) {
404+ errorMessage .value = ` 以下文件未加入上传队列:${summarizeUploadSelectionRejections (rejections )} ` ;
405+ }
383406 input .value = ' ' ;
384407};
385408
386409const buildUploadPayload = (file : File ) => {
387410 const payload = new FormData ();
388411 payload .append (' file' , file );
389412 payload .append (' folder' , upload .folder .trim ());
390- payload .append (' visibility' , upload . visibility );
413+ payload .append (' visibility' , ' public ' );
391414
392415 const altText = upload .altText .trim ();
393416 if (altText ) {
@@ -411,6 +434,40 @@ const summarizeUploadFailures = (failures: string[]) => {
411434 return ` ${visible };另有 ${failures .length - 3 } 个文件失败 ` ;
412435};
413436
437+ const summarizeUploadSelectionRejections = (failures : string []) => {
438+ const visible = failures .slice (0 , 3 ).join (' ;' );
439+ if (failures .length <= 3 ) {
440+ return visible ;
441+ }
442+
443+ return ` ${visible };另有 ${failures .length - 3 } 个文件不符合限制 ` ;
444+ };
445+
446+ const formatAssetVisibilityOption = (value : AssetVisibility ) => {
447+ if (value === ' private' ) {
448+ return ' 私有(历史值,不再支持写入)' ;
449+ }
450+
451+ return ' 公开' ;
452+ };
453+
454+ const validateSelectedUploadFile = (file : File ) => {
455+ const config = uploadConfig .value ;
456+ if (! config ) {
457+ return null ;
458+ }
459+
460+ if (file .size > config .maxUploadBytes ) {
461+ return ` 超过 ${formatFileSize (config .maxUploadBytes )} ` ;
462+ }
463+
464+ if (! config .acceptedMimeTypes .includes (file .type )) {
465+ return ' 文件类型不受支持' ;
466+ }
467+
468+ return null ;
469+ };
470+
414471const uploadAsset = async () => {
415472 if (uploadFileCount .value === 0 ) {
416473 errorMessage .value = ' 请先选择至少一个要上传的文件。' ;
@@ -644,7 +701,13 @@ const goToAssetPage = async (nextPage: number) => {
644701 <span >可见性</span >
645702 <select v-model =" filters.visibility" >
646703 <option value =" all" >全部可见性</option >
647- <option v-for =" visibility in visibilityOptions" :key =" visibility" :value =" visibility" >{{ formatAssetVisibility(visibility) }}</option >
704+ <option
705+ v-for =" visibility in filterVisibilityOptions.filter((value) => value !== 'all')"
706+ :key =" visibility"
707+ :value =" visibility"
708+ >
709+ {{ formatAssetVisibility(visibility) }}
710+ </option >
648711 </select >
649712 </label >
650713 </div >
@@ -762,6 +825,14 @@ const goToAssetPage = async (nextPage: number) => {
762825 <dt >对象目录</dt >
763826 <dd class =" muted" >{{ upload.folder || 'media' }}</dd >
764827 </div >
828+ <div class =" summary-item" >
829+ <dt >大小上限</dt >
830+ <dd class =" muted" >{{ uploadLimitSummary }}</dd >
831+ </div >
832+ <div class =" summary-item" >
833+ <dt >允许类型</dt >
834+ <dd class =" muted" >{{ uploadMimeSummary }}</dd >
835+ </div >
765836 </dl >
766837
767838 <div v-if =" uploadPreviewSrc" class =" summary-item summary-asset" >
@@ -814,9 +885,8 @@ const goToAssetPage = async (nextPage: number) => {
814885 <div class =" field-grid field-grid-2" >
815886 <label class =" field" >
816887 <span >可见性</span >
817- <select v-model =" upload.visibility" >
818- <option v-for =" visibility in visibilityOptions" :key =" visibility" :value =" visibility" >{{ formatAssetVisibility(visibility) }}</option >
819- </select >
888+ <input value =" 公开" type =" text" readonly />
889+ <small >当前上传链路只支持公开媒体;历史 private 值不再允许新写入。</small >
820890 </label >
821891 <label class =" field" >
822892 <span >{{ uploadHasMultipleFiles ? '统一 Alt 文案(可选)' : 'Alt 文案' }}</span >
@@ -889,6 +959,9 @@ const goToAssetPage = async (nextPage: number) => {
889959 <h3 >访问与路径</h3 >
890960 <div class =" panel-meta" >{{ formatAssetVisibility(form.visibility) }}</div >
891961 </div >
962+ <div v-if =" legacyPrivateVisibility" class =" field-warning" >
963+ 这个媒体记录带有历史 `private` 值,但当前 R2 写入链路并不提供真正的私有访问控制。继续维护前请先改为公开,或等待后续引入私有桶 / 签名 URL。
964+ </div >
892965 <div class =" field-grid field-grid-2 field-grid-compact" >
893966 <label class =" field" >
894967 <span >存储提供方</span >
@@ -963,7 +1036,14 @@ const goToAssetPage = async (nextPage: number) => {
9631036 <label class =" field" >
9641037 <span >可见性</span >
9651038 <select v-model =" form.visibility" >
966- <option v-for =" visibility in visibilityOptions" :key =" visibility" :value =" visibility" >{{ formatAssetVisibility(visibility) }}</option >
1039+ <option
1040+ v-for =" visibility in editorVisibilityOptions"
1041+ :key =" visibility"
1042+ :value =" visibility"
1043+ :disabled =" visibility === 'private'"
1044+ >
1045+ {{ formatAssetVisibilityOption(visibility) }}
1046+ </option >
9671047 </select >
9681048 </label >
9691049 <label class =" field" >
@@ -1005,6 +1085,15 @@ const goToAssetPage = async (nextPage: number) => {
10051085</template >
10061086
10071087<style scoped>
1088+ .field-warning {
1089+ padding : 0.72rem 0.78rem ;
1090+ border : 1px solid rgba (167 , 52 , 32 , 0.18 );
1091+ border-radius : 12px ;
1092+ background : rgba (167 , 52 , 32 , 0.06 );
1093+ color : var (--danger );
1094+ line-height : 1.5 ;
1095+ }
1096+
10081097.asset-table th :first-child ,
10091098.asset-table td :first-child {
10101099 width : 5.4rem ;
0 commit comments