diff --git a/src/api/prison/ai-dash-entry/index.ts b/src/api/prison/ai-dash-entry/index.ts index 570af494..f988af11 100644 --- a/src/api/prison/ai-dash-entry/index.ts +++ b/src/api/prison/ai-dash-entry/index.ts @@ -1,4 +1,5 @@ import request from '@/config/axios' +import { createAccessToken } from '@/api/system/oauth2/token' /** 风险分布数据项 */ export interface RiskDistributionVO { @@ -57,15 +58,68 @@ export interface FocusPersonPageReqVO { areaId?: number } +// Token 缓存 +let cachedToken: string | null = null +let tokenExpireTime: number = 0 + +/** + * 获取并缓存 access token + */ +const getAuthToken = async (): Promise => { + const now = Date.now() + + // 如果 token 还未过期,直接返回缓存的 token + if (cachedToken && tokenExpireTime > now) { + return cachedToken + } + + // 调用 token 接口获取新的 token + const tokenData = await createAccessToken( + { + grant_type: 'password', + username: 'andp', + password: 'an123', + scope: 'prison:ai-dash-entry:query' + }, + 1, // 租户ID + 'android-app', + 'android-secret-key-2024' + ) + + // 缓存 token,设置为过期时间前5分钟失效 + cachedToken = tokenData.access_token + tokenExpireTime = Date.now() + (tokenData.expires_in - 5 * 60) * 1000 + + return cachedToken +} + +/** + * 带认证的请求方法 + */ +const authRequest = async (options: { url: string; params?: any }): Promise => { + const token = await getAuthToken() + return await request.get({ + ...options, + headers: { + Authorization: `Bearer ${token}` + } + }) +} + /** AI心航360° API */ export const AiDashEntryApi = { /** 获取AI心航360°统计数据 */ getStatistics: async (): Promise => { - return await request.get({ url: '/prison/dashboard/ai-dash-entry/statistics' }) + return await authRequest({ + url: '/prison/dashboard/ai-dash-entry/statistics' + }) }, /** 获取重点关注对象分页列表 */ getFocusPersonPage: async (params: FocusPersonPageReqVO) => { - return await request.get({ url: '/prison/dashboard/ai-dash-entry/focus-person-page', params }) + return await authRequest({ + url: '/prison/dashboard/ai-dash-entry/focus-person-page', + params + }) } } diff --git a/src/api/prison/dashboard/index.ts b/src/api/prison/dashboard/index.ts index 169fe3c0..ce70c81a 100644 --- a/src/api/prison/dashboard/index.ts +++ b/src/api/prison/dashboard/index.ts @@ -1,4 +1,53 @@ import request from '@/config/axios' +import { createAccessToken } from '@/api/system/oauth2/token' + +// Token 缓存 +let cachedToken: string | null = null +let tokenExpireTime: number = 0 + +/** + * 获取并缓存 access token + */ +const getAuthToken = async (): Promise => { + const now = Date.now() + + // 如果 token 还未过期,直接返回缓存的 token + if (cachedToken && tokenExpireTime > now) { + return cachedToken + } + + // 调用 token 接口获取新的 token + const tokenData = await createAccessToken( + { + grant_type: 'password', + username: 'andp', + password: 'an123', + scope: 'prison:ai-dash-entry:query' + }, + 1, // 租户ID + 'android-app', + 'android-secret-key-2024' + ) + + // 缓存 token,设置为过期时间前5分钟失效 + cachedToken = tokenData.access_token + tokenExpireTime = Date.now() + (tokenData.expires_in - 5 * 60) * 1000 + + return cachedToken +} + +/** + * 带认证的请求方法 + */ +const authRequest = async (options: { url: string; params?: any }): Promise => { + const token = await getAuthToken() + return await request.get({ + ...options, + headers: { + Authorization: `Bearer ${token}` + } + }) +} /** 看板统计响应 */ export interface DashboardStatisticsVO { @@ -219,6 +268,9 @@ export const DashboardApi = { // 获取罪犯Dashboard统计信息 getPrisonerStats: async (prisonerId: number): Promise => { - return await request.get({ url: '/prison/dashboard/prisoner-stats', params: { prisonerId } }) + return await authRequest({ + url: '/prison/dashboard/prisoner-stats', + params: { prisonerId } + }) } } diff --git a/src/api/system/oauth2/token.ts b/src/api/system/oauth2/token.ts index ac89ae89..413d1d33 100644 --- a/src/api/system/oauth2/token.ts +++ b/src/api/system/oauth2/token.ts @@ -1,14 +1,27 @@ import request from '@/config/axios' +import axios from 'axios' +import { config } from '@/config/axios/config' export interface OAuth2TokenVO { id: number - accessToken: string - refreshToken: string - userId: number - userType: number - clientId: string - createTime: Date - expiresTime: Date + access_token: string + refresh_token: string + user_id: number + user_type: number + client_id: string + create_time: Date + expires_in: number // 过期时间(秒) + token_type: string +} + +/** OAuth2 token 请求参数 */ +export interface OAuth2TokenReqVO { + grant_type: string + username: string + password: string + scope?: string + client_id?: string + client_secret?: string } // 查询 token列表 @@ -20,3 +33,43 @@ export const getAccessTokenPage = (params: PageParam) => { export const deleteAccessToken = (accessToken: string) => { return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken }) } + +/** + * 获取OAuth2访问令牌 + * 使用密码模式获取token,需要Basic认证 + * @param params token请求参数 + * @param tenantId 租户ID + * @param clientId 客户端ID(可选,默认使用配置的clientId) + * @param clientSecret 客户端密钥(可选) + */ +export const createAccessToken = async ( + params: OAuth2TokenReqVO, + tenantId: string | number, + clientId?: string, + clientSecret?: string +): Promise => { + const { base_url } = config + + // 构建Basic认证头 + const authHeader = btoa(`${clientId || 'android-app'}:${clientSecret || 'android-secret-key-2024'}`) + + // 发送请求 + const response = await axios.post( + `${base_url}/system/oauth2/token`, + new URLSearchParams({ + grant_type: params.grant_type, + username: params.username, + password: params.password, + scope: params.scope || 'prison:ai-dash-entry:query' + }), + { + headers: { + Authorization: `Basic ${authHeader}`, + 'Tenant-ID': String(tenantId), + 'Content-Type': 'application/x-www-form-urlencoded' + } + } + ) + + return response.data.data +} diff --git a/src/permission.ts b/src/permission.ts index b087307f..7235fddd 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -54,7 +54,7 @@ const whiteList = [ '/bind', '/register', '/oauthLogin/gitee', - '/dashboard', // Dashboard 页面 + '/prisoner/prisoner/dashboard', // Dashboard 页面 '/ai-dash-entry' // DashEntry 页面 ] @@ -62,6 +62,10 @@ const whiteList = [ router.beforeEach(async (to, from, next) => { start() loadStart() + if (to.path === '/prisoner/prisoner/dashboard' || to.path === '/ai-dash-entry') { + next() + return + } if (getAccessToken()) { if (to.path === '/login') { next({ path: '/' }) diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index f3eebca5..5986e4a6 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -185,7 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [ noTagsView: true } }, - + { + path: '/ai-dash-entry', + component: () => import('@/views/DashEntry/DashEntry.vue'), + name: 'aiDashEntry', + meta: { + hidden: true, + title: '画像入口', + noTagsView: true + } + }, { path: '/login', component: () => import('@/views/Login/Login.vue'), @@ -758,9 +767,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ component: () => import('@/views/iot/ota/firmware/detail/index.vue') } ] - }, - - + } ] export default remainingRouter diff --git a/src/views/Dashboard/Index.vue b/src/views/Dashboard/Index.vue index 55e8b203..6a0016f8 100644 --- a/src/views/Dashboard/Index.vue +++ b/src/views/Dashboard/Index.vue @@ -85,7 +85,7 @@
大帐统计
- +
@@ -640,7 +640,7 @@ onUnmounted(() => { font-size: 18px; font-weight: bold; color: #ffffff; - margin-bottom: 12px; + margin-bottom: 10px; } } diff --git a/src/views/Dashboard/components/BarChart.vue b/src/views/Dashboard/components/BarChart.vue index 97b541d8..367b4865 100644 --- a/src/views/Dashboard/components/BarChart.vue +++ b/src/views/Dashboard/components/BarChart.vue @@ -269,26 +269,25 @@ watch( .chart-cards { display: flex; justify-content: space-around; - margin-bottom: 15px; flex-shrink: 0; } .chart-card-item { text-align: center; - padding: 15px 25px; + padding: 4px; background: rgba(56, 102, 141, 0.3); border-radius: 8px; min-width: 100px; .card-value { - font-size: 28px; + font-size: 14px; font-weight: bold; color: #00d4ff; - margin-bottom: 8px; + margin-bottom: 4px; } .card-label { - font-size: 16px; + font-size: 10px; color: rgba(255, 255, 255, 0.8); } } diff --git a/src/views/Dashboard/components/ConsumptionRecords/Index.vue b/src/views/Dashboard/components/ConsumptionRecords/Index.vue index dbb86c19..6382eacd 100644 --- a/src/views/Dashboard/components/ConsumptionRecords/Index.vue +++ b/src/views/Dashboard/components/ConsumptionRecords/Index.vue @@ -155,7 +155,7 @@ watch( .consumption-records-title { text-align: center; - font-size: 16px; + font-size: 14px; font-weight: bold; color: #ffffff; } @@ -218,13 +218,14 @@ watch( } .record-date { - font-size: 12px; + font-size: 10px; color: rgba(255, 255, 255, 0.9); } .record-name { - font-size: 12px; + font-size: 10px; font-weight: 500; + color: #a855f7; &.name-purple { color: #a855f7; @@ -252,12 +253,12 @@ watch( } .record-category { - font-size: 12px; + font-size: 10px; color: rgba(255, 255, 255, 0.9); } .record-amount { - font-size: 12px; + font-size: 10px; font-weight: 500; color: white; text-align: right; @@ -268,6 +269,7 @@ watch( } .relationship-item { background: #422b1f; + color: #ffa500; padding: 6px 12px; border-radius: 6px; display: flex; @@ -295,9 +297,10 @@ watch( align-items: center; flex-shrink: 0; margin-top: 2px; + color: #ffa500; svg { - width: 14px; - height: 16px; + width: 12px; + height: 14px; } &.icon-orange { color: #ffa500; @@ -314,11 +317,11 @@ watch( } .relationship-name { - font-size: 13px; + font-size: 11px; font-weight: 500; } .relationship-relate { - font-size: 12px; + font-size: 11px; opacity: 0.8; } diff --git a/src/views/Dashboard/components/InfoCard/Index.vue b/src/views/Dashboard/components/InfoCard/Index.vue index 15b2f197..9ac19329 100644 --- a/src/views/Dashboard/components/InfoCard/Index.vue +++ b/src/views/Dashboard/components/InfoCard/Index.vue @@ -168,7 +168,7 @@ const props = withDefaults( } .header-title { - font-size: 16px; + font-size: 14px; margin-right: 6px; color: white; font-weight: bold; @@ -190,11 +190,11 @@ const props = withDefaults( } .info-tag { - padding: 6px 10px; + padding: 4px 10px; background: #3f6973; border: 1px solid rgba(56, 102, 141, 0.5); border-radius: 4px; - font-size: 12px; + font-size: 10px; color: #d8f0ff; white-space: nowrap; } @@ -206,7 +206,7 @@ const props = withDefaults( } .records-content-title { - font-size: 14px; + font-size: 10px; color: #d8f0ff; font-weight: bold; } @@ -214,13 +214,13 @@ const props = withDefaults( .info-list { display: flex; flex-direction: column; - gap: 6px; + gap: 4px; } .info-item { display: flex; align-items: center; - font-size: 13px; + font-size: 10px; color: white; } @@ -239,7 +239,7 @@ const props = withDefaults( display: flex; flex-direction: column; gap: 2px; - height: 150px; + height: 162px; overflow-y: auto; &::-webkit-scrollbar { display: none; @@ -283,14 +283,14 @@ const props = withDefaults( } .record-date { - font-size: 12px; + font-size: 10px; font-weight: 600; color: white; margin-bottom: 2px; } .record-text { - font-size: 12px; + font-size: 10px; color: #d8f0ff; line-height: 1.5; } diff --git a/src/views/Dashboard/components/RecentRewardsPunishments/Index.vue b/src/views/Dashboard/components/RecentRewardsPunishments/Index.vue index fa6d509d..0abe64e2 100644 --- a/src/views/Dashboard/components/RecentRewardsPunishments/Index.vue +++ b/src/views/Dashboard/components/RecentRewardsPunishments/Index.vue @@ -41,7 +41,7 @@ defineOptions({ name: 'RecentRewardsPunishments' }) interface RewardPunishmentItem { date?: string // 日期(可选) - type: 'reward' | 'punishment' + type: 'reward' | 'danger' typeText: string // 类型文本(表扬奖励/警告等) content: string // 内容 } @@ -65,7 +65,7 @@ const filteredList = computed(() => { } else if (activeFilter.value === 'reward') { return listData.value.filter((item) => item.type === 'reward') } else { - return listData.value.filter((item) => item.type === 'punishment') + return listData.value.filter((item) => item.type === 'danger') } }) @@ -179,14 +179,13 @@ watch( // 时间线标记点 .timeline-dot { position: absolute; - left: -6px; + left: -8px; top: 50%; transform: translateY(-50%); width: 10px; height: 10px; border-radius: 50%; - border: 2px solid rgba(13, 30, 50, 0.8); - background: rgba(13, 30, 50, 0.8); + background: rgba(3, 173, 252, 0.8); z-index: 1; &.reward { @@ -207,7 +206,7 @@ watch( border: 1px solid rgba(56, 102, 141, 0.3); border-radius: 4px; padding: 8px 12px; - margin-left: 8px; + margin-left: 1px; } .card-type { @@ -226,7 +225,7 @@ watch( } .card-description { - font-size: 12px; + font-size: 10px; color: rgba(255, 255, 255, 0.7); line-height: 1.5; } diff --git a/src/views/Dashboard/components/ScoreAssessment/Index.vue b/src/views/Dashboard/components/ScoreAssessment/Index.vue index ae0489fc..fd2c9eeb 100644 --- a/src/views/Dashboard/components/ScoreAssessment/Index.vue +++ b/src/views/Dashboard/components/ScoreAssessment/Index.vue @@ -73,7 +73,13 @@ watch( () => props.data, (newData) => { if (newData && newData.length > 0) { - scoreData.value = newData + scoreData.value = newData.map((item:any) => ( + { + ...item, + level: item?.level === '良好' ? 'good' : + item?.level === '较差' ? 'poor' : 'exllent' + } + )) } }, { immediate: true, deep: true } @@ -101,7 +107,7 @@ watch( } .header-title { - font-size: 16px; + font-size: 14px; margin-right: 6px; color: white; font-weight: bold; @@ -132,7 +138,7 @@ watch( } .header-cell { - font-size: 13px; + font-size: 11px; color: rgba(255, 255, 255, 0.9); font-weight: 500; display: flex; @@ -173,7 +179,7 @@ watch( } .row-cell { - font-size: 13px; + font-size: 11px; color: #ffffff; display: flex; align-items: center; @@ -213,11 +219,8 @@ watch( color: #ffffff; white-space: nowrap; font-size: 12px; - - &.level-excellent { - background: #51869290; - border: 1px solid #518692; - } + background: #51869290; + border: 1px solid #518692; &.level-good { background: #47363390;