Files
reader-star/apps/admin/src/components/common/oss-image-upload/index.vue
rjl ce5ee2a289 feat(admin): 新增活动管理功能并优化字典组件
- 新增活动管理相关功能,包括活动创建、编辑、删除和状态管理
- 新增 PublishStatus 枚举定义活动发布状态
- 优化字典组件,支持字符串和数字类型的字典值
- 重构业务数据存储逻辑,简化字典数据获取流程
- 新增活动详情页面,支持分组管理和队伍配置
- 集成富文本编辑器用于活动规则编辑
- 优化图片上传组件,支持小图上传模式
- 修复模板管理中的区域选择和分页问题
- 更新 TypeScript 类型定义,完善 API 接口
- 调整主题配置,新增活动状态相关颜色
2026-02-04 18:01:52 +08:00

154 lines
4.7 KiB
Vue

<script setup lang="ts">
import type { UploadCustomRequestOptions } from 'naive-ui'
import { NButton, NSpin, NUpload, useMessage } from 'naive-ui'
import { ref } from 'vue'
import { getAliOssTokenAxios } from '@/service/api/upload'
import { browserPathJoin } from '@/utils/date'
import { initOSSClient, uploadFileToOSS } from '@/utils/oss'
const props = withDefaults(defineProps<{
/** 图片地址 */
modelValue?: string
/** 上传路径 */
path?: string // 上传路径,默认 temp/ 目录,同名文件会覆盖
/** 提示文字 */
hint?: string
/** 最大文件大小 (KB) */
maxSize?: number
/** 接受的文件类型 */
accept?: string
/** 模式: default-大图上传(带文字), mini-小图上传(仅图标) */
variant?: 'default' | 'mini'
}>(), {
hint: '支持 JPG/PNG 格式',
accept: 'image/*',
variant: 'default',
})
const emit = defineEmits(['update:modelValue'])
const loading = ref(false)
const message = useMessage()
async function customRequest({ file, onFinish, onError }: UploadCustomRequestOptions) {
try {
// 校验文件大小
if (props.maxSize && file.file) {
const sizeKB = file.file.size / 1024
if (sizeKB > props.maxSize) {
const maxSizeText = props.maxSize >= 1024 ? `${(props.maxSize / 1024).toFixed(2)}MB` : `${props.maxSize}KB`
throw new Error(`文件大小不能超过 ${maxSizeText}`)
}
}
loading.value = true
// 1. 获取上传凭证
const { data: tokenData, error: tokenError } = await getAliOssTokenAxios()
if (tokenError || !tokenData) {
throw new Error('获取上传凭证失败')
}
// 2. 初始化 OSS 客户端
const client = initOSSClient(tokenData.data || {})
// 3. 准备路径
let path = props.path || `temp/${Date.now()}`
path = browserPathJoin(path, file.name)
// 4. 上传文件
await uploadFileToOSS(client, file.file as File, path)
// 5. 获取 URL
const url = browserPathJoin(import.meta.env.VITE_BASE_OSS_URL, path)
emit('update:modelValue', url)
onFinish()
}
catch (error: any) {
message.error(error.message || '上传失败')
onError()
}
finally {
loading.value = false
}
}
function handleRemove() {
emit('update:modelValue', '')
}
</script>
<template>
<NUpload
:accept="accept"
:show-file-list="false"
:custom-request="customRequest"
class="block"
:class="{ 'w-full': props.variant === 'default' }"
>
<div
class="relative flex flex-col cursor-pointer items-center justify-center overflow-hidden transition-all hover:bg-gray-100"
:class="[
props.variant === 'default' ? 'h-[400px] w-full rounded-3xl bg-[#F5F8FF]' : 'h-full w-full bg-[#F5F8FF] rounded-lg',
!modelValue && props.variant === 'default' ? 'border-2 border-dashed border-gray-300' : '',
!modelValue && props.variant === 'mini' ? 'border border-dashed border-gray-300' : '',
]"
>
<div v-if="loading" class="absolute inset-0 z-50 flex items-center justify-center bg-white/50">
<NSpin :size="props.variant === 'mini' ? 'small' : 'large'" />
</div>
<template v-else>
<div
v-if="modelValue"
class="group absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity hover:opacity-100"
>
<div class="flex gap-4">
<!-- NUpload trigger 会自动处理点击事件 -->
<NButton v-if="props.variant === 'default'" ghost color="#fff" size="small">
更换
</NButton>
<div v-else class="cursor-pointer text-xs text-white">
更换
</div>
<NButton v-if="props.variant === 'default'" ghost color="#fff" size="small" @click.stop="handleRemove">
删除
</NButton>
</div>
</div>
<img
v-if="modelValue"
:src="modelValue"
class="h-full w-full object-cover"
alt="uploaded"
>
<div v-else class="flex flex-col items-center justify-center text-gray-400">
<template v-if="props.variant === 'default'">
<slot name="icon">
<icon-ic-baseline-upload class="mb-4 text-6xl" />
</slot>
<div class="text-lg text-gray-600 font-medium">
点击上传
</div>
<div class="mt-2 text-sm text-gray-400">
{{ hint }}
</div>
</template>
<template v-else>
<icon-ic-baseline-upload class="text-xl" />
</template>
</div>
</template>
</div>
</NUpload>
</template>
<style scoped>
:deep(.n-upload-trigger) {
width: 100%;
height: 100%;
}
</style>