Browse Source

代码归档

shuchang 1 week ago
parent
commit
df21cde7fa
100 changed files with 4150 additions and 560 deletions
  1. 29 5
      common/config/api.js
  2. 15 3
      common/config/application-api.js
  3. 8 2
      common/status/index.js
  4. 2 1
      manifest.json
  5. 12 0
      pages.json
  6. 1 1
      pages/subpack/components/apply/apply-item.vue
  7. 7 7
      pages/subpack/login-pwd/login-pwd.vue
  8. 68 40
      pages/subpack/pages/application/componenets/page1.vue
  9. 152 6
      pages/subpack/pages/application/componenets/page2.vue
  10. 22 6
      pages/subpack/pages/application/componenets/page4.vue
  11. 5 4
      pages/subpack/pages/application/info.vue
  12. 104 35
      pages/subpack/pages/application/list.vue
  13. 225 27
      pages/subpack/pages/apply/apply.vue
  14. 97 0
      pages/subpack/pages/chang-pwd/chang-pwd.vue
  15. 11 3
      pages/subpack/pages/choose-people/choose-people.vue
  16. 4 4
      pages/subpack/pages/login/login.vue
  17. 70 8
      pages/subpack/pages/myInfo/info.vue
  18. 18 4
      pages/subpack/pages/nurse/clockIn.vue
  19. 284 0
      pages/subpack/pages/nurse/end-care.vue
  20. BIN
      pages/subpack/static/images/logo-header-bg.png
  21. 35 4
      pages/tabbar/application/application.vue
  22. 8 9
      pages/tabbar/mine/mine.vue
  23. 290 0
      uni_modules/uview-plus/changelog.md
  24. 7 2
      uni_modules/uview-plus/components/u--form/u--form.vue
  25. 109 0
      uni_modules/uview-plus/components/u-action-sheet-data/u-action-sheet-data.vue
  26. 18 3
      uni_modules/uview-plus/components/u-album/u-album.vue
  27. 4 1
      uni_modules/uview-plus/components/u-calendar/calendar.js
  28. 14 7
      uni_modules/uview-plus/components/u-calendar/header.vue
  29. 25 3
      uni_modules/uview-plus/components/u-calendar/month.vue
  30. 14 1
      uni_modules/uview-plus/components/u-calendar/props.js
  31. 4 0
      uni_modules/uview-plus/components/u-calendar/u-calendar.vue
  32. 40 0
      uni_modules/uview-plus/components/u-card/card.js
  33. 26 32
      uni_modules/uview-plus/components/u-card/props.js
  34. 44 15
      uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue
  35. 3 2
      uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue
  36. 3 1
      uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue
  37. 6 1
      uni_modules/uview-plus/components/u-collapse-item/collapseItem.js
  38. 31 0
      uni_modules/uview-plus/components/u-collapse-item/props.js
  39. 2 0
      uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue
  40. 6 1
      uni_modules/uview-plus/components/u-datetime-picker/datetimePicker.js
  41. 17 3
      uni_modules/uview-plus/components/u-datetime-picker/props.js
  42. 18 7
      uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue
  43. 10 8
      uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue
  44. 3 1
      uni_modules/uview-plus/components/u-float-button/u-float-button.vue
  45. 0 2
      uni_modules/uview-plus/components/u-form-item/u-form-item.vue
  46. 13 7
      uni_modules/uview-plus/components/u-form/u-form.vue
  47. 61 19
      uni_modules/uview-plus/components/u-icon/u-icon.vue
  48. 15 13
      uni_modules/uview-plus/components/u-image/u-image.vue
  49. 1 1
      uni_modules/uview-plus/components/u-index-list/u-index-list.vue
  50. 4 0
      uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue
  51. 2 1
      uni_modules/uview-plus/components/u-line-progress/lineProgress.js
  52. 5 0
      uni_modules/uview-plus/components/u-line-progress/props.js
  53. 5 1
      uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue
  54. 5 1
      uni_modules/uview-plus/components/u-modal/modal.js
  55. 20 0
      uni_modules/uview-plus/components/u-modal/props.js
  56. 29 6
      uni_modules/uview-plus/components/u-modal/u-modal.vue
  57. 0 1
      uni_modules/uview-plus/components/u-navbar-mini/u-navbar-mini.vue
  58. 5 0
      uni_modules/uview-plus/components/u-navbar/props.js
  59. 10 4
      uni_modules/uview-plus/components/u-navbar/u-navbar.vue
  60. 5 1
      uni_modules/uview-plus/components/u-number-box/numberBox.js
  61. 21 1
      uni_modules/uview-plus/components/u-number-box/props.js
  62. 53 39
      uni_modules/uview-plus/components/u-number-box/u-number-box.vue
  63. 283 0
      uni_modules/uview-plus/components/u-pagination/u-pagination.vue
  64. 42 28
      uni_modules/uview-plus/components/u-parse/node/node.vue
  65. 5 5
      uni_modules/uview-plus/components/u-parse/parse.js
  66. 78 15
      uni_modules/uview-plus/components/u-parse/parser.js
  67. 7 2
      uni_modules/uview-plus/components/u-parse/u-parse.vue
  68. 128 0
      uni_modules/uview-plus/components/u-picker-data/u-picker-data.vue
  69. 6 1
      uni_modules/uview-plus/components/u-picker/picker.js
  70. 20 1
      uni_modules/uview-plus/components/u-picker/props.js
  71. 153 36
      uni_modules/uview-plus/components/u-picker/u-picker.vue
  72. 24 1
      uni_modules/uview-plus/components/u-popup/u-popup.vue
  73. 133 63
      uni_modules/uview-plus/components/u-qrcode/qrcode.js
  74. 23 9
      uni_modules/uview-plus/components/u-qrcode/u-qrcode.vue
  75. 4 0
      uni_modules/uview-plus/components/u-search/props.js
  76. 1 0
      uni_modules/uview-plus/components/u-search/search.js
  77. 11 0
      uni_modules/uview-plus/components/u-search/u-search.vue
  78. 218 0
      uni_modules/uview-plus/components/u-select/u-select.vue
  79. 14 4
      uni_modules/uview-plus/components/u-slider/u-slider.vue
  80. 6 1
      uni_modules/uview-plus/components/u-status-bar/props.js
  81. 2 1
      uni_modules/uview-plus/components/u-status-bar/statusBar.js
  82. 9 2
      uni_modules/uview-plus/components/u-status-bar/u-status-bar.vue
  83. 17 7
      uni_modules/uview-plus/components/u-steps-item/u-steps-item.vue
  84. 6 1
      uni_modules/uview-plus/components/u-steps/u-steps.vue
  85. 7 2
      uni_modules/uview-plus/components/u-sticky/u-sticky.vue
  86. 3 2
      uni_modules/uview-plus/components/u-subsection/u-subsection.vue
  87. 1 1
      uni_modules/uview-plus/components/u-swiper/u-swiper.vue
  88. 77 3
      uni_modules/uview-plus/components/u-table/u-table.vue
  89. 515 0
      uni_modules/uview-plus/components/u-table2/u-table2.vue
  90. 6 1
      uni_modules/uview-plus/components/u-tabs/props.js
  91. 2 1
      uni_modules/uview-plus/components/u-tabs/tabs.js
  92. 18 0
      uni_modules/uview-plus/components/u-tabs/u-tabs.vue
  93. 29 3
      uni_modules/uview-plus/components/u-tag/props.js
  94. 6 1
      uni_modules/uview-plus/components/u-tag/tag.js
  95. 43 8
      uni_modules/uview-plus/components/u-tag/u-tag.vue
  96. 42 4
      uni_modules/uview-plus/components/u-td/u-td.vue
  97. 11 2
      uni_modules/uview-plus/components/u-textarea/u-textarea.vue
  98. 7 0
      uni_modules/uview-plus/components/u-th/props.js
  99. 67 0
      uni_modules/uview-plus/components/u-th/u-th.vue
  100. 1 1
      uni_modules/uview-plus/components/u-toast/toast.js

+ 29 - 5
common/config/api.js

@@ -1,6 +1,15 @@
-const {
-	http
-} = uni.$u
+// console.log(uni.$u)
+
+// const {
+// 	http
+// } = uni.$u
+
+import {
+	http,
+	toast
+} from '@/uni_modules/uview-plus'
+
+
 
 // 获取用户信息
 export const getUserInfoUrl = (data) => http.get('/prod-api/api/wechat/getInfo', data);
