Bladeren bron

feat(rui-du): 重构日报汇总分组展示与筛选

- 新增日报汇总接口并接入年月日分组数据
- 支持顶部关键字搜索和悬浮条件筛选
- 新增按日期与按队伍视图切换
- 精简日报详情卡片展示
Zimo 2 weken geleden
bovenliggende
commit
14fc0ef9d4
29 gewijzigde bestanden met toevoegingen van 3601 en 622 verwijderingen
  1. 8 0
      api/ruiduReport.js
  2. 2 2
      config/env.dev.js
  3. 2 2
      config/env.prod.js
  4. 328 328
      pages/ruiDu/index.vue
  5. 1099 290
      pages/ruiDuReport/daily-detail.vue
  6. 48 0
      uni_modules/uni-collapse/changelog.md
  7. 402 0
      uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue
  8. 147 0
      uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue
  9. 106 0
      uni_modules/uni-collapse/package.json
  10. 12 0
      uni_modules/uni-collapse/readme.md
  11. 13 0
      uni_modules/uv-cell/changelog.md
  12. 15 0
      uni_modules/uv-cell/components/uv-cell-group/props.js
  13. 63 0
      uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue
  14. 116 0
      uni_modules/uv-cell/components/uv-cell/props.js
  15. 209 0
      uni_modules/uv-cell/components/uv-cell/uv-cell.vue
  16. 89 0
      uni_modules/uv-cell/package.json
  17. 11 0
      uni_modules/uv-cell/readme.md
  18. 5 0
      uni_modules/uv-collapse/changelog.md
  19. 60 0
      uni_modules/uv-collapse/components/uv-collapse-item/props.js
  20. 229 0
      uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue
  21. 20 0
      uni_modules/uv-collapse/components/uv-collapse/props.js
  22. 86 0
      uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue
  23. 89 0
      uni_modules/uv-collapse/package.json
  24. 11 0
      uni_modules/uv-collapse/readme.md
  25. 10 0
      uni_modules/uv-subsection/changelog.md
  26. 54 0
      uni_modules/uv-subsection/components/uv-subsection/props.js
  27. 269 0
      uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue
  28. 87 0
      uni_modules/uv-subsection/package.json
  29. 11 0
      uni_modules/uv-subsection/readme.md

+ 8 - 0
api/ruiduReport.js

@@ -8,6 +8,14 @@ export function getRuiduReportPage(params) {
   });
 }
 
