权限调整 #9

Merged
tangweijie merged 2 commits from lm/fix/databoard into master 2026-01-21 17:26:36 +08:00
11 changed files with 230 additions and 56 deletions

View File

@ -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<string> => {
const now = Date.now()
// 如果 token 还未过期,直接返回缓存的 token
if (cachedToken && tokenExpireTime > now) {
return cachedToken
}
// 调用 token 接口获取新的 token
const tokenData = await createAccessToken(
{
grant_type: 'password',
username: 'admin',
password: 'admin123',
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 <T>(options: { url: string; params?: any }): Promise<T> => {
const token = await getAuthToken()
return await request.get<T>({
...options,
headers: {
Authorization: `Bearer ${token}`
}
})
}
/** AI心航360° API */
export const AiDashEntryApi = {
/** 获取AI心航360°统计数据 */
getStatistics: async (): Promise<AiDashEntryStatisticsVO> => {
return await request.get({ url: '/prison/dashboard/ai-dash-entry/statistics' })
return await authRequest<AiDashEntryStatisticsVO>({
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
})
}
}

View File

@ -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<string> => {
const now = Date.now()
// 如果 token 还未过期,直接返回缓存的 token
if (cachedToken && tokenExpireTime > now) {
return cachedToken
}
// 调用 token 接口获取新的 token
const tokenData = await createAccessToken(
{
grant_type: 'password',
username: 'admin',
password: 'admin123',
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 <T>(options: { url: string; params?: any }): Promise<T> => {
const token = await getAuthToken()
return await request.get<T>({
...options,
headers: {
Authorization: `Bearer ${token}`
}
})
}
/** 看板统计响应 */
export interface DashboardStatisticsVO {
@ -219,6 +268,9 @@ export const DashboardApi = {
// 获取罪犯Dashboard统计信息
getPrisonerStats: async (prisonerId: number): Promise<PrisonerDashboardStatsRespVO> => {
return await request.get({ url: '/prison/dashboard/prisoner-stats', params: { prisonerId } })
return await authRequest<PrisonerDashboardStatsRespVO>({
url: '/prison/dashboard/prisoner-stats',
params: { prisonerId }
})
}
}

View File

@ -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访问令牌
* 使tokenBasic认证
* @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<OAuth2TokenVO> => {
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
}

View File

@ -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: '/' })

View File

@ -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

View File

@ -85,7 +85,7 @@
</div>
<div class="dashboard-content-bottom-right">
<div class="dashboard-content-bottom-right-title">大帐统计</div>
<BarChart :height="'240px'" :data="barChartData" :card-data="barCardData" />
<BarChart :height="'200px'" :data="barChartData" :card-data="barCardData" />
</div>
</div>
</div>
@ -640,7 +640,7 @@ onUnmounted(() => {
font-size: 18px;
font-weight: bold;
color: #ffffff;
margin-bottom: 12px;
margin-bottom: 10px;
}
}
</style>

View File

@ -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);
}
}

View File

@ -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;
}
</style>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
&.level-good {
background: #47363390;