@@ -13,12 +22,17 @@ export const applyUrl = (params, config = {}) => http.post('/prod-api/care/appli
 // 修改
 export const applyModifyUrl = (params, config = {}) => http.put('/prod-api/care/applications', params, config);
 // 获取申请人列表
-export const getPersonListUrl = (data) => http.get('/prod-api/care/persons/list', data);
+export const getPersonListUrl = (data) => http.get('/prod-api/care/persons/applist', data);
 // 获取所在医院列表
 export const getHospitalListUrl = (data) => http.get('/prod-api/care/hospital/list', data);
 // 上传
 export const uploadAvatarUrl = (params, config = {}) => http.upload('/prod-api/system/user/profile/avatar', params,
 	config);
+// 上传
+export const uploadImgUrl = (data) => {
+	// console.log('config11', data);
+	return http.upload('/prod-api/common/upload', data);
+}
 // 获取未读消息数
 export const getMsgCountUrl = (data) => http.get('/prod-api/system/notice/myNoticeList', data);
 // 修改消息
@@ -27,7 +41,17 @@ export const modifyMsgUrl = (params, config = {}) => http.post('/prod-api/system
 // 账号密码登录
 export const loginByPwdUrl = (params, config = {}) => http.post('/prod-api/api/wechat/wxlogin', params, config);
 
-
+// 获取街道列表
+export const getStreetListUrl = (data) => http.get('/prod-api/system/dept/list', data);
+// 修改个人信息
+export const updateUserInfoUrl = (params, config = {}) => http.put('/prod-api/system/user/profile',
+	params, config);
+// 修改密码
+export const updateUserPwdUrl = (params, config = {}) => http.put('/prod-api/system/user/profile/updatePwd',
+	params, config);
+// 出院审核
+export const leaveCheckUrl = (params, config = {}) => http.post('/prod-api/care/applications/completeCheck',
+	params, config);
 
 // 注册
 export const registerUrl = (params, config = {}) => http.post('/xc-order/api//order/user/register', params, config);

+ 15 - 3
common/config/application-api.js

@@ -1,6 +1,11 @@
-const {
-	http
-} = uni.$u
+// const {
+// 	http
+// } = uni.$u
+
+import {
+	http,
+	toast
+} from '@/uni_modules/uview-plus'
 
 //获取用户协议列表
 export const getProtocolList = (data) => http.get('/prod-api/care/protocol/list', data);
@@ -15,6 +20,8 @@ export const editNurses = (data) => http.get(`/prod-api/care/nurses/${data.id??0
 
 //护理申请列表
 export const getCareList = (data) => http.get(`/prod-api/care/applications/list`, data);
+//护理申请列表
+export const getStreetListUrl = (data) => http.get(`/prod-api/care/applications/oneCheckList`, data);
 //护理详情
 export const getCareInfo = (data) => http.get(`/prod-api/care/applications/${data.id??0}`, data);
 
@@ -31,6 +38,9 @@ export const delCareApp = (params, config = {}) => http.delete(`/prod-api/care/a
 export const postCareAssign = (parmas, config = {}) => http.post('/prod-api/care/applications/assign', parmas, config)
 //护理审核
 export const postCareCheck = (parmas, config = {}) => http.post('/prod-api/care/applications/check', parmas, config)
+// 街道初审
+export const postStreetCheck = (parmas, config = {}) => http.post('/prod-api/care/applications/onecheck', parmas,
+	config)
 //完成护理申请
 export const postCareComplete = (parmas, config = {}) => http.post('/prod-api/care/applications/complete', parmas,
 	config)
@@ -56,6 +66,8 @@ export const getImgNumber = (data) => http.get(`/prod-api/system/workConfig/type
 export const addCareClocks = (params, config = {}) => http.post(`/prod-api/care/clocks`, params, config);
 //添加死亡记录
 export const addDieRecord = (params, config = {}) => http.post(`/prod-api/care/clocks/die`, params, config);
+//添加死亡记录
+export const endCareRequest = (params, config = {}) => http.post(`/prod-api/care/applications/complete`, params, config);
 //修改护理打卡记录
 export const updateCareClocks = (params, config = {}) => http.put(`/prod-api/care/clocks`, params, config);
 //删除护理打卡记录

+ 8 - 2
common/status/index.js

@@ -5,6 +5,12 @@
 // 5(护理指派人员 - 指派) - 护理公司
 // 6(护理打卡 - 打卡) - 护理人员
 const STATUS_CONFIGE = [{
+		dictCode: 301,
+		dictLabel: "待街道审核",
+		dictSort: 0,
+		dictType: "care_apply_status",
+		dictValue: "one_check"
+	}, {
 		dictCode: 119,
 		dictLabel: "待指派人员",
 		dictSort: 0,
@@ -13,7 +19,7 @@ const STATUS_CONFIGE = [{
 	},
 	{
 		dictCode: 110,
-		dictLabel: "待审核",
+		dictLabel: "待区县审核",
 		dictSort: 1,
 		dictType: "care_apply_status",
 		dictValue: "wait_check"
@@ -124,4 +130,4 @@ export function getDict(query = {}) {
 		}
 	})
 	return distRes;
-}
+}

+ 2 - 1
manifest.json

@@ -62,7 +62,8 @@
         "lazyCodeLoading" : "requiredComponents",
         "optimization" : {
             "subPackages" : true
-        }
+        },
+        "permission" : {}
     },
     "mp-alipay" : {
         "usingComponents" : true

+ 12 - 0
pages.json

@@ -61,6 +61,12 @@
 				}
 			},
 			{
+				"path": "pages/chang-pwd/chang-pwd",
+				"style": {
+					"navigationStyle": "custom",
+					"navigationBarTitleText": "修改密码"
+				}
+			}, {
 				"path": "pages/application/list",
 				"style": {
 					"navigationStyle": "custom",
@@ -131,6 +137,12 @@
 				}
 			},
 			{
+				"path": "pages/nurse/end-care",
+				"style": {
+					"navigationStyle": "custom",
+					"navigationBarTitleText": "护理打卡-结束"
+				}
+			}, {
 				"path": "login-pwd/login-pwd",
 				"style": {
 					"navigationStyle": "custom",

+ 1 - 1
pages/subpack/components/apply/apply-item.vue

@@ -65,7 +65,7 @@
 		emit('tapItem', props.item)
 	}
 	const statusText = computed(() => {
-		// console.log('???', props.item);
+		if (props.item.dealStatus === 'wait_complete') return '待审批';
 		if (props.item.dealStatus === 'complete') return '已完成';
 		let dict = getDict({
 			dictValue: props.item.status,

+ 7 - 7
pages/subpack/login-pwd/login-pwd.vue

@@ -11,12 +11,12 @@
 				<image src="/pages/subpack/static/images/login-phone-icon.png" style="width: 36rpx;height: 36rpx;"
 					mode="aspectFill">
 				</image>
-				<text class="font-md ml-08" style="color: #002F61;">用户名</text>
+				<text class="font-md ml-08" style="color: #002F61;">手机号</text>
 			</view>
 			<view class="flex-column justify-center mt-08 px-4"
 				style="height: 88rpx;border-radius: 50rpx;background: rgba(215, 231, 255, 0.3);">
-				<u-input placeholder="请输入用户名" v-model.trim="phone" border="none" :maxlength="11"
-					:fontSize="16" clearable></u-input>
+				<u-input placeholder="请输入手机号" v-model.trim="phone" border="none" :maxlength="11" :fontSize="16"
+					clearable></u-input>
 			</view>
 
 			<view class="flex-row align-center" style="margin-left: 18rpx;margin-top: 36rpx;">
@@ -76,11 +76,11 @@
 		methods: {
 			checkForm() {
 				if (!this.phone) {
-					return uni.$u.toast('请输入用户名')
+					return uni.$u.toast('请输入手机号')
+				}
+				if (!uni.$u.test.mobile(this.phone)) {
+					return uni.$u.toast('请输入正确的手机号码')
 				}
-				// if (!uni.$u.test.mobile(this.phone)) {
-				// 	return uni.$u.toast('请输入正确的手机号码')
-				// }
 				// if (this.password.length < 8) {
 				// 	return uni.$u.toast('请输入8-16位的密码')
 				// }

+ 68 - 40
pages/subpack/pages/application/componenets/page1.vue

@@ -2,7 +2,8 @@
 	<view class="page1-warp">
 		<up-form labelPosition="top" :model="formData" ref="formRef">
 			<!-- 'wait_assign_company,wait_assign_nurse'.includes(item.status) -->
-			<up-form-item label="指派任务" labelWidth="auto" v-if="item.status && item.status.includes('wait_assign')" required>
+			<up-form-item label="指派任务" labelWidth="auto" v-if="item.status && item.status.includes('wait_assign')"
+				required>
 				<view class="flex-row justify-between align-center" style="width: 100%;">
 					<view v-if="item.status === 'wait_assign_company'">
 						<view class="">护理公司</view>
@@ -42,14 +43,12 @@
 		</up-form>
 		<view class="btn-box">
 			<view class="flex-row ">
-				<up-button class="up-button" v-if="isShowBack" type="error" 
-				style="width: 250rpx;" @tap="checkTap(2)">‌退回</up-button>
+				<up-button class="up-button" v-if="isShowBack" type="error" style="width: 250rpx;"
+					@tap="checkTap(2)">‌退回</up-button>
 				<view v-if="isShowBack" class="btn-place"></view>
-				<up-button v-if='item.status && item.status.includes("wait_assign")' 
-				class="up-button" type="primary" @tap="assignTap"
-				>指派</up-button>
-				<up-button v-else class="up-button" type="primary"
-				 @tap="checkTap(1)">通过</up-button>
+				<up-button v-if='item.status && item.status.includes("wait_assign")' class="up-button" type="primary"
+					@tap="assignTap">指派</up-button>
+				<up-button v-else class="up-button" type="primary" @tap="checkTap(1)">通过</up-button>
 			</view>
 		</view>
 	</view>
@@ -61,7 +60,8 @@
 		reactive,
 		computed,
 		onMounted,
-		onUnmounted
+		onUnmounted,
+		getCurrentInstance
 	} from 'vue';
 	import {
 		useStore
@@ -69,10 +69,15 @@
 	import {
 		postCareAssign,
 		postCareCheck,
+		postStreetCheck,
 		postCareComplete
 	} from '@/common/config/application-api.js'
-	
-	
+
+	const {
+		proxy
+	} = getCurrentInstance()
+
+
 	const store = useStore();
 	const props = defineProps({
 		item: {
@@ -94,11 +99,11 @@
 		remark: ''
 	})
 	const chooseInfo = ref({}); //选择的数据
-	
+
 	//名字取值
 	const nameText = computed(() => {
 		return (name) => {
-			name = name.replaceAll('南充','').replaceAll('服务','').replaceAll('养老', '').replaceAll('有限公司', '');
+			name = name.replaceAll('南充', '').replaceAll('服务', '').replaceAll('养老', '').replaceAll('有限公司', '');
 			let index = name.length - 2;
 			index = index >= 0 ? index : 0;
 			return name.substring(index);
@@ -113,30 +118,32 @@
 		if (chooseInfo.value.name) {
 			params.dufaultIds = chooseInfo.value.id + "";
 		}
-		
-		if(props.item.status === 'wait_assign_company') {
+
+		if (props.item.status === 'wait_assign_company') {
 			params.pageType = 'company';
-		}else if(props.item.status === 'wait_assign_nurse') {
+		} else if (props.item.status === 'wait_assign_nurse') {
 			params.pageType = 'user';
 		}
 		uni.$u.route('/pages/subpack/pages/application/choose', params)
 	}
-	
+
 	//是否显示退回按钮
-	function isShowBack(){
+	function isShowBack() {
 		let user = store.state.vuex_user;
 		return user.roles.includes('area') && props.item.status == 'wait_check';
 	}
-	
+
 	//点击处理指派
-	function assignTap(){
-		if(!chooseInfo.value.id) {
+	function assignTap() {
+		if (!chooseInfo.value.id) {
 			uni.$u.toast('请选择指派的公司');
 			return false;
 		}
-		let checkParams = {...props.item};
-		
-		if(props.item.status === 'wait_assign_nurse') {
+		let checkParams = {
+			...props.item
+		};
+
+		if (props.item.status === 'wait_assign_nurse') {
 			checkParams.status = 'assigned';
 			checkParams.nurseId = chooseInfo.value.id;
 		} else {
@@ -144,10 +151,10 @@
 			checkParams.companyId = chooseInfo.value.id;
 		}
 		checkParams.assignNurseRemark = formData.remark;
-		postCareAssign(checkParams).then(res=>{
+		postCareAssign(checkParams).then(res => {
 			uni.$u.toast('指派成功');
 			uni.$emit('renderApplyList');
-			setTimeout(()=>{
+			setTimeout(() => {
 				uni.$u.route({
 					type: 'back'
 				})
@@ -155,26 +162,47 @@
 		})
 	}
 	//点击处理审核 1 审核  2 退回
-	function checkTap(type = 2){
-		if(!formData.remark){
+	function checkTap(type = 2) {
+		if (!formData.remark) {
 			uni.$u.toast('审核理由不能为空');
 			return false;
 		}
-		let checkParams = {...props.item};
-		checkParams.status = 'wait_assign_company';
-		if(type == 2) {
+		let checkParams = {
+			...props.item
+		};
+		if (proxy.vuex_user.roles.includes('village')) {
+			checkParams.status = 'wait_check';
+		} else {
+			checkParams.status = 'wait_assign_company';
+		}
+
+		if (type == 2) {
 			checkParams.status = 'disagree';
 		}
 		checkParams.auditReason = formData.remark;
-		postCareCheck(checkParams).then(res=>{
-			uni.$u.toast('审核成功');
-			uni.$emit('renderApplyList');
-			setTimeout(()=>{
-				uni.$u.route({
-					type: 'back'
-				})
-			}, 1000)
-		})
+
+		if (proxy.vuex_user.roles.includes('village')) {
+			postStreetCheck(checkParams).then(res => {
+				uni.$u.toast('初审成功');
+				uni.$emit('renderApplyList');
+				setTimeout(() => {
+					uni.$u.route({
+						type: 'back'
+					})
+				}, 1000)
+			})
+		} else {
+			postCareCheck(checkParams).then(res => {
+				uni.$u.toast('审核成功');
+				uni.$emit('renderApplyList');
+				setTimeout(() => {
+					uni.$u.route({
+						type: 'back'
+					})
+				}, 1000)
+			})
+		}
+
 	}
 
 	onMounted(() => {

+ 152 - 6
pages/subpack/pages/application/componenets/page2.vue

@@ -2,11 +2,36 @@
 	<view>
 		<view class="info-dot">护理申请</view>
 		<view class="info-list">
-			<view class="info-item" v-for="(item, index) in itemHandle" :key="index">
+			<!-- <view class="info-item" v-for="(item, index) in itemHandle" :key="index">
 				<text class="info-item-label">{{item.name}}</text>
 				<text class="info-item-content text-overflow">{{item.value??'-'}}</text>
-			</view>
+			</view> -->
+			<up-cell-group>
+				<up-cell v-for="(item, index) in itemHandle" :key="index" :title="item.name"
+					:value="item.value??'-'"></up-cell>
+				<up-cell title="申请单">
+					<template #right-icon>
+						<image :src="getImgPath(item.imgPath)" style="width: 80rpx;height:80rpx" mode="aspectFit"
+							@click="previewImg(item.imgPath)"></image>
+					</template>
+				</up-cell>
+				<up-cell v-if="item.dealStatus === 'wait_complete' || item.dealStatus === 'complete'" title="出院时间"
+					:value="item.leaveTime"></up-cell>
+				<up-cell v-if="item.dealStatus === 'wait_complete' || item.dealStatus === 'complete'" title="出院证明">
+					<template #right-icon>
+						<image :src="getImgPath(item.leaveImg)" style="width: 80rpx;height:80rpx" mode="aspectFit"
+							@click="previewImg(item.leaveImg)"></image>
+					</template>
+				</up-cell>
+			</up-cell-group>
+
+			<!-- <view class="info-item">
+				<text class="info-item-label">申请单</text>
+				<image :src="getImgPath(item.imgPath)" style="width: 100rpx;" mode="aspectFit"
+					@click="previewImg(item.imgPath)"></image>
+			</view> -->
 		</view>
+
 		<view v-if="item.dieTime" class="info-dot">死亡信息</view>
 		<view v-if="item.dieTime" class="info-list">
 			<view class="info-item">
@@ -45,7 +70,27 @@
 				</view>
 			</view>
 		</view>
-		<view v-if="'no_check,disagree'.includes(item.status)">
+		<template v-if="showLeaveCheck">
+			<view class="info-dot">出院审核</view>
+			<view class="info-list">
+				<view class="info-item flex-column" style="align-items: flex-start;">
+					<view class="info-item-label">是否通过</view>
+					<view class="info-item-content info-item-content2">
+						<u-radio-group v-model="isPass" placement="row">
+							<u-radio name="1" label="同意"></u-radio>
+							<u-radio name="0" label="不同意"></u-radio>
+						</u-radio-group>
+					</view>
+				</view>
+				<view class="info-item flex-column" style="align-items: flex-start;">
+					<view class="info-item-label">审核说明</view>
+					<view class="info-item-content info-item-content2">
+						<up-textarea v-model.trim="leaveCheckReason" placeholder="请输入审核说明" count />
+					</view>
+				</view>
+			</view>
+		</template>
+		<view v-if="computeShowEdit">
 			<view class="btn-box-place"></view>
 			<view class="btn-box flex-row" :class="{'btn-box-disagree': item.status == 'disagree'}">
 				<view class="u-flex flex-column align-center justify-center" style="width: 120rpx;" @tap="editTap">
@@ -61,6 +106,10 @@
 				<up-button class="up-button" type="primary" @tap="applyTap">提交审核</up-button>
 			</view>
 		</view>
+		<view v-if="showLeaveCheckBtn" class="btn-box flex-row"
+			:class="{'btn-box-disagree': item.status == 'disagree'}">
+			<up-button class="up-button" type="primary" @tap="submitLeaveCheck">提交</up-button>
+		</view>
 	</view>
 </template>
 
@@ -81,6 +130,9 @@
 		postCareStart,
 		rateRequest
 	} from '@/common/config/application-api.js'
+	import {
+		leaveCheckUrl
+	} from '@/common/config/api.js'
 
 	const {
 		proxy
@@ -95,6 +147,9 @@
 		}
 	})
 
+	const isPass = ref('1');
+	const leaveCheckReason = ref('');
+
 	const rateCount = ref(0)
 
 	const objKeyValue = reactive([{
@@ -102,7 +157,15 @@
 			key: 'personName'
 		},
 		{
-			name: '首选性别',
+			name: '性别',
+			key: 'gender'
+		},
+		{
+			name: '身份证号',
+			key: 'idNumber'
+		},
+		{
+			name: '护工性别',
 			key: 'nurseGender'
 		},
 		{
@@ -114,8 +177,8 @@
 			key: 'address'
 		},
 		{
-			name: '预计天数',
-			key: 'careDays'
+			name: '是否残疾',
+			key: 'disability'
 		},
 		{
 			name: '申请日期',
@@ -142,6 +205,20 @@
 		}
 	})
 
+	const showLeaveCheckBtn = computed(() => {
+		console.log(props.item.dealStatus,'?????');
+		console.log('wait_complete'.includes(props.item.dealStatus),'?????');
+		return props.item.dealStatus.includes('wait_complete') && proxy.vuex_user.roles.includes('company')
+	})
+
+	const showLeaveCheck = computed(() => {
+		return props.item.dealStatus.includes('wait_complete') && proxy.vuex_user.roles.includes('company')
+	})
+
+	const computeShowEdit = computed(() => {
+		return 'no_check,disagree'.includes(props.item.status) && proxy.vuex_user.roles.includes('company')
+	})
+
 	//中间层 - item和objKeyValue
 	const itemHandle = computed(() => {
 		let itemArray = uni.$u.deepClone(objKeyValue);
@@ -154,12 +231,54 @@
 						dictType: 'sys_user_sex'
 					}).dictLabel ?? '-';
 				}
+				if (keyObj.key === 'disability') {
+					if (value === 'Y') {
+						value = '是'
+					} else {
+						value = '否'
+					}
+				}
+				if (keyObj.key === 'gender') {
+					if (value === '0') {
+						value = '男'
+					} else {
+						value = '女'
+					}
+				}
 				keyObj.value = value;
 			}
 		})
 		return itemArray;
 	})
 
+	const submitLeaveCheck = () => {
+		uni.showModal({
+			title: '出院审核',
+			content: '确定提交审核吗?',
+			showCancel: true,
+			success: (e) => {
+				if (e.confirm) {
+					let params = {
+						applyId: props.item.applyId,
+						dealStatus: isPass.value === '1' ? 'complete' : 'in_progress'
+					};
+					if (isPass.value !== '1') {
+						params.auditReason = leaveCheckReason.value
+					}
+					// console.log('params',params);
+					leaveCheckUrl(params)
+						.then(res => {
+							uni.$emit('renderApplyList')
+							uni.navigateBack();
+						})
+						.catch(err => {
+							console.log(err);
+						})
+				}
+			}
+		})
+	}
+
 	const showRateBtn = computed(() => {
 		if (rateCount.value > 0 && !props.item.scoreAssess) {
 			return true
@@ -172,6 +291,33 @@
 		return proxy.vuex_user.roles.includes('village')
 	})
 
+	const previewImg = (filename) => {
+		if (filename) {
+			if (filename.indexOf('prod-api') !== -1) {
+				return uni.previewImage({
+					urls: [uni.$u.http.config.baseURL + filename]
+				})
+			}
+			return uni.previewImage({
+				urls: [uni.$u.http.config.baseURL + '/prod-api/' + filename]
+			})
+		}
+		return;
+
+
+	}
+
+	const getImgPath = (filename) => {
+		if (filename) {
+			if (filename.indexOf('prod-api') !== -1) {
+				return uni.$u.http.config.baseURL + filename;
+			}
+			return uni.$u.http.config.baseURL + 'prod-api' + filename;
+		}
+		return '';
+
+	}
+
 
 	// 提交评分
 	const submitRate = () => {

+ 22 - 6
pages/subpack/pages/application/componenets/page4.vue

@@ -39,12 +39,12 @@
 					</template>
 				</up-steps-item>
 			</up-steps>
-		
+
 			<view v-if="listData.length === 0">
-				<up-empty text="暂无打卡记录"/>
+				<up-empty text="暂无打卡记录" />
 			</view>
 		</view>
-		<view v-if="item.dealStatus!=='complete'" class="btn-box">
+		<view v-if="computeShowBtn" class="btn-box">
 			<view class="flex-row ">
 				<up-button class="up-button" type="error" style="width: 250rpx;" @tap="dieHandle">死亡</up-button>
 				<view class="btn-place"></view>
@@ -60,7 +60,9 @@
 	import {
 		ref,
 		onMounted,
-		onUnmounted
+		onUnmounted,
+		computed,
+		getCurrentInstance
 	} from 'vue';
 	import {
 		useStore
@@ -77,6 +79,10 @@
 	const listData = ref([]);
 	const listTotal = ref(0);
 
+	const {
+		proxy
+	} = getCurrentInstance();
+
 
 	const props = defineProps({
 		item: {
@@ -88,6 +94,7 @@
 	})
 
 	onMounted(() => {
+		console.log('onMounted Page4');
 		uni.$on('reloadLine', () => {
 			getList();
 		})
@@ -108,12 +115,17 @@
 		}
 	}
 
+	const computeShowBtn = computed(() => {
+		return proxy.vuex_user.roles.includes('nurse') && props.item.dealStatus !== 'complete' && props.item
+			.dealStatus !== 'wait_complete'
+	})
+
 
 	function getList() {
 		if (!props.item.applyId) return false;
 		let params = {
 			pageNum: 1,
-			pageSize: 50,
+			pageSize: 500,
 			applyId: props.item.applyId
 		}
 		getClockList({
@@ -149,6 +161,10 @@
 
 	//结束
 	function endHandle() {
+		uni.$u.route('/pages/subpack/pages/nurse/end-care', {
+			id: props.item.applyId
+		});
+		return;
 		console.log(props.item);
 		uni.showModal({
 			title: '结束护理',
@@ -157,7 +173,7 @@
 			success: (e) => {
 				if (e.confirm) {
 					let params = props.item;
-					params.dealStatus = 'complete';
+					params.dealStatus = 'wait_complete';
 					params.payStatus = 'dis_pay';
 					completeTask(params)
 						.then(res => {

+ 5 - 4
pages/subpack/pages/application/info.vue

@@ -49,6 +49,7 @@
 
 	function tabbarChange(key) {
 		console.log('key', key);
+		console.log('tabbarKey', tabbarKey.value);
 		if (key === tabbarKey.value) return;
 		tabbarKey.value = key;
 	}
@@ -78,14 +79,14 @@
 		//审批页面只能区民政、公司看到
 		if (pageName === 'page2') {
 			if (dealStatus == 'complete') judgeBool = false;
-			else if (roles.includes('village')) judgeBool = false;
+			else if (roles.includes('village') && status === "one_check") judgeBool = true;
 			else if (!roleAuthArray.includes(status)) judgeBool = false;
 			else if (roles.includes('nurse')) judgeBool = false;
 			//流程记录页面只有除了护理人员之外的人看到	
 		} else if (pageName === 'page3') {
 			if (roles.includes('nurse')) judgeBool = false;
 		} else if (pageName === 'page4') {
-			if (!roles.includes('nurse')) judgeBool = false;
+			judgeBool = roles.includes('nurse') || roles.includes('company')
 		}
 		return judgeBool && tabbarKey.value == pageName;
 	}
@@ -102,13 +103,13 @@
 		if (pageName === 'tabbar' && status == 'no_check') return false;
 		if (pageName === 'page2') {
 			if (dealStatus == 'complete') return false;
-			else if (roles.includes('village')) return false;
+			else if (roles.includes('village') && status === "one_check") return true;
 			else if (!roleAuthArray.includes(status)) return false;
 			else if (roles.includes('nurse')) return false;
 		} else if (pageName === 'page3') {
 			if (roles.includes('nurse')) return false;
 		} else if (pageName === 'page4') {
-			if (!roles.includes('nurse')) return false;
+			return roles.includes('nurse') || roles.includes('company');
 		}
 		return true;
 	}

+ 104 - 35
pages/subpack/pages/application/list.vue

@@ -34,7 +34,8 @@
 	import {
 		getCareList,
 		getAppClockList,
-		getAssignList
+		getAssignList,
+		getStreetListUrl
 	} from '@/common/config/application-api.js'
 	import {
 		onLoad,
@@ -94,21 +95,26 @@
 			}
 		}],
 		village: [{
-			name: '未送审',
+			name: '待处理',
 			query: {
-				status: 'no_check,disagree'
+				status: 'one_check'
 			}
 		}, {
 			name: '进行中',
 			query: {
-				status: 'wait_assign_nurse,wait_check,agree,wait_assign_company,assigned',
-				dealStatus: 'in_progress',
+				status: 'wait_check,wait_assign_company,wait_assign_nurse,agree,wait_assign_company,assigned',
+				dealStatus: 'no_start,in_progress',
 			}
 		}, {
 			name: '已完成',
 			query: {
 				dealStatus: 'complete'
 			}
+		}, {
+			name: '已拒绝',
+			query: {
+				status: 'disagree'
+			}
 		}],
 		area: [{
 			name: '待处理',
@@ -119,7 +125,7 @@
 			name: '进行中',
 			query: {
 				status: 'wait_assign_nurse,agree,wait_assign_company,assigned',
-				dealStatus: 'in_progress',
+				dealStatus: 'no_start,in_progress',
 			}
 		}, {
 			name: '已完成',
@@ -132,6 +138,23 @@
 				status: 'disagree'
 			}
 		}],
+		company0: [{
+			name: '未送审',
+			query: {
+				status: 'no_check,disagree'
+			}
+		}, {
+			name: '进行中',
+			query: {
+				status: 'one_check,wait_assign_nurse,wait_check,agree,wait_assign_company,assigned',
+				dealStatus: 'no_start,in_progress',
+			}
+		}, {
+			name: '已完成',
+			query: {
+				dealStatus: 'complete'
+			}
+		}],
 		company: [{
 			name: '待分配',
 			query: {
@@ -140,8 +163,13 @@
 		}, {
 			name: '进行中',
 			query: {
-				status: 'care_apply_status',
-				dealStatus: 'in_progress',
+				status: 'assigned,care_apply_status',
+				dealStatus: 'no_start,in_progress',
+			}
+		}, {
+			name: '出院审核',
+			query: {
+				dealStatus: 'wait_complete',
 			}
 		}, {
 			name: '已完成',
@@ -153,7 +181,13 @@
 			name: '护理任务',
 			query: {
 				status: 'assigned',
-				dealStatus: 'in_progress',
+				dealStatus: 'no_start,in_progress',
+			}
+		}, {
+			name: '出院审核',
+			query: {
+				status: 'assigned',
+				dealStatus: 'wait_complete',
 			}
 		}, {
 			name: '已完成',
@@ -163,19 +197,13 @@
 			}
 		}],
 	}
-	const tabList = ref([])
+	const tabList = ref([]);
 
 	let roles = user.roles ?? [];
-	roles.map(r => {
-		if (tabData.hasOwnProperty(r) && tabList.value.length === 0) tabList.value = tabData[r];
-	})
-	if (tabList.value.length === 0) tabList.value = tabData.common;
 
 
 	//根据tab计算有多少宽度
 	itemStyle.width = 750 / tabList.value.length < 150 ? '150rpx' : `${750 / tabList.value.length}rpx`;
-
-
 	const tabIndex = ref(0)
 
 	function handleTabs({
@@ -186,14 +214,22 @@
 		tabIndex.value = index;
 		proxy.$refs.paging.reload();
 	}
-	
+
+	const flag = ref('0')
+
 	const handleTitle = computed(() => {
-		if(roles.includes('area')) return '护理审核';
-		if(roles.includes('village')) return '护理申请';
-		if(roles.includes('company')) return '护理指派';
-		else return '护理打卡';
+		if (roles.includes('area')) return '护理审核';
+		if (roles.includes('village')) return '护理初审';
+		if (roles.includes('company')) {
+			if (flag.value === '0') {
+				return '我的申请'
+			} else {
+				return '护理指派'
+			}
+		}
+		return '护理打卡';
 	})
-	
+
 
 	const dataList = ref([])
 	const paging = ref(null)
@@ -210,16 +246,31 @@
 				...query
 			}
 		}
-		if(roles.includes('company')){
-			getListIsCompany(params);
-		}else if(roles.includes('nurse')) {
+		if (roles.includes('company')) {
+			if (flag.value === '0') {
+				getList(params);
+			} else {
+				getListIsCompany(params);
+			}
+		} else if (roles.includes('nurse')) {
 			getListIsNurse(params);
-		}else {
+		} else if (roles.includes('village') && tabIndex.value === 0) {
+			getStreetList(params);
+		} else {
 			getList(params);
 		}
-		
+
+	}
+
+	function getStreetList(params) {
+		getStreetListUrl({
+				params
+			})
+			.then(data => {
+				paging.value.complete(data);
+			})
 	}
-	
+
 	function getList(params) {
 		getCareList({
 			params
@@ -227,21 +278,21 @@
 			paging.value.complete(data);
 		})
 	}
-	
+
 	// -护理公司护理列表查询
 	function getListIsCompany(query) {
 		let params = {
 			...query,
-			pageNumAssign: query.pageNum??1,
-			pageSizeAssign: query.pageSize??10
+			pageNumAssign: query.pageNum ?? 1,
+			pageSizeAssign: query.pageSize ?? 10
 		}
 		getAssignList({
 			params
-		}).then(data=>{
+		}).then(data => {
 			paging.value.complete(data);
 		})
 	}
-	
+
 	// -护理人员护理列表查询
 	function getListIsNurse(query) {
 		let params = {
@@ -249,7 +300,7 @@
 		}
 		getAppClockList({
 			params
-		}).then(data=>{
+		}).then(data => {
 			paging.value.complete(data);
 		})
 	}
@@ -262,7 +313,25 @@
 			id: item.applyId
 		})
 	}
-	onLoad(() => {
+	onLoad((options) => {
+		console.log('options', options);
+		flag.value = options.flag;
+
+		roles.forEach(r => {
+			if (tabData.hasOwnProperty(r) && tabList.value.length === 0) {
+				if (flag.value === '0') {
+					tabList.value = tabData['company0']
+				} else {
+					tabList.value = tabData[r];
+				}
+			}
+		})
+		if (tabList.value.length === 0) tabList.value = tabData.common;
+
+
+		//根据tab计算有多少宽度
+		itemStyle.width = 750 / tabList.value.length < 150 ? '150rpx' : `${750 / tabList.value.length}rpx`;
+
 		uni.$on('renderApplyList', () => {
 			proxy.$refs.paging.reload();
 		})

+ 225 - 27
pages/subpack/pages/apply/apply.vue

@@ -13,9 +13,26 @@
 					<view class="flex-column py-4 px-24 mt-24"
 						style="background: linear-gradient( 180deg, #E8F4FF 0%, #FFFFFF 100%);border-radius: 16rpx;">
 						<view class="flex-row align-center justify-between">
-							<text class="font text-black2">姓名</text>
+							<text class="font text-black2">人员来源</text>
+							<text class="font text-black2" @click="chooseFrom">{{computeFromDesc}}</text>
+						</view>
+						<view class="flex-row align-center justify-between mt-4">
+							<text class="font text-black2">{{computeStreetCat}}</text>
+							<text class="font text-black2" style="max-width: 400rpx;"
+								@click="showPicker = true">{{chooseStreet.label}}</text>
+						</view>
+						<view class="flex-row align-center justify-between mt-4">
+							<text class="font text-black2">申请人</text>
 							<text class="font text-black2" @click="goChoosePeople">{{formData.name}}</text>
 						</view>
+						<view v-if="chooseFromIndex === 1" class="flex-row align-center justify-between mt-4">
+							<text class="font text-black2">所在街道</text>
+							<text class="font text-black2" style="max-width: 400rpx;">{{streetName || '未知'}}</text>
+						</view>
+						<view class="flex-row align-center justify-between mt-4">
+							<text class="font text-black2">是否残疾</text>
+							<text class="font text-black2">{{formData.disability || '请先选择申请人'}}</text>
+						</view>
 						<view class="flex-row align-center justify-between mt-4">
 							<text class="font text-black2">护理需求</text>
 							<view style="width: 440rpx;">
@@ -38,14 +55,11 @@
 									clearable :maxlength="50" inputAlign="right"></u-input>
 							</view>
 						</view>
-						<view class="flex-row align-center justify-between mt-4">
-							<text class="font text-black2">预计天数</text>
-							<view style="width: 440rpx;">
-								<u-input placeholder="请输入预计天数" border="none" type="number"
-									v-model.trim="formData.dayCount" clearable :maxlength="5"
-									inputAlign="right"></u-input>
-							</view>
-						</view>
+						<!-- <view class="flex-row align-center justify-between mt-4">
+							<text class="font text-black2">出院时间</text>
+							<text class="font text-black2"
+								@click="showTimePicker = true">{{formData.leaveTime || '请选择出院时间'}}</text>
+						</view> -->
 						<view class="flex-row align-center justify-between mt-4">
 							<text class="font text-black2">备注</text>
 							<view style="width: 440rpx;">
@@ -53,6 +67,11 @@
 									:maxlength="100" inputAlign="right"></u-input>
 							</view>
 						</view>
+						<view class="flex-row align-center justify-between mt-4">
+							<text class="font text-black2">护理申请书</text>
+							<up-upload :fileList="fileList" :maxSize="1024 * 1024 * 1024" @afterRead="afterRead"
+								@delete="deletePic" name="1" :maxCount="1" style="flex: 0;"></up-upload>
+						</view>
 					</view>
 				</view>
 
@@ -62,6 +81,15 @@
 				</view>
 			</view>
 		</scroll-view>
+
+		<up-picker v-model:show="showPicker" :defaultIndex="defaultIndex" closeOnClickOverlay :columns="streetList"
+			keyName="label" valueName="id" @confirm="handlePickerConfirm"></up-picker>
+
+		<!-- <view v-show="showTimePicker">
+			<up-datetime-picker v-model:show="showTimePicker" mode="datetime" title="出院时间" :minDate="minDate"
+				@cancel="showTimePicker = false" @confirm="handleTimeConfirm"></up-datetime-picker>
+		</view> -->
+
 	</view>
 </template>
 
@@ -69,23 +97,54 @@
 	import dayjs from 'dayjs'
 	import {
 		applyUrl,
-		applyModifyUrl
+		applyModifyUrl,
+		getStreetListUrl,
+		uploadAvatarUrl,
+		uploadImgUrl
 	} from '@/common/config/api.js';
 	import {
 		getCareInfo
 	} from '@/common/config/application-api.js'
+	import {
+		route
+	} from '@/uni_modules/uview-plus';
 	export default {
 		data() {
 			return {
+				fileList: [],
+				minDate: dayjs().valueOf(),
+				showTimePicker: false,
+				showPicker: false,
 				formId: '',
+				fromList: [{
+					label: '散居',
+					id: 1,
+				}, {
+					label: '养老院',
+					id: 2,
+				}, {
+					label: '失能中心',
+					id: 3,
+				}],
+				defaultIndex: [1],
+				chooseStreet: {
+					id: -1,
+					label: '请选择'
+				},
+				streetList: [
+					[]
+				],
+				chooseFromIndex: 0,
 				formData: {
 					personId: '',
 					name: '请选择申请人',
+					disability: '',
 					demand: '',
 					sex: '0',
 					hospital: '请选择所在医院',
 					address: '',
-					dayCount: '',
+					// dayCount: '',
+					// leaveTime: '',
 					remark: ''
 				}
 			}
@@ -99,11 +158,15 @@
 				this.formId = '';
 			}
 
+			this.getStreetList();
+
 			// 监听器
 			uni.$on('personChoose', (e) => {
-				console.log(e);
+				console.log('e', e);
 				this.formData.personId = e.id;
+				this.formData.disability = e.disability === 'Y' ? '是' : '否';
 				this.formData.name = e.name;
+				this.streetName = e.deptName || '暂无'
 			});
 
 			uni.$on('hospitalChoose', (e) => {
@@ -115,7 +178,123 @@
 			uni.$off('personChoose');
 			uni.$off('hospitalChoose');
 		},
+		computed: {
+			computeStreetCat() {
+				switch (this.chooseFromIndex) {
+					case 0:
+						return '所在街道'
+					default:
+					case 1:
+						return '所在养老院'
+					case 2:
+						return '所在失能中心'
+				}
+			},
+			computeFromDesc() {
+				return this.fromList[this.chooseFromIndex].label
+			}
+		},
 		methods: {
+			deletePic(event) {
+				this.fileList.splice(event.index, 1);
+			},
+			beforeRead(event) {
+				console.log(event);
+				return;
+			},
+			afterRead(event) {
+				console.log(event);
+				this.fileList = [{
+					...event.file,
+					status: 'uploading',
+					message: '上传中',
+				}]
+				uploadImgUrl({
+						filePath: event.file.url,
+						name: 'file',
+						custom: {
+							catch: true
+						}
+					})
+					.then(res => {
+						console.log('res', res);
+						uni.$u.toast('上传成功');
+						// getUserInfo();
+						this.fileList[0].status = 'success';
+						this.fileList[0].message = '';
+						this.fileList[0].url = uni.$u.http.config.baseURL + '/prod-api/' + res.fileName;
+						this.fileList[0].name = res.fileName;
+					})
+					.catch(err => {
+						uni.hideLoading()
+						console.log('上传Err', err);
+						this.fileList[0].status = 'failed';
+						this.fileList[0].message = '上传失败';
+					})
+			},
+			handleTimeConfirm(e) {
+				console.log('e', e);
+				if (e.value) {
+					this.formData.leaveTime = dayjs(e.value).format('YYYY-MM-DD HH:mm:ss')
+				} else {
+					this.formData.leaveTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
+				}
+				this.showTimePicker = false;
+			},
+			handlePickerConfirm(e) {
+				console.log('e', e.value[0].id);
+				this.chooseStreet = e.value[0];
+			},
+			getStreetList() {
+				getStreetListUrl({
+						params: {
+							type: this.chooseFromIndex + 1
+						}
+					})
+					.then(res => {
+						// console.log(res);
+						if (res.length > 0) {
+							let temp = res.map(item => {
+								return {
+									label: item.deptName,
+									id: item.deptId
+								}
+							})
+							this.chooseStreet = temp[0];
+							this.streetList = [temp];
+							this.defaultIndex = [0];
+						} else {
+							this.streetList = [];
+							this.defaultIndex = [0];
+							this.streetName = '';
+							this.chooseStreet = {}
+						}
+					})
+					.catch(err => {
+						console.log('街道信息err', err);
+						this.streetList = [];
+						this.defaultIndex = [0];
+						this.streetName = '';
+						this.chooseStreet = {};
+					})
+			},
+			chooseFrom() {
+				uni.showActionSheet({
+					title: '人员来源',
+					itemList: this.fromList.map(item => item.label),
+					success: res => {
+						if (this.chooseFromIndex === res.tapIndex) {
+							return;
+						}
+						this.chooseFromIndex = res.tapIndex;
+						// 获取街道/养老院/失能中心列表
+						this.getStreetList();
+					},
+					fail: function(res) {
+						console.log(res.errMsg);
+					}
+				});
+			},
 			getApplyInfo() {
 				getCareInfo({
 						id: this.formId
@@ -129,9 +308,17 @@
 							sex: res.nurseGender,
 							hospital: res.hospital,
 							address: res.address,
-							dayCount: res.careDays,
-							remark: res.remark
+							remark: res.remark,
+							disability: res.disability === 'N' ? "否" : "是"
+							// leaveTime: res.leaveTime
 						}
+						this.fileList = [];
+						this.fileList.push({
+							status: 'success',
+							message: '',
+							url: uni.$u.http.config.baseURL + '/prod-api/' + res.imgPath,
+							name: res.imgPath
+						})
 					})
 					.catch(err => {
 						console.log('申请详情Err', err);
@@ -143,16 +330,18 @@
 				})
 			},
 			goChoosePeople() {
-				uni.navigateTo({
-					url: '/pages/subpack/pages/choose-people/choose-people'
-				})
+				if (!this.chooseStreet.id || this.chooseStreet.id === -1) {
+					return uni.$u.toast('人员来源异常')
+				}
+				route('/pages/subpack/pages/choose-people/choose-people', {
+					deptId: this.chooseStreet.id
+				});
 			},
 			chooseGender() {
 				uni.showActionSheet({
 					title: '首选护理人员性别',
 					itemList: ['男', '女'],
 					success: (res) => {
-						// console.log('选择了第' + (res.tapIndex) + '个选项');
 						this.formData.sex = res.tapIndex + '';
 					},
 					fail: (err) => {
@@ -169,13 +358,14 @@
 						applyId: this.formId,
 						address: this.formData.address,
 						applyDate: dayjs().format('YYYY-MM-DD'),
-						careDays: this.formData.dayCount,
 						careNeeds: this.formData.demand,
 						hospital: this.formData.hospital,
 						nurseGender: this.formData.sex,
 						personId: this.formData.personId,
 						personName: this.formData.name,
-						remark: this.formData.remark
+						remark: this.formData.remark,
+						imgPath: this.fileList[0].name
+						// leaveTime: this.formData.leaveTime,
 					}
 					applyModifyUrl(params)
 						.then(res => {
@@ -201,13 +391,15 @@
 					params = {
 						address: this.formData.address,
 						applyDate: dayjs().format('YYYY-MM-DD'),
-						careDays: this.formData.dayCount,
+						// careDays: this.formData.dayCount,
 						careNeeds: this.formData.demand,
 						hospital: this.formData.hospital,
 						nurseGender: this.formData.sex,
 						personId: this.formData.personId,
 						personName: this.formData.name,
-						remark: this.formData.remark
+						remark: this.formData.remark,
+						imgPath: this.fileList[0].name
+						// leaveTime: this.formData.leaveTime,
 					}
 					applyUrl(params)
 						.then(res => {
@@ -233,6 +425,7 @@
 			},
 
 			handleSubmit() {
+
 				if (this.formData.name === '请选择申请人' || !this.formData.name) {
 					return uni.$u.toast('请选择申请人')
 				}
@@ -249,10 +442,14 @@
 					return uni.$u.toast('请输入详情地址')
 				}
 
-				if (!this.formData.dayCount) {
-					return uni.$u.toast('请输入预计天数')
+				if (!this.fileList[0]?.url.startsWith('http')) {
+					return uni.$u.toast('请上传护理申请书')
 				}
 
+				// if (!this.formData.leaveTime) {
+				// 	return uni.$u.toast('请选择出院时间')
+				// }
+
 				uni.showModal({
 					title: '提示',
 					content: `确定提交此次申请吗?`,
@@ -269,11 +466,12 @@
 </script>
 
 <style lang="scss" scoped>
+	::v-deep .u-datetime-picker {
+		flex: 0
+	}
+
 	.container {
 		height: 100vh;
-		// background-image: url('@/static/images/index-bg.png');
-		// background-size: cover;
-		// background-position: center;
 		display: flex;
 		flex-direction: column;
 	}

+ 97 - 0
pages/subpack/pages/chang-pwd/chang-pwd.vue

@@ -0,0 +1,97 @@
+<template>
+	<view>
+		<u-navbar class="u-navbar-box" title="修改密码" placeholder bgColor="transparent" autoBack />
+		<u-cell-group :border="false" :customStyle="{background: '#fff', margin: '30rpx',  borderRadius: '20rpx'}">
+			<u-cell title="原密码" :isLink="false" :border="false">
+				<template #value>
+					<!-- <view>{{vuex_user.nickName}}</view> -->
+					<up-input placeholder="请输入旧密码" border="none" type="password" inputAlign="right" :maxlength="20"
+						v-model.trim="originPwd"></up-input>
+				</template>
+			</u-cell>
+			<u-cell title="新密码" :isLink="false" :border="false">
+				<template #value>
+					<!-- <view>{{vuex_user.nickName}}</view> -->
+					<up-input placeholder="请输入新密码" border="none" type="password" inputAlign="right" :maxlength="20"
+						v-model.trim="newPwd"></up-input>
+				</template>
+			</u-cell>
+			<u-cell title="确认密码" :isLink="false" :border="false">
+				<template #value>
+					<up-input placeholder="请再次输入新密码" border="none" type="password" inputAlign="right" :maxlength="20"
+						v-model.trim="confirmPwd"></up-input>
+				</template>
+			</u-cell>
+		</u-cell-group>
+
+		<view style="padding-left: 20px;padding-right: 20px;">
+			<up-button type="primary" @click="updateUserPwd">修改密码</up-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		updateUserPwdUrl
+	} from '@/common/config/api.js';
+	export default {
+		data() {
+			return {
+				originPwd: '',
+				newPwd: '',
+				confirmPwd: ''
+			}
+		},
+		methods: {
+			updateUserPwd() {
+				if (!this.originPwd) {
+					return uni.$u.toast('请输入原密码');
+				}
+				if (!this.newPwd) {
+					return uni.$u.toast('请输入新密码');
+				}
+				if (!this.confirmPwd) {
+					return uni.$u.toast('请再次输入新密码');
+				}
+
+				if (this.newPwd !== this.confirmPwd) {
+					return uni.$u.toast('两次密码输入不一致');
+				}
+
+				uni.showModal({
+					title: '提示',
+					content: '您确定要修改密码吗?',
+					success(res) {
+						if (res.confirm) {
+							updateUserPwdUrl({
+									oldPassword: this.originPwd,
+									newPassword: this.newPwd
+								})
+								.then(res => {
+									proxy.$u.toast('修改成功');
+								})
+						}
+					}
+				})
+
+
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	page {
+		background-color: #F6F8FD;
+	}
+
+	:deep(.u-navbar__content__title) {
+		font-weight: bold;
+		font-size: 36rpx;
+		color: #222222;
+	}
+
+	.u-navbar-box {
+		background: radial-gradient(circle at left, #FFE5E4 0%, #EEF2F5 40%, #DBEEFB 100%);
+	}
+</style>

+ 11 - 3
pages/subpack/pages/choose-people/choose-people.vue

@@ -28,7 +28,7 @@
 					</view>
 					<view class="flex-row align-center mt-16">
 						<text class="font" style="color: #666666;">年龄:{{item.age}}</text>
-						<text class="font ml-2" style="color: #666666;">性别:{{item.gender === "1"?'男':'女'}}</text>
+						<text class="font ml-2" style="color: #666666;">性别:{{item.gender === "0"?'男':'女'}}</text>
 					</view>
 				</view>
 			</view>
@@ -50,9 +50,14 @@
 		data() {
 			return {
 				keyword: '',
+				deptId: '',
 				dataList: []
 			}
 		},
+		onLoad(options) {
+			console.log(options);
+			this.deptId = options.deptId;
+		},
 		computed: {
 			computeNavHeight() {
 				console.log(uni.$u.sys());
@@ -69,7 +74,9 @@
 						if (e.confirm) {
 							uni.$emit('personChoose', {
 								id: item.personId,
-								name: item.name
+								name: item.name,
+								deptName: item.deptName,
+								disability: item.disability
 							})
 							setTimeout(() => {
 								uni.navigateBack()
@@ -98,7 +105,8 @@
 						params: {
 							pageNum: pageNo,
 							pageSize: pageSize,
-							name: this.keyword
+							name: this.keyword,
+							deptId: this.deptId
 						}
 					})
 					.then(res => {

+ 4 - 4
pages/subpack/pages/login/login.vue

@@ -11,18 +11,18 @@
 			<view class="w-750 flex-column align-center" style="margin-top: 1100rpx;">
 				<view class="flex-row align-center justify-center"
 					style="width: 606rpx;height: 96rpx;background: linear-gradient( 90deg, #2086FF 0%, #305BFF 100%);box-shadow: 0rpx 8rpx 16rpx 0rpx rgba(48,91,255,0.32);border-radius: 50rpx;border: 2rpx solid #FFFFFF;"
-					@click="handleLogin">
-					<text class="text-white font-md font-weight-500">一键快捷登录</text>
+					@click="handleLoginPwd">
+					<text class="text-white font-md font-weight-500">账号密码登录</text>
 				</view>
 			</view>
 
-			<view class="w-750 flex-column align-center" style="margin-top: 20rpx;">
+			<!-- <view class="w-750 flex-column align-center" style="margin-top: 20rpx;">
 				<view class="flex-row align-center justify-center"
 					style="width: 606rpx;height: 96rpx;background: linear-gradient( 90deg, #2086FF 0%, #305BFF 100%);box-shadow: 0rpx 8rpx 16rpx 0rpx rgba(48,91,255,0.32);border-radius: 50rpx;border: 2rpx solid #FFFFFF;"
 					@click="handleLoginPwd">
 					<text class="text-white font-md font-weight-500">账号密码登录</text>
 				</view>
-			</view>
+			</view> -->
 		</view>
 
 	</view>

+ 70 - 8
pages/subpack/pages/myInfo/info.vue

@@ -2,22 +2,33 @@
 	<view>
 		<u-navbar class="u-navbar-box" title="个人信息" placeholder bgColor="transparent" autoBack />
 		<u-cell-group :border="false" :customStyle="{background: '#fff', margin: '30rpx',  borderRadius: '20rpx'}">
-			<u-cell title="头像" isLink @click="changeAvatar">
+			<!-- <u-cell title="头像" isLink @click="changeAvatar">
 				<template #value>
 					<up-avatar :src="computeAvatarUrl" defaultUrl="/static/mine/avatar-def.png" bgColor="#305BFF" />
 				</template>
-			</u-cell>
-			<u-cell title="名" :isLink="false" :border="false">
+			</u-cell> -->
+			<u-cell title="用户名" :isLink="false" :border="false">
 				<template #value>
-					<view>{{vuex_user.nickName}}</view>
+					<!-- <view>{{vuex_user.nickName}}</view> -->
+					<up-input placeholder="请输入用户名" border="none" inputAlign="right" :maxlength="20"
+						v-model.trim="nickName"></up-input>
 				</template>
 			</u-cell>
 			<u-cell title="手机号码" :isLink="false" :border="false">
 				<template #value>
-					<view>{{vuex_user.phonenumber}}</view>
+					<!-- <view>{{vuex_user.phonenumber}}</view> -->
+					<up-input placeholder="请输入电话号码" border="none" type="number" inputAlign="right" :maxlength="11"
+						v-model.trim="phonenumber"></up-input>
 				</template>
 			</u-cell>
+			<u-cell title="修改密码" isLink :border="false" value="点击前往" url="/pages/subpack/pages/chang-pwd/chang-pwd">
+			</u-cell>
 		</u-cell-group>
+
+		<view style="padding-left: 20px;padding-right: 20px;">
+			<up-button type="primary" @click="updateUserInfo">修改信息</up-button>
+		</view>
+
 		<view class="position-fixed" style="bottom: 150rpx;left: 100rpx;width: 550rpx;">
 			<up-button type="error" @click="logout">退出登录</up-button>
 		</view>
@@ -28,7 +39,8 @@
 <script setup>
 	import {
 		uploadAvatarUrl,
-		getUserInfoUrl
+		getUserInfoUrl,
+		updateUserInfoUrl
 	} from '@/common/config/api.js';
 	import {
 		onMounted,
@@ -36,17 +48,66 @@
 		getCurrentInstance,
 		computed
 	} from 'vue';
+	import {
+		onLoad
+	} from '@dcloudio/uni-app'
 
 	const {
 		proxy
 	} = getCurrentInstance();
 
+	const nickName = ref('');
+	const phonenumber = ref('');
+
+
+	onLoad(() => {
+		nickName.value = proxy.vuex_user.nickName;
+		phonenumber.value = proxy.vuex_user.phonenumber;
+		// console.log('proxy.vuex_user', proxy.vuex_user);
+	})
+
 	const computeAvatarUrl = computed(() => {
 		// console.log('proxy.vuex_user.avatar', proxy.vuex_user.avatar);
 		// console.log('proxy.vuex_user.avatar', uni.$u.http.config.baseURL);
 		return uni.$u.http.config.baseURL + proxy.vuex_user.avatar
 	})
 
+	const updateUserInfo = () => {
+		if (!nickName.value) {
+			return uni.$u.toast('用户名不能为空');
+		}
+		if (!phonenumber.value) {
+			return uni.$u.toast('手机号码不能为空');
+		}
+		if (!uni.$u.test.mobile(phonenumber.value)) {
+			return uni.$u.toast('请输入正确的手机号码');
+		}
+
+		uni.showModal({
+			title: '提示',
+			content: '您确定要修改个人信息吗?',
+			success(res) {
+				if (res.confirm) {
+					updateUserInfoUrl({
+							userId: proxy.vuex_user.id,
+							nickName: nickName.value,
+							phonenumber: phonenumber.value
+						})
+						.then(res => {
+							console.log('修改个人信息', res);
+							proxy.$u.toast('修改成功');
+							// getUserInfo()
+							setTimeout(() => {
+								getUserInfo()
+							}, 1000)
+						})
+				}
+			}
+		})
+	}
+
+
+
 
 	const changeAvatar = () => {
 		// console.log('更换头像');
@@ -76,7 +137,8 @@
 			.then(res => {
 				console.log('用户信息', res);
 				let user = {
-					avatar: res.user.avatar,
+					id: res.user.userId,
+					// avatar: res.user.avatar,
 					nickName: res.user.nickName,
 					phonenumber: res.user.phonenumber,
 					roles: res.roles
@@ -97,7 +159,7 @@
 				if (res.confirm) {
 					proxy.$u.vuex('vuex_token', '');
 					proxy.$u.vuex('vuex_login', false);
-					uni.reLaunch({ 	
+					uni.reLaunch({
 						url: '/pages/subpack/pages/login/login'
 					})
 				}

+ 18 - 4
pages/subpack/pages/nurse/clockIn.vue

@@ -1,7 +1,6 @@
 <template>
 	<view class="page4-warp">
-		<u-navbar class="u-navbar-box" :title="submitType==='0'?'护理打卡':'死亡记录'" placeholder bgColor="transparent"
-			autoBack />
+		<u-navbar class="u-navbar-box" :title="computePageTitle" placeholder bgColor="transparent" autoBack />
 		<view class="page4-form">
 			<up-form labelPosition="top" :model="formData" :rules="formRule" ref="formRef">
 				<up-form-item v-if="submitType === '0'" label="护理时间" labelWidth="auto" labelPosition="left" required
@@ -112,7 +111,21 @@
 		// 获取需上传的图片数量
 		getNumber();
 		// 默认打卡时间,不再选择
-		formData.nursingTime = dayjs().format('YYYY-MM-DD hh:mm:ss')
+		formData.nursingTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
+	})
+
+
+	const computePageTitle = computed(() => {
+		switch (submitType.value) {
+			case '0':
+				return '护理打卡';
+			case '1':
+				return '死亡记录';
+			case '2':
+				return '结束护理';
+			default:
+				break;
+		}
 	})
 
 	//点击打卡
@@ -120,7 +133,7 @@
 		// console.log('1=>', fileList.value);
 		formData.img = [];
 		fileList.value.map(file => {
-			formData.img.push(file.url);
+			formData.img.push(file.url.replace(new RegExp(uni.$u.http.config.baseURL, 'g'), ''));
 		});
 		formRef.value.validate().then(res => {
 			if (res) {
@@ -162,6 +175,7 @@
 			nursingRecordId: applyId.value,
 			...formData
 		}
+		console.log('reqClockParams', reqClockParams);
 		addCareClocks(reqClockParams)
 			.then(res => {
 				console.log('打卡res', res);

+ 284 - 0
pages/subpack/pages/nurse/end-care.vue

@@ -0,0 +1,284 @@
+<template>
+	<view class="page4-warp">
+		<u-navbar class="u-navbar-box" title="结束护理" placeholder bgColor="transparent" autoBack />
+		<view class="page4-form">
+			<up-form labelPosition="top" :model="formData" :rules="formRule" ref="formRef">
+				<up-form-item label="出院时间" labelWidth="auto" labelPosition="left" required prop="leaveTime"
+					@click="showTime = true">
+					<up-input v-model="formData.leaveTime" disabled disabledColor="#ffffff" border="none"
+						placeholder="请选择出院时间" />
+				</up-form-item>
+				<up-form-item label="出院证明" labelWidth="auto" prop="leaveImg" required>
+					<view class="flex-row justify-between align-center" style="width: 100%;">
+						<up-upload :fileList="fileList" :sizeType="['compressed']" :maxCount="1" @afterRead="afterRead"
+							@delete="deletePic" />
+					</view>
+				</up-form-item>
+			</up-form>
+		</view>
+
+		<u-datetime-picker ref="datetimePicker" :show="showTime" v-model="timeValue" mode="date" closeOnClickOverlay
+			@cancel="showTime = false" @close="showTime = false" @confirm="timeConfirm" :formatter="formatter" />
+		<view class="btn-box">
+			<view class="flex-row ">
+				<up-button class="up-button" type="primary" @tap="checkTap">确定</up-button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive,
+		computed,
+		onMounted
+	} from 'vue';
+	import {
+		useStore
+	} from 'vuex';
+	import {
+		onLoad,
+		onUnload,
+		onReady
+	} from "@dcloudio/uni-app"
+	import {
+		getImgNumber,
+		addCareClocks,
+		addDieRecord,
+		endCareRequest,
+		uploadImg
+	} from '@/common/config/application-api.js'
+	import {
+		uploadImgUrl
+	} from '@/common/config/api.js';
+
+	import dayjs from 'dayjs';
+
+	const applyId = ref('');
+	const submitType = ref('');
+
+
+	const store = useStore();
+	const props = defineProps({
+		item: {
+			type: Object,
+			default () {
+				return {}
+			}
+		}
+	})
+
+	const showTime = ref(false);
+	const timeValue = ref(new Date().getTime());
+	const formRef = ref(null);
+	const fileList = ref([]);
+	const formData = reactive({
+		leaveImg: '',
+		leaveTime: ''
+	})
+	const formRule = reactive({
+		leaveTime: [{
+			required: true,
+			message: '请选择出院时间',
+			trigger: ['blur', 'change']
+		}],
+		leaveImg: [{
+			required: true,
+			message: '请上传指定出院证明图片',
+			trigger: ['change', 'blur'],
+		}]
+	})
+
+	onLoad((options) => {
+		console.log('options', options);
+		applyId.value = options.id;
+	})
+
+
+	//点击打卡
+	function checkTap() {
+		console.log('1=>', fileList.value);
+
+		// formData.img = [];
+		// fileList.value.map(file => {
+		// 	formData.img.push(file.url.replace(new RegExp(uni.$u.http.config.baseURL, 'g'), ''));
+		// });
+		formRef.value.validate().then(res => {
+			if (res) {
+				submitEnd();
+				// formData.imgPaths = formData.img.join(',');
+				// formData.imgNum = formData.img.length;
+				// delete formData.img;
+				// if (submitType.value === '0') {
+				// 	addClocks();
+				// } else {
+				// 	submitDie();
+				// }
+			}
+		})
+	}
+
+	const submitEnd = () => {
+		let params = {};
+		console.log('formData', formData);
+		endCareRequest({
+				applyId: applyId.value,
+				leaveTime: formData.leaveTime + ' 00:00:00',
+				leaveImg: formData.leaveImg,
+				dealStatus: 'wait_complete',
+				payStatus: 'dis_pay'
+			})
+			.then(res => {
+				console.log('出院res', res);
+				uni.showToast({
+					title: '提交成功',
+					icon: 'none'
+				})
+				uni.$emit('renderApplyList')
+				uni.navigateBack({
+					delta: 2
+				});
+			})
+			.catch(err => {
+				uni.showToast({
+					title: '提交失败',
+					icon: 'none'
+				})
+				console.log('出院err', err);
+			})
+	}
+
+	//点击打卡 --新增打卡记录
+	function addClocks() {
+		let reqClockParams = {
+			applyId: applyId.value,
+			nursingRecordId: applyId.value,
+			...formData
+		}
+		console.log('reqClockParams', reqClockParams);
+		addCareClocks(reqClockParams)
+			.then(res => {
+				console.log('打卡res', res);
+				uni.$u.toast('打卡成功!');
+				uni.$emit('reloadLine');
+				uni.navigateBack();
+			})
+			.catch(error => {
+				console.log('打卡error', error);
+				uni.$u.toast('打卡失败!')
+			});
+	}
+
+	const datetimePicker = ref();
+	onReady(() => {
+		datetimePicker.value.setFormatter(formatter);
+	})
+
+	function formatter(type, value) {
+		if (type === 'year') {
+			return `${value}年`
+		}
+		if (type === 'month') {
+			return `${value}月`
+		}
+		if (type === 'day') {
+			return `${value}日`
+		}
+		if (type === 'hour') {
+			return `${value}时`
+		}
+		if (type === 'minute') {
+			return `${value}分`
+		}
+		return value
+	}
+
+	function timeConfirm(e) {
+		console.log('timeConfirm=>', e);
+		timeValue.value = e.value;
+		formData.leaveTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd');
+		showTime.value = false;
+	}
+
+	//删除图片
+	function deletePic(event) {
+		fileList.value.splice(event.index, 1);
+	}
+
+	//新增图片
+	const afterRead = (event) => {
+		console.log(event);
+		fileList.value = [{
+			...event.file,
+			status: 'uploading',
+			message: '上传中',
+		}]
+		uploadImgUrl({
+				filePath: event.file.url,
+				name: 'file',
+				custom: {
+					catch: true
+				}
+			})
+			.then(res => {
+				console.log('res', res);
+				uni.$u.toast('上传成功');
+				// getUserInfo();
+				fileList.value[0].status = 'success';
+				fileList.value[0].message = '';
+				fileList.value[0].url = uni.$u.http.config.baseURL + '/prod-api/' + res.fileName;
+				fileList.value[0].name = res.fileName;
+				formData.leaveImg = res.fileName;
+			})
+			.catch(err => {
+				uni.hideLoading()
+				console.log('上传Err', err);
+				fileList[0].value.status = 'failed';
+				fileList[0].value.message = '上传失败';
+			})
+	}
+
+	//获取打卡图片数量
+	const imgNum = ref(0);
+
+	function getNumber() {
+		if (submitType.value === '1') {
+			return imgNum.value = 1
+		}
+		getImgNumber({
+			params: {
+				key: 'need_img_num'
+			}
+		}).then(res => {
+			imgNum.value = res.number;
+		})
+	}
+</script>
+
+<style lang="scss" scoped>
+	:deep(.u-navbar__content__title) {
+		font-weight: bold;
+		font-size: 36rpx;
+		color: #222222;
+	}
+
+	.u-navbar-box {
+		background: radial-gradient(circle at left, #FFE5E4 0%, #EEF2F5 40%, #DBEEFB 100%);
+	}
+
+	.page4-form {
+		padding: 30rpx;
+		border-radius: 10rpx;
+		background-color: #fff;
+	}
+
+	.btn-box {
+		padding: 20rpx 36rpx;
+		background-color: #fff;
+		position: fixed;
+		bottom: 10rpx;
+		left: 0;
+		width: 690rpx;
+	}
+</style>

BIN
pages/subpack/static/images/logo-header-bg.png


+ 35 - 4
pages/tabbar/application/application.vue

@@ -41,6 +41,31 @@
 				</view>
 			</view>
 		</view>
+
+		<!--入口  -->
+		<view v-if="vuex_user.roles.includes('company')" class="px-32" style="margin-top: 40rpx;" @click="goApply">
+			<view class="position-relative flex-row align-center justify-between"
+				style="width: 100%;height: 148rpx;border-radius: 20rpx;background-color: rgba(228, 240, 255, 0.8);">
+				<image style="width: 100%;height: 100%;" src="@/static/images/index-center-bg.png" mode="aspectFill">
+				</image>
+				<view class="position-absolute top-0 left-0 flex-row align-center justify-between"
+					style="height: 100%;width: 100%;">
+					<view class="flex-row align-center" style="margin-left: 28rpx;">
+						<image style="width: 66rpx;height: 66rpx;" src="@/static/images/index-enter-logo.png"
+							mode="aspectFill"></image>
+						<view class="flex-column ml-24">
+							<text class="font-md" style="color: #4A90C6;">护理申请</text>
+							<view class="bg-white flex-row align-center justify-center"
+								style="width: 208rpx;height: 40rpx;border-radius: 20rpx;margin-top: 18rpx;">
+								<text style="color: #4A90C6;font-size: 22rpx;">一键快速处理</text>
+							</view>
+						</view>
+					</view>
+					<image src="/static/images/index-enter-arrow.png"
+						style="width: 32rpx;height: 32rpx;margin-right: 52rpx;" mode="aspectFill"></image>
+				</view>
+			</view>
+		</view>
 	</view>
 </template>
 
@@ -49,7 +74,7 @@
 		getUserInfoUrl,
 		getMsgCountUrl
 	} from '@/common/config/api.js'
-	
+
 	import {
 		getDict
 	} from '@/common/status/index.js'
@@ -79,11 +104,12 @@
 				}
 				// 民政所
 				if (this.vuex_user.roles.includes('village')) {
-					return '护理申请'
+					return '护理初审'
 				}
 				if (this.vuex_user.roles.includes('area')) {
 					return '护理审核'
 				}
+				// 护理公司
 				if (this.vuex_user.roles.includes('company')) {
 					return '护理指派'
 				}
@@ -93,6 +119,11 @@
 			}
 		},
 		methods: {
+			goApply() {
+				return uni.navigateTo({
+					url: '/pages/subpack/pages/apply/apply'
+				})
+			},
 			getUserInfo() {
 				getUserInfoUrl()
 					.then(res => {
@@ -151,10 +182,10 @@
 			},
 			// 点击预约
 			handleEnterClick() {
-				// 民政所
+				// 街道
 				if (this.vuex_user.roles.includes('village')) {
 					return uni.navigateTo({
-						url: '/pages/subpack/pages/apply/apply'
+						url: '/pages/subpack/pages/application/list'
 					})
 				}
 				// 民政局

+ 8 - 9
pages/tabbar/mine/mine.vue

@@ -19,18 +19,12 @@
 			</div>
 		</view>
 		<u-cell-group :border="false" :customStyle="{background: '#fff', margin: '30rpx', borderRadius: '20rpx'}">
-			<!-- 县民政 -->
-			<u-cell v-if="vuex_user.roles.includes('village')" title="护理申请" isLink url="/pages/subpack/pages/application/list">
+			<!-- 街道 -->
+			<u-cell v-if="vuex_user.roles.includes('village')" title="护理初审" isLink url="/pages/subpack/pages/application/list">
 				<template #icon>
 					<u-image src="/static/mine/task.png" bgColor="transparent" width="36rpx" height="36rpx" />
 				</template>
 			</u-cell>
-			<!-- 县民政 -->
-			<!-- <u-cell v-if="vuex_user.roles.includes('village')" title="业务评分" isLink url="/pages/subpack/pages/application/list">
-				<template #icon>
-					<u-image src="/static/mine/task.png" bgColor="transparent" width="36rpx" height="36rpx" />
-				</template>
-			</u-cell> -->
 			<!-- 区民政 -->
 			<u-cell v-if="vuex_user.roles.includes('area')" title="护理审核" isLink url="/pages/subpack/pages/application/list">
 				<template #icon>
@@ -44,7 +38,12 @@
 				</template>
 			</u-cell>
 			<!-- 护理公司 -->
-			<u-cell v-if="vuex_user.roles.includes('company')" title="护理指派" isLink url="/pages/subpack/pages/application/list">
+			<u-cell v-if="vuex_user.roles.includes('company')" title="我的申请" isLink url="/pages/subpack/pages/application/list?flag=0">
+				<template #icon>
+					<u-image src="/static/mine/task.png" bgColor="transparent" width="36rpx" height="36rpx" />
+				</template>
+			</u-cell>
+			<u-cell v-if="vuex_user.roles.includes('company')" title="护理指派" isLink url="/pages/subpack/pages/application/list?flag=1">
 				<template #icon>
 					<u-image src="/static/mine/task.png" bgColor="transparent" width="36rpx" height="36rpx" />
 				</template>

+ 290 - 0
uni_modules/uview-plus/changelog.md

@@ -1,3 +1,293 @@
+## 3.4.40(2025-06-06)
+fix: 升级二维码 canvas -> canvas2 感谢@yjr
+
+## 3.4.39(2025-05-31)
+fix: 修改步骤条微信小程序下的布局 感谢@jiaruiyan
+
+fix: u-tabs在屏幕尺寸发生变化时滑块位置没有发生变化 感谢@aqzhft
+
+fix: 鸿蒙平台不支持plus.runtime.openWeb 感谢@aqzhft
+
+## 3.4.38(2025-05-30)
+fix: 修复picker-data快捷组件缺少index
+
+fix: 修复picker组件双向绑定初始化及取消后复原再次打开后的当前项目
+
+## 3.4.37(2025-05-29)
+feat: modal支持设置动画时间
+
+fix: DatetimePicker v-model 绑定异步设置无效 (#803)
+
+## 3.4.36(2025-05-28)
+fix: lazy-load图片为空时显示错误
+
+## 3.4.35(2025-05-28)
+feat: 进度条支持从右往左加载
+
+## 3.4.34(2025-05-28)
+feat: table2支持自定义标题和单元格样式
+
+## 3.4.33(2025-05-27)
+fix: 修复小程序cate-tab第一次切换时没反应 感谢@jiaruiyan
+
+fix: 修复datetimepicker传入空字符串时导致组件崩溃 感谢@jiaruiyan
+
+fix: 修复album带单位的字符串参与计算导致的计算数据错误 感谢@jiaruiyan
+
+## 3.4.32(2025-05-26)
+feat: 增加状态栏独立颜色配置支持支付宝小程序状态栏对背景色识别的不友好的情况
+
+fix: 抖音二维码兼容修复
+
+feat: cate-tab组件增加rightTop插槽 #715
+
+fix: 修改 test.promise(res) 预期结果不一致
+
+## 3.4.31(2025-05-17)
+fix: 修复parse富文本组件导致鸿蒙运行白屏
+
+fix: 去除演示项目中uni.$u用法便于兼容鸿蒙
+
+feat: modal新增popupBottom插槽适用类似关闭按钮与内容区域分离的场景
+
+## 3.4.30(2025-05-16)
+feat: 新增pagination分页器组件
+
+feat: popup新增bottom插槽适用类似关闭按钮与内容区域分离的场景
+
+## 3.4.29(2025-05-15)
+fix: 修复table2横向滚动样式
+
+fix: 修复table2组件宽度兼容
+
+fix: 修复image显示png图片时默认背景色问题
+
+feat: cate-tab新增height参数便于设置组件高度
+
+feat: 在index.js种导出digit.js便于使用
+
+fix: 修复tag组件缺失iconColor属性
+
+fix: 优化index-list的setValueForTouch方法逻辑 #708
+
+feat: number-box支持change事件返回变动是点击了增加还是减少按钮
+
+fix: 修复table2在小程序下部分情形不显示表格
+
+## 3.4.28(2025-05-12)
+feat: 新增table表格组件
+
+feat: 新增element-plus风格的table2组件
+
+## 3.4.27(2025-05-06)
+fix: 修复card组件props
+
+## 3.4.26(2025-05-06)
+fix: 修复test工具引入
+
+feat: card组件支持全局设置props默认值
+
+fix: 修复image在加载错误情况下高度和宽度不正确问题
+
+fix: 修复picker-data快捷组件默认picker选中
+
+fix: 修复日历month子组件缺失emits定义
+
+## 3.4.25(2025-04-27)
+fix: up-form编译在微信小程序里样式缺失 #640
+
+fix: number-box输入为空时自动设为最小值
+
+feat: picker与datetimepicke组件hasInput模式支持inputProps属性
+
+## 3.4.24(2025-04-25)
+fix: 修复upload上传逻辑(感谢@semdy)
+
+## 3.4.23(2025-04-24)
+chore: 补全chooseFile TS类型(感谢@semdy)
+
+feat: u-search组件的图标支持显示在右边(感谢@semdy)
+
+chore: 修正chooseFile返回的数据TS类型(感谢@semdy)
+
+fix: PR导致缺失name影响uplad自动上传扩展名
+
+
+## 3.4.22(2025-04-22)
+fix: 修复自动上传偶发的success被覆盖为uploading
+
+fix: float-button缺少key #677
+
+fix: upload组件完善优化(感谢@semdy)
+
+fix: toolbar组件confirmColor属性默认改为空,以便默认使用主题色、标题字体加粗(感谢@semdy)
+
+## 3.4.21(2025-04-21)
+feat: subsection分段器支持双向绑定current
+
+feat: select组件支持maxHeight属性
+
+feat: datetime-picker支持inputBorder属性
+
+## 3.4.20(2025-04-17)
+fix: 修复navbar-mini提示border不存在
+
+feat: status-bar支持对外暴露状态栏高度值
+
+feat: upload支持自定义自动上传后处理逻辑便于对接不同规范后端
+
+feat: 优化tag组件插槽
+
+
+## 3.4.19(2025-04-14)
+fix: 修复model组件增加contentStyle带来的语法问题
+
+## 3.4.18(2025-04-14)
+fix: upload组件支持所有文件类型的onClickPreview事件
+
+## 3.4.17(2025-04-11)
+feat: select组件text插槽增加scope传递currentLabel
+
+## 3.4.16(2025-04-10)
+fix: 修复安卓新加载字体方式导致Cannot read property '$page' of undefined
+
+## 3.4.15(2025-04-10)
+improvment: 优化移步加载数据时swiper组件displayMultipleItems报错
+
+feat: modal增加contentStyle属性
+
+fix: 修复下拉菜单收起动画缺失
+
+fix: 修复sticky的offset属性值为响应式数据时失效 #237
+
+
+## 3.4.14(2025-04-09)
+feat: 支持自托管内置图标及扩展自定义图标
+
+## 3.4.13(2025-04-08)
+fix: tabs点击当前tab触发change事件
+
+## 3.4.12(2025-04-02)
+fix: dropdown关闭后遮挡页面内容 #653
+
+fix u-sticky.vue Uncaught TypeError: e.querySelector is not a function at uni-app-view.umd.js
+
+## 3.4.11(2025-03-31)
+fix: 优化upload组件预览视频的弹窗占位
+
+## 3.4.10(2025-03-28)
+feat: select组件新增多个props属性及优化
+
+fix: 修复cate-tab报错index is not defined #661
+
+
+## 3.4.9(2025-03-27)
+fix: 修复upload组件split报错
+
+fix: 修复float-button缺少flex样式
+
+## 3.4.8(2025-03-27)
+fix: 修复upload组件split报错
+
+fix: 移除mapState
+
+## 3.4.7(2025-03-26)
+fix: 修复action-sheet-data和picker-data数据回显
+
+fix:  优化upload组件视频封面兼容
+
+## 3.4.6(2025-03-25)
+feat: checkbox触发change时携带name参数
+
+feat: upload组件支持服务器本机和阿里云OSS自动上传功能及上传进度条
+
+feat: upload组件支持视频预览及oss上传时获取视频封面图
+
+feat: 新增up-action-sheet-data快捷组件
+
+feat: 新增up-picker-data快捷组件
+
+## 3.4.5(2025-03-24)
+feat: tag组件新增textSize/height/padding/borderRadius属性
+
+feat: 新增genLightColor自动计算浅色方法及tag组件支持autoBgColor自动计算背景色
+
+## 3.4.4(2025-03-13)
+feat: modal增加异步操作进行中点击取消弹出提示特性防止操作被中断
+
+fix: 修复toast组件show方法类型声明
+
+## 3.4.3(2025-03-12)
+fix: 修复textarea自动增高时在输入时高度异常
+
+## 3.4.2(2025-03-11)
+feat: step组件增加title插槽及增加辅助class便于自定义样式
+
+## 3.4.1(2025-03-11)
+feat: 新机制确保setConfig与http在nvue等环境下生效
+
+## 3.3.74(2025-03-06)
+fix: CateTab语法问题
+
+## 3.3.73(2025-03-06)
+feat: CateTab新增v-model:current属性
+
+## 3.3.72(2025-02-28)
+feat: tabs组件支持icon图标及插槽
+
+## 3.3.71(2025-02-27)
+feat: 折叠面板collapse增加titileStyle/iconStyle/rightIconStyle属性
+
+feat: 折叠面板组件新增cellCustomStyle/cellCustomClass属性
+
+fix: select组件盒模型
+
+## 3.3.70(2025-02-24)
+fix: 修改u-checkbox-group组件changes事件发生位置
+
+## 3.3.69(2025-02-19)
+picker允许传递禁用颜色props
+
+slider组件isRange状态下增加min max插槽分开显示内容
+
+feat: 新增经典下拉框组件up-select
+
+## 3.3.68(2025-02-12)
+fix: 修复weekText类型
+
+feat: 日历增加单选与多选指定禁止选中的日期功能
+
+fix: NumberBox删除数字时取值有误 #613
+
+## 3.3.67(2025-02-11)
+feat: navbar支持返回全局拦截器配置
+
+feat: 表单-校验-支持无提示-得到校验结果
+
+feat: picker传递hasInput属性时候,可以禁用输入框点击
+
+## 3.3.66(2025-02-09)
+feat: steps-item增加content插槽
+
+## 3.3.65(2025-02-05)
+feat: number-box组件新增按钮圆角/按钮宽度/数据框背景色/迷你模式
+## 3.3.64(2025-01-18)
+feat: 日历组件支持自定义星期文案
+
+## 3.3.63(2025-01-13)
+fix: cate-tab支持支付宝小程序
+
+fix: textarea 修复 placeholder-style
+
+fix: 修复在图片加载及加载失败时容器宽度
+
+fix: waterfall组件报错Maximum recursive updates
+
+## 3.3.62(2025-01-10)
+feat: sleder滑动选择器双滑块增加外层触发值的变动功能
+
+fix: picker支持hasInput优化
+
 ## 3.3.61(2024-12-31)
 fix: 修复微信getSystemInfoSync接口废弃警告
 

+ 7 - 2
uni_modules/uview-plus/components/u--form/u--form.vue

@@ -43,7 +43,12 @@
 			setRules(rules) {
 				this.$refs.uForm.setRules(rules)
 			},
-			validate() {
+			/**
+			 * 校验全部数据
+			 * @param {Object} options
+			 * @param {Boolean} options.showErrorMsg -是否显示校验信息,
+			 */
+			validate(options) {
 				/**
 				 * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
 				 * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
@@ -52,7 +57,7 @@
 				// #ifdef MP-WEIXIN
 				this.setMpData()
 				// #endif
-				return this.$refs.uForm.validate()
+				return this.$refs.uForm.validate(options)
 			},
 			validateField(value, callback) {
 				// #ifdef MP-WEIXIN

+ 109 - 0
uni_modules/uview-plus/components/u-action-sheet-data/u-action-sheet-data.vue

@@ -0,0 +1,109 @@
+<template>
+	<view class="u-action-sheet-data">
+		<view class="u-action-sheet-data__trigger">
+			<slot name="trigger"></slot>
+			<up-input
+				v-if="!$slots['trigger']"
+				:modelValue="current"
+				disabled
+				disabledColor="#ffffff"
+				:placeholder="title"
+				border="none"
+			></up-input>
+			<view @click="show = true"
+				class="u-action-sheet-data__trigger__cover"></view>
+		</view>
+		<up-action-sheet
+			:show="show"
+			:actions="options"
+			:title="title"
+			safeAreaInsetBottom
+			:description="description"
+			@close="show = false"
+			@select="select"
+		>
+		</up-action-sheet>
+	</view>
+</template>
+
+<script>
+export default {
+    props: {
+		modelValue: {
+			type: [String, Number],
+			default: ''
+		},
+		title: {
+			type: String,
+			default: ''
+		},
+		description: {
+			type: String,
+			default: ''
+		},
+		options: {
+			type: Array,
+			default: () => {
+				return []
+			}
+		},
+		valueKey: {
+			type: String,
+			default: 'value'
+		},
+		labelKey: {
+			type: String,
+			default: 'name'
+		}
+    },
+    data() {
+        return {
+			show: false,
+			current: '',
+        }
+    },
+    created() {
+		if (this.modelValue) {
+			this.options.forEach((ele) => {
+				if (ele[this.valueKey] == this.modelValue) {
+					this.current = ele[this.labelKey]
+				}
+			})
+		}
+    },
+    emits: ['update:modelValue'],
+	watch: {
+		modelValue() {
+			this.options.forEach((ele) => {
+				if (ele[this.valueKey] == this.modelValue) {
+					this.current = ele[this.labelKey]
+				}
+			})
+		}
+	},
+    methods: {
+        hideKeyboard() {
+            uni.hideKeyboard()
+        },
+        select(e) {
+            this.$emit('update:modelValue', e[this.valueKey])
+			this.current = e[this.labelKey]
+        },
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+	.u-action-sheet-data {
+		&__trigger {
+			position: relative;
+			&__cover {
+				position: absolute;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+			}
+		}
+	}
+</style>

+ 18 - 3
uni_modules/uview-plus/components/u-album/u-album.vue

@@ -213,14 +213,29 @@ export default {
             uni.getImageInfo({
                 src,
                 success: (res) => {
+                    let singleSize = this.singleSize;
+                    // 单位
+                    let unit = '';
+                    if (Number.isNaN(Number(this.singleSize))) {
+                        // 大小中有字符 则记录字符
+                        unit = this.singleSize.replace(/\d+/g, ''); // 单位
+                        singleSize = Number(this.singleSize.replace(/\D+/g, ''), 10); // 具体值
+                    }
+
                     // 判断图片横向还是竖向展示方式
                     const isHorizotal = res.width >= res.height
                     this.singleWidth = isHorizotal
-                        ? this.singleSize
-                        : (res.width / res.height) * this.singleSize
+                        ? singleSize
+                        : (res.width / res.height) * singleSize
                     this.singleHeight = !isHorizotal
-                        ? this.singleSize
+                        ? singleSize
                         : (res.height / res.width) * this.singleWidth
+
+                    // 如果有单位统一设置单位
+                    if(unit != null && unit !== ''){
+                        this.singleWidth = this.singleWidth + unit
+                        this.singleHeight = this.singleHeight + unit
+                    }
                 },
                 fail: () => {
                     this.getComponentWidth()

+ 4 - 1
uni_modules/uview-plus/components/u-calendar/calendar.js

@@ -37,6 +37,9 @@ export default {
         showRangePrompt: true,
         allowSameDay: false,
 		round: 0,
-		monthNum: 3
+		monthNum: 3,
+        weekText: ['一', '二', '三', '四', '五', '六', '日'],
+        forbidDays: [],
+        forbidDaysToast: '该日期已禁用',
     }
 }

+ 14 - 7
uni_modules/uview-plus/components/u-calendar/header.vue

@@ -9,13 +9,13 @@
 			v-if="showSubtitle"
 		>{{ subtitle }}</text>
 		<view class="u-calendar-header__weekdays">
-			<text class="u-calendar-header__weekdays__weekday"></text>
-			<text class="u-calendar-header__weekdays__weekday"></text>
-			<text class="u-calendar-header__weekdays__weekday"></text>
-			<text class="u-calendar-header__weekdays__weekday"></text>
-			<text class="u-calendar-header__weekdays__weekday"></text>
-			<text class="u-calendar-header__weekdays__weekday"></text>
-			<text class="u-calendar-header__weekdays__weekday"></text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[0] }}</text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[1] }}</text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[2] }}</text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[3] }}</text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[4] }}</text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[5] }}</text>
+			<text class="u-calendar-header__weekdays__weekday">{{ weekText[6] }}</text>
 		</view>
 	</view>
 </template>
@@ -47,6 +47,13 @@
 				type: Boolean,
 				default: true
 			},
+			// 星期文本
+			weekText: {
+				type: Array,
+				default: () => {
+					return ['一', '二', '三', '四', '五', '六', '日']
+				}
+			},
 		},
 		data() {
 			return {

+ 25 - 3
uni_modules/uview-plus/components/u-calendar/month.vue

@@ -12,11 +12,11 @@
 					:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
 					<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
 						<text class="u-calendar-month__days__day__select__info"
-							:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
+							:class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__info--disabled' : '']"
 							:style="[textStyle(item1)]">{{ item1.day }}</text>
 						<text v-if="getBottomInfo(index, index1, item1)"
 							class="u-calendar-month__days__day__select__buttom-info"
-							:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
+							:class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__buttom-info--disabled' : '']"
 							:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
 						<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
 					</view>
@@ -126,6 +126,14 @@
 			allowSameDay: {
 				type: Boolean,
 				default: false
+			},
+			forbidDays: {
+				type: Array,
+				default: () => []
+			},
+			forbidDaysToast: {
+				type: String,
+				default: ''
 			}
 		},
 		data() {
@@ -167,7 +175,7 @@
 						style.marginLeft = addUnit(week * dayWidth, 'px')
 					}
 					if (this.mode === 'range') {
-						// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+						// 之所以需要这么写,是因为DCloud公司的iOS客户端导致的bug
 						style.paddingLeft = 0
 						style.paddingRight = 0
 						style.paddingBottom = 0
@@ -284,6 +292,7 @@
 		mounted() {
 			this.init()
 		},
+		emits: ['monthSelected', 'updateMonthTop'],
 		methods: {
 			init() {
 				// 初始化默认选中
@@ -297,6 +306,13 @@
 					})
 				})
 			},
+			isForbid(item) {
+				let date = dayjs(item.date).format("YYYY-MM-DD")
+				if (this.mode !== 'range' && this.forbidDays.includes(date)) {
+					return true
+				}
+				return false
+			},
 			// 判断两个日期是否相等
 			dateSame(date1, date2) {
 				return dayjs(date1).isSame(dayjs(date2))
@@ -362,6 +378,12 @@
 				this.item = item
 				const date = dayjs(item.date).format("YYYY-MM-DD")
 				if (item.disabled) return
+				if (this.isForbid(item)) {
+					uni.showToast({
+						title: this.forbidDaysToast
+					})
+					return
+				}
 				// 对上一次选择的日期数组进行深度克隆
 				let selected = deepClone(this.selected)
 				if (this.mode === 'single') {

+ 14 - 1
uni_modules/uview-plus/components/u-calendar/props.js

@@ -142,6 +142,19 @@ export const props = defineMixin({
 		monthNum: {
 			type: [Number, String],
 			default: 3
-		}	
+		},
+        // 星期文案
+        weekText: {
+			type: Array,
+			default: defProps.calendar.weekText
+		},
+        forbidDays: {
+			type: Array,
+			default: defProps.calendar.forbidDays
+		},
+        forbidDaysToast:{
+			type: String,
+			default: defProps.calendar.forbidDaysToast
+		},
     }
 })

+ 4 - 0
uni_modules/uview-plus/components/u-calendar/u-calendar.vue

@@ -13,6 +13,7 @@
 				:subtitle="subtitle"
 				:showSubtitle="showSubtitle"
 				:showTitle="showTitle"
+				:weekText="weekText"
 			></uHeader>
 			<scroll-view
 				:style="{
@@ -41,6 +42,8 @@
 					:rangePrompt="rangePrompt"
 					:showRangePrompt="showRangePrompt"
 					:allowSameDay="allowSameDay"
+					:forbidDays="forbidDays"
+					:forbidDaysToast="forbidDaysToast"
 					ref="month"
 					@monthSelected="monthSelected"
 					@updateMonthTop="updateMonthTop"
@@ -106,6 +109,7 @@ import test from '../../libs/function/test';
  * @property {Boolean}				allowSameDay	    是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
  * @property {Number|String}	    round				圆角值,默认无圆角  (默认 0 )
  * @property {Number|String}	    monthNum			最多展示的月份数量  (默认 3 )
+ * @property {Array}	            weekText			星期文案  (默认 ['一', '二', '三', '四', '五', '六', '日'] )
  *
  * @event {Function()} confirm 		点击确定按钮时触发		选择日期相关的返回参数
  * @event {Function()} close 		日历关闭时触发			可定义页面关闭时的回调事件

+ 40 - 0
uni_modules/uview-plus/components/u-card/card.js

@@ -0,0 +1,40 @@
+/*
+ * @Author       : jry
+ * @Description  :
+ * @version      : 3.0
+ * @Date         : 2025-04-26 16:37:21
+ * @LastAuthor   : jry
+ * @lastTime     : 2025-04-26 16:37:21
+ * @FilePath     : /uview-plus/libs/config/props/card.js
+ */
+export default {
+	// card组件的props
+	card: {
+		full: false,
+		title: '',
+		titleColor: '#303133',
+		titleSize: '15px',
+		subTitle: '',
+		subTitleColor: '#909399',
+		subTitleSize: '13px',
+		border: true,
+		index: '',
+		margin: '15px',
+		borderRadius: '8px',
+		headStyle: {},
+		bodyStyle: {},
+		footStyle: {},
+		headBorderBottom: true,
+		footBorderTop: true,
+		thumb: '',
+		thumbWidth: '30px',
+		thumbCircle: false,
+		padding: '15px',
+		paddingHead: '',
+        paddingBody: '',
+        paddingFoot: '',
+        showHead: true,
+        showFoot: true,
+        boxShadow: 'none'
+	}
+}

+ 26 - 32
uni_modules/uview-plus/components/u-card/props.js

@@ -6,135 +6,129 @@ export const propsCard = defineMixin({
         // 与屏幕两侧是否留空隙
 		full: {
 			type: Boolean,
-			default: false
+			default: () => defProps.card.full
 		},
 		// 标题
 		title: {
 			type: String,
-			default: ''
+			default: () => defProps.card.title
 		},
 		// 标题颜色
 		titleColor: {
 			type: String,
-			default: '#303133'
+			default: () => defProps.card.titleColor
 		},
 		// 标题字体大小
 		titleSize: {
 			type: [Number, String],
-			default: '15px'
+			default: () => defProps.card.titleSize
 		},
 		// 副标题
 		subTitle: {
 			type: String,
-			default: ''
+			default: () => defProps.card.subTitle
 		},
 		// 副标题颜色
 		subTitleColor: {
 			type: String,
-			default: '#909399'
+			default: () => defProps.card.subTitleColor
 		},
 		// 副标题字体大小
 		subTitleSize: {
 			type: [Number, String],
-			default: '13'
+			default: () => defProps.card.subTitleSize
 		},
 		// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
 		border: {
 			type: Boolean,
-			default: true
+			default: () => defProps.card.border
 		},
 		// 用于标识点击了第几个
 		index: {
 			type: [Number, String, Object],
-			default: ''
+			default: () => defProps.card.index
 		},
 		// 用于隔开上下左右的边距,带单位的写法,如:"30px 30px","20px 20px 30px 30px"
 		margin: {
 			type: String,
-			default: '15px'
+			default: () => defProps.card.margin
 		},
 		// card卡片的圆角
 		borderRadius: {
 			type: [Number, String],
-			default: '8px'
+			default: () => defProps.card.borderRadius
 		},
 		// 头部自定义样式,对象形式
 		headStyle: {
 			type: Object,
-			default() {
-				return {};
-			}
+			default: () => defProps.card.headStyle
 		},
 		// 主体自定义样式,对象形式
 		bodyStyle: {
 			type: Object,
-			default() {
-				return {};
-			}
+			default: () => defProps.card.bodyStyle
 		},
 		// 底部自定义样式,对象形式
 		footStyle: {
 			type: Object,
-			default() {
-				return {};
-			}
+			default: () => defProps.card.footStyle
 		},
 		// 头部是否下边框
 		headBorderBottom: {
 			type: Boolean,
-			default: true
+			default: () => defProps.card.headBorderBottom
 		},
 		// 底部是否有上边框
 		footBorderTop: {
 			type: Boolean,
-			default: true
+			default: () => defProps.card.footBorderTop
 		},
 		// 标题左边的缩略图
 		thumb: {
 			type: String,
-			default: ''
+			default: () => defProps.card.thumb
 		},
 		// 缩略图宽高
 		thumbWidth: {
 			type: [String, Number],
-			default: '30px'
+			default: () => defProps.card.thumbWidth
 		},
 		// 缩略图是否为圆形
 		thumbCircle: {
 			type: Boolean,
-			default: false
+			default: () => defProps.card.thumbCircle
 		},
 		// 给head,body,foot的内边距
 		padding: {
 			type: [String, Number],
-			default: '15px'
+			default: () => defProps.card.padding
 		},
 		paddingHead: {
 			type: [String, Number],
-			default: ''
+			default: () => defProps.card.paddingHead
 		},
 		paddingBody: {
 			type: [String, Number],
-			default: ''
+			default: () => defProps.card.paddingBody
 		},
 		paddingFoot: {
 			type: [String, Number],
-			default: ''
+			default: () => defProps.card.paddingFoot
 		},
 		// 是否显示头部
 		showHead: {
 			type: Boolean,
-			default: true
+			default: () => defProps.card.showHead
 		},
 		// 是否显示尾部
 		showFoot: {
 			type: Boolean,
-			default: true
+			default: () => defProps.card.showFoot
 		},
 		// 卡片外围阴影,字符串形式
 		boxShadow: {
 			type: String,
-			default: 'none'
+			default: () => defProps.card.boxShadow
 		}
     }
 })

+ 44 - 15
uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue

@@ -1,22 +1,28 @@
 <template>
-	<view class="u-cate-tab">
+	<view class="u-cate-tab" :style="{ height: addUnit(height) }">
 		<view class="u-cate-tab__wrap">
 			<scroll-view class="u-cate-tab__view u-cate-tab__menu-scroll-view"
                 scroll-y scroll-with-animation :scroll-top="scrollTop"
 			    :scroll-into-view="itemId">
 				<view v-for="(item, index) in tabList" :key="index" class="u-cate-tab__item"
-                    :class="[current == index ? 'u-cate-tab__item-active' : '']"
+                    :class="[innerCurrent == index ? 'u-cate-tab__item-active' : '']"
 				 @tap.stop="swichMenu(index)">
-                    <slot name="tabItem" :item="item">
-                        <text class="u-line-1">{{item[tabKeyName]}}</text>
+					<slot name="tabItem" :item="item">
                     </slot>
+                    <text v-if="!$slots['tabItem']" class="u-line-1">{{item[tabKeyName]}}</text>
 				</view>
 			</scroll-view>
-			<scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="u-cate-tab__right-box" @scroll="rightScroll">
+			<scroll-view :scroll-top="scrollRightTop" scroll-with-animation
+				scroll-y class="u-cate-tab__right-box" @scroll="rightScroll">
+				<slot name="rightTop" :tabList="tabList">
+                </slot>
 				<view class="u-cate-tab__page-view">
-					<view class="u-cate-tab__page-item" :id="'item' + index" v-for="(item , index) in tabList" :key="index">
+					<view class="u-cate-tab__page-item" :id="'item' + index"
+						v-for="(item , index) in tabList" :key="index">
                         <slot name="itemList" :item="item">
-                            <view class="item-title">
+                        </slot>
+						<template v-if="!$slots['itemList']">
+							<view class="item-title">
                                 <text>{{item[tabKeyName]}}</text>
                             </view>
                             <view class="item-container">
@@ -29,7 +35,7 @@
                                     </slot>
                                 </template>
                             </view>
-                        </slot>
+						</template>
 					</view>
 				</view>
 			</scroll-view>
@@ -37,8 +43,14 @@
 	</view>
 </template>
 <script>
+	import { addUnit } from '../../libs/function/index';
 	export default {
+		name: 'up-cate-tab',
         props: {
+			height: {
+                type: String,
+                default: '100%'
+            },
             tabList: {
                 type: Array,
                 default: () => {
@@ -52,6 +64,10 @@
             itemKeyName: {
                 type: String,
                 default: 'name'
+            },
+			current: {
+                type: Number,
+                default: 0
             }
         },
         watch: {
@@ -59,11 +75,12 @@
                 this.getMenuItemTop()
             }
         },
+		emits: ['update:current'],
 		data() {
 			return {
 				scrollTop: 0, //tab标题的滚动条位置
 				oldScrollTop: 0,
-				current: 0, // 预设当前项的值
+				innerCurrent: 0, // 预设当前项的值
 				menuHeight: 0, // 左边菜单的高度
 				menuItemHeight: 0, // 左边菜单item的高度
 				itemId: '', // 栏目右边scroll-view用于滚动的id
@@ -75,20 +92,30 @@
 			}
 		},
 		onMounted() {
+			this.innerCurrent = this.current;
+			this.leftMenuStatus(this.innerCurrent);
 			this.getMenuItemTop()
 		},
+		watch: {
+			current(nval) {
+				this.innerCurrent = nval;
+				this.leftMenuStatus(this.innerCurrent);
+			}
+		},
 		methods: {
+			addUnit,
 			// 点击左边的栏目切换
 			async swichMenu(index) {
 				if(this.arr.length == 0) {
 					await this.getMenuItemTop();
 				}
-				if (index == this.current) return;
+				if (index == this.innerCurrent) return;
 				this.scrollRightTop = this.oldScrollTop;
 				this.$nextTick(function(){
 					this.scrollRightTop = this.arr[index];
-					this.current = index;
+					this.innerCurrent = index;
 					this.leftMenuStatus(index);
+					this.$emit('update:current', index);
 				})
 			},
 			// 获取一个目标元素的高度
@@ -128,7 +155,8 @@
 			},
 			// 设置左边菜单的滚动状态
 			async leftMenuStatus(index) {
-				this.current = index;
+				this.innerCurrent = index;
+				this.$emit('update:current', index);
 				// 如果为0,意味着尚未初始化
 				if (this.menuHeight == 0 || this.menuItemHeight == 0) {
 					await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
@@ -139,8 +167,8 @@
 			},
 			// 获取右边菜单每个item到顶部的距离
 			getMenuItemTop() {
-				new Promise(resolve => {
-					let selectorQuery = uni.createSelectorQuery();
+				return new Promise(resolve => {
+					let selectorQuery = uni.createSelectorQuery().in(this);
 					selectorQuery.selectAll('.u-cate-tab__page-item').boundingClientRect((rects) => {
 						// 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
 						if(!rects.length) {
@@ -153,8 +181,8 @@
 						rects.forEach((rect) => {
 							// 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
 							this.arr.push(rect.top - rects[0].top);
-							resolve();
 						})
+                        resolve();
 					}).exec()
 				})
 			},
@@ -262,6 +290,7 @@
 	}
 
 	.u-cate-tab__right-box {
+		flex: 1;
 		background-color: rgb(250, 250, 250);
 	}
 

+ 3 - 2
uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue

@@ -100,8 +100,7 @@
 						values.push(child.name)
 					}
 				})
-				// 发出事件
-				this.$emit('change', values)
+			
 				// 修改通过v-model绑定的值
 				// #ifdef VUE3
 				this.$emit("update:modelValue", values);
@@ -109,6 +108,8 @@
 				// #ifdef VUE2
 				this.$emit("input", values);
 				// #endif
+        // 放在最后更新,否则change事件传出去的values不会更新
+				this.$emit('change', values)
 			},
 		}
 	}

+ 3 - 1
uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue

@@ -248,7 +248,9 @@
 				}
 			},
 			emitEvent() {
-				this.$emit('change', this.isChecked)
+				this.$emit('change', this.isChecked, {
+					name: this.name
+				})
 				// 双向绑定
 				if (this.usedAlone) {
 					this.$emit('update:checked', this.isChecked)

+ 6 - 1
uni_modules/uview-plus/components/u-collapse-item/collapseItem.js

@@ -21,6 +21,11 @@ export default {
         name: '',
         icon: '',
         duration: 300,
-        showRight: true
+        showRight: true,
+        titleStyle: {},
+        iconStyle: {},
+		rightIconStyle: {},
+        cellCustomStyle: {},
+        cellCustomClass: ''
     }
 }

+ 31 - 0
uni_modules/uview-plus/components/u-collapse-item/props.js

@@ -7,6 +7,13 @@ export const props = defineMixin({
             type: String,
             default: () => defProps.collapseItem.title
         },
+        // 标题的样式
+        titleStyle: {
+            type: [Object, String],
+			default: () => {
+				return defProps.collapseItem.titleStyle
+			}
+        },
         // 标题右侧内容
         value: {
             type: String,
@@ -62,5 +69,29 @@ export const props = defineMixin({
             type: Boolean,
             default: () => defProps.collapseItem.showRight
         },
+        // 左侧图标样式
+        iconStyle: {
+            type: [Object, String],
+            default: () => {
+				return defProps.collapseItem.iconStyle
+			}
+        },
+        // 右侧箭头图标的样式
+        rightIconStyle: {
+            type: [Object, String],
+            default: () => {
+				return defProps.collapseItem.rightIconStyle
+			}
+        },
+        cellCustomStyle: {
+            type: [Object, String],
+            default: () => {
+				return defProps.collapseItem.cellCustomStyle
+			}
+        },
+        cellCustomClass: {
+            type: String,
+            default: () => defProps.collapseItem.cellCustomClass
+        }
     }
 })

+ 2 - 0
uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue

@@ -11,6 +11,8 @@
 			@click="clickHandler"
 			:arrowDirection="expanded ? 'up' : 'down'"
 			:disabled="disabled"
+			:customClass="cellCustomClass"
+			:customStyle="cellCustomStyle"
 		>
 			<!-- 微信小程序不支持,因为微信中不支持 <slot name="title" #title />的写法 -->
 			<template #title>

+ 6 - 1
uni_modules/uview-plus/components/u-datetime-picker/datetimePicker.js

@@ -32,6 +32,11 @@ export default {
         confirmColor: '#3c9cff',
         visibleItemCount: 5,
         closeOnClickOverlay: false,
-        defaultIndex: []
+        defaultIndex: [],
+        inputBorder: 'surround',
+        disabled: false,
+        disabledColor: '',
+        placeholder: '请选择',
+        inputProps: {},
     }
 }

+ 17 - 3
uni_modules/uview-plus/components/u-datetime-picker/props.js

@@ -5,15 +5,29 @@ export const props = defineMixin({
         // 是否显示input
         hasInput: {
             type: Boolean,
-            default: () => false
+            default: false
+        },
+        inputProps: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        inputBorder: {
+            type: String,
+            default: () => defProps.input.inputBorder
         },
 		disabled: {
             type: Boolean,
-            default: () => false
+            default: () => defProps.input.disabled
         },
+		disabledColor:{
+			type: String,
+			default: () => defProps.input.disabledColor
+		},
         placeholder: {
             type: String,
-            default: () => '请选择'
+            default: () => defProps.input.placeholder
         },
         format: {
             type: String,

+ 18 - 7
uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue

@@ -1,15 +1,13 @@
 <template>
     <view class="u-datetime-picker">
         <view v-if="hasInput" class="u-datetime-picker__has-input"
-            @click="onShowByClickInput" 
+            @click="onShowByClickInput"
         >
             <slot name="trigger" :value="inputValue">
 				<up-input
-					:placeholder="placeholder"
 					:readonly="!!showByClickInput"
-					border="surround"
 					v-model="inputValue"
-					:disabled="disabled"
+					v-bind="inputPropsInner"
 				></up-input>
 				<div class="input-cover">
 				</div>
@@ -133,7 +131,17 @@
 		computed: {
 			// 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
 			propsChange() {
-				return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]
+				return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, this.modelValue]
+			},
+			// input的props
+			inputPropsInner() {
+				return {
+					border: this.inputBorder,
+            		placeholder: this.placeholder,
+					disabled: this.disabled,
+					disabledColor: this.disabledColor,
+					...this.inputProps
+				}
 			}
 		},
 		mounted() {
@@ -428,7 +436,10 @@
 			},
 			// 根据minDate、maxDate、minHour、maxHour等边界值,判断各列的开始和结束边界值
 			getBoundary(type, innerValue) {
-			    const value = new Date(innerValue)
+			    let value = new Date(innerValue)
+                if(isNaN(value.getTime())){
+                    value = new Date()
+                }
 			    const boundary = new Date(this[`${type}Date`])
 			    const year = dayjs(boundary).year()
 			    let month = 1
@@ -467,7 +478,7 @@
 				if(!this.disabled){
 					this.showByClickInput = !this.showByClickInput
 				}
-				
+
 			}
 		}
 	}

+ 10 - 8
uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue

@@ -20,12 +20,12 @@
 			</view>
 		</view>
 		<view class="u-dropdown__content" :style="[contentStyle, {
-			transition: `opacity ${duration / 1000}s linear`,
-			top: addUnit(height),
-			height: contentHeight + 'px'
+			transition: `opacity ${duration / 1000}s, z-index ${duration / 1000}s linear`,
+			top: addUnit(height)
 		}]"
 		 @tap="maskClick" @touchmove.stop.prevent>
-			<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
+			<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle, {
+			}]">
 				<slot></slot>
 			</view>
 			<view class="u-dropdown__content__mask"></view>
@@ -129,6 +129,7 @@
 				// 展开时,设置下拉内容的样式
 				this.contentStyle = {
 					zIndex: 11,
+					height: this.contentHeight + 'px'
 				}
 				// 标记展开状态以及当前展开项的索引
 				this.active = true;
@@ -147,10 +148,11 @@
 				this.active = false;
 				this.current = 99999;
 				// 下拉内容的样式进行调整,不透明度设置为0
-				this.contentStyle = {
-					zIndex: -1,
-					opacity: 0
-				}
+				this.contentStyle.zIndex = -1;
+				this.contentStyle.opacity = 0;
+				setTimeout(() => {
+					this.contentStyle.height = 0;
+				}, this.duration)
 			},
 			// 点击遮罩
 			maskClick() {

+ 3 - 1
uni_modules/uview-plus/components/u-float-button/u-float-button.vue

@@ -9,6 +9,7 @@
         <view class="u-float-button__main" @click="clickHandler" :style="{
             backgroundColor: backgroundColor,
             color: color,
+			display: 'flex',
             flexDirection: 'row',
             justifyContent: 'center',
             alignItems: 'center',
@@ -24,10 +25,11 @@
                 bottom: height
             }">
                 <slot name="list">
-                    <template v-for="item in list">
+                    <template :key="index" v-for="(item, index) in list">
                         <view class="u-float-button__item" :style="{
                             backgroundColor: item?.backgroundColor ? item?.backgroundColor : backgroundColor,
                             color: item?.color ? item?.color : color,
+							display: 'flex',
                             flexDirection: 'row',
                             justifyContent: 'center',
                             alignItems: 'center',

+ 0 - 2
uni_modules/uview-plus/components/u-form-item/u-form-item.vue

@@ -240,10 +240,8 @@
 
 					&__slot {
 						flex: 1;
-						/* #ifndef MP */
 						@include flex;
 						align-items: center;
-						/* #endif */
 					}
 
 					&__icon {

+ 13 - 7
uni_modules/uview-plus/components/u-form/u-form.vue

@@ -126,7 +126,7 @@
 				});
 			},
 			// 对部分表单字段进行校验
-			async validateField(value, callback, event = null) {
+			async validateField(value, callback, event = null,options) {
 				// $nextTick是必须的,否则model的变更,可能会延后于此方法的执行
 				this.$nextTick(() => {
 					// 校验错误信息,返回给回调方法,用于存放所有form-item的错误信息
@@ -191,9 +191,11 @@
 												errorsRes.push(...errors);
 												childErrors.push(...errors);
 											}
-											child.message =
-												childErrors[0]?.message ? childErrors[0].message : null;
-
+											//没有配置,或者配置了showErrorMsg为true时候,才修改子组件message,默认没有配置
+											if(!options||options?.showErrorMsg==true){
+												child.message =
+													childErrors[0]?.message ? childErrors[0].message : null;
+											}
 											if (i == (rules.length - 1)) {
 												resolve(errorsRes)
 											}
@@ -217,8 +219,12 @@
 						});
 				});
 			},
-			// 校验全部数据
-			validate(callback) {
+			/**
+			 * 校验全部数据
+			 * @param {Object} options
+			 * @param {Boolean} options.showErrorMsg -是否显示校验信息,
+			 */
+			validate(options) {
 				// 开发环境才提示,生产环境不会提示
 				if (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) {
 					error('未设置rules,请看文档说明!如果已经设置,请刷新页面。');
@@ -240,7 +246,7 @@
 							} else {
 								resolve(true)
 							}
-						});
+						},null,options);
 					});
 				});
 			},

+ 61 - 19
uni_modules/uview-plus/components/u-icon/u-icon.vue

@@ -35,24 +35,13 @@
 </template>
 
 <script>
-	// #ifdef APP-NVUE
-	// nvue通过weex的dom模块引入字体,相关文档地址如下:
-	// https://weex.apache.org/zh/docs/modules/dom.html#addrule
-	const fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf'
-	const domModule = weex.requireModule('dom')
-	domModule.addRule('fontFace', {
-		'fontFamily': "uicon-iconfont",
-		'src': `url('${fontUrl}')`
-	})
-	// #endif
-
 	// 引入图标名称,已经对应的unicode
-	import icons from './icons'
+	import icons from './icons';
 	import { props } from './props';
+	import config from '../../libs/config/config';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
 	import { addUnit, addStyle } from '../../libs/function/index';
-	import config from '../../libs/config/config';
 	/**
 	 * icon 图标
 	 * @description 基于字体的图标集,包含了大多数常见场景的图标。
@@ -81,9 +70,58 @@
 	 */
 	export default {
 		name: 'u-icon',
+		beforeCreate() {
+			
+			// #ifdef APP-NVUE
+			// nvue通过weex的dom模块引入字体,相关文档地址如下:
+			// https://weex.apache.org/zh/docs/modules/dom.html#addrule
+			const domModule = weex.requireModule('dom');
+			domModule.addRule('fontFace', {
+				'fontFamily': "uicon-iconfont",
+				'src': `url('${config.iconUrl}')`
+			});
+			if (config.customIcon.family) {
+				domModule.addRule('fontFace', {
+					'fontFamily': config.customIcon.family,
+					'src': `url('${config.customIcon.url}')`
+				});
+			}
+			// #endif
+			// #ifdef APP || H5 || MP-WEIXIN || MP-ALIPAY
+			uni.loadFontFace({
+				family: 'uicon-iconfont',
+				source: 'url("' + config.iconUrl + '")',
+				success() {
+					// console.log('内置字体图标加载成功');
+				},
+				fail() {
+					console.error('内置字体图标加载出错');
+				}
+			});
+			if (config.customIcon.family) {
+				uni.loadFontFace({
+					family: config.customIcon.family,
+					source: 'url("' + config.customIcon.url + '")',
+					success() {
+						// console.log('扩展字体图标加载成功');
+					},
+					fail() {
+						console.error('扩展字体图标加载出错');
+					}
+				});
+			}
+			// #endif
+			// #ifdef APP-NVUE
+			if (this.customFontFamily) {
+				domModule.addRule('fontFace', {
+					'fontFamily': `${this.customPrefix}-${this.customFontFamily}`,
+					'src': `url('${this.customFontUrl}')`
+				})
+			}
+        	// #endif
+    	},
 		data() {
 			return {
-
 			}
 		},
 		emits: ['click'],
@@ -92,7 +130,7 @@
 			uClasses() {
 				let classes = []
 				classes.push(this.customPrefix + '-' + this.name)
-				// uView的自定义图标类名为u-iconfont
+				// uview-plus内置图标类名为u-iconfont
 				if (this.customPrefix == 'uicon') {
 					classes.push('u-iconfont')
 				} else {
@@ -117,6 +155,9 @@
 					// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
 					top: addUnit(this.top)
 				}
+				if (this.customPrefix !== 'uicon') {
+					style.fontFamily = this.customPrefix
+				}
 				// 非主题色值时,才当作颜色值
 				if (this.color && !config.type.includes(this.color)) style.color = this.color
 
@@ -136,7 +177,9 @@
 			// 通过图标名,查找对应的图标
 			icon() {
 				// 使用自定义图标的时候页面上会把name属性也展示出来,所以在这里处理一下
-				if (this.customPrefix !== "uicon") return "";
+				if (this.customPrefix !== "uicon") {
+					return config.customIcons[this.name] || this.name;
+				}
 				// 如果内置的图标中找不到对应的图标,就直接返回name值,因为用户可能传入的是unicode代码
 				return icons['uicon-' + this.name] || this.name
 			}
@@ -164,13 +207,12 @@
 	$u-icon-error: $u-error !default;
 	$u-icon-label-line-height:1 !default;
 
-	/* #ifndef APP-NVUE */
-	// 非nvue下加载字体
+	/* #ifdef MP-QQ || MP-TOUTIAO || MP-BAIDU || MP-KUAISHOU || MP-XHS */
+	// 2025/04/09在App/微信/支付宝/鸿蒙元服务已改用uni.loadFontFace加载字体
 	@font-face {
 		font-family: 'uicon-iconfont';
 		src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
 	}
-
 	/* #endif */
 
 	.u-icon {

+ 15 - 13
uni_modules/uview-plus/components/u-image/u-image.vue

@@ -45,7 +45,10 @@
 				v-if="showError && isError && !loading"
 				class="u-image__error"
 				:style="{
-					borderRadius: shape == 'circle' ? '50%' : addUnit(radius)
+					borderRadius: shape == 'circle' ? '50%' : addUnit(radius),
+					backgroundColor: this.bgColor,
+					width: addUnit(width),
+					height: addUnit(height)
 				}"
 			>
 				<slot name="error">
@@ -115,7 +118,6 @@
 					if (!n) {
 						// 如果传入null或者'',或者false,或者undefined,标记为错误状态
 						this.isError = true
-						
 					} else {
 						this.isError = false;
 						this.loading = true;
@@ -132,13 +134,13 @@
 				style.height = addUnit(this.height);
 				// #endif
 				// #ifndef APP-NVUE
-				if (this.width == '100%') {
-					style.width = this.width;
+				if (this.loading || this.isError || this.width == '100%' || this.mode != 'heightFix') {
+					style.width = addUnit(this.width);
 				} else {
 					style.width = 'fit-content';
 				}
-				if (this.height == '100%') {
-					style.height = this.height;
+				if (this.loading || this.isError || this.height == '100%' || this.mode != 'widthFix') {
+					style.height = addUnit(this.height);
 				} else {
 					style.height = 'fit-content';
 				}
@@ -153,13 +155,13 @@
 				style.height = addUnit(this.height);
 				// #endif
 				// #ifndef APP-NVUE
-				if (this.width == '100%') {
-					style.width = this.width;
+				if (this.loading || this.isError || this.width == '100%' || this.mode != 'heightFix') {
+					style.width = addUnit(this.width);
 				} else {
 					style.width = 'fit-content';
 				}
-				if (this.height == '100%') {
-					style.height = this.height;
+				if (this.loading || this.isError || this.height == '100%' || this.mode != 'widthFix') {
+					style.height = addUnit(this.height);
 				} else {
 					style.height = 'fit-content';
 				}
@@ -221,9 +223,9 @@
 			// 移除图片的背景色
 			removeBgColor() {
 				// 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景
-				this.backgroundStyle = {
-					backgroundColor: this.bgColor || '#ffffff'
-				};
+				// this.backgroundStyle = {
+				// 	backgroundColor: this.bgColor || '#ffffff'
+				// };
 			}
 		}
 	};

+ 1 - 1
uni_modules/uview-plus/components/u-index-list/u-index-list.vue

@@ -419,7 +419,7 @@
 					return child
 				})
 				// console.log('this.children[currentIndex].top', children[currentIndex].top)
-				if (children[currentIndex]?.top) {
+				if (children[currentIndex]?.top || children[currentIndex].top === 0) {
 					this.scrollTop = children[currentIndex].top - getPx(customNavHeight)
 				}
 				// #endif

+ 4 - 0
uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue

@@ -223,6 +223,10 @@
                     if (res.intersectionRatio > 0) {
                         // 懒加载状态改变
                         this.isShow = true;
+                        // 图片为空时显示错误
+                        if (!this.image) {
+                            this.loadError();
+                        }
                         // 如果图片已经加载,去掉监听,减少性能的消耗
                         this.disconnectObserver('contentObserver');
                     }

+ 2 - 1
uni_modules/uview-plus/components/u-line-progress/lineProgress.js

@@ -14,6 +14,7 @@ export default {
         inactiveColor: '#ececec',
         percentage: 0,
         showText: true,
-        height: 12
+        height: 12,
+		fromRight: false,
     }
 }

+ 5 - 0
uni_modules/uview-plus/components/u-line-progress/props.js

@@ -25,6 +25,11 @@ export const props = defineMixin({
         height: {
             type: [String, Number],
             default: () => defProps.lineProgress.height
+        },
+		// 是否从右往左加载
+		fromRight: {
+            type: Boolean,
+            default: () => defProps.lineProgress.fromRight
         }
     }
 })

+ 5 - 1
uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue

@@ -62,6 +62,11 @@
 				style.width = this.lineWidth
 				style.backgroundColor = this.activeColor
 				style.height = addUnit(this.height)
+				if (this.fromRight) {
+					style.right = 0;
+				} else {
+					style.left = 0;
+				}
 				return style
 			},
 			innserPercentage() {
@@ -127,7 +132,6 @@
 		&__line {
 			position: absolute;
 			top: 0;
-			left: 0;
 			bottom: 0;
 			align-items: center;
 			@include flex(row);

+ 5 - 1
uni_modules/uview-plus/components/u-modal/modal.js

@@ -26,6 +26,10 @@ export default {
         negativeTop: 0,
         width: '650rpx',
         confirmButtonShape: '',
-        contentTextAlign: 'left'
+        duration: 400,
+        contentTextAlign: 'left',
+        asyncCloseTip: '操作中...',
+        asyncCancelClose: false,
+        contentStyle: {}
     }
 }

+ 20 - 0
uni_modules/uview-plus/components/u-modal/props.js

@@ -82,10 +82,30 @@ export const props = defineMixin({
             type: String,
             default: () => defProps.modal.confirmButtonShape
         },
+        // 弹窗动画过度时间
+        duration: {
+            type: [Number],
+            default: defProps.modal.duration
+        },
         // 文案对齐方式
         contentTextAlign: {
             type: String,
             default: () => defProps.modal.contentTextAlign
         },
+        // 异步确定时如果点击了取消时候的提示文案
+        asyncCloseTip: {
+            type: String,
+            default: () => defProps.modal.asyncCloseTip
+        },
+        // 是否异步关闭,只对取消按钮有效
+        asyncCancelClose: {
+            type: Boolean,
+            default: () => defProps.modal.asyncCancelClose
+        },
+        // 内容样式
+        contentStyle: {
+            type: Object,
+            default: () => defProps.modal.contentStyle
+        }
     }
 })

+ 29 - 6
uni_modules/uview-plus/components/u-modal/u-modal.vue

@@ -11,7 +11,7 @@
 		}"
 		:closeOnClickOverlay="closeOnClickOverlay"
 		:safeAreaInsetBottom="false"
-		:duration="400"
+		:duration="duration"
 		@click="clickHandler"
 	>
 		<view
@@ -26,9 +26,7 @@
 			>{{ title }}</view>
 			<view
 				class="u-modal__content"
-				:style="{
-					paddingTop: `${title ? 12 : 25}px`
-				}"
+				:style="contentStyleCpu"
 			>
 				<slot>
 					<text class="u-modal__content__text" :style="{textAlign: contentTextAlign}">
@@ -89,6 +87,9 @@
 				</view>
 			</template>
 		</view>
+		<template #bottom>
+			<slot name="popupBottom"></slot>
+		</template>
 	</u-popup>
 </template>
 
@@ -117,6 +118,7 @@
 	 * @property {String | Number}	negativeTop			往上偏移的值,给一个负的margin-top,往上偏移,避免和键盘重合的情况,单位任意,数值则默认为px单位 (默认 0 )
 	 * @property {String | Number}	width				modal宽度,不支持百分比,可以数值,px,rpx单位 (默认 '650rpx' )
 	 * @property {String}			confirmButtonShape	确认按钮的样式,如设置,将不会显示取消按钮
+	 * @property {Number}			duration			弹窗动画过度时间 (默认 400 )
 	 * @event {Function} confirm	点击确认按钮时触发
 	 * @event {Function} cancel		点击取消按钮时触发
 	 * @event {Function} close		点击遮罩关闭出发,closeOnClickOverlay为true有效
@@ -137,7 +139,14 @@
 				if (n && this.loading) this.loading = false
 			}
 		},
-		emits: ["confirm", "cancel", "close", "update:show"],
+		emits: ["confirm", "cancel", "close", "update:show", 'cancelOnAsync'],
+		computed: {
+			contentStyleCpu() {
+				let style = this.contentStyle;
+				style.paddingTop = `${this.title ? 12 : 25}px`
+				return style;
+			}
+		},
 		methods: {
 			addUnit,
 			// 点击确定按钮
@@ -152,7 +161,21 @@
 			},
 			// 点击取消按钮
 			cancelHandler() {
-				this.$emit('update:show', false)
+				// 如果点击了确定按钮,确定按钮正在请求接口执行异步操作,那么限制不能取消。
+				if (this.asyncClose && this.loading) {
+					if (this.asyncCloseTip) {
+						uni.showToast({
+							title: this.asyncCloseTip,
+							icon: 'none'
+						});
+					}
+					this.$emit('cancelOnAsync')
+				} else {
+					// 如果配置了取消时异步关闭
+					if (!this.asyncCancelClose) {
+						this.$emit('update:show', false)
+					}
+				}
 				this.$emit('cancel')
 			},
 			// 点击遮罩

+ 0 - 1
uni_modules/uview-plus/components/u-navbar-mini/u-navbar-mini.vue

@@ -6,7 +6,6 @@
 			></u-status-bar>
 			<view
 				class="u-navbar-mini__content"
-				:class="[border && 'u-border-bottom']"
 				:style="{
 					height: addUnit(height),
 					backgroundColor: bgColor,

+ 5 - 0
uni_modules/uview-plus/components/u-navbar/props.js

@@ -58,6 +58,11 @@ export const props = defineMixin({
 			type: String,
 			default: () => defProps.navbar.bgColor
 		},
+        // 状态栏背景颜色 不写会使用背景颜色bgColor
+        statusBarBgColor: {
+            type: String,
+            default: () => ''
+        },
 		// 标题的宽度
 		titleWidth: {
 			type: [String, Number],

+ 10 - 4
uni_modules/uview-plus/components/u-navbar/u-navbar.vue

@@ -10,7 +10,7 @@
 		<view :class="[fixed && 'u-navbar--fixed']">
 			<u-status-bar
 				v-if="safeAreaInsetTop"
-				:bgColor="bgColor"
+				:bgColor="statusBarBgColor ? statusBarBgColor : bgColor"
 			></u-status-bar>
 			<view
 				class="u-navbar__content"
@@ -77,6 +77,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
+	import config  from '../../libs/config/config';
 	import { addUnit, addStyle, getPx, getWindowInfo } from '../../libs/function/index';
 	/**
 	 * Navbar 自定义导航栏
@@ -93,6 +94,7 @@
 	 * @property {String}			title				导航栏标题,如设置为空字符,将会隐藏标题占位区域
 	 * @property {String}			titleColor			文字颜色 (默认 '' )
 	 * @property {String}			bgColor				导航栏背景设置 (默认 '#ffffff' )
+	 * @property {String}			statusBarBgColor	状态栏背景颜色 不写同导航栏背景设置
 	 * @property {String | Number}	titleWidth			导航栏标题的最大宽度,内容超出会以省略号隐藏 (默认 '400rpx' )
 	 * @property {String | Number}	height				导航栏高度(不包括状态栏高度在内,内部自动加上)(默认 '44px' )
 	 * @property {String | Number}	leftIconSize		左侧返回图标的大小(默认 20px )
@@ -120,8 +122,12 @@
 			leftClick() {
 				// 如果配置了autoBack,自动返回上一页
 				this.$emit('leftClick')
-				if(this.autoBack) {
-					uni.navigateBack()
+				if (config.interceptor.navbarLeftClick != null) {
+					config.interceptor.navbarLeftClick()
+				} else {
+					if(this.autoBack) {
+						uni.navigateBack()
+					}
 				}
 			},
 			// 点击右侧区域
@@ -165,7 +171,7 @@
 
 			&__left {
 				left: 0;
-				
+
 				&--hover {
 					opacity: 0.7;
 				}

+ 5 - 1
uni_modules/uview-plus/components/u-number-box/numberBox.js

@@ -25,11 +25,15 @@ export default {
         decimalLength: null,
         longPress: true,
         color: '#323233',
+        buttonWidth: 30,
         buttonSize: 30,
+        buttonRadius: '0px',
         bgColor: '#EBECEE',
+        inputBgColor: '#EBECEE',
         cursorSpacing: 100,
         disableMinus: false,
         disablePlus: false,
-        iconStyle: ''
+        iconStyle: '',
+        miniMode: false
     }
 }

+ 21 - 1
uni_modules/uview-plus/components/u-number-box/props.js

@@ -86,16 +86,31 @@ export const props = defineMixin({
             type: String,
             default: () => defProps.numberBox.color
         },
+        // 按钮宽度
+        buttonWidth: {
+            type: [String, Number],
+            default: () => defProps.numberBox.buttonWidth
+        },
         // 按钮大小,宽高等于此值,单位px,输入框高度和此值保持一致
         buttonSize: {
             type: [String, Number],
             default: () => defProps.numberBox.buttonSize
         },
+        // 按钮圆角
+        buttonRadius: {
+            type: [String],
+            default: () => defProps.numberBox.buttonRadius
+        },
         // 输入框和按钮的背景颜色
         bgColor: {
             type: String,
             default: () => defProps.numberBox.bgColor
         },
+        // 输入框背景颜色
+        inputBgColor: {
+            type: String,
+            default: () => defProps.numberBox.inputBgColor
+        },
         // 指定光标于键盘的距离,避免键盘遮挡输入框,单位px
         cursorSpacing: {
             type: [String, Number],
@@ -115,6 +130,11 @@ export const props = defineMixin({
         iconStyle: {
             type: [Object, String],
             default: () => defProps.numberBox.iconStyle
-        }
+        },
+        // 迷你模式
+        miniMode: {
+            type: Boolean,
+            default: () => defProps.numberBox.miniMode
+        },
     }
 })

+ 53 - 39
uni_modules/uview-plus/components/u-number-box/u-number-box.vue

@@ -5,12 +5,12 @@
 		    @tap.stop="clickHandler('minus')"
 		    @touchstart="onTouchStart('minus')"
 		    @touchend.stop="clearTimeout"
-		    v-if="showMinus && $slots.minus"
+		    v-if="showMinus && !hideMinus && $slots.minus"
 		>
 			<slot name="minus" />
 		</view>
 		<view
-		    v-else-if="showMinus"
+		    v-else-if="showMinus && !hideMinus"
 		    class="u-number-box__minus cursor-pointer"
 		    @tap.stop="clickHandler('minus')"
 		    @touchstart="onTouchStart('minus')"
@@ -29,36 +29,38 @@
 			></u-icon>
 		</view>
 
-		<slot name="input">
-			<!-- #ifdef MP-WEIXIN -->
-			<input
-			    :disabled="disabledInput || disabled"
-			    :cursor-spacing="getCursorSpacing"
-			    :class="{ 'u-number-box__input--disabled': disabled || disabledInput }"
-			    :value="currentValue"
-			    class="u-number-box__input"
-			    @blur="onBlur"
-			    @focus="onFocus"
-			    @input="onInput"
-			    type="number"
-			    :style="[inputStyle]"
-			/>
-			<!-- #endif -->
-			<!-- #ifndef MP-WEIXIN -->
-			<input
-			    :disabled="disabledInput || disabled"
-			    :cursor-spacing="getCursorSpacing"
-			    :class="{ 'u-number-box__input--disabled': disabled || disabledInput }"
-			    v-model="currentValue"
-			    class="u-number-box__input"
-			    @blur="onBlur"
-			    @focus="onFocus"
-			    @input="onInput"
-			    type="number"
-			    :style="[inputStyle]"
-			/>
-			<!-- #endif -->
-		</slot>
+		<template v-if="!hideMinus">
+			<slot name="input">
+				<!-- #ifdef MP-WEIXIN -->
+				<input
+					:disabled="disabledInput || disabled"
+					:cursor-spacing="getCursorSpacing"
+					:class="{ 'u-number-box__input--disabled': disabled || disabledInput }"
+					:value="currentValue"
+					class="u-number-box__input"
+					@blur="onBlur"
+					@focus="onFocus"
+					@input="onInput"
+					type="number"
+					:style="[inputStyle]"
+				/>
+				<!-- #endif -->
+				<!-- #ifndef MP-WEIXIN -->
+				<input
+					:disabled="disabledInput || disabled"
+					:cursor-spacing="getCursorSpacing"
+					:class="{ 'u-number-box__input--disabled': disabled || disabledInput }"
+					v-model="currentValue"
+					class="u-number-box__input"
+					@blur="onBlur"
+					@focus="onFocus"
+					@input="onInput"
+					type="number"
+					:style="[inputStyle]"
+				/>
+				<!-- #endif -->
+			</slot>
+		</template>
 		<view
 		    class="u-number-box__slot cursor-pointer"
 		    @tap.stop="clickHandler('plus')"
@@ -164,6 +166,9 @@
 			// #endif
 		},
 		computed: {
+			hideMinus() {
+				return this.currentValue == 0 && this.miniMode == true
+			},
 			getCursorSpacing() {
 				// 判断传入的单位,如果为px单位,需要转成px
 				return getPx(this.cursorSpacing)
@@ -173,8 +178,10 @@
 				return (type) => {
 					const style = {
 						backgroundColor: this.bgColor,
+						width: addUnit(this.buttonWidth),
 						height: addUnit(this.buttonSize),
-						color: this.color
+						color: this.color,
+						borderRadius: this.buttonRadius
 					}
 					if (this.isDisabled(type)) {
 						style.backgroundColor = '#f7f8fa'
@@ -187,7 +194,7 @@
 				const disabled = this.disabled || this.disabledInput
 				const style = {
 					color: this.color,
-					backgroundColor: this.bgColor,
+					backgroundColor: this.inputBgColor || this.bgColor,
 					height: addUnit(this.buttonSize),
 					width: addUnit(this.inputWidth)
 				}
@@ -304,11 +311,17 @@
 					value = ''
 				} = e.detail || {}
 				// 为空返回
-				if (value === '') return
+				if (value === '') {
+					// 为空自动设为最小值
+					this.emitChange(this.min)
+					return
+				}
 				let formatted = this.filter(value)
+				// https://github.com/ijry/uview-plus/issues/613
+				this.emitChange(value);
 				// 最大允许的小数长度
 				if (this.decimalLength !== null && formatted.indexOf('.') !== -1) {
-					const pair = formatted.split('.');
+					const pair = formatted.split('.')
 					formatted = `${pair[0]}.${pair[1].slice(0, this.decimalLength)}`
 				}
 				formatted = this.format(formatted)
@@ -318,8 +331,8 @@
 				// #endif 
 			
 			},
-			// 发出change事件
-			emitChange(value) {
+			// 发出change事件,type目前只支持点击时有值,手动输入不支持。
+			emitChange(value, type = '') {
 				// 如果开启了异步变更值,则不修改内部的值,需要用户手动在外部通过v-model变更
 				if (!this.asyncChange) {
 					this.$nextTick(() => {
@@ -336,6 +349,7 @@
 				this.$emit('change', {
 					value,
 					name: this.name,
+					type: type // 当前变更类型
 				});
 			},
 			onChange() {
@@ -347,7 +361,7 @@
 				}
 				const diff = type === 'minus' ? -this.step : +this.step
 				const value = this.format(this.add(+this.currentValue, diff))
-				this.emitChange(value)
+				this.emitChange(value, type)
 				this.$emit(type)
 			},
 			// 对值扩大后进行四舍五入,再除以扩大因子,避免出现浮点数操作的精度问题

+ 283 - 0
uni_modules/uview-plus/components/u-pagination/u-pagination.vue

@@ -0,0 +1,283 @@
+<template>
+  <view class="u-pagination">
+    <!-- 上一页按钮 -->
+    <view
+      :class="[
+        'u-pagination-btn',
+        { disabled: currentPage === 1 }
+      ]"
+      :style="{ backgroundColor: buttonBgColor, borderColor: buttonBorderColor }"
+      @click="prev"
+    >
+      <template v-if="prevText">
+        {{ prevText }}
+      </template>
+      <up-icon v-else name="arrow-left"></up-icon>
+    </view>
+
+    <!-- 页码列表 -->
+    <block v-for="page in displayedPages" :key="page" v-if="layout.includes('pager')">
+      <view
+        :class="[
+          'u-pagination-item',
+          { active: page === currentPage }
+        ]"
+        @click="goTo(page)"
+      >
+        {{ page }}
+      </view>
+    </block>
+
+    <!-- 总数显示 -->
+    <view v-if="total > 0 && layout.includes('total')" class="u-pagination-total">
+        {{ currentPage }} / {{ totalPages }}
+    </view>
+
+    <!-- 每页数量选择器 -->
+    <!-- <picker
+      v-if="layout.includes('sizes')"
+      mode="selector"
+      :range="pageSizes"
+      range-key="label"
+      :value="pageSizeIndex"
+      @change="handleSizeChange"
+      class="u-pagination-sizes"
+    >
+      <view>{{ pageSizeLabel }}</view>
+    </picker> -->
+
+    <!-- 下一页按钮 -->
+    <view
+      :class="[
+        'u-pagination-btn',
+        { disabled: currentPage === totalPages }
+      ]"
+      :style="{ backgroundColor: buttonBgColor, borderColor: buttonBorderColor }"
+      @click="next"
+    >
+      <template v-if="nextText">
+        下一页
+      </template>
+      <up-icon v-else name="arrow-right"></up-icon>
+    </view>
+
+    <!-- 跳转输入框 -->
+    <!-- <view v-if="layout.includes('jumper')">
+      <text>前往</text>
+      <input
+        type="number"
+        class="u-pagination-jumper"
+        :value="currentPageInput"
+        @input="onInputPage"
+        @confirm="onConfirmPage"
+      />
+      <text>页</text>
+    </view> -->
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'u-pagination',
+  props: {
+    // 当前页码
+    currentPage: {
+      type: Number,
+      default: 1
+    },
+    // 每页条目数
+    pageSize: {
+      type: Number,
+      default: 10
+    },
+    // 总数据条目数
+    total: {
+      type: Number,
+      default: 0
+    },
+    // 上一页按钮文案
+    prevText: {
+      type: String,
+      default: ''
+    },
+    // 下一页按钮文案
+    nextText: {
+      type: String,
+      default: ''
+    },
+    buttonBgColor: {
+      type: String,
+      default: '#f5f7fa'
+    },
+    buttonBorderColor: {
+      type: String,
+      default: '#dcdfe6'
+    },
+    // 可选的每页条目数
+    pageSizes: {
+      type: Array,
+      default: () => [10, 20, 30, 40, 50]
+    },
+    // 布局方式(类似 el-pagination)
+    layout: {
+      type: String,
+      default: 'prev, pager, next'
+    },
+    // 是否隐藏只有一个页面时的分页控件
+    hideOnSinglePage: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['update:currentPage', 'update:pageSize', 'current-change', 'size-change'],
+  data() {
+    return {
+      currentPageInput: this.currentPage + ''
+    };
+  },
+  computed: {
+    totalPages() {
+      return Math.max(1, Math.ceil(this.total / this.pageSize));
+    },
+    pageSizeIndex() {
+      const index = this.pageSizes.findIndex(size => size.value === this.pageSize);
+      return index >= 0 ? index : 0;
+    },
+    pageSizeLabel() {
+      const found = this.pageSizes.find(size => size.value === this.pageSize);
+      return found?.label || this.pageSize;
+    },
+    displayedPages() {
+        const total = this.totalPages;
+        const current = this.currentPage;
+
+        if (total <= 4) {
+            return Array.from({ length: total }, (_, i) => i + 1);
+        }
+
+        const pages = [];
+
+        // 当前页靠近头部
+        if (current <= 2) {
+            for (let i = 1; i <= 4; i++) {
+            pages.push(i);
+            }
+            pages.push('...');
+            pages.push(total);
+        }
+        // 当前页在尾部附近
+        else if (current >= total - 1) {
+            pages.push(1);
+            pages.push('...');
+            for (let i = total - 3; i <= total; i++) {
+            pages.push(i);
+            }
+        }
+        // 中间情况
+        else {
+            pages.push(1);
+            pages.push('...');
+            pages.push(current - 1);
+            pages.push(current);
+            pages.push(current + 1);
+            pages.push('...');
+            pages.push(total);
+        }
+
+        return pages;
+    }
+    // 控制是否隐藏
+  },
+  watch: {
+    currentPage(val) {
+      this.currentPageInput = val + '';
+    }
+  },
+  methods: {
+    handleSizeChange(e) {
+      const selected = e.detail.value;
+      const size = this.pageSizes[selected]?.value || this.pageSizes[0].value;
+      this.$emit('update:pageSize', size);
+      this.$emit('size-change', size);
+    },
+    prev() {
+      if (this.currentPage > 1) {
+        this.goTo(this.currentPage - 1);
+      }
+    },
+    next() {
+      if (this.currentPage < this.totalPages) {
+        this.goTo(this.currentPage + 1);
+      }
+    },
+    goTo(page) {
+      if (page === '...' || page === this.currentPage) return;
+      this.$emit('update:currentPage', page);
+      this.$emit('current-change', page);
+    },
+    onInputPage(e) {
+      this.currentPageInput = e.detail.value;
+    },
+    onConfirmPage(e) {
+      const num = parseInt(e.detail.value);
+      if (!isNaN(num) && num >= 1 && num <= this.totalPages) {
+        this.goTo(num);
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.u-pagination {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  font-size: 14px;
+  color: #606266;
+
+  .u-pagination-total {
+    margin-right: 10px;
+  }
+
+  .u-pagination-sizes {
+    margin-right: 10px;
+    padding: 4px 4px;
+    border: 1rpx solid #dcdfe6;
+    border-radius: 4px;
+  }
+
+  .u-pagination-btn {
+    margin: 0 3px;
+    padding: 4px 4px;
+    border: 1rpx solid #dcdfe6;
+    border-radius: 4px;
+    background-color: #f5f7fa;
+    &.disabled {
+      opacity: 0.5;
+    }
+  }
+
+  .u-pagination-item {
+    margin: 0 2px;
+    padding: 4px 8px;
+    border-radius: 4px;
+    &.active {
+      background-color: #409eff;
+      color: white;
+    }
+  }
+
+  .u-pagination-jumper {
+    width: 40px;
+    height: 28px;
+    margin: 0 5px;
+    padding: 0 5px;
+    border: 1rpx solid #dcdfe6;
+    border-radius: 4px;
+    font-size: 14px;
+  }
+}
+</style>

+ 42 - 28
uni_modules/uview-plus/components/u-parse/node/node.vue

@@ -10,13 +10,19 @@
       <!-- #endif -->
       <!-- #ifndef H5 || (APP-PLUS && VUE2) -->
       <!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
-      <rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="'<img class=\'_img\' style=\''+n.attrs.style+'\' src=\''+n.attrs.src+'\'>'" :data-i="i" @tap.stop="imgTap" />
+      <rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style||'',src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
       <!-- #endif -->
-      <!-- #ifndef H5 || APP-PLUS -->
-      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #ifdef APP-HARMONY -->
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+ctrl[i]+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || APP-PLUS || MP-KUAISHOU -->
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifdef MP-KUAISHOU -->
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src" :lazy-load="opts[0]" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap"></image>
       <!-- #endif -->
       <!-- #ifdef APP-PLUS && VUE3 -->
-      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||''))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
       <!-- #endif -->
       <!-- 文本 -->
       <!-- #ifdef MP-WEIXIN -->
@@ -25,14 +31,14 @@
       <!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
       <text v-else-if="n.text" decode>{{n.text}}</text>
       <!-- #endif -->
-      <text v-else-if="n.name==='br'">\n</text>
+      <text v-else-if="n.name==='br'">{{'\n'}}</text>
       <!-- 链接 -->
       <view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
         <node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
       </view>
       <!-- 视频 -->
       <!-- #ifdef APP-PLUS -->
-      <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
+      <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" :data-i="i" @vplay.stop="play" />
       <!-- #endif -->
       <!-- #ifndef APP-PLUS -->
       <video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
@@ -61,13 +67,13 @@
           </block>
         </view>
       </view>
-
+      <!-- insert -->
       <!-- 富文本 -->
       <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
       <rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
       <!-- #endif -->
       <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
-      <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
+      <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
       <!-- #endif -->
       <!-- 继续递归 -->
       <view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
@@ -106,7 +112,6 @@ module.exports = {
 }
 </script>
 <script>
-
 import node from './node'
 export default {
   name: 'node',
@@ -122,7 +127,7 @@ export default {
     return {
       ctrl: {},
       // #ifdef MP-WEIXIN
-      isiOS: uni.getDeviceInfo().system.includes('iOS')
+      isiOS: uni.getSystemInfoSync().system.includes('iOS')
       // #endif
     }
   },
@@ -138,14 +143,13 @@ export default {
     opts: Array
   },
   components: {
-
-    // #ifndef (H5 || APP-PLUS) && VUE3
+    // #ifndef ((H5 || APP-PLUS) && VUE3) || APP-HARMONY
     node
     // #endif
   },
   mounted () {
     this.$nextTick(() => {
-      for (this.root = this.$parent; this.root.$options.name !== 'u-parse'; this.root = this.root.$parent);
+      for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
     })
     // #ifdef H5 || APP-PLUS
     if (this.opts[0]) {
@@ -168,14 +172,14 @@ export default {
     }
     // #endif
   },
-  beforeUnmount () {
+  beforeDestroy () {
     // #ifdef H5 || APP-PLUS
     if (this.observer) {
       this.observer.disconnect()
     }
     // #endif
   },
-  methods:{
+  methods: {
     // #ifdef MP-WEIXIN
     toJSON () { return this },
     // #endif
@@ -184,7 +188,15 @@ export default {
      * @param {Event} e
      */
     play (e) {
-      this.root.$emit('play')
+      const i = e.currentTarget.dataset.i
+      const node = this.childs[i]
+      this.root.$emit('play', {
+        source: node.name,
+        attrs: {
+          ...node.attrs,
+          src: node.src[this.ctrl[i] || 0]
+        }
+      })
       // #ifndef APP-PLUS
       if (this.root.pauseVideo) {
         let flag = false
@@ -227,7 +239,14 @@ export default {
       // #ifdef H5 || APP-PLUS
       node.attrs.src = node.attrs.src || node.attrs['data-src']
       // #endif
-      this.root.$emit('imgTap', node.attrs)
+      // #ifndef APP-HARMONY
+      this.root.$emit('imgtap', node.attrs)
+      // #endif
+      // #ifdef APP-HARMONY
+      this.root.$emit('imgtap', {
+        ...node.attrs
+      })
+      // #endif
       // 自动预览图片
       if (this.root.previewImg) {
         uni.previewImage({
@@ -299,7 +318,7 @@ export default {
      * @description 检查是否所有图片加载完毕
      */
     checkReady () {
-      if (!this.root.lazyLoad) {
+      if (this.root && !this.root.lazyLoad) {
         this.root._unloadimgs -= 1
         if (!this.root._unloadimgs) {
           setTimeout(() => {
@@ -321,7 +340,7 @@ export default {
       const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
       const attrs = node.attrs || e
       const href = attrs.href
-      this.root.$emit('linkTap', Object.assign({
+      this.root.$emit('linktap', Object.assign({
         innerText: this.root.getText(node.children || []) // 链接内的文本内容
       }, attrs))
       if (href) {
@@ -346,6 +365,9 @@ export default {
             // #ifdef APP-PLUS
             plus.runtime.openWeb(href)
             // #endif
+            // #ifdef APP-HARMONY
+            plus.runtime.openURL(href)
+            // #endif
           }
         } else {
           // 跳转页面
@@ -407,9 +429,7 @@ export default {
 ._a {
   padding: 1.5px 0 1.5px 0;
   color: #366092;
-  /* #ifndef APP-NVUE */
   word-break: break-all;
-  /* #endif */
 }
 
 /* a 标签点击态效果 */
@@ -427,9 +447,7 @@ export default {
 /* 内部样式 */
 
 ._block {
-  /* #ifndef APP-NVUE */
   display: block;
-  /* #endif */
 }
 
 ._b,
@@ -476,9 +494,7 @@ export default {
 ._h4,
 ._h5,
 ._h6 {
-  /* #ifndef APP-NVUE */
   display: block;
-  /* #endif */
   font-weight: bold;
 }
 
@@ -500,9 +516,7 @@ export default {
 
 ._ol,
 ._ul {
-  /* #ifndef APP-NVUE */
   display: block;
-  /* #endif */
   padding-left: 40px;
   margin: 1em 0;
 }
@@ -581,4 +595,4 @@ export default {
   height: 225px;
 }
 /* #endif */
-</style>
+</style>

+ 5 - 5
uni_modules/uview-plus/components/u-parse/parse.js

@@ -1,11 +1,11 @@
 /*
- * @Author       : LQ
+ * @Author       : jry
  * @Description  :
- * @version      : 1.0
+ * @version      : 3.0
  * @Date         : 2021-08-20 16:44:21
- * @LastAuthor   : LQ
- * @lastTime     : 2021-08-20 17:17:33
- * @FilePath     : /u-view2.0/uview-ui/libs/config/props/parse.js
+ * @LastAuthor   : jry
+ * @lastTime     : 2025-05-17 17:17:33
+ * @FilePath     : /uview-plus/libs/config/props/parse.js
  */
 export default {
     // parse

+ 78 - 15
uni_modules/uview-plus/components/u-parse/parser.js

@@ -71,19 +71,23 @@ const config = {
     viewbox: 'viewBox',
     attributename: 'attributeName',
     repeatcount: 'repeatCount',
-    repeatdur: 'repeatDur'
+    repeatdur: 'repeatDur',
+    foreignobject: 'foreignObject'
   }
 }
-const tagSelector={}
-// #ifdef APP || H5 || MP-WEIXIN
-const { windowWidth } = uni.getWindowInfo()
-const { system } = uni.getDeviceInfo()
+const tagSelector = {}
+let windowWidth, system
+// #ifdef MP-WEIXIN
+if (uni.canIUse('getWindowInfo')) {
+  windowWidth = uni.getWindowInfo().windowWidth
+  system = uni.getDeviceInfo().system
+} else {
 // #endif
-// #ifndef APP || H5 || MP-WEIXIN
-const {
-  windowWidth,
-  system
-} = uni.getSystemInfoSync()
+  const systemInfo = uni.getSystemInfoSync()
+  windowWidth = systemInfo.windowWidth
+  // #ifdef MP-WEIXIN
+  system = systemInfo.system
+}
 // #endif
 const blankChar = makeMap(' ,\r,\n,\t,\f')
 let idIndex = 0
@@ -325,6 +329,7 @@ Parser.prototype.onTagName = function (name) {
   this.tagName = this.xml ? name : name.toLowerCase()
   if (this.tagName === 'svg') {
     this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
+    config.ignoreTags.style = undefined // svg 标签内 style 可用
   }
 }
 
@@ -335,12 +340,18 @@ Parser.prototype.onTagName = function (name) {
  */
 Parser.prototype.onAttrName = function (name) {
   name = this.xml ? name : name.toLowerCase()
+  // #ifdef (VUE3 && (H5 || APP-PLUS)) || APP-PLUS-NVUE
+  if (name.includes('?') || name.includes(';')) {
+    this.attrName = undefined
+    return
+  }
+  // #endif
   if (name.substr(0, 5) === 'data-') {
     if (name === 'data-src' && !this.attrs.src) {
       // data-src 自动转为 src
       this.attrName = 'src'
     } else if (this.tagName === 'img' || this.tagName === 'a') {
-      // a 和 img 标签保留 data- 的属性,可以在 imgTap 和 linkTap 事件中使用
+      // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
       this.attrName = name
     } else {
       // 剩余的移除以减小大小
@@ -461,7 +472,7 @@ Parser.prototype.onOpenTag = function (selfClose) {
           node.webp = 'T'
         }
         // data url 图片如果没有设置 original-src 默认为不可预览的小图片
-        if (attrs.src.includes('data:') && !attrs['original-src']) {
+        if (attrs.src.includes('data:') && this.options.previewImg !== 'all' && !attrs['original-src']) {
           attrs.ignore = 'T'
         }
         if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
@@ -555,6 +566,13 @@ Parser.prototype.onOpenTag = function (selfClose) {
       if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
         node.h = 'T'
       }
+      if (node.w && node.h && styleObj['object-fit']) {
+        if (styleObj['object-fit'] === 'contain') {
+          node.m = 'aspectFit'
+        } else if (styleObj['object-fit'] === 'cover') {
+          node.m = 'aspectFill'
+        }
+      }
     } else if (node.name === 'svg') {
       siblings.push(node)
       this.stack.push(node)
@@ -681,11 +699,19 @@ Parser.prototype.popNode = function () {
         return
       }
       const name = config.svgDict[node.name] || node.name
+      if (name === 'foreignObject') {
+        for (const child of (node.children || [])) {
+          if (child.attrs && !child.attrs.xmlns) {
+            child.attrs.xmlns = 'http://www.w3.org/1999/xhtml'
+            break
+          }
+        }
+      }
       src += '<' + name
       for (const item in node.attrs) {
         const val = node.attrs[item]
         if (val) {
-          src += ` ${config.svgDict[item] || item}="${val}"`
+          src += ` ${config.svgDict[item] || item}="${val.replace(/"/g, '')}"`
         }
       }
       if (!node.children) {
@@ -707,6 +733,7 @@ Parser.prototype.popNode = function () {
     node.children = undefined
     // #endif
     this.xml = false
+    config.ignoreTags.style = true
     return
   }
 
@@ -843,6 +870,10 @@ Parser.prototype.popNode = function () {
     if (node.flag && node.c) {
       // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
       styleObj.display = 'grid'
+      if (styleObj['border-collapse'] === 'collapse') {
+        styleObj['border-collapse'] = undefined
+        spacing = 0
+      }
       if (spacing) {
         styleObj['grid-gap'] = spacing + 'px'
         styleObj.padding = spacing + 'px'
@@ -860,6 +891,23 @@ Parser.prototype.popNode = function () {
         for (let i = 0; i < nodes.length; i++) {
           if (nodes[i].name === 'tr') {
             trList.push(nodes[i])
+          } else if (nodes[i].name === 'colgroup') {
+            let colI = 1
+            for (const col of (nodes[i].children || [])) {
+              if (col.name === 'col') {
+                const style = col.attrs.style || ''
+                const start = style.indexOf('width') ? style.indexOf(';width') : 0
+                // 提取出宽度
+                if (start !== -1) {
+                  let end = style.indexOf(';', start + 6)
+                  if (end === -1) {
+                    end = style.length
+                  }
+                  width[colI] = style.substring(start ? start + 7 : 6, end)
+                }
+                colI += 1
+              }
+            }
           } else {
             traversal(nodes[i].children || [])
           }
@@ -986,11 +1034,26 @@ Parser.prototype.popNode = function () {
       node.children = [table]
       attrs = table.attrs
     }
+  } else if ((node.name === 'tbody' || node.name === 'tr') && node.flag && node.c) {
+    node.flag = undefined;
+    (function traversal (nodes) {
+      for (let i = 0; i < nodes.length; i++) {
+        if (nodes[i].name === 'td') {
+          // 颜色样式设置给单元格避免丢失
+          for (const style of ['color', 'background', 'background-color']) {
+            if (styleObj[style]) {
+              nodes[i].attrs.style = style + ':' + styleObj[style] + ';' + (nodes[i].attrs.style || '')
+            }
+          }
+        } else {
+          traversal(nodes[i].children || [])
+        }
+      }
+    })(children)
   } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
     for (let i = this.stack.length; i--;) {
-      if (this.stack[i].name === 'table') {
+      if (this.stack[i].name === 'table' || this.stack[i].name === 'tbody' || this.stack[i].name === 'tr') {
         this.stack[i].flag = 1 // 指示含有合并单元格
-        break
       }
     }
   } else if (node.name === 'ruby') {

+ 7 - 2
uni_modules/uview-plus/components/u-parse/u-parse.vue

@@ -12,7 +12,7 @@
 
 <script>
 /**
- * mp-html v2.4.1
+ * mp-html v2.5.1
  * @description 富文本组件
  * @tutorial https://github.com/jin-yufeng/mp-html
  * @property {String} container-style 容器的样式
@@ -433,7 +433,12 @@ export default {
 						} else if (href.includes('://')) {
 							// 打开外链
 							if (this.copyLink) {
-								plus.runtime.openWeb(href)
+							    // #ifdef APP-PLUS
+                                plus.runtime.openWeb(href)
+                                // #endif
+                                // #ifdef APP-HARMONY
+                                plus.runtime.openURL(href)
+                                // #endif
 							}
 						} else {
 							uni.navigateTo({

+ 128 - 0
uni_modules/uview-plus/components/u-picker-data/u-picker-data.vue

@@ -0,0 +1,128 @@
+<template>
+	<view class="u-picker-data">
+		<view class="u-picker-data__trigger">
+			<slot name="trigger" :current="current"></slot>
+			<up-input
+				v-if="!$slots['trigger']"
+				:modelValue="current"
+				disabled
+				disabledColor="#ffffff"
+				:placeholder="title"
+				border="none"
+			></up-input>
+			<view @click="show = true"
+				class="u-picker-data__trigger__cover"></view>
+		</view>
+		<up-picker
+			:show="show"
+			:columns="optionsInner"
+			:keyName="labelKey"
+			:defaultIndex="defaultIndex"
+			@confirm="select"
+			@cancel="cancel">
+		</up-picker>
+	</view>
+</template>
+
+<script>
+export default {
+    props: {
+		modelValue: {
+			type: [String, Number],
+			default: ''
+		},
+		title: {
+			type: String,
+			default: ''
+		},
+		description: {
+			type: String,
+			default: ''
+		},
+		options: {
+			type: Array,
+			default: () => {
+				return []
+			}
+		},
+		valueKey: {
+			type: String,
+			default: 'id'
+		},
+		labelKey: {
+			type: String,
+			default: 'name'
+		}
+    },
+    data() {
+        return {
+			show: false,
+			current: '',
+			defaultIndex: [],
+        }
+    },
+    created() {
+		if (this.modelValue) {
+			this.options.forEach((ele, index) => {
+				if (ele[this.valueKey] == this.modelValue) {
+					this.current = ele[this.labelKey]
+					this.defaultIndex = [index]
+				}
+			})
+		}
+    },
+	watch: {
+		modelValue() {
+			if (this.modelValue) {
+				this.options.forEach((ele, index) => {
+					if (ele[this.valueKey] == this.modelValue) {
+						this.current = ele[this.labelKey]
+						this.defaultIndex = [index]
+					}
+				})
+			}
+		}
+	},
+	computed: {
+		optionsInner() {
+			return [this.options];
+		}
+	},
+    emits: ['update:modelValue'],
+    methods: {
+        hideKeyboard() {
+            uni.hideKeyboard()
+        },
+		cancel() {
+			this.show = false;
+		},
+        select(e) {
+			const {
+			    columnIndex,
+			    index,
+				value,
+			} = e;
+			this.show = false;
+			// console.log(value);
+            this.$emit('update:modelValue', value[0][this.valueKey]);
+			this.defaultIndex = columnIndex;
+			this.current = value[0][this.labelKey];
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+	.u-picker-data {
+		&__trigger {
+			position: relative;
+			&__cover {
+				position: absolute;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+			}
+		}
+	}
+</style>

+ 6 - 1
uni_modules/uview-plus/components/u-picker/picker.js

@@ -20,12 +20,17 @@ export default {
         cancelText: '取消',
         confirmText: '确定',
         cancelColor: '#909193',
-        confirmColor: '#3c9cff',
+        confirmColor: '',
         visibleItemCount: 5,
         keyName: 'text',
+		valueName: 'value',
         closeOnClickOverlay: false,
         defaultIndex: [],
 		immediateChange: true,
 		zIndex: 10076,
+        disabled: false,
+        disabledColor: '',
+        placeholder: '请选择',
+        inputProps: {},
     }
 }

+ 20 - 1
uni_modules/uview-plus/components/u-picker/props.js

@@ -10,9 +10,23 @@ export const props = defineMixin({
             type: Boolean,
             default: false
         },
+        inputProps: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        disabled: {
+            type: Boolean,
+            default: () => defProps.picker.disabled
+        },
+		disabledColor:{
+			type: String,
+			default: () => defProps.picker.disabledColor
+		},
         placeholder: {
             type: String,
-            default: () => '请选择'
+            default: () => defProps.picker.placeholder
         },
         // 是否展示picker弹窗
         show: {
@@ -79,6 +93,11 @@ export const props = defineMixin({
             type: String,
             default: () => defProps.picker.keyName
         },
+		// 选项对象中,需要获取的属性值键名
+		valueName: {
+		    type: String,
+		    default: () => defProps.picker.valueName
+		},
         // 是否允许点击遮罩关闭选择器
         closeOnClickOverlay: {
             type: Boolean,

+ 153 - 36
uni_modules/uview-plus/components/u-picker/u-picker.vue

@@ -1,12 +1,18 @@
 <template>
-    <view class="u-picker-warrper">
-        <view v-if="hasInput" class="u-picker-input cursor-pointer" @click="showByClickInput = !showByClickInput">
-            <slot>
-                <view>
-					{{ inputLabel && inputLabel.length ? inputLabel.join('/') : placeholder }}
-				</view>
-            </slot>
-        </view>
+    <view class="u-picker-wraper">
+		<view v-if="hasInput" class="u-picker-input cursor-pointer" @click="onShowByClickInput">
+			<slot :value="inputLabel">
+			</slot>
+			<slot name="trigger" :value="inputLabel">
+			</slot>
+			<up-input
+				v-if="!$slots['default'] && !$slots['$default'] && !$slots['trigger']"
+				:readonly="true"
+				v-model="inputLabel"
+				v-bind="inputPropsInner">
+			</up-input>
+			<div class="input-cover"></div>
+		</view>
 		<u-popup
 			:show="show || (hasInput && showByClickInput)"
 			:mode="popupMode"
@@ -114,48 +120,120 @@ export default {
 			// 上一次的变化列索引
 			columnIndex: 0,
             showByClickInput: false,
+			currentActiveValue: [] //当前用户选中,但是还没确认的值,用户没做change操作时候,点击确认可以默认选中第一个
 		}
 	},
 	watch: {
-		// 监听默认索引的变化,重新设置对应的值
-		defaultIndex: {
+		// 监听columns参数的变化
+		columns: {
 			immediate: true,
 			deep:true,
 			handler(n) {
-				this.setIndexs(n, true)
+				this.setColumns(n)
 			}
 		},
-		// 监听columns参数的变化
-		columns: {
+		// 监听默认索引的变化,重新设置对应的值
+		defaultIndex: {
 			immediate: true,
 			deep:true,
-			handler(n) {
-				this.setColumns(n)
+			handler(n,o) {
+				// 修复uniapp调用子组件直接:defaultIndex="[0]"这样写
+				// v-model的值变化时候导致defaultIndexwatch也会执行的问题
+				//单纯vue不会出现
+				if (!o || n.join("/") != o.join("/")) {
+					this.setIndexs(n, true)
+				}
 			}
 		},
+		modelValue: {
+			immediate: true,
+			deep:true,
+			handler(n,o) {
+				// 修复uniapp调用子组件直接:defaultIndex="[0]"这样写
+				// v-model的值变化时候导致defaultIndexwatch也会执行的问题
+				//单纯vue不会出现
+				if (!o || n.join("/") != o.join("/")) {
+					let arr = [];
+					if (n != null) {
+						n.forEach((element, index) => {
+							let currentCols = this.getColumnValues(index)
+							if (currentCols && Object.prototype.toString.call(currentCols) === '[object Object]') {
+								currentCols.forEach((item, index2) => {
+									if (item[this.keyName] == element) {
+										arr.push(index2)
+									}
+								})
+							} else {
+								currentCols.forEach((item, index2) => {
+									if (item == element) {
+										arr.push(index2)
+									}
+								})
+							}
+						});
+						// alert(arr)
+						if (arr.length == 0 && this.defaultIndex) {
+						} else {
+							this.setIndexs(arr, true)
+						}
+					}
+				}
+			}
+		}
 	},
 	emits: ['close', 'cancel', 'confirm', 'change', 'update:modelValue', 'update:show'],
     computed: {
-        inputLabel() {
-            let items = this.innerColumns.map((item, index) => item[this.innerIndex[index]])
-            let res = []
-            items.forEach(element => {
-                res.push(element[this.keyName])
-            });
-            return res
-        },
-        inputValue() {
-            let items = this.innerColumns.map((item, index) => item[this.innerIndex[index]])
-            let res = []
-            items.forEach(element => {
-                res.push(element['id'])
-            });
-            return res
-        }
+		// input的props
+		inputPropsInner() {
+			return {
+				border: this.inputBorder,
+				placeholder: this.placeholder,
+				disabled: this.disabled,
+				disabledColor: this.disabledColor,
+				...this.inputProps
+			}
+		},
+		//已选&&已确认的值显示在input上面的文案
+		inputLabel() {
+			let firstItem = this.innerColumns[0] && this.innerColumns[0][0];
+			// //区分是不是对象数组
+			if (firstItem && Object.prototype.toString.call(firstItem) === '[object Object]') {
+				let res = this.innerColumns[0].filter(item => this.modelValue.includes(item['id']))
+				res = res.map(item => item[this.keyName]);
+				return res.join("/");
+
+			} else {
+				//用户确定的值,才显示到输入框
+				return this.modelValue.join("/");
+			}
+		},
+		//已选,待确认的值
+		inputValue() {
+			let items = this.innerColumns.map((item, index) => item[this.innerIndex[index]])
+			let res = []
+			//区分是不是对象数组
+			if (items[0] && Object.prototype.toString.call(items[0]) === '[object Object]') {
+				//对象数组返回属性值集合
+				items.forEach(element => {
+					res.push(element && element[this.valueName])
+				});
+			} else {
+				//非对象数组返回元素集合
+				items.forEach((element, index) => {
+					res.push(element)
+				});
+			}
+			return res
+		}
     },
 	methods: {
 		addUnit,
 		testArray: test.array,
+		onShowByClickInput(){
+			if(!this.disabled){
+				this.showByClickInput=!this.showByClickInput;
+			}
+		},
 		// 获取item需要显示的文字,判别为对象还是文本
 		getItemText(item) {
 			if (test.object(item)) {
@@ -170,6 +248,7 @@ export default {
                 if (this.hasInput) {
                     this.showByClickInput = false
                 }
+				this.setDefault()
 				this.$emit('update:show', false)
 				this.$emit('close')
 			}
@@ -179,15 +258,37 @@ export default {
             if (this.hasInput) {
                 this.showByClickInput = false
             }
+			this.setDefault()
 			this.$emit('update:show', false)
 			this.$emit('cancel')
 		},
+		setDefault() {
+			let arr = [0]
+			if (this.lastIndex.length == 0) {
+				//如果有默认值&&默认值的数组长度是正确的,就用默认值
+				if (Array.isArray(this.defaultIndex) && this.defaultIndex.length == this.innerColumns.length) {
+					arr = [...this.defaultIndex];
+				} else {
+					//否则默认都选中第一个
+					arr = Array(this.innerColumns.length).fill(0);
+				}
+			} else {
+				arr = deepClone(this.lastIndex)
+			}
+			this.setLastIndex(arr)
+			this.setIndexs(arr)
+		},
 		// 点击工具栏的确定按钮
 		confirm() {
+			// 如果用户有没有触发过change
+			if (!this.currentActiveValue.length) {
+				this.setDefault()
+			}
             this.$emit('update:modelValue', this.inputValue)
             if (this.hasInput) {
                 this.showByClickInput = false
             }
+			this.setLastIndex(this.innerIndex)
 			this.$emit('update:show', false)
 			this.$emit('confirm', {
 				indexs: this.innerIndex,
@@ -202,6 +303,8 @@ export default {
 			} = e.detail
 			let index = 0,
 				columnIndex = 0
+			//记录用户选中但是还没确认的值
+			this.currentActiveValue = value;	
 			// 通过对比前后两次的列索引,得出当前变化的是哪一列
 			for (let i = 0; i < value.length; i++) {
 				let item = value[i]
@@ -216,11 +319,14 @@ export default {
 			this.columnIndex = columnIndex
 			const values = this.innerColumns
 			// 将当前的各项变化索引,设置为"上一次"的索引变化值
-			this.setLastIndex(value)
+			// this.setLastIndex(value)
 			this.setIndexs(value)
-
-            this.$emit('update:modelValue', this.inputValue)
-
+			//如果是非自带输入框才会在change时候触发v-model绑值的变化
+			//否则会非常的奇怪,用户未确认,值就变了
+			// if (!this.hasInput) {
+			// 	this.$emit('update:modelValue', this.inputValue)
+			// }
+			
 			this.$emit('change', {
 				// #ifndef MP-WEIXIN || MP-LARK
 				// 微信小程序不能传递this,会因为循环引用而报错
@@ -303,7 +409,18 @@ export default {
 
 	.u-picker {
 		position: relative;
-
+		&-input {
+			position: relative;
+			.input-cover {
+				opacity: 0;
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				z-index:1;
+			}
+		}
 		&__view {
 
 			&__column {

+ 24 - 1
uni_modules/uview-plus/components/u-popup/u-popup.vue

@@ -1,5 +1,13 @@
 <template>
-	<view class="u-popup"  :class="[customClass]">
+	<view class="u-popup" :class="[customClass]"
+		:style="{width: show == false ? '0px' : '',
+			height: show == false ? '0px' : ''}">
+		<view class="u-popup__trigger">
+			<slot name="trigger">
+			</slot>
+			<view @click="open"
+				class="u-popup__trigger__cover"></view>
+		</view>
 		<u-overlay
 			:show="show"
 			@click="overlayClick"
@@ -43,6 +51,7 @@
 				</view>
 				<u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom>
 			</view>
+			<slot name="bottom"></slot>
 		</u-transition>
 	</view>
 </template>
@@ -188,6 +197,9 @@
 					this.$emit('close')
 				}
 			},
+			open(e) {
+				this.$emit('update:show', true)
+			},
 			close(e) {
 				this.$emit('update:show', false)
 				this.$emit('close')
@@ -240,6 +252,17 @@
 
 	.u-popup {
 		flex: $u-popup-flex;
+		
+		&__trigger {
+			position: relative;
+			&__cover {
+				position: absolute;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+			}
+		}
 
 		&__content {
 			background-color: $u-popup-content-background-color;

+ 133 - 63
uni_modules/uview-plus/components/u-qrcode/qrcode.js

@@ -1036,7 +1036,11 @@ let QRCode = {};
             usingComponents: opt.usingComponents,
             showLoading: opt.showLoading,
             loadingText: opt.loadingText,
+            that: opt.that
         };
+
+        let canvas = null;
+        
         if (typeof opt === 'string') { // 只编码ASCII字符串
             opt = {
                 text: opt
@@ -1083,8 +1087,25 @@ let QRCode = {};
             }
             return options.foreground;
         }
+        
+        let getCanvas = async (id) => {
+            return new Promise((resolve, reject)=>{
+                try {
+                    const query = uni.createSelectorQuery().in(this.options.that);
+                    query.select(`#${id}`)
+                    .fields({ node: true, size: true })
+                    .exec((res) => {
+                        resolve(res[0].node)
+                    })
+                }
+                catch (e) {
+                    console.error("createCanvasContextFail",e)
+                }
+                
+            })
+        }
         // 创建canvas
-        let createCanvas = function (options) {
+        let createCanvas = async function (options) {
             if(options.showLoading){
                 uni.showLoading({
                     title: options.loadingText,
@@ -1094,8 +1115,17 @@ let QRCode = {};
             var ctx = '';
             if (options.nvueContext) {
                 ctx = options.nvueContext;
-            } else {
-                ctx = uni.createCanvasContext(options.canvasId, options.context);
+            }
+            else {
+                // 获取canvas node节点
+                canvas = await getCanvas(options.canvasId);
+                // #ifdef MP
+                // 不清楚是小程序的bug还是什么原因,canvas的node节点宽高和设置的宽高不一致 重新设置下
+                canvas.width = options.size;
+                canvas.height = options.size;
+                // #endif
+
+                ctx = canvas.getContext('2d');
             }
             var count = qrCodeAlg.getModuleCount();
             var ratioSize = options.size;
@@ -1107,14 +1137,18 @@ let QRCode = {};
             for (var row = 0; row < count; row++) {
                 for (var col = 0; col < count; col++) {
                     var w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW));
-                    var h = (Math.ceil((row + 1) * tileW) - Math.floor(row * tileW));
+                    var h = (Math.ceil((row + 1) * tileH) - Math.floor(row * tileH));
                     var foreground = getForeGround({
                         row: row,
                         col: col,
                         count: count,
                         options: options
                     });
-                    ctx.setFillStyle(qrCodeAlg.modules[row][col] ? foreground : options.background);
+                    if (options.nvueContext) {
+                        ctx.setFillStyle(qrCodeAlg.modules[row][col] ? foreground : options.background);
+                    } else {
+                        ctx.fillStyle = qrCodeAlg.modules[row][col] ? foreground : options.background;
+                    }
                     ctx.fillRect(Math.round(col * tileW), Math.round(row * tileH), w, h);
                 }
             }
@@ -1122,18 +1156,47 @@ let QRCode = {};
                 var x = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
                 var y = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
                 drawRoundedRect(ctx, x, y, ratioImgSize, ratioImgSize, 2, 6, true, true)
-                ctx.drawImage(options.image, x, y, ratioImgSize, ratioImgSize);
+                if (options.nvueContext) {
+                    ctx.drawImage(options.image, x, y, ratioImgSize, ratioImgSize);
+                }
+                else {
+                    // #ifdef H5
+                    const img = new Image();
+                    // #endif
+                    // #ifndef H5
+                    const img = canvas.createImage();
+                    // #endif
+                    img.onload = () => {
+                        ctx.drawImage(img, x, y, ratioImgSize, ratioImgSize);
+                    };
+                    img.src = options.image;
+                }
                 // 画圆角矩形
                 function drawRoundedRect(ctxi, x, y, width, height, r, lineWidth, fill, stroke) {
-                    ctxi.setLineWidth(lineWidth);
-                    ctxi.setFillStyle(options.background);
-                    ctxi.setStrokeStyle(options.background);
-                    ctxi.beginPath(); // draw top and top right corner 
+                    if (options.nvueContext) {
+                        ctxi.setLineWidth(lineWidth);
+                        ctxi.setFillStyle(options.background);
+                        ctxi.setStrokeStyle(options.background);
+                    }
+                    else {
+                        ctxi.lineWidth = lineWidth;
+                        ctxi.fillStyle = options.background;
+                        ctxi.strokeStyle = options.background;
+                    }
+                    ctxi.beginPath(); // draw top and top right corner
                     ctxi.moveTo(x + r, y);
-                    ctxi.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner 
-                    ctxi.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner 
-                    ctxi.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner 
-                    ctxi.arcTo(x, y, x + r, y, r);
+                    ctxi.lineTo(x + width, y); // move to top-right corner
+                    ctxi.arc(x + width - r, y + r, r, -Math.PI / 2, 0); // draw right side and bottom right corner
+
+                    ctxi.lineTo(x + width, y + height - r);
+                    ctxi.arc(x + width - r, y + height - r, r, 0, Math.PI / 2); // draw bottom and bottom left corner
+
+                    ctxi.lineTo(x + r, y + height);
+                    ctxi.arc(x + r, y + height - r, r, Math.PI / 2, Math.PI);// draw left and top left corner
+
+                    ctxi.lineTo(x, y + r);
+                    ctxi.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
+
                     ctxi.closePath();
                     if (fill) {
                         ctxi.fill();
@@ -1144,57 +1207,64 @@ let QRCode = {};
                 }
             }
             setTimeout(() => {
-                ctx.draw(true, () => {
-                    // 保存到临时区域
-                    setTimeout(() => {
-                        if (options.nvueContext) {
-                            ctx.toTempFilePath(
-                                0,
-                                0,
-                                options.width,
-                                options.height,
-                                options.width,
-                                options.height,
-                                "",
-                                1,
-                                function(res) {
-                                    if (options.cbResult) {
-                                        options.cbResult(res.tempFilePath)
-                                    }
-                                }
-                            );
-                        } else {
-                            uni.canvasToTempFilePath({
-                                width: options.width,
-                                height: options.height,
-                                destWidth: options.width,
-                                destHeight: options.height,
-                                canvasId: options.canvasId,
-                                quality: Number(1),
-                                success: function (res) {
-                                    if (options.cbResult) {
-                                        // 由于官方还没有统一此接口的输出字段,所以先判定下  支付宝为 res.apFilePath
-                                        if (!empty(res.tempFilePath)) {
-                                            options.cbResult(res.tempFilePath)
-                                        } else if (!empty(res.apFilePath)) {
-                                            options.cbResult(res.apFilePath)
-                                        } else {
+                // canvas2 绘制是自动的不需要手动绘制
+                if(options.nvueContext){
+                    ctx.draw(true, () => {
+                        // 保存到临时区域
+                        setTimeout(() => {
+                            if (options.nvueContext) {
+                                ctx.toTempFilePath(
+                                    0,
+                                    0,
+                                    options.width,
+                                    options.height,
+                                    options.width,
+                                    options.height,
+                                    "",
+                                    1,
+                                    function(res) {
+                                        if (options.cbResult) {
                                             options.cbResult(res.tempFilePath)
                                         }
                                     }
-                                },
-                                fail: function (res) {
-                                    if (options.cbResult) {
-                                        options.cbResult(res)
-                                    }
-                                },
-                                complete: function () {
-                                    uni.hideLoading();
-                                },
-                            }, options.context);
-                        }
-                    }, options.text.length + 100);
-                });
+                                );
+                            } else {
+                                uni.canvasToTempFilePath({
+                                    width: options.width,
+                                    height: options.height,
+                                    destWidth: options.width,
+                                    destHeight: options.height,
+                                    canvasId: options.canvasId,
+                                    quality: Number(1),
+                                    success: function (res) {
+                                        if (options.cbResult) {
+                                            // 由于官方还没有统一此接口的输出字段,所以先判定下  支付宝为 res.apFilePath
+                                            if (!empty(res.tempFilePath)) {
+                                                options.cbResult(res.tempFilePath)
+                                            } else if (!empty(res.apFilePath)) {
+                                                options.cbResult(res.apFilePath)
+                                            } else {
+                                                options.cbResult(res.tempFilePath)
+                                            }
+                                        }
+                                    },
+                                    fail: function (res) {
+                                        if (options.cbResult) {
+                                            options.cbResult(res)
+                                        }
+                                    },
+                                    complete: function () {
+                                        uni.hideLoading();
+                                    },
+                                }, options.context);
+                            }
+                        }, options.text.length + 100);
+                    });
+                }
+                else{
+                    options.cbResult("")
+                }
+
             }, options.usingComponents ? 0 : 150);
         }
         createCanvas(this.options);
@@ -1232,4 +1302,4 @@ let QRCode = {};
     };
 })()
 
-export default QRCode
+export default QRCode

+ 23 - 9
uni_modules/uview-plus/components/u-qrcode/u-qrcode.vue

@@ -2,8 +2,12 @@
 	<view class="u-qrcode" @longpress="longpress">
 		<view class="u-qrcode__content" @click="preview">
 			<!-- #ifndef APP-NVUE -->
-			<canvas class="u-qrcode__canvas"
-				:id="cid" :canvas-id="cid" :style="{ width: size + unit, height: size + unit }" />
+			<canvas
+                class="u-qrcode__canvas"
+				:id="cid"
+                :canvas-id="cid"
+                type="2d"
+                :style="{ width: size + unit, height: size + unit }" />
 			<!-- #endif -->
 			<!-- #ifdef APP-NVUE -->
 			<gcanvas class="u-qrcode__canvas" ref="gcanvess"
@@ -14,11 +18,8 @@
 				:style="{ width: size + unit, height: size + unit }">
 				<up-loading-icon vertical :text="loadingText" textSize="14px"></up-loading-icon>
 			</view>
-			<!-- <image v-show="show" :src="result" :style="{ width: size + unit, height: size + unit }" /> -->
 		</view>
-		<!-- <up-action-sheet :actions="list" cancelText="取消"
-			v-model:show="popupShow" @select="selectClick">
-		</up-action-sheet> -->
+
 	</view>
 </template>
 
@@ -120,7 +121,7 @@ export default {
 			canvasObj: {}
 		}
 	},
-	mounted: function () {
+    mounted(){
 		// #ifdef APP-NVUE
 		/*获取元素引用*/
 		this.ganvas = this.$refs["gcanvess"]
@@ -135,7 +136,9 @@ export default {
 		if (this.loadMake) {
 			if (!this._empty(this.val)) {
 				setTimeout(() => {
-					this._makeCode()
+                    setTimeout(()=>{
+                        this._makeCode()
+                    })
 				}, 0);
 			}
 		}
@@ -162,6 +165,7 @@ export default {
 					correctLevel: that.lv, // 容错级别
 					image: that.icon, // 二维码图标
 					imageSize: that.iconSize,// 二维码图标大小
+                    that,
 					cbResult: function (res) { // 生成二维码的回调
 						that._result(res)
 					},
@@ -250,7 +254,7 @@ export default {
 				rt = false
 			}
 			return rt
-		}
+		},
 	},
 	watch: {
 		size: function (n, o) {
@@ -290,6 +294,16 @@ export default {
 		left: 0;
 		right: 0;
 	}
+
+    /* #ifdef MP-TOUTIAO */
+    /**字节小程序在编译时会出现一个 [hidde]:{ display: none !important; } 这个样式
+     * 会导致canvas 隐藏掉 没有找到具体原因先这样处理
+     */
+    &__canvas {
+        display: block !important;
+    }
+    /* #endif */
+
 	&__content {
 		position: relative;
 

+ 4 - 0
uni_modules/uview-plus/components/u-search/props.js

@@ -129,6 +129,10 @@ export const props = defineMixin({
         autoBlur: {
             type: Boolean,
             default: () => false
+        },
+        iconPosition: {
+            type: String,
+            default: () => defProps.search.iconPosition
         }
     }
 })

+ 1 - 0
uni_modules/uview-plus/components/u-search/search.js

@@ -27,6 +27,7 @@ export default {
         color: '#606266',
         placeholderColor: '#909399',
         searchIcon: 'search',
+        iconPosition: 'left',
         margin: '0',
         animation: false,
         value: '',

+ 11 - 0
uni_modules/uview-plus/components/u-search/u-search.vue

@@ -1,6 +1,7 @@
 <template>
 	<view
 	    class="u-search"
+		:class="[iconPosition === 'right' && 'u-search__reverse']"
 	    @tap="clickHandler"
 	    :style="[{
 			margin: margin,
@@ -45,6 +46,7 @@
 			    class="u-search__content__input"
 			    type="text"
 			    :style="[{
+					pointerEvents: disabled ? 'none' : 'auto',
 					textAlign: inputAlign,
 					color: color,
 					backgroundColor: bgColor,
@@ -99,6 +101,7 @@
 	 * @property {String}			color				输入框字体颜色(默认 '#606266' )
 	 * @property {String}			placeholderColor	placeholder的颜色(默认 '#909399' )
 	 * @property {String}			searchIcon			输入框左边的图标,可以为uView图标名称或图片路径  (默认 'search' )
+	 * @property {String}			iconPosition		输入框图标位置,left-左边, right-右边  (默认 'left' )
 	 * @property {String}			margin				组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px"   (默认 '0' )
 	 * @property {Boolean} 			animation			是否开启动画,见上方说明(默认 false )
 	 * @property {String}			value				输入框初始值
@@ -327,5 +330,13 @@ $u-search-action-margin-left: 5px !default;
 			margin-left: $u-search-action-margin-left;
 		}
 	}
+
+	&__reverse &__content__icon {
+		order: 3;
+	}
+
+	&__reverse &__content__close {
+		order: 2;
+	}
 }
 </style>

+ 218 - 0
uni_modules/uview-plus/components/u-select/u-select.vue

@@ -0,0 +1,218 @@
+<template>
+	<view class="u-select">
+		<view class="u-select__content">
+			<view class="u-select__label" @click="openSelect">
+				<slot name="text" :currentLabel="currentLabel">
+					<text class="u-select__text" v-if="showOptionsLabel">
+						{{ currentLabel }}
+					</text>
+					<text class="u-select__text" v-else>
+						{{ label }}
+					</text>
+				</slot>
+				<slot name="icon">
+					<u-icon name="arrow-down" :size="iconSize" :color="iconColor"></u-icon>
+				</slot>
+			</view>
+			<u-overlay
+				:show="isOpen"
+				@click="overlayClick"
+				v-if="overlay"
+				:zIndex="zIndex"
+				:duration="duration + 50"
+				:customStyle="overlayStyle"
+				:opacity="overlayOpacity"
+				@touchmove.stop.prevent="noop"
+			></u-overlay>
+			<view class="u-select__options__wrap"
+				:style="{ overflowY: 'auto', zIndex: zIndex + 1, left: optionsWrapLeft, right: optionsWrapRight, maxHeight: maxHeight}">
+				<view class="u-select__options" v-if="isOpen">
+					<slot name="options">
+						<view class="u-select__options_item"
+							:class="current == item[keyName] ? 'active': ''"
+							:key="index" v-for="(item, index) in options"
+							@click="selectItem(item)">
+							<slot name="optionItem" :item="item">
+								<text class="u-select__item_text" :style="{color: itemColor}"> 
+									{{item[labelName]}}
+								</text>
+							</slot>
+						</view>
+					</slot>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getWindowInfo } from '../../libs/function/index';
+export default {
+	name:"up-select",
+	emits: ['update:current', 'select'],
+	props: {
+		maxHeight: {
+			type: String,
+			default: '90vh'
+		},
+		overlay: {
+			type: Boolean,
+			default: true
+		},
+		overlayOpacity: {
+			type: Number,
+			default: 0.01
+		},
+		overlayStyle: {
+			type: Object,
+			default: () => {
+				return {}
+			}
+		},
+		duration: {
+			type: Number,
+			default: 300
+		},
+		label: {
+			type: String,
+			default: '选项'
+		},
+		options: {
+			type: Array,
+			default: () => {
+				return []
+			}
+		},
+		keyName: {
+			type: String,
+			default: 'id'
+		},
+		labelName: {
+			type: String,
+			default: 'name'
+		},
+		showOptionsLabel: {
+			type: Boolean,
+			default: false
+		},
+		current: {
+			type: [String, Number],
+			default: ''
+		},
+		zIndex: {
+			type: Number,
+			default: 11000
+		},
+		itemColor: {
+			type: String,
+			default: '#333333'
+		},
+		iconColor: {
+			type: String,
+			default: ''
+		},
+		iconSize: {
+			type: [String],
+			default: '13px'
+		}
+	},
+	data() {
+		return {
+			isOpen: false,
+			optionsWrapLeft: 'auto',
+			optionsWrapRight: 'auto'
+		}
+	},
+	computed: {
+		currentLabel() {
+			let name = '';
+			this.options.forEach((ele) => {
+				if (ele[this.keyName] === this.current) {
+					name = ele[this.labelName];
+				}
+			});
+			return name;
+		}
+    },
+    methods: {
+      openSelect() {
+        this.isOpen = true;
+		this.$nextTick(() => {
+			if (this.isOpen) {
+				this.adjustOptionsWrapPosition();
+			}
+		});
+      },
+	  overlayClick() {
+		  this.isOpen = false;
+	  },
+      selectItem(item) {
+        this.isOpen = false;
+        this.$emit('update:current', item[this.keyName]);
+        this.$emit('select', item);
+      },
+	  adjustOptionsWrapPosition() {
+		let wi = getWindowInfo();
+		let windowWidth = wi.windowWidth;
+		this.$uGetRect('.u-select__options__wrap').then(rect => {
+			console.log(rect)
+			if (rect.left + rect.width > windowWidth) {
+				// 如果右侧被遮挡,则调整到左侧
+				this.optionsWrapLeft = 'auto';
+				this.optionsWrapRight = `0px`;
+			}
+		});
+	  }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+  .u-select__content {
+    position: relative;
+    .u-select__label {
+      display: flex;
+	  justify-content: space-between;
+      /* #ifdef H5 */
+      &:hover {
+        cursor: pointer;
+      }
+      /* #endif */
+    }
+    .u-select__text {
+      margin-right: 2px;
+    }
+	.u-select__options__wrap {
+		margin-bottom: 46px;
+		position: absolute;
+		top: 20px;
+		left: 0;
+	}
+    .u-select__options {
+      min-width: 100px;
+	  box-sizing: border-box;
+      border-radius: 4px;
+      border: 1px solid #f1f1f1;
+      background-color: #fff;
+      .u-select__options_item {
+        padding: 10px 12px;
+		box-sizing: border-box;
+        width: 100%;
+        height: 100%;
+        &:hover {
+          background-color: #f7f7f7;
+        }
+        /* #ifdef H5 */
+        &:hover {
+          cursor: pointer;
+        }
+        .u-select__item_text {
+          &:hover {
+            cursor: pointer;
+          }
+        }
+        /* #endif */
+      }
+    }
+  }
+</style>

+ 14 - 4
uni_modules/uview-plus/components/u-slider/u-slider.vue

@@ -58,7 +58,7 @@
 					<view class="u-slider__button-wrap u-slider__button-wrap-0" @touchstart="onTouchStart($event, 0)"
 						@touchmove="onTouchMove($event, 0)" @touchend="onTouchEnd($event, 0)"
 						@touchcancel="onTouchEnd($event, 0)" :style="{left: (getPx(barStyle0.width) + getPx(blockSize)/2) + 'px'}">
-						<slot v-if="$slots.default  || $slots.$default"/>
+						<slot name="min" v-if="$slots.min || $slots.$min"/>
 						<view v-else class="u-slider__button" :style="[blockStyle, {
 							height: getPx(blockSize, true),
 							width: getPx(blockSize, true),
@@ -69,7 +69,8 @@
 				<view class="u-slider__button-wrap" @touchstart="onTouchStart"
 					@touchmove="onTouchMove" @touchend="onTouchEnd"
 					@touchcancel="onTouchEnd" :style="{left: (getPx(barStyle.width) + getPx(blockSize)/2) + 'px'}">
-					<slot v-if="$slots.default  || $slots.$default"/>
+					<slot name="max" v-if="isRange && ($slots.max || $slots.$max)"/>
+					<slot v-else-if="$slots.default || $slots.$default"/>
 					<view v-else class="u-slider__button" :style="[blockStyle, {
 						height: getPx(blockSize, true),
 						width: getPx(blockSize, true),
@@ -155,8 +156,17 @@
 			value(n) {
 				// 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发
 				if(this.status == 'end') this.updateValue(this.value, false);
-			}
+			},
 			// #endif
+			rangeValue:{
+            	handler(n){
+					if(this.status == 'end'){
+						this.updateValue(this.rangeValue[0], false, 0);
+						this.updateValue(this.rangeValue[1], false, 1);
+					}
+            	},
+            	deep:true
+        	}
 		},
 		created() {
 		},
@@ -377,7 +387,7 @@
 					return this.rangeValue
 				} else {
 					return valueFormat
-				} 
+				}
 			},
 			format(value, index = 1) {
 				// 将小数变成整数,为了减少对视图的更新,造成视图层与逻辑层的阻塞

+ 6 - 1
uni_modules/uview-plus/components/u-status-bar/props.js

@@ -5,6 +5,11 @@ export const props = defineMixin({
         bgColor: {
             type: String,
             default: () => defProps.statusBar.bgColor
-        }
+        },
+		// 状态栏获取得高度
+		height: {
+			type: Number,
+			default: () => defProps.statusBar.height
+		}
     }
 })

