Przeglądaj źródła

Merge branch 'fanghuisheng' of uskycloud/usky-web-mobile into master

fanghuisheng 2 dni temu
rodzic
commit
27fc065e4e
100 zmienionych plików z 3223 dodań i 645 usunięć
  1. 13 4
      src/api/business/fireIot/deviceManage.js
  2. 2 14
      src/pages/business/door/list/index.vue
  3. 262 184
      src/pages/business/fireIot/deviceManage/components/deviceDetails.vue
  4. 47 40
      src/pages/business/fireIot/deviceManage/components/deviceDetailsList.vue
  5. 29 21
      src/pages/business/fireIot/deviceManage/index.vue
  6. 2 1
      src/store/modules/common.js
  7. 1 1
      src/uni_modules/uview-plus/LICENSE
  8. 13 3
      src/uni_modules/uview-plus/README.md
  9. 402 0
      src/uni_modules/uview-plus/changelog.md
  10. 7 2
      src/uni_modules/uview-plus/components/u--form/u--form.vue
  11. 109 0
      src/uni_modules/uview-plus/components/u-action-sheet-data/u-action-sheet-data.vue
  12. 3 3
      src/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue
  13. 2 1
      src/uni_modules/uview-plus/components/u-album/album.js
  14. 5 0
      src/uni_modules/uview-plus/components/u-album/props.js
  15. 22 6
      src/uni_modules/uview-plus/components/u-album/u-album.vue
  16. 4 2
      src/uni_modules/uview-plus/components/u-alert/u-alert.vue
  17. 2 1
      src/uni_modules/uview-plus/components/u-button/button.js
  18. 6 1
      src/uni_modules/uview-plus/components/u-button/props.js
  19. 4 2
      src/uni_modules/uview-plus/components/u-button/u-button.vue
  20. 1 1
      src/uni_modules/uview-plus/components/u-button/vue.scss
  21. 4 1
      src/uni_modules/uview-plus/components/u-calendar/calendar.js
  22. 14 7
      src/uni_modules/uview-plus/components/u-calendar/header.vue
  23. 25 3
      src/uni_modules/uview-plus/components/u-calendar/month.vue
  24. 14 1
      src/uni_modules/uview-plus/components/u-calendar/props.js
  25. 4 0
      src/uni_modules/uview-plus/components/u-calendar/u-calendar.vue
  26. 40 0
      src/uni_modules/uview-plus/components/u-card/card.js
  27. 26 32
      src/uni_modules/uview-plus/components/u-card/props.js
  28. 1 1
      src/uni_modules/uview-plus/components/u-card/u-card.vue
  29. 342 0
      src/uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue
  30. 3 2
      src/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue
  31. 15 12
      src/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue
  32. 1 1
      src/uni_modules/uview-plus/components/u-code/u-code.vue
  33. 6 1
      src/uni_modules/uview-plus/components/u-collapse-item/collapseItem.js
  34. 31 0
      src/uni_modules/uview-plus/components/u-collapse-item/props.js
  35. 2 0
      src/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue
  36. 2 1
      src/uni_modules/uview-plus/components/u-column-notice/columnNotice.js
  37. 5 1
      src/uni_modules/uview-plus/components/u-column-notice/props.js
  38. 1 0
      src/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue
  39. 6 1
      src/uni_modules/uview-plus/components/u-datetime-picker/datetimePicker.js
  40. 20 2
      src/uni_modules/uview-plus/components/u-datetime-picker/props.js
  41. 58 15
      src/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue
  42. 13 11
      src/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue
  43. 169 0
      src/uni_modules/uview-plus/components/u-float-button/u-float-button.vue
  44. 0 2
      src/uni_modules/uview-plus/components/u-form-item/u-form-item.vue
  45. 62 20
      src/uni_modules/uview-plus/components/u-icon/u-icon.vue
  46. 45 13
      src/uni_modules/uview-plus/components/u-image/u-image.vue
  47. 9 8
      src/uni_modules/uview-plus/components/u-index-list/u-index-list.vue
  48. 8 1
      src/uni_modules/uview-plus/components/u-input/u-input.vue
  49. 4 0
      src/uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue
  50. 2 1
      src/uni_modules/uview-plus/components/u-line-progress/lineProgress.js
  51. 5 0
      src/uni_modules/uview-plus/components/u-line-progress/props.js
  52. 5 1
      src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue
  53. 2 2
      src/uni_modules/uview-plus/components/u-list-item/u-list-item.vue
  54. 2 2
      src/uni_modules/uview-plus/components/u-list/u-list.vue
  55. 5 1
      src/uni_modules/uview-plus/components/u-modal/modal.js
  56. 20 0
      src/uni_modules/uview-plus/components/u-modal/props.js
  57. 33 6
      src/uni_modules/uview-plus/components/u-modal/u-modal.vue
  58. 0 1
      src/uni_modules/uview-plus/components/u-navbar-mini/u-navbar-mini.vue
  59. 1 0
      src/uni_modules/uview-plus/components/u-navbar/navbar.js
  60. 10 0
      src/uni_modules/uview-plus/components/u-navbar/props.js
  61. 17 9
      src/uni_modules/uview-plus/components/u-navbar/u-navbar.vue
  62. 2 2
      src/uni_modules/uview-plus/components/u-no-network/u-no-network.vue
  63. 2 1
      src/uni_modules/uview-plus/components/u-notice-bar/noticeBar.js
  64. 5 1
      src/uni_modules/uview-plus/components/u-notice-bar/props.js
  65. 1 0
      src/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue
  66. 5 1
      src/uni_modules/uview-plus/components/u-number-box/numberBox.js
  67. 21 1
      src/uni_modules/uview-plus/components/u-number-box/props.js
  68. 53 39
      src/uni_modules/uview-plus/components/u-number-box/u-number-box.vue
  69. 1 0
      src/uni_modules/uview-plus/components/u-overlay/u-overlay.vue
  70. 283 0
      src/uni_modules/uview-plus/components/u-pagination/u-pagination.vue
  71. 38 27
      src/uni_modules/uview-plus/components/u-parse/node/node.vue
  72. 5 5
      src/uni_modules/uview-plus/components/u-parse/parse.js
  73. 79 12
      src/uni_modules/uview-plus/components/u-parse/parser.js
  74. 1 1
      src/uni_modules/uview-plus/components/u-parse/u-parse.vue
  75. 128 0
      src/uni_modules/uview-plus/components/u-picker-data/u-picker-data.vue
  76. 8 2
      src/uni_modules/uview-plus/components/u-picker/picker.js
  77. 25 1
      src/uni_modules/uview-plus/components/u-picker/props.js
  78. 159 37
      src/uni_modules/uview-plus/components/u-picker/u-picker.vue
  79. 30 5
      src/uni_modules/uview-plus/components/u-popup/u-popup.vue
  80. 14 6
      src/uni_modules/uview-plus/components/u-qrcode/qrcode.js
  81. 35 16
      src/uni_modules/uview-plus/components/u-qrcode/u-qrcode.vue
  82. 6 1
      src/uni_modules/uview-plus/components/u-radio-group/props.js
  83. 4 3
      src/uni_modules/uview-plus/components/u-radio-group/radioGroup.js
  84. 10 1
      src/uni_modules/uview-plus/components/u-radio-group/u-radio-group.vue
  85. 13 10
      src/uni_modules/uview-plus/components/u-radio/u-radio.vue
  86. 2 2
      src/uni_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue
  87. 4 0
      src/uni_modules/uview-plus/components/u-search/props.js
  88. 1 0
      src/uni_modules/uview-plus/components/u-search/search.js
  89. 11 0
      src/uni_modules/uview-plus/components/u-search/u-search.vue
  90. 218 0
      src/uni_modules/uview-plus/components/u-select/u-select.vue
  91. 29 11
      src/uni_modules/uview-plus/components/u-slider/u-slider.vue
  92. 6 1
      src/uni_modules/uview-plus/components/u-status-bar/props.js
  93. 2 1
      src/uni_modules/uview-plus/components/u-status-bar/statusBar.js
  94. 16 2
      src/uni_modules/uview-plus/components/u-status-bar/u-status-bar.vue
  95. 16 5
      src/uni_modules/uview-plus/components/u-steps-item/u-steps-item.vue
  96. 9 4
      src/uni_modules/uview-plus/components/u-sticky/u-sticky.vue
  97. 3 1
      src/uni_modules/uview-plus/components/u-subsection/u-subsection.vue
  98. 5 0
      src/uni_modules/uview-plus/components/u-swipe-action-item/index.wxs
  99. 4 0
      src/uni_modules/uview-plus/components/u-swipe-action-item/props.js
  100. 1 0
      src/uni_modules/uview-plus/components/u-swipe-action-item/swipeActionItem.js

+ 13 - 4
src/api/business/fireIot/deviceManage.js

@@ -30,7 +30,7 @@ export function dmpProductAttribute(param) {
 // 设备多属性历史数据请求
 export function historyMetrics(param) {
     return request({
-        url: "/data-tsdb-proxy/dataQuery/historyMetrics",
+        url: "/data-tsdb-proxy/dataQuery/history",
         method: "POST",
         data: param,
     });
@@ -52,9 +52,9 @@ export function getList(data) {
         method: 'POST',
         data,
     })
-  }
+}
+
 
-  
 //设备离线状态更新
 export function deviceStatus(params) {
     return request({
@@ -62,4 +62,13 @@ export function deviceStatus(params) {
         method: 'get',
         params,
     })
-  }
+}
+
+//命令下发
+export function control(params) {
+    return request({
+        url: '/service-iot/deviceHttp/control',
+        method: 'get',
+        params,
+    })
+}

+ 2 - 14
src/pages/business/door/list/index.vue

@@ -37,22 +37,10 @@
 
       <u-grid class="p10" :border="true" :col="2" gap="10px">
         <u-grid-item class="p10 radius" bgColor="#fff" v-for="(item, index) in dataList" :key="index" @click="doorSetting(item)">
-          <view
-            :style="{
-              fontSize: '14px',
-              fontWeight: 600,
-              marginBottom: '20px',
-              marginRight: 'auto',
-            }"
-          >
+          <view :style="{ fontSize: '14px', fontWeight: 600, marginBottom: '20px', marginRight: 'auto' }">
             {{ item.deviceName }}
           </view>
-          <view
-            class="flex"
-            :style="{
-              width: '100%',
-            }"
-          >
+          <view class="flex" :style="{ width: '100%' }">
             <image style="width: 40px; height: 40px" :src="'/static/images/door/lock.png'" mode="aspectFill"></image>
             <view class="iconfont oaIcon-open_door ml-auto mtb-auto font35" @click.stop="controlStore.openDoor(item, true)"></view>
           </view>

+ 262 - 184
src/pages/business/fireIot/deviceManage/components/deviceDetails.vue

@@ -11,98 +11,88 @@
     :data-theme="'theme-' + proxy.$settingStore.themeColor.name"
   >
     <template #default>
-      <view style="background: linear-gradient(to bottom, #fafbff, #e7f3ff)">
-        <view class="flex" style="padding: 15px 15px 0">
-          <view style="margin: auto auto auto 0">
-            <view style="font-size: 16px; color: #000"> {{ detailData.deviceName }} </view>
+      <view class="header-area p15">
+        <view class="header-area-top flex mb15">
+          <view class="title"> {{ commonStore.deviceManageData.deviceName }} </view>
+          <view class="status" :style="{ backgroundColor: commonStore.deviceManageData.deviceStatus == 1 ? '#16bf00' : 'red' }">
+            {{ commonStore.deviceManageData.deviceStatus == 1 ? "在线" : "离线" }}
           </view>
+        </view>
 
-          <view style="margin: auto 0 auto 0">
-            <view
-              style="margin-left: 20px; font-size: 12px; color: #ffffff; padding: 2px 10px; border-radius: 20px; line-height: 20px"
-              :style="{ fontSize: '15px', backgroundColor: detailData.deviceStatus == 1 ? '#16bf00' : 'red' }"
-            >
-              {{ detailData.deviceStatus == 1 ? "在线" : "离线" }}
+        <u-row class="header-area-center p0">
+          <u-col span="9">
+            <view class="header-area-center-item" v-for="data in listData" :key="data">
+              <span class="title">{{ data.title }}:</span>
+              <span class="value">{{ commonStore.deviceManageData[data.prop] ? commonStore.deviceManageData[data.prop] : "-" }}</span>
             </view>
-          </view>
-        </view>
-        <view class="p15" style="color: rgba(0, 0, 0, 0.7)">
-          <!-- <uni-section class="block mb10" title="基本信息" type="line"></uni-section> -->
-          <view class="basicBox p0">
-            <u-empty v-if="dataList.length <= 0" text="暂无数据" mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png"> </u-empty>
-
-            <u-row>
-              <u-col span="9" class="basicLeft">
-                <view v-for="po in dataList" :key="po">
-                  <view style="text-align: left; padding: 0px 5px 0px 5px">{{ po.title }}:</view>
-                  <view style="text-align: left; padding: 0px 5px 0px 5px">{{ po.value }}</view>
-                </view>
-              </u-col>
-              <u-col span="3">
-                <image style="width: 80px; height: 80px; margin: auto 15px auto 0" :src="'/static/images/device/jg.png'" mode="aspectFill"></image>
-              </u-col>
-            </u-row>
-          </view>
-        </view>
+          </u-col>
+          <u-col span="3">
+            <image style="width: 80px; height: 80px; display: flex; margin: auto" :src="'/static/images/device/jg.png'" mode="aspectFill"></image>
+          </u-col>
+        </u-row>
       </view>
 
