混凝土运输管理微信小程序、替班
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

461 lines
9.5 KiB

2 weeks ago
  1. <template>
  2. <view class="content">
  3. <navbar title="员工管理" leftClick @leftClick="$utils.navigateBack" />
  4. <view class="header">
  5. <view class="title">员工管理</view>
  6. <view class="subtitle">管理企业员工信息和权限</view>
  7. </view>
  8. <!-- 搜索栏 -->
  9. <view class="search-bar">
  10. <view class="search-input">
  11. <uv-icon name="search" size="20" color="#999"></uv-icon>
  12. <input v-model="searchKeyword" placeholder="搜索员工姓名或手机号" @input="onSearch" />
  13. </view>
  14. <view class="add-btn" @click="addStaff">
  15. <uv-icon name="plus" size="20" color="#fff"></uv-icon>
  16. <text>添加</text>
  17. </view>
  18. </view>
  19. <!-- 统计信息 -->
  20. <view class="stats-container">
  21. <view class="stats-item">
  22. <view class="stats-number">{{ totalStaff }}</view>
  23. <view class="stats-label">总员工数</view>
  24. </view>
  25. <view class="stats-item">
  26. <view class="stats-number">{{ activeStaff }}</view>
  27. <view class="stats-label">在职员工</view>
  28. </view>
  29. <view class="stats-item">
  30. <view class="stats-number">{{ onlineStaff }}</view>
  31. <view class="stats-label">在线员工</view>
  32. </view>
  33. </view>
  34. <!-- 员工列表 -->
  35. <view class="staff-list">
  36. <view v-if="filteredStaffList.length === 0" class="empty-state">
  37. <view class="empty-icon">👥</view>
  38. <view class="empty-text">暂无员工信息</view>
  39. </view>
  40. <view v-for="(item, index) in filteredStaffList" :key="index" class="staff-item">
  41. <view class="staff-avatar">
  42. <image :src="item.avatar" mode="aspectFill"></image>
  43. <view class="online-status" :class="{online: item.isOnline}"></view>
  44. </view>
  45. <view class="staff-info">
  46. <view class="staff-header">
  47. <view class="name">{{ item.name }}</view>
  48. <view class="role-badge" :class="item.role">{{ getRoleText(item.role) }}</view>
  49. </view>
  50. <view class="staff-details">
  51. <text>手机{{ item.phone }}</text>
  52. <text>部门{{ item.department }}</text>
  53. <text>入职时间{{ item.joinDate }}</text>
  54. </view>
  55. <view class="staff-stats">
  56. <view class="stat-item">
  57. <text class="stat-label">本月订单</text>
  58. <text class="stat-value">{{ item.monthlyOrders }}</text>
  59. </view>
  60. <view class="stat-item">
  61. <text class="stat-label">评分</text>
  62. <text class="stat-value">{{ item.rating }}</text>
  63. </view>
  64. </view>
  65. </view>
  66. <view class="staff-actions">
  67. <view class="action-btn" @click="viewStaff(item)">
  68. <uv-icon name="eye" size="16" color="#007AFF"></uv-icon>
  69. </view>
  70. <view class="action-btn" @click="editStaff(item)">
  71. <uv-icon name="edit-pen" size="16" color="#ff9500"></uv-icon>
  72. </view>
  73. <view class="action-btn" @click="deleteStaff(item)">
  74. <uv-icon name="trash" size="16" color="#ff3b30"></uv-icon>
  75. </view>
  76. </view>
  77. </view>
  78. </view>
  79. </view>
  80. </template>
  81. <script>
  82. import navbar from '@/components/base/navbar.vue'
  83. export default {
  84. name: 'StaffManage',
  85. components: {
  86. navbar
  87. },
  88. data() {
  89. return {
  90. searchKeyword: '',
  91. totalStaff: 15,
  92. activeStaff: 12,
  93. onlineStaff: 8,
  94. staffList: [
  95. {
  96. id: 1,
  97. name: '张师傅',
  98. phone: '13800138001',
  99. avatar: '/static/re/logo.png',
  100. role: 'driver',
  101. department: '运输部',
  102. joinDate: '2023-06-15',
  103. isOnline: true,
  104. monthlyOrders: 28,
  105. rating: 4.8
  106. },
  107. {
  108. id: 2,
  109. name: '李师傅',
  110. phone: '13800138002',
  111. avatar: '/static/re/logo.png',
  112. role: 'driver',
  113. department: '运输部',
  114. joinDate: '2023-08-20',
  115. isOnline: false,
  116. monthlyOrders: 22,
  117. rating: 4.6
  118. },
  119. {
  120. id: 3,
  121. name: '王主管',
  122. phone: '13800138003',
  123. avatar: '/static/re/logo.png',
  124. role: 'supervisor',
  125. department: '调度部',
  126. joinDate: '2023-03-10',
  127. isOnline: true,
  128. monthlyOrders: 45,
  129. rating: 4.9
  130. },
  131. {
  132. id: 4,
  133. name: '赵师傅',
  134. phone: '13800138004',
  135. avatar: '/static/re/logo.png',
  136. role: 'driver',
  137. department: '运输部',
  138. joinDate: '2023-09-05',
  139. isOnline: true,
  140. monthlyOrders: 31,
  141. rating: 4.7
  142. },
  143. {
  144. id: 5,
  145. name: '陈操作员',
  146. phone: '13800138005',
  147. avatar: '/static/re/logo.png',
  148. role: 'operator',
  149. department: '操作部',
  150. joinDate: '2023-11-12',
  151. isOnline: false,
  152. monthlyOrders: 18,
  153. rating: 4.5
  154. }
  155. ]
  156. }
  157. },
  158. computed: {
  159. filteredStaffList() {
  160. if (!this.searchKeyword) {
  161. return this.staffList;
  162. }
  163. return this.staffList.filter(staff =>
  164. staff.name.includes(this.searchKeyword) ||
  165. staff.phone.includes(this.searchKeyword)
  166. );
  167. }
  168. },
  169. onLoad() {
  170. uni.setNavigationBarTitle({
  171. title: '员工管理'
  172. });
  173. },
  174. methods: {
  175. onSearch() {
  176. // 搜索逻辑已在computed中实现
  177. },
  178. getRoleText(role) {
  179. switch(role) {
  180. case 'driver': return '司机';
  181. case 'supervisor': return '主管';
  182. case 'operator': return '操作员';
  183. default: return '员工';
  184. }
  185. },
  186. addStaff() {
  187. uni.navigateTo({
  188. url: '/pages_order/staff/addStaff'
  189. });
  190. },
  191. viewStaff(staff) {
  192. uni.navigateTo({
  193. url: `/pages_order/staff/staffDetail?id=${staff.id}`
  194. });
  195. },
  196. editStaff(staff) {
  197. uni.navigateTo({
  198. url: `/pages_order/staff/editStaff?id=${staff.id}`
  199. });
  200. },
  201. deleteStaff(staff) {
  202. uni.showModal({
  203. title: '确认删除',
  204. content: `确定要删除员工${staff.name}吗?此操作不可恢复。`,
  205. success: (res) => {
  206. if (res.confirm) {
  207. const index = this.staffList.findIndex(item => item.id === staff.id);
  208. if (index > -1) {
  209. this.staffList.splice(index, 1);
  210. this.totalStaff--;
  211. this.activeStaff--;
  212. if (staff.isOnline) {
  213. this.onlineStaff--;
  214. }
  215. uni.showToast({
  216. title: '删除成功',
  217. icon: 'success'
  218. });
  219. }
  220. }
  221. }
  222. });
  223. }
  224. }
  225. }
  226. </script>
  227. <style scoped lang="scss">
  228. .content {
  229. padding: 20rpx;
  230. min-height: 100vh;
  231. background-color: #f5f5f5;
  232. }
  233. .header {
  234. background-color: #fff;
  235. padding: 30rpx;
  236. border-radius: 10rpx;
  237. margin-bottom: 20rpx;
  238. text-align: center;
  239. .title {
  240. font-size: 36rpx;
  241. font-weight: bold;
  242. color: #333;
  243. margin-bottom: 10rpx;
  244. }
  245. .subtitle {
  246. font-size: 28rpx;
  247. color: #666;
  248. }
  249. }
  250. .search-bar {
  251. display: flex;
  252. gap: 20rpx;
  253. margin-bottom: 20rpx;
  254. .search-input {
  255. flex: 1;
  256. display: flex;
  257. align-items: center;
  258. padding: 20rpx;
  259. background-color: #fff;
  260. border-radius: 25rpx;
  261. gap: 15rpx;
  262. input {
  263. flex: 1;
  264. font-size: 28rpx;
  265. border: none;
  266. outline: none;
  267. }
  268. }
  269. .add-btn {
  270. display: flex;
  271. align-items: center;
  272. gap: 8rpx;
  273. padding: 20rpx 30rpx;
  274. background-color: #007AFF;
  275. color: #fff;
  276. border-radius: 25rpx;
  277. font-size: 28rpx;
  278. }
  279. }
  280. .stats-container {
  281. display: flex;
  282. background-color: #fff;
  283. border-radius: 10rpx;
  284. margin-bottom: 20rpx;
  285. padding: 30rpx 0;
  286. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  287. .stats-item {
  288. flex: 1;
  289. text-align: center;
  290. .stats-number {
  291. font-size: 48rpx;
  292. font-weight: bold;
  293. color: #007AFF;
  294. margin-bottom: 10rpx;
  295. }
  296. .stats-label {
  297. font-size: 24rpx;
  298. color: #666;
  299. }
  300. }
  301. }
  302. .staff-list {
  303. .empty-state {
  304. text-align: center;
  305. padding: 100rpx 0;
  306. background-color: #fff;
  307. border-radius: 10rpx;
  308. .empty-icon {
  309. font-size: 120rpx;
  310. margin-bottom: 20rpx;
  311. }
  312. .empty-text {
  313. font-size: 28rpx;
  314. color: #999;
  315. }
  316. }
  317. .staff-item {
  318. display: flex;
  319. background-color: #fff;
  320. border-radius: 10rpx;
  321. padding: 30rpx;
  322. margin-bottom: 20rpx;
  323. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  324. .staff-avatar {
  325. position: relative;
  326. width: 120rpx;
  327. height: 120rpx;
  328. margin-right: 20rpx;
  329. image {
  330. width: 100%;
  331. height: 100%;
  332. border-radius: 60rpx;
  333. }
  334. .online-status {
  335. position: absolute;
  336. bottom: 5rpx;
  337. right: 5rpx;
  338. width: 24rpx;
  339. height: 24rpx;
  340. border-radius: 12rpx;
  341. background-color: #ccc;
  342. border: 3rpx solid #fff;
  343. &.online {
  344. background-color: #34c759;
  345. }
  346. }
  347. }
  348. .staff-info {
  349. flex: 1;
  350. .staff-header {
  351. display: flex;
  352. align-items: center;
  353. margin-bottom: 15rpx;
  354. gap: 15rpx;
  355. .name {
  356. font-size: 32rpx;
  357. font-weight: bold;
  358. color: #333;
  359. }
  360. .role-badge {
  361. padding: 6rpx 12rpx;
  362. border-radius: 15rpx;
  363. font-size: 22rpx;
  364. color: #fff;
  365. &.driver {
  366. background-color: #007AFF;
  367. }
  368. &.supervisor {
  369. background-color: #ff9500;
  370. }
  371. &.operator {
  372. background-color: #34c759;
  373. }
  374. }
  375. }
  376. .staff-details {
  377. font-size: 26rpx;
  378. color: #666;
  379. line-height: 1.4;
  380. margin-bottom: 15rpx;
  381. text {
  382. display: block;
  383. margin-bottom: 5rpx;
  384. }
  385. }
  386. .staff-stats {
  387. display: flex;
  388. gap: 30rpx;
  389. .stat-item {
  390. font-size: 24rpx;
  391. .stat-label {
  392. color: #666;
  393. }
  394. .stat-value {
  395. color: #007AFF;
  396. font-weight: bold;
  397. }
  398. }
  399. }
  400. }
  401. .staff-actions {
  402. display: flex;
  403. flex-direction: column;
  404. gap: 15rpx;
  405. .action-btn {
  406. width: 60rpx;
  407. height: 60rpx;
  408. display: flex;
  409. align-items: center;
  410. justify-content: center;
  411. background-color: #f8f8f8;
  412. border-radius: 30rpx;
  413. }
  414. }
  415. }
  416. }
  417. </style>