feat(auth): 添加用户信息获取和附件功能

- 在App.vue中添加onMounted钩子,自动检查认证状态并获取用户信息
- 在auth.ts API文件中添加getInfo方法用于获取用户信息
- 在auth store中添加getInfo方法,实现用户信息获取逻辑
- 在订单列表页面添加批量审批权限控制,根据用户角色决定是否显示批量审批按钮
- 在采购详情页面添加附件标签页,显示合同附件和其他附件列表
- 添加文件预览功能,支持附件在线预览
- 扩展类型定义,添加附件相关类型支持
- 优化批量审批流程,移除驳回选项并修复参数传递问题
master
chenhao 2025-12-23 15:57:36 +08:00
parent 8c3cf68639
commit 09513911ae
6 changed files with 159 additions and 10 deletions

View File

@ -5,7 +5,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// App import { onMounted } from 'vue'
import { useAuthStore } from '@/store/auth'
onMounted(() => {
const authStore = useAuthStore()
//
if (authStore.isAuthenticated) {
authStore.getInfo()
}
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -19,6 +19,13 @@ export const login = (params: LoginParams): Promise<AxiosResponse<ApiResponse<{
return http.post('/login', formData) return http.post('/login', formData)
} }
/**
*
*/
export const getInfo = (): Promise<AxiosResponse<ApiResponse<any>>> => {
return http.get('/getInfo')
}
/** /**
* 退 * 退
*/ */

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { login, logout as logoutApi } from '@/api/auth' import { login, getInfo, logout as logoutApi } from '@/api/auth'
import type { LoginParams } from '@/types' import type { LoginParams } from '@/types'
interface AuthState { interface AuthState {
@ -52,6 +52,26 @@ export const useAuthStore = defineStore('auth', {
} }
}, },
/**
*
*/
async getInfo() {
if (this.userInfo) return; // 如果已有用户信息,则不再获取
try {
const response = await getInfo();
if (response.data.code === 0) {
const user = response.data.data.user;
this.userInfo = user;
} else {
throw new Error(response.data.msg || '获取用户信息失败');
}
} catch (error) {
console.error('获取用户信息失败:', error);
// 这里可以选择是否要登出
// await this.logout();
}
},
/** /**
* *
*/ */

View File

@ -298,5 +298,8 @@ export interface PurchaseDetail {
ownerName: string // 汇智负责人 ownerName: string // 汇智负责人
remark: string // 备注 remark: string // 备注
omsPurchaseOrderItemList: PurchaseOrderItem[] // 采购产品列表 omsPurchaseOrderItemList: PurchaseOrderItem[] // 采购产品列表
contractFileList?: AttachmentFile[] // 合同附件列表
attachmentList?: AttachmentFile[] // 其他附件列表
fileLog?: AttachmentFile // 单个附件
[key: string]: any [key: string]: any
} }

View File