+export function getRuiduReportSummary(params) {
+  return request({
+    url: "/pms/iot-rd-daily-report/reportSummary",
+    method: "get",
+    params,
+  });
+}
+
 export function getRuiduReportTeamPage(params) {
   return request({
     url: "/pms/iot-rd-daily-report/teamReports",

+ 2 - 2
config/env.dev.js

@@ -1,8 +1,8 @@
 // 开发环境配置
 export default {
   // apiUrl: "http://192.168.188.198:48080",
-  apiUrl: "https://iot.deepoil.cc:5443",
-  // apiUrl: 'https://aims.deepoil.cc', //正式
+  apiUrl: "http://192.168.188.86:48080",
+  // apiUrl: "https://aims.deepoil.cc", //正式
   apiUrlSuffix: "/admin-api",
   // 其他开发环境配置...
   corpId: "dingbe7f9a7e8cffa2bd35c2f4657eb6378f", //钉钉微应用 企业的CorpID - 正式环境

+ 2 - 2
config/env.prod.js

@@ -1,8 +1,8 @@
 // 生产环境配置
 export default {
   // apiUrl: 'http://192.168.1.63:8888',
-  apiUrl: "https://aims.deepoil.cc", //测试
-  // apiUrl: "https://iot.deepoil.cc:5443", //正式
+  // apiUrl: "https://aims.deepoil.cc", //测试
+  apiUrl: "https://iot.deepoil.cc:5443", //正式
   apiUrlSuffix: "/admin-api",
   // 其他开发环境配置...
   corpId: "dingbe7f9a7e8cffa2bd35c2f4657eb6378f", //钉钉微应用 企业的CorpID - 正式环境

+ 328 - 328
pages/ruiDu/index.vue

@@ -1,328 +1,328 @@
-<template>
-  <z-paging class="page" ref="paging" v-model="dataList" @query="queryList">
-    <!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
-    <!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
-    <template #top>
-      <uni-row class="search-row flex-row align-center justify-between">
-        <uni-col :span="19">
-          <uni-easyinput
-            v-model="orderName"
-            :styles="inputStyles"
-            :placeholderStyle="placeholderStyle"
-            :placeholder="$t('operation.searchText')">
-          </uni-easyinput>
-        </uni-col>
-        <uni-col :span="5" class="flex-row justify-end">
-          <button
-            class="mini-btn"
-            type="primary"
-            size="mini"
-            @click="searchList">
-            {{ $t("operation.search") }}
-          </button>
-        </uni-col>
-      </uni-row>
-      <button
-        class="create-btn"
-        type="primary"
-        size="mini"
-        @click="navigatorCreate">
-        新增日报
-      </button>
-    </template>
-
-    <view class="list">
-      <view class="item" v-for="(item, index) in dataList" :key="index">
-        <view
-          class="item-module flex-row align-center justify-between"
-          :class="{
-            tobeFilled: item.status == 0,
-          }">
-          <!-- 创建时间 -->
-          <view class="module-name">
-            {{ item.createTime ? formatDate(item.createTime) : "" }}
-          </view>
-          <!-- 工单状态 -->
-          <view
-            class="module-status"
-            :class="{
-              pending: item.status === 0, //待填写
-            }">
-            {{ fillStatusDict[item.status] }}
-          </view>
-        </view>
-        <view class="item-content">
-          <!-- 带班干部 -->
-          <view class="item-title flex-row">
-            <span class="item-title-width">{{ $t("ruiDu.shiftLeader") }}:</span>
-            <span>{{ item.responsiblePersonNames }}</span>
-          </view>
-          <!-- 日报名称 -->
-          <view class="item-title flex-row">
-            <span class="item-title-width">{{ $t("ruiDu.reportName") }}:</span>
-            <span>{{ item.reportName }}</span>
-          </view>
-          <!-- 项目 -->
-          <view class="item-title flex-row">
-            <span class="item-title-width">{{ $t("ruiDu.project") }}:</span>
-            <span>{{ item.contractName }}</span>
-          </view>
-          <!-- 任务 -->
-          <view class="item-title flex-row">
-            <span class="item-title-width">{{ $t("ruiDu.task") }}:</span>
-            <span>{{ item.taskName }}</span>
-          </view>
-
-          <!-- 创建时间 -->
-          <view class="item-title flex-row">
-            <span class="item-title-width"
-              >{{ $t("operation.createTime") }}:</span
-            >
-            <span>{{
-              item.createTime ? formatTime(item.createTime) : ""
-            }}</span>
-          </view>
-          <!-- 填写时间 -->
-          <view class="item-title flex-row">
-            <span class="item-title-width"
-              >{{ $t("operation.fillTime") }}:</span
-            >
-            <span>{{ item.fillTime ? formatTime(item.fillTime) : "" }}</span>
-          </view>
-          <view class="item-title flex-row align-center">
-            <span class="item-title-width">{{ "审批状态" }}:</span>
-            <span
-              :class="[
-                'my-tag',
-                item.auditStatus === 0
-                  ? 'my-tag-info'
-                  : item.auditStatus === 10
-                    ? 'my-tag-primary'
-                    : item.auditStatus === 20
-                      ? 'my-tag-success'
-                      : 'my-tag-danger',
-              ]">
-              {{
-                item.auditStatus === 0
-                  ? "待提交"
-                  : item.auditStatus === 10
-                    ? "待审批"
-                    : item.auditStatus === 20
-                      ? "审批通过"
-                      : "审批拒绝"
-              }}
-            </span>
-          </view>
-          <view class="item-title flex-row align-center">
-            <span class="item-title-width">{{ "非生产时间" }}:</span>
-            <span
-              :class="[
-                'my-tag',
-                item.nonProductFlag ? 'my-tag-success' : 'my-tag-danger',
-              ]">
-              {{ item.nonProductFlag ? "已填写" : "未填写" }}
-            </span>
-          </view>
-        </view>
-        <view class="item-btn flex-row align-center justify-end">
-          <!--  状态:0(待填写),1(已完成),2(填写中),3(忽略) -->
-
-          <!-- 查看 -->
-          <button type="primary" plain="true" @click="navigatorDetail(item)">
-            {{ $t("operation.view") }}
-          </button>
-          <!-- 填写 -->
-          <button
-            v-show="item.status === 0"
-            type="primary"
-            @click="navigatorEdit(item)">
-            {{ $t("operation.fill") }}
-          </button>
-          <button
-            v-show="item.auditStatus === 20 && rdNonProductFlag"
-            type="primary"
-            @click="navigatorEdit(item, 'true')">
-            {{ $t("operation.time") }}
-          </button>
-        </view>
-      </view>
-    </view>
-  </z-paging>
-</template>
-
-<script setup>
-import { ref, reactive, nextTick } from "vue";
-import { onShow } from "@dcloudio/uni-app";
-import dayjs from "dayjs";
-import { getRuiDuReportPage } from "@/api/ruiDu";
-import { useDataDictStore } from "@/store/modules/dataDict";
-import { getLoginUserInfo } from "@/api/login";
-
-// 获取字典项
-const { getStrDictOptions } = useDataDictStore();
-// 填写状态
-const fillStatusDict = reactive({});
-getStrDictOptions("operation_fill_order_status").map((item) => {
-  fillStatusDict[item.value] = item.label;
-});
-console.log("🚀 ~ getDataDictList ~ fillStatusDict:", fillStatusDict);
-
-const orderName = ref("");
-const placeholderStyle = ref("color:#797979;font-weight:500;font-size:16px");
-const inputStyles = reactive({
-  backgroundColor: "#FFFFFF",
-  color: "#797979",
-});
-const paging = ref(null);
-// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
-const dataList = ref([]);
-
-// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
-const queryList = (pageNo, pageSize) => {
-  // 此处请求仅为演示,请替换为自己项目中的请求
-  getRuiDuReportPage({
-    pageNo,
-    pageSize,
-    searchKey: orderName.value,
-  })
-    .then((res) => {
-      // 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
-      paging.value.complete(res.data.list);
-    })
-    .catch((res) => {
-      // 如果请求失败写paging.value.complete(false);
-      // 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
-      // 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
-      paging.value.complete(false);
-    });
-};
-const searchList = () => {
-  paging.value.reload();
-};
-
-const navigatorCreate = () => {
-  uni.navigateTo({
-    url: "/pages/ruiDu/create",
-  });
-};
-
-const navigatorDetail = (item) => {
-  if (item.projectId === null && item.taskId === null) {
-    uni.navigateTo({
-      url: "/pages/ruiDu/create?id=" + item.id + "&isview=detail",
-    });
-    return;
-  }
-  uni.navigateTo({
-    url: "/pages/ruiDu/detail?id=" + item.id,
-  });
-};
-const navigatorEdit = (item, istime = "false") => {
-  if (item.projectId === null && item.taskId === null) {
-    uni.navigateTo({
-      url:
-        "/pages/ruiDu/create?id=" +
-        item.id +
-        "&isview=" +
-        (istime === "true" ? "time" : "detail"),
-    });
-    return;
-  }
-  uni.navigateTo({
-    url: "/pages/ruiDu/edit?id=" + item.id + "&istime=" + istime,
-  });
-};
-const formatDate = (time) => {
-  return dayjs(time).format("YYYY-MM-DD");
-};
-const formatTime = (time) => {
-  return dayjs(time).format("YYYY-MM-DD HH:mm:ss");
-};
-
-onShow(() => {
-  nextTick(() => {
-    searchList();
-  });
-});
-
-const rdNonProductFlag = ref(false);
-
-const getLoginUser = async () => {
-  const response = await getLoginUserInfo();
-  if (response.code === 0) {
-    rdNonProductFlag.value = response.data.rdNonProductFlag;
-  }
-};
-getLoginUser();
-</script>
-
-<style lang="scss" scoped>
-@import "@/style/work-order.scss";
-
-.search-row {
-  height: 35px;
-  background: #f3f5f9;
-
-  .mini-btn {
-    height: 35px;
-    line-height: 35px;
-    width: 100%;
-    margin-left: 8px;
-  }
-}
-
-.item {
-  width: 100%;
-  // height: 245px;
-  min-height: 245px;
-  // max-height: fit-content;
-  background: #ffffff;
-  border-radius: 6px;
-  margin-top: 10px;
-}
-
-.my-tag {
-  display: inline-block;
-  padding: 0 9px;
-  height: 24px;
-  line-height: 22px; /* height - border*2 */
-  font-size: 12px;
-  border-radius: 4px;
-  box-sizing: border-box;
-  border: 1px solid;
-  white-space: nowrap;
-  margin-left: 12px;
-}
-
-/* 已填写 - 绿色 (Success) */
-.my-tag-success {
-  background-color: #f0f9eb;
-  border-color: #e1f3d8;
-  color: #67c23a;
-}
-
-/* 未填写 - 红色 (Danger) */
-.my-tag-danger {
-  background-color: #fef0f0;
-  border-color: #fde2e2;
-  color: #f56c6c;
-}
-
-.my-tag-primary {
-  background-color: #ecf5ff;
-  border-color: #d9ecff;
-  color: #409eff;
-}
-
-/* 信息 - 灰色 (Info) */
-.my-tag-info {
-  background-color: #f4f4f5;
-  border-color: #e9e9eb;
-  color: #909399;
-}
-
-.create-btn {
-  margin-top: 10px;
-  width: 100%;
-}
-</style>
+<template>
+  <z-paging class="page" ref="paging" v-model="dataList" @query="queryList">
+    <!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+    <!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+    <template #top>
+      <uni-row class="search-row flex-row align-center justify-between">
+        <uni-col :span="19">
+          <uni-easyinput
+            v-model="orderName"
+            :styles="inputStyles"
+            :placeholderStyle="placeholderStyle"
+            :placeholder="$t('operation.searchText')">
+          </uni-easyinput>
+        </uni-col>
+        <uni-col :span="5" class="flex-row justify-end">
+          <button
+            class="mini-btn"
+            type="primary"
+            size="mini"
+            @click="searchList">
+            {{ $t("operation.search") }}
+          </button>
+        </uni-col>
+      </uni-row>
+      <button
+        class="create-btn"
+        type="primary"
+        size="mini"
+        @click="navigatorCreate">
+        新增日报
+      </button>
+    </template>
+
+    <view class="list">
+      <view class="item" v-for="(item, index) in dataList" :key="index">
+        <view
+          class="item-module flex-row align-center justify-between"
+          :class="{
+            tobeFilled: item.status == 0,
+          }">
+          <!-- 创建时间 -->
+          <view class="module-name">
+            {{ item.createTime ? formatDate(item.createTime) : "" }}
+          </view>
+          <!-- 工单状态 -->
+          <view
+            class="module-status"
+            :class="{
+              pending: item.status === 0, //待填写
+            }">
+            {{ fillStatusDict[item.status] }}
+          </view>
+        </view>
+        <view class="item-content">
+          <!-- 带班干部 -->
+          <view class="item-title flex-row">
+            <span class="item-title-width">{{ $t("ruiDu.shiftLeader") }}:</span>
+            <span>{{ item.responsiblePersonNames }}</span>
+          </view>
+          <!-- 日报名称 -->
+          <view class="item-title flex-row">
+            <span class="item-title-width">{{ $t("ruiDu.reportName") }}:</span>
+            <span>{{ item.reportName }}</span>
+          </view>
+          <!-- 项目 -->
+          <view class="item-title flex-row">
+            <span class="item-title-width">{{ $t("ruiDu.project") }}:</span>
+            <span>{{ item.contractName }}</span>
+          </view>
+          <!-- 任务 -->
+          <view class="item-title flex-row">
+            <span class="item-title-width">{{ $t("ruiDu.task") }}:</span>
+            <span>{{ item.taskName }}</span>
+          </view>
+
+          <!-- 创建时间 -->
+          <view class="item-title flex-row">
+            <span class="item-title-width"
+              >{{ $t("operation.createTime") }}:</span
+            >
+            <span>{{
+              item.createTime ? formatTime(item.createTime) : ""
+            }}</span>
+          </view>
+          <!-- 填写时间 -->
+          <view class="item-title flex-row">
+            <span class="item-title-width"
+              >{{ $t("operation.fillTime") }}:</span
+            >
+            <span>{{ item.fillTime ? formatTime(item.fillTime) : "" }}</span>
+          </view>
+          <view class="item-title flex-row align-center">
+            <span class="item-title-width">{{ "审批状态" }}:</span>
+            <span
+              :class="[
+                'my-tag',
+                item.auditStatus === 0
+                  ? 'my-tag-info'
+                  : item.auditStatus === 10
+                    ? 'my-tag-primary'
+                    : item.auditStatus === 20
+                      ? 'my-tag-success'
+                      : 'my-tag-danger',
+              ]">
+              {{
+                item.auditStatus === 0
+                  ? "待提交"
+                  : item.auditStatus === 10
+                    ? "待审批"
+                    : item.auditStatus === 20
+                      ? "审批通过"
+                      : "审批拒绝"
+              }}
+            </span>
+          </view>
+          <view class="item-title flex-row align-center">
+            <span class="item-title-width">{{ "非生产时间" }}:</span>
+            <span
+              :class="[
+                'my-tag',
+                item.nonProductFlag ? 'my-tag-success' : 'my-tag-danger',
+              ]">
+              {{ item.nonProductFlag ? "已填写" : "未填写" }}
+            </span>
+          </view>
+        </view>
+        <view class="item-btn flex-row align-center justify-end">
+          <!--  状态:0(待填写),1(已完成),2(填写中),3(忽略) -->
+
+          <!-- 查看 -->
+          <button type="primary" plain="true" @click="navigatorDetail(item)">
+            {{ $t("operation.view") }}
+          </button>
+          <!-- 填写 -->
+          <button
+            v-show="item.status === 0"
+            type="primary"
+            @click="navigatorEdit(item)">
+            {{ $t("operation.fill") }}
+          </button>
+          <button
+            v-show="item.auditStatus === 20 && rdNonProductFlag"
+            type="primary"
+            @click="navigatorEdit(item, 'true')">
+            {{ $t("operation.time") }}
+          </button>
+        </view>
+      </view>
+    </view>
+  </z-paging>
+</template>
+
+<script setup>
+import { ref, reactive, nextTick } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import dayjs from "dayjs";
+import { getRuiDuReportPage } from "@/api/ruiDu";
+import { useDataDictStore } from "@/store/modules/dataDict";
+import { getLoginUserInfo } from "@/api/login";
+
+// 获取字典项
+const { getStrDictOptions } = useDataDictStore();
+// 填写状态
+const fillStatusDict = reactive({});
+getStrDictOptions("operation_fill_order_status").map((item) => {
+  fillStatusDict[item.value] = item.label;
+});
+console.log("🚀 ~ getDataDictList ~ fillStatusDict:", fillStatusDict);
+
+const orderName = ref("");
+const placeholderStyle = ref("color:#797979;font-weight:500;font-size:16px");
+const inputStyles = reactive({
+  backgroundColor: "#FFFFFF",
+  color: "#797979",
+});
+const paging = ref(null);
+// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+const dataList = ref([]);
+
+// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+const queryList = (pageNo, pageSize) => {
+  // 此处请求仅为演示,请替换为自己项目中的请求
+  getRuiDuReportPage({
+    pageNo,
+    pageSize,
+    searchKey: orderName.value,
+  })
+    .then((res) => {
+      // 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+      paging.value.complete(res.data.list);
+    })
+    .catch((res) => {
+      // 如果请求失败写paging.value.complete(false);
+      // 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+      // 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+      paging.value.complete(false);
+    });
+};
+const searchList = () => {
+  paging.value.reload();
+};
+
+const navigatorCreate = () => {
+  uni.navigateTo({
+    url: "/pages/ruiDu/create",
+  });
+};
+
+const navigatorDetail = (item) => {
+  if (item.projectId === null && item.taskId === null) {
+    uni.navigateTo({
+      url: "/pages/ruiDu/create?id=" + item.id + "&isview=detail",
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: "/pages/ruiDu/detail?id=" + item.id,
+  });
+};
+const navigatorEdit = (item, istime = "false") => {
+  if (item.projectId === null && item.taskId === null) {
+    uni.navigateTo({
+      url:
+        "/pages/ruiDu/create?id=" +
+        item.id +
+        "&isview=" +
+        (istime === "true" ? "time" : "detail"),
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: "/pages/ruiDu/edit?id=" + item.id + "&istime=" + istime,
+  });
+};
+const formatDate = (time) => {
+  return dayjs(time).format("YYYY-MM-DD");
+};
+const formatTime = (time) => {
+  return dayjs(time).format("YYYY-MM-DD HH:mm:ss");
+};
+
+onShow(() => {
+  nextTick(() => {
+    searchList();
+  });
+});
+
+const rdNonProductFlag = ref(false);
+
+const getLoginUser = async () => {
+  const response = await getLoginUserInfo();
+  if (response.code === 0) {
+    rdNonProductFlag.value = response.data.rdNonProductFlag;
+  }
+};
+getLoginUser();
+</script>
+
+<style lang="scss" scoped>
+@import "@/style/work-order.scss";
+
+.search-row {
+  height: 35px;
+  background: #f3f5f9;
+
+  .mini-btn {
+    height: 35px;
+    line-height: 35px;
+    width: 100%;
+    margin-left: 8px;
+  }
+}
+
+.item {
+  width: 100%;
+  // height: 245px;
+  min-height: 245px;
+  // max-height: fit-content;
+  background: #ffffff;
+  border-radius: 6px;
+  margin-top: 10px;
+}
+
+.my-tag {
+  display: inline-block;
+  padding: 0 9px;
+  height: 24px;
+  line-height: 22px; /* height - border*2 */
+  font-size: 12px;
+  border-radius: 4px;
+  box-sizing: border-box;
+  border: 1px solid;
+  white-space: nowrap;
+  margin-left: 12px;
+}
+
+/* 已填写 - 绿色 (Success) */
+.my-tag-success {
+  background-color: #f0f9eb;
+  border-color: #e1f3d8;
+  color: #67c23a;
+}
+
+/* 未填写 - 红色 (Danger) */
+.my-tag-danger {
+  background-color: #fef0f0;
+  border-color: #fde2e2;
+  color: #f56c6c;
+}
+
+.my-tag-primary {
+  background-color: #ecf5ff;
+  border-color: #d9ecff;
+  color: #409eff;
+}
+
+/* 信息 - 灰色 (Info) */
+.my-tag-info {
+  background-color: #f4f4f5;
+  border-color: #e9e9eb;
+  color: #909399;
+}
+
+.create-btn {
+  margin-top: 10px;
+  width: 100%;
+}
+</style>

File diff suppressed because it is too large
+ 1099 - 290
pages/ruiDuReport/daily-detail.vue


+ 48 - 0
uni_modules/uni-collapse/changelog.md

@@ -0,0 +1,48 @@
+## 1.4.8(2025-09-16)
+- 修复 modelValue 修改会两次触发 change 事件的 Bug
+## 1.4.7(2025-09-11)
+- 修复 modelValue 修改不会触发更新的 Bug
+## 1.4.6(2025-09-02)
+- 修复 modelValue 修改不会触发 change 事件的 Bug
+
+## 1.4.5(2025-09-02)
+- 修复 非手风琴模式 不能设置 modeValue 为 [] 的 Bug (question/205130)
+
+## 1.4.4(2024-03-20)
+- 修复 titleBorder类型修正
+## 1.4.3(2022-01-25)
+- 修复 初始化的时候 ,open 属性失效的bug
+## 1.4.2(2022-01-21)
+- 修复 微信小程序resize后组件收起的bug
+## 1.4.1(2021-11-22)
+- 修复 vue3中个别scss变量无法找到的问题
+## 1.4.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
+## 1.3.3(2021-08-17)
+- 优化 show-arrow 属性默认为true
+## 1.3.2(2021-08-17)
+- 新增 show-arrow 属性,控制是否显示右侧箭头
+## 1.3.1(2021-07-30)
+- 优化 vue3下小程序事件警告的问题
+## 1.3.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.2.2(2021-07-21)
+- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
+## 1.2.1(2021-07-21)
+- 优化 组件示例
+## 1.2.0(2021-07-21)
+- 新增 组件折叠动画
+- 新增 value\v-model 属性 ,动态修改面板折叠状态
+- 新增 title 插槽 ,可定义面板标题
+- 新增 border 属性 ,显示隐藏面板内容分隔线
+- 新增 title-border 属性 ,显示隐藏面板标题分隔线
+- 修复 resize 方法失效的Bug
+- 修复 change 事件返回参数不正确的Bug
+- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
+## 1.1.7(2021-05-12)
+- 新增 组件示例地址
+## 1.1.6(2021-02-05)
+- 优化 组件引用关系,通过uni_modules引用组件
+## 1.1.5(2021-02-05)
+- 调整为uni_modules目录规范

+ 402 - 0
uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue

@@ -0,0 +1,402 @@
+<template>
+	<view class="uni-collapse-item">
+		<!-- onClick(!isOpen) -->
+		<view @click="onClick(!isOpen)" class="uni-collapse-item__title"
+			:class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}">
+			<view class="uni-collapse-item__title-wrap">
+				<slot name="title">
+					<view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}">
+						<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
+						<text class="uni-collapse-item__title-text">{{ title }}</text>
+					</view>
+				</slot>
+			</view>
+			<view v-if="showArrow"
+				:class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }"
+				class="uni-collapse-item__title-arrow">
+				<uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" />
+			</view>
+		</view>
+		<view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}"
+			:style="{height: (isOpen?height:0) +'px'}">
+			<view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content"
+				:class="{open:isheight,'uni-collapse-item--border':border&&isOpen}">
+				<slot></slot>
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom')
+	// #endif
+	/**
+	 * CollapseItem 折叠面板子组件
+	 * @description 折叠面板子组件
+	 * @property {String} title 标题文字
+	 * @property {String} thumb 标题左侧缩略图
+	 * @property {String} name 唯一标志符
+	 * @property {Boolean} open = [true|false] 是否展开组件
+	 * @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
+	 * @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
+	 * @property {Boolean} disabled = [true|false] 是否展开面板
+	 * @property {Boolean} showAnimation = [true|false] 开启动画
+	 * @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
+	 */
+	export default {
+		name: 'uniCollapseItem',
+		props: {
+			// 列表标题
+			title: {
+				type: String,
+				default: ''
+			},
+			name: {
+				type: [Number, String],
+				default: ''
+			},
+			// 是否禁用
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			// #ifdef APP-PLUS
+			// 是否显示动画,app 端默认不开启动画,卡顿严重
+			showAnimation: {
+				type: Boolean,
+				default: false
+			},
+			// #endif
+			// #ifndef APP-PLUS
+			// 是否显示动画
+			showAnimation: {
+				type: Boolean,
+				default: true
+			},
+			// #endif
+			// 是否展开
+			open: {
+				type: Boolean,
+				default: false
+			},
+			// 缩略图
+			thumb: {
+				type: String,
+				default: ''
+			},
+			// 标题分隔线显示类型
+			titleBorder: {
+				type: String,
+				default: 'auto'
+			},
+			border: {
+				type: Boolean,
+				default: true
+			},
+			showArrow: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			// TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug
+			const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
+			return {
+				isOpen: false,
+				isheight: null,
+				height: 0,
+				elId,
+				nameSync: 0
+			}
+		},
+		watch: {
+			open(val) {
+				this.isOpen = val
+				this.onClick(val, 'init')
+			}
+		},
+		updated(e) {
+			this.$nextTick(() => {
+				this.init(true)
+			})
+		},
+		created() {
+			this.collapse = this.getCollapse()
+			this.oldHeight = 0
+			this.onClick(this.open, 'init')
+		},
+		// #ifndef VUE3
+		// TODO vue2
+		destroyed() {
+			if (this.__isUnmounted) return
+			this.uninstall()
+		},
+		// #endif
+		// #ifdef VUE3
+		// TODO vue3
+		unmounted() {
+			this.__isUnmounted = true
+			this.uninstall()
+		},
+		// #endif
+		mounted() {
+			if (!this.collapse) return
+			if (this.name !== '') {
+				this.nameSync = this.name
+			} else {
+				this.nameSync = this.collapse.childrens.length + ''
+			}
+			if (this.collapse.names.indexOf(this.nameSync) === -1) {
+				this.collapse.names.push(this.nameSync)
+			} else {
+				console.warn(`name 值 ${this.nameSync} 重复`);
+			}
+			if (this.collapse.childrens.indexOf(this) === -1) {
+				this.collapse.childrens.push(this)
+			}
+			this.init()
+		},
+		methods: {
+			init(type) {
+				// #ifndef APP-NVUE
+				this.getCollapseHeight(type)
+				// #endif
+				// #ifdef APP-NVUE
+				this.getNvueHwight(type)
+				// #endif
+			},
+			uninstall() {
+				if (this.collapse) {
+					this.collapse.childrens.forEach((item, index) => {
+						if (item === this) {
+							this.collapse.childrens.splice(index, 1)
+						}
+					})
+					this.collapse.names.forEach((item, index) => {
+						if (item === this.nameSync) {
+							this.collapse.names.splice(index, 1)
+						}
+					})
+				}
+			},
+			onClick(isOpen, type) {
+				if (this.disabled) return
+				this.isOpen = isOpen
+				if (this.isOpen && this.collapse) {
+					this.collapse.setAccordion(this)
+				}
+				if (type !== 'init') {
+					this.collapse.onChange(isOpen, this)
+				}
+			},
+			getCollapseHeight(type, index = 0) {
+				const views = uni.createSelectorQuery().in(this)
+				views
+					.select(`#${this.elId}`)
+					.fields({
+						size: true
+					}, data => {
+						// TODO 百度中可能获取不到节点信息 ,需要循环获取
+						if (index >= 10) return
+						if (!data) {
+							index++
+							this.getCollapseHeight(false, index)
+							return
+						}
+						// #ifdef APP-NVUE
+						this.height = data.height + 1
+						// #endif
+						// #ifndef APP-NVUE
+						this.height = data.height
+						// #endif
+						this.isheight = true
+						if (type) return
+						this.onClick(this.isOpen, 'init')
+					})
+					.exec()
+			},
+			getNvueHwight(type) {
+				const result = dom.getComponentRect(this.$refs['collapse--hook'], option => {
+					if (option && option.result && option.size) {
+						// #ifdef APP-NVUE
+						this.height = option.size.height + 1
+						// #endif
+						// #ifndef APP-NVUE
+						this.height = option.size.height
+						// #endif
+						this.isheight = true
+						if (type) return
+						this.onClick(this.open, 'init')
+					}
+				})
+			},
+			/**
+			 * 获取父元素实例
+			 */
+			getCollapse(name = 'uniCollapse') {
+				let parent = this.$parent;
+				let parentName = parent.$options.name;
+				while (parentName !== name) {
+					parent = parent.$parent;
+					if (!parent) return false;
+					parentName = parent.$options.name;
+				}
+				return parent;
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.uni-collapse-item {
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+
+		/* #endif */
+		&__title {
+			/* #ifndef APP-NVUE */
+			display: flex;
+			width: 100%;
+			box-sizing: border-box;
+			/* #endif */
+			flex-direction: row;
+			align-items: center;
+			transition: border-bottom-color .3s;
+
+			// transition-property: border-bottom-color;
+			// transition-duration: 5s;
+			&-wrap {
+				width: 100%;
+				flex: 1;
+
+			}
+
+			&-box {
+				padding: 0 15px;
+				/* #ifndef APP-NVUE */
+				display: flex;
+				width: 100%;
+				box-sizing: border-box;
+				/* #endif */
+				flex-direction: row;
+				justify-content: space-between;
+				align-items: center;
+				height: 48px;
+				line-height: 48px;
+				background-color: #fff;
+				color: #303133;
+				font-size: 13px;
+				font-weight: 500;
+				/* #ifdef H5 */
+				cursor: pointer;
+				outline: none;
+
+				/* #endif */
+				&.is-disabled {
+					.uni-collapse-item__title-text {
+						color: #999;
+					}
+				}
+
+			}
+
+			&.uni-collapse-item-border {
+				border-bottom: 1px solid #ebeef5;
+			}
+
+			&.is-open {
+				border-bottom-color: transparent;
+			}
+
+			&-img {
+				height: 22px;
+				width: 22px;
+				margin-right: 10px;
+			}
+
+			&-text {
+				flex: 1;
+				font-size: 14px;
+				/* #ifndef APP-NVUE */
+				white-space: nowrap;
+				color: inherit;
+				/* #endif */
+				/* #ifdef APP-NVUE */
+				lines: 1;
+				/* #endif */
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+
+			&-arrow {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				box-sizing: border-box;
+				/* #endif */
+				align-items: center;
+				justify-content: center;
+				width: 20px;
+				height: 20px;
+				margin-right: 10px;
+				transform: rotate(0deg);
+
+				&-active {
+					transform: rotate(-180deg);
+				}
+			}
+
+
+		}
+
+		&__wrap {
+			/* #ifndef APP-NVUE */
+			will-change: height;
+			box-sizing: border-box;
+			/* #endif */
+			background-color: #fff;
+			overflow: hidden;
+			position: relative;
+			height: 0;
+
+			&.is--transition {
+				// transition: all 0.3s;
+				transition-property: height, border-bottom-width;
+				transition-duration: 0.3s;
+				/* #ifndef APP-NVUE */
+				will-change: height;
+				/* #endif */
+			}
+
+
+
+			&-content {
+				position: absolute;
+				font-size: 13px;
+				color: #303133;
+				// transition: height 0.3s;
+				border-bottom-color: transparent;
+				border-bottom-style: solid;
+				border-bottom-width: 0;
+
+				&.uni-collapse-item--border {
+					border-bottom-width: 1px;
+					border-bottom-color: red;
+					border-bottom-color: #ebeef5;
+				}
+
+				&.open {
+					position: relative;
+				}
+			}
+		}
+
+		&--animation {
+			transition-property: transform;
+			transition-duration: 0.3s;
+			transition-timing-function: ease;
+		}
+
+	}
+</style>

+ 147 - 0
uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue

@@ -0,0 +1,147 @@
+<template>
+	<view class="uni-collapse">
+		<slot />
+	</view>
+</template>
+<script>
+	/**
+	 * Collapse 折叠面板
+	 * @description 展示可以折叠 / 展开的内容区域
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=23
+	 * @property {String|Array} value 当前激活面板改变时触发(如果是手风琴模式,参数类型为string,否则为array)
+	 * @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
+	 * @event {Function} change 切换面板时触发,如果是手风琴模式,返回类型为string,否则为array
+	 */
+	export default {
+		name: 'uniCollapse',
+		emits:['change','activeItem','input','update:modelValue'],
+		props: {
+			value: {
+				type: [String, Array],
+				default: ''
+			},
+			modelValue: {
+				type: [String, Array],
+				default: ''
+			},
+			accordion: {
+				// 是否开启手风琴效果
+				type: [Boolean, String],
+				default: false
+			},
+		},
+		data() {
+			return {}
+		},
+		computed: {
+			// TODO 兼容 vue2 和 vue3
+			dataValue() {
+				let value = (typeof this.value === 'string' && this.value === '') ||
+					(Array.isArray(this.value) && this.value.length === 0)
+				let modelValue = (typeof this.modelValue === 'string' && this.modelValue === '') ||
+					(Array.isArray(this.modelValue) && this.modelValue.length === 0)
+				if (value) {
+					return this.modelValue
+				}
+				if (modelValue) {
+					return this.value
+				}
+
+				return this.value
+			}
+		},
+		watch: {
+			dataValue: {
+				handler(newVal) {
+					this.setOpen(newVal)
+				},
+				deep: true
+			}
+		},
+		created() {
+			this.childrens = []
+			this.names = []
+		},
+		mounted() {
+			this.$nextTick(()=>{
+				this.setOpen(this.dataValue)
+			})
+		},
+		methods: {
+			setOpen(val) {
+				const str = typeof val === 'string'
+				const arr = Array.isArray(val)
+				this.childrens.forEach((vm) => {
+					if (str) {
+						if (val === vm.nameSync) {
+							if (!this.accordion) {
+								console.warn('accordion 属性为 false ,v-model 类型应该为 array')
+								return
+							}
+							vm.isOpen = true
+						}
+					}
+					if (arr) {
+						const isOpen = val.findIndex(v => v === vm.nameSync) !== -1
+						if (this.accordion && isOpen) {
+							console.warn('accordion 属性为 true ,v-model 类型应该为 string')
+							return
+						}
+						vm.isOpen = isOpen
+					}
+				})
+				this.emit(val)
+			},
+			setAccordion(self) {
+				if (!this.accordion) return
+				this.childrens.forEach((vm, index) => {
+					if (self !== vm) {
+						vm.isOpen = false
+					}
+				})
+			},
+			resize() {
+				this.childrens.forEach((vm, index) => {
+					// #ifndef APP-NVUE
+					vm.getCollapseHeight()
+					// #endif
+					// #ifdef APP-NVUE
+					vm.getNvueHwight()
+					// #endif
+				})
+			},
+			onChange(isOpen, self) {
+				let activeItem = []
+
+				if (this.accordion) {
+					activeItem = isOpen ? self.nameSync : ''
+				} else {
+					this.childrens.forEach((vm, index) => {
+						if (vm.isOpen) {
+							activeItem.push(vm.nameSync)
+						}
+					})
+				}
+				this.$emit('change', activeItem)
+				this.emit(activeItem)
+			},
+			emit(val){
+				this.$emit('input', val)
+				this.$emit('update:modelValue', val)
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	.uni-collapse {
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		display: flex;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+		flex-direction: column;
+		background-color: #fff;
+	}
+</style>

+ 106 - 0
uni_modules/uni-collapse/package.json

@@ -0,0 +1,106 @@
+{
+  "id": "uni-collapse",
+  "displayName": "uni-collapse 折叠面板",
+  "version": "1.4.8",
+  "description": "Collapse 组件,可以折叠 / 展开的内容区域。",
+  "keywords": [
+    "uni-ui",
+    "折叠",
+    "折叠面板",
+    "手风琴"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": "",
+    "uni-app": "^4.07",
+    "uni-app-x": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue",
+    "darkmode": "x",
+    "i18n": "x",
+    "widescreen": "x"
+  },
+  "uni_modules": {
+    "dependencies": [
+      "uni-scss",
+      "uni-icons"
+    ],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "x",
+        "aliyun": "x",
+        "alipay": "x"
+      },
+      "client": {
+        "uni-app": {
+          "vue": {
+            "vue2": "√",
+            "vue3": "√"
+          },
+          "web": {
+            "safari": "√",
+            "chrome": "√"
+          },
+          "app": {
+            "vue": "√",
+            "nvue": "√",
+            "android": "√",
+            "ios": "√",
+            "harmony": "√"
+          },
+          "mp": {
+            "weixin": "√",
+            "alipay": "√",
+            "toutiao": "√",
+            "baidu": "√",
+            "kuaishou": "-",
+            "jd": "-",
+            "harmony": "-",
+            "qq": "√",
+            "lark": "-"
+          },
+          "quickapp": {
+            "huawei": "√",
+            "union": "√"
+          }
+        },
+        "uni-app-x": {
+          "web": {
+            "safari": "-",
+            "chrome": "-"
+          },
+          "app": {
+            "android": "-",
+            "ios": "-",
+            "harmony": "-"
+          },
+          "mp": {
+            "weixin": "-"
+          }
+        }
+      }
+    }
+  }
+}

+ 12 - 0
uni_modules/uni-collapse/readme.md

@@ -0,0 +1,12 @@
+
+
+## Collapse 折叠面板
+> **组件名:uni-collapse**
+> 代码块: `uCollapse`
+> 关联组件:`uni-collapse-item`、`uni-icons`。
+
+
+折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 13 - 0
uni_modules/uv-cell/changelog.md

@@ -0,0 +1,13 @@
+## 1.0.5(2023-12-06)
+1. 修复uv-cell right-icon插槽编译到APP端不显示的BUG,问题来源:https://gitee.com/climblee/uv-ui/issues/I8LXZI
+## 1.0.4(2023-09-19)
+1. 增加cellStyle参数,方便自定义单元格的样式
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-06-21)
+1. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-cell 单元格

+ 15 - 0
uni_modules/uv-cell/components/uv-cell-group/props.js

@@ -0,0 +1,15 @@
+export default {
+	props: {
+		// 分组标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 是否显示外边框
+		border: {
+			type: Boolean,
+			default: true
+		},
+		...uni.$uv?.props?.cellGroup
+	}
+}

+ 63 - 0
uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue

@@ -0,0 +1,63 @@
+<template>
+    <view :style="[$uv.addStyle(customStyle)]" :class="[customClass]" class="uv-cell-group">
+        <view v-if="title" class="uv-cell-group__title">
+            <slot name="title">
+				<text class="uv-cell-group__title__text">{{ title }}</text>
+			</slot>
+        </view>
+        <view class="uv-cell-group__wrapper">
+			<uv-line v-if="border"></uv-line>
+            <slot />
+        </view>
+    </view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * cellGroup  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://www.uvui.cn/components/cell.html
+	 * 
+	 * @property {String}	title		分组标题
+	 * @property {Boolean}	border		是否显示外边框 (默认 true )
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 * 
+	 * @event {Function} click 	点击cell列表时触发
+	 * @example <uv-cell-group title="设置喜好">
+	 */
+	export default {
+		name: 'uv-cell-group',
+		mixins: [mpMixin, mixin, props]
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-cell-group-title-padding: 16px 16px 8px !default;
+	$uv-cell-group-title-font-size: 15px !default;
+	$uv-cell-group-title-line-height: 16px !default;
+	$uv-cell-group-title-color: $uv-main-color !default;
+
+    .uv-cell-group {
+		flex: 1;
+		
+        &__title {
+            padding: $uv-cell-group-title-padding;
+
+            &__text {
+                font-size: $uv-cell-group-title-font-size;
+                line-height: $uv-cell-group-title-line-height;
+                color: $uv-cell-group-title-color;
+            }
+        }
+		
+		&__wrapper {
+			position: relative;
+		}
+    }
+</style>
+

+ 116 - 0
uni_modules/uv-cell/components/uv-cell/props.js

@@ -0,0 +1,116 @@
+export default {
+	props: {
+		// 标题
+		title: {
+			type: [String, Number],
+			default: ''
+		},
+		// 标题下方的描述信息
+		label: {
+			type: [String, Number],
+			default: ''
+		},
+		// 右侧的内容
+		value: {
+			type: [String, Number],
+			default: ''
+		},
+		// 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+		icon: {
+			type: String,
+			default: ''
+		},
+		// 是否禁用cell
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 是否显示下边框
+		border: {
+			type: Boolean,
+			default: true
+		},
+		// 内容是否垂直居中(主要是针对右侧的value部分)
+		center: {
+			type: Boolean,
+			default: true
+		},
+		// 点击后跳转的URL地址
+		url: {
+			type: String,
+			default: ''
+		},
+		// 链接跳转的方式,内部使用的是uvui封装的route方法,可能会进行拦截操作
+		linkType: {
+			type: String,
+			default: 'navigateTo'
+		},
+		// 是否开启点击反馈(表现为点击时加上灰色背景)
+		clickable: {
+			type: Boolean,
+			default: false
+		},
+		// 是否展示右侧箭头并开启点击反馈
+		isLink: {
+			type: Boolean,
+			default: false
+		},
+		// 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+		required: {
+			type: Boolean,
+			default: false
+		},
+		// 右侧的图标箭头
+		rightIcon: {
+			type: String,
+			default: 'arrow-right'
+		},
+		// 右侧箭头的方向,可选值为:left,up,down
+		arrowDirection: {
+			type: String,
+			default: ''
+		},
+		// 左侧图标样式
+		iconStyle: {
+			type: [Object, String],
+			default: () => {
+				return {}
+			}
+		},
+		// 右侧箭头图标的样式
+		rightIconStyle: {
+			type: [Object, String],
+			default: () => {
+				return {}
+			}
+		},
+		// 标题的样式
+		titleStyle: {
+			type: [Object, String],
+			default: () => {
+				return {}
+			}
+		},
+		// 单位元的大小,可选值为large
+		size: {
+			type: String,
+			default: ''
+		},
+		// 点击cell是否阻止事件传播
+		stop: {
+			type: Boolean,
+			default: true
+		},
+		// 标识符,cell被点击时返回
+		name: {
+			type: [Number, String],
+			default: ''
+		},
+		// 单元格自定义样式
+		cellStyle: {
+			type: [Object, String],
+			default: () => {}
+		},
+		...uni.$uv?.props?.cell
+	}
+}

+ 209 - 0
uni_modules/uv-cell/components/uv-cell/uv-cell.vue

@@ -0,0 +1,209 @@
+<template>
+	<view class="uv-cell" :class="[customClass]" :style="[$uv.addStyle(customStyle)]"
+		:hover-class="(!disabled && (clickable || isLink)) ? 'uv-cell--clickable' : ''" :hover-stay-time="250"
+		@click="clickHandler">
+		<view class="uv-cell__body" 
+			:class="[ center && 'uv-cell--center', size === 'large' && 'uv-cell__body--large']"
+			:style="[cellStyle]"
+		>
+			<view class="uv-cell__body__content">
+				<view class="uv-cell__left-icon-wrap">
+					<slot name="icon">
+						<uv-icon v-if="icon" :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></uv-icon>
+					</slot>
+				</view>
+				<view class="uv-cell__title">
+					<slot name="title">
+						<text v-if="title" class="uv-cell__title-text" :style="[titleTextStyle]"
+							:class="[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__title-text--large']">{{ title }}</text>
+					</slot>
+					<slot name="label">
+						<text class="uv-cell__label" v-if="label"
+							:class="[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__label--large']">{{ label }}</text>
+					</slot>
+				</view>
+			</view>
+			<slot name="value">
+				<text class="uv-cell__value"
+					:class="[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__value--large']"
+					v-if="!$uv.test.empty(value)">{{ value }}</text>
+			</slot>
+			<view class="uv-cell__right-icon-wrap"
+				:class="[`uv-cell__right-icon-wrap--${arrowDirection}`]">
+				<slot name="right-icon">
+					<uv-icon v-if="isLink" :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
+						:size="size === 'large' ? 18 : 16"></uv-icon>
+				</slot>
+			</view>
+		</view>
+		<uv-line v-if="border"></uv-line>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * cell  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://www.uvui.cn/components/cell.html
+	 * @property {String | Number}	title			标题
+	 * @property {String | Number}	label			标题下方的描述信息
+	 * @property {String | Number}	value			右侧的内容
+	 * @property {String}			icon			左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+	 * @property {Boolean}			disabled		是否禁用cell	
+	 * @property {Boolean}			border			是否显示下边框 (默认 true )
+	 * @property {Boolean}			center			内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
+	 * @property {String}			url				点击后跳转的URL地址
+	 * @property {String}			linkType		链接跳转的方式,内部使用的是uvui封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
+	 * @property {Boolean}			clickable		是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) 
+	 * @property {Boolean}			isLink			是否展示右侧箭头并开启点击反馈 (默认 false )
+	 * @property {Boolean}			required		是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
+	 * @property {String}			rightIcon		右侧的图标箭头 (默认 'arrow-right')
+	 * @property {String}			arrowDirection	右侧箭头的方向,可选值为:left,up,down
+	 * @property {Object | String}			rightIconStyle	右侧箭头图标的样式
+	 * @property {Object | String}			titleStyle		标题的样式
+	 * @property {Object | String}			iconStyle		左侧图标样式
+	 * @property {String}			size			单位元的大小,可选值为 large,normal,mini 
+	 * @property {Boolean}			stop			点击cell是否阻止事件传播 (默认 true )
+	 * @property {Object | String}  cellStyle 单元格自定义样式
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}			click			点击cell列表时触发
+	 * @example 该组件需要搭配cell-group组件使用,见官方文档示例
+	 */
+	export default {
+		name: 'uv-cell',
+		emits: ['click'],
+		mixins: [mpMixin, mixin, props],
+		computed: {
+			titleTextStyle() {
+				return this.$uv.addStyle(this.titleStyle)
+			}
+		},
+		methods: {
+			// 点击cell
+			clickHandler(e) {
+				if (this.disabled) return
+				this.$emit('click', {
+					name: this.name
+				})
+				// 如果配置了url(此props参数通过mixin引入)参数,跳转页面
+				this.openPage()
+				// 是否阻止事件传播
+				this.stop && this.preventEvent(e)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-cell-padding: 10px 15px !default;
+	$uv-cell-font-size: 15px !default;
+	$uv-cell-line-height: 24px !default;
+	$uv-cell-color: $uv-main-color !default;
+	$uv-cell-icon-size: 16px !default;
+	$uv-cell-title-font-size: 15px !default;
+	$uv-cell-title-line-height: 22px !default;
+	$uv-cell-title-color: $uv-main-color !default;
+	$uv-cell-label-font-size: 12px !default;
+	$uv-cell-label-color: $uv-tips-color !default;
+	$uv-cell-label-line-height: 18px !default;
+	$uv-cell-value-font-size: 14px !default;
+	$uv-cell-value-color: $uv-content-color !default;
+	$uv-cell-clickable-color: $uv-bg-color !default;
+	$uv-cell-disabled-color: #c8c9cc !default;
+	$uv-cell-padding-top-large: 13px !default;
+	$uv-cell-padding-bottom-large: 13px !default;
+	$uv-cell-value-font-size-large: 15px !default;
+	$uv-cell-label-font-size-large: 14px !default;
+	$uv-cell-title-font-size-large: 16px !default;
+	$uv-cell-left-icon-wrap-margin-right: 4px !default;
+	$uv-cell-right-icon-wrap-margin-left: 4px !default;
+	$uv-cell-title-flex: 1 !default;
+	$uv-cell-label-margin-top: 5px !default;
+	.uv-cell {
+		&__body {
+			@include flex();
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			padding: $uv-cell-padding;
+			font-size: $uv-cell-font-size;
+			color: $uv-cell-color;
+			&__content {
+				@include flex(row);
+				align-items: center;
+				flex: 1;
+			}
+			&--large {
+				padding-top: $uv-cell-padding-top-large;
+				padding-bottom: $uv-cell-padding-bottom-large;
+			}
+		}
+		&__left-icon-wrap,
+		&__right-icon-wrap {
+			@include flex();
+			align-items: center;
+			// height: $uv-cell-line-height;
+			font-size: $uv-cell-icon-size;
+		}
+		&__left-icon-wrap {
+			margin-right: $uv-cell-left-icon-wrap-margin-right;
+		}
+		&__right-icon-wrap {
+			margin-left: $uv-cell-right-icon-wrap-margin-left;
+			transition: transform 0.3s;
+			&--up {
+				transform: rotate(-90deg);
+			}
+			&--down {
+				transform: rotate(90deg);
+			}
+		}
+		&__title {
+			flex: $uv-cell-title-flex;
+			&-text {
+				font-size: $uv-cell-title-font-size;
+				line-height: $uv-cell-title-line-height;
+				color: $uv-cell-title-color;
+				&--large {
+					font-size: $uv-cell-title-font-size-large;
+				}
+			}
+		}
+		&__label {
+			margin-top: $uv-cell-label-margin-top;
+			font-size: $uv-cell-label-font-size;
+			color: $uv-cell-label-color;
+			line-height: $uv-cell-label-line-height;
+			&--large {
+				font-size: $uv-cell-label-font-size-large;
+			}
+		}
+		&__value {
+			text-align: right;
+			font-size: $uv-cell-value-font-size;
+			line-height: $uv-cell-line-height;
+			color: $uv-cell-value-color;
+			&--large {
+				font-size: $uv-cell-value-font-size-large;
+			}
+		}
+		&--clickable {
+			background-color: $uv-cell-clickable-color;
+		}
+		&--disabled {
+			color: $uv-cell-disabled-color;
+			/* #ifndef APP-NVUE */
+			cursor: not-allowed;
+			/* #endif */
+		}
+		&--center {
+			align-items: center;
+		}
+	}
+</style>

+ 89 - 0
uni_modules/uv-cell/package.json

@@ -0,0 +1,89 @@
+{
+  "id": "uv-cell",
+  "displayName": "uv-cell 单元格 全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.5",
+  "description": "cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。",
+  "keywords": [
+    "uv-cell",
+    "uvui",
+    "uv-ui",
+    "单元格",
+    "设置页"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-icon",
+			"uv-line"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-cell/readme.md

@@ -0,0 +1,11 @@
+## Cell 单元格
+
+> **组件名:uv-cell**
+
+cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+
+### <a href="https://www.uvui.cn/components/cell.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 5 - 0
uni_modules/uv-collapse/changelog.md

@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-collapse 折叠面板

+ 60 - 0
uni_modules/uv-collapse/components/uv-collapse-item/props.js

@@ -0,0 +1,60 @@
+export default {
+	props: {
+		// 标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 标题右侧内容
+		value: {
+			type: String,
+			default: ''
+		},
+		// 标题下方的描述信息
+		label: {
+			type: String,
+			default: ''
+		},
+		// 是否禁用折叠面板
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 是否展示右侧箭头并开启点击反馈
+		isLink: {
+			type: Boolean,
+			default: true
+		},
+		// 是否开启点击反馈
+		clickable: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示内边框
+		border: {
+			type: Boolean,
+			default: true
+		},
+		// 标题的对齐方式
+		align: {
+			type: String,
+			default: 'left'
+		},
+		// 唯一标识符
+		name: {
+			type: [String, Number],
+			default: ''
+		},
+		// 标题左侧图片,可为绝对路径的图片或内置图标
+		icon: {
+			type: String,
+			default: ''
+		},
+		// 面板展开收起的过渡时间,单位ms
+		duration: {
+			type: Number,
+			default: 300
+		},
+		...uni.$uv?.props?.collapseItem
+	}
+}

+ 229 - 0
uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue

@@ -0,0 +1,229 @@
+<template>
+	<view class="uv-collapse-item">
+		<uv-cell
+			:title="title"
+			:value="value"
+			:label="label"
+			:icon="icon"
+			:isLink="isLink"
+			:clickable="clickable"
+			:border="parentData.border && showBorder"
+			@click="clickHandler"
+			:arrowDirection="expanded ? 'up' : 'down'"
+			:disabled="disabled"
+		>
+			<!-- #ifndef MP-WEIXIN -->
+			<!-- 微信小程序不支持,因为微信中不支持 <slot name="title" slot="title" />的写法 -->
+			<template slot="title">
+				<slot name="title"></slot>
+			</template>
+			<template slot="icon">
+				<slot name="icon"></slot>
+			</template>
+			<template slot="value">
+				<slot name="value"></slot>
+			</template>
+			<template slot="right-icon">
+				<slot name="right-icon"></slot>
+			</template>
+			<!-- #endif -->
+		</uv-cell>
+		<view
+			class="uv-collapse-item__content"
+			:animation="animationData"
+			ref="animation"
+		>
+			<view
+				class="uv-collapse-item__content__text content-class"
+				:id="elId"
+				:ref="elId"
+			><slot /></view>
+		</view>
+		<uv-line v-if="parentData.border"></uv-line>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation')
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	/**
+	 * collapseItem 折叠面板Item
+	 * @description 通过折叠面板收纳内容区域(搭配uv-collapse使用)
+	 * @tutorial https://www.uvui.cn/components/collapse.html
+	 * @property {String}			title 		标题
+	 * @property {String}			value 		标题右侧内容
+	 * @property {String}			label 		标题下方的描述信息
+	 * @property {Boolean}			disbled 	是否禁用折叠面板 ( 默认 false )
+	 * @property {Boolean}			isLink 		是否展示右侧箭头并开启点击反馈 ( 默认 true )
+	 * @property {Boolean}			clickable	是否开启点击反馈 ( 默认 true )
+	 * @property {Boolean}			border		是否显示内边框 ( 默认 true )
+	 * @property {String}			align		标题的对齐方式 ( 默认 'left' )
+	 * @property {String | Number}	name		唯一标识符
+	 * @property {String}			icon		标题左侧图片,可为绝对路径的图片或内置图标
+	 * @event {Function}			change 			某个item被打开或者收起时触发
+	 * @example <uv-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</uv-collapse-item>
+	 */
+	export default {
+		name: "uv-collapse-item",
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				elId: '',
+				// uni.createAnimation的导出数据
+				animationData: {},
+				// 是否展开状态
+				expanded: false,
+				// 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
+				showBorder: false,
+				// 是否动画中,如果是则不允许继续触发点击
+				animating: false,
+				// 父组件uv-collapse的参数
+				parentData: {
+					accordion: false,
+					border: false
+				}
+			};
+		},
+		watch: {
+			expanded(n) {
+				clearTimeout(this.timer)
+				this.timer = null
+				// 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果
+				this.timer = setTimeout(() => {
+					this.showBorder = n
+				}, n ? 10 : 290)
+			}
+		},
+		created() {
+			this.elId = this.$uv.guid();
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			// 异步获取内容,或者动态修改了内容时,需要重新初始化
+			init() {
+				// 初始化数据
+				this.updateParentData()
+				if (!this.parent) {
+					return this.$uv.error('uv-collapse-item必须要搭配uv-collapse组件使用')
+				}
+				const {
+					value,
+					accordion,
+					children = []
+				} = this.parent
+
+				if (accordion) {
+					if (this.$uv.test.array(value)) {
+						return this.$uv.error('手风琴模式下,uv-collapse组件的value参数不能为数组')
+					}
+					this.expanded = this.name == value
+				} else {
+					if (!this.$uv.test.array(value) && value !== null) {
+						return this.$uv.error('非手风琴模式下,uv-collapse组件的value参数必须为数组')
+					}
+					this.expanded = (value || []).some(item => item == this.name)
+				}
+				// 设置组件的展开或收起状态
+				this.$nextTick(function() {
+					this.setContentAnimate()
+				})
+			},
+			updateParentData() {
+				// 此方法在mixin中
+				this.getParentData('uv-collapse')
+			},
+			async setContentAnimate() {
+				// 每次面板打开或者收起时,都查询元素尺寸
+				// 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
+				const rect = await this.queryRect()
+				const height = this.expanded ? rect.height : 0
+				this.animating = true
+				// #ifdef APP-NVUE
+				const ref = this.$refs['animation'].ref
+				animation.transition(ref, {
+					styles: {
+						height: height + 'px'
+					},
+					duration: this.duration,
+					// 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局
+					needLayout: true,
+					timingFunction: 'ease-in-out',
+				}, () => {
+					this.animating = false
+				})
+				// #endif
+
+				// #ifndef APP-NVUE
+				const animation = uni.createAnimation({
+					timingFunction: 'ease-in-out',
+				});
+				animation
+					.height(height)
+					.step({
+						duration: this.duration,
+					})
+					.step()
+				// 导出动画数据给面板的animationData值
+				this.animationData = animation.export()
+				// 标识动画结束
+				this.$uv.sleep(this.duration).then(() => {
+					this.animating = false
+				})
+				// #endif
+			},
+			// 点击collapsehead头部
+			clickHandler() {
+				if (this.disabled && this.animating) return
+				// 设置本组件为相反的状态
+				this.parent && this.parent.onChange(this)
+			},
+			// 查询内容高度
+			queryRect() {
+				// #ifndef APP-NVUE
+				// 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uvGetRect(`#${this.elId}`).then(size => {
+						resolve(size)
+					})
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[this.elId], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
+			}
+		},
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-collapse-item {
+
+		&__content {
+			overflow: hidden;
+			height: 0;
+
+			&__text {
+				padding: 12px 15px;
+				color: $uv-content-color;
+				font-size: 14px;
+				line-height: 18px;
+			}
+		}
+	}
+</style>

+ 20 - 0
uni_modules/uv-collapse/components/uv-collapse/props.js

@@ -0,0 +1,20 @@
+export default {
+	props: {
+		// 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
+		value: {
+			type: [String, Number, Array, null],
+			default: null
+		},
+		// 是否手风琴模式
+		accordion: {
+			type: Boolean,
+			default: false
+		},
+		// 是否显示外边框
+		border: {
+			type: Boolean,
+			default: true
+		},
+		...uni.$uv?.props?.collapse
+	}
+}

+ 86 - 0
uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue

@@ -0,0 +1,86 @@
+<template>
+	<view class="uv-collapse">
+		<uv-line v-if="border"></uv-line>
+		<slot />
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * collapse 折叠面板 
+	 * @description 通过折叠面板收纳内容区域
+	 * @tutorial https://www.uvui.cn/components/collapse.html
+	 * @property {String | Number | Array}	value		当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
+	 * @property {Boolean}					accordion	是否手风琴模式( 默认 false )
+	 * @property {Boolean}					border		是否显示外边框 ( 默认 true )
+	 * @event {Function}	change 		当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
+	 * @example <uv-collapse></uv-collapse>
+	 */
+	export default {
+		name: "uv-collapse",
+		mixins: [mpMixin, mixin, props],
+		watch: {
+			needInit() {
+				this.init()
+			},
+			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+			parentData() {
+				if (this.children.length) {
+					this.children.map(child => {
+						// 判断子组件(uv-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof(child.updateParentData) === 'function' && child.updateParentData()
+					})
+				}
+			}
+		},
+		created() {
+			this.children = []
+		},
+		computed: {
+			needInit() {
+				// 通过computed,同时监听accordion和value值的变化
+				// 再通过watch去执行init()方法,进行再一次的初始化
+				return [this.accordion, this.value]
+			}
+		},
+		methods: {
+			// 重新初始化一次内部的所有子元素
+			init() {
+				this.children.map(child => {
+					child.init()
+				})
+			},
+			/**
+			 * collapse-item被点击时触发,由collapse统一处理各子组件的状态
+			 * @param {Object} target 被操作的面板的实例
+			 */
+			onChange(target) {
+				let changeArr = []
+				this.children.map((child, index) => {
+					// 如果是手风琴模式,将其他的折叠面板收起来
+					if (this.accordion) {
+						child.expanded = child === target ? !target.expanded : false
+						child.setContentAnimate()
+					} else {
+						if(child === target) {
+							child.expanded = !child.expanded
+							child.setContentAnimate()
+						}
+					}
+					// 拼接change事件中,数组元素的状态
+					changeArr.push({
+						// 如果没有定义name属性,则默认返回组件的index索引
+						name: child.name || index,
+						status: child.expanded ? 'open' : 'close'
+					})
+				})
+
+				this.$emit('change', changeArr)
+				this.$emit(target.expanded ? 'open' : 'close', target.name)
+			}
+		}
+	}
+</script>

+ 89 - 0
uni_modules/uv-collapse/package.json

@@ -0,0 +1,89 @@
+{
+  "id": "uv-collapse",
+  "displayName": "uv-collapse 折叠面板 全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.1",
+  "description": "折叠面板组件,通过折叠面板收纳内容区域,点击可展开收起,多功能参数可配置。",
+  "keywords": [
+    "uv-collapse",
+    "uvui",
+    "uv-ui",
+    "collapse",
+    "折叠面板"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-line",
+			"uv-cell"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-collapse/readme.md

@@ -0,0 +1,11 @@
+## Collapse 折叠面板
+
+> **组件名:uv-collapse**
+
+通过折叠面板收纳内容区域,点击可展开收起,多功能参数可配置。
+
+### <a href="https://www.uvui.cn/components/collapse.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 10 - 0
uni_modules/uv-subsection/changelog.md

@@ -0,0 +1,10 @@
+## 1.0.3(2023-12-20)
+1. 优化
+## 1.0.2(2023-11-27)
+1. 增加customItemStyle属性,方便修改bar样式
+2. 文档后续更新
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-subsection 分段器

+ 54 - 0
uni_modules/uv-subsection/components/uv-subsection/props.js

@@ -0,0 +1,54 @@
+export default {
+	props: {
+		// tab的数据
+		list: {
+			type: Array,
+			default: () => []
+		},
+		// 当前活动的tab的index
+		current: {
+			type: [String, Number],
+			default: 0
+		},
+		// 激活的颜色
+		activeColor: {
+			type: String,
+			default: '#3c9cff'
+		},
+		// 未激活的颜色
+		inactiveColor: {
+			type: String,
+			default: '#303133'
+		},
+		// 模式选择,mode=button为按钮形式,mode=subsection时为分段模式
+		mode: {
+			type: String,
+			default: 'button'
+		},
+		// 字体大小
+		fontSize: {
+			type: [String, Number],
+			default: 12
+		},
+		// 激活tab的字体是否加粗
+		bold: {
+			type: Boolean,
+			default: true
+		},
+		// mode = button时,组件背景颜色
+		bgColor: {
+			type: String,
+			default: '#eeeeef'
+		},
+		// 从list元素对象中读取的键名
+		keyName: {
+			type: String,
+			default: 'name'
+		},
+		customItemStyle: {
+			type: [String, Object],
+			default: ''
+		},
+		...uni.$uv?.props?.subsection
+	}
+}

+ 269 - 0
uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue

@@ -0,0 +1,269 @@
+<template>
+	<view 
+		class="uv-subsection"
+		ref="uv-subsection"
+		:class="[`uv-subsection--${mode}`]"
+		:style="[$uv.addStyle(customStyle), wrapperStyle]">
+		<view 
+			class="uv-subsection__bar"
+			ref="uv-subsection__bar"
+			:style="[barStyle]"
+			:class="[
+        mode === 'button' && 'uv-subsection--button__bar',
+        current === 0 && mode === 'subsection' && 'uv-subsection__bar--first',
+        current > 0 && current < list.length - 1 && mode === 'subsection' && 'uv-subsection__bar--center',
+        current === list.length - 1 && mode === 'subsection' && 'uv-subsection__bar--last'
+      ]"
+		></view>
+		<view class="uv-subsection__item"
+			:class="[
+        `uv-subsection__item--${index}`,
+        index < list.length - 1 && 'uv-subsection__item--no-border-right',
+        index === 0 && 'uv-subsection__item--first',
+        index === list.length - 1 && 'uv-subsection__item--last'
+      ]"
+			:ref="`uv-subsection__item--${index}`"
+			:style="[itemStyle(index)]"
+			@tap="clickHandler(index)"
+			v-for="(item, index) in list"
+			:key="index"
+		>
+			<text 
+				class="uv-subsection__item__text"
+				:style="[textStyle(index)]">{{ getText(item) }}
+			</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	// #ifdef APP-NVUE
+	const dom = uni.requireNativePlugin("dom");
+	const animation = uni.requireNativePlugin("animation");
+	// #endif
+	import props from "./props.js";
+	/**
+	 * Subsection 分段器
+	 * @description 该分段器一般用于用户从几个选项中选择某一个的场景
+	 * @tutorial https://www.uvui.cn/components/subsection.html
+	 * @property {Array}			list			tab的数据
+	 * @property {String | Number}	current			当前活动的tab的index(默认 0 )
+	 * @property {String}			activeColor		激活时的颜色(默认 '#3c9cff' )
+	 * @property {String}			inactiveColor	未激活时的颜色(默认 '#303133' )
+	 * @property {String}			mode			模式选择,mode=button为按钮形式,mode=subsection时为分段模式(默认 'button' )
+	 * @property {String | Number}	fontSize		字体大小,单位px(默认 12 )
+	 * @property {Boolean}			bold			激活选项的字体是否加粗(默认 true )
+	 * @property {String}			bgColor			组件背景颜色,mode为button时有效(默认 '#eeeeef' )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * @property {String}	        keyName	        从`list`元素对象中读取的键名(默认 'name' )
+	 *
+	 * @event {Function} change		分段器选项发生改变时触发  回调 index:选项的index索引值,从0开始
+	 * @example <uv-subsection :list="list" :current="curNow" @change="sectionChange"></uv-subsection>
+	 */
+	export default {
+		name: "uv-subsection",
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				// 组件尺寸
+				itemRect: {
+					width: 0,
+					height: 0,
+				}
+			}
+		},
+		watch: {
+			list: {
+				deep: true,
+				handler(){
+					this.init();
+				}
+			},
+			current: {
+				immediate: true,
+				handler(n) {
+					// #ifdef APP-NVUE
+					// 在安卓nvue上,如果通过translateX进行位移,到最后一个时,会导致右侧无法绘制圆角
+					// 故用animation模块进行位移
+					const ref = this.$refs?.["uv-subsection__bar"]?.ref;
+					// 不存在ref的时候(理解为第一次初始化时,需要渲染dom,进行一定延时再获取ref),这里的100ms是经过测试得出的结果(某些安卓需要延时久一点),勿随意修改
+					this.$uv.sleep(ref ? 0 : 150).then(() => {
+						animation.transition(this.$refs["uv-subsection__bar"].ref, {
+							styles: {
+								transform: `translateX(${ n * this.itemRect.width }px)`,
+								transformOrigin: "center center"
+							},
+							duration: 300,
+						});
+					});
+					// #endif
+				}
+			}
+		},
+		computed: {
+			wrapperStyle() {
+				const style = {};
+				// button模式时,设置背景色
+				if (this.mode === "button") {
+					style.backgroundColor = this.bgColor;
+				}
+				return style;
+			},
+			// 滑块的样式
+			barStyle() {
+				const style = {};
+				style.width = `${this.itemRect.width}px`;
+				style.height = `${this.itemRect.height}px`;
+				// 通过translateX移动滑块,其移动的距离为索引*item的宽度
+				// #ifndef APP-NVUE
+				style.transform = `translateX(${ this.current * this.itemRect.width }px)`;
+				// #endif
+				if (this.mode === "subsection") {
+					// 在subsection模式下,需要动态设置滑块的圆角,因为移动滑块使用的是translateX,无法通过父元素设置overflow: hidden隐藏滑块的直角
+					style.backgroundColor = this.activeColor;
+				}
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customItemStyle));
+			},
+			// 分段器item的样式
+			itemStyle(index) {
+				return (index) => {
+					const style = {};
+					if (this.mode === "subsection") {
+						// 设置border的样式
+						style.borderColor = this.activeColor;
+						style.borderWidth = "1px";
+						style.borderStyle = "solid";
+					}
+					return style;
+				};
+			},
+			// 分段器文字颜色
+			textStyle(index) {
+				return (index) => {
+					const style = {};
+					style.fontWeight = this.bold && this.current === index ? "bold" : "normal";
+					style.fontSize = this.$uv.addUnit(this.fontSize);
+					// subsection模式下,激活时默认为白色的文字
+					if (this.mode === "subsection") {
+						style.color = this.current === index ? "#fff" : this.inactiveColor;
+					} else {
+						// button模式下,激活时文字颜色默认为activeColor
+						style.color = this.current === index ? this.activeColor : this.inactiveColor;
+					}
+					return style;
+				};
+			},
+		},
+		mounted() {
+			this.init();
+		},
+		methods: {
+			init() {
+				this.$uv.sleep().then(() => this.getRect());
+			},
+			// 判断展示文本
+			getText(item) {
+				return typeof item === 'object' ? item[this.keyName] : item
+			},
+			// 获取组件的尺寸
+			getRect() {
+				// #ifndef APP-NVUE
+				this.$uvGetRect(".uv-subsection__item--0").then((size) => {
+					this.itemRect = size;
+				});
+				// #endif
+
+				// #ifdef APP-NVUE
+				const ref = this.$refs["uv-subsection__item--0"][0];
+				ref && dom.getComponentRect(ref, (res) => {
+					this.itemRect = res.size;
+				});
+				// #endif
+			},
+			clickHandler(index) {
+				this.$emit("change", index);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	.uv-subsection {
+		@include flex;
+		position: relative;
+		overflow: hidden;
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		box-sizing: border-box;
+		/* #endif */
+		&--button {
+			height: 32px;
+			background-color: rgb(238, 238, 239);
+			padding: 3px;
+			border-radius: 3px;
+			align-items: stretch;
+			&__bar {
+				background-color: #ffffff;
+			}
+		}
+		&--subsection {
+			height: 30px;
+		}
+		&__bar {
+			position: absolute;
+			/* #ifndef APP-NVUE */
+			transition-property: transform, color;
+			transition-duration: 0.3s;
+			transition-timing-function: ease-in-out;
+			/* #endif */
+			&--first {
+				border-top-left-radius: 3px;
+				border-bottom-left-radius: 3px;
+				border-top-right-radius: 0px;
+				border-bottom-right-radius: 0px;
+			}
+			&--center {
+				border-top-left-radius: 0px;
+				border-bottom-left-radius: 0px;
+				border-top-right-radius: 0px;
+				border-bottom-right-radius: 0px;
+			}
+			&--last {
+				border-top-left-radius: 0px;
+				border-bottom-left-radius: 0px;
+				border-top-right-radius: 3px;
+				border-bottom-right-radius: 3px;
+			}
+		}
+		&__item {
+			@include flex;
+			flex: 1;
+			justify-content: center;
+			align-items: center;
+			// vue环境下,需要设置相对定位,因为滑块为绝对定位,item需要在滑块的上面
+			position: relative;
+			&--no-border-right {
+				border-right-width: 0 !important;
+			}
+			&--first {
+				border-top-left-radius: 3px;
+				border-bottom-left-radius: 3px;
+			}
+			&--last {
+				border-top-right-radius: 3px;
+				border-bottom-right-radius: 3px;
+			}
+			&__text {
+				font-size: 12px;
+				line-height: 12px;
+				@include flex;
+				align-items: center;
+				transition-property: color;
+				transition-duration: 0.3s;
+			}
+		}
+	}
+</style>

+ 87 - 0
uni_modules/uv-subsection/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-subsection",
+  "displayName": "uv-subsection 分段器  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.3",
+  "description": "该分段器组件一般用于用户从几个选项中选择某一个的场景。",
+  "keywords": [
+    "uv-subsection",
+    "uvui",
+    "uv-ui",
+    "subsection",
+    "分段器"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-subsection/readme.md

@@ -0,0 +1,11 @@
+## Subsection 分段器
+
+> **组件名:uv-subsection**
+
+该分段器一般用于用户从几个选项中选择某一个的场景。
+
+### <a href="https://www.uvui.cn/components/subsection.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

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