Просмотр исходного кода

feat: 首页增加常用功能

liujt 3 часов назад
Родитель
Сommit
e0fe04f909
4 измененных файлов с 397 добавлено и 158 удалено
  1. 39 2
      api/home/index.js
  2. 2 2
      manifest.json
  3. 349 147
      pages/home/components/navigation.vue
  4. 7 7
      pages/home/pages/manage/manage.vue

+ 39 - 2
api/home/index.js

@@ -1,7 +1,8 @@
 import {
 	postJ,
 	post,
-	get
+	get,
+	deleteApi,
 } from '@/utils/request'
 import Vue from 'vue'
 
@@ -184,4 +185,40 @@ export async function getPurchaseOrderTrendSummary(params) {
 		return data.data
 	}
 	return Promise.reject(new Error(data.message))
-}  
+}  
+
+/**
+ * 当前用户菜单数据
+ * @data data
+ */
+export async function userResourceListAPI() {
+  const res = await post(Vue.prototype.apiUrl + '/sys/indexuserresource/list', { useScope: 2 });
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 保存菜单数据
+ * @data data
+ */
+export async function userResourceSaveAPI(data) {
+  const res = await postJ(Vue.prototype.apiUrl + '/sys/indexuserresource/save', data);
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 删除当前用户菜单数据
+ * @data data
+ */
+export async function userResourceDeleteAPI(data) {
+  const res = await deleteApi(Vue.prototype.apiUrl + '/sys/indexuserresource/delete', data);
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}

+ 2 - 2
manifest.json

@@ -11,9 +11,9 @@
                 "/api" : {
                     // "target" : "http://192.168.1.110:18086/",
                     // "target" : "http://123.249.79.125/api/",
-                    "target" : "http://114.116.248.196:86/api/",
+                    // "target" : "http://114.116.248.196:86/api/",
                     // "target": "http://116.63.185.248:80/api",
-                    // "target" : "http://192.168.1.251:18086/",
+                    "target" : "http://192.168.1.3:18086/",
                     // "target" : "http://aiot.zoomwin.com.cn:51001/api",
                     "changeOrigin" : true,
                     "secure" : false,

+ 349 - 147
pages/home/components/navigation.vue

@@ -1,170 +1,372 @@
 <template>
-	<view class="nav">
-		<view class="nav-item" v-for="(item, index) in workList" @click="toNav(item.link_url)">
-			<span :class="item.class"></span>
-			<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-			<label>{{ item.title }}</label>
+	<view class="nav-container">
+		<!-- 快捷功能 -->
+		<view class="quick-functions-box">
+			<view class="qfn-header">
+				<text class="qfn-title">常用功能</text>
+				<view class="qfn-actions">
+					<u-icon name="edit-pen" size="36" color="#999" @click="toggleDelMode" v-if="!isDelMode"></u-icon>
+					<text class="qfn-done" @click="toggleDelMode" v-if="isDelMode">完成</text>
+					<u-icon name="plus" size="36" color="#999" style="margin-left:16rpx" @click="openAddPopup"></u-icon>
+				</view>
+			</view>
+			<scroll-view scroll-x class="qfn-scroll" v-if="quickFunctions.length">
+				<view class="qfn-item" v-for="(it, i) in quickFunctions" :key="it.id || i" @click="handleQuick(it, i)">
+					<view class="qfn-icon" :class="isDelMode ? 'qfn-del' : ''">
+						<!-- <u-icon :name="it.icon||'grid'" size="44" color="#157A2C"></u-icon> -->
+						<span :class="'iconfont ' + it.icon"></span>
+					</view>
+					<text class="qfn-name">{{ it.name }}</text>
+					<u-icon v-if="isDelMode" name="minus-circle-fill" size="32" color="#ff4949"
+						class="qfn-badge"></u-icon>
+				</view>
+			</scroll-view>
+			<view class="qfn-empty" v-else @click="openAddPopup">
+				<u-icon name="plus-circle" size="40" color="#ccc"></u-icon>
+				<text class="qfn-empty-tip">添加常用功能</text>
+			</view>
 		</view>
+
+		<!-- 原有导航 -->
+		<view class="nav">
+			<view class="nav-item" v-for="(item, index) in workList" @click="toNav(item.link_url)">
+				<span :class="item.class"></span>
+				<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+				<label>{{ item.title }}</label>
+			</view>
+		</view>
+
+		<!-- 选择菜单弹窗 -->
+		<u-popup :show="showAddPopup" mode="bottom" :round="16" @close="showAddPopup = false" closeable>
+			<view class="add-popup">
+				<view class="add-popup-head">选择菜单</view>
+				<scroll-view scroll-y class="add-popup-body">
+					<view class="add-popup-row" v-for="(m, index) in availableMenus" :key="m.id" @click="addQuick(m, index)">
+						<view class="add-popup-left">
+							<text class="add-popup-name">{{ m.name }}</text>
+						</view>
+						<u-icon v-if="checkedIds[m.id]" name="checkbox-mark" size="36" color="#157A2C"></u-icon>
+						<u-icon v-else name="plus-circle" size="36" color="#ccc"></u-icon>
+					</view>
+					<view v-if="!availableMenus.length" class="add-popup-none">暂无菜单</view>
+				</scroll-view>
+			</view>
+		</u-popup>
+
+		<u-toast ref="uToast"></u-toast>
 	</view>
 </template>
 
 <script>
-	import {
-		getTodoList,
-		getUnreadNotifyMessageCountAPI
-	} from '@/api/wt/index.js'
-	import {
-		todoNuber
-	} from '@/api/myTicket'
-	export default {
-		props: {
-			workOrder: {
-				default: {}
-			}
+import { todoNuber } from '@/api/myTicket'
+import { userResourceListAPI, userResourceSaveAPI, userResourceDeleteAPI } from '@/api/home'
+export default {
+	props: { workOrder: { default: {} } },
+	data() {
+		return {
+			workList: [
+				{ class: 'iconfont icon-bianji', title: '工单待办', link_url: '/pages/home/myTicket/myTicket?orderType=1', badge: 0 },
+				{ class: 'iconfont icon-bianji', title: '工单已办', link_url: '/pages/home/myTicket/myTicket?orderType=2', badge: 0 },
+				{ class: 'iconfont icon-kuneipandian', title: '已发流程', link_url: '/pages/home/wt/send/send', badge: 0 },
+				{ class: 'iconfont icon-kuneipandian', title: '流程待办', link_url: '/pages/home/wt/todo/todo', badge: 0 },
+				{ class: 'iconfont icon-kuneipandian', title: '流程已办', link_url: '/pages/home/wt/done/done' }
+			],
+			quickFunctions: [],
+			isDelMode: false,
+			showAddPopup: false,
+			availableMenus: [],
+		}
+	},
+	watch: {
+			workOrder: { handler(v) { this.workList[0].badge = v.total }, deep: true }
 		},
-		data() {
-			return {
-				workList: [{
-						class: 'iconfont icon-bianji',
-						title: '工单待办',
-						link_url: '/pages/home/myTicket/myTicket?orderType=1',
-						badge: 0
-					},
-					{
-						class: 'iconfont icon-bianji',
-						title: '工单已办',
-						link_url: '/pages/home/myTicket/myTicket?orderType=2',
-						badge: 0
-					},
-					// {
-					// 	class: 'iconfont icon-dangqiangaojing',
-					// 	title: '我的消息',
-					// 	link_url: '/pages/home/wt/message/message',
-					// 	badge: 0
-					// 	// "num": 1
-					// },
-					{
-						class: 'iconfont icon-kuneipandian',
-						title: '已发流程',
-						link_url: '/pages/home/wt/send/send',
-						badge: 0
-						// "num": 1
-					},
-					{
-						class: 'iconfont icon-kuneipandian',
-						title: '流程待办',
-						link_url: '/pages/home/wt/todo/todo',
-						badge: 0
-						// "num": 1
-					},
-					{
-						class: 'iconfont icon-kuneipandian',
-						title: '流程已办',
-						link_url: '/pages/home/wt/done/done'
-						// badge: 0
-						// badge:0
-						// "num": 1
-					}
-				],
-				information: 0,
-				done: 0,
-				backlog: 0,
-				workOrderNum: 0
+		computed: {
+			// 已勾选的菜单 resourceId 映射(resourceId 即菜单 tree 中的 id)
+			checkedIds: function() {
+				var map = {}
+				var qf = this.quickFunctions || []
+				for (var i = 0; i < qf.length; i++) {
+					map[qf[i].resourceId] = true
+				}
+				return map
 			}
 		},
-		watch: {
-			workOrder: {
-				handler(newV, oldV) {
-					this.workList[0].badge = newV.total
-				},
-				deep: true
-			},
+	created() {
+		this.gettodoNuber()
+		this.loadQuick()
+		this.loadMenus()
+	},
+	methods: {
+		toNav(url) { uni.navigateTo({ url }) },
+		gettodoNuber() { todoNuber().then(r => { this.workList[3].badge = r.count }) },
+		// ===== 快捷功能 =====
+		async loadQuick() {
+			try { this.quickFunctions = await userResourceListAPI() } catch (e) {}
 		},
-		created() {
-			this.gettodoNuber()
-			// this.$nextTick(()=>{
-			// 	this.workList[0].badge = String(this.workOrder.total)
-			// })
+		loadMenus() {
+			try {
+				var raw = uni.getStorageSync('treeList')
+				if (!raw) { this.availableMenus = []; return }
+				var tree = typeof raw === 'string' ? JSON.parse(raw) : raw
+				if (Array.isArray(tree) && tree.length === 1 && tree[0].children) {
+					tree = tree[0].children
+				}
+				var arr = []
+				var walk = function (list) {
+					(list || []).forEach(function (it) {
+						if (it.menuType === 2) return
+						if (it.path && it.path.indexOf('/pages/') === 0) {
+							arr.push({ id: it.id, name: it.name || it.title || '', url: it.path, icon: it.icon || 'grid' })
+						}
+						if (it.children && it.children.length) { walk(it.children) }
+					})
+				}
+				walk(tree)
+				this.availableMenus = arr
+			} catch (e) { this.availableMenus = [] }
 		},
-		methods: {
-			toNav(url) {
-				uni.navigateTo({
-					url: url
-				})
-			},
-
-			
-			gettodoNuber() {
-				todoNuber().then(res => {
-					this.workList[3].badge = res.count
-				})
+		toggleDelMode() { this.isDelMode = !this.isDelMode },
+		openAddPopup() {
+			console.log('availableMenus~~', this.availableMenus)
+			if (!this.availableMenus.length) this.loadMenus()
+			this.showAddPopup = true
+		},
+		async addQuick(m, i) {
+			// m.id = menu tree ID, quickFunctions[] 中对应 resourceId
+			if (this.checkedIds[m.id]) {
+				// 找到对应的用户资源记录 ID 来删除
+				var found = this.quickFunctions.find(function(it) { return String(it.resourceId) === String(m.id) })
+				if (found) {
+					await userResourceDeleteAPI([found.id])
+				}
+				this.quickFunctions = this.quickFunctions.filter(function(it) { return String(it.resourceId) !== String(m.id) })
+			} else {
+				if (this.quickFunctions.length >= 12) return uni.showToast({ title: '最多12个', icon: 'none' })
+				await userResourceSaveAPI({ icon: '', pic: '', resourceId: m.id, sort: i, useScope: 2 })
+				// 保存后重新拉取列表,确保拿到服务端返回的 record id
+				await this.loadQuick()
 			}
-
-		}
+		},
+		async handleQuick(it, i) {
+			if (this.isDelMode) { 
+				await userResourceDeleteAPI([it.id])
+				this.quickFunctions.splice(i, 1)
+				return 
+			}
+			if (it.url) { uni.navigateTo({ url: it.url }) }
+		},
 	}
+}
 </script>
 
 <style lang="scss" scoped>
-	// 选项卡
-	.nav {
-		display: flex;
-		width: 100%;
-		padding: 32rpx 0;
-		margin-bottom: 24rpx;
-		align-items: center;
-		justify-content: space-around;
-		background-color: #fff;
-		font-size: $uni-font-size-sm;
-		color: $uni-text-color;
-		border-radius: 16rpx;
-
-		.nav-item {
-			position: relative;
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			margin-top: 5rpx;
-			align-items: center;
-			flex: 1;
-
-			span {
-				padding: 18rpx;
-				color: #ffffff;
-				margin-bottom: 14rpx;
-				border-radius: 50%;
-			}
+/* 快捷功能 */
+.quick-functions-box {
+	background: #fff;
+	border-radius: 16rpx;
+	margin-bottom: 16rpx;
+	padding: 20rpx;
+}
 
-			.badge {
-				position: absolute;
-				top: -8rpx;
-				left: 100rpx;
-				font-size: 24rpx;
-				padding: 0rpx 10rpx;
-				border-radius: 38rpx;
-				background-color: #ff4949;
-				color: #fff;
-				font-style: normal;
-			}
-		}
+.qfn-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 16rpx;
+}
 
-		.nav-item:nth-child(1) span {
-			background-color: $uni-color-success;
-		}
+.qfn-title {
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #333;
+}
 
-		.nav-item:nth-child(2) span {
-			background-color: $uni-color-warning;
-		}
+.qfn-actions {
+	display: flex;
+	align-items: center;
+}
 
-		.nav-item:nth-child(3) span {
-			background-color: $uni-color-primary;
-		}
+.qfn-done {
+	font-size: 26rpx;
+	color: #ff4949;
+	padding: 4rpx 16rpx;
+}
 
-		.nav-item:nth-child(4) span {
-			background-color: $uni-color-error;
-		}
+.qfn-scroll {
+	white-space: nowrap;
+	width: 100%;
+}
 
-		.nav-item:nth-child(5) span {
-			background-color: $uni-color-success;
-		}
-	}
+.qfn-item {
+	display: inline-flex;
+	flex-direction: column;
+	align-items: center;
+	width: 140rpx;
+	margin-right: 20rpx;
+	position: relative;
+}
+
+.qfn-item:last-child {
+	margin-right: 0;
+}
+
+.qfn-icon {
+	width: 88rpx;
+	height: 88rpx;
+	border-radius: 50%;
+	background: #f5f7fa;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	margin-bottom: 10rpx;
+	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, .06);
+}
+
+.qfn-icon span {
+	font-size: 44rpx;
+	color: #157A2C;
+}
+
+.qfn-del {
+	border: 2rpx dashed #ff4949;
+}
+
+.qfn-name {
+	font-size: 26rpx;
+	color: #333;
+	text-align: center;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	width: 100%;
+}
+
+.qfn-badge {
+	position: absolute;
+	top: -4rpx;
+	right: -4rpx;
+}
+
+.qfn-empty {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding: 30rpx 0;
+}
+
+.qfn-empty-tip {
+	font-size: 24rpx;
+	color: #ccc;
+	margin-top: 10rpx;
+}
+
+/* 弹窗 */
+.add-popup {
+	height: 60vh;
+	padding: 30rpx;
+	display: flex;
+	flex-direction: column;
+}
+
+.add-popup-head {
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #333;
+	text-align: center;
+	margin-bottom: 20rpx;
+}
+
+.add-popup-body {
+	flex: 1;
+	min-height: 0;
+}
+
+.add-popup-row {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 24rpx 0;
+	border-bottom: 1rpx solid #f0f0f0;
+}
+
+.add-popup-left {
+	display: flex;
+	align-items: center;
+	flex: 1;
+}
+
+.add-popup-name {
+	font-size: 28rpx;
+	color: #333;
+	margin-left: 20rpx;
+}
+
+.add-popup-none {
+	text-align: center;
+	padding: 60rpx 0;
+	color: #999;
+	font-size: 28rpx;
+}
+
+/* 原有导航 */
+.nav {
+	display: flex;
+	width: 100%;
+	padding: 32rpx 0;
+	margin-bottom: 24rpx;
+	align-items: center;
+	justify-content: space-around;
+	background-color: #fff;
+	font-size: $uni-font-size-sm;
+	color: $uni-text-color;
+	border-radius: 16rpx;
+}
+
+.nav-item {
+	position: relative;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	margin-top: 5rpx;
+	flex: 1;
+}
+
+.nav-item span {
+	padding: 18rpx;
+	color: #fff;
+	margin-bottom: 14rpx;
+	border-radius: 50%;
+}
+
+.badge {
+	position: absolute;
+	top: -8rpx;
+	left: 100rpx;
+	font-size: 24rpx;
+	padding: 0 10rpx;
+	border-radius: 38rpx;
+	background: #ff4949;
+	color: #fff;
+	font-style: normal;
+}
+
+.nav-item:nth-child(1) span {
+	background-color: $uni-color-success;
+}
+
+.nav-item:nth-child(2) span {
+	background-color: $uni-color-warning;
+}
+
+.nav-item:nth-child(3) span {
+	background-color: $uni-color-primary;
+}
+
+.nav-item:nth-child(4) span {
+	background-color: $uni-color-error;
+}
+
+.nav-item:nth-child(5) span {
+	background-color: $uni-color-success;
+}
 </style>

+ 7 - 7
pages/home/pages/manage/manage.vue

@@ -305,7 +305,7 @@ export default {
         year: this.currentYear,
         month: this.currentMonth
       })
-      console.log('getPurchaseOrderSummary', res)
+      // console.log('getPurchaseOrderSummary', res)
       // res: { yearlySummary: { quantity, amount }, monthlySummary: { quantity, amount } }
       this.purchase.yearCount = res?.yearlySummary?.quantity || 0
       this.purchase.yearAmount = yuanToWan(res?.yearlySummary?.amount)
@@ -316,7 +316,7 @@ export default {
       let res = await getPurchaseOrderTrendSummary({
         year: this.currentYear
       })
-      console.log('getPurchaseOrderTrendSummary', res)
+      // console.log('getPurchaseOrderTrendSummary', res)
       // res: { year, trends: [{ month, quantity, amount, lastQuantity, lastAmount }] }
 
       const trends = res?.trends || []
@@ -355,14 +355,14 @@ export default {
     },
     async getMonthOutput() {
       let res = await getMonthOutput()
-      console.log('getMonthOutput', res)
+      // console.log('getMonthOutput', res)
       // res: { dateStr, orderCount, formedNum }
       this.production.monthWorkOrder = res?.orderCount || 0
       this.production.monthTotal = res?.formedNum || 0
     },
     async getYearOutput() {
       let res = await getYearOutput()
-      console.log('getYearOutput', res)
+      // console.log('getYearOutput', res)
       // res: { dateStr, orderCount, formedNum }
       this.production.yearWorkOrder = res?.orderCount || 0
       this.production.yearTotal = res?.formedNum || 0
@@ -474,7 +474,7 @@ export default {
       // bizType 1=生产 2=采购 3=销售出库  11=领用
       // type 1=入库 2=出库
       let res = await getPadStockStreamStatistics()
-      console.log('getPadStockStreamStatistics', res)
+      // console.log('getPadStockStreamStatistics', res)
 
       // 重置默认值
       const wh = { yearPurchaseIn: 0, monthPurchaseIn: 0, yearMaterialOut: 0, monthMaterialOut: 0, yearProduceIn: 0, monthProduceIn: 0, yearDeliveryOut: 0, monthDeliveryOut: 0 }
@@ -509,7 +509,7 @@ export default {
       let res = await getSaleOrderTrendSummary({
         year: this.currentYear
       })
-      console.log('getSaleOrderTrendSummary', res)
+      // console.log('getSaleOrderTrendSummary', res)
 
       const trends = res?.trends || []
       // 初始化12个月的数据,默认0
@@ -555,7 +555,7 @@ export default {
       this.marketing.yearOrderAmount = yuanToWan(res?.yearlySummary?.amount)
       this.marketing.monthOrderCount = res?.monthlySummary?.quantity || 0
       this.marketing.monthOrderAmount = yuanToWan(res?.monthlySummary?.amount)
-      console.log('getSaleOrderSummary', res)
+      // console.log('getSaleOrderSummary', res)
     },
     toSaleOrderDetail(type) {
       uni.navigateTo({