+ 2 - 1
uni_modules/uview-plus/components/u-status-bar/statusBar.js

@@ -10,6 +10,7 @@
 export default {
     // statusBar
     statusBar: {
-        bgColor: 'transparent'
+        bgColor: 'transparent',
+		height: 0
     }
 }

+ 9 - 2
uni_modules/uview-plus/components/u-status-bar/u-status-bar.vue

@@ -26,7 +26,7 @@
 		mixins: [mpMixin, mixin, props],
 		data() {
 			return {
-				isH5: true
+				isH5: false
 			}
 		},
 		created() {
@@ -34,11 +34,18 @@
 			this.isH5 = true
 			// #endif
 		},
+		emits: ['update:height'],
 		computed: {
 			style() {
 				const style = {}
 				// 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式
-				style.height = addUnit(getWindowInfo().statusBarHeight, 'px')
+				let sheight = getWindowInfo().statusBarHeight
+				this.$emit('update:height', sheight)
+				if (sheight == 0) {
+					this.isH5 = true
+				} else {
+					style.height = addUnit(sheight, 'px')
+				}
 				style.backgroundColor = this.bgColor
 				return deepMerge(style, addStyle(this.customStyle))
 			}

+ 17 - 7
uni_modules/uview-plus/components/u-steps-item/u-steps-item.vue

@@ -30,13 +30,24 @@
 				</view>
 			</slot>
 		</view>
-		<view class="u-steps-item__content" :class="[`u-steps-item__content--${parentData.direction}`]"
+		<view class="u-steps-item__content" :class="[`u-steps-item__content--${parentData.direction}`,
+			parentData.current == index ? 'u-steps-item__content--current' : '']"
 			:style="[contentStyle]">
