@@ -283,6 +289,59 @@ onUnmounted(() => {
height: 100%;
background: linear-gradient(90deg, #3b82f6, #1d4ed8);
transition: width 0.3s ease;
+ position: relative;
+}
+
+/* 动态进度条动画 */
+.progress-fill.animated {
+ background: linear-gradient(90deg, #3b82f6, #60a5fa, #3b82f6);
+ background-size: 200% 100%;
+ animation: progress-gradient 2s ease infinite, progress-pulse 1.5s ease-in-out infinite;
+}
+
+.progress-fill.animated::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
+ animation: progress-shine 1.5s ease-in-out infinite;
+}
+
+@keyframes progress-gradient {
+ 0% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+ 100% { background-position: 0% 50%; }
+}
+
+@keyframes progress-pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.85; }
+}
+
+@keyframes progress-shine {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
+}
+
+/* 不确定进度条(排队中) */
+.progress-bar.indeterminate {
+ overflow: hidden;
+}
+
+.progress-fill-indeterminate {
+ width: 30%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, #3b82f6, #60a5fa, #3b82f6, transparent);
+ border-radius: 4px;
+ animation: indeterminate-slide 1.5s ease-in-out infinite;
+}
+
+@keyframes indeterminate-slide {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(400%); }
}
.progress-text {
diff --git a/demo/frontend/src/locales/en.js b/demo/frontend/src/locales/en.js
index 1184f8c..0597341 100644
--- a/demo/frontend/src/locales/en.js
+++ b/demo/frontend/src/locales/en.js
@@ -570,7 +570,9 @@ export default {
tasks: 'Task Records',
systemSettings: 'System Settings',
onlineUsers: 'Online Users',
- systemUptime: 'System Uptime'
+ systemUptime: 'System Uptime',
+ todayVisitors: 'Today Visitors',
+ loading: 'Loading...'
},
admin: {
@@ -585,6 +587,7 @@ export default {
dailyActive: 'Daily Active User Trend',
conversionRate: 'User Conversion Rate',
comparedToLastMonth: 'vs last month',
+ comparedToYesterday: 'vs yesterday',
year2025: '2025',
year2024: '2024',
year2023: '2023',
@@ -602,7 +605,8 @@ export default {
month11: 'Nov',
month12: 'Dec',
pleaseLogin: 'Please login first',
- loadDataFailed: 'Failed to load dashboard data'
+ loadDataFailed: 'Failed to load dashboard data',
+ unknownError: 'Unknown error'
},
orders: {
@@ -616,6 +620,7 @@ export default {
operation: 'Operation',
allStatus: 'All Status',
allTypes: 'All Types',
+ allPaymentMethods: 'All Payment Methods',
pending: 'Pending',
confirmed: 'Confirmed',
paid: 'Paid',
@@ -633,7 +638,18 @@ export default {
orderDetail: 'Order Detail',
basicInfo: 'Basic Info',
orderType: 'Order Type',
- paymentInfo: 'Payment Info'
+ paymentInfo: 'Payment Info',
+ confirmDeleteOrder: 'Are you sure to delete order {orderNumber}?',
+ confirmDeleteTitle: 'Confirm Delete',
+ deleteSuccess: 'Deleted successfully',
+ deleteFailed: 'Delete failed',
+ pleaseSelectOrders: 'Please select orders to delete first',
+ confirmBatchDelete: 'Are you sure to delete the selected {count} orders?',
+ batchDeleteTitle: 'Batch Delete',
+ batchDeleteSuccess: 'Batch delete successful',
+ batchDeleteFailed: 'Batch delete failed',
+ loadOrdersFailed: 'Failed to load orders',
+ apiDataFormatError: 'API data format error'
},
tasks: {
@@ -740,6 +756,30 @@ export default {
originalTasksDeleted: 'Original task records will be deleted',
irreversibleWarning: 'This operation is irreversible, please proceed with caution!',
confirmCleanup: 'Confirm Cleanup',
+ selectLevelRequired: 'Please select membership level',
+ enterPriceRequired: 'Please enter price',
+ enterValidNumber: 'Please enter a valid number',
+ enterResourcePointsRequired: 'Please enter resource points',
+ selectValidityRequired: 'Please select validity period',
+ enterUsernameRequired: 'Please enter username',
+ usernameLengthLimit: 'Username must be 2-50 characters',
+ membershipUpdateSuccess: 'Membership level updated successfully',
+ membershipUpdateFailed: 'Failed to update membership level',
+ loadMembershipFailed: 'Failed to load membership configuration',
+ usingDefaultConfig: 'Using default configuration',
+ statsRefreshSuccess: 'Statistics refreshed successfully',
+ statsRefreshFailed: 'Failed to get statistics',
+ fullCleanupSuccess: 'Full cleanup executed successfully',
+ fullCleanupFailed: 'Failed to execute full cleanup',
+ userCleanupSuccess: 'User tasks cleaned up successfully',
+ userCleanupFailed: 'Failed to cleanup user tasks',
+ configSaveSuccess: 'Cleanup configuration saved successfully',
+ configSaveFailed: 'Failed to save cleanup configuration',
+ aiModelSaveSuccess: 'AI model settings saved successfully',
+ aiModelSaveFailed: 'Failed to save AI model settings',
+ aiModelLoadFailed: 'Failed to load AI model settings',
+ includesPointsPerMonth: 'Includes {points} points/month',
+ unknown: 'Unknown',
aiModel: 'AI Model Settings',
promptOptimization: 'Prompt Optimization Settings',
promptOptimizationApiUrl: 'API Endpoint',
diff --git a/demo/frontend/src/locales/zh.js b/demo/frontend/src/locales/zh.js
index 79fd9a0..ab8e293 100644
--- a/demo/frontend/src/locales/zh.js
+++ b/demo/frontend/src/locales/zh.js
@@ -452,6 +452,7 @@ export default {
createSimilarInfo: '基于作品"{title}"创建同款',
goToCreate: '跳转到创作页面',
downloadStart: '开始下载:{title}',
+ downloadComplete: '下载完成',
shareComingSoon: '分享链接功能即将上线',
downloadWithWatermarkStart: '开始下载带水印版本',
downloadWithoutWatermarkStart: '开始下载不带水印版本(会员专享)',
@@ -463,6 +464,7 @@ export default {
bulkDeleteSuccess: '已删除选中项目',
filtersReset: '筛选器已重置',
processing: '生成中...',
+ queuing: '排队中...',
noPreview: '无预览',
videoLoadFailed: '视频加载失败',
videoFileNotExist: '视频文件可能不存在或已被删除',
@@ -583,7 +585,8 @@ export default {
tasks: '生成任务记录',
systemSettings: '系统设置',
onlineUsers: '当前在线用户',
- systemUptime: '系统运行时间'
+ systemUptime: '系统运行时间',
+ todayVisitors: '今日访客'
},
admin: {
@@ -598,6 +601,7 @@ export default {
dailyActive: '日活用户趋势',
conversionRate: '用户转化率',
comparedToLastMonth: '较上月同期',
+ comparedToYesterday: '较昨日',
year2025: '2025年',
year2024: '2024年',
year2023: '2023年',
@@ -713,6 +717,7 @@ export default {
autoCleanup: '自动清理',
perMonth: '/月',
includesPoints: '包含{points}资源点/月',
+ includesPointsPerMonth: '包含{points}资源点/月',
cleanupStatsInfo: '清理统计信息',
refresh: '刷新',
currentTotalTasks: '当前任务总数',
diff --git a/demo/frontend/src/router/index.js b/demo/frontend/src/router/index.js
index c14fde7..5348998 100644
--- a/demo/frontend/src/router/index.js
+++ b/demo/frontend/src/router/index.js
@@ -31,6 +31,8 @@ const TaskStatusPage = () => import('@/views/TaskStatusPage.vue')
const TermsOfService = () => import('@/views/TermsOfService.vue')
const UserAgreement = () => import('@/views/UserAgreement.vue')
const PrivacyPolicy = () => import('@/views/PrivacyPolicy.vue')
+const ChangePassword = () => import('@/views/ChangePassword.vue')
+const SetPassword = () => import('@/views/SetPassword.vue')
const routes = [
{
@@ -212,6 +214,18 @@ const routes = [
component: PrivacyPolicy,
meta: { title: '隐私政策' }
},
+ {
+ path: '/change-password',
+ name: 'ChangePassword',
+ component: ChangePassword,
+ meta: { title: '修改密码', requiresAuth: true }
+ },
+ {
+ path: '/set-password',
+ name: 'SetPassword',
+ component: SetPassword,
+ meta: { title: '设置密码', requiresAuth: true }
+ },
]
const router = createRouter({
diff --git a/demo/frontend/src/stores/user.js b/demo/frontend/src/stores/user.js
index 54fad05..d16f9b4 100644
--- a/demo/frontend/src/stores/user.js
+++ b/demo/frontend/src/stores/user.js
@@ -22,7 +22,8 @@ export const useUserStore = defineStore('user', () => {
// 计算属性
const isAuthenticated = computed(() => !!user.value)
- const isAdmin = computed(() => user.value?.role === 'ROLE_ADMIN')
+ const isAdmin = computed(() => user.value?.role === 'ROLE_ADMIN' || user.value?.role === 'ROLE_SUPER_ADMIN')
+ const isSuperAdmin = computed(() => user.value?.role === 'ROLE_SUPER_ADMIN')
const username = computed(() => user.value?.username || '')
// 可用积分(总积分 - 冻结积分)
@@ -141,6 +142,9 @@ export const useUserStore = defineStore('user', () => {
console.log('恢复用户状态:', user.value?.username, '角色:', user.value?.role)
}
+ // 刷新用户信息(确保角色等信息是最新的)
+ await fetchCurrentUser()
+
} catch (error) {
console.error('Failed to restore user state:', error)
clearUserData()
@@ -158,6 +162,7 @@ export const useUserStore = defineStore('user', () => {
// 计算属性
isAuthenticated,
isAdmin,
+ isSuperAdmin,
username,
availablePoints,
// 方法
diff --git a/demo/frontend/src/views/AdminDashboard.vue b/demo/frontend/src/views/AdminDashboard.vue
index 7ff30f0..09e7563 100644
--- a/demo/frontend/src/views/AdminDashboard.vue
+++ b/demo/frontend/src/views/AdminDashboard.vue
@@ -35,7 +35,7 @@
@@ -151,6 +151,7 @@
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
+import { useI18n } from 'vue-i18n'
import {
Grid,
User,
@@ -167,6 +168,7 @@ import { getDashboardOverview, getConversionRate, getDailyActiveUsersTrend, getS
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
const router = useRouter()
+const { t } = useI18n()
// 年份选择
const selectedYear = ref('2025')
@@ -186,7 +188,7 @@ const loading = ref(false)
// 系统状态数据
const onlineUsers = ref('0/500')
-const systemUptime = ref('加载中...')
+const systemUptime = ref(t('nav.loading'))
// 图表相关
const dailyActiveChart = ref(null)
@@ -272,21 +274,19 @@ const loadDashboardData = async () => {
totalUsers: data.totalUsers || 0,
paidUsers: data.paidUsers || 0,
todayRevenue: data.todayRevenue || 0,
- // 暂时使用固定值,后续可以从API获取同比数据
- // TODO: 后端需要添加计算同比变化的逻辑
- totalUsersChange: 0, // 暂时设为0,等待后端实现
- paidUsersChange: 0, // 暂时设为0,等待后端实现
- todayRevenueChange: 0 // 暂时设为0,等待后端实现
+ totalUsersChange: data.totalUsersChange ?? 0,
+ paidUsersChange: data.paidUsersChange ?? 0,
+ todayRevenueChange: data.todayRevenueChange ?? 0
}
console.log('设置后的统计数据:', stats.value)
} else {
- console.error('获取仪表盘数据失败:', data.error || data.message)
- ElMessage.error('获取仪表盘数据失败: ' + (data.message || '未知错误'))
+ console.error('Get dashboard data failed:', data.error || data.message)
+ ElMessage.error(t('dashboard.loadDataFailed') + ': ' + (data.message || t('dashboard.unknownError')))
}
} catch (error) {
- console.error('加载仪表盘数据失败:', error)
- ElMessage.error('加载仪表盘数据失败: ' + (error.message || '未知错误'))
+ console.error('Load dashboard data failed:', error)
+ ElMessage.error(t('dashboard.loadDataFailed') + ': ' + (error.message || t('dashboard.unknownError')))
} finally {
loading.value = false
}
@@ -488,44 +488,26 @@ onMounted(async () => {
}, 30000)
})
-// 获取系统统计数据
+// 获取系统统计数据(当天访问人数和系统运行时间)
const fetchSystemStats = async () => {
try {
- const response = await getSystemStatus()
- console.log('系统状态API返回:', response)
-
- if (response && response.data && response.data.success) {
- const data = response.data.data
- console.log('系统状态数据:', data)
-
- // 设置在线用户数
- const currentOnline = data.onlineUsers || 0
- const maxOnline = data.maxUsers || 500
- onlineUsers.value = `${currentOnline}/${maxOnline}`
- console.log(`在线用户数已更新: ${onlineUsers.value}`)
-
- // 设置系统运行时间
- if (data.uptime) {
- // 假设后端返回的是秒数,转换为小时和分钟
- const totalSeconds = data.uptime
- const hours = Math.floor(totalSeconds / 3600)
- const minutes = Math.floor((totalSeconds % 3600) / 60)
- systemUptime.value = `${hours}小时${minutes}分`
- } else if (data.uptimeFormatted) {
- // 或者后端直接返回格式化的字符串
- systemUptime.value = data.uptimeFormatted
- } else {
- systemUptime.value = '未知'
+ const response = await fetch('/api/admin/online-stats', {
+ headers: {
+ 'Authorization': `Bearer ${sessionStorage.getItem('token')}`
}
- console.log(`系统运行时间已更新: ${systemUptime.value}`)
+ })
+ const data = await response.json()
+ if (data.success) {
+ onlineUsers.value = data.todayVisitors || 0
+ systemUptime.value = data.uptime || t('systemSettings.unknown')
} else {
- console.warn('系统状态API返回格式不正确:', response)
- throw new Error('获取系统状态失败')
+ 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')
}
}
diff --git a/demo/frontend/src/views/AdminOrders.vue b/demo/frontend/src/views/AdminOrders.vue
index 6d70364..f428e73 100644
--- a/demo/frontend/src/views/AdminOrders.vue
+++ b/demo/frontend/src/views/AdminOrders.vue
@@ -42,8 +42,8 @@
{{ $t('nav.systemUptime') }}: {{ systemUptime }}
@@ -93,11 +93,7 @@
-
-
-
-
@@ -290,6 +286,7 @@
import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
+import { useI18n } from 'vue-i18n'
import {
Grid,
User,
@@ -310,6 +307,7 @@ import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
const router = useRouter()
const route = useRoute()
+const { t } = useI18n()
// 判断是否为管理员模式(基于路由路径)
const isAdminMode = computed(() => route.path.includes('/admin/'))
@@ -324,7 +322,7 @@ const totalOrders = ref(0)
// 系统状态数据
const onlineUsers = ref('0/500')
-const systemUptime = ref('加载中...')
+const systemUptime = ref(t('nav.loading'))
// 筛选条件
const filters = reactive({
@@ -513,17 +511,17 @@ const viewOrder = async (order) => {
const deleteOrder = async (order) => {
try {
await ElMessageBox.confirm(
- `确定要删除订单 ${order.orderNumber || order.id} 吗?`,
- '确认删除',
+ t('orders.confirmDeleteOrder', { orderNumber: order.orderNumber || order.id }),
+ t('orders.confirmDeleteTitle'),
{
- confirmButtonText: '确定',
- cancelButtonText: '取消',
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
type: 'warning',
}
)
const response = await deleteOrderAPI(order.id)
- console.log('删除订单响应:', response)
+ console.log('Delete order response:', response)
// 检查响应状态
if (response.data?.success) {
@@ -532,51 +530,51 @@ const deleteOrder = async (order) => {
orders.value.splice(index, 1)
totalOrders.value--
}
- ElMessage.success('删除成功')
+ ElMessage.success(t('orders.deleteSuccess'))
} else {
- ElMessage.error(response.data?.message || '删除失败')
+ ElMessage.error(response.data?.message || t('orders.deleteFailed'))
}
} catch (error) {
if (error !== 'cancel') {
- console.error('删除失败:', error)
- ElMessage.error('删除失败: ' + (error.message || '未知错误'))
+ console.error('Delete failed:', error)
+ ElMessage.error(t('orders.deleteFailed') + ': ' + (error.message || t('dashboard.unknownError')))
}
}
}
const deleteSelected = async () => {
if (selectedOrders.value.length === 0) {
- ElMessage.warning('请先选择要删除的订单')
+ ElMessage.warning(t('orders.pleaseSelectOrders'))
return
}
try {
await ElMessageBox.confirm(
- `确定要删除选中的 ${selectedOrders.value.length} 个订单吗?`,
- '批量删除',
+ t('orders.confirmBatchDelete', { count: selectedOrders.value.length }),
+ t('orders.batchDeleteTitle'),
{
- confirmButtonText: '确定',
- cancelButtonText: '取消',
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
type: 'warning',
}
)
const ids = selectedOrders.value.map(o => o.id)
const response = await deleteOrders(ids)
- console.log('批量删除订单响应:', response)
+ console.log('Batch delete orders response:', response)
if (response.data?.success) {
orders.value = orders.value.filter(o => !ids.includes(o.id))
totalOrders.value -= response.data?.deletedCount || ids.length
selectedOrders.value = []
- ElMessage.success(response.data?.message || '批量删除成功')
+ ElMessage.success(response.data?.message || t('orders.batchDeleteSuccess'))
} else {
- ElMessage.error(response.data?.message || '批量删除失败')
+ ElMessage.error(response.data?.message || t('orders.batchDeleteFailed'))
}
} catch (error) {
if (error !== 'cancel') {
- console.error('批量删除失败:', error)
- ElMessage.error('批量删除失败: ' + (error.message || '未知错误'))
+ console.error('Batch delete failed:', error)
+ ElMessage.error(t('orders.batchDeleteFailed') + ': ' + (error.message || t('dashboard.unknownError')))
}
}
}
@@ -615,8 +613,8 @@ const fetchOrders = async () => {
orders.value = pageData
totalOrders.value = pageData.length
} else {
- console.error('API返回数据格式错误: data不是Page对象也不是数组', pageData)
- ElMessage.error('API返回数据格式错误')
+ console.error('API data format error: data is not Page object or array', pageData)
+ ElMessage.error(t('orders.apiDataFormatError'))
}
} else if (responseData.content) {
// 直接返回Page对象(没有success包装)
@@ -627,13 +625,13 @@ const fetchOrders = async () => {
orders.value = responseData.list || []
totalOrders.value = responseData.total || 0
} else {
- console.error('API返回数据格式错误:', responseData)
- ElMessage.error('API返回数据格式错误')
+ console.error('API data format error:', responseData)
+ ElMessage.error(t('orders.apiDataFormatError'))
}
} catch (error) {
- console.error('获取订单列表失败:', error)
- ElMessage.error('获取订单列表失败: ' + (error.message || '未知错误'))
+ console.error('Get orders list failed:', error)
+ ElMessage.error(t('orders.loadOrdersFailed') + ': ' + (error.message || t('dashboard.unknownError')))
} finally {
loading.value = false
}
@@ -651,22 +649,26 @@ onMounted(() => {
fetchSystemStats()
})
-// 获取系统统计数据
+// 获取系统统计数据(当天访问人数和系统运行时间)
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')
}
}
@@ -899,6 +901,39 @@ const fetchSystemStats = async () => {
gap: 16px;
}
+/* 确保下拉框选中值可见 */
+.toolbar-left :deep(.el-select) {
+ min-width: 120px;
+}
+
+.toolbar-left :deep(.el-select__wrapper) {
+ color: #374151 !important;
+ font-size: 14px;
+ min-height: 32px;
+}
+
+.toolbar-left :deep(.el-select__wrapper *) {
+ color: #374151 !important;
+}
+
+.toolbar-left :deep(.el-select__selection) {
+ display: flex !important;
+ align-items: center !important;
+}
+
+.toolbar-left :deep(.el-select__selected-item) {
+ color: #374151 !important;
+ display: inline-flex !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+}
+
+.toolbar-left :deep(.el-select__placeholder) {
+ color: #9ca3af !important;
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
.table-container {
background: white;
border-radius: 8px;
diff --git a/demo/frontend/src/views/ApiManagement.vue b/demo/frontend/src/views/ApiManagement.vue
index 26b3dcf..401f71f 100644
--- a/demo/frontend/src/views/ApiManagement.vue
+++ b/demo/frontend/src/views/ApiManagement.vue
@@ -33,7 +33,7 @@