495 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<scroll-view scroll-y class="page">
<!-- 顶部欢迎与Logo -->
<view class="header card">
<view class="header-left">
<text class="hello">您好欢迎使用</text>
<text class="app-name">卓越智慧配送</text>
<text class="sub">高效 · 可视 · 可追溯</text>
</view>
<view class="header-right">
<image class="logo" src="/static/logo.png" mode="aspectFit" />
<button v-if="!isLoggedIn" class="login-btn" @tap="goLogin">登录</button>
<view v-else class="user-box">
<text class="user-name">{{ staff?.name || '已登录' }}</text>
<button class="logout-btn" @tap="logout">退出</button>
</view>
</view>
</view>
<!-- 顶部滑动轮播图 -->
<view class="swiper-card card">
<swiper class="swiper" :current="swiperIndex" @change="changeSwiper" previous-margin="50rpx" next-margin="50rpx"
:indicator-dots="true" indicator-color="rgba(51,51,51,0.25)" indicator-active-color="#4b7aff" autoplay
interval="3000" circular>
<swiper-item v-for="(img, idx) in swiperImgs" :key="idx">
<view class="swiper-item-wrap" :class="{ 'swiper-scale': swiperIndex !== idx }">
<image class="swiper-img" :src="img" mode="aspectFill" />
</view>
</swiper-item>
</swiper>
</view>
<!-- 主操作 -->
<view class="ops">
<view class="op-btn" v-show="isPatient || isDoctor" @tap="goStretcherApply">
<image class="op-icon" src="/static/icons/stretcher-loan.png" />
<text class="op-text">担架申请</text>
</view>
<view class="op-btn" v-show="isPatient || isDoctor || isRider" @tap="goStretcherApplyFor">
<image class="op-icon" src="/static/icons/stretcher-temp-loan.png" />
<text class="op-text">担架代申请</text>
</view>
</view>
<!-- 资产管理可折叠卡片明确从属关系 -->
<view class="section-card card">
<view class="section-header" @tap="toggleAsset">
<view class="section-left">
<image class="section-icon" src="/static/icons/asset-management.png" />
<view class="section-text">
<text class="section-title-text">资产管理</text>
<text class="section-sub">固定资产借用盘点扫码易耗品</text>
</view>
</view>
<view class="chevron" :class="{ open: assetOpen }"></view>
</view>
<view class="asset-grid" v-show="assetOpen">
<view class="grid-item" v-for="item in assetItems" :key="item.key" @tap="handleAsset(item.key)">
<image class="grid-icon" :src="item.icon" />
<text class="grid-text">{{ item.text }}</text>
</view>
</view>
</view>
<!-- 公告担架收费标准 -->
<view class="section-title">
<text>公告 · 担架收费标准</text>
</view>
<view class="notice card" @tap="previewCharge">
<image class="notice-img" src="/static/stretcher-charge-standard.png" mode="widthFix" />
<text class="notice-tip">点击可放大查看</text>
</view>
</scroll-view>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
// @ts-ignore JS模块无类型声明
import queryService from '@/service/queryService.js'
// @ts-ignore JS模块无类型声明
import config, { AssetRole } from '@/config'
// @ts-ignore JS模块无类型声明
import * as staffRole from '@/constant/staffRole.js'
// @ts-ignore JS模块无类型声明
import util from '@/utils/util.js'
// 资产管理展开状态
const assetOpen = ref(true)
// 资产管理宫格项
const assetItems = computed(() => {
const role = assetsRole.value || 0
return [
{ key: 'fixed', text: '固定资产', icon: '/static/icons/fixed-assets.png' },
{ key: 'mine', text: '我的借用', icon: '/static/icons/my-loans.png' },
{ key: 'scan', text: '扫码查看信息', icon: '/static/icons/scan-to-view-info.png' },
{ key: 'inventory', text: '资产盘点', icon: '/static/icons/asset-inventory.png' },
{ key: 'plan', text: '易耗品领用计划', icon: '/static/icons/consumables-plan.png' },
{ key: 'consumable', text: '易耗品盘点', icon: '/static/icons/consumables-inventory.png' },
{ key: 'lent', text: '出借资产管理', icon: '/static/icons/lent-assets-management.png', show: !!(role & AssetRole.TEAM_LEAD) },
{ key: 'request', text: '临时易耗品申请', icon: '/static/icons/consumables-temp-request.png', show: !!(role & AssetRole.TEAM_LEAD) },
{ key: 'approve', text: '临时易耗品申请审批', icon: '/static/icons/consumables-temp-approve.png', show: !!(role & AssetRole.CONSUMABLES_MANAGER) },
].filter(item => item.show !== false)
})
// 轮播图状态
const swiperIndex = ref(0)
const swiperImgs = ref<string[]>([
'/static/logo.png',
'/static/logo.png',
'/static/logo.png'
])
function changeSwiper(e: any) {
swiperIndex.value = e.detail.current || 0
}
async function loadSwiperImages() {
try {
const res = await queryService.getAppSlipPicUrl()
if (res?.data?.code === 0) {
const dataStr: string = res?.data?.data || ''
const pics = dataStr ? dataStr.split(';').filter(Boolean) : []
if (pics.length >= 3) {
// 取最后三张,保持与老页面一致的顺序
const last3 = pics.slice(-3)
swiperImgs.value = last3.map(p => `${config.picturePath}${p}`)
} else if (pics.length > 0) {
// 不足3张时重复填充
const filled: string[] = []
while (filled.length < 3) {
const next = pics[filled.length % pics.length]
filled.push(`${config.picturePath}${next}`)
}
swiperImgs.value = filled
}
}
} catch (err) {
// 出错则保留默认占位图
console.warn('加载轮播图失败:', err)
}
}
onMounted(() => {
loadSwiperImages()
syncLoginState()
})
// 登录与角色态
const isLoggedIn = ref(false)
const staff = ref<any | null>(null)
const assetsRole = ref<number | null>(null) // 资产系统用户信息
const isWeixin = ref(false)
const isPatient = ref(true)
const isDoctor = ref(false)
const isRider = ref(false)
function syncLoginState() {
staff.value = uni.getStorageSync('staff') || null
assetsRole.value = uni.getStorageSync('assetsRole') || null // 资产系统用户信息
isLoggedIn.value = !!staff.value
// 平台
// @ts-ignore
isWeixin.value = process?.env?.VUE_APP_PLATFORM === 'mp-weixin'
// 角色判定(简化:有担架运送角色则认为骑手,否则当作患者/医生)
if (staff.value && Array.isArray(staff.value.role_ids)) {
const set = staffRole.STRETCHER_CONVEY
const hasRider = staff.value.role_ids.some((r: string) => set.has(r))
isRider.value = hasRider
isPatient.value = !hasRider
isDoctor.value = !hasRider
} else {
isRider.value = false
isPatient.value = true
isDoctor.value = false
}
}
function goLogin() {
uni.navigateTo({ url: '/pages/login/index' })
}
function logout() {
uni.setStorageSync('staff', null)
uni.setStorageSync('token', null)
isLoggedIn.value = false
staff.value = null
isRider.value = false
isPatient.value = true
isDoctor.value = false
uni.showToast({ title: '已退出登录', icon: 'none' })
}
// 导航占位:根据后续页面路由替换
function goStretcherApply() {
uni.navigateTo({ url: '/pages/stretcher/apply/index' })
}
function goStretcherApplyFor() {
uni.navigateTo({ url: '/pages/stretcher/apply-agent/index' })
}
function toggleAsset() {
assetOpen.value = !assetOpen.value
}
function handleAsset(key: string) {
const item = assetItems.value.find((i: any) => i.key === key)
if (!item) return
if (key === 'fixed') {
uni.navigateTo({ url: '/pages/fixed-assets/index' })
return
}
if (key === 'mine') {
uni.navigateTo({ url: '/pages/fixed-assets/my-borrows' })
return
}
if (key === 'inventory') {
uni.navigateTo({ url: '/pages/fixed-assets/inventory-plan' })
return
}
if (key === 'plan') {
uni.navigateTo({ url: '/pages/consumables/plan' })
return
}
if (key === 'consumable') {
uni.navigateTo({ url: '/pages/consumables/inventory-plan' })
return
}
if (key === 'scan') {
uni.navigateTo({ url: '/pages/fixed-assets/scan' })
return
}
if (key === 'lent') {
uni.showToast({ title: `打开:${item.text}`, icon: 'none' })
return
}
if (key === 'request') {
uni.navigateTo({ url: '/pages/consumables/temp-request' })
return
}
if (key === 'approve') {
uni.navigateTo({ url: '/pages/consumables/temp-approve' })
return
}
// 其他入口可在此对接具体页面
uni.showToast({ title: `打开:${item.text}`, icon: 'none' })
}
function previewCharge() {
uni.previewImage({
urls: ['/static/stretcher-charge-standard.png']
})
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
}
.card {
background: #fff;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}
.header {
margin: 32rpx;
padding: 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.header-left {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.hello {
color: #7a7a7a;
font-size: 26rpx;
}
.app-name {
color: #111;
font-size: 40rpx;
font-weight: 600;
}
.sub {
color: #9a9aa0;
font-size: 24rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
}
.header-right {
display: flex;
align-items: center;
gap: 12rpx;
}
.login-btn,
.logout-btn {
padding: 0rpx 20rpx;
background: #3a5ddd;
color: #fff;
border-radius: 10rpx;
font-size: 24rpx;
}
.logout-btn {
background: #ef5350;
}
.user-box {
display: flex;
align-items: center;
gap: 12rpx;
flex-direction: column;
}
.user-name {
font-size: 24rpx;
color: #333;
}
/* 轮播卡片 */
.swiper-card {
margin: 0 32rpx 24rpx;
padding: 12rpx;
}
.swiper {
width: 100%;
height: 300rpx;
}
.swiper-item-wrap {
width: 100%;
height: 100%;
border-radius: 24rpx;
overflow: hidden;
transition: transform 0.25s ease, box-shadow 0.25s ease;
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.08);
}
.swiper-scale {
transform: scale(0.95);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}
.swiper-img {
width: 100%;
height: 100%;
display: block;
}
.ops {
margin: 0 32rpx 24rpx;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24rpx;
}
.op-btn {
background: linear-gradient(180deg, #f9fbff 0%, #ffffff 100%);
border-radius: 20rpx;
padding: 28rpx 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
}
.op-icon {
width: 80rpx;
height: 80rpx;
}
.op-text {
font-size: 26rpx;
color: #333;
}
.section-card {
margin: 0 32rpx 24rpx;
}
.section-header {
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.section-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.section-icon {
width: 48rpx;
height: 48rpx;
}
.section-text {
display: flex;
flex-direction: column;
}
.section-title-text {
font-size: 30rpx;
color: #222;
font-weight: 600;
}
.section-sub {
font-size: 22rpx;
color: #9a9aa0;
}
.chevron {
width: 24rpx;
height: 24rpx;
border-right: 4rpx solid #b5b6ba;
border-bottom: 4rpx solid #b5b6ba;
transform: rotate(45deg);
transition: transform 0.2s ease;
}
.chevron.open {
transform: rotate(135deg);
}
.asset-grid {
padding: 0 24rpx 24rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
}
.grid-item {
background: #fafbff;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24rpx 12rpx;
gap: 10rpx;
}
.grid-icon {
width: 64rpx;
height: 64rpx;
}
.grid-text {
font-size: 24rpx;
color: #444;
text-align: center;
line-height: 1.4;
}
.section-title {
padding: 0 32rpx 12rpx;
color: #666;
font-size: 26rpx;
}
.notice {
margin: 0 32rpx 32rpx;
padding: 20rpx;
text-align: center;
}
.notice-img {
width: 100%;
border-radius: 12rpx;
}
.notice-tip {
display: block;
color: #9a9aa0;
font-size: 22rpx;
margin-top: 12rpx;
}
</style>