-      <view class="bg-white p15 mb15">
+      <view class="body-area">
         <!-- 分段器组件 -->
-        <view class="app-subsection">
-          <u-subsection :list="list" mode="subsection" :current="tabPosition" @change="tabPositionChange" style="width: 100%; font-size: 16px"></u-subsection>
+        <view class="subsection">
+          <view class="subsection-item" v-for="(li, index) in tabs.list" :key="index" :class="{ active: index == tabs.value }" @click="tabPositionChange(index)">{{ li }}</view>
+        </view>
+
+        <view class="realTimeData-area flex plr15" v-if="tabs.value == 0">
+          <view class="realTimeData-area-item" v-for="item in realTimeData" :key="item">
+            <view class="title">{{ item.attributeName }}</view>
+            <view class="value">
+              <span v-if="item.attributeDict.length > 0">
+                {{ Number.isFinite(item.value) ? proxy.$common.mapping("name", "value", item.value, item.attributeDict) : item.value ? item.value : "-" }}
+              </span>
+              <span v-else>
+                {{ Number.isFinite(item.value) ? (Number.isInteger(item.value) ? item.value : item.value.toFixed(2)) : item.value ? item.value : "-" }}
+              </span>
+              <span style="color: #333; width: auto; font-size: 14px">{{ item.attributeUnit }} </span>
+            </view>
+          </view>
         </view>
 
-        <view v-if="tabPosition == 1">
+        <view class="plr15" v-if="tabs.value == 1">
           <view class="flex" :style="{ color: proxy.$settingStore.themeColor.color }">
             <view class="ml10" style="margin-left: auto" @click="open">选择时间</view>
             <view class="ml10" @click="modalShow = true">筛选</view>
           </view>
 
-          <chart :currentDateList="currentDateList"></chart>
-
-          <!-- <view class="tableType1">
-            <u-row>
-              <u-col span="12">
-                <view>指数</view>
-              </u-col>
-            </u-row>
-            <u-row v-for="(co, index) in content4" :key="index">
-              <u-col span="12">
-                <view>{{ co.name1 }}</view>
-              </u-col>
-            </u-row>
-          </view> -->
+          <chart :currentDateList="ecahrtsDate"></chart>
         </view>
-        <view v-if="tabPosition == 0">
-          <u-empty v-if="realTimeDataList.length <= 0" text="暂无数据" mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png"> </u-empty>
-          <view v-else class="flex" style="flex-wrap: wrap; line-height: 36px; font-size: 16px">
-            <view style="width: 100%; border-bottom: 1px solid #f3f3f3" v-for="realTime in realTimeDataList" :key="realTime">
-              {{ realTime.attributeName + ":" }}
-              <view style="color: #000; display: inline-block">{{ realTime.value }}</view>
-              {{ realTime.attributeUnit ? realTime.attributeUnit : "" }}
+
+        <view class="cotrol-area plr15" v-if="tabs.value == 2">
+          <view class="cotrol-area-item p10 mb15" v-for="(item, index) in deviceCotrolData" :key="index">
+            <u-select
+              v-if="item.commandDict.length > 0"
+              v-model:current="item.commandValue"
+              :label="item.commandName"
+              :options="item.commandDict"
+              keyName="value"
+              labelName="name"
+              @select="selectItem(item)"
+            >
+              <template #text>
+                <view class="title"> {{ item.commandName }}({{ proxy.$common.mapping("name", "value", item.commandValue, item.commandDict) }})</view>
+              </template>
+            </u-select>
+
+            <view class="title" v-else>
+              <view class="flex mb10">
+                <span style="margin: auto 0"> {{ item.commandName }}</span>
+                <span style="margin: auto 0 auto auto"> {{ item.commandValue }}</span>
+              </view>
+              <u-slider v-model="item.commandValue" :min="item.minimum" :max="item.maximum" height="5px" @change="selectItem(item)"></u-slider>
             </view>
           </view>
         </view>
-
-        <view v-if="tabPosition == 2">
-          <br />
-          <u-row gutter="10" style="justify-content: center">
-            <u-empty v-if="deviceCotrolList.length <= 0" text="暂无数据" mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png"> </u-empty>
-
-            <u-col v-else span="3" v-for="(item, index) in deviceCotrolList" :key="index">
-              <view class="demo-layout" @click="goAction(item)">{{ item.commandName }}</view>
-            </u-col>
-          </u-row>
-          <br />
-          <br />
-          <br />
-        </view>
       </view>
 
       <u-modal :show="modalShow" @confirm="modalShow = false" @close="modalShow = false" :closeOnClickOverlay="true">
         <view class="slot-content">
           <u-checkbox-group
