添加银行和政府端小程序
This commit is contained in:
34
government-mini-program/src/App.vue
Normal file
34
government-mini-program/src/App.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
193
government-mini-program/src/app.scss
Normal file
193
government-mini-program/src/app.scss
Normal file
@@ -0,0 +1,193 @@
|
||||
// 全局样式文件
|
||||
@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;
|
||||
}
|
||||
}
|
||||
510
government-mini-program/src/components/Approval.vue
Normal file
510
government-mini-program/src/components/Approval.vue
Normal file
@@ -0,0 +1,510 @@
|
||||
<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>
|
||||
424
government-mini-program/src/components/Dashboard.vue
Normal file
424
government-mini-program/src/components/Dashboard.vue
Normal file
@@ -0,0 +1,424 @@
|
||||
<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>
|
||||
514
government-mini-program/src/components/Epidemic.vue
Normal file
514
government-mini-program/src/components/Epidemic.vue
Normal file
@@ -0,0 +1,514 @@
|
||||
<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>
|
||||
366
government-mini-program/src/components/Home.vue
Normal file
366
government-mini-program/src/components/Home.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<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>
|
||||
267
government-mini-program/src/components/Login.vue
Normal file
267
government-mini-program/src/components/Login.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<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>
|
||||
503
government-mini-program/src/components/Personnel.vue
Normal file
503
government-mini-program/src/components/Personnel.vue
Normal file
@@ -0,0 +1,503 @@
|
||||
<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>
|
||||
407
government-mini-program/src/components/Profile.vue
Normal file
407
government-mini-program/src/components/Profile.vue
Normal file
@@ -0,0 +1,407 @@
|
||||
<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>
|
||||
477
government-mini-program/src/components/Service.vue
Normal file
477
government-mini-program/src/components/Service.vue
Normal file
@@ -0,0 +1,477 @@
|
||||
<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>
|
||||
663
government-mini-program/src/components/Supervision.vue
Normal file
663
government-mini-program/src/components/Supervision.vue
Normal file
@@ -0,0 +1,663 @@
|
||||
<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>
|
||||
494
government-mini-program/src/components/Warehouse.vue
Normal file
494
government-mini-program/src/components/Warehouse.vue
Normal file
@@ -0,0 +1,494 @@
|
||||
<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>
|
||||
59
government-mini-program/src/main.js
Normal file
59
government-mini-program/src/main.js
Normal file
@@ -0,0 +1,59 @@
|
||||
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
|
||||
22
government-mini-program/src/pages/approval/approval.vue
Normal file
22
government-mini-program/src/pages/approval/approval.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/dashboard/dashboard.vue
Normal file
22
government-mini-program/src/pages/dashboard/dashboard.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/epidemic/epidemic.vue
Normal file
22
government-mini-program/src/pages/epidemic/epidemic.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/index/index.vue
Normal file
22
government-mini-program/src/pages/index/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/login/login.vue
Normal file
22
government-mini-program/src/pages/login/login.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/personnel/personnel.vue
Normal file
22
government-mini-program/src/pages/personnel/personnel.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/profile/profile.vue
Normal file
22
government-mini-program/src/pages/profile/profile.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/service/service.vue
Normal file
22
government-mini-program/src/pages/service/service.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
22
government-mini-program/src/pages/warehouse/warehouse.vue
Normal file
22
government-mini-program/src/pages/warehouse/warehouse.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
108
government-mini-program/src/router/index.js
Normal file
108
government-mini-program/src/router/index.js
Normal file
@@ -0,0 +1,108 @@
|
||||
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
|
||||
44
government-mini-program/src/services/approvalService.js
Normal file
44
government-mini-program/src/services/approvalService.js
Normal file
@@ -0,0 +1,44 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
22
government-mini-program/src/services/authService.js
Normal file
22
government-mini-program/src/services/authService.js
Normal file
@@ -0,0 +1,22 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
39
government-mini-program/src/services/dashboardService.js
Normal file
39
government-mini-program/src/services/dashboardService.js
Normal file
@@ -0,0 +1,39 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
34
government-mini-program/src/services/epidemicService.js
Normal file
34
government-mini-program/src/services/epidemicService.js
Normal file
@@ -0,0 +1,34 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
34
government-mini-program/src/services/personnelService.js
Normal file
34
government-mini-program/src/services/personnelService.js
Normal file
@@ -0,0 +1,34 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
34
government-mini-program/src/services/serviceService.js
Normal file
34
government-mini-program/src/services/serviceService.js
Normal file
@@ -0,0 +1,34 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
34
government-mini-program/src/services/supervisionService.js
Normal file
34
government-mini-program/src/services/supervisionService.js
Normal file
@@ -0,0 +1,34 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
34
government-mini-program/src/services/warehouseService.js
Normal file
34
government-mini-program/src/services/warehouseService.js
Normal file
@@ -0,0 +1,34 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
89
government-mini-program/src/styles/mixins.scss
Normal file
89
government-mini-program/src/styles/mixins.scss
Normal file
@@ -0,0 +1,89 @@
|
||||
// 混入样式
|
||||
|
||||
// 文本省略
|
||||
@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;
|
||||
}
|
||||
}
|
||||
44
government-mini-program/src/styles/variables.scss
Normal file
44
government-mini-program/src/styles/variables.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
// 颜色变量
|
||||
$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);
|
||||
134
government-mini-program/src/utils/auth.js
Normal file
134
government-mini-program/src/utils/auth.js
Normal file
@@ -0,0 +1,134 @@
|
||||
// 认证工具类
|
||||
class Auth {
|
||||
constructor() {
|
||||
this.tokenKey = 'government_token'
|
||||
this.userKey = 'government_user'
|
||||
}
|
||||
|
||||
// 设置token
|
||||
setToken(token) {
|
||||
try {
|
||||
// 优先使用localStorage(H5环境)
|
||||
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 {
|
||||
// 优先使用localStorage(H5环境)
|
||||
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 {
|
||||
// 优先使用localStorage(H5环境)
|
||||
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 {
|
||||
// 优先使用localStorage(H5环境)
|
||||
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 {
|
||||
// 优先使用localStorage(H5环境)
|
||||
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()
|
||||
222
government-mini-program/src/utils/request.js
Normal file
222
government-mini-program/src/utils/request.js
Normal file
@@ -0,0 +1,222 @@
|
||||
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
|
||||
}
|
||||
109
government-mini-program/src/utils/uni-compat.js
Normal file
109
government-mini-program/src/utils/uni-compat.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user