@ -116,7 +116,7 @@
</van-tabs> </van-tabs>
<!-- 一键审批操作悬浮框 --> <!-- 一键审批操作悬浮框 -->
<div v-if="currentTab === 'pending' && orderList.length > 0" class="batch-actions-footer"> <div v-if="canBatchApprove && currentTab === 'pending' && orderList.length > 0" class="batch-actions-footer">
<div class="footer-content"> <div class="footer-content">
<van-button <van-button
type="primary" type="primary"
@ -199,16 +199,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useOrderStore } from '@/store/order' import { useOrderStore } from '@/store/order'
import { useAuthStore } from '@/store/auth'
import { formatAmount, formatDate } from '@/utils' import { formatAmount, formatDate } from '@/utils'
import type { ApprovalStatus, ApproveBtn, BatchApprovalParams } from '@/types' import type { ApprovalStatus, ApproveBtn, BatchApprovalParams } from '@/types'
import { showToast, showSuccessToast } from 'vant' import { showToast, showSuccessToast } from 'vant'
const router = useRouter() const router = useRouter()
const orderStore = useOrderStore() const orderStore = useOrderStore()
const authStore = useAuthStore()
//
const { userInfo } = storeToRefs(authStore)
// //
const { orderList, loading, finished } = storeToRefs(orderStore) const { orderList, loading, finished } = storeToRefs(orderStore)
@ -230,6 +235,15 @@ const batchApprovalOpinion = ref('')
const batchSubmitting = ref(false) const batchSubmitting = ref(false)
const selectedBatchTag = ref('') const selectedBatchTag = ref('')
//
const canBatchApprove = computed(() => {
if (!userInfo.value || !Array.isArray(userInfo.value.roles)) {
return true //
}
//
return !userInfo.value.roles.some(role => role.roleName && role.roleName.includes('商务'))
})
// //
const onLoad = async () => { const onLoad = async () => {
try { try {
@ -341,8 +355,7 @@ const showBatchApprovalDialog = () => {
// //
const getBatchOpinionTags = () => { const getBatchOpinionTags = () => {
return [ return [
'所有信息已阅,审核通过', '所有信息已阅,审核通过'
'经审查有问题,驳回'
] ]
} }
@ -369,7 +382,7 @@ const submitBatchApproval = async (approveStatus: ApproveBtn) => {
try { try {
const params: BatchApprovalParams = { const params: BatchApprovalParams = {
variables: { variables: {
approveBtn: approveStatus+'', approveBtn: approveStatus,
comment: opinion || (approveStatus === 1 ? '同意' : '') comment: opinion || (approveStatus === 1 ? '同意' : '')
} }
} }
@ -387,9 +400,13 @@ const submitBatchApproval = async (approveStatus: ApproveBtn) => {
} }
} }
onMounted(() => { onMounted(async () => {
orderStore.resetListState() orderStore.resetListState()
orderStore.resetCompletedListState() orderStore.resetCompletedListState()
//
if (authStore.isAuthenticated && !authStore.userInfo) {
await authStore.getInfo()
}
onLoad() onLoad()
}) })
</script> </script>

View File

@ -132,6 +132,32 @@
</div> </div>
</van-tab> </van-tab>
<!-- 附件 -->
<van-tab title="附件" name="attachment">
<div class="tab-content">
<div class="card" v-if="attachmentList.length">
<div class="card-body">
<div class="file-list">
<div v-for="file in attachmentList" :key="file.id" class="file-item"
@click="previewFile(file)">
<van-icon name="description"/>
<div class="file-info">
<div class="file-name">{{ file.fileName }}</div>
<div class="file-meta"> {{ formatDate(file.uploadTime) }}</div>
</div>
<van-icon name="arrow"/>
</div>
</div>
</div>
</div>
<!-- 无附件时的提示 -->
<div v-else class="empty-state">
<van-empty description="暂无附件"/>
</div>
</div>
</van-tab>
<!-- 审批历史 --> <!-- 审批历史 -->
<van-tab title="审批历史" name="approval"> <van-tab title="审批历史" name="approval">
<div class="tab-content"> <div class="tab-content">
@ -260,8 +286,8 @@ import { storeToRefs } from 'pinia'
import { showToast, showSuccessToast } from 'vant' import { showToast, showSuccessToast } from 'vant'
import { usePurchaseStore } from '@/store/purchase' import { usePurchaseStore } from '@/store/purchase'
import { getPurchaseApprovalHistory, submitPurchaseApproval } from '@/api/purchase' import { getPurchaseApprovalHistory, submitPurchaseApproval } from '@/api/purchase'
import { formatAmount, formatDate, getApprovalStatusColor } from '@/utils' import { formatAmount, formatDate, getApprovalStatusColor, getFilePreviewUrl } from '@/utils'
import type {ApprovalStatus, ApproveBtn} from '@/types' import type {ApprovalStatus, ApproveBtn, AttachmentFile, FileType} from '@/types'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -281,6 +307,16 @@ const currentApprovalStatus = ref<ApproveBtn>(3)
const submitting = ref(false) const submitting = ref(false)
const selectedTag = ref('') const selectedTag = ref('')
//
const attachmentList = computed(() => {
if (!currentPurchase.value) return []
// fileLog
if ((currentPurchase.value as any).fileLog) {
return [(currentPurchase.value as any).fileLog]
}
})
// //
const showApprovalButtons = computed(() => { const showApprovalButtons = computed(() => {
// (), // (),
@ -302,6 +338,18 @@ const getStepIcon = (status?: ApprovalStatus) => {
return iconMap[status] || 'clock' return iconMap[status] || 'clock'
} }
//
const previewFile = (file: AttachmentFile) => {
if (!file.filePath) {
showToast('文件路径不存在')
return
}
const url = getFilePreviewUrl(file.filePath)
window.open(url, '_blank')
}
// //
const getTotalAmount = () => { const getTotalAmount = () => {
if (!purchaseItems.value || purchaseItems.value.length === 0) return 0 if (!purchaseItems.value || purchaseItems.value.length === 0) return 0
@ -674,6 +722,51 @@ onMounted(async () => {
} }
} }
//
.file-list {
.file-item {
display: flex;
align-items: center;
padding: var(--spacing-md);
background: var(--background-color-tertiary);
border-radius: var(--border-radius-sm);
margin-bottom: var(--spacing-sm);
cursor: pointer;
&:last-child {
margin-bottom: 0;
}
&:active {
background: var(--border-color);
}
.van-icon:first-child {
color: var(--primary-color);
margin-right: var(--spacing-md);
}
.file-info {
flex: 1;
.file-name {
font-size: 14px;
color: var(--text-color-primary);
margin-bottom: var(--spacing-xs);
}
.file-meta {
font-size: 12px;
color: var(--text-color-tertiary);
}
}
.van-icon:last-child {
color: var(--text-color-tertiary);
}
}
}
// //
.approval-item { .approval-item {
.approval-user, .approval-user,