修改政府端前端,银行端小程序和后端接口

This commit is contained in:
2025-09-26 17:52:50 +08:00
parent 852adbcfff
commit 00dfa83fd1
237 changed files with 9172 additions and 33500 deletions

View File

@@ -1,34 +0,0 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
mounted() {
// 应用启动时的初始化逻辑
console.log('政府端小程序启动')
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f6f6f6;
}
</style>

View File

@@ -1,193 +0,0 @@
// 全局样式文件
@import './styles/variables.scss';
@import './styles/mixins.scss';
// 重置样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
background-color: #f6f6f6;
}
// 通用类
.container {
padding: 20rpx;
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
font-weight: 500;
color: #666;
margin-bottom: 16rpx;
}
.text {
font-size: 26rpx;
color: #999;
line-height: 1.5;
}
.btn {
display: inline-block;
padding: 20rpx 40rpx;
border-radius: 8rpx;
text-align: center;
font-size: 28rpx;
border: none;
cursor: pointer;
transition: all 0.3s;
&.primary {
background: #1890ff;
color: #fff;
&:hover {
background: #40a9ff;
}
}
&.success {
background: #52c41a;
color: #fff;
&:hover {
background: #73d13d;
}
}
&.warning {
background: #faad14;
color: #fff;
&:hover {
background: #ffc53d;
}
}
&.danger {
background: #ff4d4f;
color: #fff;
&:hover {
background: #ff7875;
}
}
}
.flex {
display: flex;
&.center {
align-items: center;
justify-content: center;
}
&.between {
justify-content: space-between;
}
&.around {
justify-content: space-around;
}
&.column {
flex-direction: column;
}
}
.mt-10 { margin-top: 10rpx; }
.mt-20 { margin-top: 20rpx; }
.mt-30 { margin-top: 30rpx; }
.mb-10 { margin-bottom: 10rpx; }
.mb-20 { margin-bottom: 20rpx; }
.mb-30 { margin-bottom: 30rpx; }
.ml-10 { margin-left: 10rpx; }
.ml-20 { margin-left: 20rpx; }
.mr-10 { margin-right: 10rpx; }
.mr-20 { margin-right: 20rpx; }
.p-10 { padding: 10rpx; }
.p-20 { padding: 20rpx; }
.p-30 { padding: 30rpx; }
// 加载状态
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
color: #999;
}
// 空状态
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
color: #999;
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
}
}
// 列表项
.list-item {
display: flex;
align-items: center;
padding: 30rpx 20rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.item-content {
flex: 1;
}
.item-title {
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.item-desc {
font-size: 24rpx;
color: #999;
}
.item-arrow {
font-size: 24rpx;
color: #ccc;
}
}

View File

@@ -1,510 +0,0 @@
<template>
<div class="approval-container">
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-bar">
<input
v-model="searchKeyword"
type="text"
placeholder="搜索审批记录..."
class="search-input"
@input="onSearchInput"
/>
<div class="search-icon">🔍</div>
</div>
<div class="filter-btn" @click="showFilter = true">
<text class="filter-text">筛选</text>
<div class="filter-icon"></div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div
v-for="stat in statsCards"
:key="stat.key"
class="stat-card"
:class="stat.type"
>
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-content">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</div>
<!-- 审批列表 -->
<div class="approval-list">
<div class="list-header">
<div class="list-title">审批记录</div>
<div class="add-btn" @click="handleAdd">
<text class="add-text">新增</text>
<div class="add-icon">+</div>
</div>
</div>
<div v-if="loading" class="loading">
<text class="loading-text">加载中...</text>
</div>
<div v-else-if="approvalList.length === 0" class="empty">
<div class="empty-icon">📋</div>
<div class="empty-text">暂无审批记录</div>
</div>
<div v-else class="list-content">
<div
v-for="item in approvalList"
:key="item.id"
class="approval-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<div class="item-title">{{ item.title }}</div>
<div class="item-status" :class="item.status">
{{ item.statusText }}
</div>
</div>
<div class="item-content">
<div class="item-desc">{{ item.description }}</div>
<div class="item-meta">
<div class="item-applicant">申请人{{ item.applicant }}</div>
<div class="item-time">{{ item.createTime }}</div>
</div>
</div>
<div class="item-actions">
<div v-if="item.status === 'pending'" class="action-btn approve" @click.stop="handleApprove(item)">
<text class="action-text">通过</text>
</div>
<div v-if="item.status === 'pending'" class="action-btn reject" @click.stop="handleReject(item)">
<text class="action-text">拒绝</text>
</div>
<div class="action-btn edit" @click.stop="handleEdit(item)">
<text class="action-text">编辑</text>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { approvalService } from '@/services/approvalService'
export default {
name: 'Approval',
data() {
return {
searchKeyword: '',
showFilter: false,
loading: false,
statsCards: [
{ key: 'total', icon: '📋', label: '总审批', value: '0', type: 'primary' },
{ key: 'pending', icon: '⏳', label: '待审批', value: '0', type: 'warning' },
{ key: 'approved', icon: '✅', label: '已通过', value: '0', type: 'success' },
{ key: 'rejected', icon: '❌', label: '已拒绝', value: '0', type: 'danger' }
],
approvalList: []
}
},
onLoad() {
this.loadApprovalData()
},
onPullDownRefresh() {
this.loadApprovalData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadApprovalData() {
this.loading = true
try {
const [list, stats] = await Promise.all([
approvalService.getApprovalList({
keyword: this.searchKeyword
}),
approvalService.getStats()
])
this.approvalList = list || []
this.updateStatsCards(stats)
} catch (error) {
console.error('加载审批数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
} finally {
this.loading = false
}
},
updateStatsCards(data) {
this.statsCards = this.statsCards.map(card => {
const newValue = data[card.key] || card.value
return {
...card,
value: newValue
}
})
},
onSearchInput(e) {
this.searchKeyword = e.detail.value
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.loadApprovalData()
}, 500)
},
handleAdd() {
uni.navigateTo({
url: '/pages/approval-add/approval-add'
})
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/approval-detail/approval-detail?id=${item.id}`
})
},
handleEdit(item) {
uni.navigateTo({
url: `/pages/approval-edit/approval-edit?id=${item.id}`
})
},
async handleApprove(item) {
const result = await uni.showModal({
title: '确认通过',
content: '确定要通过这个审批吗?',
confirmText: '通过',
confirmColor: '#52c41a'
})
if (result.confirm) {
try {
await approvalService.approve(item.id)
uni.showToast({
title: '审批通过',
icon: 'success'
})
this.loadApprovalData()
} catch (error) {
console.error('审批失败:', error)
uni.showToast({
title: '操作失败',
icon: 'error'
})
}
}
},
async handleReject(item) {
const result = await uni.showModal({
title: '确认拒绝',
content: '确定要拒绝这个审批吗?',
confirmText: '拒绝',
confirmColor: '#ff4d4f'
})
if (result.confirm) {
try {
await approvalService.reject(item.id)
uni.showToast({
title: '审批拒绝',
icon: 'success'
})
this.loadApprovalData()
} catch (error) {
console.error('操作失败:', error)
uni.showToast({
title: '操作失败',
icon: 'error'
})
}
}
}
}
}
</script>
<style scoped>
.approval-container {
min-height: 100vh;
background: #f6f6f6;
}
.search-section {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
}
.search-bar {
flex: 1;
position: relative;
margin-right: 20rpx;
}
.search-input {
width: 100%;
height: 72rpx;
background: #f8f9fa;
border: none;
border-radius: 36rpx;
padding: 0 60rpx 0 30rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
}
.filter-btn {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #1890ff;
border-radius: 36rpx;
color: #fff;
}
.filter-text {
font-size: 28rpx;
margin-right: 8rpx;
}
.filter-icon {
font-size: 24rpx;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 20rpx 20rpx;
}
.stat-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.stat-card.primary {
border-left: 8rpx solid #1890ff;
}
.stat-card.warning {
border-left: 8rpx solid #faad14;
}
.stat-card.success {
border-left: 8rpx solid #52c41a;
}
.stat-card.danger {
border-left: 8rpx solid #ff4d4f;
}
.stat-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.approval-list {
background: #fff;
margin: 0 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #1890ff;
border-radius: 24rpx;
color: #fff;
}
.add-text {
font-size: 24rpx;
margin-right: 8rpx;
}
.add-icon {
font-size: 24rpx;
font-weight: 600;
}
.loading,
.empty {
display: flex;
align-items: center;
justify-content: center;
padding: 80rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.list-content {
space-y: 0;
}
.approval-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.approval-item:active {
background: #f8f9fa;
}
.approval-item:last-child {
border-bottom: none;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.item-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.item-status {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.item-status.pending {
background: #fff7e6;
color: #faad14;
}
.item-status.approved {
background: #f6ffed;
color: #52c41a;
}
.item-status.rejected {
background: #fff2f0;
color: #ff4d4f;
}
.item-content {
margin-bottom: 20rpx;
}
.item-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
line-height: 1.5;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.item-applicant,
.item-time {
font-size: 22rpx;
color: #999;
}
.item-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 22rpx;
text-align: center;
}
.action-btn.approve {
background: #f6ffed;
color: #52c41a;
}
.action-btn.reject {
background: #fff2f0;
color: #ff4d4f;
}
.action-btn.edit {
background: #e6f7ff;
color: #1890ff;
}
</style>

View File

@@ -1,424 +0,0 @@
<template>
<div class="dashboard-container">
<!-- 数据概览卡片 -->
<div class="overview-cards">
<div
v-for="card in overviewCards"
:key="card.key"
class="overview-card"
:class="card.type"
>
<div class="card-icon">{{ card.icon }}</div>
<div class="card-content">
<div class="card-value">{{ card.value }}</div>
<div class="card-label">{{ card.label }}</div>
<div class="card-trend" :class="card.trend">
{{ card.trendText }}
</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-section">
<div class="section-title">数据图表</div>
<div class="chart-container">
<div class="chart-placeholder">
<div class="chart-icon">📊</div>
<div class="chart-text">图表数据加载中...</div>
</div>
</div>
</div>
<!-- 监管统计 -->
<div class="supervision-stats">
<div class="section-title">监管统计</div>
<div class="stats-list">
<div
v-for="stat in supervisionStats"
:key="stat.key"
class="stat-item"
>
<div class="stat-info">
<div class="stat-name">{{ stat.name }}</div>
<div class="stat-desc">{{ stat.desc }}</div>
</div>
<div class="stat-value">{{ stat.value }}</div>
</div>
</div>
</div>
<!-- 最近活动 -->
<div class="recent-activities">
<div class="section-title">最近活动</div>
<div class="activity-list">
<div
v-for="activity in recentActivities"
:key="activity.id"
class="activity-item"
>
<div class="activity-icon">{{ activity.icon }}</div>
<div class="activity-content">
<div class="activity-title">{{ activity.title }}</div>
<div class="activity-desc">{{ activity.desc }}</div>
<div class="activity-time">{{ activity.time }}</div>
</div>
<div class="activity-status" :class="activity.status">
{{ activity.statusText }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { dashboardService } from '@/services/dashboardService'
export default {
name: 'Dashboard',
data() {
return {
overviewCards: [
{
key: 'supervision',
icon: '🔍',
label: '监管记录',
value: '0',
trend: 'up',
trendText: '+0%',
type: 'primary'
},
{
key: 'approval',
icon: '✅',
label: '待审批',
value: '0',
trend: 'up',
trendText: '+0%',
type: 'success'
},
{
key: 'personnel',
icon: '👥',
label: '人员总数',
value: '0',
trend: 'up',
trendText: '+0%',
type: 'warning'
},
{
key: 'epidemic',
icon: '🦠',
label: '疫情预警',
value: '0',
trend: 'down',
trendText: '-0%',
type: 'danger'
}
],
supervisionStats: [
{ key: 'total', name: '总监管数', desc: '累计监管记录', value: '0' },
{ key: 'today', name: '今日监管', desc: '今日新增记录', value: '0' },
{ key: 'pending', name: '待处理', desc: '待处理事项', value: '0' },
{ key: 'completed', name: '已完成', desc: '已完成事项', value: '0' }
],
recentActivities: [
{
id: 1,
icon: '🔍',
title: '监管检查',
desc: '完成养殖场A的例行检查',
time: '2小时前',
status: 'success',
statusText: '已完成'
},
{
id: 2,
icon: '✅',
title: '审批通过',
desc: '通过养殖许可证申请',
time: '4小时前',
status: 'success',
statusText: '已通过'
},
{
id: 3,
icon: '⏳',
title: '待审批',
desc: '养殖场B的扩建申请',
time: '6小时前',
status: 'pending',
statusText: '待处理'
}
]
}
},
onLoad() {
this.loadDashboardData()
},
onPullDownRefresh() {
this.loadDashboardData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadDashboardData() {
try {
// 加载统计数据
const [stats, supervisionStats] = await Promise.all([
dashboardService.getStats(),
dashboardService.getSupervisionStats()
])
if (stats) {
this.updateOverviewCards(stats)
}
if (supervisionStats) {
this.updateSupervisionStats(supervisionStats)
}
} catch (error) {
console.error('加载看板数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
}
},
updateOverviewCards(data) {
this.overviewCards = this.overviewCards.map(card => {
const newValue = data[card.key] || card.value
return {
...card,
value: newValue
}
})
},
updateSupervisionStats(data) {
this.supervisionStats = this.supervisionStats.map(stat => {
const newValue = data[stat.key] || stat.value
return {
...stat,
value: newValue
}
})
}
}
}
</script>
<style scoped>
.dashboard-container {
min-height: 100vh;
background: #f6f6f6;
padding: 20rpx;
}
.overview-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
margin-bottom: 30rpx;
}
.overview-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.overview-card.primary {
border-left: 8rpx solid #1890ff;
}
.overview-card.success {
border-left: 8rpx solid #52c41a;
}
.overview-card.warning {
border-left: 8rpx solid #faad14;
}
.overview-card.danger {
border-left: 8rpx solid #ff4d4f;
}
.card-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.card-content {
flex: 1;
}
.card-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.card-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.card-trend {
font-size: 20rpx;
font-weight: 500;
}
.card-trend.up {
color: #52c41a;
}
.card-trend.down {
color: #ff4d4f;
}
.charts-section,
.supervision-stats,
.recent-activities {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
}
.chart-container {
height: 400rpx;
background: #f8f9fa;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.chart-placeholder {
text-align: center;
}
.chart-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.chart-text {
font-size: 28rpx;
color: #999;
}
.stats-list {
space-y: 20rpx;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.stat-item:last-child {
border-bottom: none;
}
.stat-info {
flex: 1;
}
.stat-name {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.stat-desc {
font-size: 24rpx;
color: #999;
}
.stat-value {
font-size: 32rpx;
font-weight: 600;
color: #1890ff;
}
.activity-list {
space-y: 20rpx;
}
.activity-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-icon {
font-size: 32rpx;
margin-right: 20rpx;
width: 60rpx;
text-align: center;
}
.activity-content {
flex: 1;
}
.activity-title {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.activity-desc {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.activity-time {
font-size: 20rpx;
color: #999;
}
.activity-status {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.activity-status.success {
background: #f6ffed;
color: #52c41a;
}
.activity-status.pending {
background: #fff7e6;
color: #faad14;
}
</style>

View File

@@ -1,514 +0,0 @@
<template>
<div class="epidemic-container">
<!-- 疫情预警区域 -->
<div class="alert-section">
<div class="alert-header">
<div class="alert-title">疫情预警</div>
<div class="alert-level" :class="alertLevel">
{{ alertLevelText }}
</div>
</div>
<div class="alert-content">
<div class="alert-stats">
<div class="stat-item">
<div class="stat-value">{{ alertStats.confirmed }}</div>
<div class="stat-label">确诊病例</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ alertStats.suspected }}</div>
<div class="stat-label">疑似病例</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ alertStats.recovered }}</div>
<div class="stat-label">治愈病例</div>
</div>
</div>
</div>
</div>
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-bar">
<input
v-model="searchKeyword"
type="text"
placeholder="搜索疫情记录..."
class="search-input"
@input="onSearchInput"
/>
<div class="search-icon">🔍</div>
</div>
<div class="filter-btn" @click="showFilter = true">
<text class="filter-text">筛选</text>
<div class="filter-icon"></div>
</div>
</div>
<!-- 疫情记录列表 -->
<div class="epidemic-list">
<div class="list-header">
<div class="list-title">疫情记录</div>
<div class="add-btn" @click="handleAdd">
<text class="add-text">新增</text>
<div class="add-icon">+</div>
</div>
</div>
<div v-if="loading" class="loading">
<text class="loading-text">加载中...</text>
</div>
<div v-else-if="epidemicList.length === 0" class="empty">
<div class="empty-icon">🦠</div>
<div class="empty-text">暂无疫情记录</div>
</div>
<div v-else class="list-content">
<div
v-for="item in epidemicList"
:key="item.id"
class="epidemic-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<div class="item-title">{{ item.title }}</div>
<div class="item-level" :class="item.level">
{{ item.levelText }}
</div>
</div>
<div class="item-content">
<div class="item-desc">{{ item.description }}</div>
<div class="item-meta">
<div class="item-location">📍 {{ item.location }}</div>
<div class="item-time">{{ item.createTime }}</div>
</div>
</div>
<div class="item-actions">
<div class="action-btn edit" @click.stop="handleEdit(item)">
<text class="action-text">编辑</text>
</div>
<div class="action-btn delete" @click.stop="handleDelete(item)">
<text class="action-text">删除</text>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { epidemicService } from '@/services/epidemicService'
export default {
name: 'Epidemic',
data() {
return {
searchKeyword: '',
showFilter: false,
loading: false,
alertLevel: 'low',
alertLevelText: '低风险',
alertStats: {
confirmed: 0,
suspected: 0,
recovered: 0
},
epidemicList: []
}
},
onLoad() {
this.loadEpidemicData()
},
onPullDownRefresh() {
this.loadEpidemicData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadEpidemicData() {
this.loading = true
try {
const [list, stats] = await Promise.all([
epidemicService.getEpidemicList({
keyword: this.searchKeyword
}),
epidemicService.getStats()
])
this.epidemicList = list || []
this.updateAlertStats(stats)
} catch (error) {
console.error('加载疫情数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
} finally {
this.loading = false
}
},
updateAlertStats(data) {
if (data) {
this.alertStats = {
confirmed: data.confirmed || 0,
suspected: data.suspected || 0,
recovered: data.recovered || 0
}
// 根据数据更新预警级别
const total = this.alertStats.confirmed + this.alertStats.suspected
if (total === 0) {
this.alertLevel = 'low'
this.alertLevelText = '低风险'
} else if (total < 10) {
this.alertLevel = 'medium'
this.alertLevelText = '中风险'
} else {
this.alertLevel = 'high'
this.alertLevelText = '高风险'
}
}
},
onSearchInput(e) {
this.searchKeyword = e.detail.value
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.loadEpidemicData()
}, 500)
},
handleAdd() {
uni.navigateTo({
url: '/pages/epidemic-add/epidemic-add'
})
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/epidemic-detail/epidemic-detail?id=${item.id}`
})
},
handleEdit(item) {
uni.navigateTo({
url: `/pages/epidemic-edit/epidemic-edit?id=${item.id}`
})
},
async handleDelete(item) {
const result = await uni.showModal({
title: '确认删除',
content: '确定要删除这条疫情记录吗?',
confirmText: '删除',
confirmColor: '#ff4d4f'
})
if (result.confirm) {
try {
await epidemicService.deleteEpidemic(item.id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
this.loadEpidemicData()
} catch (error) {
console.error('删除失败:', error)
uni.showToast({
title: '删除失败',
icon: 'error'
})
}
}
}
}
}
</script>
<style scoped>
.epidemic-container {
min-height: 100vh;
background: #f6f6f6;
}
.alert-section {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.alert-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.alert-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.alert-level {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.alert-level.low {
background: #f6ffed;
color: #52c41a;
}
.alert-level.medium {
background: #fff7e6;
color: #faad14;
}
.alert-level.high {
background: #fff2f0;
color: #ff4d4f;
}
.alert-stats {
display: flex;
justify-content: space-around;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 48rpx;
font-weight: 600;
color: #ff4d4f;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.search-section {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
margin: 0 20rpx 20rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.search-bar {
flex: 1;
position: relative;
margin-right: 20rpx;
}
.search-input {
width: 100%;
height: 72rpx;
background: #f8f9fa;
border: none;
border-radius: 36rpx;
padding: 0 60rpx 0 30rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
}
.filter-btn {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #1890ff;
border-radius: 36rpx;
color: #fff;
}
.filter-text {
font-size: 28rpx;
margin-right: 8rpx;
}
.filter-icon {
font-size: 24rpx;
}
.epidemic-list {
background: #fff;
margin: 0 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #1890ff;
border-radius: 24rpx;
color: #fff;
}
.add-text {
font-size: 24rpx;
margin-right: 8rpx;
}
.add-icon {
font-size: 24rpx;
font-weight: 600;
}
.loading,
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.list-content {
space-y: 0;
}
.epidemic-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.epidemic-item:active {
background: #f8f9fa;
}
.epidemic-item:last-child {
border-bottom: none;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.item-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.item-level {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.item-level.low {
background: #f6ffed;
color: #52c41a;
}
.item-level.medium {
background: #fff7e6;
color: #faad14;
}
.item-level.high {
background: #fff2f0;
color: #ff4d4f;
}
.item-content {
margin-bottom: 20rpx;
}
.item-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
line-height: 1.5;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.item-location,
.item-time {
font-size: 22rpx;
color: #999;
}
.item-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 22rpx;
text-align: center;
}
.action-btn.edit {
background: #e6f7ff;
color: #1890ff;
}
.action-btn.delete {
background: #fff2f0;
color: #ff4d4f;
}
</style>

View File

@@ -1,366 +0,0 @@
<template>
<div class="home-container">
<!-- 头部欢迎区域 -->
<div class="welcome-section">
<div class="welcome-content">
<div class="user-info">
<div class="avatar">
<image src="/static/avatar.png" class="avatar-img" />
</div>
<div class="user-details">
<div class="username">{{ userInfo.name || '管理员' }}</div>
<div class="user-role">{{ userInfo.role || '系统管理员' }}</div>
</div>
</div>
<div class="weather-info">
<div class="weather-icon"></div>
<div class="weather-text"> 25°C</div>
</div>
</div>
</div>
<!-- 快捷功能区域 -->
<div class="quick-actions">
<div class="section-title">快捷功能</div>
<div class="action-grid">
<div
v-for="action in quickActions"
:key="action.key"
class="action-item"
@click="handleAction(action)"
>
<div class="action-icon">{{ action.icon }}</div>
<div class="action-text">{{ action.name }}</div>
</div>
</div>
</div>
<!-- 数据概览区域 -->
<div class="stats-section">
<div class="section-title">数据概览</div>
<div class="stats-grid">
<div
v-for="stat in statsData"
:key="stat.key"
class="stat-item"
>
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
<div class="stat-trend" :class="stat.trend">
{{ stat.trendText }}
</div>
</div>
</div>
</div>
<!-- 最近活动区域 -->
<div class="recent-activities">
<div class="section-title">最近活动</div>
<div class="activity-list">
<div
v-for="activity in recentActivities"
:key="activity.id"
class="activity-item"
>
<div class="activity-icon">{{ activity.icon }}</div>
<div class="activity-content">
<div class="activity-title">{{ activity.title }}</div>
<div class="activity-desc">{{ activity.desc }}</div>
<div class="activity-time">{{ activity.time }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import auth from '@/utils/auth'
import { dashboardService } from '@/services/dashboardService'
export default {
name: 'Home',
data() {
return {
userInfo: {},
quickActions: [
{ key: 'dashboard', name: '数据看板', icon: '📊', path: '/pages/dashboard/dashboard' },
{ key: 'supervision', name: '监管管理', icon: '🔍', path: '/pages/supervision/supervision' },
{ key: 'approval', name: '审批管理', icon: '✅', path: '/pages/approval/approval' },
{ key: 'personnel', name: '人员管理', icon: '👥', path: '/pages/personnel/personnel' },
{ key: 'epidemic', name: '疫情监控', icon: '🦠', path: '/pages/epidemic/epidemic' },
{ key: 'service', name: '服务管理', icon: '🛠️', path: '/pages/service/service' },
{ key: 'warehouse', name: '仓库管理', icon: '📦', path: '/pages/warehouse/warehouse' },
{ key: 'profile', name: '个人中心', icon: '👤', path: '/pages/profile/profile' }
],
statsData: [
{ key: 'supervision', label: '监管记录', value: '0', trend: 'up', trendText: '+0%' },
{ key: 'approval', label: '待审批', value: '0', trend: 'up', trendText: '+0%' },
{ key: 'personnel', label: '人员总数', value: '0', trend: 'up', trendText: '+0%' },
{ key: 'epidemic', label: '疫情预警', value: '0', trend: 'down', trendText: '-0%' }
],
recentActivities: [
{
id: 1,
icon: '🔍',
title: '监管检查',
desc: '完成养殖场A的例行检查',
time: '2小时前'
},
{
id: 2,
icon: '✅',
title: '审批通过',
desc: '通过养殖许可证申请',
time: '4小时前'
},
{
id: 3,
icon: '👥',
title: '人员管理',
desc: '新增监管员张三',
time: '6小时前'
}
]
}
},
onLoad() {
this.initData()
},
onPullDownRefresh() {
this.loadData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
initData() {
// 获取用户信息
this.userInfo = auth.getUser() || {}
// 加载数据
this.loadData()
},
async loadData() {
try {
// 加载统计数据
const stats = await dashboardService.getStats()
if (stats) {
this.updateStatsData(stats)
}
} catch (error) {
console.error('加载数据失败:', error)
}
},
updateStatsData(data) {
// 更新统计数据
this.statsData = this.statsData.map(stat => {
const newValue = data[stat.key] || stat.value
return {
...stat,
value: newValue
}
})
},
handleAction(action) {
if (action.path) {
uni.navigateTo({
url: action.path
})
}
}
}
}
</script>
<style scoped>
.home-container {
min-height: 100vh;
background: #f6f6f6;
}
.welcome-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 30rpx 60rpx;
color: #fff;
}
.welcome-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
}
.avatar {
margin-right: 24rpx;
}
.avatar-img {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
background: rgba(255, 255, 255, 0.2);
}
.username {
font-size: 36rpx;
font-weight: 600;
margin-bottom: 8rpx;
}
.user-role {
font-size: 24rpx;
opacity: 0.8;
}
.weather-info {
text-align: right;
}
.weather-icon {
font-size: 32rpx;
margin-bottom: 8rpx;
}
.weather-text {
font-size: 24rpx;
opacity: 0.8;
}
.quick-actions,
.stats-section,
.recent-activities {
padding: 30rpx;
background: #fff;
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
}
.action-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 30rpx;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx 20rpx;
background: #f8f9fa;
border-radius: 16rpx;
transition: all 0.3s;
}
.action-item:active {
transform: scale(0.95);
background: #e9ecef;
}
.action-icon {
font-size: 48rpx;
margin-bottom: 16rpx;
}
.action-text {
font-size: 24rpx;
color: #666;
text-align: center;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.stat-item {
padding: 30rpx;
background: #f8f9fa;
border-radius: 16rpx;
text-align: center;
}
.stat-value {
font-size: 48rpx;
font-weight: 600;
color: #1890ff;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.stat-trend {
font-size: 20rpx;
font-weight: 500;
}
.stat-trend.up {
color: #52c41a;
}
.stat-trend.down {
color: #ff4d4f;
}
.activity-list {
space-y: 20rpx;
}
.activity-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-icon {
font-size: 32rpx;
margin-right: 20rpx;
width: 60rpx;
text-align: center;
}
.activity-content {
flex: 1;
}
.activity-title {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.activity-desc {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.activity-time {
font-size: 20rpx;
color: #999;
}
</style>

View File

@@ -1,267 +0,0 @@
<template>
<div class="login-container">
<div class="login-header">
<div class="logo">
<image src="/static/logo.png" class="logo-img" />
</div>
<div class="title">政府管理系统</div>
<div class="subtitle">Government Management System</div>
</div>
<div class="login-form">
<div class="form-item">
<input
v-model="form.username"
type="text"
placeholder="请输入用户名"
class="input"
@input="onUsernameInput"
/>
</div>
<div class="form-item">
<input
v-model="form.password"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码"
class="input"
@input="onPasswordInput"
/>
<div class="password-toggle" @click="togglePassword">
<text class="toggle-icon">{{ showPassword ? '👁️' : '👁️‍🗨️' }}</text>
</div>
</div>
<div class="form-item">
<button
class="login-btn"
:class="{ disabled: !canLogin }"
:disabled="!canLogin || loading"
@click="handleLogin"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</div>
<div class="login-tips">
<text class="tips-text">默认账号admin / 123456</text>
</div>
</div>
<div class="login-footer">
<text class="footer-text">© 2024 政府管理系统</text>
</div>
</div>
</template>
<script>
import { authService } from '@/services/authService'
import auth from '@/utils/auth'
export default {
name: 'Login',
data() {
return {
form: {
username: '',
password: ''
},
showPassword: false,
loading: false
}
},
computed: {
canLogin() {
return this.form.username.trim() && this.form.password.trim()
}
},
methods: {
onUsernameInput(e) {
this.form.username = e.detail.value
},
onPasswordInput(e) {
this.form.password = e.detail.value
},
togglePassword() {
this.showPassword = !this.showPassword
},
async handleLogin() {
if (!this.canLogin || this.loading) return
this.loading = true
try {
const response = await authService.login(this.form.username, this.form.password)
if (response && response.token) {
// 保存token
auth.setToken(response.token)
// 获取用户信息
const userInfo = await authService.getUserInfo()
if (userInfo) {
auth.setUser(userInfo)
}
// 显示成功提示
if (typeof uni !== 'undefined') {
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 2000
})
// 跳转到首页
setTimeout(() => {
uni.reLaunch({
url: '/pages/index/index'
})
}, 1000)
} else {
// H5环境
alert('登录成功')
setTimeout(() => {
window.location.href = '/'
}, 1000)
}
}
} catch (error) {
console.error('登录失败:', error)
if (typeof uni !== 'undefined') {
uni.showToast({
title: error.message || '登录失败',
icon: 'error',
duration: 3000
})
} else {
// H5环境
alert(error.message || '登录失败')
}
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
}
.login-header {
text-align: center;
margin-bottom: 80rpx;
}
.logo {
margin-bottom: 40rpx;
}
.logo-img {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: #fff;
}
.title {
font-size: 48rpx;
font-weight: 600;
color: #fff;
margin-bottom: 16rpx;
}
.subtitle {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.login-form {
width: 100%;
max-width: 600rpx;
}
.form-item {
margin-bottom: 40rpx;
position: relative;
}
.input {
width: 100%;
height: 88rpx;
background: rgba(255, 255, 255, 0.9);
border: none;
border-radius: 44rpx;
padding: 0 40rpx;
font-size: 32rpx;
color: #333;
}
.input::placeholder {
color: #999;
}
.password-toggle {
position: absolute;
right: 40rpx;
top: 50%;
transform: translateY(-50%);
padding: 20rpx;
}
.toggle-icon {
font-size: 32rpx;
color: #999;
}
.login-btn {
width: 100%;
height: 88rpx;
background: #1890ff;
color: #fff;
border: none;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.login-btn.disabled {
background: #ccc;
color: #999;
}
.login-tips {
text-align: center;
margin-top: 40rpx;
}
.tips-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
.login-footer {
position: absolute;
bottom: 40rpx;
text-align: center;
}
.footer-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
</style>

View File

@@ -1,503 +0,0 @@
<template>
<div class="personnel-container">
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-bar">
<input
v-model="searchKeyword"
type="text"
placeholder="搜索人员..."
class="search-input"
@input="onSearchInput"
/>
<div class="search-icon">🔍</div>
</div>
<div class="filter-btn" @click="showFilter = true">
<text class="filter-text">筛选</text>
<div class="filter-icon"></div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div
v-for="stat in statsCards"
:key="stat.key"
class="stat-card"
:class="stat.type"
>
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-content">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</div>
<!-- 人员列表 -->
<div class="personnel-list">
<div class="list-header">
<div class="list-title">人员管理</div>
<div class="add-btn" @click="handleAdd">
<text class="add-text">新增</text>
<div class="add-icon">+</div>
</div>
</div>
<div v-if="loading" class="loading">
<text class="loading-text">加载中...</text>
</div>
<div v-else-if="personnelList.length === 0" class="empty">
<div class="empty-icon">👥</div>
<div class="empty-text">暂无人员记录</div>
</div>
<div v-else class="list-content">
<div
v-for="item in personnelList"
:key="item.id"
class="personnel-item"
@click="handleItemClick(item)"
>
<div class="item-avatar">
<image :src="item.avatar || '/static/default-avatar.png'" class="avatar-img" />
</div>
<div class="item-content">
<div class="item-header">
<div class="item-name">{{ item.name }}</div>
<div class="item-status" :class="item.status">
{{ item.statusText }}
</div>
</div>
<div class="item-info">
<div class="item-role">{{ item.role }}</div>
<div class="item-department">{{ item.department }}</div>
</div>
<div class="item-meta">
<div class="item-phone">📞 {{ item.phone }}</div>
<div class="item-email">📧 {{ item.email }}</div>
</div>
</div>
<div class="item-actions">
<div class="action-btn edit" @click.stop="handleEdit(item)">
<text class="action-text">编辑</text>
</div>
<div class="action-btn delete" @click.stop="handleDelete(item)">
<text class="action-text">删除</text>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { personnelService } from '@/services/personnelService'
export default {
name: 'Personnel',
data() {
return {
searchKeyword: '',
showFilter: false,
loading: false,
statsCards: [
{ key: 'total', icon: '👥', label: '总人数', value: '0', type: 'primary' },
{ key: 'active', icon: '✅', label: '在职', value: '0', type: 'success' },
{ key: 'inactive', icon: '⏸️', label: '离职', value: '0', type: 'warning' },
{ key: 'new', icon: '🆕', label: '本月新增', value: '0', type: 'info' }
],
personnelList: []
}
},
onLoad() {
this.loadPersonnelData()
},
onPullDownRefresh() {
this.loadPersonnelData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadPersonnelData() {
this.loading = true
try {
const [list, stats] = await Promise.all([
personnelService.getPersonnelList({
keyword: this.searchKeyword
}),
personnelService.getStats()
])
this.personnelList = list || []
this.updateStatsCards(stats)
} catch (error) {
console.error('加载人员数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
} finally {
this.loading = false
}
},
updateStatsCards(data) {
this.statsCards = this.statsCards.map(card => {
const newValue = data[card.key] || card.value
return {
...card,
value: newValue
}
})
},
onSearchInput(e) {
this.searchKeyword = e.detail.value
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.loadPersonnelData()
}, 500)
},
handleAdd() {
uni.navigateTo({
url: '/pages/personnel-add/personnel-add'
})
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/personnel-detail/personnel-detail?id=${item.id}`
})
},
handleEdit(item) {
uni.navigateTo({
url: `/pages/personnel-edit/personnel-edit?id=${item.id}`
})
},
async handleDelete(item) {
const result = await uni.showModal({
title: '确认删除',
content: '确定要删除这个人员记录吗?',
confirmText: '删除',
confirmColor: '#ff4d4f'
})
if (result.confirm) {
try {
await personnelService.deletePersonnel(item.id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
this.loadPersonnelData()
} catch (error) {
console.error('删除失败:', error)
uni.showToast({
title: '删除失败',
icon: 'error'
})
}
}
}
}
}
</script>
<style scoped>
.personnel-container {
min-height: 100vh;
background: #f6f6f6;
}
.search-section {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
}
.search-bar {
flex: 1;
position: relative;
margin-right: 20rpx;
}
.search-input {
width: 100%;
height: 72rpx;
background: #f8f9fa;
border: none;
border-radius: 36rpx;
padding: 0 60rpx 0 30rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
}
.filter-btn {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #1890ff;
border-radius: 36rpx;
color: #fff;
}
.filter-text {
font-size: 28rpx;
margin-right: 8rpx;
}
.filter-icon {
font-size: 24rpx;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 20rpx 20rpx;
}
.stat-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.stat-card.primary {
border-left: 8rpx solid #1890ff;
}
.stat-card.success {
border-left: 8rpx solid #52c41a;
}
.stat-card.warning {
border-left: 8rpx solid #faad14;
}
.stat-card.info {
border-left: 8rpx solid #13c2c2;
}
.stat-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.personnel-list {
background: #fff;
margin: 0 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #1890ff;
border-radius: 24rpx;
color: #fff;
}
.add-text {
font-size: 24rpx;
margin-right: 8rpx;
}
.add-icon {
font-size: 24rpx;
font-weight: 600;
}
.loading,
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.list-content {
space-y: 0;
}
.personnel-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.personnel-item:active {
background: #f8f9fa;
}
.personnel-item:last-child {
border-bottom: none;
}
.item-avatar {
margin-right: 20rpx;
}
.avatar-img {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
background: #f0f0f0;
}
.item-content {
flex: 1;
margin-right: 20rpx;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.item-name {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.item-status {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.item-status.active {
background: #f6ffed;
color: #52c41a;
}
.item-status.inactive {
background: #fff7e6;
color: #faad14;
}
.item-info {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.item-role {
font-size: 24rpx;
color: #1890ff;
margin-right: 20rpx;
}
.item-department {
font-size: 24rpx;
color: #666;
}
.item-meta {
display: flex;
align-items: center;
gap: 20rpx;
}
.item-phone,
.item-email {
font-size: 22rpx;
color: #999;
}
.item-actions {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.action-btn {
padding: 12rpx 20rpx;
border-radius: 20rpx;
font-size: 20rpx;
text-align: center;
min-width: 80rpx;
}
.action-btn.edit {
background: #e6f7ff;
color: #1890ff;
}
.action-btn.delete {
background: #fff2f0;
color: #ff4d4f;
}
</style>

View File

@@ -1,407 +0,0 @@
<template>
<div class="profile-container">
<!-- 用户信息卡片 -->
<div class="user-card">
<div class="user-avatar">
<image :src="userInfo.avatar || '/static/default-avatar.png'" class="avatar-img" />
<div class="avatar-edit" @click="handleEditAvatar">
<text class="edit-text">编辑</text>
</div>
</div>
<div class="user-info">
<div class="user-name">{{ userInfo.name || '管理员' }}</div>
<div class="user-role">{{ userInfo.role || '系统管理员' }}</div>
<div class="user-email">{{ userInfo.email || 'admin@example.com' }}</div>
</div>
</div>
<!-- 功能菜单 -->
<div class="menu-section">
<div class="menu-group">
<div class="group-title">账户管理</div>
<div class="menu-list">
<div class="menu-item" @click="handleEditProfile">
<div class="menu-icon">👤</div>
<div class="menu-content">
<div class="menu-title">个人信息</div>
<div class="menu-desc">编辑个人资料</div>
</div>
<div class="menu-arrow">></div>
</div>
<div class="menu-item" @click="handleChangePassword">
<div class="menu-icon">🔒</div>
<div class="menu-content">
<div class="menu-title">修改密码</div>
<div class="menu-desc">更改登录密码</div>
</div>
<div class="menu-arrow">></div>
</div>
</div>
</div>
<div class="menu-group">
<div class="group-title">系统设置</div>
<div class="menu-list">
<div class="menu-item" @click="handleNotificationSettings">
<div class="menu-icon">🔔</div>
<div class="menu-content">
<div class="menu-title">通知设置</div>
<div class="menu-desc">管理推送通知</div>
</div>
<div class="menu-arrow">></div>
</div>
<div class="menu-item" @click="handlePrivacySettings">
<div class="menu-icon">🛡</div>
<div class="menu-content">
<div class="menu-title">隐私设置</div>
<div class="menu-desc">隐私和安全选项</div>
</div>
<div class="menu-arrow">></div>
</div>
<div class="menu-item" @click="handleAbout">
<div class="menu-icon"></div>
<div class="menu-content">
<div class="menu-title">关于我们</div>
<div class="menu-desc">版本信息和帮助</div>
</div>
<div class="menu-arrow">></div>
</div>
</div>
</div>
<div class="menu-group">
<div class="group-title">其他</div>
<div class="menu-list">
<div class="menu-item" @click="handleFeedback">
<div class="menu-icon">💬</div>
<div class="menu-content">
<div class="menu-title">意见反馈</div>
<div class="menu-desc">提交建议和问题</div>
</div>
<div class="menu-arrow">></div>
</div>
<div class="menu-item" @click="handleLogout">
<div class="menu-icon">🚪</div>
<div class="menu-content">
<div class="menu-title">退出登录</div>
<div class="menu-desc">安全退出系统</div>
</div>
<div class="menu-arrow">></div>
</div>
</div>
</div>
</div>
<!-- 版本信息 -->
<div class="version-info">
<text class="version-text">政府管理系统 v1.0.0</text>
</div>
</div>
</template>
<script>
import auth from '@/utils/auth'
export default {
name: 'Profile',
data() {
return {
userInfo: {}
}
},
onLoad() {
this.loadUserInfo()
},
methods: {
loadUserInfo() {
this.userInfo = auth.getUser() || {}
},
handleEditAvatar() {
if (typeof uni !== 'undefined') {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
// 这里可以上传头像到服务器
console.log('选择头像:', tempFilePath)
uni.showToast({
title: '头像上传功能待实现',
icon: 'none'
})
}
})
} else {
// H5环境
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.onchange = (e) => {
const file = e.target.files[0]
if (file) {
console.log('选择头像:', file)
alert('头像上传功能待实现')
}
}
input.click()
}
},
handleEditProfile() {
if (typeof uni !== 'undefined') {
uni.navigateTo({
url: '/pages/profile-edit/profile-edit'
})
} else {
alert('编辑个人资料功能待实现')
}
},
handleChangePassword() {
if (typeof uni !== 'undefined') {
uni.navigateTo({
url: '/pages/change-password/change-password'
})
} else {
alert('修改密码功能待实现')
}
},
handleNotificationSettings() {
if (typeof uni !== 'undefined') {
uni.navigateTo({
url: '/pages/notification-settings/notification-settings'
})
} else {
alert('通知设置功能待实现')
}
},
handlePrivacySettings() {
if (typeof uni !== 'undefined') {
uni.navigateTo({
url: '/pages/privacy-settings/privacy-settings'
})
} else {
alert('隐私设置功能待实现')
}
},
handleAbout() {
if (typeof uni !== 'undefined') {
uni.navigateTo({
url: '/pages/about/about'
})
} else {
alert('关于我们功能待实现')
}
},
handleFeedback() {
if (typeof uni !== 'undefined') {
uni.navigateTo({
url: '/pages/feedback/feedback'
})
} else {
alert('意见反馈功能待实现')
}
},
async handleLogout() {
let result
if (typeof uni !== 'undefined') {
result = await uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
confirmText: '退出',
confirmColor: '#ff4d4f'
})
} else {
// H5环境
result = { confirm: confirm('确定要退出登录吗?') }
}
if (result.confirm) {
try {
// 清除本地存储
auth.clearToken()
// 显示退出成功提示
if (typeof uni !== 'undefined') {
uni.showToast({
title: '退出成功',
icon: 'success',
duration: 1500
})
// 跳转到登录页
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
})
}, 1500)
} else {
// H5环境
alert('退出成功')
setTimeout(() => {
window.location.href = '/login'
}, 1000)
}
} catch (error) {
console.error('退出失败:', error)
if (typeof uni !== 'undefined') {
uni.showToast({
title: '退出失败',
icon: 'error'
})
} else {
alert('退出失败')
}
}
}
}
}
}
</script>
<style scoped>
.profile-container {
min-height: 100vh;
background: #f6f6f6;
}
.user-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60rpx 30rpx 40rpx;
color: #fff;
position: relative;
}
.user-avatar {
position: relative;
display: inline-block;
margin-bottom: 30rpx;
}
.avatar-img {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: rgba(255, 255, 255, 0.2);
border: 4rpx solid rgba(255, 255, 255, 0.3);
}
.avatar-edit {
position: absolute;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
border-radius: 20rpx;
padding: 8rpx 16rpx;
}
.edit-text {
font-size: 20rpx;
color: #fff;
}
.user-info {
text-align: left;
}
.user-name {
font-size: 40rpx;
font-weight: 600;
margin-bottom: 12rpx;
}
.user-role {
font-size: 28rpx;
opacity: 0.9;
margin-bottom: 8rpx;
}
.user-email {
font-size: 24rpx;
opacity: 0.8;
}
.menu-section {
padding: 30rpx 20rpx;
}
.menu-group {
margin-bottom: 40rpx;
}
.group-title {
font-size: 28rpx;
color: #999;
margin-bottom: 20rpx;
padding-left: 10rpx;
}
.menu-list {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.menu-item:active {
background: #f8f9fa;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-icon {
font-size: 40rpx;
margin-right: 24rpx;
width: 60rpx;
text-align: center;
}
.menu-content {
flex: 1;
}
.menu-title {
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.menu-desc {
font-size: 24rpx;
color: #999;
}
.menu-arrow {
font-size: 24rpx;
color: #ccc;
margin-left: 20rpx;
}
.version-info {
text-align: center;
padding: 40rpx 20rpx;
}
.version-text {
font-size: 24rpx;
color: #999;
}
</style>

View File

@@ -1,477 +0,0 @@
<template>
<div class="service-container">
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-bar">
<input
v-model="searchKeyword"
type="text"
placeholder="搜索服务..."
class="search-input"
@input="onSearchInput"
/>
<div class="search-icon">🔍</div>
</div>
<div class="filter-btn" @click="showFilter = true">
<text class="filter-text">筛选</text>
<div class="filter-icon"></div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div
v-for="stat in statsCards"
:key="stat.key"
class="stat-card"
:class="stat.type"
>
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-content">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</div>
<!-- 服务列表 -->
<div class="service-list">
<div class="list-header">
<div class="list-title">服务管理</div>
<div class="add-btn" @click="handleAdd">
<text class="add-text">新增</text>
<div class="add-icon">+</div>
</div>
</div>
<div v-if="loading" class="loading">
<text class="loading-text">加载中...</text>
</div>
<div v-else-if="serviceList.length === 0" class="empty">
<div class="empty-icon">🛠</div>
<div class="empty-text">暂无服务记录</div>
</div>
<div v-else class="list-content">
<div
v-for="item in serviceList"
:key="item.id"
class="service-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<div class="item-title">{{ item.title }}</div>
<div class="item-status" :class="item.status">
{{ item.statusText }}
</div>
</div>
<div class="item-content">
<div class="item-desc">{{ item.description }}</div>
<div class="item-meta">
<div class="item-category">📂 {{ item.category }}</div>
<div class="item-time">{{ item.createTime }}</div>
</div>
</div>
<div class="item-actions">
<div class="action-btn edit" @click.stop="handleEdit(item)">
<text class="action-text">编辑</text>
</div>
<div class="action-btn delete" @click.stop="handleDelete(item)">
<text class="action-text">删除</text>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { serviceService } from '@/services/serviceService'
export default {
name: 'Service',
data() {
return {
searchKeyword: '',
showFilter: false,
loading: false,
statsCards: [
{ key: 'total', icon: '🛠️', label: '总服务', value: '0', type: 'primary' },
{ key: 'active', icon: '✅', label: '运行中', value: '0', type: 'success' },
{ key: 'maintenance', icon: '🔧', label: '维护中', value: '0', type: 'warning' },
{ key: 'stopped', icon: '⏹️', label: '已停止', value: '0', type: 'danger' }
],
serviceList: []
}
},
onLoad() {
this.loadServiceData()
},
onPullDownRefresh() {
this.loadServiceData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadServiceData() {
this.loading = true
try {
const [list, stats] = await Promise.all([
serviceService.getServiceList({
keyword: this.searchKeyword
}),
serviceService.getStats()
])
this.serviceList = list || []
this.updateStatsCards(stats)
} catch (error) {
console.error('加载服务数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
} finally {
this.loading = false
}
},
updateStatsCards(data) {
this.statsCards = this.statsCards.map(card => {
const newValue = data[card.key] || card.value
return {
...card,
value: newValue
}
})
},
onSearchInput(e) {
this.searchKeyword = e.detail.value
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.loadServiceData()
}, 500)
},
handleAdd() {
uni.navigateTo({
url: '/pages/service-add/service-add'
})
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/service-detail/service-detail?id=${item.id}`
})
},
handleEdit(item) {
uni.navigateTo({
url: `/pages/service-edit/service-edit?id=${item.id}`
})
},
async handleDelete(item) {
const result = await uni.showModal({
title: '确认删除',
content: '确定要删除这个服务记录吗?',
confirmText: '删除',
confirmColor: '#ff4d4f'
})
if (result.confirm) {
try {
await serviceService.deleteService(item.id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
this.loadServiceData()
} catch (error) {
console.error('删除失败:', error)
uni.showToast({
title: '删除失败',
icon: 'error'
})
}
}
}
}
}
</script>
<style scoped>
.service-container {
min-height: 100vh;
background: #f6f6f6;
}
.search-section {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
}
.search-bar {
flex: 1;
position: relative;
margin-right: 20rpx;
}
.search-input {
width: 100%;
height: 72rpx;
background: #f8f9fa;
border: none;
border-radius: 36rpx;
padding: 0 60rpx 0 30rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
}
.filter-btn {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #1890ff;
border-radius: 36rpx;
color: #fff;
}
.filter-text {
font-size: 28rpx;
margin-right: 8rpx;
}
.filter-icon {
font-size: 24rpx;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 20rpx 20rpx;
}
.stat-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.stat-card.primary {
border-left: 8rpx solid #1890ff;
}
.stat-card.success {
border-left: 8rpx solid #52c41a;
}
.stat-card.warning {
border-left: 8rpx solid #faad14;
}
.stat-card.danger {
border-left: 8rpx solid #ff4d4f;
}
.stat-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.service-list {
background: #fff;
margin: 0 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #1890ff;
border-radius: 24rpx;
color: #fff;
}
.add-text {
font-size: 24rpx;
margin-right: 8rpx;
}
.add-icon {
font-size: 24rpx;
font-weight: 600;
}
.loading,
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.list-content {
space-y: 0;
}
.service-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.service-item:active {
background: #f8f9fa;
}
.service-item:last-child {
border-bottom: none;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.item-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.item-status {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.item-status.active {
background: #f6ffed;
color: #52c41a;
}
.item-status.maintenance {
background: #fff7e6;
color: #faad14;
}
.item-status.stopped {
background: #fff2f0;
color: #ff4d4f;
}
.item-content {
margin-bottom: 20rpx;
}
.item-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
line-height: 1.5;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.item-category,
.item-time {
font-size: 22rpx;
color: #999;
}
.item-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 22rpx;
text-align: center;
}
.action-btn.edit {
background: #e6f7ff;
color: #1890ff;
}
.action-btn.delete {
background: #fff2f0;
color: #ff4d4f;
}
</style>

View File

@@ -1,663 +0,0 @@
<template>
<div class="supervision-container">
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-bar">
<input
v-model="searchKeyword"
type="text"
placeholder="搜索监管记录..."
class="search-input"
@input="onSearchInput"
/>
<div class="search-icon">🔍</div>
</div>
<div class="filter-btn" @click="showFilter = true">
<text class="filter-text">筛选</text>
<div class="filter-icon"></div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div
v-for="stat in statsCards"
:key="stat.key"
class="stat-card"
:class="stat.type"
>
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-content">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</div>
<!-- 监管列表 -->
<div class="supervision-list">
<div class="list-header">
<div class="list-title">监管记录</div>
<div class="add-btn" @click="handleAdd">
<text class="add-text">新增</text>
<div class="add-icon">+</div>
</div>
</div>
<div v-if="loading" class="loading">
<text class="loading-text">加载中...</text>
</div>
<div v-else-if="supervisionList.length === 0" class="empty">
<div class="empty-icon">📋</div>
<div class="empty-text">暂无监管记录</div>
</div>
<div v-else class="list-content">
<div
v-for="item in supervisionList"
:key="item.id"
class="supervision-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<div class="item-title">{{ item.title }}</div>
<div class="item-status" :class="item.status">
{{ item.statusText }}
</div>
</div>
<div class="item-content">
<div class="item-desc">{{ item.description }}</div>
<div class="item-meta">
<div class="item-location">📍 {{ item.location }}</div>
<div class="item-time">{{ item.createTime }}</div>
</div>
</div>
<div class="item-actions">
<div class="action-btn edit" @click.stop="handleEdit(item)">
<text class="action-text">编辑</text>
</div>
<div class="action-btn delete" @click.stop="handleDelete(item)">
<text class="action-text">删除</text>
</div>
</div>
</div>
</div>
</div>
<!-- 筛选弹窗 -->
<div v-if="showFilter" class="filter-modal" @click="showFilter = false">
<div class="filter-content" @click.stop>
<div class="filter-header">
<div class="filter-title">筛选条件</div>
<div class="close-btn" @click="showFilter = false">×</div>
</div>
<div class="filter-body">
<div class="filter-item">
<div class="filter-label">状态</div>
<div class="filter-options">
<div
v-for="status in statusOptions"
:key="status.value"
class="filter-option"
:class="{ active: filterStatus === status.value }"
@click="filterStatus = status.value"
>
{{ status.label }}
</div>
</div>
</div>
<div class="filter-item">
<div class="filter-label">时间范围</div>
<div class="filter-options">
<div
v-for="time in timeOptions"
:key="time.value"
class="filter-option"
:class="{ active: filterTime === time.value }"
@click="filterTime = time.value"
>
{{ time.label }}
</div>
</div>
</div>
</div>
<div class="filter-footer">
<div class="filter-btn reset" @click="resetFilter">重置</div>
<div class="filter-btn confirm" @click="applyFilter">确定</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { supervisionService } from '@/services/supervisionService'
export default {
name: 'Supervision',
data() {
return {
searchKeyword: '',
showFilter: false,
loading: false,
filterStatus: '',
filterTime: '',
statsCards: [
{ key: 'total', icon: '📋', label: '总记录', value: '0', type: 'primary' },
{ key: 'pending', icon: '⏳', label: '待处理', value: '0', type: 'warning' },
{ key: 'completed', icon: '✅', label: '已完成', value: '0', type: 'success' },
{ key: 'overdue', icon: '⚠️', label: '已逾期', value: '0', type: 'danger' }
],
supervisionList: [],
statusOptions: [
{ label: '全部', value: '' },
{ label: '待处理', value: 'pending' },
{ label: '进行中', value: 'processing' },
{ label: '已完成', value: 'completed' },
{ label: '已逾期', value: 'overdue' }
],
timeOptions: [
{ label: '全部', value: '' },
{ label: '今天', value: 'today' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' },
{ label: '自定义', value: 'custom' }
]
}
},
onLoad() {
this.loadSupervisionData()
},
onPullDownRefresh() {
this.loadSupervisionData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadSupervisionData() {
this.loading = true
try {
const [list, stats] = await Promise.all([
supervisionService.getSupervisionList({
keyword: this.searchKeyword,
status: this.filterStatus,
time: this.filterTime
}),
supervisionService.getStats()
])
this.supervisionList = list || []
this.updateStatsCards(stats)
} catch (error) {
console.error('加载监管数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
} finally {
this.loading = false
}
},
updateStatsCards(data) {
this.statsCards = this.statsCards.map(card => {
const newValue = data[card.key] || card.value
return {
...card,
value: newValue
}
})
},
onSearchInput(e) {
this.searchKeyword = e.detail.value
// 防抖搜索
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.loadSupervisionData()
}, 500)
},
applyFilter() {
this.showFilter = false
this.loadSupervisionData()
},
resetFilter() {
this.filterStatus = ''
this.filterTime = ''
},
handleAdd() {
uni.navigateTo({
url: '/pages/supervision-add/supervision-add'
})
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/supervision-detail/supervision-detail?id=${item.id}`
})
},
handleEdit(item) {
uni.navigateTo({
url: `/pages/supervision-edit/supervision-edit?id=${item.id}`
})
},
async handleDelete(item) {
const result = await uni.showModal({
title: '确认删除',
content: '确定要删除这条监管记录吗?',
confirmText: '删除',
confirmColor: '#ff4d4f'
})
if (result.confirm) {
try {
await supervisionService.deleteSupervision(item.id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
this.loadSupervisionData()
} catch (error) {
console.error('删除失败:', error)
uni.showToast({
title: '删除失败',
icon: 'error'
})
}
}
}
}
}
</script>
<style scoped>
.supervision-container {
min-height: 100vh;
background: #f6f6f6;
}
.search-section {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
}
.search-bar {
flex: 1;
position: relative;
margin-right: 20rpx;
}
.search-input {
width: 100%;
height: 72rpx;
background: #f8f9fa;
border: none;
border-radius: 36rpx;
padding: 0 60rpx 0 30rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
}
.filter-btn {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #1890ff;
border-radius: 36rpx;
color: #fff;
}
.filter-text {
font-size: 28rpx;
margin-right: 8rpx;
}
.filter-icon {
font-size: 24rpx;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 20rpx 20rpx;
}
.stat-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.stat-card.primary {
border-left: 8rpx solid #1890ff;
}
.stat-card.warning {
border-left: 8rpx solid #faad14;
}
.stat-card.success {
border-left: 8rpx solid #52c41a;
}
.stat-card.danger {
border-left: 8rpx solid #ff4d4f;
}
.stat-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.supervision-list {
background: #fff;
margin: 0 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #1890ff;
border-radius: 24rpx;
color: #fff;
}
.add-text {
font-size: 24rpx;
margin-right: 8rpx;
}
.add-icon {
font-size: 24rpx;
font-weight: 600;
}
.loading,
.empty {
display: flex;
align-items: center;
justify-content: center;
padding: 80rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.list-content {
space-y: 0;
}
.supervision-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.supervision-item:active {
background: #f8f9fa;
}
.supervision-item:last-child {
border-bottom: none;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.item-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.item-status {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.item-status.pending {
background: #fff7e6;
color: #faad14;
}
.item-status.processing {
background: #e6f7ff;
color: #1890ff;
}
.item-status.completed {
background: #f6ffed;
color: #52c41a;
}
.item-status.overdue {
background: #fff2f0;
color: #ff4d4f;
}
.item-content {
margin-bottom: 20rpx;
}
.item-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
line-height: 1.5;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.item-location,
.item-time {
font-size: 22rpx;
color: #999;
}
.item-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 22rpx;
text-align: center;
}
.action-btn.edit {
background: #e6f7ff;
color: #1890ff;
}
.action-btn.delete {
background: #fff2f0;
color: #ff4d4f;
}
.filter-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.filter-content {
width: 100%;
background: #fff;
border-radius: 32rpx 32rpx 0 0;
max-height: 80vh;
overflow: hidden;
}
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.filter-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.close-btn {
font-size: 48rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.filter-body {
padding: 30rpx;
max-height: 60vh;
overflow-y: auto;
}
.filter-item {
margin-bottom: 40rpx;
}
.filter-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.filter-option {
padding: 16rpx 32rpx;
background: #f8f9fa;
border-radius: 24rpx;
font-size: 24rpx;
color: #666;
transition: all 0.3s;
}
.filter-option.active {
background: #1890ff;
color: #fff;
}
.filter-footer {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.filter-btn.reset,
.filter-btn.confirm {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 600;
}
.filter-btn.reset {
background: #f8f9fa;
color: #666;
}
.filter-btn.confirm {
background: #1890ff;
color: #fff;
}
</style>

View File

@@ -1,494 +0,0 @@
<template>
<div class="warehouse-container">
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-bar">
<input
v-model="searchKeyword"
type="text"
placeholder="搜索仓库..."
class="search-input"
@input="onSearchInput"
/>
<div class="search-icon">🔍</div>
</div>
<div class="filter-btn" @click="showFilter = true">
<text class="filter-text">筛选</text>
<div class="filter-icon"></div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div
v-for="stat in statsCards"
:key="stat.key"
class="stat-card"
:class="stat.type"
>
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-content">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</div>
<!-- 仓库列表 -->
<div class="warehouse-list">
<div class="list-header">
<div class="list-title">仓库管理</div>
<div class="add-btn" @click="handleAdd">
<text class="add-text">新增</text>
<div class="add-icon">+</div>
</div>
</div>
<div v-if="loading" class="loading">
<text class="loading-text">加载中...</text>
</div>
<div v-else-if="warehouseList.length === 0" class="empty">
<div class="empty-icon">📦</div>
<div class="empty-text">暂无仓库记录</div>
</div>
<div v-else class="list-content">
<div
v-for="item in warehouseList"
:key="item.id"
class="warehouse-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<div class="item-title">{{ item.name }}</div>
<div class="item-status" :class="item.status">
{{ item.statusText }}
</div>
</div>
<div class="item-content">
<div class="item-desc">{{ item.description }}</div>
<div class="item-meta">
<div class="item-location">📍 {{ item.location }}</div>
<div class="item-capacity">📊 容量: {{ item.capacity }}</div>
</div>
<div class="item-info">
<div class="item-manager">👤 管理员: {{ item.manager }}</div>
<div class="item-time">{{ item.createTime }}</div>
</div>
</div>
<div class="item-actions">
<div class="action-btn edit" @click.stop="handleEdit(item)">
<text class="action-text">编辑</text>
</div>
<div class="action-btn delete" @click.stop="handleDelete(item)">
<text class="action-text">删除</text>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { warehouseService } from '@/services/warehouseService'
export default {
name: 'Warehouse',
data() {
return {
searchKeyword: '',
showFilter: false,
loading: false,
statsCards: [
{ key: 'total', icon: '📦', label: '总仓库', value: '0', type: 'primary' },
{ key: 'active', icon: '✅', label: '运行中', value: '0', type: 'success' },
{ key: 'maintenance', icon: '🔧', label: '维护中', value: '0', type: 'warning' },
{ key: 'full', icon: '⚠️', label: '已满', value: '0', type: 'danger' }
],
warehouseList: []
}
},
onLoad() {
this.loadWarehouseData()
},
onPullDownRefresh() {
this.loadWarehouseData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
methods: {
async loadWarehouseData() {
this.loading = true
try {
const [list, stats] = await Promise.all([
warehouseService.getWarehouseList({
keyword: this.searchKeyword
}),
warehouseService.getStats()
])
this.warehouseList = list || []
this.updateStatsCards(stats)
} catch (error) {
console.error('加载仓库数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'error'
})
} finally {
this.loading = false
}
},
updateStatsCards(data) {
this.statsCards = this.statsCards.map(card => {
const newValue = data[card.key] || card.value
return {
...card,
value: newValue
}
})
},
onSearchInput(e) {
this.searchKeyword = e.detail.value
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.loadWarehouseData()
}, 500)
},
handleAdd() {
uni.navigateTo({
url: '/pages/warehouse-add/warehouse-add'
})
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/warehouse-detail/warehouse-detail?id=${item.id}`
})
},
handleEdit(item) {
uni.navigateTo({
url: `/pages/warehouse-edit/warehouse-edit?id=${item.id}`
})
},
async handleDelete(item) {
const result = await uni.showModal({
title: '确认删除',
content: '确定要删除这个仓库记录吗?',
confirmText: '删除',
confirmColor: '#ff4d4f'
})
if (result.confirm) {
try {
await warehouseService.deleteWarehouse(item.id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
this.loadWarehouseData()
} catch (error) {
console.error('删除失败:', error)
uni.showToast({
title: '删除失败',
icon: 'error'
})
}
}
}
}
}
</script>
<style scoped>
.warehouse-container {
min-height: 100vh;
background: #f6f6f6;
}
.search-section {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
}
.search-bar {
flex: 1;
position: relative;
margin-right: 20rpx;
}
.search-input {
width: 100%;
height: 72rpx;
background: #f8f9fa;
border: none;
border-radius: 36rpx;
padding: 0 60rpx 0 30rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
}
.filter-btn {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: #1890ff;
border-radius: 36rpx;
color: #fff;
}
.filter-text {
font-size: 28rpx;
margin-right: 8rpx;
}
.filter-icon {
font-size: 24rpx;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 20rpx 20rpx;
}
.stat-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.stat-card.primary {
border-left: 8rpx solid #1890ff;
}
.stat-card.success {
border-left: 8rpx solid #52c41a;
}
.stat-card.warning {
border-left: 8rpx solid #faad14;
}
.stat-card.danger {
border-left: 8rpx solid #ff4d4f;
}
.stat-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
.warehouse-list {
background: #fff;
margin: 0 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #1890ff;
border-radius: 24rpx;
color: #fff;
}
.add-text {
font-size: 24rpx;
margin-right: 8rpx;
}
.add-icon {
font-size: 24rpx;
font-weight: 600;
}
.loading,
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.list-content {
space-y: 0;
}
.warehouse-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.3s;
}
.warehouse-item:active {
background: #f8f9fa;
}
.warehouse-item:last-child {
border-bottom: none;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.item-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.item-status {
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
}
.item-status.active {
background: #f6ffed;
color: #52c41a;
}
.item-status.maintenance {
background: #fff7e6;
color: #faad14;
}
.item-status.full {
background: #fff2f0;
color: #ff4d4f;
}
.item-content {
margin-bottom: 20rpx;
}
.item-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
line-height: 1.5;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.item-location,
.item-capacity {
font-size: 22rpx;
color: #999;
}
.item-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.item-manager,
.item-time {
font-size: 22rpx;
color: #999;
}
.item-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 22rpx;
text-align: center;
}
.action-btn.edit {
background: #e6f7ff;
color: #1890ff;
}
.action-btn.delete {
background: #fff2f0;
color: #ff4d4f;
}
</style>

View File

@@ -1,59 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'
import VueCompositionAPI from '@vue/composition-api'
import router from './router'
// 引入全局样式
import './app.scss'
// 安装插件
Vue.use(VueCompositionAPI)
Vue.use(Vuex)
// 创建store实例
const store = new Vuex.Store({
state: {
user: null,
token: null
},
mutations: {
SET_USER(state, user) {
state.user = user
},
SET_TOKEN(state, token) {
state.token = token
},
CLEAR_AUTH(state) {
state.user = null
state.token = null
}
},
actions: {
setUser({ commit }, user) {
commit('SET_USER', user)
},
setToken({ commit }, token) {
commit('SET_TOKEN', token)
},
clearAuth({ commit }) {
commit('CLEAR_AUTH')
}
},
getters: {
isAuthenticated: state => !!(state.user && state.token)
}
})
// 创建应用实例
const app = new Vue({
store,
router,
render: h => h(App)
})
// 挂载应用
app.$mount('#app')
// 导出应用实例
export default app

View File

@@ -1,22 +0,0 @@
<template>
<div class="approval-page">
<Approval />
</div>
</template>
<script>
import Approval from '@/components/Approval.vue'
export default {
name: 'ApprovalPage',
components: {
Approval
}
}
</script>
<style scoped>
.approval-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="dashboard-page">
<Dashboard />
</div>
</template>
<script>
import Dashboard from '@/components/Dashboard.vue'
export default {
name: 'DashboardPage',
components: {
Dashboard
}
}
</script>
<style scoped>
.dashboard-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="epidemic-page">
<Epidemic />
</div>
</template>
<script>
import Epidemic from '@/components/Epidemic.vue'
export default {
name: 'EpidemicPage',
components: {
Epidemic
}
}
</script>
<style scoped>
.epidemic-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="index-page">
<Home />
</div>
</template>
<script>
import Home from '@/components/Home.vue'
export default {
name: 'IndexPage',
components: {
Home
}
}
</script>
<style scoped>
.index-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="login-page">
<Login />
</div>
</template>
<script>
import Login from '@/components/Login.vue'
export default {
name: 'LoginPage',
components: {
Login
}
}
</script>
<style scoped>
.login-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="personnel-page">
<Personnel />
</div>
</template>
<script>
import Personnel from '@/components/Personnel.vue'
export default {
name: 'PersonnelPage',
components: {
Personnel
}
}
</script>
<style scoped>
.personnel-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="profile-page">
<Profile />
</div>
</template>
<script>
import Profile from '@/components/Profile.vue'
export default {
name: 'ProfilePage',
components: {
Profile
}
}
</script>
<style scoped>
.profile-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="service-page">
<Service />
</div>
</template>
<script>
import Service from '@/components/Service.vue'
export default {
name: 'ServicePage',
components: {
Service
}
}
</script>
<style scoped>
.service-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="supervision-page">
<Supervision />
</div>
</template>
<script>
import Supervision from '@/components/Supervision.vue'
export default {
name: 'SupervisionPage',
components: {
Supervision
}
}
</script>
<style scoped>
.supervision-page {
height: 100vh;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="warehouse-page">
<Warehouse />
</div>
</template>
<script>
import Warehouse from '@/components/Warehouse.vue'
export default {
name: 'WarehousePage',
components: {
Warehouse
}
}
</script>
<style scoped>
.warehouse-page {
height: 100vh;
}
</style>

View File

@@ -1,108 +0,0 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/components/Home.vue'
import Dashboard from '@/components/Dashboard.vue'
import Supervision from '@/components/Supervision.vue'
import Approval from '@/components/Approval.vue'
import Personnel from '@/components/Personnel.vue'
import Epidemic from '@/components/Epidemic.vue'
import Service from '@/components/Service.vue'
import Warehouse from '@/components/Warehouse.vue'
import Profile from '@/components/Profile.vue'
import Login from '@/components/Login.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard
},
{
path: '/supervision',
name: 'Supervision',
component: Supervision
},
{
path: '/approval',
name: 'Approval',
component: Approval
},
{
path: '/personnel',
name: 'Personnel',
component: Personnel
},
{
path: '/epidemic',
name: 'Epidemic',
component: Epidemic
},
{
path: '/service',
name: 'Service',
component: Service
},
{
path: '/warehouse',
name: 'Warehouse',
component: Warehouse
},
{
path: '/profile',
name: 'Profile',
component: Profile
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
// 导入认证工具
const auth = require('@/utils/auth').default
// 检查是否需要登录
const requiresAuth = to.path !== '/login'
const isLoginPage = to.path === '/login'
const isAuthenticated = auth.isAuthenticated()
console.log('路由守卫:', {
from: from.path,
to: to.path,
requiresAuth,
isLoginPage,
isAuthenticated
})
if (requiresAuth && !isAuthenticated) {
// 需要登录但未登录,跳转到登录页
console.log('需要登录,跳转到登录页')
next('/login')
} else if (isLoginPage && isAuthenticated) {
// 已登录但访问登录页,跳转到首页
console.log('已登录,跳转到首页')
next('/')
} else {
// 正常访问
console.log('正常访问')
next()
}
})
export default router

View File

@@ -1,44 +0,0 @@
import { get, post, put, del } from '@/utils/request'
// 审批管理相关API
export const approvalService = {
// 获取审批列表
getApprovalList(params = {}) {
return get('/approval/list', params)
},
// 获取审批详情
getApprovalDetail(id) {
return get(`/approval/${id}`)
},
// 创建审批
createApproval(data) {
return post('/approval', data)
},
// 更新审批
updateApproval(id, data) {
return put(`/approval/${id}`, data)
},
// 删除审批
deleteApproval(id) {
return del(`/approval/${id}`)
},
// 审批通过
approve(id, data = {}) {
return post(`/approval/${id}/approve`, data)
},
// 审批拒绝
reject(id, data = {}) {
return post(`/approval/${id}/reject`, data)
},
// 获取审批统计
getStats() {
return get('/approval/stats')
}
}

View File

@@ -1,22 +0,0 @@
import { post, get } from '@/utils/request'
// 认证相关API
export const authService = {
// 用户登录
login(username, password) {
return post('/auth/login', {
username,
password
})
},
// 获取用户信息
getUserInfo() {
return get('/auth/userinfo')
},
// 登出
logout() {
return post('/auth/logout')
}
}

View File

@@ -1,39 +0,0 @@
import { get } from '@/utils/request'
// 数据看板相关API
export const dashboardService = {
// 获取统计数据
getStats() {
return get('/visualization/data')
},
// 获取监管统计
getSupervisionStats() {
return get('/supervision/stats')
},
// 获取审批统计
getApprovalStats() {
return get('/approval/stats')
},
// 获取人员统计
getPersonnelStats() {
return get('/personnel/stats')
},
// 获取疫情统计
getEpidemicStats() {
return get('/epidemic/stats')
},
// 获取服务统计
getServiceStats() {
return get('/service/stats')
},
// 获取仓库统计
getWarehouseStats() {
return get('/warehouse/stats')
}
}

View File

@@ -1,34 +0,0 @@
import { get, post, put, del } from '@/utils/request'
// 疫情监控相关API
export const epidemicService = {
// 获取疫情列表
getEpidemicList(params = {}) {
return get('/epidemic/list', params)
},
// 获取疫情详情
getEpidemicDetail(id) {
return get(`/epidemic/${id}`)
},
// 创建疫情记录
createEpidemic(data) {
return post('/epidemic', data)
},
// 更新疫情记录
updateEpidemic(id, data) {
return put(`/epidemic/${id}`, data)
},
// 删除疫情记录
deleteEpidemic(id) {
return del(`/epidemic/${id}`)
},
// 获取疫情统计
getStats() {
return get('/epidemic/stats')
}
}

View File

@@ -1,34 +0,0 @@
import { get, post, put, del } from '@/utils/request'
// 人员管理相关API
export const personnelService = {
// 获取人员列表
getPersonnelList(params = {}) {
return get('/personnel/list', params)
},
// 获取人员详情
getPersonnelDetail(id) {
return get(`/personnel/${id}`)
},
// 创建人员
createPersonnel(data) {
return post('/personnel', data)
},
// 更新人员
updatePersonnel(id, data) {
return put(`/personnel/${id}`, data)
},
// 删除人员
deletePersonnel(id) {
return del(`/personnel/${id}`)
},
// 获取人员统计
getStats() {
return get('/personnel/stats')
}
}

View File

@@ -1,34 +0,0 @@
import { get, post, put, del } from '@/utils/request'
// 服务管理相关API
export const serviceService = {
// 获取服务列表
getServiceList(params = {}) {
return get('/service/list', params)
},
// 获取服务详情
getServiceDetail(id) {
return get(`/service/${id}`)
},
// 创建服务
createService(data) {
return post('/service', data)
},
// 更新服务
updateService(id, data) {
return put(`/service/${id}`, data)
},
// 删除服务
deleteService(id) {
return del(`/service/${id}`)
},
// 获取服务统计
getStats() {
return get('/service/stats')
}
}

View File

@@ -1,34 +0,0 @@
import { get, post, put, del } from '@/utils/request'
// 监管管理相关API
export const supervisionService = {
// 获取监管列表
getSupervisionList(params = {}) {
return get('/supervision/list', params)
},
// 获取监管详情
getSupervisionDetail(id) {
return get(`/supervision/${id}`)
},
// 创建监管记录
createSupervision(data) {
return post('/supervision', data)
},
// 更新监管记录
updateSupervision(id, data) {
return put(`/supervision/${id}`, data)
},
// 删除监管记录
deleteSupervision(id) {
return del(`/supervision/${id}`)
},
// 获取监管统计
getStats() {
return get('/supervision/stats')
}
}

View File

@@ -1,34 +0,0 @@
import { get, post, put, del } from '@/utils/request'
// 仓库管理相关API
export const warehouseService = {
// 获取仓库列表
getWarehouseList(params = {}) {
return get('/warehouse/list', params)
},
// 获取仓库详情
getWarehouseDetail(id) {
return get(`/warehouse/${id}`)
},
// 创建仓库
createWarehouse(data) {
return post('/warehouse', data)
},
// 更新仓库
updateWarehouse(id, data) {
return put(`/warehouse/${id}`, data)
},
// 删除仓库
deleteWarehouse(id) {
return del(`/warehouse/${id}`)
},
// 获取仓库统计
getStats() {
return get('/warehouse/stats')
}
}

View File

@@ -1,89 +0,0 @@
// 混入样式
// 文本省略
@mixin text-ellipsis($lines: 1) {
@if $lines == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
// 清除浮动
@mixin clearfix {
&::after {
content: '';
display: table;
clear: both;
}
}
// 居中
@mixin center($direction: both) {
@if $direction == both {
display: flex;
align-items: center;
justify-content: center;
} @else if $direction == horizontal {
display: flex;
justify-content: center;
} @else if $direction == vertical {
display: flex;
align-items: center;
}
}
// 按钮样式
@mixin button($bg-color: $primary-color, $text-color: #fff) {
display: inline-block;
padding: 20rpx 40rpx;
background: $bg-color;
color: $text-color;
border: none;
border-radius: $border-radius-base;
text-align: center;
font-size: $font-size-lg;
cursor: pointer;
transition: all 0.3s;
&:hover {
opacity: 0.8;
}
&:active {
transform: scale(0.98);
}
}
// 卡片样式
@mixin card {
background: $background-color-white;
border-radius: $border-radius-lg;
padding: $spacing-lg;
margin-bottom: $spacing-base;
box-shadow: $box-shadow-base;
}
// 输入框样式
@mixin input {
width: 100%;
padding: 24rpx 20rpx;
border: 1rpx solid $border-color;
border-radius: $border-radius-base;
font-size: $font-size-lg;
background: $background-color-white;
&:focus {
border-color: $primary-color;
outline: none;
}
&::placeholder {
color: $text-color-placeholder;
}
}

View File

@@ -1,44 +0,0 @@
// 颜色变量
$primary-color: #1890ff;
$success-color: #52c41a;
$warning-color: #faad14;
$danger-color: #ff4d4f;
$info-color: #1890ff;
$text-color: #333;
$text-color-secondary: #666;
$text-color-disabled: #999;
$text-color-placeholder: #ccc;
$background-color: #f6f6f6;
$background-color-light: #fafafa;
$background-color-white: #fff;
$border-color: #d9d9d9;
$border-color-light: #f0f0f0;
// 字体大小
$font-size-xs: 20rpx;
$font-size-sm: 24rpx;
$font-size-base: 26rpx;
$font-size-lg: 28rpx;
$font-size-xl: 30rpx;
$font-size-xxl: 32rpx;
// 间距
$spacing-xs: 8rpx;
$spacing-sm: 16rpx;
$spacing-base: 20rpx;
$spacing-lg: 30rpx;
$spacing-xl: 40rpx;
$spacing-xxl: 60rpx;
// 圆角
$border-radius-sm: 4rpx;
$border-radius-base: 8rpx;
$border-radius-lg: 16rpx;
// 阴影
$box-shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
$box-shadow-base: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
$box-shadow-lg: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);

View File

@@ -1,134 +0,0 @@
// 认证工具类
class Auth {
constructor() {
this.tokenKey = 'government_token'
this.userKey = 'government_user'
}
// 设置token
setToken(token) {
try {
// 优先使用localStorageH5环境
if (typeof localStorage !== 'undefined') {
localStorage.setItem(this.tokenKey, token)
} else if (typeof uni !== 'undefined') {
// uni-app环境
uni.setStorageSync(this.tokenKey, token)
}
return true
} catch (error) {
console.error('设置token失败:', error)
return false
}
}
// 获取token
getToken() {
try {
// 优先使用localStorageH5环境
if (typeof localStorage !== 'undefined') {
return localStorage.getItem(this.tokenKey) || ''
} else if (typeof uni !== 'undefined') {
// uni-app环境
return uni.getStorageSync(this.tokenKey) || ''
}
return ''
} catch (error) {
console.error('获取token失败:', error)
return ''
}
}
// 清除token
clearToken() {
try {
// 优先使用localStorageH5环境
if (typeof localStorage !== 'undefined') {
localStorage.removeItem(this.tokenKey)
localStorage.removeItem(this.userKey)
} else if (typeof uni !== 'undefined') {
// uni-app环境
uni.removeStorageSync(this.tokenKey)
uni.removeStorageSync(this.userKey)
}
return true
} catch (error) {
console.error('清除token失败:', error)
return false
}
}
// 设置用户信息
setUser(user) {
try {
// 优先使用localStorageH5环境
if (typeof localStorage !== 'undefined') {
localStorage.setItem(this.userKey, JSON.stringify(user))
} else if (typeof uni !== 'undefined') {
// uni-app环境
uni.setStorageSync(this.userKey, user)
}
return true
} catch (error) {
console.error('设置用户信息失败:', error)
return false
}
}
// 获取用户信息
getUser() {
try {
// 优先使用localStorageH5环境
if (typeof localStorage !== 'undefined') {
const userStr = localStorage.getItem(this.userKey)
return userStr ? JSON.parse(userStr) : null
} else if (typeof uni !== 'undefined') {
// uni-app环境
return uni.getStorageSync(this.userKey) || null
}
return null
} catch (error) {
console.error('获取用户信息失败:', error)
return null
}
}
// 检查是否已登录
isAuthenticated() {
const token = this.getToken()
const user = this.getUser()
return !!(token && user)
}
// 登出
logout() {
this.clearToken()
// 跳转到登录页
if (typeof uni !== 'undefined') {
uni.reLaunch({
url: '/pages/login/login'
})
} else {
// H5环境
window.location.href = '/login'
}
}
// 检查token是否过期
isTokenExpired() {
const token = this.getToken()
if (!token) return true
try {
// 简单的token过期检查实际项目中应该解析JWT
const tokenData = JSON.parse(atob(token.split('.')[1]))
const currentTime = Math.floor(Date.now() / 1000)
return tokenData.exp < currentTime
} catch (error) {
console.error('解析token失败:', error)
return true
}
}
}
export default new Auth()

View File

@@ -1,222 +0,0 @@
import auth from './auth'
// API基础配置
const BASE_URL = process.env.NODE_ENV === 'development'
? 'http://localhost:5352/api'
: 'https://your-domain.com/api'
// 请求拦截器
const request = (options) => {
return new Promise((resolve, reject) => {
// 添加认证头
const token = auth.getToken()
if (token) {
options.headers = {
...options.headers,
'Authorization': `Bearer ${token}`
}
}
// 添加基础URL
if (!options.url.startsWith('http')) {
options.url = BASE_URL + options.url
}
// 添加默认配置
const config = {
timeout: 10000,
...options
}
console.log('发起请求:', config)
// 检查环境,使用不同的请求方法
if (typeof uni !== 'undefined') {
// uni-app环境
uni.request({
...config,
success: (res) => {
console.log('请求成功:', res)
// 检查响应状态
if (res.statusCode === 200) {
const { code, message, data } = res.data
if (code === 200) {
resolve(data)
} else if (code === 401) {
// token过期清除本地存储并跳转登录
auth.clearToken()
uni.reLaunch({
url: '/pages/login/login'
})
reject(new Error(message || '认证失败'))
} else {
reject(new Error(message || '请求失败'))
}
} else {
reject(new Error(`请求失败: ${res.statusCode}`))
}
},
fail: (err) => {
console.error('请求失败:', err)
reject(new Error(err.errMsg || '网络请求失败'))
}
})
} else {
// H5环境使用fetch
fetch(config.url, {
method: config.method || 'GET',
headers: {
'Content-Type': 'application/json',
...config.headers
},
body: config.data ? JSON.stringify(config.data) : undefined
})
.then(response => {
console.log('请求成功:', response)
if (response.ok) {
return response.json()
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
})
.then(data => {
const { code, message, data: responseData } = data
if (code === 200) {
resolve(responseData)
} else if (code === 401) {
// token过期清除本地存储并跳转登录
auth.clearToken()
window.location.href = '/login'
reject(new Error(message || '认证失败'))
} else {
reject(new Error(message || '请求失败'))
}
})
.catch(error => {
console.error('请求失败:', error)
reject(new Error(error.message || '网络请求失败'))
})
}
})
}
// GET请求
export const get = (url, params = {}) => {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
const fullUrl = queryString ? `${url}?${queryString}` : url
return request({
url: fullUrl,
method: 'GET'
})
}
// POST请求
export const post = (url, data = {}) => {
return request({
url,
method: 'POST',
data,
header: {
'Content-Type': 'application/json'
}
})
}
// PUT请求
export const put = (url, data = {}) => {
return request({
url,
method: 'PUT',
data,
header: {
'Content-Type': 'application/json'
}
})
}
// DELETE请求
export const del = (url) => {
return request({
url,
method: 'DELETE'
})
}
// 文件上传
export const upload = (url, filePath, formData = {}) => {
return new Promise((resolve, reject) => {
const token = auth.getToken()
if (typeof uni !== 'undefined') {
// uni-app环境
uni.uploadFile({
url: BASE_URL + url,
filePath,
name: 'file',
formData,
header: {
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
try {
const data = JSON.parse(res.data)
if (data.code === 200) {
resolve(data.data)
} else {
reject(new Error(data.message || '上传失败'))
}
} catch (error) {
reject(new Error('解析响应失败'))
}
},
fail: (err) => {
reject(new Error(err.errMsg || '上传失败'))
}
})
} else {
// H5环境使用FormData
const form = new FormData()
form.append('file', filePath)
// 添加其他表单数据
Object.keys(formData).forEach(key => {
form.append(key, formData[key])
})
fetch(BASE_URL + url, {
method: 'POST',
headers: {
'Authorization': token ? `Bearer ${token}` : ''
},
body: form
})
.then(response => response.json())
.then(data => {
if (data.code === 200) {
resolve(data.data)
} else {
reject(new Error(data.message || '上传失败'))
}
})
.catch(error => {
reject(new Error(error.message || '上传失败'))
})
}
})
}
export default {
get,
post,
put,
delete: del,
upload
}

View File

@@ -1,109 +0,0 @@
// uni-app兼容工具用于H5环境
export const uni = {
// 显示提示
showToast(options) {
if (typeof uni !== 'undefined') {
return uni.showToast(options)
} else {
// H5环境
alert(options.title || '提示')
}
},
// 显示模态框
showModal(options) {
if (typeof uni !== 'undefined') {
return uni.showModal(options)
} else {
// H5环境
return new Promise((resolve) => {
const result = confirm(options.content || '确认操作?')
resolve({ confirm: result, cancel: !result })
})
}
},
// 页面跳转
navigateTo(options) {
if (typeof uni !== 'undefined') {
return uni.navigateTo(options)
} else {
// H5环境
window.location.href = options.url
}
},
// 重新启动
reLaunch(options) {
if (typeof uni !== 'undefined') {
return uni.reLaunch(options)
} else {
// H5环境
window.location.href = options.url
}
},
// 选择图片
chooseImage(options) {
if (typeof uni !== 'undefined') {
return uni.chooseImage(options)
} else {
// H5环境
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.multiple = options.count > 1
input.onchange = (e) => {
const files = Array.from(e.target.files)
const tempFilePaths = files.map(file => URL.createObjectURL(file))
options.success && options.success({ tempFilePaths })
}
input.click()
}
},
// 下拉刷新
stopPullDownRefresh() {
if (typeof uni !== 'undefined') {
return uni.stopPullDownRefresh()
}
// H5环境不需要处理
},
// 设置存储
setStorageSync(key, data) {
if (typeof uni !== 'undefined') {
return uni.setStorageSync(key, data)
} else {
// H5环境
localStorage.setItem(key, JSON.stringify(data))
}
},
// 获取存储
getStorageSync(key) {
if (typeof uni !== 'undefined') {
return uni.getStorageSync(key)
} else {
// H5环境
const data = localStorage.getItem(key)
try {
return data ? JSON.parse(data) : ''
} catch {
return data || ''
}
}
},
// 移除存储
removeStorageSync(key) {
if (typeof uni !== 'undefined') {
return uni.removeStorageSync(key)
} else {
// H5环境
localStorage.removeItem(key)
}
}
}
export default uni