-			<up-text :text="title" :type="parentData.current == index ? 'main' : 'content'" lineHeight="20px"
-				:size="parentData.current == index ? 14 : 13"></up-text>
-			<slot name="desc">
-				<up-text :text="desc" type="tips" size="12"></up-text>
+			<slot name="content" :index="index">
 			</slot>
+			<template v-if="!$slots['content']">
+				<view class="u-steps-item__content__title">
+					<slot name="title">
+					</slot>
+					<up-text v-if="!$slots['title']" :text="title" :type="parentData.current == index ? 'main' : 'content'" lineHeight="20px"
+						:size="parentData.current == index ? 14 : 13"></up-text>
+				</view>
+				<view class="u-steps-item__content__desc">
+					<slot name="desc">
+					</slot>
+					<up-text v-if="!$slots['desc']" :text="desc" type="tips" size="12"></up-text>
+				</view>
+			</template>
 		</view>
 		<!-- <view
 		    class="u-steps-item__line"
@@ -108,8 +119,7 @@
 					style.width = this.size.width + 'px'
 					style.left = this.size.width / 2 + 'px'
 				} else {
-					style.height = '100%';
-					// style.height = this.size.height + 'px'
+					style.height = this.size.height + 'px'
 					// style.top = this.size.height / 2 + 'px'
 				}
 				style.backgroundColor = this.parent.children?.[this.index + 1]?.error ? color.error : this.index <

+ 6 - 1
uni_modules/uview-plus/components/u-steps/u-steps.vue

@@ -21,7 +21,7 @@
 	 * @property {String}			activeColor		激活状态颜色 (默认 '#3c9cff' )
 	 * @property {String}			inactiveColor	未激活状态颜色 (默认 '#969799' )
 	 * @property {String}			activeIcon		激活状态的图标
-	 * @property {String}			inactiveIcon	未激活状态图标 
+	 * @property {String}			inactiveIcon	未激活状态图标
 	 * @property {Boolean}			dot				是否显示点类型 (默认 false )
 	 * @example <u-steps current="0"><u-steps-item title="已出库" desc="10:35" ></u-steps-item></u-steps>
 	 */
@@ -85,6 +85,11 @@
 			display: grid;
 			grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
 			/* #endif */
+
+            //微信小程序优化的比抖音的好 因此微信小程序使用flex布局
+            /* #ifdef MP-WEIXIN */
+            display: flex !important;
+            /* #endif */
 		}
 	}
 </style>

+ 7 - 2
uni_modules/uview-plus/components/u-sticky/u-sticky.vue

@@ -1,10 +1,10 @@
 <template>
 	<view
 		class="u-sticky"
-		:id="elId"
 		:style="[style]"
 	>
 		<view