-            v-model="checkboxValueList"
+            v-model="checkbox.value"
             @change="
               (val) => {
                 checkboxChange(val);
@@ -111,7 +101,7 @@
             :size="14"
             :activeColor="proxy.$settingStore.themeColor.color"
           >
-            <u-checkbox class="mb10" v-for="option in checkboxDataList" :key="option" :label="option.attributeName" :name="option.attributeCode"> </u-checkbox>
+            <u-checkbox class="mb10" v-for="option in checkbox.list" :key="option" :label="option.attributeName" :name="option.attributeCode"> </u-checkbox>
           </u-checkbox-group>
         </view>
       </u-modal>
@@ -126,7 +116,7 @@
 import { onLoad, onShow, onReady, onHide, onLaunch, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
 import { ref, reactive, computed, getCurrentInstance, toRefs, inject, watch } from "vue";
 /*----------------------------------接口引入-----------------------------------*/
-import { dmpDeviceInfo, dmpProductAttribute, historyMetrics, last, getList } from "@/api/business/fireIot/deviceManage.js";
+import { dmpProductAttribute, historyMetrics, last, getList, control } from "@/api/business/fireIot/deviceManage.js";
 /*----------------------------------组件引入-----------------------------------*/
 import chart from "./chart.vue";
 /*----------------------------------store引入-----------------------------------*/
@@ -136,72 +126,47 @@ import { useStores, commonStores } from "@/store/modules/index";
 const { proxy } = getCurrentInstance();
 const commonStore = commonStores();
 /*----------------------------------变量声明-----------------------------------*/
-const dataList = ref([
-  {
-    title: "设备类型",
-    value: commonStore.deviceDetailsArray.productName,
-  },
-  {
-    title: "设备编号",
-    value: commonStore.deviceDetailsArray.deviceId,
+const state = reactive({
+  listData: [
+    { title: "设备类型", prop: "productName" },
+    { title: "设备编号", prop: "deviceId" },
+    { title: "SIM卡号", prop: "simCode" },
+    { title: "安装位置", prop: "installAddress" },
+    { title: "添加时间", prop: "createdTime" },
+  ],
+
+  tabs: {
+    list: ["实时数据", "历史数据", "指令操作"],
+    value: 0,
   },
-  {
-    title: "物联网卡号",
-    value: commonStore.deviceDetailsArray.simCode,
-  },
-  {
-    title: "安装位置",
-    value: commonStore.deviceDetailsArray.installAddress,
-  },
-  {
-    title: "添加时间",
-    value: commonStore.deviceDetailsArray.createdTime ? commonStore.deviceDetailsArray.createdTime.replace("T", " ") : "",
-  },
-]);
 
-const checkboxDataList = ref([]); //复选框渲染数据存储
-const checkboxValueList = ref([]); //复选框值数据存储
-
-const realTimeDataList = ref([]); //实时数据存储
-const tableDataList = ref([]); //表格数据存储
-const currentDateList = ref([]); //图表数据存储
+  metrics: [], //属性编号数据存储
+  metricsValue: {}, //属性编号值数据存储
+  realTimeData: [], //实时数据存储
+  ecahrtsDate: [], //图表数据存储
+  deviceCotrolData: [], //设备调试数据存储
+  checkbox: {
+    list: [], //复选框渲染数据存储
+    value: [], //复选框值数据存储
+  },
+});
+const { listData, tabs, metrics, metricsValue, realTimeData, ecahrtsDate, deviceCotrolData, checkbox } = toRefs(state);
 
 const modalShow = ref(false); //模态框显示隐藏
-
 const calendar = ref(null);
-const calendarStartTime = ref(""); //日历开始时间
-const calendarEndTime = ref(""); //日历结束时间
+const calendarStartTime = ref(proxy.$dayjs().format("YYYY-MM-DD")); //日历开始时间
+const calendarEndTime = ref(proxy.$dayjs().format("YYYY-MM-DD")); //日历结束时间
 const productId = ref(0); //产品id
 const deviceId = ref(0); //设备id
-const detailData = ref({}); //设备详情数据存储
-const deviceCotrolList = ref([]); //设备调试数据存储
-function open() {
-  calendar.value.open();
-}
 
 /**
  * @初始化
  */
-
-/**
- * @详情查询
- * @api接口查询
- */
-function dmpDeviceInfoApi() {
-  dmpDeviceInfo({ productId: productId.value, deviceId: deviceId.value, current: 1, size: 10 }).then((requset) => {
-    if (requset.status === "SUCCESS") {
-      dataList.value[0].value = requset.data.records[0].deviceName;
-      dataList.value[1].value = requset.data.records[0].deviceId;
-      dataList.value[2].value = requset.data.records[0].simCode;
-      dataList.value[3].value = requset.data.records[0].installAddress;
-      dataList.value[4].value = requset.data.records[0].createdTime ? requset.data.records[0].createdTime.replace("T", " ") : requset.data.records[0].createdTime;
-      detailData.value = requset.data.records[0];
-    }
-  });
-}
-
 function init() {
-  dmpDeviceInfoApi();
+  state.metrics = [];
+  state.realTimeData = [];
+  state.checkbox.list = [];
+
   dmpProductAttribute({
     current: 1,
     size: 100,
@@ -210,63 +175,74 @@ function init() {
     deviceId: deviceId.value,
   }).then((requset) => {
     if (requset.status === "SUCCESS") {
-      checkboxDataList.value = requset.data.records;
-      realTimeDataList.value = requset.data.records;
-
-      var array = [];
-
-      requset.data.records.forEach((el) => {
-        array.push(el.attributeCode);
+      requset.data.records.forEach((item) => {
+        item.attributeDict = item.attributeDict ? JSON.parse(item.attributeDict) : [];
+        state.metrics.push(item.attributeCode.toLowerCase());
+        state.realTimeData.push(item);
+        state.checkbox.list.push(item);
       });
 
       last({
-        deviceUUId: detailData.value.deviceUuid,
-        metrics: array,
-      }).then((requsets) => {
-        if (requsets.status === "SUCCESS") {
-          realTimeDataList.value.forEach((el) => {
-            el.value = 0;
-            requsets.data.forEach((e) => {
-              if (el.attributeCode === e.metric) {
-                el.value = e.value;
-              }
-            });
+        metrics: state.metrics,
+        deviceuuid: [commonStore.deviceManageData.deviceUuid],
+      }).then((requset) => {
+        if (requset.status != "SUCCESS") return;
+        state.metricsValue = requset.data[0].metrics;
+        Object.keys(state.metricsValue).forEach((key) => {
+          state.realTimeData.forEach((el) => {
+            if (el.attributeCode.toLowerCase() === key) {
+              el.value = state.metricsValue[key];
+            }
           });
-        }
+        });
       });
     }
   });
 }
 
 function deviceControlData() {
+  state.deviceCotrolData = [];
   getList({
     current: 1,
     size: 10,
-    productCode: detailData.value.productCode,
+    productCode: commonStore.deviceManageData.productCode,
   }).then((response) => {
-    deviceCotrolList.value = response.data.records;
-    // console.log(response.data.records)
-    // dataList.value = response.data.records;
-    // state.total = response.data.total;
-    // state.loading = false;
+    response.data.records.forEach((e) => {
+      e.commandDict = e.commandDict ? JSON.parse(e.commandDict) : [];
+      e.commandValue = state.metricsValue[e.commandCode.toLowerCase()];
+      state.deviceCotrolData.push(e);
+    });
+  });
+}
+
+function selectItem(e) {
+  var params = {
+    commandCode: e.commandCode,
+    commandValue: e.commandValue,
+    productCode: e.productCode,
+    deviceUuid: commonStore.deviceManageData.deviceUuid,
+    categoryType: commonStore.deviceManageData.categoryType,
+    gatewayUuid: commonStore.deviceManageData.gatewayUuid,
+  };
+  control(params).then((res) => {
+    init();
+    proxy.$modal.msg(res.data.message);
   });
 }
 
 /**
  * @tabs切换change事件
  */
-const list = ref(["实时数据", "历史数据", "设备调试"]);
-const tabPosition = ref(0);
+
 function tabPositionChange(index) {
-  tabPosition.value = index;
+  state.tabs.value = index;
 }
 
 /**
  * @checkbox选中change事件
  */
 function checkboxChange(value) {
-  checkboxValueList.value = value;
-
+  state.checkbox.value = value;
   historyMetricsApi();
 }
 
@@ -287,32 +263,30 @@ function historyMetricsApi() {
   historyMetrics({
     startTime: calendarStartTime.value ? calendarStartTime.value + " 00:00:00" : calendarStartTime.value,
     endTime: calendarEndTime.value ? calendarEndTime.value + " 23:59:59" : calendarEndTime.value,
-    deviceUUId: detailData.value.deviceUuid,
-    // deviceId: commonStore.deviceDetailsArray.deviceId,
-    // deviceType: commonStore.deviceDetailsArray.deviceType,
-    metrics: checkboxValueList.value,
+    deviceuuid: [commonStore.deviceManageData.deviceUuid],
+    metrics: state.checkbox.value.length > 0 ? state.checkbox.value : state.metrics,
   }).then((requset) => {
     if (requset.status === "SUCCESS") {
-      currentDateList.value = requset.data;
+      var metrics = requset.data[0].metrics;
 
-      checkboxDataList.value.forEach((el) => {
-        currentDateList.value.forEach((e) => {
-          if (el.attributeCode == e.metric) {
+      state.checkbox.list.forEach((el) => {
+        metrics.forEach((e) => {
+          if (el.attributeCode.toLowerCase() == e.metric) {
             e.attributeName = el.attributeName;
           }
         });
       });
 
-      currentDateList.value.forEach((el) => {
+      metrics.forEach((el) => {
+        el.data = [];
         if (el.metricItems.length > 0) {
-          el.data = [];
           el.metricItems.forEach((e) => {
             el.data.push([e.timestamp, e.value]);
           });
-        } else {
-          el.data = [];
         }
       });
+
+      state.ecahrtsDate = metrics;
     }
   });
 }
@@ -321,6 +295,10 @@ function goAction(row) {
   proxy.$tab.navigateTo(`/pages/business/fireIot/deviceManage/components/goAction?productCode=${row.productCode}&commandCode=${row.commandCode}&deviceId=${deviceId.value}`);
 }
 
+function open() {
+  calendar.value.open();
+}
+
 onReady(() => {});
 
 onShow(() => {
@@ -344,7 +322,7 @@ onLoad((options) => {
 });
 
 watch(
-  () => tabPosition.value,
+  () => state.tabs.value,
   (val) => {
     if (val == 2) {
       deviceControlData();
@@ -354,28 +332,128 @@ watch(
 </script>
 
 <style lang="scss" scoped>
-uni-page-body {
-  background-color: #fff;
-}
+.header-area {
+  border-radius: 10px;
+  background: linear-gradient(to bottom, #fafbff, #e7f3ff);
+
+  &-top {
+    justify-content: space-between;
+
+    .title {
+      font-size: 18px;
+      color: #000;
+      font-weight: 600;
+    }
+    .status {
+      font-size: 15px;
+      color: #ffffff;
+      padding: 2px 10px;
+      border-radius: 20px;
+      line-height: 22px;
+    }
+  }
 
-.basicBox {
-  font-size: 16px;
-  .basicLeft view {
-    display: inline-block;
+  &-center {
+    width: 100%;
+    text-align: left;
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.7);
+
+    &-item {
+      > span {
+        display: inline-block;
+        line-height: 25px;
+      }
+
+      .title,
+      .value {
+        padding: 0px 5px 0px 5px;
+      }
+    }
+  }
+}
+.body-area {
+  .realTimeData-area {
+    flex-wrap: wrap;
     line-height: 30px;
-    .subsection__item__text {
-      font-size: 16px !important;
+    font-size: 16px;
+
+    &-item {
+      width: calc(50% - 7.5px);
+      padding: 10px;
+      margin-bottom: 10px;
+      border-radius: 5px;
+      box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.1);
+      background-color: #ffffff;
+
+      &:nth-child(2n -1) {
+        margin-right: 15px;
+      }
+      .title {
+        font-size: 14px;
+      }
+      .value {
+        display: inline-block;
+        color: #000;
+        font-size: 16px;
+        font-weight: 600;
+      }
+    }
+  }
+
+  .cotrol-area {
+    &-item {
+      border-radius: 5px;
+      box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.1);
+
+      .title {
+        color: #000;
+        font-weight: 600;
+        font-size: 14px;
+      }
+    }
+  }
+  .subsection {
+    display: flex;
+    justify-content: center;
+    margin: 15px 0;
+
+    &-item {
+      margin-right: 15px;
+
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+
+    .active {
+      font-weight: 600;
+      animation: colorChange 1s forwards;
+
+      @keyframes colorChange {
+        0% {
+          color: #666666;
+        }
+        100% {
+          color: #000;
+        } /* 深色 */
+      }
     }
   }
 }
-.app-subsection {
-  display: flex;
-  margin-bottom: 10px;
-  // padding: 0px 5rem;
+
+:deep() {
+  .u-slider__show-value {
+    margin: 10px 0px 10px 18px !important;
+  }
+}
+
+uni-page-body {
+  background-color: #fff;
 }
 .demo-layout {
-  border: 1px solid #e0e0e0;
   padding: 15px 10px;
-  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
+  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.1);
+  border-radius: 5px;
 }
 </style>

+ 47 - 40
src/pages/business/fireIot/deviceManage/components/deviceDetailsList.vue

@@ -1,5 +1,5 @@
 <template>
-  <u-navbar :titleStyle="{ color: '#000' }" :autoBack="true" :title="`${productName}(${total})`" :placeholder="true" :safeAreaInsetTop="true" bgColor="#fff">
+  <u-navbar :titleStyle="{ color: '#000' }" :autoBack="true" :title="`${params.productName}(${total})`" :placeholder="true" :safeAreaInsetTop="true" bgColor="#fff">
     <template #left>
       <view class="u-navbar__content__left__item">
         <u-icon name="arrow-left" size="20" color="#000"></u-icon>
@@ -9,7 +9,7 @@
 
   <oa-scroll
     customClass="scroll-height"
-    :pageSize="pageSize"
+    :pageSize="params.size"
     :total="total"
     :isSticky="false"
     :refresherLoad="true"
@@ -26,7 +26,7 @@
         <view class="flex bg-white p10" style="position: relative">
           <u--input
             style="width: 100%"
-            v-model="deviceName"
+            v-model="params.deviceName"
             placeholder="请输入设备名称"
             prefixIcon="search"
             prefixIconStyle="font-size: 22px;color: #909399"
@@ -39,8 +39,8 @@
 
         <oa-dropdown :dropdownShow="dropdownShow" :closeOnClickOverlay="true" @close="dropdownShow = false">
           <template #content>
-            <u-radio-group v-model="radioValue" placement="column" iconPlacement="right" @change="radioChange">
-              <u-radio v-for="ra in radioList" :key="ra" :activeColor="proxy.$settingStore.themeColor.color" :label="ra.label" :name="ra.value"></u-radio>
+            <u-radio-group v-model="radio.value" placement="column" iconPlacement="right" @change="radioChange">
+              <u-radio v-for="item in radio.list" :key="item" :activeColor="proxy.$settingStore.themeColor.color" :label="item.label" :name="item.value"></u-radio>
             </u-radio-group>
           </template>
         </oa-dropdown>
@@ -69,34 +69,41 @@
 </template>
 
 <script setup>
+/*----------------------------------依赖引入-----------------------------------*/
 import { onLoad, onShow, onReady, onHide, onLaunch, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
 import { ref, reactive, computed, getCurrentInstance, toRefs, inject } from "vue";
-import { useStores, commonStores } from "@/store/modules/index";
-
+/*----------------------------------接口引入-----------------------------------*/
 import { dmpDeviceInfo } from "@/api/business/fireIot/deviceManage.js";
-
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+import { useStores, commonStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+/*----------------------------------公共变量-----------------------------------*/
 const { proxy } = getCurrentInstance();
 const commonStore = commonStores();
-
-const dataList = ref([]);
-const deviceName = ref("");
-const productId = ref("");
-const productName = ref("");
-const pageSize = ref(20);
-const current = ref(1);
-const total = ref(0);
-
-const data = reactive({
-  radioList: [
-    { label: "全部", value: "" },
-    { label: "在线", value: "1" },
-    { label: "离线", value: "2" },
-  ],
-  radioValue: "1",
+/*----------------------------------变量声明-----------------------------------*/
+const state = reactive({
+  dataList: [],
+  total: 0,
+  params: {
+    deviceName: "",
+    productId: "",
+    productName: "",
+    size: 20,
+    current: 1,
+  },
+  radio: {
+    list: [
+      { label: "全部", value: "" },
+      { label: "在线", value: "1" },
+      { label: "离线", value: "2" },
+    ],
+    value: "1",
+  },
   dropdownShow: false,
 });
 
-const { radioList, radioValue, dropdownShow } = toRefs(data);
+const { dataList, total, params, radio, dropdownShow } = toRefs(state);
 
 /**
  * @页面初始化
@@ -111,10 +118,10 @@ function init() {
  */
 
 function dmpDeviceInfoApi() {
-  dmpDeviceInfo({ productId: productId.value, deviceName: deviceName.value, current: current.value, size: pageSize.value, deviceStatus: radioValue.value }).then((requset) => {
+  dmpDeviceInfo(state.params).then((requset) => {
     if (requset.status == "SUCCESS") {
-      dataList.value = requset.data.records;
-      total.value = requset.data.total;
+      state.dataList = requset.data.records;
+      state.total = requset.data.total;
     }
   });
 }
@@ -122,28 +129,28 @@ function dmpDeviceInfoApi() {
 /**
  * @设备详情跳转点击事件
  */
-function handleToDevice(array) {
-  proxy.$tab.navigateTo(`/pages/business/fireIot/deviceManage/components/deviceDetails?productId=${array.productId}&deviceId=${array.deviceId}`);
+function handleToDevice(event) {
+  proxy.$tab.navigateTo(`/pages/business/fireIot/deviceManage/components/deviceDetails?productId=${event.productId}&deviceId=${event.deviceId}`);
 
-  commonStore.deviceDetailsArray = array;
-  commonStore.deviceDetailsArray.productName = productName.value;
+  commonStore.deviceManageData = event;
+  commonStore.deviceManageData.createdTime = event.createdTime ? event.createdTime.replace("T", " ") : "";
+  commonStore.deviceManageData.productName = state.params.productName;
 }
 
 /**
  * @单选change事件
  */
 function radioChange(e) {
-  // console.log(e,'e')
-  radioValue.value = e;
+  state.radio.value = e;
   dmpDeviceInfoApi();
-  dropdownShow.value = false;
+  state.dropdownShow = false;
 }
 
 /**
  * @scrollView加载数据
  */
 function load() {
-  pageSize.value += 10;
+  state.params.size += 10;
   init();
 }
 
@@ -151,8 +158,8 @@ function load() {
  * @scrollView刷新数据
  */
 function refresh() {
-  deviceName.value = "";
-  pageSize.value = 20;
+  state.params.deviceName = "";
+  state.params.size = 20;
   init();
 }
 
@@ -165,10 +172,10 @@ onShow(() => {
 
 onLoad((options) => {
   if ("productName" in options) {
-    productName.value = options.productName;
+    state.params.productName = options.productName;
   }
   if ("id" in options) {
-    productId.value = parseInt(options.id);
+    state.params.productId = parseInt(options.id);
     init();
   }
 });

+ 29 - 21
src/pages/business/fireIot/deviceManage/index.vue

@@ -15,12 +15,12 @@
     :data-theme="'theme-' + proxy.$settingStore.themeColor.type"
   >
     <template #default>
-      <view class="deviceManage" style="border-top:1px solid rgba(0,0,0,.1)">
-        <u-grid :border="true" >
-          <u-grid-item v-for="(base, index) in dataList" :key="index" @click="handleToDevice(base.id, base.productName,base.deviceCount)">
-            <!-- <u-badge type="primary" max="9999" :value="base.deviceCount" :showZero="true" :absolute="true" :offset="[10, 10, 0, 0]"></u-badge> -->
-            <image class="mb15 mt35" style="width: 40px; height: 40px" :src="base.typeImg" mode="aspectFill"></image>
-            <view class="mb15 text-ellipsis" style="width: 100%; text-align: center">{{ base.productName }}</view>
+      <view class="deviceManage" style="border-top: 1px solid rgba(0, 0, 0, 0.1)">
+        <u-grid :border="true">
+          <u-grid-item v-for="(item, index) in dataList" :key="index" @click="handleToDevice(item)">
+            <!-- <u-badge type="primary" max="9999" :value="item.deviceCount" :showZero="true" :absolute="true" :offset="[10, 10, 0, 0]"></u-badge> -->
+            <image class="mb15 mt35" style="width: 40px; height: 40px" :src="item.typeImg" mode="aspectFill"></image>
+            <view class="mb15 text-ellipsis" style="width: 100%; text-align: center">{{ item.productName }}</view>
           </u-grid-item>
         </u-grid>
       </view>
@@ -29,24 +29,32 @@
 </template>
 
 <script setup>
+/*----------------------------------依赖引入-----------------------------------*/
 import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
-import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance } from "vue";
-import { useStores, commonStores } from "@/store/modules/index";
-
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, toRefs } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
 import { dmpProductInfo } from "@/api/business/fireIot/deviceManage.js";
-
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+import { useStores, commonStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+/*----------------------------------公共变量-----------------------------------*/
 const { proxy } = getCurrentInstance();
+/*----------------------------------变量声明-----------------------------------*/
+const state = reactive({
+  dataList: [],
+  pageSize: 21,
+  current: 1,
+  total: 0,
+});
 
-const dataList = ref([]);
-const pageSize = ref(21);
-const current = ref(1);
-const total = ref(0);
+const { dataList, pageSize, current, total } = toRefs(state);
 
 /**
  * @页面初始化
  */
 function init() {
-  dmpProductInfo({ productName: "", current: current.value, size: pageSize.value }).then((requset) => {
+  dmpProductInfo({ productName: "", current: state.current, size: state.pageSize }).then((requset) => {
     if (requset.status === "SUCCESS") {
       requset.data.records.forEach((el) => {
         if (!el.typeImg) {
@@ -58,21 +66,21 @@ function init() {
         }
       });
 
-      dataList.value = requset.data.records;
-      total.value = requset.data.total;
+      state.dataList = requset.data.records;
+      state.total = requset.data.total;
     }
   });
 }
 
-function handleToDevice(id, productName,total) {
-  proxy.$tab.navigateTo(`/pages/business/fireIot/deviceManage/components/deviceDetailsList?id=${id}&productName=${productName}`);
+function handleToDevice(event) {
+  proxy.$tab.navigateTo(`/pages/business/fireIot/deviceManage/components/deviceDetailsList?id=${event.id}&productName=${event.productName}`);
 }
 
 /**
  * @scrollView加载数据
  */
 function load() {
-  pageSize.value += 10;
+  state.pageSize += 10;
   init();
 }
 
@@ -80,7 +88,7 @@ function load() {
  * @scrollView刷新数据
  */
 function refresh() {
-  pageSize.value = 20;
+  state.pageSize = 20;
   init();
 }
 

+ 2 - 1
src/store/modules/common.js

@@ -52,7 +52,7 @@ const commonStore = defineStore("common", {
       { streetTown: "莘庄工业区", id: 1216, x: 121.376508452784, y: 31.0678185611843 },
     ],
 
-    deviceDetailsArray: {}, //设备详情页面-数据存储
+    deviceManageData: {}, //设备详情页面-数据存储
 
     facilitiesGatherType: "", //设施采集类型-数据存储
     facilitiesGatherArray: {
@@ -79,6 +79,7 @@ const commonStore = defineStore("common", {
       typeGuise: "", //点线面类型
     }, //设施采集页面-数据存储
   }),
+  unistorage: true,
   actions: {
     /**
      * @计算当前坐标和G点坐标的偏差值

+ 1 - 1
src/uni_modules/uview-plus/LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 www.uviewui.com
+Copyright (c) 2024 https://uiadmin.net/uview-plus
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 13 - 3
src/uni_modules/uview-plus/README.md

@@ -1,5 +1,5 @@
 <p align="center">
-    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+    <img alt="logo" src="https://uiadmin.net/uview-plus/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
 </p>
 <h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3>
 <h3 align="center">多平台快速开发的UI框架</h3>
@@ -12,9 +12,19 @@
 
 ## 说明
 
-uview-plus,是uni-app全面兼容vue3/nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
+uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
 
-## [官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
+## 可视化设计
+
+uview-plus现已推出免费可视化设计,可以方便的进行页面可视化设计,导出源码即可使用。极大提高前端页面开发效率;如产品经理设计师直接使用更可作为高保真高可用原型制作工具,让设计稿即代码,无需传统的设计稿开发还原步骤。
+
+<img src="https://s3.bmp.ovh/imgs/2024/11/24/fd58d00071e6e5df.png" width="900" height="auto" >
+<img src="https://s3.bmp.ovh/imgs/2024/11/24/8e85a519fe627fb1.png" width="900" height="auto" >
+
+
+## 文档
+[官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
+[备用文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus)
 
 
 ## 预览

+ 402 - 0
src/uni_modules/uview-plus/changelog.md

@@ -1,3 +1,405 @@
+## 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接口废弃警告
+
+fix: 'u-status-bar' symbol missing
+
+## 3.3.60(2024-12-30)
+feat: 日期组件支持禁用
+
+fix: ts定义修复 #600
+
+feat: Tabs组件选中时增加一个active的class #595
+
+## 3.3.59(2024-12-30)
+fix: Property "isH5" was accessed during render
+
+## 3.3.58(2024-12-26)
+fix: slider组件change事件传参
+
+## 3.3.57(2024-12-23)
+fix: slider组件change事件传参
+
+feat: 更新u-picker组件增加当前选中class类名
+
+## 3.3.56(2024-12-18)
+feat: 在u-alert组件中添加关闭事件
+
+## 3.3.55(2024-12-17)
+add: swiper增加双向绑定
+
+## 3.3.54(2024-12-11)
+add: qrcode支持props控制是否开启点击预览
+
+add: 新增cate-tab垂直分类组件
+
+## 3.3.53(2024-12-10)
+fix: 修复popup居中模式点击内容区域触发关闭
+
+## 3.3.52(2024-12-09)
+add: notice-bar支持justifyContent属性
+
+## 3.3.51(2024-12-09)
+add: radio增加label插槽
+
+## 3.3.50(2024-12-05)
+fix: 优化popup等对禁止背景滚动机制
+
+add: slider在弹窗使用示例
+
+fix: card组件类名问题
+
+## 3.3.49(2024-12-02)
+fix: 去除album多余的$u引用
+
+fix: 优化图片组件兼容性
+
+add: picker组件增加zIndex属性
+
+add: text增加是否占满剩余空间属性
+
+add: input颜色示例
+
+## 3.3.48(2024-11-29)
+add: 文本行数限制样式提高到10行
+
+del: 去除不跨端的inputmode
+## 3.3.47(2024-11-28)
+fix: 时间选择器在hasInput模式下部分机型键盘弹出
+
+## 3.3.46(2024-11-26)
+fix: 修复text传递事件参数
+
+## 3.3.45(2024-11-24)
+add: navbar组件支持配置标题颜色
+
+fix: 边框按钮警告类型下颜色变量使用错误
+
+## 3.3.43(2024-11-18)
+fix: 支持瀑布流组件v-model置为[]
+
+add: 新增字符串路径访问工具方法getValueByPath
+
+add: 新增float-button悬浮按钮组件
+
+## 3.3.42(2024-11-15)
+add: button组件支持stop参数阻止冒泡
+
+## 3.3.41(2024-11-13)
+fix: u-radio-group invalid import
+
+improvement: 优化图片组件宽高及修复事件event传递
+
+## 3.3.40(2024-11-11)
+add: 组件radioGroup增加gap属性用于设置item间隔 
+
+fix: 修复H5全局导入
+
+## 3.3.39(2024-11-04)
+fix: 修复相册组件
+
+## 3.3.38(2024-11-04)
+fix: 修复视频预览报错 #510
+
+add: album组件增加stop参数支持阻止事件冒泡
+
+## 3.3.37(2024-10-21)
+fix: 修复因为修改组件名称前缀,导致h5打包后$parent方法内找不到父组件的问题
+
+fix: 修复datetime-picker选择2000年以前日期出错
+
+## 3.3.36(2024-10-09)
+fix: toast 自动关闭
+
+feat: 增加微信小程序用户昵称审核完毕回调及修改 ts 定义文件
+
+## 3.3.35(2024-10-08)
+feat: modal和picker支持v-model:show双向绑定
+
+feat: 支持checkbox使用slot自定义label后自带点击事件 #522
+
+feat: swipe-action支持自动关闭特性及初始化打开状态
+
 ## 3.3.34(2024-09-23)
 feat: 支持toast设置duration值为-1时不自动关闭
 

+ 7 - 2
src/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
src/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>

+ 3 - 3
src/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue

@@ -171,13 +171,13 @@
 			closeHandler() {
 				// 允许点击遮罩关闭时,才发出close事件
 				if(this.closeOnClickOverlay) {
-					this.$emit('update:show')
+					this.$emit('update:show', false)
 					this.$emit('close')
 				}
 			},
 			// 点击取消按钮
 			cancel() {
-				this.$emit('update:show')
+				this.$emit('update:show', false)
 				this.$emit('close')
 			},
 			selectHandler(index) {
@@ -185,7 +185,7 @@
 				if (item && !item.disabled && !item.loading) {
 					this.$emit('select', item)
 					if (this.closeOnClickAction) {
-						this.$emit('update:show')
+						this.$emit('update:show', false)
 						this.$emit('close')
 					}
 				}

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

@@ -22,6 +22,7 @@ export default {
         rowCount: 3,
         showMore: true,
         autoWrap: false,
-        unit: 'px'
+        unit: 'px',
+        stop: true,
     }
 }

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

@@ -76,6 +76,11 @@ export const props = defineMixin({
         unit: {
             type: [String],
             default: () => defProps.album.unit
+        },
+        // 阻止点击冒泡
+        stop: {
+            type: Boolean,
+            default: () => defProps.album.stop
         }
     }
 })

+ 22 - 6
src/uni_modules/uview-plus/components/u-album/u-album.vue

@@ -13,7 +13,7 @@
                 v-for="(item, index1) in arr"
                 :key="index1"
                 :style="[imageStyle(index + 1, index1 + 1)]"
-                @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
+                @tap="previewFullImage ? onPreviewTap($event, getSrc(item)) : ''"
             >
                 <image
                     :src="getSrc(item)"
@@ -119,7 +119,6 @@ export default {
         imageStyle() {
             return (index1, index2) => {
                 const { space, rowCount, multipleSize, urls } = this,
-                    { addUnit, addStyle } = uni.$u,
                     rowLen = this.showUrls.length,
                     allLen = this.urls.length
                 const style = {
@@ -189,7 +188,7 @@ export default {
     methods: {
         addUnit,
         // 预览图片
-        onPreviewTap(url) {
+        onPreviewTap(e, url) {
             const urls = this.urls.map((item) => {
                 return this.getSrc(item)
             })
@@ -197,6 +196,8 @@ export default {
                 current: url,
                 urls
             })
+            // 是否阻止事件传播
+			this.stop && this.preventEvent(e)
         },
         // 获取图片的路径
         getSrc(item) {
@@ -212,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 - 2
src/uni_modules/uview-plus/components/u-alert/u-alert.vue

@@ -79,6 +79,7 @@
 	 * @property {String | Number}	fontSize    字体大小  (默认 14 )
 	 * @property {Object}			customStyle	定义需要用到的外部样式
 	 * @event    {Function}        click       点击组件时触发
+	 * @event    {Function}        close       点击关闭按钮时触发
 	 * @example  <u-alert :title="title"  type = "warning" :closable="closable" :description = "description"></u-alert>
 	 */
 	export default {
@@ -116,7 +117,7 @@
 				}
 			}
 		},
-		emits: ["click"],
+		emits: ["click","close"],
 		methods: {
 			addUnit,
 			addStyle,
@@ -126,7 +127,8 @@
 			},
 			// 点击关闭按钮
 			closeHandler() {
-				this.show = false
+				this.show = false   
+				this.$emit('close')
 			}
 		}
 	}

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

@@ -37,6 +37,7 @@ export default {
         text: '',
         icon: '',
         iconColor: '',
-        color: ''
+        color: '',
+        stop: true,
     }
 }

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

@@ -149,6 +149,11 @@ export const props = defineMixin({
         color: {
             type: String,
             default: () => defProps.button.color
-        }
+        },
+        // 停止冒泡
+        stop: {
+            type: Boolean,
+            default: () => defProps.button.stop
+        },
     }
 })

+ 4 - 2
src/uni_modules/uview-plus/components/u-button/u-button.vue

@@ -271,14 +271,16 @@ export default {
 		'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'],
     methods: {
         addStyle,
-        clickHandler() {
+        clickHandler(e: any) {
             // 非禁止并且非加载中,才能点击
             if (!this.disabled && !this.loading) {
 				// 进行节流控制,每this.throttle毫秒内,只在开始处执行
 				throttle(() => {
-					this.$emit("click");
+					this.$emit("click", e);
 				}, this.throttleTime);
             }
+            // 是否阻止事件传播
+            this.stop && this.preventEvent(e)
         },
         // 下面为对接uniapp官方按钮开放能力事件回调的对接
         getphonenumber(res: any) {

+ 1 - 1
src/uni_modules/uview-plus/components/u-button/vue.scss

@@ -12,7 +12,7 @@ $u-button-icon-margin-left:4px !default;
 $u-button-plain-u-button-info-color:$u-info;
 $u-button-plain-u-button-success-color:$u-success;
 $u-button-plain-u-button-error-color:$u-error;
-$u-button-plain-u-button-warning-color:$u-error;
+$u-button-plain-u-button-warning-color:$u-warning;
 
 .u-button {
 	width: 100%;

+ 4 - 1
src/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
src/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
src/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
src/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
src/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
src/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
src/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
 		}
     }
 })

+ 1 - 1
src/uni_modules/uview-plus/components/u-card/u-card.vue

@@ -18,7 +18,7 @@
 			}"
 			@tap="headClick"
 		>
-			<view v-if="!$slots.head" class="u-flex u-row-between">
+			<view v-if="!$slots.head" class="u-flex u-flex-between">
 				<view class="u-card__head--left u-flex u-line-1" v-if="title">
 					<image
 						:src="thumb"

+ 342 - 0
src/uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue

@@ -0,0 +1,342 @@
+<template>
+	<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="[innerCurrent == index ? 'u-cate-tab__item-active' : '']"
+				 @tap.stop="swichMenu(index)">
+					<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-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">
+                        <slot name="itemList" :item="item">
+                        </slot>
+						<template v-if="!$slots['itemList']">
+							<view class="item-title">
+                                <text>{{item[tabKeyName]}}</text>
+                            </view>
+                            <view class="item-container">
+                                <template v-for="(item1, index1) in item.children" :key="index1">
+                                    <slot name="pageItem" :pageItem="item1">
+                                        <view class="thumb-box" >
+                                            <image class="item-menu-image" :src="item1.icon" mode=""></image>
+                                            <view class="item-menu-name">{{item1[itemKeyName]}}</view>
+                                        </view>
+                                    </slot>
+                                </template>
+                            </view>
+						</template>
+					</view>
+				</view>
+			</scroll-view>
+		</view>
+	</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: () => {
+                    return []
+                }
+            },
+            tabKeyName: {
+                type: String,
+                default: 'name'
+            },
+            itemKeyName: {
+                type: String,
+                default: 'name'
+            },
+			current: {
+                type: Number,
+                default: 0
+            }
+        },
+        watch: {
+            tabList() {
+                this.getMenuItemTop()
+            }
+        },
+		emits: ['update:current'],
+		data() {
+			return {
+				scrollTop: 0, //tab标题的滚动条位置
+				oldScrollTop: 0,
+				innerCurrent: 0, // 预设当前项的值
+				menuHeight: 0, // 左边菜单的高度
+				menuItemHeight: 0, // 左边菜单item的高度
+				itemId: '', // 栏目右边scroll-view用于滚动的id
+				menuItemPos: [],
+                rects: [],
+				arr: [],
+				scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
+				timer: null, // 定时器
+			}
+		},
+		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.innerCurrent) return;
+				this.scrollRightTop = this.oldScrollTop;
+				this.$nextTick(function(){
+					this.scrollRightTop = this.arr[index];
+					this.innerCurrent = index;
+					this.leftMenuStatus(index);
+					this.$emit('update:current', index);
+				})
+			},
+			// 获取一个目标元素的高度
+			getElRect(elClass, dataVal) {
+				new Promise((resolve, reject) => {
+					const query = uni.createSelectorQuery().in(this);
+					query.select('.' + elClass).fields({
+						size: true
+					}, res => {
+						// 如果节点尚未生成,res值为null,循环调用执行
+						if (!res) {
+							setTimeout(() => {
+								this.getElRect(elClass);
+							}, 10);
+							return;
+						}
+						this[dataVal] = res.height;
+						resolve();
+					}).exec();
+				})
+			},
+			// 观测元素相交状态
+			async observer() {
+				this.tabList.map((val, index) => {
+					let observer = uni.createIntersectionObserver(this);
+					// 检测右边scroll-view的id为itemxx的元素与u-cate-tab__right-box的相交状态
+					// 如果跟.u-cate-tab__right-box底部相交,就动态设置左边栏目的活动状态
+					observer.relativeTo('.u-cate-tab__right-box', {
+						top: 0
+					}).observe('#item' + index, res => {
+						if (res.intersectionRatio > 0) {
+							let id = res.id.substring(4);
+							this.leftMenuStatus(id);
+						}
+					})
+				})
+			},
+			// 设置左边菜单的滚动状态
+			async leftMenuStatus(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');
+					await this.getElRect('u-cate-tab__item', 'menuItemHeight');
+				}
+				// 将菜单活动item垂直居中
+				this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
+			},
+			// 获取右边菜单每个item到顶部的距离
+			getMenuItemTop() {
+				return new Promise(resolve => {
+					let selectorQuery = uni.createSelectorQuery().in(this);
+					selectorQuery.selectAll('.u-cate-tab__page-item').boundingClientRect((rects) => {
+						// 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
+						if(!rects.length) {
+							setTimeout(() => {
+								this.getMenuItemTop();
+							}, 10);
+							return ;
+						}
+                        this.rects = rects;
+						rects.forEach((rect) => {
+							// 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
+							this.arr.push(rect.top - rects[0].top);
+						})
+                        resolve();
+					}).exec()
+				})
+			},
+			// 右边菜单滚动
+			async rightScroll(e) {
+				this.oldScrollTop = e.detail.scrollTop;
+                // console.log(e.detail.scrollTop)
+                // console.log(JSON.stringify(this.arr))
+				if(this.arr.length == 0) {
+					await this.getMenuItemTop();
+				}
+				if(this.timer) return ;
+				if(!this.menuHeight) {
+					await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
+				}
+				setTimeout(() => { // 节流
+					this.timer = null;
+					// scrollHeight为右边菜单垂直中点位置
+					let scrollHeight = e.detail.scrollTop + 1;
+                    // console.log(e.detail.scrollTop)
+					for (let i = 0; i < this.arr.length; i++) {
+						let height1 = this.arr[i];
+						let height2 = this.arr[i + 1];
+                        // console.log('i', i)
+                        // console.log('height1', height1)
+                        // console.log('height2', height2)
+						// 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
+						if (!height2 || scrollHeight >= height1 && scrollHeight <= height2) {
+                            // console.log('scrollHeight', scrollHeight)
+                            // console.log('height1', height1)
+                            // console.log('height2', height2)
+							this.leftMenuStatus(i);
+							return ;
+						}
+					}
+				}, 10)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.u-cate-tab {
+		display: flex;
+		flex-direction: column;
+	}
+
+	.u-cate-tab__wrap {
+		flex: 1;
+		display: flex;
+		overflow: hidden;
+	}
+
+	.u-search-inner {
+		background-color: rgb(234, 234, 234);
+		border-radius: 100rpx;
+		display: flex;
+		align-items: center;
+		padding: 10rpx 16rpx;
+	}
+
+	.u-search-text {
+		font-size: 26rpx;
+		color: $u-tips-color;
+		margin-left: 10rpx;
+	}
+
+	.u-cate-tab__view {
+		width: 200rpx;
+		height: 100%;
+	}
+
+	.u-cate-tab__item {
+		height: 110rpx;
+		background: #f6f6f6;
+		box-sizing: border-box;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 26rpx;
+		color: #444;
+		font-weight: 400;
+		line-height: 1;
+	}
+
+	.u-cate-tab__item-active {
+		position: relative;
+		color: #000;
+		font-size: 30rpx;
+		font-weight: 600;
+		background: #fff;
+	}
+
+	.u-cate-tab__item-active::before {
+		content: "";
+		position: absolute;
+		border-left: 4px solid $u-primary;
+		height: 32rpx;
+		left: 0;
+		top: 39rpx;
+	}
+
+	.u-cate-tab__view {
+		height: 100%;
+	}
+
+	.u-cate-tab__right-box {
+		flex: 1;
+		background-color: rgb(250, 250, 250);
+	}
+
+	.u-cate-tab__page-view {
+		padding: 16rpx;
+	}
+
+	.u-cate-tab__page-item {
+		margin-bottom: 30rpx;
+		background-color: #fff;
+		padding: 16rpx;
+		border-radius: 8rpx;
+	}
+
+	.u-cate-tab__page-item:last-child {
+		min-height: 100vh;
+	}
+
+	.item-title {
+		font-size: 26rpx;
+		color: $u-main-color;
+		font-weight: bold;
+	}
+
+	.item-menu-name {
+		font-weight: normal;
+		font-size: 24rpx;
+		color: $u-main-color;
+	}
+
+	.item-container {
+		display: flex;
+		flex-wrap: wrap;
+	}
+
+	.thumb-box {
+		width: 33.333333%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		margin-top: 20rpx;
+	}
+
+	.item-menu-image {
+		width: 120rpx;
+		height: 120rpx;
+	}
+</style>

+ 3 - 2
src/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)
 			},
 		}
 	}

+ 15 - 12
src/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue

@@ -11,7 +11,7 @@
 		    :class="iconClasses"
 		    :style="[iconWrapStyle]"
 		>
-			<slot name="icon">
+			<slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor">
 				<u-icon
 				    class="u-checkbox__icon-wrap__icon"
 				    name="checkbox-mark"
@@ -20,16 +20,17 @@
 				/>
 			</slot>
 		</view>
-		<slot name="label" :label="label" :elDisabled="elDisabled">
-			<text
-				@tap.stop="labelClickHandler"
-				:style="{
-					color: elDisabled ? elInactiveColor : elLabelColor,
-					fontSize: elLabelSize,
-					lineHeight: elLabelSize
-				}"
-			>{{label}}</text>
-		</slot>
+		<view class="u-checkbox__label-wrap cursor-pointer" @tap.stop="labelClickHandler">
+			<slot name="label" :label="label" :elDisabled="elDisabled">
+				<text
+					:style="{
+						color: elDisabled ? elInactiveColor : elLabelColor,
+						fontSize: elLabelSize,
+						lineHeight: elLabelSize
+					}"
+				>{{label}}</text>
+			</slot>
+		</view>
 	</view>
 </template>
 
@@ -247,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)

+ 1 - 1
src/uni_modules/uview-plus/components/u-code/u-code.vue

@@ -107,7 +107,7 @@
 				if(!this.keepRunning || !this.timer) return
 				// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
 				// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
-				if(this.secNum > 0 && this.secNum < this.seconds) {
+				if(this.secNum > 0 && this.secNum <= this.seconds) {
 					// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
 					let nowTimestamp = Math.floor((+ new Date()) / 1000)
 					// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数

+ 6 - 1
src/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
src/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
src/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>

+ 2 - 1
src/uni_modules/uview-plus/components/u-column-notice/columnNotice.js

@@ -19,6 +19,7 @@ export default {
         speed: 80,
         step: false,
         duration: 1500,
-        disableTouch: true
+        disableTouch: true,
+		justifyContent: 'flex-start'
     }
 }

+ 5 - 1
src/uni_modules/uview-plus/components/u-column-notice/props.js

@@ -52,6 +52,10 @@ export const props = defineMixin({
         disableTouch: {
             type: Boolean,
             default: () => defProps.columnNotice.disableTouch
-        }
+        },
+		justifyContent: {
+			type: String,
+			default: () => defProps.columnNotice.justifyContent
+		}
     }
 })

+ 1 - 0
src/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue

@@ -28,6 +28,7 @@
 				v-for="(item, index) in text"
 				:key="index"
 				class="u-notice__swiper__item"
+				:style="{'justifyContent': justifyContent}"
 			>
 				<text
 					class="u-notice__swiper__item__text u-line-1"

+ 6 - 1
src/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: {},
     }
 }

+ 20 - 2
src/uni_modules/uview-plus/components/u-datetime-picker/props.js

@@ -5,11 +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: () => defProps.input.disabled
+        },
+		disabledColor:{
+			type: String,
+			default: () => defProps.input.disabledColor
+		},
         placeholder: {
             type: String,
-            default: () => '请选择'
+            default: () => defProps.input.placeholder
         },
         format: {
             type: String,

+ 58 - 15
src/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue

@@ -1,14 +1,17 @@
 <template>
     <view class="u-datetime-picker">
         <view v-if="hasInput" class="u-datetime-picker__has-input"
-            @click="showByClickInput = !showByClickInput"
+            @click="onShowByClickInput"
         >
-            <u-input
-                :placeholder="placeholder"
-                :readonly="!!showByClickInput"
-                border="surround"
-                v-model="inputValue"
-            ></u-input>
+            <slot name="trigger" :value="inputValue">
+				<up-input
+					:readonly="!!showByClickInput"
+					v-model="inputValue"
+					v-bind="inputPropsInner"
+				></up-input>
+				<div class="input-cover">
+				</div>
+			</slot>
         </view>
         <u-picker
             ref="picker"
@@ -128,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() {
@@ -335,11 +348,12 @@
 			        if (this.filter) {
 			            values = this.filter(type, values)
 						if (!values || (values && values.length == 0)) {
-							uni.showToast({
-								title: '日期filter结果不能为空',
-								icon: 'error',
-								mask: true
-							})
+							// uni.showToast({
+							// 	title: '日期filter结果不能为空',
+							// 	icon: 'error',
+							// 	mask: true
+							// })
+							console.log('日期filter结果不能为空')
 						}
 			        }
 			        return { type, values }
@@ -353,7 +367,8 @@
 			// 得出合法的时间
 			correctValue(value) {
 				const isDateMode = this.mode !== 'time'
-				if (isDateMode && !test.date(value)) {
+				// if (isDateMode && !test.date(value)) {
+				if (isDateMode && !dayjs.unix(value).isValid()) {
 					// 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间
 					value = this.minDate
 				} else if (!isDateMode && !value) {
@@ -421,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
@@ -455,6 +473,12 @@
 			        [`${type}Hour`]: hour,
 			        [`${type}Minute`]: minute
 			    }
+			},
+			onShowByClickInput(){
+				if(!this.disabled){
+					this.showByClickInput = !this.showByClickInput
+				}
+
 			}
 		}
 	}
@@ -463,10 +487,29 @@
 <style lang="scss" scoped>
 	@import '../../libs/css/components.scss';
 	.u-datetime-picker {
+		flex: 1;
         &__has-input {
+			position: relative;
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
             /* #ifndef APP-NVUE */
             width: 100%;
             /* #endif */
+			.input-cover {
+				opacity: 0;
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left:0;
+				right:0;
+				display: flex;
+				flex-direction: column;
+				justify-content: center;
+				border-radius: 4px;
+				border: 1px solid #eee;
+				padding: 0 10px;
+			}
         }
 	}
 </style>

+ 13 - 11
src/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>
@@ -37,7 +37,7 @@
     import { props } from './props';
     import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, sys} from '../../libs/function/index';
+	import { addUnit, getWindowInfo} from '../../libs/function/index';
 	/**
 	 * dropdown 下拉菜单
 	 * @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
@@ -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() {
@@ -166,8 +168,8 @@
 			getContentHeight() {
 				// 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度
 				// 才能让遮罩占满菜单一下,直到屏幕底部的高度
-				// sys()为uview-plus封装的获取设备信息的方法
-				let windowHeight = sys().windowHeight;
+				// getWindowInfo()为uview-plus封装的获取设备信息的方法
+				let windowHeight = getWindowInfo().windowHeight;
 				this.$uGetRect('.u-dropdown__menu').then(res => {
 					// 这里获取的是dropdown的尺寸,在H5上,uniapp获取尺寸是有bug的(以前提出修复过,后来又出现了此bug,目前hx2.8.11版本)
 					// H5端bug表现为元素尺寸的top值为导航栏底部到到元素的上边沿的距离,但是元素的bottom值确是导航栏顶部到元素底部的距离

+ 169 - 0
src/uni_modules/uview-plus/components/u-float-button/u-float-button.vue

@@ -0,0 +1,169 @@
+<template>
+    <view
+        class="u-float-button" :style="{
+            position: 'fixed',
+            top: top,
+            bottom: bottom,
+            right: right,
+        }">
+        <view class="u-float-button__main" @click="clickHandler" :style="{
+            backgroundColor: backgroundColor,
+            color: color,
+			display: 'flex',
+            flexDirection: 'row',
+            justifyContent: 'center',
+            alignItems: 'center',
+            width: width,
+            height: height,
+            borderRadius: '50%',
+            borderColor: borderColor,
+        }">
+            <slot :showList="showList">
+                <up-icon class="cursor-pointer" :class="{'show-list': showList}" name="plus" :color="color"></up-icon>
+            </slot>
+            <view v-if="showList" class="u-float-button__list" :style="{
+                bottom: height
+            }">
+                <slot name="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',
+                            width: width,
+                            height: height,
+                            borderRadius: '50%',
+                            borderColor: item?.borderColor ? item?.borderColor :  borderColor,
+                        }" @click="itemClick(item, index)">
+                            <up-icon :name="item.name" :color="item?.color ? item?.color : color"></up-icon>
+                        </view>
+                    </template>
+                </slot>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import { mpMixin } from '../../libs/mixin/mpMixin';
+import { mixin } from '../../libs/mixin/mixin';
+import { addStyle, addUnit, deepMerge } from '../../libs/function/index';
+/**
+ * FloatButton 悬浮按钮
+ * @description 悬浮按钮常用于屏幕右下角点击展开的操作菜单
+ * @tutorial https://ijry.github.io/uview-plus/components/floatButton.html
+ * @property {String}                backgroundColor      背景颜色
+ * @event {Function} click  点击触发事件
+ * @example <up-float-button></up-float-button>
+ */
+export default {
+    name: 'u-float-button',
+    // #ifdef MP
+    mixins: [mpMixin, mixin],
+    // #endif
+    // #ifndef MP
+    mixins: [mpMixin, mixin],
+    // #endif
+   emits: ['click', 'item-click'],
+    computed: {
+    },
+    props: {
+        // 背景颜色
+        backgroundColor: {
+            type: String,
+            default: '#2979ff'
+        },
+        // 文字颜色
+        color: {
+            type: String,
+            default: '#fff'
+        },
+        // 宽度
+        width: {
+            type: String,
+            default: '50px'
+        },
+        // 高度
+        height: {
+            type: String,
+            default: '50px'
+        },
+        // 边框颜色,默认为空字符串表示无边框
+        borderColor: {
+            type: String,
+            default: ''
+        },
+        // 右侧偏移量
+        right: {
+            type: [String, Number],
+            default: '30px'
+        },
+        // 顶部偏移量,未提供默认值,可能需要根据具体情况设置
+        top: {
+            type: [String, Number],
+            default: '',
+        },
+        // 底部偏移量
+        bottom: {
+            type: String,
+            default: ''
+        },
+        // 是否为菜单项
+        isMenu: {
+            type: Boolean,
+            default: false
+        },
+        list: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            showList: false
+        }
+    },
+    methods: {
+        addStyle,
+        clickHandler(e) {
+            if (this.isMenu) {
+                this.showList = !this.showList
+                this.$emit('click', e)
+            } else {
+                this.$emit('click', e)
+            }
+        },
+        itemClick(item, index) {
+            this.$emit('item-click', {
+                ...item,
+                index
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/components.scss';
+
+.u-float-button {
+    z-index: 999;
+    .show-list {
+        transform: rotate(45deg);
+    }
+    &__list {
+        position: absolute;
+        bottom: 0px;
+        display: flex;
+        flex-direction: column;
+        >view {
+            margin: 5px 0px;
+        }
+    }
+}
+</style>

+ 0 - 2
src/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 {

+ 62 - 20
src/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
 			}
@@ -145,7 +188,7 @@
 			addStyle,
 			addUnit,
 			clickHandler(e) {
-				this.$emit('click', this.index)
+				this.$emit('click', this.index, e)
 				// 是否阻止事件冒泡
 				this.stop && this.preventEvent(e)
 			}
@@ -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 {

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

@@ -2,10 +2,11 @@
 	<u-transition
 		mode="fade"
 		:show="show"
+		:style="transStyle"
 		:duration="fade ? 1000 : 0"
 	>
 		<view
-			class="u-image"
+			class="u-image box-border"
 			@tap="onClick"
 			:style="[wrapStyle, backgroundStyle]"
 		>
@@ -19,9 +20,9 @@
 				:lazy-load="lazyLoad"
 				class="u-image__image"
 				:style="{
-					borderRadius: shape == 'circle' ? '10000px' : addUnit(radius),
 					width: addUnit(width),
-					height: addUnit(height)
+					height: addUnit(height),
+					borderRadius: shape == 'circle' ? '10000px' : addUnit(radius)
 				}"
 			></image>
 			<view
@@ -37,8 +38,6 @@
 				<slot name="loading">
 					<u-icon
 						:name="loadingIcon"
-						:width="width"
-						:height="height"
 					></u-icon>
 				</slot>
 			</view>
@@ -47,6 +46,7 @@
 				class="u-image__error"
 				:style="{
 					borderRadius: shape == 'circle' ? '50%' : addUnit(radius),
+					backgroundColor: this.bgColor,
 					width: addUnit(width),
 					height: addUnit(height)
 				}"
@@ -54,8 +54,6 @@
 				<slot name="error">
 					<u-icon
 						:name="errorIcon"
-						:width="width"
-						:height="height"
 					></u-icon>
 				</slot>
 			</view>
@@ -120,7 +118,6 @@
 					if (!n) {
 						// 如果传入null或者'',或者false,或者undefined,标记为错误状态
 						this.isError = true
-						
 					} else {
 						this.isError = false;
 						this.loading = true;
@@ -129,11 +126,46 @@
 			}
 		},
 		computed: {
+			transStyle() {
+				let style = {};
+				// 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位
+				// #ifdef APP-NVUE
+				style.width = addUnit(this.width);
+				style.height = addUnit(this.height);
+				// #endif
+				// #ifndef APP-NVUE
+				if (this.loading || this.isError || this.width == '100%' || this.mode != 'heightFix') {
+					style.width = addUnit(this.width);
+				} else {
+					style.width = 'fit-content';
+				}
+				if (this.loading || this.isError || this.height == '100%' || this.mode != 'widthFix') {
+					style.height = addUnit(this.height);
+				} else {
+					style.height = 'fit-content';
+				}
+				// #endif
+				return style;
+			},
 			wrapStyle() {
 				let style = {};
 				// 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位
+				// #ifdef APP-NVUE
 				style.width = addUnit(this.width);
 				style.height = addUnit(this.height);
+				// #endif
+				// #ifndef APP-NVUE
+				if (this.loading || this.isError || this.width == '100%' || this.mode != 'heightFix') {
+					style.width = addUnit(this.width);
+				} else {
+					style.width = 'fit-content';
+				}
+				if (this.loading || this.isError || this.height == '100%' || this.mode != 'widthFix') {
+					style.height = addUnit(this.height);
+				} else {
+					style.height = 'fit-content';
+				}
+				// #endif
 				// 如果是显示圆形,设置一个很多的半径值即可
 				style.borderRadius = this.shape == 'circle' ? '10000px' : addUnit(this.radius)
 				// 如果设置圆角,必须要有hidden,否则可能圆角无效
@@ -156,8 +188,8 @@
 		methods: {
 			addUnit,
 			// 点击图片
-			onClick() {
-				this.$emit('click')
+			onClick(e) {
+				this.$emit('click', e)
 			},
 			// 图片加载失败
 			onErrorHandler(err) {
@@ -191,9 +223,9 @@
 			// 移除图片的背景色
 			removeBgColor() {
 				// 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景
-				this.backgroundStyle = {
-					backgroundColor: 'transparent'
-				};
+				// this.backgroundStyle = {
+				// 	backgroundColor: this.bgColor || '#ffffff'
+				// };
 			}
 		}
 	};

+ 9 - 8
src/uni_modules/uview-plus/components/u-index-list/u-index-list.vue

@@ -105,7 +105,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, sys, sleep, getPx } from '../../libs/function/index';
+	import { addUnit, getWindowInfo, sleep, getPx } from '../../libs/function/index';
 
 	// #ifdef APP-NVUE
 	// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
@@ -152,7 +152,7 @@
 				// scroll-view的高度
 				scrollViewHeight: 0,
 				// 系统信息
-				sys: sys(),
+				sys: {},
 				scrolling: false,
 				scrollIntoView: '',
 				pageY: 0,
@@ -188,6 +188,7 @@
 		created() {
 			this.children = []
 			this.anchors = []
+			this.sys = getWindowInfo()
 		},
 		mounted() {
 			this.init()
@@ -320,7 +321,7 @@
 					const {
 						height
 					} = size
-					const sysData = sys()
+					const sysData = getWindowInfo()
 					const windowHeight = sysData.windowHeight
 					let customNavHeight = 0
 					// 消除各端导航栏非原生和原生导致的差异,让索引列表字母对屏幕垂直居中
@@ -358,7 +359,7 @@
 				let index = this.currentIndex;
 				// 对H5的pageY进行修正,这是由于uni-app自作多情在H5中将触摸点的坐标跟H5的导航栏结合导致的问题
 				// #ifdef H5
-				// pageY += sys().windowTop
+				// pageY += getWindowInfo().windowTop
 				// #endif
 				// 对第一和最后一个字母做边界处理,因为用户可能在字母列表上触摸到两端的尽头后依然继续滑动
 				// console.log('top1', top)
@@ -418,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
@@ -476,8 +477,8 @@
 				let children = this.children
 				// #ifdef APP-NVUE
 				// nvue下获取的滚动条偏移为负数,需要转为正数
-				let sys = uni.getSystemInfoSync()
-				scrollTop = Math.abs(e.contentOffset.y) / 10	
+				let sys = getWindowInfo()
+				scrollTop = Math.abs(e.contentOffset.y) / 10
 				// console.log('native', e)
 				// #endif
 
@@ -515,7 +516,7 @@
 					if (scrollTop <= children[0].top || scrollTop >= children[len - 1].top + children[len - 1].height) {
 						this.activeIndex = -1
 						break
-					} else if (!nextItem) { 
+					} else if (!nextItem) {
 						// 当不存在下一个item时,意味着历遍到了最后一个
 						this.activeIndex = len - 1
 						break

+ 8 - 1
src/uni_modules/uview-plus/components/u-input/u-input.vue

@@ -45,6 +45,7 @@
             	    @focus="onFocus"
             	    @confirm="onConfirm"
             	    @keyboardheightchange="onkeyboardheightchange"
+                    @nicknamereview="onnicknamereview"
             	/>
             </view>
             <view
@@ -224,7 +225,7 @@ export default {
         },
     },
     // #ifdef VUE3
-    emits: ['update:modelValue', 'focus', 'blur', 'change', 'confirm', 'clear', 'keyboardheightchange'],
+    emits: ['update:modelValue', 'focus', 'blur', 'change', 'confirm', 'clear', 'keyboardheightchange', 'nicknamereview'],
     // #endif
     methods: {
 		// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
@@ -274,6 +275,9 @@ export default {
 		onkeyboardheightchange(event) {
             this.$emit("keyboardheightchange", event);
         },
+        onnicknamereview(event) {
+            this.$emit("nicknamereview", event);
+        },
         // 内容发生变化,进行处理
         valueChange(value, isOut = false) {
             if(this.clearInput) {
@@ -313,6 +317,9 @@ export default {
          * 无法触发u-form-item的点击事件,这里通过手动调用u-form-item的方法进行触发
          */
         clickHandler() {
+            if (this.disabled || this.readonly) {
+                uni.hideKeyboard();
+            }
             // #ifdef APP-NVUE
             if (os() === "android") {
                 const formItem = $parent.call(this, "u-form-item");

+ 4 - 0
src/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
src/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
src/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
src/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);

+ 2 - 2
src/uni_modules/uview-plus/components/u-list-item/u-list-item.vue

@@ -19,7 +19,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { sys } from '../../libs/function/index';
+	import { getWindowInfo } from '../../libs/function/index';
 	// #ifdef APP-NVUE
 	const dom = uni.requireNativePlugin('dom')
 	// #endif
@@ -39,7 +39,7 @@
 				rect: {},
 				index: 0,
 				show: true,
-				sys: sys()
+				sys: getWindowInfo()
 			}
 		},
 		computed: {

+ 2 - 2
src/uni_modules/uview-plus/components/u-list/u-list.vue

@@ -50,7 +50,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, addStyle, deepMerge, sleep, sys } from '../../libs/function/index';
+	import { addUnit, addStyle, deepMerge, sleep, getWindowInfo } from '../../libs/function/index';
 	// #ifdef APP-NVUE
 	const dom = uni.requireNativePlugin('dom')
 	// #endif
@@ -90,7 +90,7 @@
 				innerScrollTop: 0,
 				// vue下,scroll-view在上拉加载时的偏移值
 				offset: 0,
-				sys: sys()
+				sys: getWindowInfo()
 			}
 		},
 		computed: {

+ 5 - 1
src/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
src/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
+        }
     }
 })

+ 33 - 6
src/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,10 +118,11 @@
 	 * @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有效
-	 * @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" />
+	 * @example <u-modal :show="show" />
 	 */
 	export default {
 		name: 'u-modal',
@@ -137,7 +139,14 @@
 				if (n && this.loading) this.loading = false
 			}
 		},
-		emits: ["confirm", "cancel", "close"],
+		emits: ["confirm", "cancel", "close", "update:show", 'cancelOnAsync'],
+		computed: {
+			contentStyleCpu() {
+				let style = this.contentStyle;
+				style.paddingTop = `${this.title ? 12 : 25}px`
+				return style;
+			}
+		},
 		methods: {
 			addUnit,
 			// 点击确定按钮
@@ -145,11 +154,28 @@
 				// 如果配置了异步关闭,将按钮值为loading状态
 				if (this.asyncClose) {
 					this.loading = true;
+				} else {
+					this.$emit('update:show', false)
 				}
 				this.$emit('confirm')
 			},
 			// 点击取消按钮
 			cancelHandler() {
+				// 如果点击了确定按钮,确定按钮正在请求接口执行异步操作,那么限制不能取消。
+				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')
 			},
 			// 点击遮罩
@@ -159,6 +185,7 @@
 			// 透明遮罩的子元素做了.stop处理,所以点击内容区,也不会导致误触发
 			clickHandler() {
 				if (this.closeOnClickOverlay) {
+					this.$emit('update:show', false)
 					this.$emit('close')
 				}
 			}

+ 0 - 1
src/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,

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

@@ -20,6 +20,7 @@ export default {
         rightText: '',
         rightIcon: '',
         title: '',
+        titleColor: '',
         bgColor: '#ffffff',
         titleWidth: '400rpx',
         height: '44px',

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

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

+ 17 - 9
src/uni_modules/uview-plus/components/u-navbar/u-navbar.vue

@@ -4,13 +4,13 @@
 			class="u-navbar__placeholder"
 			v-if="fixed && placeholder"
 			:style="{
-				height: addUnit(getPx(height) + sys().statusBarHeight,'px'),
+				height: addUnit(getPx(height) + getWindowInfo().statusBarHeight,'px'),
 			}"
 		></view>
 		<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"
@@ -47,6 +47,7 @@
 						class="u-line-1 u-navbar__content__title"
 						:style="[{
 							width: addUnit(titleWidth),
+							color: titleColor,
 						}, addStyle(titleStyle)]"
 					>{{ title }}</text>
 				</slot>
@@ -76,7 +77,8 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, addStyle, getPx, sys } from '../../libs/function/index';
+	import config  from '../../libs/config/config';
+	import { addUnit, addStyle, getPx, getWindowInfo } from '../../libs/function/index';
 	/**
 	 * Navbar 自定义导航栏
 	 * @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uni-app带的导航栏。
@@ -85,12 +87,14 @@
 	 * @property {Boolean}			placeholder			固定在顶部时,是否生成一个等高元素,以防止塌陷 (默认 false )
 	 * @property {Boolean}			fixed				导航栏是否固定在顶部 (默认 false )
 	 * @property {Boolean}			border				导航栏底部是否显示下边框 (默认 false )
-	 * @property {String}			leftIcon			左边返回图标的名称,只能为uView自带的图标 (默认 'arrow-left' )
+	 * @property {String}			leftIcon			左边返回图标的名称,只能为uview-pls自带的图标 (默认 'arrow-left' )
 	 * @property {String}			leftText			左边的提示文字
 	 * @property {String}			rightText			右边的提示文字
-	 * @property {String}			rightIcon			右边返回图标的名称,只能为uView自带的图标
+	 * @property {String}			rightIcon			右边返回图标的名称,只能为uview-plus自带的图标
 	 * @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 )
@@ -112,14 +116,18 @@
 		methods: {
 			addStyle,
 			addUnit,
-			sys,
+			getWindowInfo,
 			getPx,
 			// 点击左侧区域
 			leftClick() {
 				// 如果配置了autoBack,自动返回上一页
 				this.$emit('leftClick')
-				if(this.autoBack) {
-					uni.navigateBack()
+				if (config.interceptor.navbarLeftClick != null) {
+					config.interceptor.navbarLeftClick()
+				} else {
+					if(this.autoBack) {
+						uni.navigateBack()
+					}
 				}
 			},
 			// 点击右侧区域
@@ -163,7 +171,7 @@
 
 			&__left {
 				left: 0;
-				
+
 				&--hover {
 					opacity: 0.7;
 				}

+ 2 - 2
src/uni_modules/uview-plus/components/u-no-network/u-no-network.vue

@@ -46,7 +46,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { toast } from '../../libs/function/index';
+	import { toast, getDeviceInfo } from '../../libs/function/index';
 	/**
 	 * noNetwork 无网络提示
 	 * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。
@@ -67,7 +67,7 @@
 			}
 		},
 		mounted() {
-			this.isIOS = (uni.getSystemInfoSync().platform === 'ios')
+			this.isIOS = (getDeviceInfo().platform === 'ios')
 			uni.onNetworkStatusChange((res) => {
 				this.isConnected = res.isConnected
 				this.networkType = res.networkType

+ 2 - 1
src/uni_modules/uview-plus/components/u-notice-bar/noticeBar.js

@@ -22,6 +22,7 @@ export default {
         duration: 2000,
         disableTouch: true,
         url: '',
-        linkType: 'navigateTo'
+        linkType: 'navigateTo',
+		justifyContent: 'flex-start'
     }
 }

+ 5 - 1
src/uni_modules/uview-plus/components/u-notice-bar/props.js

@@ -67,6 +67,10 @@ export const props = defineMixin({
         linkType: {
             type: String,
             default: () => defProps.noticeBar.linkType
-        }
+        },
+		justifyContent: {
+            type: String,
+            default: () => defProps.noticeBar.justifyContent
+        },
     }
 })

+ 1 - 0
src/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue

@@ -17,6 +17,7 @@
 				:disable-touch="disableTouch"
 				:fontSize="fontSize"
 				:duration="duration"
+				:justifyContent="justifyContent"
 				@close="close"
 				@click="click"
 			></u-column-notice>

+ 5 - 1
src/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
src/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
src/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)
 			},
 			// 对值扩大后进行四舍五入,再除以扩大因子,避免出现浮点数操作的精度问题

+ 1 - 0
src/uni_modules/uview-plus/components/u-overlay/u-overlay.vue

@@ -5,6 +5,7 @@
 	    :duration="duration"
 	    :custom-style="overlayStyle"
 	    @click="clickHandler"
+		@touchmove.stop.prevent="noop"
 	>
 		<slot />
 	</u-transition>

+ 283 - 0
src/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>

+ 38 - 27
src/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',
@@ -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) {
@@ -407,9 +426,7 @@ export default {
 ._a {
   padding: 1.5px 0 1.5px 0;
   color: #366092;
-  /* #ifndef APP-NVUE */
   word-break: break-all;
-  /* #endif */
 }
 
 /* a 标签点击态效果 */
@@ -427,9 +444,7 @@ export default {
 /* 内部样式 */
 
 ._block {
-  /* #ifndef APP-NVUE */
   display: block;
-  /* #endif */
 }
 
 ._b,
@@ -476,9 +491,7 @@ export default {
 ._h4,
 ._h5,
 ._h6 {
-  /* #ifndef APP-NVUE */
   display: block;
-  /* #endif */
   font-weight: bold;
 }
 
@@ -500,9 +513,7 @@ export default {
 
 ._ol,
 ._ul {
-  /* #ifndef APP-NVUE */
   display: block;
-  /* #endif */
   padding-left: 40px;
   margin: 1em 0;
 }
@@ -581,4 +592,4 @@ export default {
   height: 225px;
 }
 /* #endif */
-</style>
+</style>

+ 5 - 5
src/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

+ 79 - 12
src/uni_modules/uview-plus/components/u-parse/parser.js

@@ -71,16 +71,24 @@ const config = {
     viewbox: 'viewBox',
     attributename: 'attributeName',
     repeatcount: 'repeatCount',
-    repeatdur: 'repeatDur'
+    repeatdur: 'repeatDur',
+    foreignobject: 'foreignObject'
   }
 }
-const tagSelector={}
-const {
-  windowWidth,
+const tagSelector = {}
+let windowWidth, system
+// #ifdef MP-WEIXIN
+if (uni.canIUse('getWindowInfo')) {
+  windowWidth = uni.getWindowInfo().windowWidth
+  system = uni.getDeviceInfo().system
+} else {
+// #endif
+  const systemInfo = uni.getSystemInfoSync()
+  windowWidth = systemInfo.windowWidth
   // #ifdef MP-WEIXIN
-  system
-  // #endif
-} = uni.getSystemInfoSync()
+  system = systemInfo.system
+}
+// #endif
 const blankChar = makeMap(' ,\r,\n,\t,\f')
 let idIndex = 0
 
@@ -321,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 可用
   }
 }
 
@@ -331,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 {
       // 剩余的移除以减小大小
@@ -457,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://')) {
@@ -551,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)
@@ -677,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) {
@@ -703,6 +733,7 @@ Parser.prototype.popNode = function () {
     node.children = undefined
     // #endif
     this.xml = false
+    config.ignoreTags.style = true
     return
   }
 
@@ -839,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'
@@ -856,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 || [])
           }
@@ -982,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') {

+ 1 - 1
src/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 容器的样式

+ 128 - 0
src/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>

+ 8 - 2
src/uni_modules/uview-plus/components/u-picker/picker.js

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

+ 25 - 1
src/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,
@@ -99,5 +118,10 @@ export const props = defineMixin({
 			type: Boolean,
 			default: false
 		},
+		// 层级
+		zIndex: {
+		    type: [String, Number],
+		    default: () => defProps.picker.zIndex
+		},
     }
 })

+ 159 - 37
src/uni_modules/uview-plus/components/u-picker/u-picker.vue

@@ -1,15 +1,22 @@
 <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"
+			:zIndex="zIndex"
 			@close="closeHandler"
 		>
 			<view class="u-picker">
@@ -47,6 +54,7 @@
 						<view
 							v-if="testArray(item)"
 							class="u-picker__view__column__item u-line-1"
+							:class="[index1 === innerIndex[index] && 'u-picker__view__column__item--selected']"
 							v-for="(item1, index1) in item"
 							:key="index1"
 							:style="{
@@ -112,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'],
+	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)) {
@@ -168,6 +248,8 @@ export default {
                 if (this.hasInput) {
                     this.showByClickInput = false
                 }
+				this.setDefault()
+				this.$emit('update:show', false)
 				this.$emit('close')
 			}
 		},
@@ -176,14 +258,38 @@ 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,
 				value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
@@ -197,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]
@@ -211,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,会因为循环引用而报错
@@ -298,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 {

+ 30 - 5
src/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"
@@ -17,10 +25,12 @@
 			@afterEnter="afterEnter"
 			@click="clickHandler"
 		>
+			<!-- @click.stop不能去除,去除会导致居中模式下点击内容区域触发关闭弹窗 -->
 			<view
 				class="u-popup__content"
 				:style="[contentStyle]"
-				@tap.stop="noop"
+				@click.stop="noop"
+				@touchmove.stop.prevent="noop"
 			>
 				<u-status-bar v-if="safeAreaInsetTop"></u-status-bar>
 				<slot></slot>
@@ -41,6 +51,7 @@
 				</view>
 				<u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom>
 			</view>
+			<slot name="bottom"></slot>
 		</u-transition>
 	</view>
 </template>
@@ -49,7 +60,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, addStyle, deepMerge, sleep, sys } from '../../libs/function/index';
+	import { addUnit, addStyle, deepMerge, sleep, getWindowInfo } from '../../libs/function/index';
 	/**
 	 * popup 弹窗
 	 * @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
@@ -72,7 +83,7 @@
 	 * @property {Object}			customStyle			组件的样式,对象形式
 	 * @event {Function} open 弹出层打开
 	 * @event {Function} close 弹出层收起
-	 * @example <u-popup v-model="show"><text>出淤泥而不染,濯清涟而不妖</text></u-popup>
+	 * @example <u-popup v-model:show="show"><text>出淤泥而不染,濯清涟而不妖</text></u-popup>
 	 */
 	export default {
 		name: 'u-popup',
@@ -137,7 +148,7 @@
 				// 不使用css方案,是因为nvue不支持css的iPhoneX安全区查询属性
 				const {
 					safeAreaInsets
-				} = sys()
+				} = getWindowInfo()
 				if (this.mode !== 'center') {
 					style.flex = 1
 				}
@@ -186,6 +197,9 @@
 					this.$emit('close')
 				}
 			},
+			open(e) {
+				this.$emit('update:show', true)
+			},
 			close(e) {
 				this.$emit('update:show', false)
 				this.$emit('close')
@@ -238,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;

+ 14 - 6
src/uni_modules/uview-plus/components/u-qrcode/qrcode.js

@@ -1128,12 +1128,20 @@ let QRCode = {};
                     ctxi.setLineWidth(lineWidth);
                     ctxi.setFillStyle(options.background);
                     ctxi.setStrokeStyle(options.background);
-                    ctxi.beginPath(); // draw top and top right corner 
+                    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();
@@ -1232,4 +1240,4 @@ let QRCode = {};
     };
 })()
 
-export default QRCode
+export default QRCode

+ 35 - 16
src/uni_modules/uview-plus/components/u-qrcode/u-qrcode.vue

@@ -99,6 +99,10 @@ export default {
 			type: String,
 			default: '生成中'
 		},
+		allowPreview: {
+			type: Boolean,
+			default: false
+		},
 	},
 	emits: ['result', 'longpress'],
 	data() {
@@ -189,26 +193,31 @@ export default {
 				});
 			}
 		},
-		preview() {
+		preview(e) {
 			// 预览图片
 			// console.log(this.result)
-			uni.previewImage({
-				urls: [this.result],
-				longPressActions: {
-					itemList: ['保存二维码图片'],
-					success: function(data) {
-						// console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
-						switch (data.tapIndex) {
-							case 0:
-								that._saveCode();
-								break;
+			if (this.allowPreview) {
+				uni.previewImage({
+					urls: [this.result],
+					longPressActions: {
+						itemList: ['保存二维码图片'],
+						success: function(data) {
+							// console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
+							switch (data.tapIndex) {
+								case 0:
+									that._saveCode();
+									break;
+							}
+						},
+						fail: function(err) {
+							console.log(err.errMsg);
 						}
-					},
-					fail: function(err) {
-						console.log(err.errMsg);
 					}
-				}
-			});
+				});
+			}
+			this.$emit('preview', {
+				url: this.result
+			}, e)
 		},
 		longpress() {
 			this.$emit('longpress', this.result)
@@ -281,6 +290,16 @@ export default {
 		left: 0;
 		right: 0;
 	}
+
+    /* #ifdef MP-TOUTIAO */
+    /**字节小程序在编译时会出现一个 [hidde]:{ display: none !important; } 这个样式
+     * 会导致canvas 隐藏掉 没有找到具体原因先这样处理
+     */
+    &__canvas {
+        display: block !important;
+    }
+    /* #endif */
+
 	&__content {
 		position: relative;
 

+ 6 - 1
src/uni_modules/uview-plus/components/u-radio-group/props.js

@@ -90,6 +90,11 @@ export const props = defineMixin({
         iconPlacement: {
             type: String,
             default: () => defProps.radio.iconPlacement
-        }
+        },
+        // item 之间的间距
+		gap: {
+			type: [String, Number],
+			default: () => defProps.radioGroup.gap
+		}
     }
 })

+ 4 - 3
src/uni_modules/uview-plus/components/u-radio-group/radioGroup.js

@@ -3,8 +3,8 @@
  * @Description  :
  * @version      : 1.0
  * @Date         : 2021-08-20 16:44:21
- * @LastAuthor   : LQ
- * @lastTime     : 2021-08-20 17:03:12
+ * @LastAuthor   : CPS
+ * @lastTime     : 2024-11-05 16:01:12
  * @FilePath     : /u-view2.0/uview-ui/libs/config/props/radioGroup.js
  */
 export default {
@@ -25,6 +25,7 @@ export default {
         iconColor: '#ffffff',
         iconSize: 12,
         borderBottom: false,
-        iconPlacement: 'left'
+        iconPlacement: 'left',
+        gap: "10px"
     }
 }

+ 10 - 1
src/uni_modules/uview-plus/components/u-radio-group/u-radio-group.vue

@@ -2,6 +2,7 @@
 	<view
 	    class="u-radio-group"
 	    :class="bemClass"
+			:style="radioGroupStyle"
 	>
 		<slot></slot>
 	</view>
@@ -11,6 +12,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
+	import { addUnit, addStyle, deepMerge } from '../../libs/function/index';
 
 	/**
 	 * radioRroup 单选框父组件
@@ -32,7 +34,8 @@
 	 * @property {String | Number}				iconSize		图标的大小,单位px (默认 12 )
 	 * @property {Boolean}						borderBottom	placement为row时,是否显示下边框 (默认 false )
 	 * @property {String}						iconPlacement	图标与文字的对齐方式 (默认 'left' )
-     * @property {Object}						customStyle		组件的样式,对象形式
+	 * @property {Object}						gap				item 之间的间距
+   * @property {Object}						customStyle		组件的样式,对象形式
 	 * @event {Function} change 任一个radio状态发生变化时触发
 	 * @example <u-radio-group v-model="value"></u-radio-group>
 	 */
@@ -60,6 +63,12 @@
 				// this.bem为一个computed变量,在mixin中
 				return this.bem('radio-group', ['placement'])
 			},
+			radioGroupStyle() {
+				const style = {
+					gap: addUnit(this.gap)
+				};
+				return deepMerge(style, addStyle(this.customStyle));
+			}
 		},
 		watch: {
 			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件

+ 13 - 10
src/uni_modules/uview-plus/components/u-radio/u-radio.vue

@@ -11,7 +11,7 @@
 		    :class="iconClasses"
 		    :style="[iconWrapStyle]"
 		>
-			<slot name="icon">
+			<slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor">
 				<u-icon
 				    class="u-radio__icon-wrap__icon"
 				    name="checkbox-mark"
@@ -20,15 +20,18 @@
 				/>
 			</slot>
 		</view>
-		<text
-			class="u-radio__text"
-		    @tap.stop="labelClickHandler"
-		    :style="{
-				color: elDisabled ? elInactiveColor : elLabelColor,
-				fontSize: elLabelSize,
-				lineHeight: elLabelSize
-			}"
-		>{{label}}</text>
+		<view class="u-radio__label-wrap cursor-pointer" @tap.stop="labelClickHandler">
+			<slot name="label" :label="label" :elDisabled="elDisabled">
+				<text
+					class="u-radio__text"
+					:style="{
+						color: elDisabled ? elInactiveColor : elLabelColor,
+						fontSize: elLabelSize,
+						lineHeight: elLabelSize
+					}"
+				>{{label}}</text>
+			</slot>
+		</view>
 	</view>
 </template>
 

+ 2 - 2
src/uni_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue

@@ -11,7 +11,7 @@
 	import { props } from "./props.js";
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addStyle, deepMerge, addUnit, sys } from '../../libs/function/index';
+	import { addStyle, deepMerge, addUnit, getWindowInfo } from '../../libs/function/index';
 	/**
 	 * SafeBottom 底部安全区
 	 * @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
@@ -36,7 +36,7 @@
 				const style = {};
 				// #ifdef APP-NVUE || MP-TOUTIAO
 				// nvue下,高度使用js计算填充
-				style.height = addUnit(sys().safeAreaInsets.bottom, 'px');
+				style.height = addUnit(getWindowInfo().safeAreaInsets.bottom, 'px');
 				// #endif
 				return deepMerge(style, addStyle(this.customStyle));
 			},

+ 4 - 0
src/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
src/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
src/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
src/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>

+ 29 - 11
src/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() {
 		},
@@ -166,6 +176,10 @@
 				// #ifndef APP-NVUE
 				this.$uGetRect('.u-slider__base').then(rect => {
 					this.sliderRect = rect;
+					// console.log('sliderRect', this.sliderRect)
+					if (this.sliderRect.width == 0) {
+						console.info('如在弹窗等元素中使用,请使用v-if来显示滑块,否则无法计算长度。')
+					}
 					this.init()
 				});
 				// #endif
@@ -265,8 +279,8 @@
 				this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + parseFloat(this.min);
 				this.status = 'moving';
 				// 发出moving事件
-				this.$emit('changing');
-				this.updateValue(this.newValue, true, index);
+				let $crtFmtValue = this.updateValue(this.newValue, true, index);
+				this.$emit('changing', $crtFmtValue);
 			},
 			onTouchMove(event, index = 1) {
 				if (this.disabled) return;
@@ -274,7 +288,7 @@
 				// 触摸后第一次移动已经将status设置为moving状态,故触摸第二次移动不会触发本事件
 				if (this.status == 'start') this.$emit('start');
 				let touches = event.touches[0];
-				console.log('touchs', touches)
+				// console.log('touchs', touches)
 				// 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
 				let clientX = 0;
 				// #ifndef APP-NVUE
@@ -289,14 +303,14 @@
 				this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + parseFloat(this.min);
 				this.status = 'moving';
 				// 发出moving事件
-				this.$emit('changing');
-				this.updateValue(this.newValue, true, index);
+				let $crtFmtValue = this.updateValue(this.newValue, true, index);
+				this.$emit('changing', $crtFmtValue);
 			},
 			onTouchEnd(event, index = 1) {
 				if (this.disabled) return;
 				if (this.status === 'moving') {
-					this.updateValue(this.newValue, false, index);
-					this.$emit('change');
+					let $crtFmtValue = this.updateValue(this.newValue, false, index);
+					this.$emit('change', $crtFmtValue);
 				}
 				this.status = 'end';
 			},
@@ -369,7 +383,11 @@
 					default:
 						break;
 				}
-				
+				if (this.isRange) {
+					return this.rangeValue
+				} else {
+					return valueFormat
+				}
 			},
 			format(value, index = 1) {
 				// 将小数变成整数,为了减少对视图的更新,造成视图层与逻辑层的阻塞

+ 6 - 1
src/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
src/uni_modules/uview-plus/components/u-status-bar/statusBar.js

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

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

@@ -2,6 +2,7 @@
 	<view
 	    :style="[style]"
 	    class="u-status-bar"
+		:class="[isH5 && 'u-safe-area-inset-top']"
 	>
 		<slot />
 	</view>
@@ -11,7 +12,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, addStyle, deepMerge, sys } from '../../libs/function/index';
+	import { addUnit, addStyle, deepMerge, getWindowInfo } from '../../libs/function/index';
 	/**
 	 * StatbusBar 状态栏占位
 	 * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
@@ -25,13 +26,26 @@
 		mixins: [mpMixin, mixin, props],
 		data() {
 			return {
+				isH5: false
 			}
 		},
+		created() {
+			// #ifdef H5
+			this.isH5 = true
+			// #endif
+		},
+		emits: ['update:height'],
 		computed: {
 			style() {
 				const style = {}
 				// 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式
-				style.height = addUnit(sys().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))
 			}

+ 16 - 5
src/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]">
-			<u-text :text="title" :type="parentData.current == index ? 'main' : 'content'" lineHeight="20px"
-				:size="parentData.current == index ? 14 : 13"></u-text>
-			<slot name="desc">
-				<u-text :text="desc" type="tips" size="12"></u-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"

+ 9 - 4
src/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"
 		>
@@ -17,7 +17,7 @@
 	import { props } from './props';
 	import { mpMixin } from '../../libs/mixin/mpMixin';
 	import { mixin } from '../../libs/mixin/mixin';
-	import { addUnit, addStyle, deepMerge, getPx, guid, sys, os } from '../../libs/function/index';
+	import { addUnit, addStyle, deepMerge, getPx, guid, getDeviceInfo, os } from '../../libs/function/index';
 	import zIndex from '../../libs/config/zIndex';
 	/**
 	 * sticky 吸顶
@@ -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]
 				})
@@ -150,7 +155,7 @@
 				// #endif
 
 				// 如果安卓版本高于8.0,依然认为是支持css sticky的(因为安卓7在某些机型,可能不支持sticky)
-				if (os() === 'android' && Number(sys().system) > 8) {
+				if (os() === 'android' && Number(getDeviceInfo().system) > 8) {
 					this.cssSticky = true
 				}
 

+ 3 - 1
src/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,6 +217,8 @@ export default {
             // #endif
         },
         clickHandler(index) {
+            this.innerCurrent = index;
+			this.$emit('update:current', index);
             this.$emit("change", index);
         },
     },

+ 5 - 0
src/uni_modules/uview-plus/components/u-swipe-action-item/index.wxs

@@ -218,6 +218,11 @@ function sizeChange(newValue, oldValue, ownerInstance, instance) {
 		}
 	}
 	state.buttonsWidth = buttonsWidth
+
+	// 支持默认打开
+	if (state.show) {
+		openSwipeAction(instance, ownerInstance)
+	}
 }
 
 module.exports = {

+ 4 - 0
src/uni_modules/uview-plus/components/u-swipe-action-item/props.js

@@ -7,6 +7,10 @@ export const props = defineMixin({
             type: Boolean,
             default: () => defProps.swipeActionItem.show
         },
+        closeOnClick: {
+            type: Boolean,
+            default: () => defProps.swipeActionItem.closeOnClick
+        },
         // 标识符,如果是v-for,可用index索引值
         name: {
             type: [String, Number],

+ 1 - 0
src/uni_modules/uview-plus/components/u-swipe-action-item/swipeActionItem.js

@@ -11,6 +11,7 @@ export default {
     // swipeActionItem 组件
     swipeActionItem: {
         show: false,
+        closeOnClick: true,
         name: '',
         disabled: false,
         threshold: 20,

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików