Files
todo-frontend/src/pages/index/index.vue
Agent ef4dceba1b
All checks were successful
continuous-integration/drone/push Build is passing
fix: 按钮等间距分布,宽度调为18%
2026-03-28 12:54:55 +00:00

582 lines
14 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>
<view class="page">
<!-- 顶部欢迎区域 -->
<view class="header">
<view class="welcome-section">
<text class="welcome-text">欢迎回来</text>
<text class="username">{{ userInfo.username || '用户' }}</text>
</view>
<view class="role-badge" :class="roleClass">
<Icon name="user" :size="24" color="#fff" />
<text class="role-text">{{ roleText }}</text>
</view>
</view>
<!-- 快捷操作 - 管理员/销售 -->
<view class="stats-card" v-if="!isCustomer && !isGuest">
<view class="card-header">
<text class="card-title">今日概览</text>
</view>
<view class="stats-grid">
<view class="stat-item">
<view class="stat-icon order-icon">
<Icon name="order" :size="40" color="#667eea" />
</view>
<view class="stat-info">
<text class="stat-value">{{ stats.orderCount || 0 }}</text>
<text class="stat-label">今日订单</text>
</view>
</view>
<view class="stat-item">
<view class="stat-icon money-icon">
<Icon name="money" :size="40" color="#fa8c16" />
</view>
<view class="stat-info">
<text class="stat-value">¥{{ stats.actualAmount || 0 }}</text>
<text class="stat-label">今日销售额</text>
</view>
</view>
<view class="stat-item">
<view class="stat-icon alert-icon">
<Icon name="alert" :size="40" color="#ff4d4f" />
</view>
<view class="stat-info">
<text class="stat-value">{{ stats.stockAlerts || 0 }}</text>
<text class="stat-label">库存预警</text>
</view>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<!-- 订单相关 -->
<text class="section-title">订单</text>
<view class="menu-grid">
<!-- 管理员菜单 -->
<template v-if="isAdmin">
<view class="menu-card" @click="goTo('/pages/order/create')">
<Icon name="edit" :size="36" color="#fff" />
<text class="menu-card-title">创建订单</text>
</view>
<view class="menu-card" @click="goToTab('/pages/order/list')">
<Icon name="order" :size="36" color="#fff" />
<text class="menu-card-title">订单列表</text>
</view>
<view class="menu-card" @click="goTo('/pages/order/search')">
<Icon name="search" :size="36" color="#fff" />
<text class="menu-card-title">订单查询</text>
</view>
<view class="menu-card" @click="goTo('/pages/order/return')">
<Icon name="right" :size="36" color="#fff" />
<text class="menu-card-title">退货</text>
</view>
</template>
<!-- 销售菜单 -->
<template v-else-if="isSales">
<view class="menu-card" @click="goTo('/pages/order/create')">
<Icon name="edit" :size="36" color="#fff" />
<text class="menu-card-title">创建订单</text>
</view>
<view class="menu-card" @click="goToTab('/pages/order/list')">
<Icon name="order" :size="36" color="#fff" />
<text class="menu-card-title">订单列表</text>
</view>
</template>
<!-- 顾客菜单 -->
<template v-else-if="isCustomer">
<view class="menu-card" @click="goToTab('/pages/order/list')">
<Icon name="order" :size="36" color="#fff" />
<text class="menu-card-title">我的订单</text>
</view>
</template>
</view>
<!-- 管理相关 -->
<text class="section-title" style="margin-top: 30rpx;">管理</text>
<view class="menu-grid">
<!-- 管理员菜单 -->
<template v-if="isAdmin">
<view class="menu-card" @click="goTo('/pages/product/manage')">
<Icon name="product" :size="36" color="#fff" />
<text class="menu-card-title">商品管理</text>
</view>
<view class="menu-card" @click="goTo('/pages/category/index')">
<Icon name="setting" :size="36" color="#fff" />
<text class="menu-card-title">种类管理</text>
</view>
<view class="menu-card" @click="goStock()">
<Icon name="stock" :size="36" color="#fff" />
<text class="menu-card-title">库存管理</text>
</view>
<view class="menu-card" @click="goTo('/pages/stock/in')">
<Icon name="plus" :size="36" color="#fff" />
<text class="menu-card-title">入库</text>
</view>
<view class="menu-card" @click="goTo('/pages/stock/flow')">
<Icon name="flow" :size="36" color="#fff" />
<text class="menu-card-title">库存流水</text>
</view>
</template>
<!-- 销售菜单 -->
<template v-else-if="isSales">
<view class="menu-card" @click="goTo('/pages/product/list')">
<Icon name="product" :size="36" color="#fff" />
<text class="menu-card-title">商品浏览</text>
</view>
</template>
<!-- 顾客菜单 -->
<template v-else-if="isCustomer">
<view class="menu-card" @click="goTo('/pages/product/list')">
<Icon name="product" :size="36" color="#fff" />
<text class="menu-card-title">商品浏览</text>
</view>
</template>
<!-- 游客菜单 -->
<template v-else-if="isGuest">
<view class="guest-card" @click="goTo('/pages/login/index')">
<Icon name="user" :size="80" color="#fff" />
<text class="guest-text">点击登录</text>
</view>
</template>
</view>
</view>
<!-- 提示区域 -->
<view class="tips-section" v-if="isCustomer">
<view class="tip-card">
<text class="tip-title">温馨提示</text>
<view class="tip-list">
<text class="tip-item"> 您可以浏览商品</text>
<text class="tip-item"> 您可以查看半年内的订单</text>
<text class="tip-item"> 如需下单请联系销售人员</text>
</view>
</view>
</view>
<view class="tips-section" v-if="isGuest">
<view class="tip-card">
<text class="tip-title">欢迎使用</text>
<view class="tip-list">
<text class="tip-item"> 请登录后使用完整功能</text>
<text class="tip-item"> 登录后可浏览商品和查看订单</text>
</view>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section" v-if="!isGuest">
<button class="logout-btn" @click="logout">
<Icon name="logout" :size="32" color="#fff" style="margin-right: 10rpx" />
退出登录
</button>
</view>
</view>
</template>
<script>
import authApi from '@/api/auth'
import orderApi from '@/api/order'
import productApi from '@/api/product'
import { getRole, isAdmin, isSales, isCustomer as checkIsCustomer, isGuest as checkIsGuest, canManageProduct, canCreateOrder, canViewStats } from '@/utils/auth'
export default {
data() {
return {
userInfo: {},
role: 'guest',
isAdmin: false,
isSales: false,
isCustomer: false,
isGuest: false,
stats: {
orderCount: 0,
actualAmount: 0,
stockAlerts: 0
}
}
},
computed: {
roleText() {
if (this.isAdmin) return '管理员'
if (this.isSales) return '销售员'
if (this.isCustomer) return '顾客'
return '游客'
},
roleClass() {
if (this.isAdmin) return 'admin'
if (this.isCustomer) return 'customer'
return ''
}
},
onLoad() {
this.role = getRole()
this.isAdmin = isAdmin()
this.isSales = isSales()
this.isCustomer = checkIsCustomer()
this.isGuest = checkIsGuest()
this.loadUserInfo()
if (canViewStats()) {
this.loadStats()
}
},
methods: {
async loadUserInfo() {
const localRole = uni.getStorageSync('role')
if (localRole) {
this.userInfo = {
username: localRole === 'admin' ? '管理员' : '顾客',
role: localRole
}
return
}
try {
const userInfo = await authApi.getCurrentUser()
this.userInfo = userInfo
} catch (e) {
console.error(e)
}
},
async loadStats() {
try {
const today = new Date().toISOString().split('T')[0]
const stats = await orderApi.getStatistics({ startDate: today })
this.stats.orderCount = stats.orderCount || 0
this.stats.actualAmount = stats.actualAmount || 0
const alerts = await productApi.getStockAlerts()
this.stats.stockAlerts = alerts ? alerts.length : 0
} catch (e) {
console.error(e)
}
},
goTo(url) {
uni.navigateTo({ url })
},
goToTab(url) {
uni.switchTab({ url })
},
goStock() {
uni.navigateTo({ url: '/pages/stock/list' })
},
async logout() {
try {
await authApi.logout()
} catch (e) {
console.error(e)
}
uni.removeStorageSync('token')
uni.removeStorageSync('userId')
uni.removeStorageSync('role')
uni.reLaunch({ url: '/pages/login/index' })
}
}
}
</script>
<style>
.page {
min-height: 100vh;
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
padding: 30rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 20rpx;
margin-bottom: 30rpx;
}
.welcome-section {
display: flex;
flex-direction: column;
}
.welcome-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 8rpx;
}
.username {
font-size: 48rpx;
font-weight: bold;
color: #fff;
}
.role-badge {
display: flex;
align-items: center;
padding: 12rpx 24rpx;
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
}
.role-badge.admin {
background: rgba(255, 215, 0, 0.3);
}
.role-badge.customer {
background: rgba(255, 255, 255, 0.2);
}
.role-text {
font-size: 24rpx;
color: #fff;
margin-left: 8rpx;
}
.stats-card {
background: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
}
.card-header {
margin-bottom: 20rpx;
}
.card-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.stats-grid {
display: flex;
justify-content: space-between;
}
.stat-item {
display: flex;
align-items: center;
flex: 1;
}
.stat-icon {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
}
.order-icon {
background: #e6f7ff;
}
.money-icon {
background: #fff7e6;
}
.alert-icon {
background: #fff1f0;
}
.stat-info {
display: flex;
flex-direction: column;
}
.stat-value {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.stat-label {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.menu-section {
margin-bottom: 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #fff;
margin-bottom: 20rpx;
display: block;
}
.menu-grid {
display: flex;
flex-wrap: wrap;
}
/* 手机端 - 等间距布局 */
.menu-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding: 10rpx;
}
.menu-card {
width: 18%;
aspect-ratio: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12rpx;
margin: 8rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6rpx;
}
.menu-card:active {
opacity: 0.9;
}
.menu-card-title {
font-size: 20rpx;
color: #fff;
font-weight: bold;
margin-top: 6rpx;
}
@media (min-width: 768px) {
.menu-card {
width: 15%;
}
}
.menu-card:active {
transform: scale(0.98);
opacity: 0.9;
}
.menu-card-icon {
width: 50rpx;
height: 50rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6rpx;
}
.menu-card-icon.blue {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.menu-card-icon.green {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.menu-card-icon.orange {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.menu-card-icon.red {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.menu-card-icon.purple {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.menu-card-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.menu-card-desc {
font-size: 22rpx;
color: #999;
}
.guest-card {
width: 100%;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 24rpx;
padding: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
border: 2rpx dashed rgba(255, 255, 255, 0.5);
}
.guest-icon {
margin-bottom: 20rpx;
}
.guest-text {
font-size: 32rpx;
font-weight: bold;
color: #fff;
margin-bottom: 10rpx;
}
.guest-desc {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
}
.tips-section {
margin-bottom: 30rpx;
}
.tip-card {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 24rpx;
padding: 30rpx;
}
.tip-title {
font-size: 28rpx;
font-weight: bold;
color: #fff;
display: block;
margin-bottom: 16rpx;
}
.tip-list {
display: flex;
flex-direction: column;
}
.tip-item {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 36rpx;
}
.logout-section {
padding: 0 20rpx;
}
.logout-btn {
width: 100%;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 2rpx solid rgba(255, 255, 255, 0.3);
border-radius: 50rpx;
padding: 24rpx;
color: #fff;
font-size: 28rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.logout-btn:active {
background: rgba(255, 255, 255, 0.3);
}
</style>