+		:id="elId"
 			:style="[stickyContent]"
 			class="u-sticky__content"
 		>
@@ -90,6 +90,11 @@
 		mounted() {
 			this.init()
 		},
+		watch: {
+			offsetTop(nval) {
+				this.getStickyTop()
+			}
+		},
 		methods: {
 			init() {
 				this.getStickyTop()
@@ -114,7 +119,7 @@
 			observeContent() {
 				// 先断掉之前的观察
 				this.disconnectObserver('contentObserver')
-				const contentObserver = uni.createIntersectionObserver({
+				const contentObserver = uni.createIntersectionObserver(this,{
 					// 检测的区间范围
 					thresholds: [0.95, 0.98, 1]
 				})

+ 3 - 2
uni_modules/uview-plus/components/u-subsection/u-subsection.vue

@@ -189,7 +189,7 @@ export default {
     beforeUnmount() {
         uni.offWindowResize(this.windowResizeCallback)
     },
-	emits: ["change"],
+	emits: ["change", "update:current"],
     methods: {
         addStyle,
         init() {
@@ -217,7 +217,8 @@ export default {
             // #endif
         },
         clickHandler(index) {
-            this.innerCurrent = index
+            this.innerCurrent = index;
+			this.$emit('update:current', index);
             this.$emit("change", index);
         },
     },

+ 1 - 1
uni_modules/uview-plus/components/u-swiper/u-swiper.vue

@@ -30,7 +30,7 @@
 			:previousMargin="addUnit(previousMargin)"
 			:nextMargin="addUnit(nextMargin)"
 			:acceleration="acceleration"
-			:displayMultipleItems="displayMultipleItems"
+			:displayMultipleItems="list.length > 0 ? displayMultipleItems : 0"
 			:easingFunction="easingFunction"
 		>
 			<swiper-item

+ 77 - 3
uni_modules/uview-plus/components/u-table/u-table.vue

@@ -1,6 +1,8 @@
 <template>
-	<view class="u-table">
-		
+	<view class="u-table" :style="[tableStyle]">
+		<template v-if="show">
+			<slot />
+		</template>
 	</view>
 </template>
 
@@ -12,14 +14,86 @@
 	 * Table 表格 
 	 * @description 表格组件一般用于展示大量结构化数据的场景 本组件标签类似HTML的table表格,由table、tr、th、td四个组件组成
 	 * @tutorial https://ijry.github.io/uview-plus/components/table.html
+	 * @property {String} border-color 表格边框的颜色(默认#e4e7ed)
+	 * @property {String} bg-color 表格的背景颜色(默认#ffffff)
+	 * @property {String} align 单元格的内容对齐方式,作用类似css的text-align(默认center)
+	 * @property {String} padding 单元格的内边距,同css的padding写法(默认10rpx 0)
+	 * @property {String Number} font-size 单元格字体大小,单位rpx(默认28)
+	 * @property {String} color 单元格字体颜色(默认#606266)
+	 * @property {Object} th-style th单元格的样式,对象形式(将th所需参数放在table组件,是为了避免每一个th组件要写一遍)
+	 * @event {Function} click 点击组件时触发
+	 * @event {Function} close 点击关闭按钮时触发
 	 * @example <u-table><u-tr><u-th>学校</u-th </u-tr> <u-tr><u-td>浙江大学</u-td> </u-tr> <u-tr><u-td>清华大学</u-td> </u-tr></u-table>
 	 */
 	export default {
 		name: 'u-table',
 		mixins: [mpMixin, mixin, props],
+		props: {
+			borderColor: {
+				type: String,
+				default: '#e4e7ed'
+			},
+			align: {
+				type: String,
+				default: 'center'
+			},
+			// td的内边距
+			padding: {
+				type: String,
+				default: '5px 3px'
+			},
+			// 字体大小
+			fontSize: {
+				type: [String],
+				default: '14px'
+			},
+			// 字体颜色
+			color: {
+				type: String,
+				default: '#606266'
+			},
+			// th的自定义样式
+			thStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// table的背景颜色
+			bgColor: {
+				type: String,
+				default: '#ffffff'
+			}
+		},
 		data() {
 			return {
-				
+				show: true
+			}
+		},
+		watch: {
+			align() {
+				this.change();
+			},
+			borderColor() {
+				this.change();
+			}
+		},
+		computed: {
+			tableStyle() {
+				let style = {};
+				style.borderLeft = `solid 1px ${this.borderColor}`;
+				style.borderTop = `solid 1px ${this.borderColor}`;
+				style.backgroundColor = this.bgColor;;
+				return style;
+			}
+		},
+		methods: {
+			change() {
+				this.show = false;
+				this.$nextTick(() => {
+					this.show = true;
+				});
+				// this.$forceUpdate();
 			}
 		}
 	}

+ 515 - 0
uni_modules/uview-plus/components/u-table2/u-table2.vue

@@ -0,0 +1,515 @@
+<template>
+    <view scroll-x class="u-table2" :style="{ height: height ? height + 'px' : 'auto' }">
+        <!-- 表头 -->
+        <view v-if="showHeader" class="u-table-header" :class="{ 'u-table-sticky': fixedHeader }" :style="{minWidth: scrollWidth}">
+            <view class="u-table-row">
+                <view v-for="(col, colIndex) in columns" :key="col.key" class="u-table-cell"
+                    :style="headerColStyle(col)"
+					:class="[
+                        col.align ? 'u-text-' + col.align : '',
+                        headerCellClassName ? headerCellClassName(col) : '',
+                        col.fixed === 'left' ? 'u-table-fixed-left' : '',
+                        col.fixed === 'right' ? 'u-table-fixed-right' : ''
+                    ]" @click="handleHeaderClick(col)">
+                    {{ col.title }}
+                    <view v-if="col.sortable">
+                        {{ getSortIcon(col.key) }}
+                    </view>
+                </view>
+            </view>
+        </view>
+
+        <!-- 表体 -->
+        <view class="u-table-body" :style="{ minWidth: scrollWidth, maxHeight: maxHeight ? maxHeight + 'px' : 'none' }">
+            <template v-if="data && data.length > 0">
+                <template v-for="(row, index) in sortedData" :key="row[rowKey] || index">
+                    <view class="u-table-row" :class="[
+                        highlightCurrentRow && currentRow === row ? 'u-table-row-highlight' : '',
+                        rowClassName ? rowClassName(row, index) : '',
+                        stripe && index % 2 === 1 ? 'u-table-row-zebra' : ''
+                    ]" @click="handleRowClick(row)">
+                        <view v-for="(col, colIndex) in columns" :key="col.key"
+							class="u-table-cell" :class="[
+                            col.align ? 'u-text-' + col.align : '',
+                            cellClassName ? cellClassName(row, col) : '',
+                            col.fixed === 'left' ? 'u-table-fixed-left' : '',
+                            col.fixed === 'right' ? 'u-table-fixed-right' : ''
+                        ]" :style="cellStyleInner({row: row, column: col,
+							rowIndex: index, columnIndex: colIndex, level: 0})">
+                            <!-- 复选框列 -->
+                            <view v-if="col.type === 'selection'">
+                                <checkbox :checked="isSelected(row)"
+									@click.stop="toggleSelect(row)" />
+                            </view>
+
+                            <!-- 树形结构展开图标 -->
+                            <view v-else-if="col.type === 'expand'"
+								@click.stop="toggleExpand(row)">
+                                {{ isExpanded(row) ? '▼' : '▶' }}
+                            </view>
+
+                            <!-- 默认插槽或文本 -->
+                            <slot name="cell" :row="row" :column="col"
+								:rowIndex="index" :columnIndex="colIndex">
+                                <view class="u-table-cell_content">
+                                    {{ row[col.key] }}
+                                </view>
+                            </slot>
+                        </view>
+                    </view>
+
+                    <!-- 子级渲染 -->
+                    <template v-if="isExpanded(row) && row[treeProps.children] && row[treeProps.children].length">
+                        <view v-for="childRow in row[treeProps.children]" :key="childRow[rowKey]"
+                            class="u-table-row u-table-row-child">
+                            <view v-for="(col2, col2Index) in columns" :key="col2.key" class="u-table-cell"
+                                :style="cellStyleInner({row: childRow, column: col2,
+									rowIndex: index, columnIndex: col2Index, level: 1})">
+                                <slot name="cell" :row="childRow" :column="col2" :prow="row"
+									:rowIndex="index" :columnIndex="col2Index" :level="1">
+                                    <view class="u-table-cell_content">
+                                        {{ childRow[col2.key] }}
+                                    </view>
+                                </slot>
+                            </view>
+                        </view>
+                    </template>
+                </template>
+            </template>
+            <template v-else>
+                <slot name="empty">
+                    <view class="u-table-empty">{{ emptyText }}</view>
+                </slot>
+            </template>
+        </view>
+    </view>
+</template>
+
+<script>
+import { ref, watch, computed } from 'vue'
+import { addUnit, sleep } from '../../libs/function/index';
+
+export default {
+    name: 'u-table2',
+    props: {
+        data: {
+            type: Array,
+            required: true,
+            default: () => {
+                return []
+            }
+        },
+        columns: {
+            type: Array,
+            required: true,
+            default: () => {
+                return []
+            },
+            validator: cols =>
+                cols.every(col =>
+                    ['default', 'selection', 'expand'].includes(col.type || 'default')
+                )
+        },
+        stripe: {
+            type: Boolean,
+            default: false
+        },
+        border: {
+            type: Boolean,
+            default: false
+        },
+        height: {
+            type: [String, Number],
+            default: null
+        },
+        maxHeight: {
+            type: [String, Number],
+            default: null
+        },
+        showHeader: {
+            type: Boolean,
+            default: true
+        },
+        highlightCurrentRow: {
+            type: Boolean,
+            default: false
+        },
+        rowKey: {
+            type: String,
+            default: 'id'
+        },
+        currentRowKey: {
+            type: [String, Number],
+            default: null
+        },
+        rowStyle: {
+            type: Object,
+            default: () => ({})
+        },
+        cellClassName: {
+            type: Function,
+            default: null
+        },
+		cellStyle: {
+		    type: Function,
+		    default: null
+		},
+        headerCellClassName: {
+            type: Function,
+            default: null
+        },
+        rowClassName: {
+            type: Function,
+            default: null
+        },
+        context: {
+            type: Object,
+            default: null
+        },
+        showOverflowTooltip: {
+            type: Boolean,
+            default: false
+        },
+        lazy: {
+            type: Boolean,
+            default: false
+        },
+        load: {
+            type: Function,
+            default: null
+        },
+        treeProps: {
+            type: Object,
+            default: () => ({
+                children: 'children',
+                hasChildren: 'hasChildren'
+            })
+        },
+        defaultExpandAll: {
+            type: Boolean,
+            default: false
+        },
+        expandRowKeys: {
+            type: Array,
+            default: () => []
+        },
+        sortOrders: {
+            type: Array,
+            default: () => ['ascending', 'descending']
+        },
+        sortable: {
+            type: [Boolean, String],
+            default: false
+        },
+        multiSort: {
+            type: Boolean,
+            default: false
+        },
+        sortBy: {
+            type: String,
+            default: null
+        },
+        sortMethod: {
+            type: Function,
+            default: null
+        },
+        filters: {
+            type: Object,
+            default: () => ({})
+        },
+        fixedHeader: {
+            type: Boolean,
+            default: true
+        },
+        emptyText: {
+            type: String,
+            default: '暂无数据'
+        },
+    },
+    emits: [
+        'select', 'select-all', 'selection-change',
+        'cell-click', 'row-click', 'row-dblclick',
+        'header-click', 'sort-change', 'filter-change',
+        'current-change', 'expand-change'
+    ],
+    data() {
+        return {
+            scrollWidth: 'auto'
+        }
+    },
+    mounted() {
+        this.getComponentWidth()
+    },
+	computed: {
+	},
+    methods: {
+        addUnit,
+		headerColStyle(col) {
+			let style = {
+				width: col.width ? addUnit(col.width) : 'auto',
+				flex: col.width ? 'none' : 1
+			};
+			if (col?.style) {
+				style = {...style, ...col?.style};
+			}
+			return style;
+		},
+		setCellStyle(e) {
+			this.cellStyle = e
+		},
+		cellStyleInner(scope) {
+			let style = {
+				width: scope.column?.width ? addUnit(scope.column.width) : 'auto',
+				flex: scope.column?.width ? 'none' : 1,
+				paddingLeft: (24 * scope.level) + 'px'
+			};
+			if (this.cellStyle != null) {
+				let styleCalc = this.cellStyle(scope)
+				if (styleCalc != null) {
+					style = {...style, ...styleCalc}
+				}
+			}
+			return style;
+		},
+        // 获取组件的宽度
+		async getComponentWidth() {
+			// 延时一定时间,以获取dom尺寸
+			await sleep(30)
+			this.$uGetRect('.u-table-row').then(size => {
+				this.scrollWidth = size.width + 'px'
+			})
+		},
+    },
+    setup(props, { emit }) {
+        const expandedKeys = ref([...props.expandRowKeys]);
+        const selectedRows = ref([]);
+        const sortConditions = ref([]);
+
+        // 当前高亮行
+        const currentRow = ref(null);
+
+        watch(
+            () => props.expandRowKeys,
+            newVal => {
+                expandedKeys.value = [...newVal];
+            }
+        );
+
+        watch(
+            () => props.currentRowKey,
+            newVal => {
+                const found = props.data.find(item => item[props.rowKey] === newVal);
+                if (found) {
+                    currentRow.value = found;
+                }
+            }
+        );
+
+        // 过滤后的数据
+        const filteredData = computed(() => {
+            return props.data.filter(row => {
+                return Object.keys(props.filters).every(key => {
+                    const filter = props.filters[key];
+                    if (!filter) return true;
+                    return row[key]?.toString().includes(filter.toString());
+                });
+            });
+        });
+
+        // 排序后的数据
+        const sortedData = computed(() => {
+            if (!sortConditions.value.length) return filteredData.value;
+
+            const data = [...filteredData.value];
+
+            return data.sort((a, b) => {
+                for (const condition of sortConditions.value) {
+                    const { field, order } = condition;
+                    let valA = a[field];
+                    let valB = b[field];
+
+                    if (props.sortMethod) {
+                        const result = props.sortMethod(a, b, field);
+                        if (result !== 0) return result * (order === 'ascending' ? 1 : -1);
+                    }
+
+                    if (valA < valB) return order === 'ascending' ? -1 : 1;
+                    if (valA > valB) return order === 'ascending' ? 1 : -1;
+                }
+                return 0;
+            });
+        });
+
+        function handleRowClick(row) {
+            if (props.highlightCurrentRow) {
+                const oldRow = currentRow.value;
+                currentRow.value = row;
+                emit('current-change', row, oldRow);
+            }
+            emit('row-click', row);
+        }
+
+        function handleHeaderClick(column) {
+            if (!column.sortable) return;
+
+            const index = sortConditions.value.findIndex(c => c.field === column.key);
+            let newOrder = 'ascending';
+
+            if (index >= 0) {
+                if (sortConditions.value[index].order === 'ascending') {
+                    newOrder = 'descending';
+                } else {
+                    sortConditions.value.splice(index, 1);
+                    emit('sort-change', sortConditions.value);
+                    return;
+                }
+            }
+
+            if (!props.multiSort) {
+                sortConditions.value = [{ field: column.key, order: newOrder }];
+            } else {
+                if (index >= 0) {
+                    sortConditions.value[index].order = newOrder;
+                } else {
+                    sortConditions.value.push({ field: column.key, order: newOrder });
+                }
+            }
+
+            emit('sort-change', sortConditions.value);
+        }
+
+        function getSortIcon(field) {
+            const cond = sortConditions.value.find(c => c.field === field);
+            if (!cond) return '';
+            return cond.order === 'ascending' ? '↑' : '↓';
+        }
+
+        function toggleSelect(row) {
+            const index = selectedRows.value.findIndex(r => r[props.rowKey] === row[props.rowKey]);
+            if (index >= 0) {
+                selectedRows.value.splice(index, 1);
+            } else {
+                selectedRows.value.push(row);
+            }
+            emit('selection-change', selectedRows.value);
+            emit('select', row);
+        }
+
+        function isSelected(row) {
+            return selectedRows.value.some(r => r[props.rowKey] === row[props.rowKey]);
+        }
+
+        function toggleExpand(row) {
+            const key = row[props.rowKey];
+            const index = expandedKeys.value.indexOf(key);
+            if (index === -1) {
+                expandedKeys.value.push(key);
+            } else {
+                expandedKeys.value.splice(index, 1);
+            }
+            emit('expand-change', expandedKeys.value);
+        }
+
+        function isExpanded(row) {
+            return expandedKeys.value.includes(row[props.rowKey]);
+        }
+
+        return {
+            currentRow,
+            sortedData,
+            expandedKeys,
+            selectedRows,
+            sortConditions,
+            handleRowClick,
+            handleHeaderClick,
+            getSortIcon,
+            toggleSelect,
+            isSelected,
+            toggleExpand,
+            isExpanded
+        };
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.u-table2 {
+    width: auto;
+    overflow: auto;
+    white-space: nowrap;
+
+    .u-table-header {
+        min-width: 100% !important;
+        width: fit-content;
+        background-color: #f5f7fa;
+    }
+
+    .u-table-body {
+        min-width: 100% !important;
+        width: fit-content;
+    }
+
+    .u-table-sticky {
+        position: sticky;
+        top: 0;
+        z-index: 10;
+    }
+
+    .u-table-row {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        border-bottom: 1rpx solid #ebeef5;
+        overflow: hidden;
+    }
+
+    .u-table-cell {
+        flex: 1;
+        display: flex;
+        flex-direction: row;
+        padding: 5px 4px;
+        font-size: 14px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    .u-table-fixed-left {
+        position: sticky;
+        left: 0;
+        z-index: 9;
+    }
+
+    .u-table-fixed-right {
+        position: sticky;
+        right: 0;
+        z-index: 9;
+    }
+
+    .u-table-row-zebra {
+        background-color: #fafafa;
+    }
+
+    .u-table-row-highlight {
+        background-color: #f5f7fa;
+    }
+
+    .u-table-empty {
+        text-align: center;
+        padding: 20px;
+        color: #999;
+    }
+
+    .u-text-left {
+        text-align: left;
+    }
+
+    .u-text-center {
+        text-align: center;
+    }
+
+    .u-text-right {
+        text-align: right;
+    }
+}
+</style>

+ 6 - 1
uni_modules/uview-plus/components/u-tabs/props.js

@@ -61,6 +61,11 @@ export const props = defineMixin({
 		keyName: {
 			type: String,
 			default: () => defProps.tabs.keyName
-		}
+		},
+        // 左侧图标样式
+        iconStyle: {
+            type: [String, Object],
+            default: () => defProps.tabs.iconStyle
+        }
     }
 })

+ 2 - 1
uni_modules/uview-plus/components/u-tabs/tabs.js

@@ -27,6 +27,7 @@ export default {
         },
         scrollable: true,
 		current: 0,
-		keyName: 'name'
+		keyName: 'name',
+        iconStyle: {}
     }
 }

+ 18 - 0
uni_modules/uview-plus/components/u-tabs/u-tabs.vue

@@ -27,6 +27,15 @@
 								item.disabled && 'u-tabs__wrapper__nav__item--disabled',
 								innerCurrent == index ? 'u-tabs__wrapper__nav__item-active' : '']"
 						>
+							<slot v-if="$slots.icon" name="icon" :item="item" :keyName="keyName" :index="index" />
+							<template v-else>
+								<view class="u-tabs__wrapper__nav__item__prefix-icon" v-if="item.icon">
+									<up-icon
+										:name="item.icon"
+										:customStyle="addStyle(iconStyle)"
+									></up-icon>
+								</view>
+							</template>
 							<slot v-if="$slots.content" name="content" :item="item" :keyName="keyName" :index="index" />
 							<slot v-else-if="!$slots.content && ($slots.default || $slots.$default)"
 								:item="item" :keyName="keyName" :index="index" />
@@ -169,7 +178,14 @@
 		},
 		async mounted() {
 			this.init()
+            this.windowResizeCallback = (res) => {
+                this.init()
+            }
+            uni.onWindowResize(this.windowResizeCallback)
 		},
+        beforeUnmount() {
+            uni.offWindowResize(this.windowResizeCallback)
+        },
 		emits: ['click', 'longPress', 'change', 'update:current'],
 		methods: {
 			addStyle,
@@ -220,6 +236,8 @@
 				}, index)
 				// 如果disabled状态,返回
 				if (item.disabled) return
+				// 如果点击当前不触发change
+				if (this.innerCurrent == index) return
 				this.innerCurrent = index
 				this.resize()
 				this.$emit('update:current', index)

+ 29 - 3
uni_modules/uview-plus/components/u-tag/props.js

@@ -82,9 +82,35 @@ export const props = defineMixin({
             type: String,
             default: () => defProps.tag.icon,
 		},
-		iconColor: {
+        // 图标颜色
+        iconColor: {
             type: String,
-            default: () => defProps.tag.iconColor
-        }
+            default: () => defProps.tag.iconColor,
+		},
+        // 自定义尺寸字体大小
+		textSize: {
+            type: String,
+            default: () => defProps.tag.textSize
+        },
+        // 自定义尺寸高度
+        height: {
+            type: String,
+            default: () => defProps.tag.height
+        },
+        // 自定义尺寸padding
+        padding: {
+            type: String,
+            default: () => defProps.tag.padding
+        },
+        // 自定义尺寸
+        borderRadius: {
+            type: String,
+            default: () => defProps.tag.borderRadius
+        },
+        // 自动计算背景色
+        autoBgColor: {
+            type: Number,
+            default: () => defProps.tag.autoBgColor
+        },
     }
 })

