diff --git a/.env b/.env index 0bca3fa..8706e2e 100644 --- a/.env +++ b/.env @@ -1,3 +1,9 @@ +# 与主站同仓;/sub/auth/* 子账号登注册不依赖本开关。为 true 时根路径进 /sub/auth/login、侧栏为子账号受限菜单 +VITE_IS_SUB_PORTAL=false VITE_API_URL="https://api.tianyuanapi.com" VITE_CAPTCHA_SCENE_ID="wynt39to" -VITE_CAPTCHA_ENCRYPTED_MODE=false \ No newline at end of file +VITE_CAPTCHA_ENCRYPTED_MODE=false +# 子账号专属前端域名(生产建议配置,如 https://subsole.tianyuanapi.com) +VITE_SUB_PORTAL_BASE_URL="" +# 主控制台前端域名(用于从子域回跳主域,如 https://console.tianyuanapi.com) +VITE_MAIN_PORTAL_BASE_URL="" \ No newline at end of file diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 55a19f9..666770a 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -366,7 +366,6 @@ "useThrottleFn": true, "useThrottledRefHistory": true, "useTimeAgo": true, - "useTimeAgoIntl": true, "useTimeout": true, "useTimeoutFn": true, "useTimeoutPoll": true, diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 516d1ce..1af4034 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -817,7 +817,6 @@ declare module 'vue' { readonly useThrottleFn: UnwrapRef readonly useThrottledRefHistory: UnwrapRef readonly useTimeAgo: UnwrapRef - readonly useTimeAgoIntl: UnwrapRef readonly useTimeout: UnwrapRef readonly useTimeoutFn: UnwrapRef readonly useTimeoutPoll: UnwrapRef diff --git a/components.d.ts b/components.d.ts index 06a2b1e..2bdf440 100644 --- a/components.d.ts +++ b/components.d.ts @@ -58,6 +58,7 @@ declare module 'vue' { ElSegmented: typeof import('element-plus/es')['ElSegmented'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] + ElSpace: typeof import('element-plus/es')['ElSpace'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] diff --git a/src/api/index.js b/src/api/index.js index 2f4b01e..c0c360b 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -40,6 +40,20 @@ export const userApi = { getUserStats: () => request.get('/users/admin/stats') } +export const subPortalApi = { + register: (data) => request.post('/sub-portal/register', data) +} + +export const subordinateApi = { + createInvitation: (data) => request.post('/subordinate/invitations', data || {}), + listSubordinates: (params) => request.get('/subordinate/subordinates', { params }), + allocate: (data) => request.post('/subordinate/allocate', data), + listAllocations: (params) => request.get('/subordinate/allocations', { params }), + assignSubscription: (data) => request.post('/subordinate/assign-subscription', data), + listChildSubscriptions: (params) => request.get('/subordinate/child-subscriptions', { params }), + removeChildSubscription: (subscriptionId, data) => request.delete(`/subordinate/child-subscriptions/${subscriptionId}`, { data }) +} + // 验证码(阿里云滑块)相关接口 export const captchaApi = { // 获取加密场景 ID,用于前端加密模式初始化滑块 @@ -145,8 +159,8 @@ export const financeApi = { getAlipayOrderStatus: (params) => request.get('/finance/wallet/alipay-order-status', { params }), // 管理员充值功能 - transferRecharge: (data) => request.post('/admin/finance/transfer-recharge', data), - giftRecharge: (data) => request.post('/admin/finance/gift-recharge', data), + adminTransferRecharge: (data) => request.post('/admin/finance/transfer-recharge', data), + adminGiftRecharge: (data) => request.post('/admin/finance/gift-recharge', data), // 充值记录相关接口 getUserRechargeRecords: (params) => request.get('/finance/wallet/recharge-records', { params }), diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..40beb05 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg deleted file mode 100644 index 7565660..0000000 --- a/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/styles/auth.css b/src/assets/styles/auth.css index 63dd2ce..3f8ea3e 100644 --- a/src/assets/styles/auth.css +++ b/src/assets/styles/auth.css @@ -2,33 +2,36 @@ /* 输入框样式优化 */ .auth-input :deep(.el-input__wrapper) { - border-radius: 8px !important; + border-radius: 10px !important; transition: all 0.3s ease !important; - border: 1px solid #d1d5db !important; + border: 1px solid #d5deef !important; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04) !important; + min-height: 44px !important; } .auth-input :deep(.el-input__wrapper:hover) { - border-color: #3b82f6 !important; + border-color: #7aa9f8 !important; } .auth-input :deep(.el-input__wrapper.is-focus) { - border-color: #3b82f6 !important; - box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important; + border-color: #2563eb !important; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.13) !important; } /* 按钮样式优化 */ .auth-button :deep(.el-button--primary) { - border-radius: 8px !important; - font-weight: 500 !important; + border-radius: 10px !important; + font-weight: 600 !important; transition: all 0.3s ease !important; - background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%) !important; + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 55%, #1e40af 100%) !important; border: none !important; + box-shadow: 0 8px 18px rgba(37, 99, 235, 0.25) !important; } .auth-button :deep(.el-button--primary:hover) { transform: translateY(-1px) !important; - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important; - background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%) !important; + box-shadow: 0 12px 24px rgba(37, 99, 235, 0.32) !important; + background: linear-gradient(135deg, #1d4ed8 0%, #1e40af 100%) !important; } .auth-button :deep(.el-button--primary:active) { @@ -36,19 +39,61 @@ } /* Radio button 样式优化 */ +.auth-method-tabs { + border-radius: 12px; + padding: 4px; + background: linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%); + border: 1px solid #e2e8f0; + display: flex !important; + width: 100%; +} + +.auth-method-tab { + flex: 1; +} + +.auth-method-tabs :deep(.el-radio-button) { + flex: 1; + display: block; +} + +.auth-method-tabs :deep(.el-radio-button__inner) { + width: 100%; + border-left: none !important; +} + +.auth-method-tabs :deep(.el-radio-button:first-child .el-radio-button__inner) { + border-radius: 10px !important; +} + +.auth-method-tabs :deep(.el-radio-button:last-child .el-radio-button__inner) { + border-radius: 10px !important; +} + +.auth-method-tabs :deep(.el-radio-button.is-active .el-radio-button__inner) { + border-color: transparent !important; +} + .auth-radio :deep(.el-radio-button__inner) { + width: 100% !important; border: none !important; + border-radius: 10px !important; background: transparent !important; - color: #6b7280 !important; - font-weight: 500 !important; + color: #475569 !important; + font-weight: 600 !important; transition: all 0.3s ease !important; - padding: 12px 16px !important; + padding: 10px 14px !important; + box-shadow: none !important; } .auth-radio :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { - background: white !important; - color: #3b82f6 !important; - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) !important; + background: #ffffff !important; + color: #0f172a !important; + box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12) !important; +} + +.auth-radio :deep(.el-radio-button__inner:hover) { + color: #0f172a !important; } /* 表单标签样式 */ @@ -58,21 +103,21 @@ /* 链接样式 */ .auth-link { - @apply text-gray-600 hover:text-sky-600 transition-colors mx-2; + @apply text-slate-500 hover:text-blue-700 transition-colors mx-2; } /* 卡片样式 */ .auth-card { - @apply bg-white/95 backdrop-blur-sm shadow-2xl rounded-2xl border border-white/20; + @apply bg-white/96 shadow-2xl rounded-2xl border border-white/40; } /* 标题样式 */ .auth-title { - @apply text-2xl font-bold text-gray-900 mb-2; + @apply text-2xl font-bold text-slate-900 mb-2; } .auth-subtitle { - @apply text-gray-600 text-sm; + @apply text-slate-500 text-sm; } /* 响应式优化 */ diff --git a/src/components/layout/AppHeader.vue b/src/components/layout/AppHeader.vue index 73581c0..8d4b209 100644 --- a/src/components/layout/AppHeader.vue +++ b/src/components/layout/AppHeader.vue @@ -62,7 +62,7 @@ 个人中心 - + @@ -93,7 +93,7 @@ import { UserIcon as User } from '@heroicons/vue/24/outline' -const props = defineProps({ +defineProps({ title: { type: String, required: true @@ -115,11 +115,10 @@ const props = defineProps({ const emit = defineEmits(['user-command']) -const router = useRouter() const userStore = useUserStore() const appStore = useAppStore() -const showNotifications = ref(false) +const showHomeEntry = computed(() => userStore.accountKind !== 'subordinate') // 处理用户菜单命令 const handleUserCommand = async (command) => { diff --git a/src/components/layout/AppSidebar.vue b/src/components/layout/AppSidebar.vue index e500ba4..d87d811 100644 --- a/src/components/layout/AppSidebar.vue +++ b/src/components/layout/AppSidebar.vue @@ -28,7 +28,7 @@ -