feat(bx-mobile): add bxClient and retarget business APIs to shanxia2.0
Remove legacy bx user/role/menu/auth APIs. Wire auth store to portal temporarily so the app still typechecks; IAM admin pages use local stubs until Task 6 drops their routes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
c7a8930ad6
commit
3f1faeae34
9 files changed
+340
-262
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import axios, { type InternalAxiosRequestConfig } from 'axios'
|
||||
import { showToast } from 'vant'
|
||||
import { portalAuthApi } from './portalAuth'
|
||||
|
||||
export class BizError extends Error {
|
||||
code?: number
|
||||
constructor(message: string, code?: number) {
|
||||
super(message)
|
||||
this.code = code
|
||||
this.name = 'BizError'
|
||||
}
|
||||
}
|
||||
|
||||
const BX_COLLECTION_ROOT =
|
||||
/^\/(reimburses|advances|payments|workflows|expense-items|invoices|invoice-wallets|invoice-ledgers)$/
|
||||
|
||||
function normalizeBxUrl(url?: string): string | undefined {
|
||||
if (!url || url.endsWith('/')) return url
|
||||
return BX_COLLECTION_ROOT.test(url) ? `${url}/` : url
|
||||
}
|
||||
|
||||
const bxClient = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL || '/api',
|
||||
timeout: 0,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
let refreshing: Promise<boolean> | null = null
|
||||
|
||||
async function tryRefresh(): Promise<boolean> {
|
||||
const rt = localStorage.getItem('refreshToken')
|
||||
if (!rt) return false
|
||||
try {
|
||||
const res: any = await portalAuthApi.refreshToken(rt)
|
||||
const token = res?.data?.token
|
||||
const refreshToken = res?.data?.refreshToken
|
||||
if (!token) return false
|
||||
localStorage.setItem('accessToken', token)
|
||||
if (refreshToken) localStorage.setItem('refreshToken', refreshToken)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
bxClient.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
if (config.url) config.url = normalizeBxUrl(config.url)
|
||||
if (config.data instanceof FormData) {
|
||||
delete (config.headers as any)['Content-Type']
|
||||
}
|
||||
const token = localStorage.getItem('accessToken')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
|
||||
bxClient.interceptors.response.use(
|
||||
(res) => {
|
||||
if (res.config.responseType === 'blob') return res.data
|
||||
const body = res.data
|
||||
if (body?.code !== undefined && body.code !== 0) {
|
||||
showToast(body.msg || '请求失败')
|
||||
return Promise.reject(new BizError(body.msg || '请求失败', body.code))
|
||||
}
|
||||
return body
|
||||
},
|
||||
async (err) => {
|
||||
const original = err.config
|
||||
if (err.response?.status === 401 && original && !(original as any)._retry) {
|
||||
;(original as any)._retry = true
|
||||
if (!refreshing) {
|
||||
refreshing = tryRefresh().finally(() => {
|
||||
refreshing = null
|
||||
})
|
||||
}
|
||||
const ok = await refreshing
|
||||
if (ok) {
|
||||
original.headers = original.headers || {}
|
||||
original.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`
|
||||
return bxClient.request(original)
|
||||
}
|
||||
localStorage.removeItem('accessToken')
|
||||
localStorage.removeItem('refreshToken')
|
||||
showToast('登录已过期,请重新登录')
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
} else if (err.response?.status !== 401) {
|
||||
const msg =
|
||||
err.response?.data?.msg || err.response?.data?.detail || err.message || '网络异常'
|
||||
showToast(msg)
|
||||
}
|
||||
return Promise.reject(err)
|
||||
},
|
||||
)
|
||||
|
||||
export default bxClient
|
||||
@@ -1,64 +1 @@
|
||||
import axios from 'axios'
|
||||
import { showToast } from 'vant'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 0, // 0 = no timeout
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
/**
|
||||
* Business error marker — page-level catch blocks should check this
|
||||
* to avoid double-toasting (interceptor already showed the toast).
|
||||
*/
|
||||
export class BizError extends Error {
|
||||
code?: number
|
||||
constructor(message: string, code?: number) {
|
||||
super(message)
|
||||
this.code = code
|
||||
this.name = 'BizError'
|
||||
}
|
||||
}
|
||||
|
||||
client.interceptors.response.use(
|
||||
(res) => {
|
||||
const body = res.data
|
||||
if (body.code !== 0 && body.code !== undefined) {
|
||||
showToast(body.msg || '请求失败')
|
||||
return Promise.reject(new BizError(body.msg || '请求失败', body.code))
|
||||
}
|
||||
return body
|
||||
},
|
||||
(err) => {
|
||||
if (err.response?.status === 401) {
|
||||
const auth = useAuthStore()
|
||||
auth.logout()
|
||||
showToast('登录已过期,请重新登录')
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
} else if (err.response?.status === 403 && err.response?.data?.detail === '用户已被禁用') {
|
||||
const auth = useAuthStore()
|
||||
auth.logout()
|
||||
showToast('账号已被禁用,请联系管理员')
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
} else {
|
||||
const msg = err.response?.data?.msg || err.response?.data?.detail || err.message || '网络异常'
|
||||
showToast(msg)
|
||||
}
|
||||
return Promise.reject(err)
|
||||
},
|
||||
)
|
||||
|
||||
export default client
|
||||
export { default, BizError } from './bxClient'
|
||||
+76
-116
@@ -1,122 +1,85 @@
|
||||
import client from './client'
|
||||
import bxClient from './bxClient'
|
||||
|
||||
export const authApi = {
|
||||
login: (data: { username: string; password: string }) =>
|
||||
client.post('/auth/login', data),
|
||||
register: (data: { username: string; password: string; realName: string }) =>
|
||||
client.post('/auth/register', data),
|
||||
me: () => client.get('/auth/me'),
|
||||
}
|
||||
|
||||
export const userApi = {
|
||||
list: (params?: Record<string, any>) => client.get('/users/', { params }),
|
||||
dropdown: () => client.get('/users/dropdown'),
|
||||
getById: (id: number) => client.get(`/users/${id}`),
|
||||
create: (data: Record<string, any>) => client.post('/users/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/users/${id}`, data),
|
||||
delete: (id: number) => client.delete(`/users/${id}`),
|
||||
resetPassword: (id: number) => client.put(`/users/${id}/reset-password`),
|
||||
}
|
||||
|
||||
export const roleApi = {
|
||||
list: (params?: Record<string, any>) => client.get('/roles/', { params }),
|
||||
getById: (id: number) => client.get(`/roles/${id}`),
|
||||
create: (data: Record<string, any>) => client.post('/roles/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/roles/${id}`, data),
|
||||
delete: (id: number) => client.delete(`/roles/${id}`),
|
||||
getUsers: (roleId: number) => client.get(`/roles/${roleId}/users`),
|
||||
assignUsers: (id: number, userIds: number[]) =>
|
||||
client.put(`/roles/${id}/users`, { userIds }),
|
||||
assignMenus: (id: number, menuIds: number[]) =>
|
||||
client.put(`/roles/${id}/menus`, { menuIds }),
|
||||
}
|
||||
|
||||
export const menuApi = {
|
||||
mine: () => client.get('/menus/mine'),
|
||||
tree: () => client.get('/menus/tree'),
|
||||
getByRole: (roleId: number) => client.get(`/menus/role/${roleId}`),
|
||||
getById: (id: number) => client.get(`/menus/${id}`),
|
||||
create: (data: Record<string, any>) => client.post('/menus/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/menus/${id}`, data),
|
||||
delete: (id: number) => client.delete(`/menus/${id}`),
|
||||
}
|
||||
export type BxId = string | number
|
||||
|
||||
export const expenseItemApi = {
|
||||
tree: () => client.get('/expense-items/tree'),
|
||||
getById: (id: number) => client.get(`/expense-items/${id}`),
|
||||
create: (data: Record<string, any>) => client.post('/expense-items/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/expense-items/${id}`, data),
|
||||
delete: (id: number) => client.delete(`/expense-items/${id}`),
|
||||
tree: () => bxClient.get('/expense-items/tree'),
|
||||
getById: (id: BxId) => bxClient.get(`/expense-items/${id}`),
|
||||
create: (data: Record<string, any>) => bxClient.post('/expense-items/', data),
|
||||
update: (id: BxId, data: Record<string, any>) => bxClient.put(`/expense-items/${id}`, data),
|
||||
delete: (id: BxId) => bxClient.delete(`/expense-items/${id}`),
|
||||
}
|
||||
|
||||
export const workflowApi = {
|
||||
list: (params?: Record<string, any>) => client.get('/workflows/', { params }),
|
||||
getByBizType: (bizType: string) => client.get(`/workflows/biz/${bizType}`),
|
||||
getById: (id: number) => client.get(`/workflows/${id}`),
|
||||
create: (data: Record<string, any>) => client.post('/workflows/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/workflows/${id}`, data),
|
||||
saveNodes: (id: number, nodes: Record<string, any>[]) =>
|
||||
client.put(`/workflows/${id}/nodes`, nodes),
|
||||
delete: (id: number) => client.delete(`/workflows/${id}`),
|
||||
list: (params?: Record<string, any>) => bxClient.get('/workflows/', { params }),
|
||||
getByBizType: (bizType: string) => bxClient.get(`/workflows/biz/${bizType}`),
|
||||
getById: (id: BxId) => bxClient.get(`/workflows/${id}`),
|
||||
create: (data: Record<string, any>) => bxClient.post('/workflows/', data),
|
||||
update: (id: BxId, data: Record<string, any>) => bxClient.put(`/workflows/${id}`, data),
|
||||
saveNodes: (id: BxId, nodes: Record<string, any>[]) =>
|
||||
bxClient.put(`/workflows/${id}/nodes`, nodes),
|
||||
delete: (id: BxId) => bxClient.delete(`/workflows/${id}`),
|
||||
}
|
||||
|
||||
export const advanceApi = {
|
||||
list: (params?: Record<string, any>) => client.get('/advances/', { params }),
|
||||
my: (params?: Record<string, any>) => client.get('/advances/my', { params }),
|
||||
myStats: () => client.get('/advances/my/stats'),
|
||||
list: (params?: Record<string, any>) => bxClient.get('/advances/', { params }),
|
||||
my: (params?: Record<string, any>) => bxClient.get('/advances/my', { params }),
|
||||
myStats: () => bxClient.get('/advances/my/stats'),
|
||||
pendingApproval: (params?: Record<string, any>) =>
|
||||
client.get('/advances/pending-approval', { params }),
|
||||
bxClient.get('/advances/pending-approval', { params }),
|
||||
approvedByMe: (params?: Record<string, any>) =>
|
||||
client.get('/advances/approved-by-me', { params }),
|
||||
getById: (id: number) => client.get(`/advances/${id}`),
|
||||
getApprovals: (id: number) => client.get(`/advances/${id}/approvals`),
|
||||
create: (data: Record<string, any>) => client.post('/advances/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/advances/${id}`, data),
|
||||
submit: (id: number) => client.post(`/advances/${id}/submit`),
|
||||
approve: (id: number, data: { approvalOpinion?: string }) =>
|
||||
client.post(`/advances/${id}/approve`, data),
|
||||
reject: (id: number, data: { approvalOpinion?: string }) =>
|
||||
client.post(`/advances/${id}/reject`, data),
|
||||
delete: (id: number) => client.delete(`/advances/${id}`),
|
||||
bxClient.get('/advances/approved-by-me', { params }),
|
||||
getById: (id: BxId) => bxClient.get(`/advances/${id}`),
|
||||
getApprovals: (id: BxId) => bxClient.get(`/advances/${id}/approvals`),
|
||||
create: (data: Record<string, any>) => bxClient.post('/advances/', data),
|
||||
update: (id: BxId, data: Record<string, any>) => bxClient.put(`/advances/${id}`, data),
|
||||
submit: (id: BxId) => bxClient.post(`/advances/${id}/submit`),
|
||||
approve: (id: BxId, data: { approvalOpinion?: string }) =>
|
||||
bxClient.post(`/advances/${id}/approve`, data),
|
||||
reject: (id: BxId, data: { approvalOpinion?: string }) =>
|
||||
bxClient.post(`/advances/${id}/reject`, data),
|
||||
delete: (id: BxId) => bxClient.delete(`/advances/${id}`),
|
||||
}
|
||||
|
||||
export const reimburseApi = {
|
||||
generateTitle: () => client.get('/reimburses/generate-title'),
|
||||
list: (params?: Record<string, any>) => client.get('/reimburses/', { params }),
|
||||
my: (params?: Record<string, any>) => client.get('/reimburses/my', { params }),
|
||||
myStats: () => client.get('/reimburses/my/stats'),
|
||||
generateTitle: () => bxClient.get('/reimburses/generate-title'),
|
||||
list: (params?: Record<string, any>) => bxClient.get('/reimburses/', { params }),
|
||||
my: (params?: Record<string, any>) => bxClient.get('/reimburses/my', { params }),
|
||||
myStats: () => bxClient.get('/reimburses/my/stats'),
|
||||
pendingApproval: (params?: Record<string, any>) =>
|
||||
client.get('/reimburses/pending-approval', { params }),
|
||||
bxClient.get('/reimburses/pending-approval', { params }),
|
||||
approvedByMe: (params?: Record<string, any>) =>
|
||||
client.get('/reimburses/approved-by-me', { params }),
|
||||
getById: (id: number) => client.get(`/reimburses/${id}`),
|
||||
getApprovals: (id: number) => client.get(`/reimburses/${id}/approvals`),
|
||||
create: (data: Record<string, any>) => client.post('/reimburses/', data),
|
||||
update: (id: number, data: Record<string, any>) => client.put(`/reimburses/${id}`, data),
|
||||
submit: (id: number) => client.post(`/reimburses/${id}/submit`),
|
||||
approve: (id: number, data: { approvalOpinion?: string }) =>
|
||||
client.post(`/reimburses/${id}/approve`, data),
|
||||
reject: (id: number, data: { approvalOpinion?: string }) =>
|
||||
client.post(`/reimburses/${id}/reject`, data),
|
||||
delete: (id: number) => client.delete(`/reimburses/${id}`),
|
||||
bxClient.get('/reimburses/approved-by-me', { params }),
|
||||
getById: (id: BxId) => bxClient.get(`/reimburses/${id}`),
|
||||
getApprovals: (id: BxId) => bxClient.get(`/reimburses/${id}/approvals`),
|
||||
create: (data: Record<string, any>) => bxClient.post('/reimburses/', data),
|
||||
update: (id: BxId, data: Record<string, any>) => bxClient.put(`/reimburses/${id}`, data),
|
||||
submit: (id: BxId) => bxClient.post(`/reimburses/${id}/submit`),
|
||||
approve: (id: BxId, data: { approvalOpinion?: string }) =>
|
||||
bxClient.post(`/reimburses/${id}/approve`, data),
|
||||
reject: (id: BxId, data: { approvalOpinion?: string }) =>
|
||||
bxClient.post(`/reimburses/${id}/reject`, data),
|
||||
delete: (id: BxId) => bxClient.delete(`/reimburses/${id}`),
|
||||
}
|
||||
|
||||
export const paymentApi = {
|
||||
pending: (params?: Record<string, any>) => client.get('/payments/pending', { params }),
|
||||
pay: (id: number, data?: { payRemark?: string; actualAmount?: number }) =>
|
||||
client.post(`/payments/${id}/pay`, data),
|
||||
offset: (id: number, data: { advanceId: number; offsetAmount: number; remark?: string }) =>
|
||||
client.post(`/payments/${id}/offset`, data),
|
||||
pending: (params?: Record<string, any>) => bxClient.get('/payments/pending', { params }),
|
||||
pay: (id: BxId, data?: { payRemark?: string; actualAmount?: number }) =>
|
||||
bxClient.post(`/payments/${id}/pay`, data),
|
||||
offset: (
|
||||
id: BxId,
|
||||
data: { advanceId: BxId; offsetAmount: number; remark?: string },
|
||||
) => bxClient.post(`/payments/${id}/offset`, data),
|
||||
batchOffset: (
|
||||
id: number,
|
||||
id: BxId,
|
||||
data: {
|
||||
items: Array<{ advanceId: number; offsetAmount: number }>
|
||||
items: Array<{ advanceId: BxId; offsetAmount: number }>
|
||||
payAmount: number
|
||||
remark?: string
|
||||
},
|
||||
) => client.post(`/payments/${id}/batch-offset`, data),
|
||||
) => bxClient.post(`/payments/${id}/batch-offset`, data),
|
||||
advanceIssues: (params?: Record<string, any>) =>
|
||||
client.get('/payments/advance-issues', {
|
||||
bxClient.get('/payments/advance-issues', {
|
||||
params: params
|
||||
? {
|
||||
page: params.page,
|
||||
@@ -127,41 +90,38 @@ export const paymentApi = {
|
||||
: undefined,
|
||||
}),
|
||||
exportAdvanceIssues: (params?: Record<string, any>) =>
|
||||
client.get('/payments/advance-issues/export', {
|
||||
bxClient.get('/payments/advance-issues/export', {
|
||||
params: params
|
||||
? { issue_status: params.issueStatus, keyword: params.keyword }
|
||||
: undefined,
|
||||
responseType: 'blob',
|
||||
}),
|
||||
issueAdvance: (
|
||||
advanceId: number,
|
||||
data: { issueRemark?: string; actualAmount?: number },
|
||||
) => client.post(`/payments/advance/${advanceId}/issue`, data),
|
||||
processed: (params?: Record<string, any>) =>
|
||||
client.get('/payments/processed', { params }),
|
||||
advanceOffsetRecords: (advanceId: number) =>
|
||||
client.get(`/payments/advance/${advanceId}/offset-records`),
|
||||
reimburseOffsetRecords: (reimburseId: number) =>
|
||||
client.get(`/payments/${reimburseId}/offset-records`),
|
||||
issueAdvance: (advanceId: BxId, data: { issueRemark?: string; actualAmount?: number }) =>
|
||||
bxClient.post(`/payments/advance/${advanceId}/issue`, data),
|
||||
processed: (params?: Record<string, any>) => bxClient.get('/payments/processed', { params }),
|
||||
advanceOffsetRecords: (advanceId: BxId) =>
|
||||
bxClient.get(`/payments/advance/${advanceId}/offset-records`),
|
||||
reimburseOffsetRecords: (reimburseId: BxId) =>
|
||||
bxClient.get(`/payments/${reimburseId}/offset-records`),
|
||||
}
|
||||
|
||||
export const attachmentApi = {
|
||||
upload: (file: File, bizType: string = '', bizId: number = 0) => {
|
||||
upload: (file: File, bizType: string = '', bizId: BxId = 0) => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
return client.post('/attachments/upload', form, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
return bxClient.post('/attachments/upload', form, {
|
||||
params: { biz_type: bizType, biz_id: bizId },
|
||||
})
|
||||
},
|
||||
download: (id: number) => client.get(`/attachments/download/${id}`, { responseType: 'blob' }),
|
||||
preview: (id: number) => client.get(`/attachments/preview/${id}`),
|
||||
delete: (id: number) => client.delete(`/attachments/${id}`),
|
||||
listByBiz: (bizType: string, bizId: number) =>
|
||||
client.get('/attachments/list', { params: { biz_type: bizType, biz_id: bizId } }),
|
||||
bindBiz: (ids: number[], bizType: string, bizId: number) =>
|
||||
client.put('/attachments/bind-biz', null, {
|
||||
download: (id: BxId) =>
|
||||
bxClient.get(`/attachments/download/${id}`, { responseType: 'blob' }),
|
||||
preview: (id: BxId) => bxClient.get(`/attachments/preview/${id}`),
|
||||
delete: (id: BxId) => bxClient.delete(`/attachments/${id}`),
|
||||
listByBiz: (bizType: string, bizId: BxId) =>
|
||||
bxClient.get('/attachments/list', { params: { biz_type: bizType, biz_id: bizId } }),
|
||||
bindBiz: (ids: BxId[], bizType: string, bizId: BxId) =>
|
||||
bxClient.put('/attachments/bind-biz', null, {
|
||||
params: { ids: ids.join(','), biz_type: bizType, biz_id: bizId },
|
||||
}),
|
||||
downloadUrl: (id: number) => `/api/attachments/download/${id}`,
|
||||
downloadUrl: (id: BxId) => `/api/attachments/download/${id}`,
|
||||
}
|
||||
+100
-47
@@ -1,17 +1,21 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { authApi, menuApi } from '@/api'
|
||||
import { portalAuthApi } from '@/api/portalAuth'
|
||||
import { filterVisibleEntries } from '@/utils/bxPermission'
|
||||
import type { BxMenuEntry } from '@/constants/bxMenuCatalog'
|
||||
|
||||
/** 门户用户(id 为 UUID 字符串) */
|
||||
export interface UserInfo {
|
||||
id: number
|
||||
id: string
|
||||
username: string
|
||||
realName: string
|
||||
phone?: string
|
||||
email?: string
|
||||
avatar?: string
|
||||
roleList: { id: number; roleName: string; roleCode: string }[]
|
||||
role?: string
|
||||
roles?: string[]
|
||||
avatarUrl?: string | null
|
||||
}
|
||||
|
||||
/** @deprecated 旧 bx 菜单树;首页请用 visibleEntries(Task 7) */
|
||||
export interface MenuItem {
|
||||
id: number
|
||||
parentId: number
|
||||
@@ -25,74 +29,123 @@ export interface MenuItem {
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const token = ref(localStorage.getItem('token') || '')
|
||||
const accessToken = ref(
|
||||
localStorage.getItem('accessToken') || localStorage.getItem('token') || '',
|
||||
)
|
||||
const refreshToken = ref(localStorage.getItem('refreshToken') || '')
|
||||
const user = ref<UserInfo | null>(null)
|
||||
const menuCodes = ref<string[]>([])
|
||||
const isSuperAdmin = ref(false)
|
||||
const allMenus = ref(false)
|
||||
/** 兼容旧首页:Task 7 前保持空数组 */
|
||||
const menuList = ref<MenuItem[]>([])
|
||||
|
||||
const isLoggedIn = computed(() => !!token.value)
|
||||
const realName = computed(() => user.value?.realName || '')
|
||||
const roleCodes = computed(() => user.value?.roleList?.map((r) => r.roleCode) || [])
|
||||
const isLoggedIn = computed(() => !!accessToken.value)
|
||||
const realName = computed(() => user.value?.realName || user.value?.username || '')
|
||||
|
||||
const isAdmin = computed(() => roleCodes.value.includes('ADMIN'))
|
||||
const isFinance = computed(() => roleCodes.value.includes('FINANCE'))
|
||||
const isGeneralManager = computed(() => roleCodes.value.includes('GENERAL_MANAGER'))
|
||||
const visibleEntries = computed((): BxMenuEntry[] =>
|
||||
filterVisibleEntries({
|
||||
isSuperAdmin: isSuperAdmin.value,
|
||||
all: allMenus.value,
|
||||
menuCodes: menuCodes.value,
|
||||
}),
|
||||
)
|
||||
|
||||
/** @deprecated 旧角色码;路由守卫将改为 menuCodes(Task 6) */
|
||||
const roleCodes = computed(() => {
|
||||
const roles = user.value?.roles || []
|
||||
if (user.value?.role) return Array.from(new Set([user.value.role, ...roles]))
|
||||
return roles
|
||||
})
|
||||
const isAdmin = computed(() => false)
|
||||
const isFinance = computed(() => false)
|
||||
const isGeneralManager = computed(() => false)
|
||||
|
||||
function persistTokens(token: string, rt?: string) {
|
||||
accessToken.value = token
|
||||
localStorage.setItem('accessToken', token)
|
||||
localStorage.removeItem('token')
|
||||
if (rt) {
|
||||
refreshToken.value = rt
|
||||
localStorage.setItem('refreshToken', rt)
|
||||
}
|
||||
}
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
const res: any = await authApi.login({ username, password })
|
||||
if (res.code === 0) {
|
||||
token.value = res.data.accessToken
|
||||
localStorage.setItem('token', res.data.accessToken)
|
||||
await fetchUser()
|
||||
return true
|
||||
}
|
||||
throw new Error(res.msg || '登录失败')
|
||||
const res: any = await portalAuthApi.login(username, password)
|
||||
const token = res?.data?.token
|
||||
if (!token) throw new Error(res?.message || '登录失败')
|
||||
persistTokens(token, res?.data?.refreshToken)
|
||||
await fetchUser()
|
||||
return true
|
||||
}
|
||||
|
||||
async function fetchUser() {
|
||||
try {
|
||||
const res: any = await authApi.me()
|
||||
if (res.code === 0) {
|
||||
user.value = res.data
|
||||
}
|
||||
await fetchMenus()
|
||||
const res: any = await portalAuthApi.currentUser()
|
||||
user.value = res.data
|
||||
const perm: any = await portalAuthApi.menuCodes()
|
||||
isSuperAdmin.value = !!perm.data?.isSuperAdmin
|
||||
allMenus.value = !!perm.data?.all
|
||||
menuCodes.value = perm.data?.menuCodes || []
|
||||
} catch {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMenus() {
|
||||
try {
|
||||
const res: any = await menuApi.mine()
|
||||
if (res.code === 0) {
|
||||
menuList.value = res.data || []
|
||||
}
|
||||
} catch {
|
||||
menuList.value = []
|
||||
}
|
||||
menuList.value = []
|
||||
}
|
||||
|
||||
function logout() {
|
||||
token.value = ''
|
||||
async function logout() {
|
||||
try {
|
||||
await portalAuthApi.logout()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
accessToken.value = ''
|
||||
refreshToken.value = ''
|
||||
user.value = null
|
||||
menuCodes.value = []
|
||||
isSuperAdmin.value = false
|
||||
allMenus.value = false
|
||||
menuList.value = []
|
||||
localStorage.removeItem('accessToken')
|
||||
localStorage.removeItem('refreshToken')
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
|
||||
function can(menuCode: string) {
|
||||
if (isSuperAdmin.value || allMenus.value) return true
|
||||
return menuCodes.value.includes(menuCode)
|
||||
}
|
||||
|
||||
function hasMenu(code: string): boolean {
|
||||
function check(items: MenuItem[]): boolean {
|
||||
for (const item of items) {
|
||||
if (item.menuCode === code) return true
|
||||
if (item.children?.length && check(item.children)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return check(menuList.value)
|
||||
return can(code)
|
||||
}
|
||||
|
||||
return {
|
||||
token, user, menuList,
|
||||
isLoggedIn, realName, roleCodes,
|
||||
isAdmin, isFinance, isGeneralManager,
|
||||
login, fetchUser, fetchMenus, logout, hasMenu,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
/** @deprecated 使用 accessToken */
|
||||
token: accessToken,
|
||||
user,
|
||||
menuList,
|
||||
menuCodes,
|
||||
isSuperAdmin,
|
||||
allMenus,
|
||||
isLoggedIn,
|
||||
realName,
|
||||
roleCodes,
|
||||
isAdmin,
|
||||
isFinance,
|
||||
isGeneralManager,
|
||||
visibleEntries,
|
||||
login,
|
||||
fetchUser,
|
||||
fetchMenus,
|
||||
logout,
|
||||
can,
|
||||
hasMenu,
|
||||
}
|
||||
})
|
||||
@@ -151,7 +151,19 @@ import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showDialog } from 'vant'
|
||||
import { DELETE_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
|
||||
import { menuApi } from '@/api'
|
||||
/** IAM 已迁门户;本页路由将在 Task 6 移除 */
|
||||
const menuApi: any = {
|
||||
tree: async () => ({ code: 0, data: [] }),
|
||||
create: async () => {
|
||||
throw new Error('请使用门户系统管理菜单')
|
||||
},
|
||||
update: async () => {
|
||||
throw new Error('请使用门户系统管理菜单')
|
||||
},
|
||||
delete: async () => {
|
||||
throw new Error('请使用门户系统管理菜单')
|
||||
},
|
||||
}
|
||||
import { menuTypeLabel, statusLabel } from '@/utils/format'
|
||||
import StatusTag from '@/components/mobile/StatusTag.vue'
|
||||
import MenuNode from '@/components/mobile/MenuNode.vue'
|
||||
|
||||
@@ -237,8 +237,35 @@ import { ref, reactive, computed, onMounted, h, defineComponent } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showConfirmDialog } from 'vant'
|
||||
import { DELETE_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
|
||||
import { roleApi, menuApi, userApi } from '@/api'
|
||||
import { BizError } from '@/api/client'
|
||||
|
||||
/** IAM 已迁门户;本页路由将在 Task 6 移除 */
|
||||
const roleApi: any = {
|
||||
list: async () => ({ code: 0, data: { rows: [], total: 0 } }),
|
||||
create: async () => {
|
||||
throw new Error('请使用门户系统管理角色')
|
||||
},
|
||||
update: async () => {
|
||||
throw new Error('请使用门户系统管理角色')
|
||||
},
|
||||
delete: async () => {
|
||||
throw new Error('请使用门户系统管理角色')
|
||||
},
|
||||
getUsers: async () => ({ code: 0, data: [] }),
|
||||
assignUsers: async () => {
|
||||
throw new Error('请使用门户系统管理角色')
|
||||
},
|
||||
assignMenus: async () => {
|
||||
throw new Error('请使用门户系统管理角色')
|
||||
},
|
||||
}
|
||||
const menuApi: any = {
|
||||
tree: async () => ({ code: 0, data: [] }),
|
||||
getByRole: async () => ({ code: 0, data: [] }),
|
||||
}
|
||||
const userApi: any = {
|
||||
list: async () => ({ code: 0, data: { rows: [], total: 0 } }),
|
||||
}
|
||||
import { statusLabel } from '@/utils/format'
|
||||
import StatusTag from '@/components/mobile/StatusTag.vue'
|
||||
import { useKeywordSearch } from '@/hooks/useKeywordSearch'
|
||||
|
||||
@@ -226,8 +226,27 @@ import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showConfirmDialog } from 'vant'
|
||||
import { DELETE_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
|
||||
import { userApi, roleApi } from '@/api'
|
||||
import { BizError } from '@/api/client'
|
||||
|
||||
/** IAM 已迁门户;本页路由将在 Task 6 移除,保留空实现以免编译失败 */
|
||||
const userApi: any = {
|
||||
list: async () => ({ code: 0, data: { rows: [], total: 0 } }),
|
||||
create: async () => {
|
||||
throw new Error('请使用门户系统管理用户')
|
||||
},
|
||||
update: async () => {
|
||||
throw new Error('请使用门户系统管理用户')
|
||||
},
|
||||
delete: async () => {
|
||||
throw new Error('请使用门户系统管理用户')
|
||||
},
|
||||
resetPassword: async () => {
|
||||
throw new Error('请使用门户系统管理用户')
|
||||
},
|
||||
}
|
||||
const roleApi: any = {
|
||||
list: async () => ({ code: 0, data: { rows: [], total: 0 } }),
|
||||
}
|
||||
import { statusLabel } from '@/utils/format'
|
||||
import StatusTag from '@/components/mobile/StatusTag.vue'
|
||||
import { useKeywordSearch } from '@/hooks/useKeywordSearch'
|
||||
|
||||
@@ -128,7 +128,7 @@ import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showDialog } from 'vant'
|
||||
import { DELETE_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
|
||||
import { workflowApi, userApi } from '@/api'
|
||||
import { workflowApi } from '@/api'
|
||||
import { statusLabel, statusTagClass } from '@/utils/format'
|
||||
import StatusTag from '@/components/mobile/StatusTag.vue'
|
||||
|
||||
@@ -165,21 +165,8 @@ function nodeKey(node: Record<string, any>): string {
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const res: any = await userApi.dropdown()
|
||||
const data = res.data ?? res
|
||||
userOptions.value = (data || []).map((u: any) => {
|
||||
const name = u.realName || u.username
|
||||
const roles = u.roles || ''
|
||||
return {
|
||||
id: u.id,
|
||||
realName: name,
|
||||
displayName: roles ? `${name}(${roles})` : name,
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
userOptions.value = []
|
||||
}
|
||||
// 旧 bx userApi 已移除;P3 再接门户用户下拉
|
||||
userOptions.value = []
|
||||
}
|
||||
|
||||
async function loadWfDef() {
|
||||
|
||||
@@ -128,7 +128,7 @@ import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showDialog } from 'vant'
|
||||
import { DELETE_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
|
||||
import { workflowApi, userApi } from '@/api'
|
||||
import { workflowApi } from '@/api'
|
||||
import { statusLabel, statusTagClass } from '@/utils/format'
|
||||
import StatusTag from '@/components/mobile/StatusTag.vue'
|
||||
|
||||
@@ -165,21 +165,8 @@ function nodeKey(node: Record<string, any>): string {
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const res: any = await userApi.dropdown()
|
||||
const data = res.data ?? res
|
||||
userOptions.value = (data || []).map((u: any) => {
|
||||
const name = u.realName || u.username
|
||||
const roles = u.roles || ''
|
||||
return {
|
||||
id: u.id,
|
||||
realName: name,
|
||||
displayName: roles ? `${name}(${roles})` : name,
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
userOptions.value = []
|
||||
}
|
||||
// 旧 bx userApi 已移除;P3 再接门户用户下拉
|
||||
userOptions.value = []
|
||||
}
|
||||
|
||||
async function loadWfDef() {
|
||||
|
||||
Reference in new issue
Block a user