feat: 促销系统完善、发票页重构、前端中文化、订单表迁移及多项优化

前端:

- 发票页完整重构:智能按钮状态、防重复开票、空态引导、订单多选

- 全局中文化:Ant Design Vue locale配置、清除残留英文UI文本

- 新增关于我们、联系我们页面

- 首页活动专区、搜索页、Skill详情等多处优化

后端:

- 订单模块:新增original_amount/promotion_deduct_amount字段及DB迁移

- 促销系统:完善促销规则、过期任务、批量查询等

- 新增RateLimit注解及拦截器、CORS过滤器、Health检查接口

- Logback日志配置、points枚举修复等
This commit is contained in:
Developer
2026-03-23 16:38:13 +08:00
parent 942465b758
commit e7d9f47c61
107 changed files with 2964 additions and 309 deletions

View File

@@ -204,12 +204,34 @@
<div class="form-tip">支持 MP4/MOV/AVI/WEBM 格式最大 100MB也可粘贴网盘链接</div>
<a-progress v-if="videoUploading" :percent="videoProgress" size="small" style="margin-top: 4px" />
</a-form-item>
<a-form-item label="作品链接" name="portfolioUrl">
<a-input v-model:value="form.portfolioUrl" placeholder="请输入个人作品集或GitHub链接选填">
<template #prefix>
<desktop-outlined />
</template>
</a-input>
<a-form-item label="作品" name="portfolioUrl">
<div class="upload-area">
<a-upload
:file-list="portfolioFileList"
:before-upload="beforePortfolioUpload"
:custom-request="handlePortfolioUpload"
:max-count="5"
multiple
accept=".zip,.rar,.7z,.tar.gz,.pdf,.doc,.docx,.ppt,.pptx,.png,.jpg,.jpeg,.gif"
@remove="handlePortfolioRemove"
>
<a-button :loading="portfolioUploading">
<template #icon><upload-outlined /></template>
{{ portfolioUploading ? '上传中...' : '选择作品文件' }}
</a-button>
</a-upload>
<div class="upload-or"></div>
<a-input
v-model:value="form.portfolioUrl"
placeholder="粘贴作品集链接GitHub、网盘等"
:disabled="!!portfolioFileList.length"
style="flex: 1"
>
<template #prefix><link-outlined /></template>
</a-input>
</div>
<div class="form-tip">支持 ZIP/RAR/PDF/图片等格式单文件最大 50MB最多 5 也可粘贴链接</div>
<a-progress v-if="portfolioUploading" :percent="portfolioProgress" size="small" style="margin-top: 4px" />
</a-form-item>
<a-form-item label="期望收益" name="expectedIncome">
@@ -325,12 +347,17 @@ import {
ProjectOutlined,
DollarCircleOutlined,
CheckCircleFilled,
CheckCircleOutlined,
TrophyOutlined,
StarOutlined,
UploadOutlined,
VideoCameraOutlined,
LinkOutlined,
DesktopOutlined
DesktopOutlined,
WalletOutlined,
RiseOutlined,
TeamOutlined,
RightOutlined
} from '@ant-design/icons-vue'
const formRef = ref(null)
@@ -347,6 +374,11 @@ const videoFileList = ref([])
const videoUploading = ref(false)
const videoProgress = ref(0)
// 作品集上传状态
const portfolioFileList = ref([])
const portfolioUploading = ref(false)
const portfolioProgress = ref(0)
const form = reactive({
realName: '',
phone: '',
@@ -488,6 +520,50 @@ const handleVideoRemove = () => {
videoProgress.value = 0
}
// 作品集上传前校验
const beforePortfolioUpload = (file) => {
if (file.size > 50 * 1024 * 1024) {
message.error('单个文件大小不能超过 50MB')
return false
}
return true
}
// 作品集上传处理
const handlePortfolioUpload = async ({ file, onSuccess, onError }) => {
portfolioUploading.value = true
portfolioProgress.value = 0
const progressTimer = setInterval(() => {
if (portfolioProgress.value < 90) portfolioProgress.value += 10
}, 200)
try {
const res = await uploadApi.uploadFile('portfolio', file)
clearInterval(progressTimer)
portfolioProgress.value = 100
const url = res.data?.url || res.data?.fileUrl
portfolioFileList.value = [...portfolioFileList.value, { uid: file.uid, name: file.name, status: 'done', url }]
// 将所有已上传文件URL用逗号拼接存入portfolioUrl
form.portfolioUrl = portfolioFileList.value.map(f => f.url).join(',')
onSuccess(res, file)
message.success('文件上传成功')
} catch (err) {
clearInterval(progressTimer)
onError(err)
message.error('文件上传失败:' + (err.response?.data?.message || err.message))
} finally {
portfolioUploading.value = false
portfolioProgress.value = 0
}
}
// 作品集文件删除
const handlePortfolioRemove = (file) => {
portfolioFileList.value = portfolioFileList.value.filter(f => f.uid !== file.uid)
form.portfolioUrl = portfolioFileList.value.length
? portfolioFileList.value.map(f => f.url).join(',')
: ''
}
const rules = {
realName: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
phone: [
@@ -544,6 +620,8 @@ const resetForm = () => {
resumeProgress.value = 0
videoFileList.value = []
videoProgress.value = 0
portfolioFileList.value = []
portfolioProgress.value = 0
}
</script>