+ 6 - 1
uni_modules/uview-plus/components/u-tag/tag.js

@@ -25,6 +25,11 @@ export default {
 		closable: false,
 		show: true,
 		icon: '',
-		iconColor: ''
+		iconColor: '',
+		textSize: '',
+		height: '',
+		padding: '',
+		borderRadius: '',
+		autoBgColor: 0
 	}
 }

+ 43 - 8
uni_modules/uview-plus/components/u-tag/u-tag.vue

@@ -32,15 +32,29 @@
 						></u-icon>
 					</view>
 				</slot>
-				<text
-					class="u-tag__text"
-					:style="[textColor]"
-					:class="[`u-tag__text--${type}`, plain && `u-tag__text--${type}--plain`, `u-tag__text--${size}`]"
-				>
-					<slot>
-						{{ text }}
+				<view class="u-tag__content">
+					<slot name="content">
 					</slot>
-				</text>
+					<template v-if="!$slots.content">
+						<text
+							v-if="!$slots.default && !$slots.$default"
+							class="u-tag__text"
+							:style="[textColor]"
+							:class="[`u-tag__text--${type}`, plain && `u-tag__text--${type}--plain`, `u-tag__text--${size}`]"
+						>
+							{{ text }}
+						</text>
+						<text
+							v-else
+							class="u-tag__text"
+							:style="[textColor]"
+							:class="[`u-tag__text--${type}`, plain && `u-tag__text--${type}--plain`, `u-tag__text--${size}`]"
+						>
+							<slot>
+							</slot>
+						</text>
+					</template>
+				</view>
 			</view>
 			<view
 				class="u-tag__close"
