feat: 添加任务状态级联触发器,优化支付和做同款功能
主要更新: - 添加 MySQL 触发器实现 task_status 表到其他表的状态级联 - 移除控制器中的多表状态检查代码 - 完善做同款功能,支持参数传递 - 支付宝 USD 转 CNY 汇率转换 - 修复状态枚举映射问题 注意: 触发器仅在 task_status 更新时触发,部分代码仍直接更新业务表
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="online-users">
|
||||
{{ $t('nav.onlineUsers') }}: <span class="highlight">{{ onlineUsers }}</span>
|
||||
{{ $t('nav.todayVisitors') }}: <span class="highlight">{{ onlineUsers }}</span>
|
||||
</div>
|
||||
<div class="system-uptime">
|
||||
{{ $t('nav.systemUptime') }}: <span class="highlight">{{ systemUptime }}</span>
|
||||
@@ -108,7 +108,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="price">${{ level.price || 0 }}{{ $t('systemSettings.perMonth') }}</p>
|
||||
<p class="description">{{ level.description || $t('systemSettings.includesPoints', { points: level.resourcePoints || 0 }) }}</p>
|
||||
<p class="description">{{ $t('systemSettings.includesPointsPerMonth', { points: level.resourcePoints || level.pointsBonus || 0 }) }}</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<el-button type="primary" @click="editLevel(level)">{{ $t('common.edit') }}</el-button>
|
||||
@@ -360,26 +360,6 @@
|
||||
>
|
||||
<label for="monthly" class="radio-label">{{ $t('systemSettings.monthly') }}</label>
|
||||
</div>
|
||||
<div class="radio-option">
|
||||
<input
|
||||
type="radio"
|
||||
id="quarterly"
|
||||
v-model="editForm.validityPeriod"
|
||||
value="quarterly"
|
||||
class="radio-input"
|
||||
>
|
||||
<label for="quarterly" class="radio-label">{{ $t('systemSettings.quarterly') }}</label>
|
||||
</div>
|
||||
<div class="radio-option">
|
||||
<input
|
||||
type="radio"
|
||||
id="yearly"
|
||||
v-model="editForm.validityPeriod"
|
||||
value="yearly"
|
||||
class="radio-input"
|
||||
>
|
||||
<label for="yearly" class="radio-label">{{ $t('systemSettings.yearly') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -447,9 +427,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
Grid,
|
||||
User,
|
||||
@@ -467,13 +448,14 @@ import { getMembershipLevels, updateMembershipLevel } from '@/api/members'
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 选项卡状态
|
||||
const activeTab = ref('membership')
|
||||
|
||||
// 系统状态数据
|
||||
const onlineUsers = ref('0/500')
|
||||
const systemUptime = ref('加载中...')
|
||||
const onlineUsers = ref('0')
|
||||
const systemUptime = ref(t('nav.loading'))
|
||||
|
||||
// 会员收费标准相关
|
||||
const membershipLevels = ref([])
|
||||
@@ -486,18 +468,18 @@ const editForm = reactive({
|
||||
level: '',
|
||||
price: '',
|
||||
resourcePoints: 0,
|
||||
validityPeriod: 'quarterly'
|
||||
validityPeriod: 'monthly'
|
||||
})
|
||||
|
||||
const editRules = reactive({
|
||||
level: [{ required: true, message: '请选择会员等级', trigger: 'change' }],
|
||||
const editRules = computed(() => ({
|
||||
level: [{ required: true, message: t('systemSettings.selectLevelRequired'), trigger: 'change' }],
|
||||
price: [
|
||||
{ required: true, message: '请输入价格', trigger: 'blur' },
|
||||
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效的数字', trigger: 'blur' }
|
||||
{ required: true, message: t('systemSettings.enterPriceRequired'), trigger: 'blur' },
|
||||
{ pattern: /^\d+(\.\d+)?$/, message: t('systemSettings.enterValidNumber'), trigger: 'blur' }
|
||||
],
|
||||
resourcePoints: [{ required: true, message: '请输入资源点数量', trigger: 'blur' }],
|
||||
validityPeriod: [{ required: true, message: '请选择有效期', trigger: 'change' }]
|
||||
})
|
||||
resourcePoints: [{ required: true, message: t('systemSettings.enterResourcePointsRequired'), trigger: 'blur' }],
|
||||
validityPeriod: [{ required: true, message: t('systemSettings.selectValidityRequired'), trigger: 'change' }]
|
||||
}))
|
||||
|
||||
// 任务清理相关
|
||||
const cleanupStats = ref(null)
|
||||
@@ -512,12 +494,12 @@ const userCleanupForm = reactive({
|
||||
username: ''
|
||||
})
|
||||
|
||||
const userCleanupRules = reactive({
|
||||
const userCleanupRules = computed(() => ({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '用户名长度在2到50个字符', trigger: 'blur' }
|
||||
{ required: true, message: t('systemSettings.enterUsernameRequired'), trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: t('systemSettings.usernameLengthLimit'), trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
}))
|
||||
|
||||
const cleanupConfig = reactive({
|
||||
retentionDays: 30,
|
||||
@@ -595,7 +577,7 @@ const saveEdit = async () => {
|
||||
price: parseFloat(editForm.price),
|
||||
resourcePoints: parseInt(editForm.resourcePoints),
|
||||
pointsBonus: parseInt(editForm.resourcePoints),
|
||||
description: `包含${editForm.resourcePoints}资源点/月`
|
||||
description: t('systemSettings.includesPointsPerMonth', { points: editForm.resourcePoints })
|
||||
}
|
||||
|
||||
await updateMembershipLevel(editForm.id, updateData)
|
||||
@@ -606,17 +588,17 @@ const saveEdit = async () => {
|
||||
membershipLevels.value[index].price = parseFloat(editForm.price)
|
||||
membershipLevels.value[index].pointsBonus = parseInt(editForm.resourcePoints)
|
||||
membershipLevels.value[index].resourcePoints = parseInt(editForm.resourcePoints)
|
||||
membershipLevels.value[index].description = `包含${editForm.resourcePoints}资源点/月`
|
||||
membershipLevels.value[index].description = t('systemSettings.includesPointsPerMonth', { points: editForm.resourcePoints })
|
||||
}
|
||||
|
||||
ElMessage.success('会员等级更新成功')
|
||||
ElMessage.success(t('systemSettings.membershipUpdateSuccess'))
|
||||
editDialogVisible.value = false
|
||||
|
||||
// 重新加载会员等级配置
|
||||
await loadMembershipLevels()
|
||||
} catch (error) {
|
||||
console.error('更新会员等级失败:', error)
|
||||
ElMessage.error('更新会员等级失败: ' + (error.response?.data?.message || error.message))
|
||||
console.error('Update membership level failed:', error)
|
||||
ElMessage.error(t('systemSettings.membershipUpdateFailed') + ': ' + (error.response?.data?.message || error.message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,31 +631,31 @@ const loadMembershipLevels = async () => {
|
||||
price: level.price || 0,
|
||||
resourcePoints: level.pointsBonus || 0,
|
||||
pointsBonus: level.pointsBonus || 0,
|
||||
description: level.description || `包含${level.pointsBonus || 0}资源点/月`
|
||||
description: level.description || t('systemSettings.includesPointsPerMonth', { points: level.pointsBonus || 0 })
|
||||
}))
|
||||
console.log('会员等级配置加载成功:', membershipLevels.value)
|
||||
console.log('Membership levels loaded:', membershipLevels.value)
|
||||
} else {
|
||||
// 如果没有数据,使用默认值
|
||||
console.warn('数据库中没有会员等级数据,使用默认值')
|
||||
console.warn('No membership data in database, using defaults')
|
||||
membershipLevels.value = [
|
||||
{ id: 1, name: '免费版会员', price: 0, resourcePoints: 200, description: '包含200资源点/月' },
|
||||
{ id: 2, name: '标准版会员', price: 59, resourcePoints: 500, description: '包含500资源点/月' },
|
||||
{ id: 3, name: '专业版会员', price: 250, resourcePoints: 2000, description: '包含2000资源点/月' }
|
||||
{ id: 1, name: t('systemSettings.freeMembership'), price: 0, resourcePoints: 200, description: t('systemSettings.includesPointsPerMonth', { points: 200 }) },
|
||||
{ id: 2, name: t('systemSettings.standardMembership'), price: 59, resourcePoints: 500, description: t('systemSettings.includesPointsPerMonth', { points: 500 }) },
|
||||
{ id: 3, name: t('systemSettings.professionalMembership'), price: 250, resourcePoints: 2000, description: t('systemSettings.includesPointsPerMonth', { points: 2000 }) }
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载会员等级配置失败:', error)
|
||||
console.error('错误详情:', error.response?.data || error.message)
|
||||
console.error('Load membership config failed:', error)
|
||||
console.error('Error details:', error.response?.data || error.message)
|
||||
|
||||
// 显示更详细的错误信息
|
||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || '未知错误'
|
||||
ElMessage.warning(`加载会员等级配置失败: ${errorMessage},使用默认配置`)
|
||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('systemSettings.unknown')
|
||||
ElMessage.warning(`${t('systemSettings.loadMembershipFailed')}: ${errorMessage}, ${t('systemSettings.usingDefaultConfig')}`)
|
||||
|
||||
// 使用默认值,确保页面可以正常显示
|
||||
membershipLevels.value = [
|
||||
{ id: 1, name: '免费版会员', price: 0, resourcePoints: 200, description: '包含200资源点/月' },
|
||||
{ id: 2, name: '标准版会员', price: 59, resourcePoints: 500, description: '包含500资源点/月' },
|
||||
{ id: 3, name: '专业版会员', price: 250, resourcePoints: 2000, description: '包含2000资源点/月' }
|
||||
{ id: 1, name: t('systemSettings.freeMembership'), price: 0, resourcePoints: 200, description: t('systemSettings.includesPointsPerMonth', { points: 200 }) },
|
||||
{ id: 2, name: t('systemSettings.standardMembership'), price: 59, resourcePoints: 500, description: t('systemSettings.includesPointsPerMonth', { points: 500 }) },
|
||||
{ id: 3, name: t('systemSettings.professionalMembership'), price: 250, resourcePoints: 2000, description: t('systemSettings.includesPointsPerMonth', { points: 2000 }) }
|
||||
]
|
||||
} finally {
|
||||
loadingLevels.value = false
|
||||
@@ -691,10 +673,10 @@ const refreshStats = async () => {
|
||||
try {
|
||||
const response = await cleanupApi.getCleanupStats()
|
||||
cleanupStats.value = response.data
|
||||
ElMessage.success('统计信息刷新成功')
|
||||
ElMessage.success(t('systemSettings.statsRefreshSuccess'))
|
||||
} catch (error) {
|
||||
console.error('获取统计信息失败:', error)
|
||||
ElMessage.error('获取统计信息失败')
|
||||
console.error('Get statistics failed:', error)
|
||||
ElMessage.error(t('systemSettings.statsRefreshFailed'))
|
||||
} finally {
|
||||
loadingStats.value = false
|
||||
}
|
||||
@@ -705,13 +687,13 @@ const performFullCleanup = async () => {
|
||||
loadingCleanup.value = true
|
||||
try {
|
||||
const response = await cleanupApi.performFullCleanup()
|
||||
ElMessage.success('完整清理执行成功')
|
||||
console.log('清理结果:', response.data)
|
||||
ElMessage.success(t('systemSettings.fullCleanupSuccess'))
|
||||
console.log('Cleanup result:', response.data)
|
||||
// 刷新统计信息
|
||||
await refreshStats()
|
||||
} catch (error) {
|
||||
console.error('执行完整清理失败:', error)
|
||||
ElMessage.error('执行完整清理失败')
|
||||
console.error('Execute full cleanup failed:', error)
|
||||
ElMessage.error(t('systemSettings.fullCleanupFailed'))
|
||||
} finally {
|
||||
loadingCleanup.value = false
|
||||
}
|
||||
@@ -731,15 +713,15 @@ const performUserCleanup = async () => {
|
||||
loadingUserCleanup.value = true
|
||||
try {
|
||||
const response = await cleanupApi.cleanupUserTasks(userCleanupForm.username)
|
||||
ElMessage.success('用户任务清理成功')
|
||||
console.log('清理结果:', response.data)
|
||||
ElMessage.success(t('systemSettings.userCleanupSuccess'))
|
||||
console.log('Cleanup result:', response.data)
|
||||
// 刷新统计信息
|
||||
await refreshStats()
|
||||
// 关闭对话框
|
||||
handleCloseUserCleanupDialog()
|
||||
} catch (error) {
|
||||
console.error('清理用户任务失败:', error)
|
||||
ElMessage.error('清理用户任务失败')
|
||||
console.error('Cleanup user tasks failed:', error)
|
||||
ElMessage.error(t('systemSettings.userCleanupFailed'))
|
||||
} finally {
|
||||
loadingUserCleanup.value = false
|
||||
}
|
||||
@@ -761,17 +743,17 @@ const performUserCleanup_old = async () => {
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json()
|
||||
ElMessage.success(`用户 ${userCleanupForm.username} 的任务清理完成`)
|
||||
console.log('用户清理结果:', result)
|
||||
ElMessage.success(t('systemSettings.userCleanupSuccess'))
|
||||
console.log('User cleanup result:', result)
|
||||
// 关闭对话框并刷新统计信息
|
||||
handleCloseUserCleanupDialog()
|
||||
await refreshStats()
|
||||
} else {
|
||||
ElMessage.error('清理用户任务失败')
|
||||
ElMessage.error(t('systemSettings.userCleanupFailed'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('清理用户任务失败:', error)
|
||||
ElMessage.error('清理用户任务失败')
|
||||
console.error('Cleanup user tasks failed:', error)
|
||||
ElMessage.error(t('systemSettings.userCleanupFailed'))
|
||||
} finally {
|
||||
loadingUserCleanup.value = false
|
||||
}
|
||||
@@ -783,10 +765,10 @@ const saveCleanupConfig = async () => {
|
||||
// 这里可以添加保存配置的API调用
|
||||
// 目前只是模拟保存
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
ElMessage.success('清理配置保存成功')
|
||||
ElMessage.success(t('systemSettings.configSaveSuccess'))
|
||||
} catch (error) {
|
||||
console.error('保存清理配置失败:', error)
|
||||
ElMessage.error('保存清理配置失败')
|
||||
console.error('Save cleanup config failed:', error)
|
||||
ElMessage.error(t('systemSettings.configSaveFailed'))
|
||||
} finally {
|
||||
loadingConfig.value = false
|
||||
}
|
||||
@@ -795,7 +777,11 @@ const saveCleanupConfig = async () => {
|
||||
// 加载AI模型设置
|
||||
const loadAiModelSettings = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/settings')
|
||||
const response = await fetch('/api/admin/settings', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${sessionStorage.getItem('token')}`
|
||||
}
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.promptOptimizationModel) {
|
||||
@@ -812,7 +798,7 @@ const loadAiModelSettings = async () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载AI模型设置失败:', error)
|
||||
console.error('Load AI model settings failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,7 +809,8 @@ const saveAiModelSettings = async () => {
|
||||
const response = await fetch('/api/admin/settings', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${sessionStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
promptOptimizationModel: promptOptimizationModel.value,
|
||||
@@ -833,13 +820,13 @@ const saveAiModelSettings = async () => {
|
||||
})
|
||||
})
|
||||
if (response.ok) {
|
||||
ElMessage.success('AI模型设置保存成功')
|
||||
ElMessage.success(t('systemSettings.aiModelSaveSuccess'))
|
||||
} else {
|
||||
throw new Error('保存失败')
|
||||
throw new Error('Save failed')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存AI模型设置失败:', error)
|
||||
ElMessage.error('保存AI模型设置失败')
|
||||
console.error('Save AI model settings failed:', error)
|
||||
ElMessage.error(t('systemSettings.aiModelSaveFailed'))
|
||||
} finally {
|
||||
savingAiModel.value = false
|
||||
}
|
||||
@@ -853,22 +840,26 @@ onMounted(() => {
|
||||
loadAiModelSettings()
|
||||
})
|
||||
|
||||
// 获取系统统计数据
|
||||
// 获取系统统计数据(当天访问人数和系统运行时间)
|
||||
const fetchSystemStats = async () => {
|
||||
try {
|
||||
// 临时使用计算值,后续可以从API获取
|
||||
// 计算在线用户数(这里简化处理)
|
||||
const randomOnline = Math.floor(Math.random() * 50) + 10
|
||||
onlineUsers.value = `${randomOnline}/500`
|
||||
|
||||
// 计算系统运行时间(基于当前时间简单模拟)
|
||||
const hours = new Date().getHours()
|
||||
const minutes = new Date().getMinutes()
|
||||
systemUptime.value = `${hours}小时${minutes}分`
|
||||
const response = await fetch('/api/admin/online-stats', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${sessionStorage.getItem('token')}`
|
||||
}
|
||||
})
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
onlineUsers.value = data.todayVisitors || 0
|
||||
systemUptime.value = data.uptime || t('systemSettings.unknown')
|
||||
} else {
|
||||
onlineUsers.value = '0'
|
||||
systemUptime.value = t('systemSettings.unknown')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统统计失败:', error)
|
||||
onlineUsers.value = '0/500'
|
||||
systemUptime.value = '未知'
|
||||
console.error('Get online stats failed:', error)
|
||||
onlineUsers.value = '0'
|
||||
systemUptime.value = t('systemSettings.unknown')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -954,15 +945,15 @@ const fetchSystemStats = async () => {
|
||||
|
||||
.online-users,
|
||||
.system-uptime {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
color: #3b82f6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
||||
Reference in New Issue
Block a user