| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- <template>
- <uni-navbar-lite :showLeft=false :show-right=false title="首页"></uni-navbar-lite>
- <view class="page-container">
- <!-- <view>
- <text class="page-header"> 首页 </text>
- </view> -->
- <!-- 快捷操作 -->
- <view class="section-header">
- <text class="section-title">快捷操作</text>
- </view>
- <view class="quick-cards">
- <view v-for="(item, index) in filteredQuickFunctions" :key="index" class="quick-card" @click="handleQuickClick(item)">
- <image class="quick-card-bg" :src="item.bgImage" mode="aspectFill"></image>
- <view class="quick-card-content">
- <text class="quick-card-title">{{ item.title }}</text>
- </view>
- <view class="quick-badge" v-if="item.badge > 0">
- <text class="badge-text">{{item.badge > 99 ? '99+' : item.badge}}</text>
- </view>
- </view>
- </view>
- <view class="divider"></view>
- <view class="receive-section">
- <view class="section-header">
- <text class="section-title">待签收</text>
- <text class="section-link" @click="navigateToOut" v-if="receiveListData.length > 0">查看全部</text>
- </view>
- <template v-if="receiveListData.length > 0">
- <view
- v-for="(item, index) in receiveListData"
- :key="index"
- class="receive-item"
- >
- <view class="receive-item-top">
- <view class="receive-item-title-row">
- <text class="receive-item-name">{{ getItemName(item) }}</text>
- <text class="receive-item-type">{{ getItemTypeName(item) }}</text>
- </view>
- <view class="receive-item-qty">
- <text class="qty-label">数量</text>
- <text class="qty-value">{{ getQuantity(item) }}</text>
- <text class="qty-unit">{{ getMeasureName(item) }}</text>
- </view>
- </view>
- <view class="receive-item-bottom">
- <view class="receive-item-status">
- <view class="status-dot"></view>
- <text class="status-text">待签收</text>
- </view>
- <button class="receive-btn" @click="handleSignReceive(item)">签收</button>
- </view>
- </view>
- </template>
- <template v-else>
- <view class="empty-tips">
- <text class="empty-text">暂无待签收物料</text>
- </view>
- </template>
- </view>
-
- <!-- 底部 TabBar -->
- <custom-tabbar :current="0" />
- </view>
- </template>
- <script setup lang="uts">
- import { ref, onMounted, onUnmounted } from 'vue'
- import { getUserInfo } from '../../utils/storage'
- import { checkPermission } from '../../utils/permission'
- import { getPendingReceiveLines, signReceiveLine, getProductSalseList } from '../../api/out/index'
- import { getPendingReceiveApplyCount, getConfirmedApplyCount } from '../../api/apply/index'
- type QuickFunction = {
- id: number
- title: string
- bgImage: string
- icon: string
- path: string
- badge:number
- permission: string
- }
- const receiveListData = ref<UTSJSONObject[]>([])
- const receiveLoading = ref<boolean>(false)
- let currentUserId: string = ''
- let currentUserName: string = ''
- let refreshTimer: number | null = null
- let timerLock = false
-
- const quickFunctions = ref<QuickFunction[]>([
- {
- id: 1,
- title: '物料申请',
- bgImage: '/static/images/workbench/3.png',
- icon: '',
- path: '/pages/apply/index',
- badge: 0,
- permission: 'mes:wm:purchaseApply:list'
- },
- {
- id: 2,
- title: '手动出库',
- bgImage: '/static/images/workbench/2.png',
- icon: '',
- path: '/pages/out/index',
- badge: 0,
- permission: 'mes:wm:productsalse:list'
- },
- {
- id: 3,
- title: '系统物料',
- bgImage: '/static/images/workbench/4.png',
- icon: '',
- path: '/pages/item/index',
- badge: 0,
- permission: 'mes:md:mditem:list'
- },
- {
- id: 4,
- title: '生产汇报',
- bgImage: '/static/images/workbench/5.png',
- icon: '',
- path: '/pages/pro/index',
- badge: 0,
- permission: 'mes:pro:proreport:list'
- },
- {
- id: 5,
- title: '采购订单',
- bgImage: '/static/images/workbench/6.png',
- icon: '',
- path: '/pages/purchase/index',
- badge: 0,
- permission: 'wm:purchase:list'
- },
- {
- id: 6,
- title: '库存查询',
- bgImage: '/static/images/workbench/7.png',
- icon: '',
- path: '/pages/warehouse/index',
- badge: 0,
- permission: 'mes:wm:wmstock:list'
- }
- ])
- const filteredQuickFunctions = computed(() => {
- return quickFunctions.value.filter(item => {
- if (!item.permission || item.permission.length === 0) {
- return true
- }
- return checkPermission(item.permission)
- })
- })
-
- // 快捷功能点击
- const handleQuickClick = (item: QuickFunction): void => {
- if (item.path.length > 0) {
- uni.navigateTo({
- url: item.path,
- fail: (err: any) => {
- console.log('导航失败', err)
- }
- })
- } else {
- uni.showToast({
- title: item.title,
- icon: 'none'
- })
- }
- }
- // 跳转到申请单列表
- const navigateToApply = (): void => {
- // 跳转到申请单列表
- uni.navigateTo({
- url: '/pages/apply/index'
- })
- }
- // 加载待签收列表
- const loadReceiveList = (): void => {
- if (currentUserId.length === 0) return
- receiveLoading.value = true
- // 查询待签收物料,最多显示10条
- getPendingReceiveLines(1, 10, currentUserId).then((res: any) => {
- const result = res as UTSJSONObject
- const rows = result['rows']
- if (rows != null) {
- const data = rows as UTSJSONObject[]
- receiveListData.value = data
- } else {
- receiveListData.value = []
- }
- receiveLoading.value = false
- }).catch((e) => {
- console.error('加载待签收列表失败:', e)
- receiveListData.value = []
- receiveLoading.value = false
- })
- }
- // 获取物料名称
- const getItemName = (item: any): string => {
- if (item == null) return ''
- const jsonItem = item as UTSJSONObject
- const val = jsonItem['itemName']
- return val != null ? val.toString() : ''
- }
- // 获取物料分类
- const getItemTypeName = (item: any): string => {
- if (item == null) return ''
- const jsonItem = item as UTSJSONObject
- const val = jsonItem['itemTypeName']
- return val != null ? val.toString() : ''
- }
- // 获取数量
- const getQuantity = (item: any): string => {
- if (item == null) return '0'
- const jsonItem = item as UTSJSONObject
- const val = jsonItem['quantitySalse']
- return val != null ? val.toString() : '0'
- }
- // 获取单位
- const getMeasureName = (item: any): string => {
- if (item == null) return ''
- const jsonItem = item as UTSJSONObject
- const val = jsonItem['measureName']
- return val != null ? val.toString() : ''
- }
- // 处理签收
- const handleSignReceive = (item: any): void => {
- if (item == null) return
- const jsonItem = item as UTSJSONObject
- const lineId = jsonItem['lineId']
- if (lineId == null) {
- uni.showToast({ title: '明细ID不存在', icon: 'none' })
- return
- }
- uni.showModal({
- title: '提示',
- content: '确认签收该物料?',
- success: (res) => {
- if (res.confirm) {
- signReceiveLine(lineId.toString()).then(() => {
- uni.showToast({ title: '签收成功', icon: 'success' })
- // 重新加载待签收列表
- loadReceiveList()
- }).catch((e) => {
- const error = e as UTSError
- const errMsg = error?.message
- uni.showToast({ title: errMsg.toString(), icon: 'none' })
- })
- }
- }
- })
- }
- // 跳转到出库单列表
- const navigateToOut = (): void => {
- uni.navigateTo({
- url: '/pages/out/index'
- })
- }
- // 用户名
- const userName = ref<string>('用户')
- // 停止定时刷新
- const stopRefreshTimer = (): void => {
- if (timerLock && refreshTimer != null) {
- clearInterval(refreshTimer as number)
- refreshTimer = null
- timerLock = false
- }
- }
- // 加载出库单 badge 数量
- const loadOutstockBadge = (): void => {
- if (currentUserId.length === 0) return
- // 查询待签收的出库单
- getProductSalseList(1, 1000, '', currentUserId, 'PENDING').then((res: any) => {
- const result = res as UTSJSONObject
- const rows = result['rows']
- if (rows != null) {
- const data = rows as UTSJSONObject[]
- // 统计待签收数量大于0的出库单个数
- const pendingCount = data.filter((item: UTSJSONObject) => {
- const unsignedCount = item['unsignedCount']
- return unsignedCount != null && parseInt(unsignedCount.toString()) > 0
- }).length
- // 更新快捷功能中的出库单 badge
- quickFunctions.value.forEach((functionItem) => {
- if (functionItem.id === 2) {
- functionItem.badge = pendingCount
- }
- })
- }
- }).catch((e) => {
- console.error('加载出库单 badge 失败:', e)
- })
- }
-
- // 加载物料申请 badge 数量
- const loadApplyBadge = (): void => {
- const hasPurchasePermission = checkPermission('mes:wm:mergePurchase:add')
-
- if (hasPurchasePermission) {
- // 有采购权限,统计已确认单据数量
- getConfirmedApplyCount().then((res: any) => {
- const result = res as UTSJSONObject
- const count = result['data']
- // 更新快捷功能中的物料申请 badge
- quickFunctions.value.forEach((functionItem) => {
- if (functionItem.id === 1) {
- functionItem.badge = count != null ? parseInt(count.toString()) : 0
- }
- })
- }).catch((e) => {
- console.error('加载物料申请 badge 失败:', e)
- })
- } else {
- // 没有采购权限,统计待领取数量(保持现状)
- if (currentUserId.length === 0) return
- getPendingReceiveApplyCount(currentUserId).then((res: any) => {
- const result = res as UTSJSONObject
- const count = result['data']
- // 更新快捷功能中的物料申请 badge
- quickFunctions.value.forEach((functionItem) => {
- if (functionItem.id === 1) {
- functionItem.badge = count != null ? parseInt(count.toString()) : 0
- }
- })
- }).catch((e) => {
- console.error('加载物料申请 badge 失败:', e)
- })
- }
- }
- // 启动定时刷新
- const startRefreshTimer = (): void => {
- stopRefreshTimer() // 先清除已有的定时器
- timerLock = true
- refreshTimer = setInterval(() => {
- console.log("当前登录:" + currentUserName)
- const userInfo = getUserInfo();
- if (userInfo != null) {
- loadReceiveList()
- loadOutstockBadge()
- loadApplyBadge()
- }else{
- console.log("停止定时器")
- stopRefreshTimer()
- }
- }, 5000) // 5秒刷新一次
- }
- // 初始化
- onMounted(() => {
- const userInfo = getUserInfo()
- if (userInfo != null) {
- const realName = userInfo['nickName'] as string | null
- userName.value = realName ?? '用户'
- const userId = userInfo['userId']
- const userNameVal = userInfo['userName']
- currentUserId = userId != null ? userId.toString() : ''
- currentUserName = userNameVal != null ? userNameVal.toString() : ''
- // 加载待签收列表
- loadReceiveList()
- // 加载出库单 badge 数量
- loadOutstockBadge()
- // 加载物料申请 badge 数量
- loadApplyBadge()
- // 启动定时刷新,每5秒刷新一次
- startRefreshTimer()
- }
- })
- // 组件卸载时清除定时器
- onUnmounted(() => {
- stopRefreshTimer()
- })
- </script>
- <style lang="scss">
- page {
- background-color: #f5f7fa;
- }
- .page-container {
- flex: 1;
- height: 100vh;
- padding: calc(env(safe-area-inset-top) + 20rpx) 24rpx 160rpx;
- background-color: #f5f7fa;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- overflow-y: auto;
- }
- .section-header {
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: 24rpx 0 16rpx;
- .section-title {
- font-size: 32rpx;
- color: #1d2129;
- font-weight: 600;
- }
- .section-link {
- font-size: 26rpx;
- color: #165dff;
- }
- }
- .divider {
- height: 2rpx;
- background-color: #e5e6eb;
- margin: 8rpx 0;
- }
- .quick-cards {
- flex-direction: row;
- flex-wrap: wrap;
- margin: 0 -8rpx;
- .quick-card {
- position: relative;
- width: calc(33.33% - 16rpx);
- height: 176rpx;
- margin: 8rpx;
- overflow: hidden;
- border-radius: 20rpx;
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
- &-bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- }
- &-content {
- position: relative;
- z-index: 1;
- padding: 20rpx;
- height: 100%;
- justify-content: flex-end;
- }
- &-title {
- font-size: 26rpx;
- color: #333333;
- font-weight: 600;
- }
- }
- .quick-badge {
- position: absolute;
- top: 12rpx;
- right: 12rpx;
- min-width: 32rpx;
- height: 32rpx;
- background-color: #f53f3f;
- border-radius: 16rpx;
- padding: 0 8rpx;
- justify-content: center;
- align-items: center;
- z-index: 2;
- .badge-text {
- font-size: 20rpx;
- color: #ffffff;
- font-weight: 500;
- }
- }
- }
- .receive-section {
- padding-top: 8rpx;
- }
- .receive-item {
- background-color: #ffffff;
- border-radius: 16rpx;
- margin-bottom: 16rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
- overflow: hidden;
- &-top {
- padding: 24rpx 24rpx 16rpx;
- }
- &-title-row {
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16rpx;
- }
- &-name {
- font-size: 28rpx;
- color: #1d2129;
- font-weight: 600;
- flex: 1;
- }
- &-type {
- font-size: 22rpx;
- color: #165dff;
- background-color: #e8f3ff;
- padding: 4rpx 14rpx;
- border-radius: 6rpx;
- }
- &-qty {
- flex-direction: row;
- align-items: baseline;
- .qty-label {
- font-size: 24rpx;
- color: #86909c;
- margin-right: 8rpx;
- }
- .qty-value {
- font-size: 36rpx;
- color: #1d2129;
- font-weight: 700;
- }
- .qty-unit {
- font-size: 24rpx;
- color: #86909c;
- margin-left: 4rpx;
- }
- }
- &-bottom {
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: 14rpx 24rpx;
- background-color: #fafbfc;
- border-top: 2rpx solid #f2f3f5;
- }
- &-status {
- flex-direction: row;
- align-items: center;
- .status-dot {
- width: 12rpx;
- height: 12rpx;
- border-radius: 6rpx;
- background-color: #ff7d00;
- margin-right: 8rpx;
- }
- .status-text {
- font-size: 24rpx;
- color: #ff7d00;
- }
- }
- .receive-btn {
- padding: 12rpx 32rpx;
- background-color: #165dff;
- color: #ffffff;
- font-size: 26rpx;
- border-radius: 8rpx;
- border: none;
- font-weight: 500;
- }
- }
- .empty-tips {
- padding: 60rpx 0;
- justify-content: center;
- align-items: center;
- .empty-text {
- font-size: 26rpx;
- color: #c9cdd4;
- }
- }
- </style>
|