495 lines
12 KiB
Vue
495 lines
12 KiB
Vue
<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>
|