@@ -64,6 +78,7 @@
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
 	import test from '../../libs/function/test';
+	import { addUnit, genLightColor } from '../../libs/function/index';
 	/**
 	 * Tag 标签
 	 * @description tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景
@@ -107,6 +122,19 @@
 				if(this.borderColor) {
 					style.borderColor = this.borderColor
 				}
+				if (this.height) {
+					style.height = addUnit(this.height)
+					style.lineHeight = addUnit(this.height)
+				}
+				if (this.padding) {
+					style.padding = this.padding
+				}
+				if (this.borderRadius) {
+					style.borderRadius = addUnit(this.borderRadius)
+				}
+				if (this.autoBgColor > 0 && this.color) {
+					style.backgroundColor = this.getBagColor(this.color)
+				}
 				return style
 			},
 			// nvue下,文本颜色无法继承父元素
@@ -115,6 +143,9 @@
 				if (this.color) {
 					style.color = this.color
 				}
+				if (this.textSize) {
+					style.textSize = addUnit(this.textSize)
+				}
 				return style
 			},
 			imgStyle() {
@@ -149,6 +180,10 @@
 			// 点击标签
 			clickHandler() {
 				this.$emit('click', this.name)
+			},
+			// 根据颜色计算浅色作为背景
+			getBagColor(darkColor) {
+				return genLightColor(darkColor, this.autoBgColor)
 			}
 		}
 	}

+ 42 - 4
uni_modules/uview-plus/components/u-td/u-td.vue

@@ -1,6 +1,6 @@
 <template>
-	<view class="u-td">
-		
+	<view class="u-td" :style="[tdStyle]">
+		<slot></slot>
 	</view>
 </template>
 
@@ -8,6 +8,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
+	import { addUnit, $parent } from '../../libs/function/index';
 	/** 
 	 * Td 表格中的单元格
 	 * @description 
@@ -19,9 +20,36 @@
 	export default {
 		name: 'u-td',
 		mixins: [mpMixin, mixin, props],
+		props: {
+			// 宽度,百分比或者具体带单位的值,如30%, 200rpx等,一般使用百分比
+			width: {
+				type: [String],
+				default: 'auto'
+			}
+		},
 		data() {
 			return {
-				
+				tdStyle: {
+					
+				}
+			}
+		},
+		created() {
+			this.parent = false;
+		},
+		mounted() {
+			this.parent = $parent.call(this, 'u-table');
+			if (this.parent) {
+				// 将父组件的相关参数,合并到本组件
+				let style = {};
+				if (this.width != "auto") style.flex = `0 0 ${this.width}`;
+				style.textAlign = this.parent.align;
+				style.fontSize = addUnit(this.parent.fontSize);
+				style.padding = this.parent.padding;
+				style.borderBottom = `solid 1px ${this.parent.borderColor}`;
+				style.borderRight = `solid 1px ${this.parent.borderColor}`;
+				style.color = this.parent.color;
+				this.tdStyle = style;
 			}
 		}
 	}
@@ -29,5 +57,15 @@
 
 <style lang="scss" scoped>
 	@import "../../libs/css/components.scss";
-	
+	.u-td {
+		@include flex;
+		flex-direction: column;
+		flex: 1;
+		justify-content: center;
+		font-size: 14px;
+		color: $u-content-color;
+		align-self: stretch;
+		box-sizing: border-box;
+		height: 100%;
+	}
 </style>

+ 11 - 2
uni_modules/uview-plus/components/u-textarea/u-textarea.vue

@@ -3,9 +3,9 @@
         <textarea
             class="u-textarea__field"
             :value="innerValue"
-            :style="{ height: addUnit(height) }"
+            :style="fieldStyle"
             :placeholder="placeholder"
-            :placeholder-style="addStyle(placeholderStyle, 'string')"
+            :placeholder-style="addStyle(placeholderStyle, typeof placeholderStyle === 'string' ? 'string' : 'object')"
             :placeholder-class="placeholderClass"
             :disabled="disabled"
             :focus="focus"
@@ -146,6 +146,15 @@ export default {
         // #endif
 	},
     computed: {
+		fieldStyle() {
+			let style = {};
+			style['height'] = addUnit(this.height);
+			if (this.autoHeight) {
+				style['height'] = 'auto';
+				style['minHeight'] = addUnit(this.height);
+			}
+			return style;
+		},
         // 组件的类名
         textareaClass() {
             let classes = [],

+ 7 - 0
uni_modules/uview-plus/components/u-th/props.js

@@ -0,0 +1,7 @@
+import { defineMixin } from '../../libs/vue'
+import defProps from '../../libs/config/props.js'
+export const props = defineMixin({
+    props: {
+
+    }
+})

+ 67 - 0
uni_modules/uview-plus/components/u-th/u-th.vue

@@ -0,0 +1,67 @@
+<template>
+	<view class="u-th" :style="[thStyle]">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	import { props } from './props';
+	import { mpMixin } from '../../libs/mixin/mpMixin';
+	import { mixin } from '../../libs/mixin/mixin';
+	import { addUnit, $parent } from '../../libs/function/index';
+	/** 
+	 * Td 表格中的单元格
+	 * @description 
+	 * @tutorial url
+	 * @property {String | Number} 
+	 * @event {Function}
+	 * @example
+	 */
+	export default {
+		name: 'u-th',
+		mixins: [mpMixin, mixin, props],
+		props: {
+			// 宽度,百分比或者具体带单位的值,如30%, 200rpx等,一般使用百分比
+			width: {
+				type: [String],
+				default: ''
+			}
+		},
+		data() {
+			return {
+				thStyle: {}
+			}
+		},
+		created() {
+			this.parent = false;
+		},
+		mounted() {
+			this.parent = $parent.call(this, 'u-table');
+			if (this.parent) {
+				// 将父组件的相关参数,合并到本组件
+				let style = {};
+				if (this.width) style.flex = `0 0 ${this.width}`;
+				style.textAlign = this.parent.align;
+				style.padding = this.parent.padding;
+				style.borderBottom = `solid 1px ${this.parent.borderColor}`;
+				style.borderRight = `solid 1px ${this.parent.borderColor}`;
+				Object.assign(style, this.parent.thStyle);
+				this.thStyle = style;
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	.u-th {
+		@include flex;
+		flex-direction: column;
+		flex: 1;
+		justify-content: center;
+		font-size: 28rpx;
+		color: $u-main-color;
+		font-weight: bold;
+		background-color: rgb(245, 246, 248);
+	}
+</style>

+ 1 - 1
uni_modules/uview-plus/components/u-toast/toast.js

@@ -12,7 +12,7 @@ export default {
     toast: {
         zIndex: 10090,
         loading: false,
-        text: '',
+        message: '',
         icon: '',
         type: '',
         loadingMode: '',

Some files were not shown because too many files changed in this diff