Commit a2702f6d63c98787da3cd7a4105a83ccd77fef59

Authored by 刘淇
1 parent a7c9cfdc

巡查计划

Showing 545 changed files with 71009 additions and 19 deletions

Too many changes.

To preserve performance only 100 of 545 files are displayed.

1 1 <script setup>
2   -// 错误:import { onLaunch } from 'vue';
3   -// 正确:从 uni-app 导入应用生命周期钩子
4   -import { onLaunch } from '@dcloudio/uni-app';
  2 +import { onLaunch, onShow } from '@dcloudio/uni-app';
5 3  
  4 +// 应用启动时初始化
6 5 onLaunch(() => {
7 6 console.log('App Launch');
  7 + // 清空登录重定向残留(可选)
  8 + uni.removeStorageSync('loginRedirectPath');
8 9 });
  10 +
  11 +// 监听所有页面显示(核心:检测登录状态)
  12 +onShow(() => {
  13 + checkLoginStatus();
  14 +});
  15 +
  16 +// 登录检测核心函数
  17 +const checkLoginStatus = () => {
  18 + // 1. 获取当前页面信息(避免登录页循环跳转)
  19 + const pages = getCurrentPages();
  20 + if (pages.length === 0) return; // 无页面时跳过
  21 + const currentPage = pages[pages.length - 1];
  22 + const currentPath = currentPage.route; // 当前页面路径(如:pages/plan/index)
  23 +
  24 + // // 2. 白名单:无需登录的页面(登录页必须加,其他按需加)
  25 + // const whiteList = ['pages/login/index', 'pages/register/index']; // 可追加首页等
  26 + // if (whiteList.includes(currentPath)) return;
  27 +
  28 + // 3. 检测登录状态(仅判断token是否存在,极简版)
  29 + const token = uni.getStorageSync('jcss_token'); // 登录成功后需存token
  30 + if (!token) {
  31 + // 记录当前页面(登录后可返回)
  32 + uni.setStorageSync('loginRedirectPath', currentPath);
  33 + // 跳转到登录页(用redirectTo避免返回栈残留)
  34 + uni.redirectTo({
  35 + url: '/pages/login/index'
  36 + });
  37 + }
  38 +};
9 39 </script>
10 40  
11 41 <style lang="scss">
... ...
pages-sub/daily/patrol-manage/add-patrol-record/index.vue 0 → 100644
  1 +<template>
  2 + <view class="form-page">
  3 + <!-- uview表单容器 -->
  4 + <u-form
  5 + ref="formRef"
  6 + :model="formData"
  7 + :rules="formRules"
  8 + label-width="180rpx"
  9 + label-position="left"
  10 + >
  11 + <!-- 1. 文本域区域 -->
  12 + <u-form-item
  13 + label="问题描述"
  14 + prop="content"
  15 + class="form-item"
  16 + >
  17 + <u-textarea
  18 + v-model="formData.content"
  19 + placeholder="请输入问题描述..."
  20 + maxlength="200"
  21 + :show-word-limit="true"
  22 + :height="200"
  23 + border
  24 + class="textarea"
  25 + ></u-textarea>
  26 + <view class="word-count">{{ formData.content.length }}/200</view>
  27 + </u-form-item>
  28 +
  29 + <!-- 2. 图片上传区域(带星号,限制1-3张) -->
  30 + <u-form-item
  31 + label="上传图片"
  32 + prop="images"
  33 + class="form-item"
  34 + required
  35 + >
  36 + <u-upload
  37 + v-model="formData.images"
  38 + :action="uploadUrl"
  39 + :max-count="3"
  40 + :multiple="true"
  41 + @after-read="handleAfterRead"
  42 + @delete="handleDelete"
  43 + @on-exceed="handleExceed"
  44 + upload-text="选择图片"
  45 + del-color="#ff4d4f"
  46 + class="upload-wrap"
  47 + ></u-upload>
  48 + <view class="tips">(最少1张,最多3张)</view>
  49 + <view v-if="uploadTips" class="upload-tips">{{ uploadTips }}</view>
  50 + </u-form-item>
  51 +
  52 + <!-- 3. 单选选择区域 -->
  53 + <u-form-item
  54 + label="是否转为工单"
  55 + prop="isWorkOrder"
  56 + class="form-item"
  57 + required
  58 + >
  59 + <u-radio-group
  60 + v-model="formData.isWorkOrder"
  61 + :list="radioList"
  62 + active-color="#1989fa"
  63 + class="radio-group"
  64 + ></u-radio-group>
  65 + </u-form-item>
  66 + </u-form>
  67 +
  68 + <!-- 底部提交按钮 -->
  69 + <view class="btn-wrap">
  70 + <u-button
  71 + type="primary"
  72 + size="default"
  73 + @click="handleSubmit"
  74 + :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }"
  75 + >
  76 + 提交
  77 + </u-button>
  78 + </view>
  79 + </view>
  80 +</template>
  81 +
  82 +<script setup>
  83 +import { ref, reactive } from 'vue';
  84 +
  85 +// 表单实例
  86 +const formRef = ref(null);
  87 +
  88 +// 1. 表单数据(统一管理)
  89 +const formData = reactive({
  90 + content: '', // 问题描述
  91 + images: [], // 上传图片列表
  92 + isWorkOrder: '' // 是否转为工单 1:是 2:否
  93 +});
  94 +
  95 +// 2. 图片上传相关
  96 +const uploadUrl = ref(''); // 替换为真实上传接口地址
  97 +const uploadTips = ref(''); // 上传提示文字
  98 +
  99 +// 3. 单选列表
  100 +const radioList = ref([
  101 + { label: '是', value: '1' },
  102 + { label: '否', value: '2' }
  103 +]);
  104 +
  105 +// 4. 表单验证规则
  106 +const formRules = ref({
  107 + // 问题描述验证
  108 + content: [
  109 + { required: true, message: '请输入问题描述', trigger: 'blur' },
  110 + { max: 200, message: '问题描述不能超过200字', trigger: 'blur' }
  111 + ],
  112 + // 图片验证(自定义规则)
  113 + images: [
  114 + {
  115 + validator: (rule, value, callback) => {
  116 + if (value.length < 1) {
  117 + callback(new Error('最少需要上传1张图片'));
  118 + uploadTips.value = '最少需要上传1张图片!';
  119 + } else if (value.length > 3) {
  120 + callback(new Error('最多只能上传3张图片'));
  121 + uploadTips.value = '最多只能上传3张图片!';
  122 + } else {
  123 + uploadTips.value = '';
  124 + callback();
  125 + }
  126 + },
  127 + trigger: ['change', 'blur']
  128 + }
  129 + ],
  130 + // 单选验证
  131 + isWorkOrder: [
  132 + { required: true, message: '请选择是否转为工单', trigger: 'change' }
  133 + ]
  134 +});
  135 +
  136 +// 图片上传后处理
  137 +const handleAfterRead = (event) => {
  138 + const { file } = event;
  139 + // 模拟上传(实际项目替换为真实接口请求)
  140 + setTimeout(() => {
  141 + // 上传成功后将文件加入列表
  142 + formData.images.push({
  143 + url: file.url, // 本地临时路径/线上地址
  144 + name: file.name,
  145 + status: 'success'
  146 + });
  147 + // 手动触发图片验证
  148 + formRef.value.validateField('images');
  149 + }, 500);
  150 +};
  151 +
  152 +// 删除图片
  153 +const handleDelete = (index) => {
  154 + formData.images.splice(index, 1);
  155 + // 手动触发图片验证
  156 + formRef.value.validateField('images');
  157 +};
  158 +
  159 +// 超出最大数量提示
  160 +const handleExceed = () => {
  161 + uni.showToast({
  162 + title: '最多只能上传3张图片',
  163 + icon: 'none'
  164 + });
  165 +};
  166 +
  167 +// 提交表单
  168 +const handleSubmit = () => {
  169 + // 触发表单整体验证
  170 + formRef.value.validate((valid) => {
  171 + if (!valid) {
  172 + return; // 验证失败则返回
  173 + }
  174 +
  175 + // 验证通过,构造提交数据
  176 + const submitData = {
  177 + content: formData.content,
  178 + images: formData.images.map(item => item.url), // 提取图片地址
  179 + isWorkOrder: formData.isWorkOrder
  180 + };
  181 +
  182 + // 模拟接口提交
  183 + uni.showLoading({ title: '提交中...' });
  184 + setTimeout(() => {
  185 + uni.hideLoading();
  186 + uni.showToast({ title: '提交成功', icon: 'success' });
  187 + // 重置表单
  188 + formRef.value.resetFields();
  189 + formData.images = []; // 手动清空图片列表
  190 + uploadTips.value = '';
  191 + }, 1500);
  192 + });
  193 +};
  194 +</script>
  195 +
  196 +<style scoped lang="scss">
  197 +.form-page {
  198 + background-color: #f8f8f8;
  199 + min-height: 100vh;
  200 + padding: 20rpx;
  201 + box-sizing: border-box;
  202 + display: flex;
  203 + flex-direction: column;
  204 + padding-bottom: 100rpx; // 给底部按钮留空间
  205 +}
  206 +
  207 +// 表单项通用样式
  208 +.form-item {
  209 + background-color: #fff;
  210 + border-radius: 12rpx;
  211 + padding: 20rpx;
  212 + margin-bottom: 20rpx;
  213 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  214 +}
  215 +
  216 +// 文本域样式
  217 +.textarea {
  218 + margin-bottom: 10rpx;
  219 +}
  220 +
  221 +.word-count {
  222 + font-size: 24rpx;
  223 + color: #999;
  224 + text-align: right;
  225 + margin-top: 5rpx;
  226 +}
  227 +
  228 +// 上传区域样式
  229 +.upload-wrap {
  230 + margin-top: 10rpx;
  231 +}
  232 +
  233 +.tips {
  234 + font-size: 24rpx;
  235 + color: #999;
  236 + margin: 10rpx 0 5rpx 0;
  237 +}
  238 +
  239 +.upload-tips {
  240 + font-size: 24rpx;
  241 + color: #ff4d4f;
  242 + margin-top: 5rpx;
  243 +}
  244 +
  245 +// 单选组样式
  246 +.radio-group {
  247 + margin-top: 10rpx;
  248 +}
  249 +
  250 +// 底部按钮容器
  251 +.btn-wrap {
  252 + position: fixed;
  253 + bottom: 0;
  254 + left: 0;
  255 + right: 0;
  256 + z-index: 999;
  257 + padding: 0;
  258 + background-color: #f8f8f8;
  259 + // 兼容微信小程序底部安全区
  260 + /* #ifdef MP-WEIXIN */
  261 + padding-bottom: constant(safe-area-inset-bottom);
  262 + padding-bottom: env(safe-area-inset-bottom);
  263 + /* #endif */
  264 +}
  265 +
  266 +// 覆盖u-form默认样式(可选)
  267 +:deep(.u-form-item__content) {
  268 + padding-left: 0 !important;
  269 +}
  270 +
  271 +:deep(.u-form-item__label) {
  272 + font-size: 30rpx !important;
  273 + color: #333 !important;
  274 + font-weight: 500 !important;
  275 +}
  276 +
  277 +:deep(.u-form-item__error-message) {
  278 + font-size: 24rpx !important;
  279 + color: #ff4d4f !important;
  280 + margin-top: 5rpx !important;
  281 +}
  282 +</style>
0 283 \ No newline at end of file
... ...
pages-sub/daily/patrol-manage/index.vue 0 → 100644
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 12 \ No newline at end of file
... ...
pages-sub/daily/patrol-plan/index.vue renamed to pages-sub/daily/patrol-manage/patrol-plan/index.vue
... ... @@ -51,12 +51,12 @@
51 51 <!-- 道路名称 -->
52 52 <view class="row-item">
53 53 <text class="label">道路名称:</text>
54   - <text class="value">{{ item.roadName }}</text>
  54 + <text class="value up-line-1">{{ item.roadName }}</text>
55 55 </view>
56 56 <!-- 所属街道 -->
57 57 <view class="row-item">
58 58 <text class="label">所属街道:</text>
59   - <text class="value">{{ item.street }}</text>
  59 + <text class="value up-line-1">{{ item.street }}</text>
60 60 </view>
61 61 <!-- 养护级别 + 计划明细按钮 -->
62 62 <view class="row-item flex-between">
... ... @@ -69,12 +69,12 @@
69 69 <!-- 计划类型 -->
70 70 <view class="row-item">
71 71 <text class="label">计划类型:</text>
72   - <text class="value">{{ item.planType }}</text>
  72 + <text class="value up-line-1">{{ item.planType }}</text>
73 73 </view>
74 74 <!-- 计划时间 -->
75 75 <view class="row-item">
76 76 <text class="label">计划时间:</text>
77   - <text class="value">{{ item.planTime }}</text>
  77 + <text class="value up-line-1">{{ item.planTime }}</text>
78 78 </view>
79 79 </view>
80 80 </view>
... ... @@ -92,7 +92,7 @@
92 92  
93 93 <script setup>
94 94 import { ref, reactive, onMounted } from 'vue';
95   -import { onLoad } from '@dcloudio/uni-app';
  95 +import { onLoad, onShow } from '@dcloudio/uni-app';
96 96 // Tabs 配置
97 97 const tabList = ref([
98 98 {name: '待完成', id: 'pending'},
... ... @@ -136,9 +136,9 @@ const generateMockData = (page) =&gt; {
136 136 };
137 137 const maintainLevels = ['一级养护', '二级养护', '三级养护'];
138 138 const planTypes = ['日常养护', '专项养护', '应急养护'];
139   - const streets = ['西长安街街道', '东城区东华门街道', '朝阳区望京街道', '海淀区中关村街道', '丰台区马家堡街道'];
  139 + const streets = ['西长安街街道西长安街街道西长安街街道西长安街街道', '东城区东华门街道', '朝阳区望京街道', '海淀区中关村街道', '丰台区马家堡街道'];
140 140 const roadNames = [
141   - '长安街', '王府井大街', '中关村南大街', '朝阳北路', '建国路',
  141 + '长安街王府井大街王府井大街王府井大街王府井大街王府井大街', '王府井大街王府井大街王府井大街王府井大街王府井大街', '中关村南大街', '朝阳北路', '建国路',
142 142 '复兴路', '西直门外大街', '广渠路', '阜成门外大街', '安定门东大街'
143 143 ];
144 144 // 生成当前页数据
... ... @@ -146,7 +146,7 @@ const generateMockData = (page) =&gt; {
146 146 const idx = (page - 1) * pageSize.value + i;
147 147 mockList.push({
148 148 id: idx + 1,
149   - roadName: `${roadNames[idx % roadNames.length]}${idx + 1}段`,
  149 + roadName: `${roadNames[i]}${roadNames[i]}${roadNames[i]}${idx + 1}段`,
150 150 street: streets[Math.floor(Math.random() * streets.length)],
151 151 maintainLevel: maintainLevels[Math.floor(Math.random() * maintainLevels.length)],
152 152 planType: planTypes[Math.floor(Math.random() * planTypes.length)],
... ... @@ -168,16 +168,25 @@ const loadData = (isRefresh = false) =&gt; {
168 168 }
169 169 }, 500);
170 170 };
171   -// 跳转到计划明细页面
  171 +// 跳转到待完成计划明细页面
172 172 const gotoDetail = (item) => {
173 173 uni.navigateTo({
174   - url: `/pages/plan-detail/index?id=${item.id}&status=${activeTab.value}`
  174 + url: `/pages-sub/daily/patrol-manage/pending-plan-detail/index?id=${item.id}&status=${activeTab.value}`
175 175 });
176 176 };
177 177 // 页面加载初始化
178 178 onLoad(() => {
179 179 loadData(true);
180 180 });
  181 +
  182 +// 页面每次显示时刷新(包括回退)
  183 +onShow(() => {
  184 + // 避免首次加载重复请求(onLoad已执行)
  185 + if (planList.value.length > 0) {
  186 + pageNo.value = 1;
  187 + loadData(true); // 重新请求接口,刷新列表
  188 + }
  189 +});
181 190 </script>
182 191  
183 192 <style scoped lang="scss">
... ...
pages-sub/daily/patrol-manage/pending-plan-detail/index.vue 0 → 100644
  1 +<template>
  2 + <view class="pending-plan-detail">
  3 + <!-- 计划详情卡片 -->
  4 + <view class="detail-card">
  5 + <!-- 标题行 -->
  6 + <view class="detail-item">
  7 + <text class="label">标题:</text>
  8 + <text class="value up-line-1">{{ planInfo && planInfo.title ? planInfo.title : '-' }}</text>
  9 + </view>
  10 +
  11 + <!-- 计划编码行 -->
  12 + <view class="detail-item">
  13 + <text class="label">计划编码:</text>
  14 + <text class="value up-line-1">{{ planInfo && planInfo.code ? planInfo.code : '-' }}</text>
  15 + </view>
  16 +
  17 + <!-- 养护周期行 -->
  18 + <view class="detail-item">
  19 + <text class="label">养护周期:</text>
  20 + <text class="value up-line-1">{{ planInfo && planInfo.cycle ? planInfo.cycle : '-' }}</text>
  21 + </view>
  22 +
  23 + <!-- 计划完成次数行 -->
  24 + <view class="detail-item">
  25 + <text class="label">计划完成次数:</text>
  26 + <text class="value up-line-1">{{ planInfo && planInfo.planCount ? planInfo.planCount : 0 }} 次</text>
  27 + </view>
  28 +
  29 + <!-- 已完成次数行 + 查看记录按钮 -->
  30 + <view class="detail-item flex-between">
  31 + <view class="left-wrap">
  32 + <text class="label">已完成次数:</text>
  33 + <text class="value up-line-1">{{ planInfo && planInfo.finishCount ? planInfo.finishCount : 0 }} 次</text>
  34 + </view>
  35 + <!-- 查看记录按钮(限制宽度+紧凑样式) -->
  36 + <u-button
  37 + type="primary"
  38 + size="mini"
  39 + :disabled="!(planInfo && planInfo.code)"
  40 + @click="gotoFinishPlanDetail"
  41 + class="view-record-btn"
  42 + >
  43 + 查看记录
  44 + </u-button>
  45 + </view>
  46 +
  47 + <!-- 计划有效期行 -->
  48 + <view class="detail-item">
  49 + <text class="label">计划有效期:</text>
  50 + <text class="value up-line-1">{{ planInfo && planInfo.validTime ? planInfo.validTime : '-' }}</text>
  51 + </view>
  52 + </view>
  53 +
  54 + <!-- 底部新增记录按钮 -->
  55 + <view class="fixed-btn-wrap">
  56 + <u-button
  57 + type="primary"
  58 + size="default"
  59 + @click="addNewRecord"
  60 + :disabled="!(planInfo && planInfo.code)"
  61 + :style="{ width: '100%', height: '88rpx', fontSize: '30rpx', borderRadius: 0 }"
  62 + >
  63 + 新增记录
  64 + </u-button>
  65 + </view>
  66 + </view>
  67 +</template>
  68 +
  69 +<script setup>
  70 +import { ref } from 'vue';
  71 +import { onLoad, onShow } from '@dcloudio/uni-app';
  72 +
  73 +// 响应式数据定义
  74 +const planInfo = ref({
  75 + title: '123213',
  76 + code: '',
  77 + cycle: '',
  78 + planCount: 0,
  79 + finishCount: 0,
  80 + validTime: ''
  81 +});
  82 +const planId = ref('')
  83 +
  84 +// 页面加载接收参数
  85 +onLoad((options) => {
  86 + console.log('计划ID:', options.id);
  87 + planId.value = options.id;
  88 +});
  89 +
  90 +// 页面显示时请求数据
  91 +onShow(() => {
  92 + if (!planId.value) {
  93 + uni.showToast({
  94 + title: '计划ID不存在',
  95 + icon: 'none'
  96 + });
  97 + return;
  98 + }
  99 + getPlanDetail(planId.value);
  100 +});
  101 +
  102 +// 模拟接口请求
  103 +const getPlanDetail = (id) => {
  104 + setTimeout(() => {
  105 + planInfo.value = {
  106 + title: '朝阳区望京街道道路养护计划朝阳区望京街道道路养护计划朝阳区望京街道道路养护计划',
  107 + code: 'YH-2025-0891YH-2025-0891YH-2025-0891YH-2025-0891',
  108 + cycle: '每月1次',
  109 + planCount: 12,
  110 + finishCount: 3,
  111 + validTime: '2025-01-01 至 2025-12-31'
  112 + };
  113 + }, 300);
  114 +};
  115 +
  116 +// 跳转到已完成计划明细
  117 +const gotoFinishPlanDetail = () => {
  118 + // if (!planInfo.value || !planInfo.value.code) {
  119 + // uni.showToast({
  120 + // title: '计划数据未加载完成',
  121 + // icon: 'none'
  122 + // });
  123 + // return;
  124 + // }
  125 + uni.navigateTo({
  126 + url: `/pages-sub/daily/patrol-manage/finish-plan-detail/index?planId=${planInfo.value.code}`
  127 + });
  128 +};
  129 +
  130 +// 新增记录
  131 +const addNewRecord = () => {
  132 + if (!planInfo.value || !planInfo.value.code) {
  133 + uni.showToast({
  134 + title: '计划数据未加载完成',
  135 + icon: 'none'
  136 + });
  137 + return;
  138 + }
  139 + uni.navigateTo({
  140 + url: `/pages-sub/daily/patrol-manage/add-patrol-record/index?planId=${planInfo.value.code}`
  141 + });
  142 +};
  143 +</script>
  144 +
  145 +<style scoped lang="scss">
  146 +.pending-plan-detail {
  147 + background-color: #f8f8f8;
  148 + display: flex;
  149 + flex-direction: column;
  150 + min-height: 100vh; // 恢复最小高度,保证页面占满屏幕
  151 + padding-bottom: 88rpx; // 给底部按钮留空间
  152 +}
  153 +
  154 +.detail-card {
  155 + padding: 20rpx;
  156 + background-color: #fff;
  157 + border-radius: 12rpx;
  158 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  159 + flex: 1;
  160 +}
  161 +
  162 +.detail-item {
  163 + display: flex;
  164 + align-items: center;
  165 + font-size: 28rpx;
  166 + line-height: 1.8;
  167 + padding: 10rpx 0;
  168 + border-bottom: 1px solid #f5f5f5;
  169 +
  170 + &:last-child {
  171 + border-bottom: none;
  172 + }
  173 +}
  174 +
  175 +.flex-between {
  176 + display: flex;
  177 + justify-content: space-between;
  178 + align-items: center;
  179 +
  180 + .left-wrap {
  181 + display: flex;
  182 + align-items: center;
  183 + flex: 1; // 占满剩余空间,按钮靠右
  184 + }
  185 +}
  186 +
  187 +.label {
  188 + color: #999;
  189 + width: 200rpx;
  190 + flex-shrink: 0;
  191 +}
  192 +
  193 +.value {
  194 + color: #333;
  195 + flex: 1;
  196 + word-break: break-all;
  197 +}
  198 +
  199 +// 查看记录按钮样式(核心优化)
  200 +.view-record-btn {
  201 + width: 160rpx !important; // 固定宽度,按需调整
  202 + padding: 0 !important; // 移除内边距,减少宽度
  203 + margin-right: 10rpx;
  204 + font-size: 24rpx !important; // 缩小字体,更紧凑
  205 + height: 50rpx !important; // 调整高度,比例协调
  206 +}
  207 +
  208 +// 底部固定按钮容器
  209 +.fixed-btn-wrap {
  210 + position: fixed;
  211 + bottom: 0;
  212 + left: 0;
  213 + right: 0;
  214 + z-index: 999;
  215 + padding: 0;
  216 + background-color: #f8f8f8;
  217 +}
  218 +
  219 +// 兼容小程序底部安全区
  220 +/* #ifdef MP-WEIXIN */
  221 +.fixed-btn-wrap {
  222 + padding-bottom: constant(safe-area-inset-bottom);
  223 + padding-bottom: env(safe-area-inset-bottom);
  224 +}
  225 +/* #endif */
  226 +</style>
0 227 \ No newline at end of file
... ...
pages.json
... ... @@ -30,18 +30,37 @@
30 30 "root": "pages-sub/daily",
31 31 "pages": [
32 32 {
33   - "path": "patrol-plan/index",
  33 + "path": "patrol-manage/patrol-plan/index",
34 34 "style": {
35 35 "navigationBarTitleText": "巡查计划",
36 36 "enablePullDownRefresh": false
37 37 }
38 38 },
39 39 {
40   - "path": "pages/plan-detail/index",
  40 + "path": "patrol-manage/pending-plan-detail/index",
41 41 "style": {
42   - "navigationBarTitleText": "计划明细"
  42 + "navigationBarTitleText": "待完成计划明细",
  43 + "enablePullDownRefresh": false
  44 + }
  45 + },
  46 +
  47 + {
  48 + "path": "patrol-manage/finish-plan-detail/index",
  49 + "style": {
  50 + "navigationBarTitleText": "已完成计划明细",
  51 + "enablePullDownRefresh": false
43 52 }
44 53 },
  54 +
  55 + {
  56 + "path": "patrol-manage/add-patrol-record/index",
  57 + "style": {
  58 + "navigationBarTitleText": "添加巡查记录",
  59 + "enablePullDownRefresh": false
  60 + }
  61 + },
  62 +
  63 +
45 64 {
46 65 "path": "work-order/index",
47 66 "style": { "navigationBarTitleText": "工单上报" }
... ...
pages/workbench/index.vue
... ... @@ -41,7 +41,7 @@ import { getDailyManageMenu, getProblemManageMenu } from &#39;@/api/workbench&#39;;
41 41  
42 42 const userStore = useUserStore();
43 43 const dailyMenuList = ref([
44   - { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-plan/index' },
  44 + { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-manage/patrol-plan/index' },
45 45 { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },
46 46 { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },
47 47 { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },
... ... @@ -65,7 +65,7 @@ const loadMenuData = async () =&gt; {
65 65 try {
66 66 const [dailyRes, problemRes] = await Promise.all([getDailyManageMenu(), getProblemManageMenu()]);
67 67 dailyMenuList.value = dailyRes || [
68   - { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-plan/index' },
  68 + { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-manage/patrol-plan/index' },
69 69 { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },
70 70 { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },
71 71 { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },
... ... @@ -80,7 +80,7 @@ const loadMenuData = async () =&gt; {
80 80 console.error('加载菜单失败:', err);
81 81 // 兜底数据
82 82 dailyMenuList.value = [
83   - { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-plan/index' },
  83 + { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-manage/patrol-plan/index' },
84 84 { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },
85 85 { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },
86 86 { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },
... ...
uni_modules/uview-plus/LICENSE 0 → 100644
  1 +MIT License
  2 +
  3 +Copyright (c) 2024 https://uiadmin.net/uview-plus
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +of this software and associated documentation files (the "Software"), to deal
  7 +in the Software without restriction, including without limitation the rights
  8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the Software is
  10 +furnished to do so, subject to the following conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be included in all
  13 +copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21 +SOFTWARE.
0 22 \ No newline at end of file
... ...
uni_modules/uview-plus/README.md 0 → 100644
  1 +<p align="center">
  2 + <img alt="logo" src="https://uiadmin.net/uview-plus/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
  3 +</p>
  4 +<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3>
  5 +<h3 align="center">多平台快速开发的UI框架</h3>
  6 +
  7 +[![stars](https://img.shields.io/github/stars/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
  8 +[![forks](https://img.shields.io/github/forks/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
  9 +[![issues](https://img.shields.io/github/issues/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus/issues)
  10 +[![release](https://img.shields.io/github/v/release/ijry/uview-plus?style=flat-square)](https://gitee.com/jry/uview-plus/releases)
  11 +[![license](https://img.shields.io/github/license/ijry/uview-plus?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
  12 +
  13 +## 说明
  14 +
  15 +uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x(即将发布)的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
  16 +
  17 +## 可视化设计
  18 +
  19 +uview-plus现已推出免费可视化设计,可以方便的进行页面可视化设计,导出源码即可使用。极大提高前端页面开发效率;如产品经理设计师直接使用更可作为高保真高可用原型制作工具,让设计稿即代码,无需传统的设计稿开发还原步骤。
  20 +
  21 +<img src="https://s3.bmp.ovh/imgs/2024/11/24/fd58d00071e6e5df.png" width="900" height="auto" >
  22 +<img src="https://s3.bmp.ovh/imgs/2024/11/24/8e85a519fe627fb1.png" width="900" height="auto" >
  23 +
  24 +
  25 +## 文档
  26 +[官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
  27 +[备用文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus)
  28 +
  29 +
  30 +## 预览
  31 +
  32 +您可以通过**微信**扫码,查看最佳的演示效果。
  33 +<br>
  34 +<br>
  35 +<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" >
  36 +
  37 +## 链接
  38 +
  39 +- [官方文档](https://uview-plus.jiangruyi.com)
  40 +- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html)
  41 +- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html)
  42 +- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html)
  43 +
  44 +## 交流反馈
  45 +
  46 +欢迎加入我们的QQ群交流反馈:[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html)
  47 +
  48 +## 关于PR
  49 +
  50 +> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
  51 +> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
  52 +
  53 +## 安装
  54 +
  55 +#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus)
  56 +
  57 +请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容
  58 +
  59 +## 快速上手
  60 +
  61 +请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容
  62 +
  63 +## 使用方法
  64 +配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
  65 +
  66 +```html
  67 +<template>
  68 + <u-button text="按钮"></u-button>
  69 +</template>
  70 +```
  71 +
  72 +## 版权信息
  73 +uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。
  74 +
... ...
uni_modules/uview-plus/changelog.md 0 → 100644
  1 +## 3.6.20(2025-12-08)
  2 +improvment: 优化form组件语法普适性
  3 +
  4 +## 3.6.19(2025-12-01)
  5 +fix: 修复商品SKU组件i18n
  6 +
  7 +fix: Choose组件会有width警告提示 #915
  8 +
  9 +## 3.6.18(2025-11-30)
  10 +feature: upload组件file模式支持微信小程序chooseMessageFile的extension属性 #913
  11 +
  12 +## 3.6.17(2025-11-18)
  13 +add: picker-data支持表单验证
  14 +
  15 +## 3.6.16(2025-11-18)
  16 +improvment: 优化下拉菜单示例
  17 +
  18 +## 3.6.15(2025-11-10)
  19 +fix: 修复tabs添加activeStyle切换后滑块无法居中
  20 +
  21 +## 3.6.14(2025-11-07)
  22 +fix: 修复引用拼写
  23 +
  24 +## 3.6.13(2025-11-06)
  25 +improvment: 组合式API升级之gap示例改造
  26 +
  27 +## 3.6.12(2025-11-05)
  28 +improvment: empty示例改为组合式API
  29 +
  30 +## 3.6.11(2025-11-05)
  31 +improvment: divider示例改为组合式API
  32 +
  33 +## 3.6.10(2025-11-04)
  34 +improvment: checkbox示例改为组合式API
  35 +
  36 +## 3.6.9(2025-10-21)
  37 +improvment: cell示例改为组合式API
  38 +
  39 +## 3.6.8(2025-10-20)
  40 +imporvment: button示例改为组合式API
  41 +
  42 +## 3.6.7(2025-10-17)
  43 +fix: 优化picker当前列判断
  44 +
  45 +## 3.6.6(2025-10-13)
  46 +fix: 修复getWindowInfo微信小程序下报警告
  47 +
  48 +## 3.6.5(2025-10-11)
  49 +🐛fix: 修复picker双向绑定 #753
  50 +
  51 +## 3.6.4(2025-10-09)
  52 +🐛fix: import rpx2px function and add to exports in index.js
  53 +
  54 +## 3.6.3(2025-10-09)
  55 +fix: 修复getWindowInfo微信小程序下报警告
  56 +
  57 +## 3.6.2(2025-10-05)
  58 +fix: 修复picker双向绑定 #753
  59 +
  60 +## 3.6.1(2025-10-01)
  61 +fix: 修复poster海报组件微信小程序图片绘制失败
  62 +
  63 +## 3.6.0(2025-09-30)
  64 +fix: 修复parse事件报错及统一事件名称小写
  65 +
  66 +## 3.5.57(2025-09-30)
  67 +improvment: 更换部分资源域名提升访问速度
  68 +
  69 +## 3.5.56(2025-09-29)
  70 +fix: 修复picker和datetime-picker在部分原生渲染时hasInput触发键盘弹起
  71 +
  72 +## 3.5.55(2025-09-29)
  73 +feat: waterfall新增after-add-one和after-add-all事件
  74 +
  75 +## 3.5.54(2025-09-25)
  76 +🐞 fix: 解决索引列表初始化时获取不到高度导致的字母列表异常
  77 +
  78 +## 3.5.53(2025-09-19)
  79 +improvment: back-top示例组合式API改造
  80 +
  81 +## 3.5.52(2025-09-19)
  82 +fix: 优化tabbar的borderColor优先级
  83 +
  84 +## 3.5.51(2025-09-19)
  85 +fix: 微信小程序下u-transition包裹的内容因为没有默认flex column而出现布局错误
  86 +
  87 +## 3.5.50(2025-09-18)
  88 +fix: 修复tabbar组件borderColor类型
  89 +
  90 +## 3.5.49(2025-09-18)
  91 +fix: 修复action-sheet列表中第一个item的hover style
  92 +
  93 +## 3.5.48(2025-09-17)
  94 +fix: 修复table2的fixedHeader参数生效
  95 +
  96 +## 3.5.47(2025-09-16)
  97 +fix: 修复modelValue类型
  98 +
  99 +## 3.5.46(2025-09-15)
  100 +feat: table2新增支持span-method合并单元格
  101 +
  102 +## 3.5.45(2025-09-13)
  103 +fix: 修复数据分配时未等待获取元素后再执行下一次循环导致数据插入错误,addTime属性未使用修改。
  104 +
  105 +## 3.5.44(2025-09-12)
  106 +fix: 单选快捷组件作为受控组件,当modelValue清除选择时,组件状态未被清除。
  107 +
  108 +## 3.5.43(2025-09-10)
  109 +fix: 增加subsection兼容性索引转为数字
  110 +
  111 +## 3.5.42(2025-09-09)
  112 +feat: index-list增加下边距高度
  113 +
  114 +## 3.5.41(2025-09-08)
  115 +fix: 修复cascader方法引用位置
  116 +
  117 +## 3.5.40(2025-09-08)
  118 +feat: goods-sku组件支持多语言
  119 +
  120 +## 3.5.39(2025-09-07)
  121 +fix: 修复cascader多语言问题
  122 +
  123 +## 3.5.38(2025-09-07)
  124 +improvment: 完善演示图标
  125 +
  126 +## 3.5.37(2025-09-05)
  127 +feat: cascader支持多语言
  128 +
  129 +## 3.5.36(2025-09-04)
  130 +improvment: 补充图标
  131 +
  132 +## 3.5.35(2025-09-04)
  133 +feat: cascader级联组件增加垂直头部与单列选项支持
  134 +
  135 +## 3.5.34(2025-09-03)
  136 +improvement: 优化steps-item
  137 +
  138 +## 3.5.33(2025-09-02)
  139 +升级uni-app至最新版
  140 +## 3.5.32(2025-09-01)
  141 +feat: cate-tab支持非联动跟随的单一切换模式
  142 +
  143 +feat: choose组件示例使用cate-tab的单一切换模式
  144 +
  145 +
  146 +## 3.5.31(2025-09-01)
  147 +feat: 新增choose通用选项选择器组件
  148 +
  149 +## 3.5.30(2025-08-31)
  150 +fix: 修复缺失cascader级联选择器
  151 +
  152 +## 3.5.29(2025-08-30)
  153 +feat: 新增cascader级联选择器
  154 +
  155 +## 3.5.28(2025-08-29)
  156 +feat: 多语言增加泰语
  157 +
  158 +## 3.5.27(2025-08-29)
  159 +feat: 新增基于tooltip的popover组件
  160 +
  161 +## 3.5.26(2025-08-29)
  162 +improvment: 增强多语言兼容性
  163 +
  164 +## 3.5.25(2025-08-28)
  165 +fix: 修复左侧与右侧弹出时页面打开闪现问题
  166 +
  167 +## 3.5.24(2025-08-28)
  168 +feat: 优化tooltip弹窗定位
  169 +
  170 +feat: tooltip组件新增forcePosition支持强制精确定位
  171 +
  172 +## 3.5.23(2025-08-28)
  173 +improvment: 调整pdf阅读器默认高度
  174 +
  175 +## 3.5.22(2025-08-28)
  176 +feat: 新增goods-sku商品SKU选购组件
  177 +
  178 +## 3.5.21(2025-08-27)
  179 +fix: 修复popup组件手势控制参数注释
  180 +
  181 +## 3.5.20(2025-08-27)
  182 +improvement: 提升checkbox条件编译兼容性
  183 +
  184 +## 3.5.19(2025-08-27)
  185 +feat: popup新增iOS风格手势控制上下高度及下滑关闭
  186 +
  187 +## 3.5.18(2025-08-26)
  188 +feat: 新增coupon优惠券组件
  189 +
  190 +## 3.5.17(2025-08-26)
  191 +fix: 分支合并冲突修复
  192 +
  193 +feat: picker-data组件新增cancel/close/confirm事件支持
  194 +
  195 +## 3.5.16(2025-08-26)
  196 +feat: tabbar新增支持中间凸起按钮
  197 +
  198 +## 3.5.15(2025-08-25)
  199 +feat: 之前的color-picker颜色选择器按计划上线
  200 +
  201 +## 3.5.14(2025-08-25)
  202 +feat: 新增PDF阅读器组件
  203 +
  204 +## 3.5.13(2025-08-24)
  205 +fix: 修复短视频示例https
  206 +
  207 +## 3.5.12(2025-08-23)
  208 +feat: short-video短视频组件增加视频播放示例
  209 +
  210 +## 3.5.11(2025-08-23)
  211 +feat: 新增short-video短视频切换组件
  212 +
  213 +feat: tabbar支持backgroundColor和borderColor属性
  214 +
  215 +feat: slider支持innerStyle样式属性
  216 +
  217 +## 3.5.10(2025-08-22)
  218 +feat: 新增poster海报生成组件
  219 +
  220 +
  221 +## 3.5.9(2025-08-22)
  222 +feat: 优化qrcode组件支持toTempFilePath方法
  223 +
  224 +## 3.5.8(2025-08-22)
  225 +fix: 优化rpx下calendar高度计算强制px
  226 +
  227 +## 3.5.7(2025-08-22)
  228 +feat: input和search新增onlyClearableOnFocused参数支持控制是否仅聚焦时显示清除按钮
  229 +
  230 +## 3.5.6(2025-08-21)
  231 +feat: picker弹窗及datetime-picker组件支持页面内模式
  232 +
  233 +## 3.5.5(2025-08-21)
  234 +feat: popup弹窗及canlendar组件支持页面内插入无弹窗模式
  235 +
  236 +## 3.5.4(2025-08-21)
  237 +feat: popup弹窗及canlendar组件支持页面内插入无弹窗模式
  238 +
  239 +## 3.5.3(2025-08-21)
  240 +fix: 修复下拉刷新多语言key
  241 +
  242 +## 3.5.2(2025-08-20)
  243 +fix: 修复非cli模式下引入的三方库报An error occurred while trying to read the map file错误
  244 +
  245 +fix: 去除i18n多语言多余console
  246 +
  247 +## 3.5.1(2025-08-20)
  248 +feat: calendar组件月份标题自动适配多语言
  249 +
  250 +## 3.5.0(2025-08-20)
  251 +feat: 组件库新增多语言支持且无侵入无依赖内置八种语言
  252 +
  253 +## 3.4.107(2025-08-20)
  254 +feat: input组件支持密码显示切换
  255 +
  256 +## 3.4.106(2025-08-20)
  257 +feat: tooltip组件支持左右方向弹窗
  258 +
  259 +## 3.4.105(2025-08-19)
  260 +feat: 增加markdown解析器组件AI对话流式响应示例
  261 +
  262 +## 3.4.104(2025-08-19)
  263 +feat: tooltip支持自定义触发器
  264 +
  265 +feat: tooltip支持插槽自定义内容
  266 +
  267 +feat: tooltip支持单击触发
  268 +
  269 +feat: tooltip支持设置弹窗背景色
  270 +
  271 +## 3.4.103(2025-08-19)
  272 +feat: 内置一份dayjs库
  273 +
  274 +fix: 删除个别组件多余重复方法
  275 +
  276 +fix: 修复marked库App打包时报错
  277 +
  278 +## 3.4.102(2025-08-19)
  279 +fix: 小程序下cate-tab动态加载数据后尺寸获取及联动异常
  280 +
  281 +## 3.4.101(2025-08-18)
  282 +improvment: 增加一些涉及触摸组件PC端需要仿真模式提示
  283 +
  284 +## 3.4.100(2025-08-18)
  285 +feat: 新增markdown解析器组件
  286 +
  287 +## 3.4.99(2025-08-18)
  288 +fix: 修复parse富文本组件可能导致无限循环的问题
  289 +
  290 +## 3.4.98(2025-08-17)
  291 +feat: alert组件新增transitionMode/icon/duration/modelValue等参数
  292 +
  293 +improvment: 完善alert组件注释
  294 +
  295 +## 3.4.97(2025-08-16)
  296 +improvment: 完善album组件注释
  297 +
  298 +## 3.4.96(2025-08-16)
  299 +improvment: 完善action-sheet注释
  300 +
  301 +## 3.4.95(2025-08-16)
  302 +del: 去除不再需要的重复组件
  303 +
  304 +## 3.4.94(2025-08-15)
  305 +improvment: 组件库内部icon前缀统一
  306 +
  307 +## 3.4.93(2025-08-15)
  308 +fix: 修复dropdown组件在打开下拉菜单时content高度为NANpx的问题
  309 +
  310 +## 3.4.92(2025-08-14)
  311 +fix: 修复多余空格导致cropper编译出错 #830
  312 +
  313 +## 3.4.91(2025-08-14)
  314 +feat: 新增agreement弹窗协议组件
  315 +
  316 +## 3.4.90(2025-08-13)
  317 +feat: datetimepicker新增datehour类型
  318 +
  319 +## 3.4.89(2025-08-13)
  320 +fix: 修复u-form-item组件缺少对labelPosition的判断
  321 +
  322 +## 3.4.88(2025-08-13)
  323 +fix: subsection组件的disabled属性应为Boolean类型
  324 +
  325 +## 3.4.87(2025-08-12)
  326 +improvment: 签名组件示例前缀改为up
  327 +
  328 +## 3.4.86(2025-08-12)
  329 +feat: 新增signature签名签字组件
  330 +
  331 +## 3.4.85(2025-08-11)
  332 +feat: 完善图片裁剪功能支持props及JS两种方式
  333 +
  334 +fix: 修复H5下裁剪后图片黑屏
  335 +
  336 +## 3.4.84(2025-08-11)
  337 +feat: 新增cropper图片裁剪组件
  338 +
  339 +## 3.4.83(2025-08-11)
  340 +feat: 新增barcode无三方依赖条码组
  341 +
  342 +## 3.4.82(2025-08-11)
  343 +improvment: 优化table2递归组件逻辑
  344 +
  345 +feat: 支持树状列表复选框
  346 +
  347 +## 3.4.81(2025-08-10)
  348 +feat: table2新增树状结构递归支持
  349 +
  350 +## 3.4.80(2025-08-10)
  351 +feat: 新增table2固定列对树状支持
  352 +
  353 +## 3.4.79(2025-08-09)
  354 +feat: table2组件支持左侧固定列功能
  355 +
  356 +## 3.4.78(2025-08-09)
  357 +fix: 修复table2在popup弹窗中无法横向滚动
  358 +
  359 +## 3.4.77(2025-08-08)
  360 +feat: 优化waterfall瀑布流组件逻辑
  361 +
  362 +feat: waterfall瀑布流组件支持多列
  363 +
  364 +feat: waterfall瀑布流组件支持响应式列数
  365 +
  366 +## 3.4.76(2025-08-08)
  367 +feat: 取消props中tabs组件默认颜色影响主题色生效
  368 +
  369 +fix: 修复parse富文本组件报错$options
  370 +
  371 +feat: timeFormat时间格式化方法支持UTC格式时间 #596
  372 +
  373 +## 3.4.75(2025-08-07)
  374 +feat: 新增loadFontOnce参数控制是否全局只加载一次字体图标
  375 +
  376 +## 3.4.74(2025-08-07)
  377 +feat: input组件新增cursorColor属性
  378 +
  379 +## 3.4.73(2025-08-05)
  380 +improvment: 优化下拉刷新默认图标
  381 +
  382 +## 3.4.72(2025-08-05)
  383 +improvment: 优化virtual-list虚拟列表样式
  384 +
  385 +feat: 新增virtual-list虚拟列表演示
  386 +
  387 +## 3.4.71(2025-08-05)
  388 +feat: 新增virtual-list虚拟列表组件
  389 +
  390 +feat: 新增pull-refresh下拉刷新结合虚拟列表使用
  391 +
  392 +## 3.4.70(2025-08-05)
  393 +fix: 修复qrcode组件长按事件传入错误cid导致的报错
  394 +
  395 +fix: 修复index-list组件key为undefined导致的索引报错消失
  396 +
  397 +## 3.4.69(2025-08-04)
  398 +feat: pull-refresh下拉刷新新增上拉加载特性
  399 +
  400 +## 3.4.68(2025-08-03)
  401 +feat: 新增pull-refresh下拉刷新组件
  402 +
  403 +## 3.4.67(2025-08-02)
  404 +feat: number-box支持disabledBgColor参数
  405 +
  406 +## 3.4.66(2025-08-01)
  407 +improvment: 增加组件图标
  408 +
  409 +feat: table2组件新增列排序插槽
  410 +
  411 +fix: 分段器组件onWindowResize使用条件编译
  412 +
  413 +fix: 修复u-line-1单行省略样式
  414 +
  415 +## 3.4.65(2025-07-26)
  416 +fix: 修复部分环境下字体图标全局加载问题
  417 +
  418 +## 3.4.64(2025-07-25)
  419 +feat: 新增dragsort拖动排序组件
  420 +
  421 +## 3.4.63(2025-07-24)
  422 +feat: count-down支持slot传递时间参数
  423 +
  424 +## 3.4.62(2025-07-24)
  425 +feat: divider支持默认插槽
  426 +
  427 +## 3.4.61(2025-07-24)
  428 +feat: divider支持默认插槽
  429 +
  430 +## 3.4.60(2025-07-23)
  431 +fix: 修复use阶段全局加载图标引起App端不明$page报错
  432 +
  433 +## 3.4.59(2025-07-23)
  434 +fix: 修复album组件和CateTab组件watch和emits重复
  435 +
  436 +fix: 修复样式优先级导致form表单labelPosition属性top失效
  437 +
  438 +## 3.4.58(2025-07-22)
  439 +fix: 修复样式优先级影响grid宫格组件布局
  440 +
  441 +## 3.4.57(2025-07-22)
  442 +feat: 新增title标题组件
  443 +
  444 +## 3.4.56(2025-07-21)
  445 +fix: 去除多余hotCity参数
  446 +
  447 +## 3.4.55(2025-07-21)
  448 +feat: 新增city-locate城市定位选择组件
  449 +
  450 +feat: 优化index-list组件索引
  451 +
  452 +## 3.4.54(2025-07-18)
  453 +feat: dropdown组件的highlight方法支持同时高亮多个菜单项
  454 +
  455 +feat: 支持一次性全局加载icon字体
  456 +
  457 +## 3.4.53(2025-07-18)
  458 +feat: dropdown组件的highlight方法支持同时高亮多个菜单项 感谢@keeplearning66
  459 +
  460 +## 3.4.52(2025-07-16)
  461 +fix: 修复底部安全区域组件兼容性
  462 +
  463 +## 3.4.51(2025-07-14)
  464 +fix: 修复u-slider在click后没有触发change事件
  465 +
  466 +## 3.4.50(2025-07-13)
  467 +feat: subsection分段器添加禁用参数
  468 +
  469 +## 3.4.49(2025-07-11)
  470 +feat: picker支持bgColor、round、duration和overlayOpacity属性
  471 +
  472 +## 3.4.48(2025-07-10)
  473 +fix: 官方文档Card示例组件多行显示省略号样式异常
  474 +
  475 +feat: album组件支持自定义preview事件
  476 +
  477 +## 3.4.47(2025-07-09)
  478 +fix: 修复datetime-picker打开时,数值可能出现对不上的问题
  479 +
  480 +feat: Subsection 分段器添加支持从 list中读取激活文字颜色和未激活文字颜色
  481 +
  482 +fix: 修复modal定义confirmButton
  483 +
  484 +fix: safe-bottom底部安全距离在小程序优先用JS计算
  485 +
  486 +feat: 新增tree树形组件
  487 +
  488 +## 3.4.46(2025-07-08)
  489 +feat: 上传组件预览视频支持videoPreviewObjectFit参数
  490 +
  491 +feat: td增加多个样式props
  492 +
  493 +fix: 修复noticeBar字号增大时文字遮挡
  494 +
  495 +feat: 项目工程增加pinia
  496 +
  497 +## 3.4.45(2025-07-01)
  498 +fix: 修复picker-data组件缺少name
  499 +
  500 +fix: 优化picker高度单位
  501 +
  502 +## 3.4.44(2025-06-30)
  503 +fix: 修复indexList中stikcy属性写死的问题(always true)
  504 +
  505 +feat: 搜索框添加新的右侧插槽
  506 +
  507 +fix: 修复indexList中丢失的select event
  508 +
  509 +fix: 解决因为层级问题导致点击picker选择器无法正常弹出
  510 +
  511 +## 3.4.43(2025-06-16)
  512 +feat: table2支持header插槽
  513 +
  514 +## 3.4.42(2025-06-12)
  515 +fix: 修复qrcode中默认id问题及canvas2时App无法绘制二维码 感谢@jiaruiyan
  516 +
  517 +## 3.4.41(2025-06-11)
  518 +feat: qrcode支持 新参数useRootHeightAndWidth 是否使用根节点的宽高 感谢@YJR
  519 +
  520 +feat: toast支持设置zIndex层级
  521 +
  522 +
  523 +
  524 +## 3.4.40(2025-06-06)
  525 +fix: 升级二维码 canvas -> canvas2 感谢@yjr
  526 +
  527 +## 3.4.39(2025-05-31)
  528 +fix: 修改步骤条微信小程序下的布局 感谢@jiaruiyan
  529 +
  530 +fix: u-tabs在屏幕尺寸发生变化时滑块位置没有发生变化 感谢@aqzhft
  531 +
  532 +fix: 鸿蒙平台不支持plus.runtime.openWeb 感谢@aqzhft
  533 +
  534 +## 3.4.38(2025-05-30)
  535 +fix: 修复picker-data快捷组件缺少index
  536 +
  537 +fix: 修复picker组件双向绑定初始化及取消后复原再次打开后的当前项目
  538 +
  539 +## 3.4.37(2025-05-29)
  540 +feat: modal支持设置动画时间
  541 +
  542 +fix: DatetimePicker v-model 绑定异步设置无效 (#803)
  543 +
  544 +## 3.4.36(2025-05-28)
  545 +fix: lazy-load图片为空时显示错误
  546 +
  547 +## 3.4.35(2025-05-28)
  548 +feat: 进度条支持从右往左加载
  549 +
  550 +## 3.4.34(2025-05-28)
  551 +feat: table2支持自定义标题和单元格样式
  552 +
  553 +## 3.4.33(2025-05-27)
  554 +fix: 修复小程序cate-tab第一次切换时没反应 感谢@jiaruiyan
  555 +
  556 +fix: 修复datetimepicker传入空字符串时导致组件崩溃 感谢@jiaruiyan
  557 +
  558 +fix: 修复album带单位的字符串参与计算导致的计算数据错误 感谢@jiaruiyan
  559 +
  560 +## 3.4.32(2025-05-26)
  561 +feat: 增加状态栏独立颜色配置支持支付宝小程序状态栏对背景色识别的不友好的情况
  562 +
  563 +fix: 抖音二维码兼容修复
  564 +
  565 +feat: cate-tab组件增加rightTop插槽 #715
  566 +
  567 +fix: 修改 test.promise(res) 预期结果不一致
  568 +
  569 +## 3.4.31(2025-05-17)
  570 +fix: 修复parse富文本组件导致鸿蒙运行白屏
  571 +
  572 +fix: 去除演示项目中uni.$u用法便于兼容鸿蒙
  573 +
  574 +feat: modal新增popupBottom插槽适用类似关闭按钮与内容区域分离的场景
  575 +
  576 +## 3.4.30(2025-05-16)
  577 +feat: 新增pagination分页器组件
  578 +
  579 +feat: popup新增bottom插槽适用类似关闭按钮与内容区域分离的场景
  580 +
  581 +## 3.4.29(2025-05-15)
  582 +fix: 修复table2横向滚动样式
  583 +
  584 +fix: 修复table2组件宽度兼容
  585 +
  586 +fix: 修复image显示png图片时默认背景色问题
  587 +
  588 +feat: cate-tab新增height参数便于设置组件高度
  589 +
  590 +feat: 在index.js种导出digit.js便于使用
  591 +
  592 +fix: 修复tag组件缺失iconColor属性
  593 +
  594 +fix: 优化index-list的setValueForTouch方法逻辑 #708
  595 +
  596 +feat: number-box支持change事件返回变动是点击了增加还是减少按钮
  597 +
  598 +fix: 修复table2在小程序下部分情形不显示表格
  599 +
  600 +## 3.4.28(2025-05-12)
  601 +feat: 新增table表格组件
  602 +
  603 +feat: 新增element-plus风格的table2组件
  604 +
  605 +## 3.4.27(2025-05-06)
  606 +fix: 修复card组件props
  607 +
  608 +## 3.4.26(2025-05-06)
  609 +fix: 修复test工具引入
  610 +
  611 +feat: card组件支持全局设置props默认值
  612 +
  613 +fix: 修复image在加载错误情况下高度和宽度不正确问题
  614 +
  615 +fix: 修复picker-data快捷组件默认picker选中
  616 +
  617 +fix: 修复日历month子组件缺失emits定义
  618 +
  619 +## 3.4.25(2025-04-27)
  620 +fix: up-form编译在微信小程序里样式缺失 #640
  621 +
  622 +fix: number-box输入为空时自动设为最小值
  623 +
  624 +feat: picker与datetimepicke组件hasInput模式支持inputProps属性
  625 +
  626 +## 3.4.24(2025-04-25)
  627 +fix: 修复upload上传逻辑(感谢@semdy)
  628 +
  629 +## 3.4.23(2025-04-24)
  630 +chore: 补全chooseFile TS类型(感谢@semdy)
  631 +
  632 +feat: u-search组件的图标支持显示在右边(感谢@semdy)
  633 +
  634 +chore: 修正chooseFile返回的数据TS类型(感谢@semdy)
  635 +
  636 +fix: PR导致缺失name影响uplad自动上传扩展名
  637 +
  638 +
  639 +## 3.4.22(2025-04-22)
  640 +fix: 修复自动上传偶发的success被覆盖为uploading
  641 +
  642 +fix: float-button缺少key #677
  643 +
  644 +fix: upload组件完善优化(感谢@semdy)
  645 +
  646 +fix: toolbar组件confirmColor属性默认改为空,以便默认使用主题色、标题字体加粗(感谢@semdy)
  647 +
  648 +## 3.4.21(2025-04-21)
  649 +feat: subsection分段器支持双向绑定current
  650 +
  651 +feat: select组件支持maxHeight属性
  652 +
  653 +feat: datetime-picker支持inputBorder属性
  654 +
  655 +## 3.4.20(2025-04-17)
  656 +fix: 修复navbar-mini提示border不存在
  657 +
  658 +feat: status-bar支持对外暴露状态栏高度值
  659 +
  660 +feat: upload支持自定义自动上传后处理逻辑便于对接不同规范后端
  661 +
  662 +feat: 优化tag组件插槽
  663 +
  664 +
  665 +## 3.4.19(2025-04-14)
  666 +fix: 修复model组件增加contentStyle带来的语法问题
  667 +
  668 +## 3.4.18(2025-04-14)
  669 +fix: upload组件支持所有文件类型的onClickPreview事件
  670 +
  671 +## 3.4.17(2025-04-11)
  672 +feat: select组件text插槽增加scope传递currentLabel
  673 +
  674 +## 3.4.16(2025-04-10)
  675 +fix: 修复安卓新加载字体方式导致Cannot read property '$page' of undefined
  676 +
  677 +## 3.4.15(2025-04-10)
  678 +improvment: 优化移步加载数据时swiper组件displayMultipleItems报错
  679 +
  680 +feat: modal增加contentStyle属性
  681 +
  682 +fix: 修复下拉菜单收起动画缺失
  683 +
  684 +fix: 修复sticky的offset属性值为响应式数据时失效 #237
  685 +
  686 +
  687 +## 3.4.14(2025-04-09)
  688 +feat: 支持自托管内置图标及扩展自定义图标
  689 +
  690 +## 3.4.13(2025-04-08)
  691 +fix: tabs点击当前tab触发change事件
  692 +
  693 +## 3.4.12(2025-04-02)
  694 +fix: dropdown关闭后遮挡页面内容 #653
  695 +
  696 +fix u-sticky.vue Uncaught TypeError: e.querySelector is not a function at uni-app-view.umd.js
  697 +
  698 +## 3.4.11(2025-03-31)
  699 +fix: 优化upload组件预览视频的弹窗占位
  700 +
  701 +## 3.4.10(2025-03-28)
  702 +feat: select组件新增多个props属性及优化
  703 +
  704 +fix: 修复cate-tab报错index is not defined #661
  705 +
  706 +
  707 +## 3.4.9(2025-03-27)
  708 +fix: 修复upload组件split报错
  709 +
  710 +fix: 修复float-button缺少flex样式
  711 +
  712 +## 3.4.8(2025-03-27)
  713 +fix: 修复upload组件split报错
  714 +
  715 +fix: 移除mapState
  716 +
  717 +## 3.4.7(2025-03-26)
  718 +fix: 修复action-sheet-data和picker-data数据回显
  719 +
  720 +fix: 优化upload组件视频封面兼容
  721 +
  722 +## 3.4.6(2025-03-25)
  723 +feat: checkbox触发change时携带name参数
  724 +
  725 +feat: upload组件支持服务器本机和阿里云OSS自动上传功能及上传进度条
  726 +
  727 +feat: upload组件支持视频预览及oss上传时获取视频封面图
  728 +
  729 +feat: 新增up-action-sheet-data快捷组件
  730 +
  731 +feat: 新增up-picker-data快捷组件
  732 +
  733 +## 3.4.5(2025-03-24)
  734 +feat: tag组件新增textSize/height/padding/borderRadius属性
  735 +
  736 +feat: 新增genLightColor自动计算浅色方法及tag组件支持autoBgColor自动计算背景色
  737 +
  738 +## 3.4.4(2025-03-13)
  739 +feat: modal增加异步操作进行中点击取消弹出提示特性防止操作被中断
  740 +
  741 +fix: 修复toast组件show方法类型声明
  742 +
  743 +## 3.4.3(2025-03-12)
  744 +fix: 修复textarea自动增高时在输入时高度异常
  745 +
  746 +## 3.4.2(2025-03-11)
  747 +feat: step组件增加title插槽及增加辅助class便于自定义样式
  748 +
  749 +## 3.4.1(2025-03-11)
  750 +feat: 新机制确保setConfig与http在nvue等环境下生效
  751 +
  752 +## 3.3.74(2025-03-06)
  753 +fix: CateTab语法问题
  754 +
  755 +## 3.3.73(2025-03-06)
  756 +feat: CateTab新增v-model:current属性
  757 +
  758 +## 3.3.72(2025-02-28)
  759 +feat: tabs组件支持icon图标及插槽
  760 +
  761 +## 3.3.71(2025-02-27)
  762 +feat: 折叠面板collapse增加titileStyle/iconStyle/rightIconStyle属性
  763 +
  764 +feat: 折叠面板组件新增cellCustomStyle/cellCustomClass属性
  765 +
  766 +fix: select组件盒模型
  767 +
  768 +## 3.3.70(2025-02-24)
  769 +fix: 修改u-checkbox-group组件changes事件发生位置
  770 +
  771 +## 3.3.69(2025-02-19)
  772 +picker允许传递禁用颜色props
  773 +
  774 +slider组件isRange状态下增加min max插槽分开显示内容
  775 +
  776 +feat: 新增经典下拉框组件up-select
  777 +
  778 +## 3.3.68(2025-02-12)
  779 +fix: 修复weekText类型
  780 +
  781 +feat: 日历增加单选与多选指定禁止选中的日期功能
  782 +
  783 +fix: NumberBox删除数字时取值有误 #613
  784 +
  785 +## 3.3.67(2025-02-11)
  786 +feat: navbar支持返回全局拦截器配置
  787 +
  788 +feat: 表单-校验-支持无提示-得到校验结果
  789 +
  790 +feat: picker传递hasInput属性时候,可以禁用输入框点击
  791 +
  792 +## 3.3.66(2025-02-09)
  793 +feat: steps-item增加content插槽
  794 +
  795 +## 3.3.65(2025-02-05)
  796 +feat: number-box组件新增按钮圆角/按钮宽度/数据框背景色/迷你模式
  797 +## 3.3.64(2025-01-18)
  798 +feat: 日历组件支持自定义星期文案
  799 +
  800 +## 3.3.63(2025-01-13)
  801 +fix: cate-tab支持支付宝小程序
  802 +
  803 +fix: textarea 修复 placeholder-style
  804 +
  805 +fix: 修复在图片加载及加载失败时容器宽度
  806 +
  807 +fix: waterfall组件报错Maximum recursive updates
  808 +
  809 +## 3.3.62(2025-01-10)
  810 +feat: sleder滑动选择器双滑块增加外层触发值的变动功能
  811 +
  812 +fix: picker支持hasInput优化
  813 +
  814 +## 3.3.61(2024-12-31)
  815 +fix: 修复微信getSystemInfoSync接口废弃警告
  816 +
  817 +fix: 'u-status-bar' symbol missing
  818 +
  819 +## 3.3.60(2024-12-30)
  820 +feat: 日期组件支持禁用
  821 +
  822 +fix: ts定义修复 #600
  823 +
  824 +feat: Tabs组件选中时增加一个active的class #595
  825 +
  826 +## 3.3.59(2024-12-30)
  827 +fix: Property "isH5" was accessed during render
  828 +
  829 +## 3.3.58(2024-12-26)
  830 +fix: slider组件change事件传参
  831 +
  832 +## 3.3.57(2024-12-23)
  833 +fix: slider组件change事件传参
  834 +
  835 +feat: 更新u-picker组件增加当前选中class类名
  836 +
  837 +## 3.3.56(2024-12-18)
  838 +feat: 在u-alert组件中添加关闭事件
  839 +
  840 +## 3.3.55(2024-12-17)
  841 +add: swiper增加双向绑定
  842 +
  843 +## 3.3.54(2024-12-11)
  844 +add: qrcode支持props控制是否开启点击预览
  845 +
  846 +add: 新增cate-tab垂直分类组件
  847 +
  848 +## 3.3.53(2024-12-10)
  849 +fix: 修复popup居中模式点击内容区域触发关闭
  850 +
  851 +## 3.3.52(2024-12-09)
  852 +add: notice-bar支持justifyContent属性
  853 +
  854 +## 3.3.51(2024-12-09)
  855 +add: radio增加label插槽
  856 +
  857 +## 3.3.50(2024-12-05)
  858 +fix: 优化popup等对禁止背景滚动机制
  859 +
  860 +add: slider在弹窗使用示例
  861 +
  862 +fix: card组件类名问题
  863 +
  864 +## 3.3.49(2024-12-02)
  865 +fix: 去除album多余的$u引用
  866 +
  867 +fix: 优化图片组件兼容性
  868 +
  869 +add: picker组件增加zIndex属性
  870 +
  871 +add: text增加是否占满剩余空间属性
  872 +
  873 +add: input颜色示例
  874 +
  875 +## 3.3.48(2024-11-29)
  876 +add: 文本行数限制样式提高到10行
  877 +
  878 +del: 去除不跨端的inputmode
  879 +## 3.3.47(2024-11-28)
  880 +fix: 时间选择器在hasInput模式下部分机型键盘弹出
  881 +
  882 +## 3.3.46(2024-11-26)
  883 +fix: 修复text传递事件参数
  884 +
  885 +## 3.3.45(2024-11-24)
  886 +add: navbar组件支持配置标题颜色
  887 +
  888 +fix: 边框按钮警告类型下颜色变量使用错误
  889 +
  890 +## 3.3.43(2024-11-18)
  891 +fix: 支持瀑布流组件v-model置为[]
  892 +
  893 +add: 新增字符串路径访问工具方法getValueByPath
  894 +
  895 +add: 新增float-button悬浮按钮组件
  896 +
  897 +## 3.3.42(2024-11-15)
  898 +add: button组件支持stop参数阻止冒泡
  899 +
  900 +## 3.3.41(2024-11-13)
  901 +fix: u-radio-group invalid import
  902 +
  903 +improvement: 优化图片组件宽高及修复事件event传递
  904 +
  905 +## 3.3.40(2024-11-11)
  906 +add: 组件radioGroup增加gap属性用于设置item间隔
  907 +
  908 +fix: 修复H5全局导入
  909 +
  910 +## 3.3.39(2024-11-04)
  911 +fix: 修复相册组件
  912 +
  913 +## 3.3.38(2024-11-04)
  914 +fix: 修复视频预览报错 #510
  915 +
  916 +add: album组件增加stop参数支持阻止事件冒泡
  917 +
  918 +## 3.3.37(2024-10-21)
  919 +fix: 修复因为修改组件名称前缀,导致h5打包后$parent方法内找不到父组件的问题
  920 +
  921 +fix: 修复datetime-picker选择2000年以前日期出错
  922 +
  923 +## 3.3.36(2024-10-09)
  924 +fix: toast 自动关闭
  925 +
  926 +feat: 增加微信小程序用户昵称审核完毕回调及修改 ts 定义文件
  927 +
  928 +## 3.3.35(2024-10-08)
  929 +feat: modal和picker支持v-model:show双向绑定
  930 +
  931 +feat: 支持checkbox使用slot自定义label后自带点击事件 #522
  932 +
  933 +feat: swipe-action支持自动关闭特性及初始化打开状态
  934 +
  935 +## 3.3.34(2024-09-23)
  936 +feat: 支持toast设置duration值为-1时不自动关闭
  937 +
  938 +## 3.3.33(2024-09-18)
  939 +fix: 修复test.date('008')等验证结果不准确
  940 +
  941 +## 3.3.32(2024-09-09)
  942 +fix: u-keyboard名称冲突warning
  943 +
  944 +## 3.3.31(2024-08-31)
  945 +feat: qrcode初步支持nvue
  946 +
  947 +## 3.3.30(2024-08-30)
  948 +fix: slider兼容step为字符串类型
  949 +
  950 +## 3.3.29(2024-08-30)
  951 +fix: 修复tabs组件current参数为字符串处理逻辑
  952 +
  953 +## 3.3.28(2024-08-26)
  954 +fix: list组件滑动偏移量不一样取绝对值导致iOS下拉偏移量计算错误
  955 +
  956 +## 3.3.27(2024-08-22)
  957 +fix: 修复up-datetime-picker组件toolbarRightSlot定义缺失
  958 +
  959 +fix: 修复FormItem的rules更新错误的问题
  960 +
  961 +## 3.3.26(2024-08-22)
  962 +fix: 批量注册全局组件优化
  963 +
  964 +## 3.3.25(2024-08-21)
  965 +fix: 修复slider在app-vue下样式问题
  966 +
  967 +## 3.3.24(2024-08-19)
  968 +fix: 修复时间选择器hasInput模式小程序不生效
  969 +
  970 +feat: 支持H5导入所有组件
  971 +
  972 +## 3.3.23(2024-08-17)
  973 +feat: swipe-action增加closeAll方法
  974 +
  975 +fix: 兼容tabs在某些场景下index小于0时自动设置为0
  976 +
  977 +add: 通用mixin新增navTo页面跳转方法
  978 +
  979 +## 3.3.21(2024-08-15)
  980 +improvement: 优化二维码组件loading及支持预览与长按事件 #351
  981 +
  982 +fix: 修复swipe-action自动关闭其它功能及组件卸载自动关闭
  983 +
  984 +## 3.3.20(2024-08-15)
  985 +refactor: props默认值文件移至组件文件夹内便于查找
  986 +## 3.3.19(2024-08-14)
  987 +fix: 修复2被rpx兼容处理只在数字值生效
  988 +
  989 +add: 增加swiper自定义插槽示例
  990 +
  991 +## 3.3.18(2024-08-13)
  992 +feat: 新增支持datetime-picker工具栏插槽及picker插槽支持修复
  993 +## 3.3.17(2024-08-12)
  994 +feat: swiper组件增加默认slot便于自定义
  995 +
  996 +feat: grid新增间隔参数
  997 +
  998 +feat: picker新增toolbar-right和toolbar-bottom插槽
  999 +
  1000 +## 3.3.16(2024-08-12)
  1001 +fix: 解决swiper中title换行后多余的内容未被遮挡问题
  1002 +
  1003 +fix: 修复迷你导航适配异形屏
  1004 +
  1005 +## 3.3.15(2024-08-09)
  1006 +fix: 修复默认单位设置为rpx时一些组件高度间距异常
  1007 +
  1008 +fix: 修复日历在rpx单位下布局异常
  1009 +
  1010 +feat: code-input支持App端展示输入光标
  1011 +
  1012 +## 3.3.14(2024-08-09)
  1013 +add: 增加box组件
  1014 +
  1015 +add: 增加card卡片组件
  1016 +
  1017 +
  1018 +## 3.3.13(2024-08-08)
  1019 +feat: input支持调用原生组件的focus和blur方法
  1020 +
  1021 +improvement: grid-item条件编译优化
  1022 +
  1023 +add: 新增迷你导航组件
  1024 +
  1025 +## 3.3.12(2024-08-06)
  1026 +improvement: $u挂载时机调整便于打包分离chunk
  1027 +
  1028 +fix: steps新增itemStyle属性名称冲突
  1029 +
  1030 +## 3.3.11(2024-08-05)
  1031 +feat: 新增支持upload组件的deletable/maxCount/accept变更监听 #333
  1032 +
  1033 +feat: 新增支持tabs在swiper中使用
  1034 +
  1035 +feat: 新增FormItem支持独立设置验证规则rules
  1036 +
  1037 +fix: 修复index-list未设置$slots.header时索引高亮失效
  1038 +
  1039 +## 3.3.10(2024-08-02)
  1040 +fix: 修复index-list偶发的滑动最后一个索引报错top不存在
  1041 +
  1042 +fix: 修复gird在QQ、抖音小程序下布局
  1043 +
  1044 +feat: 优化step支持自定义样式prop
  1045 +
  1046 +feat: action-sheet组件支持v-model:show双向绑定
  1047 +
  1048 +fix: 小程序下steps和grid都统一采用grid布局
  1049 +
  1050 +fix: 修复支付宝小程序下input类型为数字时双向绑定失效
  1051 +
  1052 +feat : form 表单 validate 校验不通过后 error增加字段prop信息 #304
  1053 +
  1054 +fix: form组件异步校异常验问题 #393
  1055 +
  1056 +## 3.3.9(2024-08-01)
  1057 +fix: 优化获取nvue元素
  1058 +
  1059 +feat: modal新增contentTextAlign设置文案对齐方式
  1060 +
  1061 +fix: 修复NVUE下tabbar文字不显示 #458
  1062 +
  1063 +feat: loading-page增加zIndex属性
  1064 +
  1065 +fix: 相册在宽度较小时换行问题
  1066 +
  1067 +feat: album相册增加自适应自动换行模式
  1068 +
  1069 +feat: album相册增加图片尺寸单位prop
  1070 +
  1071 +fix: 修复calendar日历月份居中
  1072 +
  1073 +## 3.3.8(2024-07-31)
  1074 +feat: slider支持进度条任意位置触发按钮拖动
  1075 +
  1076 +fix: 修复app-vue下modal标题不居中
  1077 +
  1078 +fix: #459 TS setConfig 声明异常
  1079 +
  1080 +feat: tabs组件增加longPress长按事件
  1081 +
  1082 +feat: 新增showRight属性控制collapse右侧图标显隐
  1083 +
  1084 +fix: 优化nvue下css警告
  1085 +
  1086 +## 3.3.7(2024-07-29)
  1087 +feat: 支持IndexList组件支持在弹窗等场景下使用及联动优化
  1088 +
  1089 +feat: popup组件支持v-model:show双向绑定
  1090 +
  1091 +feat: 优化tabs的current双向绑定
  1092 +
  1093 +fix: checkbox独立使用时checked赋初始值可以,但是手动切换时值没有做双向绑定! #455
  1094 +
  1095 +feat: slider组件支持区间双滑块
  1096 +
  1097 +fix: toast 支持自定义图标?可传入了决对路径的 icon也没有用 #409
  1098 +
  1099 +feat: form-item校验失败时 增加class方便自定义显示错误的展示方式 #394
  1100 +
  1101 +fix: up-cell的required配置不生效 #395
  1102 +
  1103 +fix: 横向滚动组件,微信小程序编译后会有警告 #415
  1104 +
  1105 +fix: u-picker内部对默认值defaultIndex的监听 #425
  1106 +
  1107 +feat: toast 组件支持遮掩层穿透 #417
  1108 +
  1109 +fix: 兼容vue的slot编译bug #423
  1110 +
  1111 +fix: upload 微信小程序 点击预览视频报错 #424
  1112 +
  1113 +fix: u-number-box 组件修改【integer, decimalLength, min, max 】props时没有触发绑定值更新 #429
  1114 +
  1115 +feat: Tabs组件能否支持自定义插槽 #439
  1116 +
  1117 +feat: ActionSheet 可以配置最大高度吗, 我当做select使用了。 #445
  1118 +
  1119 +fix: cursor-pointer优化
  1120 +
  1121 +feat: 新版slider组件兼容NVUE改造
  1122 +
  1123 +feat: 新增slider组件手动实现以支持样式自定义
  1124 +
  1125 +perf:补充TS声明提示信息
  1126 +
  1127 +修复:ActionSheet 操作菜单cancelText属性为空DOM节点还存在并且可以点击问题
  1128 +
  1129 +fix: 去除预留的beforeDestroy兼容容易在某些sdk下不识别条件编译
  1130 +
  1131 +## 3.3.6(2024-07-23)
  1132 +feat: u-album组件添加radius,shape参数,定义参考当前u-image参数
  1133 +
  1134 +fix: 修复了calendar组件title和日期title未垂直居中的问题
  1135 +
  1136 +fix: update:modelValue缺失emit定义
  1137 +
  1138 +## 3.3.5(2024-07-10)
  1139 +picker组件支持hasInput模式
  1140 +
  1141 +## 3.3.4(2024-07-07)
  1142 +fix: input组件双向绑定问题 #419
  1143 +
  1144 +lazy-load完善emit
  1145 +
  1146 +优化通用小程序分享
  1147 +
  1148 +## 3.3.2(2024-06-27)
  1149 +fix: 在Nvue环境中编译,出现大量警告 #406
  1150 +## 3.3.1(2024-06-27)
  1151 +u-button组件报错,找不到button mixins #407
  1152 +## 3.3.0(2024-06-27)
  1153 +feat: checkbox支持label设置slot
  1154 +
  1155 +feat: modal增加customClass
  1156 +
  1157 +feat: navbar、popup、tabs、text支持customClass
  1158 +
  1159 +fix: cell组建缺少flex布局
  1160 +
  1161 +fix: 修复微信小程序真机调试时快速输入出现文本回退问题
  1162 +
  1163 +feat: tag增加默认slot
  1164 +
  1165 +公共mixin改造为按需导入语法
  1166 +
  1167 +refactor: 组件props混入mixin改造为按需导入语法
  1168 +
  1169 +fix: u-tabbar 安卓手机点击按钮变蓝问题 #396
  1170 +
  1171 +feat: upload组建增加extension属性
  1172 +
  1173 +fix: upload组件参数mode添加left
  1174 +
  1175 +fix: 修复阴影在非nvue时白色背景色不显示
  1176 +
  1177 +## 3.2.24(2024-06-11)
  1178 +fix: 修复时间选择器confirm事件触发时机导致2次才会触发v-model更新
  1179 +## 3.2.23(2024-05-30)
  1180 +fix: #378 H5 u-input 在表单中初始值为空也会触发一次 formValidate(this,"change")事件导致进入页面直接校验了一次
  1181 +
  1182 +fix: #373 搜索组件up-search的@clear事件无效
  1183 +
  1184 +fix: #372 ActionSheet 组件的取消按钮触发区域太小
  1185 +
  1186 +## 3.2.22(2024-05-13)
  1187 +上传组件支持微信小程序预览视频
  1188 +
  1189 +修复折叠面板右侧箭头不显示
  1190 +
  1191 +修复uxp2px
  1192 +
  1193 +## 3.2.21(2024-05-10)
  1194 +fix: loading-icon修复flex布局
  1195 +## 3.2.20(2024-05-10)
  1196 +修复瀑布流大小写#355
  1197 +## 3.2.19(2024-05-10)
  1198 +去除意外的文件引入
  1199 +## 3.2.18(2024-05-09)
  1200 +fix: 349 popup 组件设置 zIndex 属性后,组件渲染异常#
  1201 +feat: 搜索框增加adjustPosition属性
  1202 +fix: #331增加u-action-sheet__cancel
  1203 +优化mixin兼容性
  1204 +feat: #326 up-list增加下拉刷新功能
  1205 +fix: #319 优化up-tabs参数与定义匹配
  1206 +fix: index-list组件微信小程序端使用自定义导航栏异常
  1207 +fix: #285 pickerimmediateChange 写死为true
  1208 +fix: #111 u-scroll-list组件,隐藏指示器后报错, 提示找不到ref
  1209 +list增加微信小程序防抖配置
  1210 +
  1211 +## 3.2.17(2024-05-08)
  1212 +fix: 支付宝小程序二维码渲染
  1213 +## 3.2.16(2024-05-06)
  1214 +修复tabs中,当前激活样式的undefined bug
  1215 +
  1216 +fix: #341u-code 倒计时没结束前退出,再次进入结束后退出界面,再次进入重新开始倒计时bug
  1217 +
  1218 +受到uni-app内置text样式影响修复
  1219 +
  1220 +## 3.2.15(2024-04-28)
  1221 +优化时间选择器hasInput模式初始化值
  1222 +## 3.2.14(2024-04-24)
  1223 +去除pleaseSetTranspileDependencies
  1224 +
  1225 +http采用useStore
  1226 +
  1227 +## 3.2.13(2024-04-22)
  1228 +修复modal标题样式
  1229 +
  1230 +优化日期选择器hasInput模式宽度
  1231 +
  1232 +## 3.2.12(2024-04-22)
  1233 +修复color应用
  1234 +## 3.2.11(2024-04-18)
  1235 +修复import化带来的问题
  1236 +## 3.2.10(2024-04-17)
  1237 +完善input清空事件App端失效的兼容性
  1238 +
  1239 +修复日历组件二次打开后当前月份显示不正确
  1240 +
  1241 +## 3.2.9(2024-04-16)
  1242 +组件内uni.$u用法改为import引入
  1243 +
  1244 +规范化及兼容性增强
  1245 +
  1246 +## 3.2.8(2024-04-15)
  1247 +修复up-tag语法错
  1248 +## 3.2.7(2024-04-15)
  1249 +修复下拉菜单背景色在支付宝小程序无效
  1250 +
  1251 +setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置
  1252 +
  1253 +## 3.2.6(2024-04-14)
  1254 +修复某些情况下滑动单元格默认右侧按钮是展开的问题
  1255 +## 3.2.5(2024-04-13)
  1256 +调整分段器尺寸及修复窗口大小改变时重新计算尺寸
  1257 +
  1258 +多个组件支持cursor-pointer增强PC端体验
  1259 +
  1260 +## 3.2.4(2024-04-12)
  1261 +初步支持typescript
  1262 +## 3.2.3(2024-04-12)
  1263 +fix: 修复square属性在小程序下无效问题
  1264 +
  1265 +fix:修复lastIndex异常导致的column异常问题
  1266 +
  1267 +fix: alipayapp picker style
  1268 +
  1269 +feat(button): 添加用户同意隐私协议事件回调
  1270 +
  1271 +fix: input switch password
  1272 +
  1273 +fix: 修复u-code组件keepRuning失效问题
  1274 +
  1275 +feat: form-item添加labelPosition属性
  1276 +
  1277 +新增dropdown组件
  1278 +
  1279 +分段器支持内部current值
  1280 +
  1281 +优化cell和action-sheet视觉大小
  1282 +
  1283 +修复tabs文字换行
  1284 +
  1285 +## 3.2.2(2024-04-11)
  1286 +修复换行符问题
  1287 +## 3.2.1(2024-04-11)
  1288 +修复演示H5二维码
  1289 +
  1290 +fix: #270 ReadMore 展开阅读更多内容变化兼容
  1291 +
  1292 +fix: #238Calendar组件maxDate修改为不能小于minDate
  1293 +
  1294 +checkbox支持独立使用
  1295 +
  1296 +修复popup中在微信小程序中真机调试滚动失效
  1297 +
  1298 +## 3.2.0(2024-04-10)
  1299 +修复轮播图在nvue显示
  1300 +修复疑似u-slider名称被占用导致slider在App下不显示
  1301 +解决微信小程序提示 Some selectors are not allowed in component wxss
  1302 +示例中u-前缀统一为up-
  1303 +增加瀑布流与图片懒加载组件
  1304 +fix: #308修复tag组件缺失iconColor参数
  1305 +fix: #297使用grid布局解决目前编译为抖音小程序无法开启virtualHost
  1306 +## 3.1.52(2024-04-07)
  1307 +工具类方法调用import化改造
  1308 +新增up-copy复制组件
  1309 +## 3.1.51(2024-04-07)
  1310 +优化时间选择器自带输入框格式化显示
  1311 +防止按钮文字换行
  1312 +修复订单列表模板滑动
  1313 +增加u-qrcode二维码组件
  1314 +## 3.1.49(2024-03-27)
  1315 +日期时间组件支持自带输入框
  1316 +fix: popup弹窗滚动穿透问题
  1317 +fix: 修复小程序numberbox bug
  1318 +## 3.1.48(2024-03-18)
  1319 +fix:[plugin:uni:pre-css] Unbalanced delimiter found in string
  1320 +## 3.1.47(2024-03-18)
  1321 +fix: setConfig设置组件默认参数无效问题
  1322 +fix: 修复自定义图标无效问题
  1323 +feat: 增加u-form-item单独设置规则变量
  1324 +fix:#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况
  1325 +
  1326 +## 3.1.46(2024-01-29)
  1327 +beforeUnmount
  1328 +## 3.1.45(2024-01-24)
  1329 +fix: #262ext组件为超链接的情况下size属性不生效
  1330 +fix: #263最新版本3.1.42中微信小程序u-swipe-action-item报错
  1331 +fix: #224最新版本3.1.42中微信小程序u-swipe-action-item报错
  1332 +fix: #263支持支付宝小程序
  1333 +fix: #261u-input在直接修改v-model的绑定值时,每隔一次会无法出发change事件
  1334 +优化折叠面板兼容微信小程序
  1335 +## 3.1.42(2024-01-15)
  1336 +修复u-number-box默认值0时在小程序不显示值
  1337 +优化u-code的timer判断
  1338 +优化支付宝小程序下textarea字数统计兼容
  1339 +优化u-calendar
  1340 +## 3.1.41(2023-11-18)
  1341 +#215优化u-cell图标容器间距问题
  1342 +## 3.1.40(2023-11-16)
  1343 +修复u-slider双向绑定
  1344 +## 3.1.39(2023-11-10)
  1345 +修复头条小程序不支持env(safe-area-inset-bottom)
  1346 +优化#201u-grid 指定列数导致闪烁
  1347 +#193IndexList 索引列表 高度错误
  1348 +其他优化
  1349 +## 3.1.38(2023-10-08)
  1350 +修复u-slider
  1351 +## 3.1.37(2023-09-13)
  1352 +完善emits定义及修复code-input双向数据绑定
  1353 +## 3.1.36(2023-08-08)
  1354 +修复富文本事件名称大小写
  1355 +## 3.1.35(2023-08-02)
  1356 +修复编译到支付宝小程序u-form报错
  1357 +## 3.1.34(2023-07-27)
  1358 +修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错
  1359 +## 3.1.33(2023-07-13)
  1360 +修复弹窗进入动画、模板页面样式等
  1361 +## 3.1.31(2023-07-11)
  1362 +修复dayjs引用
  1363 +## 3.0.8(2022-07-12)
  1364 +修复u-tag默认宽度撑满容器
  1365 +## 3.0.7(2022-07-12)
  1366 +修复u-navbar自定义插槽演示示例
  1367 +## 3.0.6(2022-07-11)
  1368 +修复u-image缺少emits申明
  1369 +## 3.0.5(2022-07-11)
  1370 +修复u-upload缺少emits申明
  1371 +## 3.0.4(2022-07-10)
  1372 +修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定
  1373 +## 3.0.3(2022-07-09)
  1374 +启用自建演示二维码
  1375 +## 3.0.2(2022-07-09)
  1376 +修复dayjs/clipboard等导致打包报错
  1377 +## 3.0.1(2022-07-09)
  1378 +增加插件市场地址
  1379 +## 3.0.0(2022-07-09)
  1380 +# uview-plus(vue3)初步发布
... ...
uni_modules/uview-plus/components/u-action-sheet-data/u-action-sheet-data.vue 0 → 100644
  1 +<template>
  2 + <view class="u-action-sheet-data">
  3 + <view class="u-action-sheet-data__trigger">
  4 + <slot name="trigger"></slot>
  5 + <up-input
  6 + v-if="!$slots['trigger']"
  7 + :modelValue="current"
  8 + disabled
  9 + disabledColor="#ffffff"
  10 + :placeholder="title"
  11 + border="none"
  12 + ></up-input>
  13 + <view @click="show = true"
  14 + class="u-action-sheet-data__trigger__cover"></view>
  15 + </view>
  16 + <up-action-sheet
  17 + :show="show"
  18 + :actions="options"
  19 + :title="title"
  20 + safeAreaInsetBottom
  21 + :description="description"
  22 + @close="show = false"
  23 + @select="select"
  24 + >
  25 + </up-action-sheet>
  26 + </view>
  27 +</template>
  28 +
  29 +<script>
  30 +export default {
  31 + props: {
  32 + modelValue: {
  33 + type: [String, Number],
  34 + default: ''
  35 + },
  36 + title: {
  37 + type: String,
  38 + default: ''
  39 + },
  40 + description: {
  41 + type: String,
  42 + default: ''
  43 + },
  44 + options: {
  45 + type: Array,
  46 + default: () => {
  47 + return []
  48 + }
  49 + },
  50 + valueKey: {
  51 + type: String,
  52 + default: 'value'
  53 + },
  54 + labelKey: {
  55 + type: String,
  56 + default: 'name'
  57 + }
  58 + },
  59 + data() {
  60 + return {
  61 + show: false,
  62 + current: '',
  63 + }
  64 + },
  65 + created() {
  66 + if (this.modelValue) {
  67 + this.options.forEach((ele) => {
  68 + if (ele[this.valueKey] == this.modelValue) {
  69 + this.current = ele[this.labelKey]
  70 + }
  71 + })
  72 + }
  73 + },
  74 + emits: ['update:modelValue'],
  75 + watch: {
  76 + modelValue() {
  77 + this.options.forEach((ele) => {
  78 + if (ele[this.valueKey] == this.modelValue) {
  79 + this.current = ele[this.labelKey]
  80 + }
  81 + })
  82 + }
  83 + },
  84 + methods: {
  85 + hideKeyboard() {
  86 + uni.hideKeyboard()
  87 + },
  88 + select(e) {
  89 + this.$emit('update:modelValue', e[this.valueKey])
  90 + this.current = e[this.labelKey]
  91 + },
  92 + }
  93 +}
  94 +</script>
  95 +
  96 +<style lang="scss" scoped>
  97 + .u-action-sheet-data {
  98 + &__trigger {
  99 + position: relative;
  100 + &__cover {
  101 + position: absolute;
  102 + top: 0;
  103 + left: 0;
  104 + right: 0;
  105 + bottom: 0;
  106 + }
  107 + }
  108 + }
  109 +</style>
0 110 \ No newline at end of file
... ...
uni_modules/uview-plus/components/u-action-sheet/actionSheet.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 3.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : jry
  7 + * @lastTime : 2025-08-16 10:52:35
  8 + * @FilePath : /uview-plus/libs/config/props/actionSheet.js
  9 + */
  10 +export default {
  11 + // action-sheet组件
  12 + actionSheet: {
  13 + show: false,
  14 + title: '',
  15 + description: '',
  16 + actions: [],
  17 + index: '',
  18 + cancelText: '',
  19 + closeOnClickAction: true,
  20 + safeAreaInsetBottom: true,
  21 + openType: '',
  22 + closeOnClickOverlay: true,
  23 + round: 0,
  24 + wrapMaxHeight: '600px'
  25 + }
  26 +}
... ...
uni_modules/uview-plus/components/u-action-sheet/props.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 3.0
  5 + * @LastAuthor : jry
  6 + * @lastTime : 2025-08-16 10:52:35
  7 + * @FilePath : /uview-plus/libs/config/props/props.js
  8 + */
  9 +import { defineMixin } from '../../libs/vue'
  10 +import defProps from '../../libs/config/props.js'
  11 +
  12 +export const props = defineMixin({
  13 + props: {
  14 + // 操作菜单是否展示 (默认false)
  15 + show: {
  16 + type: Boolean,
  17 + default: () => defProps.actionSheet.show
  18 + },
  19 + // 标题
  20 + title: {
  21 + type: String,
  22 + default: () => defProps.actionSheet.title
  23 + },
  24 + // 选项上方的描述信息
  25 + description: {
  26 + type: String,
  27 + default: () => defProps.actionSheet.description
  28 + },
  29 + // 数据
  30 + actions: {
  31 + type: Array,
  32 + default: () => defProps.actionSheet.actions
  33 + },
  34 + // 取消按钮的文字,不为空时显示按钮
  35 + cancelText: {
  36 + type: String,
  37 + default: () => defProps.actionSheet.cancelText
  38 + },
  39 + // 点击某个菜单项时是否关闭弹窗
  40 + closeOnClickAction: {
  41 + type: Boolean,
  42 + default: () => defProps.actionSheet.closeOnClickAction
  43 + },
  44 + // 处理底部安全区(默认true)
  45 + safeAreaInsetBottom: {
  46 + type: Boolean,
  47 + default: () => defProps.actionSheet.safeAreaInsetBottom
  48 + },
  49 + // 小程序的打开方式
  50 + openType: {
  51 + type: String,
  52 + default: () => defProps.actionSheet.openType
  53 + },
  54 + // 点击遮罩是否允许关闭 (默认true)
  55 + closeOnClickOverlay: {
  56 + type: Boolean,
  57 + default: () => defProps.actionSheet.closeOnClickOverlay
  58 + },
  59 + // 圆角值
  60 + round: {
  61 + type: [Boolean, String, Number],
  62 + default: () => defProps.actionSheet.round
  63 + },
  64 + // 选项区域最大高度
  65 + wrapMaxHeight: {
  66 + type: [String],
  67 + default: () => defProps.actionSheet.wrapMaxHeight
  68 + },
  69 + }
  70 +})
... ...
uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue 0 → 100644
  1 +<template>
  2 + <u-popup
  3 + :show="show"
  4 + mode="bottom"
  5 + @close="closeHandler"
  6 + :safeAreaInsetBottom="safeAreaInsetBottom"
  7 + :round="round"
  8 + >
  9 + <view class="u-action-sheet">
  10 + <!-- 顶部标题区域 -->
  11 + <view
  12 + class="u-action-sheet__header"
  13 + v-if="title"
  14 + >
  15 + <text class="u-action-sheet__header__title u-line-1">{{title}}</text>
  16 + <view
  17 + class="u-action-sheet__header__icon-wrap"
  18 + @tap.stop="cancel"
  19 + >
  20 + <up-icon
  21 + name="close"
  22 + size="17"
  23 + color="#c8c9cc"
  24 + bold
  25 + ></up-icon>
  26 + </view>
  27 + </view>
  28 + <!-- 描述信息 -->
  29 + <text
  30 + class="u-action-sheet__description"
  31 + :style="[{
  32 + marginTop: `${title && description ? 0 : '18px'}`
  33 + }]"
  34 + v-if="description"
  35 + >{{description}}</text>
  36 + <slot>
  37 + <!-- 分割线 -->
  38 + <u-line v-if="description"></u-line>
  39 + <!-- 操作项列表 -->
  40 + <scroll-view scroll-y class="u-action-sheet__item-wrap" :style="{maxHeight: wrapMaxHeight}">
  41 + <view :key="index" v-for="(item, index) in actions">
  42 + <!-- #ifdef MP -->
  43 + <button
  44 + class="u-reset-button"
  45 + :openType="item.openType"
  46 + @getuserinfo="onGetUserInfo"
  47 + @contact="onContact"
  48 + @getphonenumber="onGetPhoneNumber"
  49 + @error="onError"
  50 + @launchapp="onLaunchApp"
  51 + @opensetting="onOpenSetting"
  52 + :lang="lang"
  53 + :session-from="sessionFrom"
  54 + :send-message-title="sendMessageTitle"
  55 + :send-message-path="sendMessagePath"
  56 + :send-message-img="sendMessageImg"
  57 + :show-message-card="showMessageCard"
  58 + :app-parameter="appParameter"
  59 + @tap="selectHandler(index)"
  60 + :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
  61 + >
  62 + <!-- #endif -->
  63 + <view
  64 + class="u-action-sheet__item-wrap__item"
  65 + @tap.stop="selectHandler(index)"
  66 + :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
  67 + :hover-stay-time="150"
  68 + :style="getItemHoverStyle(index)"
  69 + >
  70 + <template v-if="!item.loading">
  71 + <text
  72 + class="u-action-sheet__item-wrap__item__name"
  73 + :style="[itemStyle(index)]"
  74 + >{{ item.name }}</text>
  75 + <text
  76 + v-if="item.subname"
  77 + class="u-action-sheet__item-wrap__item__subname"
  78 + >{{ item.subname }}</text>
  79 + </template>
  80 + <!-- 加载状态图标 -->
  81 + <u-loading-icon
  82 + v-else
  83 + custom-class="van-action-sheet__loading"
  84 + size="18"
  85 + mode="circle"
  86 + />
  87 + </view>
  88 + <!-- #ifdef MP -->
  89 + </button>
  90 + <!-- #endif -->
  91 + <!-- 选项间分割线 -->
  92 + <u-line v-if="index !== actions.length - 1"></u-line>
  93 + </view>
  94 + </scroll-view>
  95 + </slot>
  96 + <!-- 取消按钮前的分割区域 -->
  97 + <u-gap
  98 + bgColor="#eaeaec"
  99 + height="6"
  100 + v-if="cancelText"
  101 + ></u-gap>
  102 + <!-- 取消按钮 -->
  103 + <view class="u-action-sheet__item-wrap__item u-action-sheet__cancel"
  104 + hover-class="u-action-sheet--hover" @tap="cancel" v-if="cancelText">
  105 + <text
  106 + @touchmove.stop.prevent
  107 + :hover-stay-time="150"
  108 + class="u-action-sheet__cancel-text"
  109 + >{{cancelText}}</text>
  110 + </view>
  111 + </view>
  112 + </u-popup>
  113 +</template>
  114 +
  115 +<script>
  116 + import { openType } from '../../libs/mixin/openType'
  117 + import { buttonMixin } from '../../libs/mixin/button'
  118 + import { props } from './props';
  119 + import { mpMixin } from '../../libs/mixin/mpMixin';
  120 + import { mixin } from '../../libs/mixin/mixin';
  121 + import { addUnit } from '../../libs/function/index';
  122 + /**
  123 + * ActionSheet 操作菜单
  124 + * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
  125 + * @tutorial https://uview-plus.jiangruyi.com/components/actionSheet.html
  126 + *
  127 + * @property {Boolean} show 操作菜单是否展示 (默认 false )
  128 + * @property {String} title 操作菜单标题
  129 + * @property {String} description 选项上方的描述信息
  130 + * @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
  131 + * @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
  132 + * @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
  133 + * @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
  134 + * @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
  135 + * @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
  136 + * @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
  137 + * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
  138 + * @property {String} sessionFrom 会话来源,openType="contact"时有效
  139 + * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
  140 + * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
  141 + * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
  142 + * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
  143 + * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
  144 + *
  145 + * @event {Function} select 点击ActionSheet列表项时触发
  146 + * @event {Function} close 点击取消按钮时触发
  147 + * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
  148 + * @event {Function} contact 客服消息回调,openType="contact"时有效
  149 + * @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
  150 + * @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
  151 + * @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
  152 + * @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
  153 + * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
  154 + */
  155 + export default {
  156 + name: "u-action-sheet",
  157 + // 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
  158 + mixins: [openType, buttonMixin, mixin, props],
  159 + data() {
  160 + return {
  161 +
  162 + }
  163 + },
  164 + computed: {
  165 + // 操作项目的样式
  166 + itemStyle() {
  167 + return (index) => {
  168 + let style = {};
  169 + if (this.actions[index].color) style.color = this.actions[index].color
  170 + if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize)
  171 + // 选项被禁用的样式
  172 + if (this.actions[index].disabled) style.color = '#c0c4cc'
  173 + return style;
  174 + }
  175 + },
  176 + },
  177 + emits: ["close", "select", "update:show"],
  178 + methods: {
  179 + // 关闭操作菜单事件处理
  180 + closeHandler() {
  181 + // 允许点击遮罩关闭时,才发出close事件
  182 + if(this.closeOnClickOverlay) {
  183 + this.$emit('update:show', false)
  184 + this.$emit('close')
  185 + }
  186 + },
  187 + // 点击取消按钮
  188 + cancel() {
  189 + this.$emit('update:show', false)
  190 + this.$emit('close')
  191 + },
  192 + // 选择操作项处理
  193 + selectHandler(index) {
  194 + const item = this.actions[index]
  195 + if (item && !item.disabled && !item.loading) {
  196 + this.$emit('select', item)
  197 + if (this.closeOnClickAction) {
  198 + this.$emit('update:show', false)
  199 + this.$emit('close')
  200 + }
  201 + }
  202 + },
  203 + // 动态处理Hover时候第一个item的圆角
  204 + getItemHoverStyle(index) {
  205 + if (index === 0 && this.round && !this.title && !this.description) {
  206 + return {
  207 + borderTopLeftRadius: `${this.round}px`,
  208 + borderTopRightRadius: `${this.round}px`,
  209 + }
  210 + }
  211 + return {}
  212 + },
  213 + }
  214 + }
  215 +</script>
  216 +
  217 +<style lang="scss" scoped>
  218 + $u-action-sheet-reset-button-width:100% !default;
  219 + $u-action-sheet-title-font-size: 16px !default;
  220 + $u-action-sheet-title-padding: 12px 30px !default;
  221 + $u-action-sheet-title-color: $u-main-color !default;
  222 + $u-action-sheet-header-icon-wrap-right:15px !default;
  223 + $u-action-sheet-header-icon-wrap-top:15px !default;
  224 + $u-action-sheet-description-font-size:13px !default;
  225 + $u-action-sheet-description-color:14px !default;
  226 + $u-action-sheet-description-margin: 18px 15px !default;
  227 + $u-action-sheet-item-wrap-item-padding:17px !default;
  228 + $u-action-sheet-item-wrap-name-font-size:16px !default;
  229 + $u-action-sheet-item-wrap-subname-font-size:13px !default;
  230 + $u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
  231 + $u-action-sheet-item-wrap-subname-margin-top:10px !default;
  232 + $u-action-sheet-cancel-text-font-size:16px !default;
  233 + $u-action-sheet-cancel-text-color:$u-content-color !default;
  234 + $u-action-sheet-cancel-text-font-size:15px !default;
  235 + $u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
  236 +
  237 + .u-reset-button {
  238 + width: $u-action-sheet-reset-button-width;
  239 + }
  240 +
  241 + .u-action-sheet {
  242 + text-align: center;
  243 + &__header {
  244 + position: relative;
  245 + padding: $u-action-sheet-title-padding;
  246 + &__title {
  247 + font-size: $u-action-sheet-title-font-size;
  248 + color: $u-action-sheet-title-color;
  249 + font-weight: bold;
  250 + text-align: center;
  251 + }
  252 +
  253 + &__icon-wrap {
  254 + position: absolute;
  255 + right: $u-action-sheet-header-icon-wrap-right;
  256 + top: $u-action-sheet-header-icon-wrap-top;
  257 + }
  258 + }
  259 +
  260 + &__description {
  261 + font-size: $u-action-sheet-description-font-size;
  262 + color: $u-tips-color;
  263 + margin: $u-action-sheet-description-margin;
  264 + text-align: center;
  265 + }
  266 +
  267 + &__item-wrap {
  268 +
  269 + &__item {
  270 + padding: $u-action-sheet-item-wrap-item-padding;
  271 + @include flex;
  272 + align-items: center;
  273 + justify-content: center;
  274 + flex-direction: column;
  275 +
  276 + &__name {
  277 + font-size: $u-action-sheet-item-wrap-name-font-size;
  278 + color: $u-main-color;
  279 + text-align: center;
  280 + }
  281 +
  282 + &__subname {
  283 + font-size: $u-action-sheet-item-wrap-subname-font-size;
  284 + color: $u-action-sheet-item-wrap-subname-color;
  285 + margin-top: $u-action-sheet-item-wrap-subname-margin-top;
  286 + text-align: center;
  287 + }
  288 + }
  289 + }
  290 +
  291 + &__cancel-text {
  292 + font-size: $u-action-sheet-cancel-text-font-size;
  293 + color: $u-action-sheet-cancel-text-color;
  294 + text-align: center;
  295 + // padding: $u-action-sheet-cancel-text-font-size;
  296 + }
  297 +
  298 + &--hover {
  299 + background-color: $u-action-sheet-cancel-text-hover-background-color;
  300 + }
  301 + }
  302 +</style>
0 303 \ No newline at end of file
... ...
uni_modules/uview-plus/components/u-agreement/u-agreement.vue 0 → 100644
  1 +<style scoped lang="scss">
  2 + .agreement-content {
  3 + width: 100%;;
  4 + display: inline-block;
  5 + flex-direction: column;
  6 + .agreement-url {
  7 + display: inline-block;
  8 + color: blue;
  9 + // #ifdef H5
  10 + cursor: pointer;
  11 + // #endif
  12 + }
  13 + }
  14 +</style>
  15 +
  16 +<template>
  17 + <view class="up-agreement">
  18 + <up-modal v-model:show="show" showCancelButton @confirm="confirm" @cancel="close" confirmText="阅读并同意">
  19 + <view class="agreement-content">
  20 + <slot>
  21 + 我们非常重视您的个人信息和隐私保护。为了更好地保障您的个人权益,在您使用我们的产品前,
  22 + 请务必审慎阅读《<text class="agreement-url" @click="urlClick('urlProtocol')">用户协议</text>》
  23 + 和《<text class="agreement-url" @click="urlClick('urlPrivacy')">隐私政策</text>》内的所有条款,
  24 + 尤其是:1.我们对您的个人信息的收集/保存/使用/对外提供/保护等规则条款,以及您的用户权利等条款;2. 约定我们的限制责任、免责
  25 + 条款;3.其他以颜色或加粗进行标识的重要条款。如您对以上协议有任何疑问,请先不要同意,您点击“同意并继续”的行为即表示您已阅读
  26 + 完毕并同意以上协议的全部内容。
  27 + </slot>
  28 + </view>
  29 + </up-modal>
  30 + </view>
  31 +</template>
  32 +
  33 +<script>
  34 + export default {
  35 + name: 'up-agreement',
  36 + props: {
  37 + urlProtocol: {
  38 + type: String,
  39 + default: '/pages/user_agreement/agreement/info?title=用户协议'
  40 + },
  41 + urlPrivacy: {
  42 + type: String,
  43 + default: '/pages/user_agreement/agreement/info?title=隐私政策'
  44 + },
  45 + },
  46 + emits: ['confirm'],
  47 + data() {
  48 + return {
  49 + show: false
  50 + }
  51 + },
  52 + methods: {
  53 + close() {
  54 + // #ifdef H5
  55 + window.opener = null;
  56 + window.close();
  57 + // #endif
  58 + // #ifdef APP-PLUS
  59 + plus.runtime.quit();
  60 + // #endif
  61 + },
  62 + confirm() {
  63 + this.show = false;
  64 + this.$emit('confirm', 1);
  65 + },
  66 + showModal() {
  67 + this.show = true;
  68 + },
  69 + urlClick(type) {
  70 + uni.navigateTo({
  71 + url: this[type]
  72 + });
  73 + }
  74 + }
  75 + }
  76 +</script>
... ...
uni_modules/uview-plus/components/u-album/album.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : jry
  7 + * @lastTime : 2025-08-16 16:32:24
  8 + * @FilePath : /uview-plus/libs/config/props/album.js
  9 + */
  10 +export default {
  11 + // album 组件
  12 + album: {
  13 + urls: [],
  14 + keyName: '',
  15 + singleSize: 180,
  16 + multipleSize: 70,
  17 + space: 6,
  18 + singleMode: 'scaleToFill',
  19 + multipleMode: 'aspectFill',
  20 + maxCount: 9,
  21 + previewFullImage: true,
  22 + rowCount: 3,
  23 + showMore: true,
  24 + autoWrap: false,
  25 + unit: 'px',
  26 + stop: true,
  27 + }
  28 +}
... ...
uni_modules/uview-plus/components/u-album/props.js 0 → 100644
  1 +/*
  2 + * @Author : jry
  3 + * @Description :
  4 + * @version : 3.0
  5 + * @LastAuthor : jry
  6 + * @lastTime : 2025-08-16 16:35:24
  7 + * @FilePath : /uview-plus/components/u-album/props.js
  8 + */
  9 +import { defineMixin } from '../../libs/vue'
  10 +import defProps from '../../libs/config/props.js'
  11 +
  12 +export const props = defineMixin({
  13 + props: {
  14 + // 图片地址,Array<String>|Array<Object>形式
  15 + urls: {
  16 + type: Array,
  17 + default: () => defProps.album.urls
  18 + },
  19 + // 指定从数组的对象元素中读取哪个属性作为图片地址
  20 + keyName: {
  21 + type: String,
  22 + default: () => defProps.album.keyName
  23 + },
  24 + // 单图时,图片长边的长度
  25 + singleSize: {
  26 + type: [String, Number],
  27 + default: () => defProps.album.singleSize
  28 + },
  29 + // 多图时,图片边长
  30 + multipleSize: {
  31 + type: [String, Number],
  32 + default: () => defProps.album.multipleSize
  33 + },
  34 + // 多图时,图片水平和垂直之间的间隔
  35 + space: {
  36 + type: [String, Number],
  37 + default: () => defProps.album.space
  38 + },
  39 + // 单图时,图片缩放裁剪的模式
  40 + singleMode: {
  41 + type: String,
  42 + default: () => defProps.album.singleMode
  43 + },
  44 + // 多图时,图片缩放裁剪的模式
  45 + multipleMode: {
  46 + type: String,
  47 + default: () => defProps.album.multipleMode
  48 + },
  49 + // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
  50 + maxCount: {
  51 + type: [String, Number],
  52 + default: () => defProps.album.maxCount
  53 + },
  54 + // 是否可以预览图片
  55 + previewFullImage: {
  56 + type: Boolean,
  57 + default: () => defProps.album.previewFullImage
  58 + },
  59 + // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
  60 + rowCount: {
  61 + type: [String, Number],
  62 + default: () => defProps.album.rowCount
  63 + },
  64 + // 超出maxCount时是否显示查看更多的提示
  65 + showMore: {
  66 + type: Boolean,
  67 + default: () => defProps.album.showMore
  68 + },
  69 + // 图片形状,circle-圆形,square-方形
  70 + shape: {
  71 + type: String,
  72 + default: () => defProps.image.shape
  73 + },
  74 + // 圆角,单位任意
  75 + radius: {
  76 + type: [String, Number],
  77 + default: () => defProps.image.radius
  78 + },
  79 + // 自适应换行
  80 + autoWrap: {
  81 + type: Boolean,
  82 + default: () => defProps.album.autoWrap
  83 + },
  84 + // 单位
  85 + unit: {
  86 + type: [String],
  87 + default: () => defProps.album.unit
  88 + },
  89 + // 阻止点击冒泡
  90 + stop: {
  91 + type: Boolean,
  92 + default: () => defProps.album.stop
  93 + }
  94 + }
  95 +})
... ...
uni_modules/uview-plus/components/u-album/u-album.vue 0 → 100644
  1 +<template>
  2 + <view class="u-album">
  3 + <!-- 相册行容器,每行显示 rowCount 个图片 -->
  4 + <view
  5 + class="u-album__row"
  6 + ref="u-album__row"
  7 + v-for="(arr, index) in showUrls"
  8 + :forComputedUse="albumWidth"
  9 + :key="index"
  10 + :style="{flexWrap: autoWrap ? 'wrap' : 'nowrap'}"
  11 + >
  12 + <!-- 图片包装容器 -->
  13 + <view
  14 + class="u-album__row__wrapper"
  15 + v-for="(item, index1) in arr"
  16 + :key="index1"
  17 + :style="[imageStyle(index + 1, index1 + 1)]"
  18 + @tap="onPreviewTap($event, getSrc(item))"
  19 + >
  20 + <!-- 图片显示 -->
  21 + <image
  22 + :src="getSrc(item)"
  23 + :mode="
  24 + urls.length === 1
  25 + ? imageHeight > 0
  26 + ? singleMode
  27 + : 'widthFix'
  28 + : multipleMode
  29 + "
  30 + :style="[
  31 + {
  32 + width: imageWidth,
  33 + height: imageHeight,
  34 + borderRadius: shape == 'circle' ? '10000px' : addUnit(radius)
  35 + }
  36 + ]"
  37 + ></image>
  38 + <!-- 超出最大显示数量时的更多提示 -->
  39 + <view
  40 + v-if="
  41 + showMore &&
  42 + urls.length > rowCount * showUrls.length &&
  43 + index === showUrls.length - 1 &&
  44 + index1 === showUrls[showUrls.length - 1].length - 1
  45 + "
  46 + class="u-album__row__wrapper__text"
  47 + :style="{
  48 + borderRadius: shape == 'circle' ? '50%' : addUnit(radius),
  49 + }"
  50 + >
  51 + <up-text
  52 + :text="`+${urls.length - maxCount}`"
  53 + color="#fff"
  54 + :size="multipleSize * 0.3"
  55 + align="center"
  56 + customStyle="justify-content: center"
  57 + ></up-text>
  58 + </view>
  59 + </view>
  60 + </view>
  61 + </view>
  62 +</template>
  63 +
  64 +<script>
  65 +import { props } from './props';
  66 +import { mpMixin } from '../../libs/mixin/mpMixin';
  67 +import { mixin } from '../../libs/mixin/mixin';
  68 +import { addUnit, sleep } from '../../libs/function/index';
  69 +import test from '../../libs/function/test';
  70 +// #ifdef APP-NVUE
  71 +// 不支持百分比单位,这里需要通过dom查询组件的宽度
  72 +const dom = uni.requireNativePlugin('dom')
  73 +// #endif
  74 +
  75 +/**
  76 + * Album 相册
  77 + * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
  78 + * @tutorial https://uview-plus.jiangruyi.com/components/album.html
  79 + *
  80 + * @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
  81 + * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
  82 + * @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
  83 + * @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
  84 + * @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
  85 + * @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
  86 + * @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
  87 + * @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
  88 + * @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
  89 + * @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
  90 + * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
  91 + * @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
  92 + * @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
  93 + * @property {Boolean} autoWrap 自适应换行模式,不受rowCount限制,图片会自动换行 (默认 false )
  94 + * @property {String} unit 图片单位 (默认 px )
  95 + * @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
  96 + * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
  97 + */
  98 +export default {
  99 + name: 'u-album',
  100 + mixins: [mpMixin, mixin, props],
  101 + data() {
  102 + return {
  103 + // 单图的宽度
  104 + singleWidth: 0,
  105 + // 单图的高度
  106 + singleHeight: 0,
  107 + // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
  108 + singlePercent: 0.6
  109 + }
  110 + },
  111 + watch: {
  112 + urls: {
  113 + immediate: true,
  114 + handler(newVal) {
  115 + // 当只有一张图片时,获取图片尺寸信息
  116 + if (newVal.length === 1) {
  117 + this.getImageRect()
  118 + }
  119 + }
  120 + }
  121 + },
  122 + computed: {
  123 + /**
  124 + * 计算图片样式
  125 + * @param {Number} index1 - 行索引
  126 + * @param {Number} index2 - 列索引
  127 + * @returns {Object} 图片样式对象
  128 + */
  129 + imageStyle() {
  130 + return (index1, index2) => {
  131 + const { space, rowCount, multipleSize, urls } = this,
  132 + rowLen = this.showUrls.length,
  133 + allLen = this.urls.length
  134 + const style = {
  135 + marginRight: addUnit(space),
  136 + marginBottom: addUnit(space)
  137 + }
  138 + // 如果为最后一行,则每个图片都无需下边框
  139 + if (index1 === rowLen && !this.autoWrap) style.marginBottom = 0
  140 + // 每行的最右边一张和总长度的最后一张无需右边框
  141 + if (!this.autoWrap) {
  142 + if (
  143 + index2 === rowCount ||
  144 + (index1 === rowLen &&
  145 + index2 === this.showUrls[index1 - 1].length)
  146 + )
  147 + style.marginRight = 0
  148 + }
  149 + return style
  150 + }
  151 + },
  152 + /**
  153 + * 将图片地址数组划分为二维数组,用于按行显示
  154 + * @returns {Array} 二维数组,每个子数组代表一行图片
  155 + */
  156 + showUrls() {
  157 + if (this.autoWrap) {
  158 + // 自动换行模式下,所有图片放在一行中显示
  159 + return [ this.urls.slice(0, this.maxCount) ];
  160 + } else {
  161 + // 固定行数模式下,按 rowCount 分割图片
  162 + const arr = []
  163 + this.urls.map((item, index) => {
  164 + // 限制最大展示数量
  165 + if (index + 1 <= this.maxCount) {
  166 + // 计算该元素为第几个素组内
  167 + const itemIndex = Math.floor(index / this.rowCount)
  168 + // 判断对应的索引是否存在
  169 + if (!arr[itemIndex]) {
  170 + arr[itemIndex] = []
  171 + }
  172 + arr[itemIndex].push(item)
  173 + }
  174 + })
  175 + return arr
  176 + }
  177 + },
  178 + /**
  179 + * 计算图片宽度
  180 + * @returns {String} 图片宽度样式值
  181 + */
  182 + imageWidth() {
  183 + return addUnit(
  184 + this.urls.length === 1 ? this.singleWidth : this.multipleSize, this.unit
  185 + )
  186 + },
  187 + /**
  188 + * 计算图片高度
  189 + * @returns {String} 图片高度样式值
  190 + */
  191 + imageHeight() {
  192 + return addUnit(
  193 + this.urls.length === 1 ? this.singleHeight : this.multipleSize, this.unit
  194 + )
  195 + },
  196 + /**
  197 + * 计算相册总宽度,用于外部组件对齐
  198 + * 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
  199 + * @returns {Number} 相册宽度
  200 + */
  201 + albumWidth() {
  202 + let width = 0
  203 + if (this.urls.length === 1) {
  204 + width = this.singleWidth
  205 + } else {
  206 + width =
  207 + this.showUrls[0].length * this.multipleSize +
  208 + this.space * (this.showUrls[0].length - 1)
  209 + }
  210 + this.$emit('albumWidth', width)
  211 + return width
  212 + }
  213 + },
  214 + emits: ['preview', 'albumWidth'],
  215 + methods: {
  216 + addUnit,
  217 + /**
  218 + * 点击图片预览
  219 + * @param {Event} e - 点击事件对象
  220 + * @param {String} url - 当前点击图片的地址
  221 + */
  222 + onPreviewTap(e, url) {
  223 + // 获取所有图片地址
  224 + const urls = this.urls.map((item) => {
  225 + return this.getSrc(item)
  226 + })
  227 + if (this.previewFullImage) {
  228 + // 使用系统默认预览图片功能
  229 + uni.previewImage({
  230 + current: url,
  231 + urls
  232 + })
  233 + // 是否阻止事件传播
  234 + this.stop && this.preventEvent(e)
  235 + } else {
  236 + // 发送自定义预览事件
  237 + this.$emit('preview', {
  238 + urls,
  239 + currentIndex: urls.indexOf(url)
  240 + })
  241 + }
  242 + },
  243 + /**
  244 + * 获取图片地址
  245 + * @param {String|Object} item - 图片项,可以是字符串或对象
  246 + * @returns {String} 图片地址
  247 + */
  248 + getSrc(item) {
  249 + return test.object(item)
  250 + ? (this.keyName && item[this.keyName]) || item.src
  251 + : item
  252 + },
  253 + /**
  254 + * 单图时,获取图片的尺寸
  255 + * 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
  256 + * 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
  257 + */
  258 + getImageRect() {
  259 + const src = this.getSrc(this.urls[0])
  260 + uni.getImageInfo({
  261 + src,
  262 + success: (res) => {
  263 + let singleSize = this.singleSize;
  264 + // 单位
  265 + let unit = '';
  266 + if (Number.isNaN(Number(this.singleSize))) {
  267 + // 大小中有字符 则记录字符
  268 + unit = this.singleSize.replace(/\d+/g, ''); // 单位
  269 + singleSize = Number(this.singleSize.replace(/\D+/g, ''), 10); // 具体值
  270 + }
  271 +
  272 + // 判断图片横向还是竖向展示方式
  273 + const isHorizotal = res.width >= res.height
  274 + this.singleWidth = isHorizotal
  275 + ? singleSize
  276 + : (res.width / res.height) * singleSize
  277 + this.singleHeight = !isHorizotal
  278 + ? singleSize
  279 + : (res.height / res.width) * this.singleWidth
  280 +
  281 + // 如果有单位统一设置单位
  282 + if(unit != null && unit !== ''){
  283 + this.singleWidth = this.singleWidth + unit
  284 + this.singleHeight = this.singleHeight + unit
  285 + }
  286 + },
  287 + fail: () => {
  288 + // 获取图片信息失败时,通过组件宽度计算
  289 + this.getComponentWidth()
  290 + }
  291 + })
  292 + },
  293 + /**
  294 + * 获取组件的宽度,用于计算单图显示尺寸
  295 + */
  296 + async getComponentWidth() {
  297 + // 延时一定时间,以获取dom尺寸
  298 + await sleep(30)
  299 + // #ifndef APP-NVUE
  300 + // H5、小程序等平台通过 $uGetRect 获取组件宽度
  301 + this.$uGetRect('.u-album__row').then((size) => {
  302 + this.singleWidth = size.width * this.singlePercent
  303 + })
  304 + // #endif
  305 +
  306 + // #ifdef APP-NVUE
  307 + // NVUE 平台通过 dom 插件获取组件宽度
  308 + // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
  309 + const ref = this.$refs['u-album__row'][0]
  310 + ref &&
  311 + dom.getComponentRect(ref, (res) => {
  312 + this.singleWidth = res.size.width * this.singlePercent
  313 + })
  314 + // #endif
  315 + }
  316 + }
  317 +}
  318 +</script>
  319 +
  320 +<style lang="scss" scoped>
  321 +.u-album {
  322 + @include flex(column);
  323 +
  324 + &__row {
  325 + @include flex(row);
  326 +
  327 + &__wrapper {
  328 + position: relative;
  329 +
  330 + &__text {
  331 + position: absolute;
  332 + top: 0;
  333 + left: 0;
  334 + right: 0;
  335 + bottom: 0;
  336 + background-color: rgba(0, 0, 0, 0.3);
  337 + @include flex(row);
  338 + justify-content: center;
  339 + align-items: center;
  340 + }
  341 + }
  342 + }
  343 +}
  344 +</style>
0 345 \ No newline at end of file
... ...
uni_modules/uview-plus/components/u-alert/alert.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 3.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : jry
  7 + * @lastTime : 2025-08-17 17:23:53
  8 + * @FilePath : /uview-plus/libs/config/props/alert.js
  9 + */
  10 +export default {
  11 + // alert警告组件
  12 + alert: {
  13 + title: '',
  14 + type: 'warning',
  15 + description: '',
  16 + closable: false,
  17 + showIcon: false,
  18 + effect: 'light',
  19 + center: false,
  20 + fontSize: 14,
  21 + transitionMode: 'fade',
  22 + duration: 0,
  23 + icon: '',
  24 + value: true
  25 + }
  26 +}
... ...
uni_modules/uview-plus/components/u-alert/props.js 0 → 100644
  1 +/*
  2 + * @Author : jry
  3 + * @Description :
  4 + * @version : 3.0
  5 + * @LastAuthor : jry
  6 + * @lastTime : 2025-08-17 17:23:53
  7 + * @FilePath : /uview-plus/libs/config/props/props.js
  8 + */
  9 +import { defineMixin } from '../../libs/vue'
  10 +import defProps from '../../libs/config/props.js'
  11 +
  12 +export const props = defineMixin({
  13 + props: {
  14 + // 显示文字
  15 + title: {
  16 + type: String,
  17 + default: () => defProps.alert.title
  18 + },
  19 + // 主题,success/warning/info/error
  20 + type: {
  21 + type: String,
  22 + default: () => defProps.alert.type
  23 + },
  24 + // 辅助性文字
  25 + description: {
  26 + type: String,
  27 + default: () => defProps.alert.description
  28 + },
  29 + // 是否可关闭
  30 + closable: {
  31 + type: Boolean,
  32 + default: () => defProps.alert.closable
  33 + },
  34 + // 是否显示图标
  35 + showIcon: {
  36 + type: Boolean,
  37 + default: () => defProps.alert.showIcon
  38 + },
  39 + // 浅或深色调,light-浅色,dark-深色
  40 + effect: {
  41 + type: String,
  42 + default: () => defProps.alert.effect
  43 + },
  44 + // 文字是否居中
  45 + center: {
  46 + type: Boolean,
  47 + default: () => defProps.alert.center
  48 + },
  49 + // 字体大小
  50 + fontSize: {
  51 + type: [String, Number],
  52 + default: () => defProps.alert.fontSize
  53 + },
  54 + // 动画类型
  55 + transitionMode: {
  56 + type: [String],
  57 + default: () => defProps.alert.transitionMode
  58 + },
  59 + // 自动定时关闭毫秒
  60 + duration: {
  61 + type: [Number],
  62 + default: () => defProps.alert.duration
  63 + },
  64 + // 自定义图标
  65 + icon: {
  66 + type: [String],
  67 + default: () => defProps.alert.icon
  68 + },
  69 + // 是否显示
  70 + modelValue: {
  71 + type: [Boolean],
  72 + default: () => defProps.alert.value
  73 + }
  74 + }
  75 +})
... ...
uni_modules/uview-plus/components/u-alert/u-alert.vue 0 → 100644
  1 +<template>
  2 + <up-transition
  3 + :mode="transitionMode"
  4 + :show="show"
  5 + >
  6 + <view
  7 + class="u-alert"
  8 + :class="[`u-alert--${type}--${effect}`]"
  9 + @tap.stop="clickHandler"
  10 + :style="[addStyle(customStyle)]"
  11 + >
  12 + <!-- 左侧图标 -->
  13 + <view
  14 + class="u-alert__icon"
  15 + v-if="showIcon"
  16 + >
  17 + <up-icon
  18 + :name="iconName"
  19 + size="18"
  20 + :color="iconColor"
  21 + ></up-icon>
  22 + </view>
  23 + <!-- 内容区域 -->
  24 + <view
  25 + class="u-alert__content"
  26 + :style="[{
  27 + paddingRight: closable ? '20px' : 0
  28 + }]"
  29 + >
  30 + <!-- 标题 -->
  31 + <text
  32 + class="u-alert__content__title"
  33 + v-if="title"
  34 + :style="[{
  35 + fontSize: addUnit(fontSize),
  36 + textAlign: center ? 'center' : 'left'
  37 + }]"
  38 + :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
  39 + >{{ title }}</text>
  40 + <!-- 描述信息 -->
  41 + <text
  42 + class="u-alert__content__desc"
  43 + v-if="description"
  44 + :style="[{
  45 + fontSize: addUnit(fontSize),
  46 + textAlign: center ? 'center' : 'left'
  47 + }]"
  48 + :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
  49 + >{{ description }}</text>
  50 + </view>
  51 + <!-- 关闭按钮 -->
  52 + <view
  53 + class="u-alert__close"
  54 + v-if="closable"
  55 + @tap.stop="closeHandler"
  56 + >
  57 + <slot name="close">
  58 + <up-icon
  59 + name="close"
  60 + :color="iconColor"
  61 + size="15"
  62 + ></up-icon>
  63 + </slot>
  64 + </view>
  65 + </view>
  66 + </up-transition>
  67 +</template>
  68 +
  69 +<script>
  70 + import { props } from './props';
  71 + import { mpMixin } from '../../libs/mixin/mpMixin';
  72 + import { mixin } from '../../libs/mixin/mixin';
  73 + import { addUnit, addStyle } from '../../libs/function/index';
  74 + /**
  75 + * Alert 警告提示
  76 + * @description 警告提示,展现需要关注的信息。
  77 + * @tutorial https://uview-plus.jiangruyi.com/components/alertTips.html
  78 + *
  79 + * @property {String} title 显示的文字
  80 + * @property {String} type 使用预设的颜色 (默认 'warning' )
  81 + * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
  82 + * @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
  83 + * @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
  84 + * @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
  85 + * @property {Boolean} center 文字是否居中 (默认 false )
  86 + * @property {String | Number} fontSize 字体大小 (默认 14 )
  87 + * @property {Object} customStyle 定义需要用到的外部样式
  88 + * @property {String} transitionMode 过渡动画模式 (默认 'fade' )
  89 + * @property {String | Number} duration 自动关闭延时(毫秒),设置为0或负数则不自动关闭 (默认 0 )
  90 + * @property {String} icon 自定义图标名称,优先级高于type默认图标
  91 + * @property {Boolean} modelValue/v-model 绑定值,控制是否显示 (默认 true )
  92 + * @event {Function} click 点击组件时触发
  93 + * @event {Function} close 点击关闭按钮时触发
  94 + * @event {Function} closed 关闭动画结束时触发
  95 + * @example <up-alert :title="title" type = "warning" :closable="closable" :description = "description"></up-alert>
  96 + */
  97 + export default {
  98 + name: 'u-alert',
  99 + mixins: [mpMixin, mixin, props],
  100 + data() {
  101 + return {
  102 + // 控制组件显示隐藏
  103 + show: true
  104 + }
  105 + },
  106 + computed: {
  107 + // 根据不同的主题类型返回对应的图标颜色
  108 + iconColor() {
  109 + return this.effect === 'light' ? this.type : '#fff'
  110 + },
  111 + // 不同主题对应不同的图标
  112 + iconName() {
  113 + // 如果用户自定义了图标,则优先使用自定义图标
  114 + if (this.icon) return this.icon;
  115 +
  116 + switch (this.type) {
  117 + case 'success':
  118 + return 'checkmark-circle-fill';
  119 + break;
  120 + case 'error':
  121 + return 'close-circle-fill';
  122 + break;
  123 + case 'warning':
  124 + return 'error-circle-fill';
  125 + break;
  126 + case 'info':
  127 + return 'info-circle-fill';
  128 + break;
  129 + case 'primary':
  130 + return 'more-circle-fill';
  131 + break;
  132 + default:
  133 + return 'error-circle-fill';
  134 + }
  135 + }
  136 + },
  137 + emits: ["click","close", "closed", "update:modelValue"],
  138 + watch: {
  139 + modelValue: {
  140 + handler(newVal) {
  141 + this.show = newVal;
  142 + },
  143 + immediate: true
  144 + },
  145 + show: {
  146 + handler(newVal) {
  147 + this.$emit('update:modelValue', newVal);
  148 +
  149 + // 如果是从显示到隐藏,且启用了自动关闭功能
  150 + if (!newVal && this.duration > 0) {
  151 + this.$emit('closed');
  152 + }
  153 + }
  154 + }
  155 + },
  156 + mounted() {
  157 + // 如果设置了自动关闭时间,则在指定时间后自动关闭
  158 + if (this.duration > 0) {
  159 + setTimeout(() => {
  160 + this.closeHandler();
  161 + }, this.duration);
  162 + }
  163 + },
  164 + methods: {
  165 + addUnit,
  166 + addStyle,
  167 + // 点击内容区域触发click事件
  168 + clickHandler() {
  169 + this.$emit('click')
  170 + },
  171 + // 点击关闭按钮触发close事件并隐藏组件
  172 + closeHandler() {
  173 + this.show = false
  174 + this.$emit('close');
  175 + }
  176 + }
  177 + }
  178 +</script>
  179 +
  180 +<style lang="scss" scoped>
  181 +
  182 + .u-alert {
  183 + position: relative;
  184 + background-color: $u-primary;
  185 + padding: 8px 10px;
  186 + @include flex(row);
  187 + align-items: center;
  188 + border-top-left-radius: 4px;
  189 + border-top-right-radius: 4px;
  190 + border-bottom-left-radius: 4px;
  191 + border-bottom-right-radius: 4px;
  192 +
  193 + &--primary--dark {
  194 + background-color: $u-primary;
  195 + }
  196 +
  197 + &--primary--light {
  198 + background-color: #ecf5ff;
  199 + }
  200 +
  201 + &--error--dark {
  202 + background-color: $u-error;
  203 + }
  204 +
  205 + &--error--light {
  206 + background-color: #FEF0F0;
  207 + }
  208 +
  209 + &--success--dark {
  210 + background-color: $u-success;
  211 + }
  212 +
  213 + &--success--light {
  214 + background-color: #f5fff0;
  215 + }
  216 +
  217 + &--warning--dark {
  218 + background-color: $u-warning;
  219 + }
  220 +
  221 + &--warning--light {
  222 + background-color: #FDF6EC;
  223 + }
  224 +
  225 + &--info--dark {
  226 + background-color: $u-info;
  227 + }
  228 +
  229 + &--info--light {
  230 + background-color: #f4f4f5;
  231 + }
  232 +
  233 + &__icon {
  234 + margin-right: 5px;
  235 + }
  236 +
  237 + &__content {
  238 + @include flex(column);
  239 + flex: 1;
  240 +
  241 + &__title {
  242 + color: $u-main-color;
  243 + font-size: 14px;
  244 + font-weight: bold;
  245 + color: #fff;
  246 + margin-bottom: 2px;
  247 + }
  248 +
  249 + &__desc {
  250 + color: $u-main-color;
  251 + font-size: 14px;
  252 + flex-wrap: wrap;
  253 + color: #fff;
  254 + }
  255 + }
  256 +
  257 + &__title--dark,
  258 + &__desc--dark {
  259 + color: #FFFFFF;
  260 + }
  261 +
  262 + &__text--primary--light,
  263 + &__text--primary--light {
  264 + color: $u-primary;
  265 + }
  266 +
  267 + &__text--success--light,
  268 + &__text--success--light {
  269 + color: $u-success;
  270 + }
  271 +
  272 + &__text--warning--light,
  273 + &__text--warning--light {
  274 + color: $u-warning;
  275 + }
  276 +
  277 + &__text--error--light,
  278 + &__text--error--light {
  279 + color: $u-error;
  280 + }
  281 +
  282 + &__text--info--light,
  283 + &__text--info--light {
  284 + color: $u-info;
  285 + }
  286 +
  287 + &__close {
  288 + position: absolute;
  289 + top: 11px;
  290 + right: 10px;
  291 + }
  292 + }
  293 +</style>
... ...
uni_modules/uview-plus/components/u-avatar-group/avatarGroup.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:49:55
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js
  9 + */
  10 +export default {
  11 + // avatarGroup 组件
  12 + avatarGroup: {
  13 + urls: [],
  14 + maxCount: 5,
  15 + shape: 'circle',
  16 + mode: 'scaleToFill',
  17 + showMore: true,
  18 + size: 40,
  19 + keyName: '',
  20 + gap: 0.5,
  21 + extraValue: 0
  22 + }
  23 +}
... ...
uni_modules/uview-plus/components/u-avatar-group/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 头像图片组
  6 + urls: {
  7 + type: Array,
  8 + default: () => defProps.avatarGroup.urls
  9 + },
  10 + // 最多展示的头像数量
  11 + maxCount: {
  12 + type: [String, Number],
  13 + default: () => defProps.avatarGroup.maxCount
  14 + },
  15 + // 头像形状
  16 + shape: {
  17 + type: String,
  18 + default: () => defProps.avatarGroup.shape
  19 + },
  20 + // 图片裁剪模式
  21 + mode: {
  22 + type: String,
  23 + default: () => defProps.avatarGroup.mode
  24 + },
  25 + // 超出maxCount时是否显示查看更多的提示
  26 + showMore: {
  27 + type: Boolean,
  28 + default: () => defProps.avatarGroup.showMore
  29 + },
  30 + // 头像大小
  31 + size: {
  32 + type: [String, Number],
  33 + default: () => defProps.avatarGroup.size
  34 + },
  35 + // 指定从数组的对象元素中读取哪个属性作为图片地址
  36 + keyName: {
  37 + type: String,
  38 + default: () => defProps.avatarGroup.keyName
  39 + },
  40 + // 头像之间的遮挡比例
  41 + gap: {
  42 + type: [String, Number],
  43 + validator(value) {
  44 + return value >= 0 && value <= 1
  45 + },
  46 + default: () => defProps.avatarGroup.gap
  47 + },
  48 + // 需额外显示的值
  49 + extraValue: {
  50 + type: [Number, String],
  51 + default: () => defProps.avatarGroup.extraValue
  52 + }
  53 + }
  54 +})
... ...
uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue 0 → 100644
  1 +<template>
  2 + <view class="u-avatar-group">
  3 + <view
  4 + class="u-avatar-group__item"
  5 + v-for="(item, index) in showUrl"
  6 + :key="index"
  7 + :style="{
  8 + marginLeft: index === 0 ? 0 : addUnit(-size * gap)
  9 + }"
  10 + >
  11 + <u-avatar
  12 + :size="size"
  13 + :shape="shape"
  14 + :mode="mode"
  15 + :src="testObject(item) ? keyName && item[keyName] || item.url : item"
  16 + ></u-avatar>
  17 + <view
  18 + class="u-avatar-group__item__show-more"
  19 + v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
  20 + @tap="clickHandler"
  21 + >
  22 + <up-text
  23 + color="#ffffff"
  24 + :size="size * 0.4"
  25 + :text="`+${extraValue || urls.length - showUrl.length}`"
  26 + align="center"
  27 + customStyle="justify-content: center"
  28 + ></up-text>
  29 + </view>
  30 + </view>
  31 + </view>
  32 +</template>
  33 +
  34 +<script>
  35 + import { props } from './props';
  36 + import { mpMixin } from '../../libs/mixin/mpMixin';
  37 + import { mixin } from '../../libs/mixin/mixin';
  38 + import { addUnit } from '../../libs/function/index';
  39 + import test from '../../libs/function/test';
  40 + /**
  41 + * AvatarGroup 头像组
  42 + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
  43 + * @tutorial https://uview-plus.jiangruyi.com/components/avatar.html
  44 + *
  45 + * @property {Array} urls 头像图片组 (默认 [] )
  46 + * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
  47 + * @property {String} shape 头像形状( 'circle' (默认) | 'square' )
  48 + * @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
  49 + * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
  50 + * @property {String | Number} size 头像大小 (默认 40 )
  51 + * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
  52 + * @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
  53 + * @property {String | Number} extraValue 需额外显示的值
  54 + * @event {Function} showMore 头像组更多点击
  55 + * @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
  56 + */
  57 + export default {
  58 + name: 'u-avatar-group',
  59 + mixins: [mpMixin, mixin, props],
  60 + data() {
  61 + return {
  62 +
  63 + }
  64 + },
  65 + computed: {
  66 + showUrl() {
  67 + return this.urls.slice(0, this.maxCount)
  68 + }
  69 + },
  70 + emits: ["showMore"],
  71 + methods: {
  72 + addUnit,
  73 + testObject: test.object,
  74 + clickHandler() {
  75 + this.$emit('showMore')
  76 + }
  77 + },
  78 + }
  79 +</script>
  80 +
  81 +<style lang="scss" scoped>
  82 +
  83 + .u-avatar-group {
  84 + @include flex;
  85 +
  86 + &__item {
  87 + margin-left: -10px;
  88 + position: relative;
  89 +
  90 + &--no-indent {
  91 + // 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
  92 + margin-left: 0;
  93 + }
  94 +
  95 + &__show-more {
  96 + position: absolute;
  97 + top: 0;
  98 + bottom: 0;
  99 + left: 0;
  100 + right: 0;
  101 + background-color: rgba(0, 0, 0, 0.3);
  102 + @include flex;
  103 + align-items: center;
  104 + justify-content: center;
  105 + border-radius: 100px;
  106 + }
  107 + }
  108 + }
  109 +</style>
... ...
uni_modules/uview-plus/components/u-avatar/avatar.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:49:22
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatar.js
  9 + */
  10 +export default {
  11 + // avatar 组件
  12 + avatar: {
  13 + src: '',
  14 + shape: 'circle',
  15 + size: 40,
  16 + mode: 'scaleToFill',
  17 + text: '',
  18 + bgColor: '#c0c4cc',
  19 + color: '#ffffff',
  20 + fontSize: 18,
  21 + icon: '',
  22 + mpAvatar: false,
  23 + randomBgColor: false,
  24 + defaultUrl: '',
  25 + colorIndex: '',
  26 + name: ''
  27 + }
  28 +}
... ...
uni_modules/uview-plus/components/u-avatar/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +import test from '../../libs/function/test';
  4 +export const props = defineMixin({
  5 + props: {
  6 + // 头像图片路径(不能为相对路径)
  7 + src: {
  8 + type: String,
  9 + default: () => defProps.avatar.src
  10 + },
  11 + // 头像形状,circle-圆形,square-方形
  12 + shape: {
  13 + type: String,
  14 + default: () => defProps.avatar.shape
  15 + },
  16 + // 头像尺寸
  17 + size: {
  18 + type: [String, Number],
  19 + default: () => defProps.avatar.size
  20 + },
  21 + // 裁剪模式
  22 + mode: {
  23 + type: String,
  24 + default: () => defProps.avatar.mode
  25 + },
  26 + // 显示的文字
  27 + text: {
  28 + type: String,
  29 + default: () => defProps.avatar.text
  30 + },
  31 + // 背景色
  32 + bgColor: {
  33 + type: String,
  34 + default: () => defProps.avatar.bgColor
  35 + },
  36 + // 文字颜色
  37 + color: {
  38 + type: String,
  39 + default: () => defProps.avatar.color
  40 + },
  41 + // 文字大小
  42 + fontSize: {
  43 + type: [String, Number],
  44 + default: () => defProps.avatar.fontSize
  45 + },
  46 + // 显示的图标
  47 + icon: {
  48 + type: String,
  49 + default: () => defProps.avatar.icon
  50 + },
  51 + // 显示小程序头像,只对百度,微信,QQ小程序有效
  52 + mpAvatar: {
  53 + type: Boolean,
  54 + default: () => defProps.avatar.mpAvatar
  55 + },
  56 + // 是否使用随机背景色
  57 + randomBgColor: {
  58 + type: Boolean,
  59 + default: () => defProps.avatar.randomBgColor
  60 + },
  61 + // 加载失败的默认头像(组件有内置默认图片)
  62 + defaultUrl: {
  63 + type: String,
  64 + default: () => defProps.avatar.defaultUrl
  65 + },
  66 + // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
  67 + colorIndex: {
  68 + type: [String, Number],
  69 + // 校验参数规则,索引在0-19之间
  70 + validator(n) {
  71 + return test.range(n, [0, 19]) || n === ''
  72 + },
  73 + default: () => defProps.avatar.colorIndex
  74 + },
  75 + // 组件标识符
  76 + name: {
  77 + type: String,
  78 + default: () => defProps.avatar.name
  79 + }
  80 + }
  81 +})
... ...
uni_modules/uview-plus/components/u-avatar/u-avatar.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-avatar"
  4 + :class="[`u-avatar--${shape}`]"
  5 + :style="[{
  6 + backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : random(0, 19)] : bgColor) : 'transparent',
  7 + width: addUnit(size),
  8 + height: addUnit(size),
  9 + }, addStyle(customStyle)]"
  10 + @tap="clickHandler"
  11 + >
  12 + <slot>
  13 + <!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
  14 + <open-data
  15 + v-if="mpAvatar && allowMp"
  16 + type="userAvatarUrl"
  17 + :style="[{
  18 + width: addUnit(size),
  19 + height: addUnit(size)
  20 + }]"
  21 + />
  22 + <!-- #endif -->
  23 + <!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
  24 + <template v-if="mpAvatar && allowMp"></template>
  25 + <!-- #endif -->
  26 + <up-icon
  27 + v-else-if="icon"
  28 + :name="icon"
  29 + :size="fontSize"
  30 + :color="color"
  31 + ></up-icon>
  32 + <up-text
  33 + v-else-if="text"
  34 + :text="text"
  35 + :size="fontSize"
  36 + :color="color"
  37 + align="center"
  38 + customStyle="justify-content: center"
  39 + ></up-text>
  40 + <image
  41 + class="u-avatar__image"
  42 + v-else
  43 + :class="[`u-avatar__image--${shape}`]"
  44 + :src="avatarUrl || defaultUrl"
  45 + :mode="mode"
  46 + @error="errorHandler"
  47 + :style="[{
  48 + width: addUnit(size),
  49 + height: addUnit(size)
  50 + }]"
  51 + ></image>
  52 + </slot>
  53 + </view>
  54 +</template>
  55 +
  56 +<script>
  57 + import { props } from './props';
  58 + import { mpMixin } from '../../libs/mixin/mpMixin';
  59 + import { mixin } from '../../libs/mixin/mixin';
  60 + import { addStyle, addUnit, random } from '../../libs/function/index';
  61 + const base64Avatar =
  62 + "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
  63 + /**
  64 + * Avatar 头像
  65 + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
  66 + * @tutorial https://uview-plus.jiangruyi.com/components/avatar.html
  67 + *
  68 + * @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
  69 + * @property {String} shape 头像形状 ( circle (默认) | square)
  70 + * @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 )
  71 + * @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' )
  72 + * @property {String} text 用文字替代图片,级别优先于src
  73 + * @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' )
  74 + * @property {String} color 文字颜色 (默认 '#ffffff' )
  75 + * @property {String | Number} fontSize 文字大小 (默认 18 )
  76 + * @property {String} icon 显示的图标
  77 + * @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false )
  78 + * @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false )
  79 + * @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
  80 + * @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
  81 + * @property {String} name 组件标识符 (默认 'level' )
  82 + * @property {Object} customStyle 定义需要用到的外部样式
  83 + *
  84 + * @event {Function} click 点击组件时触发 index: 用户传递的标识符
  85 + * @example <u-avatar :src="src" mode="square"></u-avatar>
  86 + */
  87 + export default {
  88 + name: 'u-avatar',
  89 + mixins: [mpMixin, mixin, props],
  90 + data() {
  91 + return {
  92 + // 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色
  93 + colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
  94 + '#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
  95 + '#73d1f1',
  96 + '#80a7dc'
  97 + ],
  98 + avatarUrl: this.src,
  99 + allowMp: false
  100 + }
  101 + },
  102 + watch: {
  103 + // 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值
  104 + // 而组件内部不能直接修改props的值,所以需要一个中间变量
  105 + src: {
  106 + immediate: true,
  107 + handler(newVal) {
  108 + this.avatarUrl = newVal
  109 + // 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示
  110 + if(!newVal) {
  111 + this.errorHandler()
  112 + }
  113 + }
  114 + }
  115 + },
  116 + computed: {
  117 + imageStyle() {
  118 + const style = {}
  119 + return style
  120 + }
  121 + },
  122 + created() {
  123 + this.init()
  124 + },
  125 + emits: ["click"],
  126 + methods: {
  127 + addStyle,
  128 + addUnit,
  129 + random,
  130 + init() {
  131 + // 目前只有这几个小程序平台具有open-data标签
  132 + // 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑
  133 + // 故目前自动获取小程序头像只支持这几个平台
  134 + // #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
  135 + this.allowMp = true
  136 + // #endif
  137 + },
  138 + // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
  139 + isImg() {
  140 + return this.src.indexOf('/') !== -1
  141 + },
  142 + // 图片加载时失败时触发
  143 + errorHandler() {
  144 + this.avatarUrl = this.defaultUrl || base64Avatar
  145 + },
  146 + clickHandler(e) {
  147 + this.$emit('click', this.name, e)
  148 + }
  149 + }
  150 + }
  151 +</script>
  152 +
  153 +<style lang="scss" scoped>
  154 +
  155 + .u-avatar {
  156 + @include flex;
  157 + align-items: center;
  158 + justify-content: center;
  159 +
  160 + &--circle {
  161 + border-radius: 100px;
  162 + }
  163 +
  164 + &--square {
  165 + border-radius: 4px;
  166 + }
  167 +
  168 + &__image {
  169 + &--circle {
  170 + border-radius: 100px;
  171 + overflow: hidden;
  172 + }
  173 +
  174 + &--square {
  175 + border-radius: 4px;
  176 + }
  177 + }
  178 + }
  179 +</style>
... ...
uni_modules/uview-plus/components/u-back-top/backtop.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:50:18
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/backtop.js
  9 + */
  10 +export default {
  11 + // backtop组件
  12 + backtop: {
  13 + mode: 'circle',
  14 + icon: 'arrow-upward',
  15 + text: '',
  16 + duration: 100,
  17 + scrollTop: 0,
  18 + top: 400,
  19 + bottom: 100,
  20 + right: 20,
  21 + zIndex: 9,
  22 + iconStyle: {
  23 + color: '#909399',
  24 + fontSize: '19px'
  25 + }
  26 + }
  27 +}
... ...
uni_modules/uview-plus/components/u-back-top/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 返回顶部的形状,circle-圆形,square-方形
  6 + mode: {
  7 + type: String,
  8 + default: () => defProps.backtop.mode
  9 + },
  10 + // 自定义图标
  11 + icon: {
  12 + type: String,
  13 + default: () => defProps.backtop.icon
  14 + },
  15 + // 提示文字
  16 + text: {
  17 + type: String,
  18 + default: () => defProps.backtop.text
  19 + },
  20 + // 返回顶部滚动时间
  21 + duration: {
  22 + type: [String, Number],
  23 + default: () => defProps.backtop.duration
  24 + },
  25 + // 滚动距离
  26 + scrollTop: {
  27 + type: [String, Number],
  28 + default: () => defProps.backtop.scrollTop
  29 + },
  30 + // 距离顶部多少距离显示,单位px
  31 + top: {
  32 + type: [String, Number],
  33 + default: () => defProps.backtop.top
  34 + },
  35 + // 返回顶部按钮到底部的距离,单位px
  36 + bottom: {
  37 + type: [String, Number],
  38 + default: () => defProps.backtop.bottom
  39 + },
  40 + // 返回顶部按钮到右边的距离,单位px
  41 + right: {
  42 + type: [String, Number],
  43 + default: () => defProps.backtop.right
  44 + },
  45 + // 层级
  46 + zIndex: {
  47 + type: [String, Number],
  48 + default: () => defProps.backtop.zIndex
  49 + },
  50 + // 图标的样式,对象形式
  51 + iconStyle: {
  52 + type: Object,
  53 + default: () => defProps.backtop.iconStyle
  54 + }
  55 + }
  56 +})
... ...
uni_modules/uview-plus/components/u-back-top/u-back-top.vue 0 → 100644
  1 +<template>
  2 + <u-transition
  3 + mode="fade"
  4 + :customStyle="backTopStyle"
  5 + :show="show"
  6 + >
  7 + <view
  8 + class="u-back-top"
  9 + :style="[contentStyle]"
  10 + v-if="!$slots.default && !$slots.$default"
  11 + @click="backToTop"
  12 + >
  13 + <up-icon
  14 + :name="icon"
  15 + :custom-style="iconStyle"
  16 + ></up-icon>
  17 + <text
  18 + v-if="text"
  19 + class="u-back-top__text"
  20 + >{{text}}</text>
  21 + </view>
  22 + <slot v-else />
  23 + </u-transition>
  24 +</template>
  25 +
  26 +<script>
  27 + import { props } from './props';
  28 + import { mpMixin } from '../../libs/mixin/mpMixin';
  29 + import { mixin } from '../../libs/mixin/mixin';
  30 + import { addUnit, addStyle, getPx, deepMerge, error } from '../../libs/function/index';
  31 + // #ifdef APP-NVUE
  32 + const dom = weex.requireModule('dom')
  33 + // #endif
  34 + /**
  35 + * backTop 返回顶部
  36 + * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
  37 + * @tutorial https://uview-plus.jiangruyi.com/components/backTop.html
  38 + *
  39 + * @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
  40 + * @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
  41 + * @property {String} text 提示文字
  42 + * @property {String | Number} duration 返回顶部滚动时间 (默认 100)
  43 + * @property {String | Number} scrollTop 滚动距离 (默认 0 )
  44 + * @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
  45 + * @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
  46 + * @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
  47 + * @property {String | Number} zIndex 层级 (默认 9 )
  48 + * @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
  49 + * @property {Object} customStyle 定义需要用到的外部样式
  50 + *
  51 + * @example <u-back-top :scrollTop="scrollTop"></u-back-top>
  52 + */
  53 + export default {
  54 + name: 'u-back-top',
  55 + mixins: [mpMixin, mixin, props],
  56 + computed: {
  57 + backTopStyle() {
  58 + // 动画组件样式
  59 + const style = {
  60 + bottom: addUnit(this.bottom),
  61 + right: addUnit(this.right),
  62 + width: '40px',
  63 + height: '40px',
  64 + position: 'fixed',
  65 + zIndex: 10,
  66 + }
  67 + return style
  68 + },
  69 + show() {
  70 + return getPx(this.scrollTop) > getPx(this.top)
  71 + },
  72 + contentStyle() {
  73 + const style = {}
  74 + let radius = 0
  75 + // 是否圆形
  76 + if(this.mode === 'circle') {
  77 + radius = '100px'
  78 + } else {
  79 + radius = '4px'
  80 + }
  81 + // 为了兼容安卓nvue,只能这么分开写
  82 + style.borderTopLeftRadius = radius
  83 + style.borderTopRightRadius = radius
  84 + style.borderBottomLeftRadius = radius
  85 + style.borderBottomRightRadius = radius
  86 + return deepMerge(style, addStyle(this.customStyle))
  87 + }
  88 + },
  89 + emits: ["click"],
  90 + methods: {
  91 + backToTop() {
  92 + // #ifdef APP-NVUE
  93 + if (!this.$parent.$refs['u-back-top']) {
  94 + error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
  95 + }
  96 + dom.scrollToElement(this.$parent.$refs['u-back-top'], {
  97 + offset: 0
  98 + })
  99 + // #endif
  100 +
  101 + // #ifndef APP-NVUE
  102 + uni.pageScrollTo({
  103 + scrollTop: 0,
  104 + duration: this.duration
  105 + });
  106 + // #endif
  107 + this.$emit('click')
  108 + }
  109 + }
  110 + }
  111 +</script>
  112 +
  113 +<style lang="scss" scoped>
  114 + $u-back-top-flex:1 !default;
  115 + $u-back-top-height:100% !default;
  116 + $u-back-top-background-color:#E1E1E1 !default;
  117 + $u-back-top-tips-font-size:12px !default;
  118 + .u-back-top {
  119 + @include flex;
  120 + flex-direction: column;
  121 + align-items: center;
  122 + flex:$u-back-top-flex;
  123 + height: $u-back-top-height;
  124 + justify-content: center;
  125 + background-color: $u-back-top-background-color;
  126 +
  127 + &__tips {
  128 + font-size:$u-back-top-tips-font-size;
  129 + transform: scale(0.8);
  130 + }
  131 + }
  132 +</style>
... ...
uni_modules/uview-plus/components/u-badge/badge.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-23 19:51:50
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/badge.js
  9 + */
  10 +export default {
  11 + // 徽标数组件
  12 + badge: {
  13 + isDot: false,
  14 + value: '',
  15 + show: true,
  16 + max: 999,
  17 + type: 'error',
  18 + showZero: false,
  19 + bgColor: null,
  20 + color: null,
  21 + shape: 'circle',
  22 + numberType: 'overflow',
  23 + offset: [],
  24 + inverted: false,
  25 + absolute: false
  26 + }
  27 +}
... ...
uni_modules/uview-plus/components/u-badge/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 是否显示圆点
  6 + isDot: {
  7 + type: Boolean,
  8 + default: () => defProps.badge.isDot
  9 + },
  10 + // 显示的内容
  11 + value: {
  12 + type: [Number, String],
  13 + default: () => defProps.badge.value
  14 + },
  15 + // 显示的内容
  16 + modelValue: {
  17 + type: [Number, String],
  18 + default: () => defProps.badge.modelValue
  19 + },
  20 + // 是否显示
  21 + show: {
  22 + type: Boolean,
  23 + default: () => defProps.badge.show
  24 + },
  25 + // 最大值,超过最大值会显示 '{max}+'
  26 + max: {
  27 + type: [Number, String],
  28 + default: () => defProps.badge.max
  29 + },
  30 + // 主题类型,error|warning|success|primary
  31 + type: {
  32 + type: String,
  33 + default: () => defProps.badge.type
  34 + },
  35 + // 当数值为 0 时,是否展示 Badge
  36 + showZero: {
  37 + type: Boolean,
  38 + default: () => defProps.badge.showZero
  39 + },
  40 + // 背景颜色,优先级比type高,如设置,type参数会失效
  41 + bgColor: {
  42 + type: [String, null],
  43 + default: () => defProps.badge.bgColor
  44 + },
  45 + // 字体颜色
  46 + color: {
  47 + type: [String, null],
  48 + default: () => defProps.badge.color
  49 + },
  50 + // 徽标形状,circle-四角均为圆角,horn-左下角为直角
  51 + shape: {
  52 + type: String,
  53 + default: () => defProps.badge.shape
  54 + },
  55 + // 设置数字的显示方式,overflow|ellipsis|limit
  56 + // overflow会根据max字段判断,超出显示`${max}+`
  57 + // ellipsis会根据max判断,超出显示`${max}...`
  58 + // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
  59 + numberType: {
  60 + type: String,
  61 + default: () => defProps.badge.numberType
  62 + },
  63 + // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
  64 + offset: {
  65 + type: Array,
  66 + default: () => defProps.badge.offset
  67 + },
  68 + // 是否反转背景和字体颜色
  69 + inverted: {
  70 + type: Boolean,
  71 + default: () => defProps.badge.inverted
  72 + },
  73 + // 是否绝对定位
  74 + absolute: {
  75 + type: Boolean,
  76 + default: () => defProps.badge.absolute
  77 + }
  78 + }
  79 +})
... ...
uni_modules/uview-plus/components/u-badge/u-badge.vue 0 → 100644
  1 +<template>
  2 + <text
  3 + v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
  4 + :class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
  5 + :style="[addStyle(customStyle), badgeStyle]"
  6 + class="u-badge"
  7 + >{{ isDot ? '' :showValue }}</text>
  8 +</template>
  9 +
  10 +<script>
  11 + import { props } from './props';
  12 + import { mpMixin } from '../../libs/mixin/mpMixin';
  13 + import { mixin } from '../../libs/mixin/mixin';
  14 + import { addStyle, addUnit } from '../../libs/function/index';
  15 + /**
  16 + * badge 徽标数
  17 + * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
  18 + * @tutorial https://uview-plus.jiangruyi.com/components/badge.html
  19 + *
  20 + * @property {Boolean} isDot 是否显示圆点 (默认 false )
  21 + * @property {String | Number} value 显示的内容
  22 + * @property {Boolean} show 是否显示 (默认 true )
  23 + * @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
  24 + * @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
  25 + * @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
  26 + * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
  27 + * @property {String} color 字体颜色 (默认 '#ffffff' )
  28 + * @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
  29 + * @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
  30 + * @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
  31 + * @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
  32 + * @property {Boolean} absolute 是否绝对定位(默认 false )
  33 + * @property {Object} customStyle 定义需要用到的外部样式
  34 + * @example <u-badge :type="type" :count="count"></u-badge>
  35 + */
  36 + export default {
  37 + name: 'u-badge',
  38 + mixins: [mpMixin, props, mixin],
  39 + computed: {
  40 + // 是否将badge中心与父组件右上角重合
  41 + boxStyle() {
  42 + let style = {};
  43 + return style;
  44 + },
  45 + // 整个组件的样式
  46 + badgeStyle() {
  47 + const style = {}
  48 + if(this.color) {
  49 + style.color = this.color
  50 + }
  51 + if (this.bgColor && !this.inverted) {
  52 + style.backgroundColor = this.bgColor
  53 + }
  54 + if (this.absolute) {
  55 + style.position = 'absolute'
  56 + // 如果有设置offset参数
  57 + if(this.offset.length) {
  58 + // top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
  59 + const top = this.offset[0]
  60 + const right = this.offset[1] || top
  61 + style.top = addUnit(top)
  62 + style.right = addUnit(right)
  63 + }
  64 + }
  65 + return style
  66 + },
  67 + showValue() {
  68 + switch (this.numberType) {
  69 + case "overflow":
  70 + return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
  71 + break;
  72 + case "ellipsis":
  73 + return Number(this.value) > Number(this.max) ? "..." : this.value
  74 + break;
  75 + case "limit":
  76 + return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
  77 + Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
  78 + 1e3 * 100) / 100 + "k" : this.value
  79 + break;
  80 + default:
  81 + return Number(this.value)
  82 + }
  83 + },
  84 + },
  85 + methods: {
  86 + addStyle
  87 + }
  88 + }
  89 +</script>
  90 +
  91 +<style lang="scss" scoped>
  92 +
  93 + $u-badge-primary: $u-primary !default;
  94 + $u-badge-error: $u-error !default;
  95 + $u-badge-success: $u-success !default;
  96 + $u-badge-info: $u-info !default;
  97 + $u-badge-warning: $u-warning !default;
  98 + $u-badge-dot-radius: 100px !default;
  99 + $u-badge-dot-size: 8px !default;
  100 + $u-badge-dot-right: 4px !default;
  101 + $u-badge-dot-top: 0 !default;
  102 + $u-badge-text-font-size: 11px !default;
  103 + $u-badge-text-right: 10px !default;
  104 + $u-badge-text-padding: 2px 5px !default;
  105 + $u-badge-text-align: center !default;
  106 + $u-badge-text-color: #FFFFFF !default;
  107 +
  108 + .u-badge {
  109 + border-top-right-radius: $u-badge-dot-radius;
  110 + border-top-left-radius: $u-badge-dot-radius;
  111 + border-bottom-left-radius: $u-badge-dot-radius;
  112 + border-bottom-right-radius: $u-badge-dot-radius;
  113 + @include flex;
  114 + line-height: $u-badge-text-font-size;
  115 + text-align: $u-badge-text-align;
  116 + font-size: $u-badge-text-font-size;
  117 + color: $u-badge-text-color;
  118 +
  119 + &--dot {
  120 + height: $u-badge-dot-size;
  121 + width: $u-badge-dot-size;
  122 + }
  123 +
  124 + &--inverted {
  125 + font-size: 13px;
  126 + }
  127 +
  128 + &--not-dot {
  129 + padding: $u-badge-text-padding;
  130 + }
  131 +
  132 + &--horn {
  133 + border-bottom-left-radius: 0;
  134 + }
  135 +
  136 + &--primary {
  137 + background-color: $u-badge-primary;
  138 + }
  139 +
  140 + &--primary--inverted {
  141 + color: $u-badge-primary;
  142 + }
  143 +
  144 + &--error {
  145 + background-color: $u-badge-error;
  146 + }
  147 +
  148 + &--error--inverted {
  149 + color: $u-badge-error;
  150 + }
  151 +
  152 + &--success {
  153 + background-color: $u-badge-success;
  154 + }
  155 +
  156 + &--success--inverted {
  157 + color: $u-badge-success;
  158 + }
  159 +
  160 + &--info {
  161 + background-color: $u-badge-info;
  162 + }
  163 +
  164 + &--info--inverted {
  165 + color: $u-badge-info;
  166 + }
  167 +
  168 + &--warning {
  169 + background-color: $u-badge-warning;
  170 + }
  171 +
  172 + &--warning--inverted {
  173 + color: $u-badge-warning;
  174 + }
  175 + }
  176 +</style>
... ...
uni_modules/uview-plus/components/u-barcode/u-barcode.vue 0 → 100644
  1 +<template>
  2 + <view class="u-barcode" v-if="calcSizeDone">
  3 + <canvas
  4 + v-if="showCanvas && !error"
  5 + :id="canvasId"
  6 + :canvas-id="canvasId"
  7 + :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
  8 + ></canvas>
  9 + <image
  10 + v-else-if="!showCanvas && !error"
  11 + :src="barcodeImage"
  12 + :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
  13 + mode="aspectFit"
  14 + />
  15 + <view
  16 + v-else
  17 + class="error-container"
  18 + :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
  19 + >
  20 + <text class="error-text">{{ error }}</text>
  21 + </view>
  22 + </view>
  23 +</template>
  24 +
  25 +<script>
  26 +import { nextTick } from 'vue'
  27 +import { t } from '../../libs/i18n'
  28 +
  29 +export default {
  30 + name: 'u-barcode',
  31 + props: {
  32 + // 条码值
  33 + value: {
  34 + type: [String, Number],
  35 + required: true
  36 + },
  37 + // 条码格式
  38 + format: {
  39 + type: String,
  40 + default: 'auto',
  41 + validator: function (value) {
  42 + return [
  43 + 'auto',
  44 + 'CODE128', 'CODE128A', 'CODE128B', 'CODE128C',
  45 + 'EAN13', 'EAN8', 'EAN5', 'EAN2',
  46 + 'UPC', 'UPCA', 'UPCE',
  47 + 'CODE39',
  48 + 'ITF', 'ITF14',
  49 + 'MSI', 'MSI10', 'MSI11', 'MSI1010', 'MSI1110',
  50 + 'pharmacode',
  51 + 'codabar'
  52 + ].includes(value)
  53 + }
  54 + },
  55 + // 宽度
  56 + width: {
  57 + type: Number,
  58 + default: 200
  59 + },
  60 + // 高度
  61 + height: {
  62 + type: Number,
  63 + default: 80
  64 + },
  65 + // 是否显示文本
  66 + displayValue: {
  67 + type: Boolean,
  68 + default: true
  69 + },
  70 + // 文本内容
  71 + text: {
  72 + type: String,
  73 + default: undefined
  74 + },
  75 + // 字体选项
  76 + fontOptions: {
  77 + type: String,
  78 + default: ''
  79 + },
  80 + // 字体
  81 + font: {
  82 + type: String,
  83 + default: 'monospace'
  84 + },
  85 + // 文本对齐方式
  86 + textAlign: {
  87 + type: String,
  88 + default: 'center'
  89 + },
  90 + // 文本位置
  91 + textPosition: {
  92 + type: String,
  93 + default: 'bottom'
  94 + },
  95 + // 文本边距
  96 + textMargin: {
  97 + type: Number,
  98 + default: 2
  99 + },
  100 + // 字体大小
  101 + fontSize: {
  102 + type: Number,
  103 + default: 14
  104 + },
  105 + // 背景色
  106 + background: {
  107 + type: String,
  108 + default: '#ffffff'
  109 + },
  110 + // 条码颜色
  111 + lineColor: {
  112 + type: String,
  113 + default: '#000000'
  114 + },
  115 + // 边距
  116 + margin: {
  117 + type: Number,
  118 + default: 10
  119 + },
  120 + // 上边距
  121 + marginTop: {
  122 + type: Number,
  123 + default: undefined
  124 + },
  125 + // 下边距
  126 + marginBottom: {
  127 + type: Number,
  128 + default: undefined
  129 + },
  130 + // 左边距
  131 + marginLeft: {
  132 + type: Number,
  133 + default: undefined
  134 + },
  135 + // 右边距
  136 + marginRight: {
  137 + type: Number,
  138 + default: undefined
  139 + },
  140 + // 使用canvas还是生成图片
  141 + useCanvas: {
  142 + type: Boolean,
  143 + default: true
  144 + }
  145 + },
  146 + data() {
  147 + return {
  148 + canvasId: 'barcode-' + Math.random().toString(36).substr(2, 9),
  149 + barcodeImage: '',
  150 + showCanvas: false,
  151 + canvasWidth: 0,
  152 + canvasHeight: 0,
  153 + calcSizeDone: false,
  154 + error: ''
  155 + }
  156 + },
  157 + watch: {
  158 + value() {
  159 + this.generateBarcode()
  160 + },
  161 + format() {
  162 + this.generateBarcode()
  163 + },
  164 + width() {
  165 + this.generateBarcode()
  166 + },
  167 + height() {
  168 + this.generateBarcode()
  169 + },
  170 + displayValue() {
  171 + this.generateBarcode()
  172 + },
  173 + text() {
  174 + this.generateBarcode()
  175 + },
  176 + font() {
  177 + this.generateBarcode()
  178 + },
  179 + textAlign() {
  180 + this.generateBarcode()
  181 + },
  182 + textPosition() {
  183 + this.generateBarcode()
  184 + },
  185 + textMargin() {
  186 + this.generateBarcode()
  187 + },
  188 + fontSize() {
  189 + this.generateBarcode()
  190 + },
  191 + background() {
  192 + this.generateBarcode()
  193 + },
  194 + lineColor() {
  195 + this.generateBarcode()
  196 + },
  197 + margin() {
  198 + this.generateBarcode()
  199 + }
  200 + },
  201 + mounted() {
  202 + /**
  203 + * @author jry <ijry@qq.com>
  204 + */
  205 + this.$nextTick(() => {
  206 + this.generateBarcode()
  207 + })
  208 + },
  209 + methods: {
  210 + /**
  211 + * 生成条形码
  212 + * @author jry <ijry@qq.com>
  213 + * @param {String|Number} value - 条码值
  214 + * @param {Object} options - 条码配置选项
  215 + */
  216 + generateBarcode() {
  217 + // 统一处理默认值
  218 + const margin = this.margin
  219 + const options = {
  220 + format: this.format || 'auto',
  221 + width: this.width,
  222 + height: this.height,
  223 + displayValue: this.displayValue,
  224 + text: this.text,
  225 + fontOptions: this.fontOptions || '',
  226 + font: this.font || 'monospace',
  227 + textAlign: this.textAlign || 'center',
  228 + textPosition: this.textPosition || 'bottom',
  229 + textMargin: this.textMargin !== undefined ? this.textMargin : 2,
  230 + fontSize: this.fontSize || 20,
  231 + background: this.background || '#ffffff',
  232 + lineColor: this.lineColor || '#000000',
  233 + margin: margin,
  234 + marginTop: this.marginTop !== undefined ? this.marginTop : margin,
  235 + marginBottom: this.marginBottom !== undefined ? this.marginBottom : margin,
  236 + marginLeft: this.marginLeft !== undefined ? this.marginLeft : margin,
  237 + marginRight: this.marginRight !== undefined ? this.marginRight : margin
  238 + }
  239 +
  240 + // 清理未定义的选项
  241 + Object.keys(options).forEach(key => {
  242 + if (options[key] === undefined) {
  243 + delete options[key]
  244 + }
  245 + })
  246 +
  247 + if (this.useCanvas) {
  248 + // 使用canvas渲染
  249 + this.showCanvas = true
  250 + this.$nextTick(() => {
  251 + this.renderToCanvas(options)
  252 + })
  253 + } else {
  254 + // 生成图片
  255 + this.showCanvas = false
  256 + this.renderToImage(options)
  257 + }
  258 + },
  259 +
  260 + /**
  261 + * 渲染条形码到canvas
  262 + * @author jry <ijry@qq.com>
  263 + * @param {Object} options - 条码配置选项
  264 + */
  265 + async renderToCanvas(options) {
  266 + try {
  267 + // 计算canvas尺寸
  268 + this.calculateCanvasSize(options)
  269 +
  270 + await nextTick()
  271 +
  272 + // 获取canvas上下文
  273 + const ctx = uni.createCanvasContext(this.canvasId, this)
  274 +
  275 + // 清空画布
  276 + ctx.setFillStyle(options.background)
  277 + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
  278 +
  279 + // 生成条形码数据
  280 + const barcodeData = this.encodeBarcode(this.value, options.format)
  281 +
  282 + if (barcodeData) {
  283 + // 绘制条形码
  284 + this.drawBarcode(ctx, barcodeData, options)
  285 + }
  286 +
  287 + // 绘制到canvas
  288 + ctx.draw(false, () => {
  289 + // 绘制完成回调
  290 + this.$emit('rendered', { type: 'canvas', id: this.canvasId })
  291 + })
  292 + } catch (error) {
  293 + console.error('生成条码失败:', error)
  294 + this.error = error.message || t('up.barcode.error')
  295 + this.$emit('error', error)
  296 + }
  297 + },
  298 +
  299 + /**
  300 + * 渲染条形码为图片
  301 + * @author jry <ijry@qq.com>
  302 + * @param {Object} options - 条码配置选项
  303 + */
  304 + renderToImage(options) {
  305 + try {
  306 + // 计算canvas尺寸
  307 + this.calculateCanvasSize(options)
  308 +
  309 + // 创建临时canvas用于生成图片
  310 + const tempCanvasId = 'temp-' + this.canvasId
  311 + const ctx = uni.createCanvasContext(tempCanvasId, this)
  312 +
  313 + // 清空画布
  314 + ctx.setFillStyle(options.background)
  315 + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
  316 +
  317 + // 生成条形码数据
  318 + const barcodeData = this.encodeBarcode(this.value, options.format)
  319 +
  320 + if (barcodeData) {
  321 + // 绘制条形码
  322 + this.drawBarcode(ctx, barcodeData, options)
  323 + }
  324 +
  325 + // 绘制到临时canvas并生成图片
  326 + ctx.draw(false, () => {
  327 + // 延迟一小段时间确保canvas绘制完成
  328 + setTimeout(() => {
  329 + // 将canvas转换为图片
  330 + uni.canvasToTempFilePath({
  331 + canvasId: tempCanvasId,
  332 + success: (res) => {
  333 + this.barcodeImage = res.tempFilePath
  334 + this.$emit('rendered', { type: 'image', value: this.value, path: res.tempFilePath })
  335 + },
  336 + fail: (error) => {
  337 + console.error('生成条码图片失败:', error)
  338 + this.$emit('error', error)
  339 + }
  340 + }, this)
  341 + }, 100)
  342 + })
  343 + } catch (error) {
  344 + console.error('生成条码图片失败:', error)
  345 + this.$emit('error', error)
  346 + }
  347 + },
  348 +
  349 + /**
  350 + * 计算canvas尺寸
  351 + * @author jry <ijry@qq.com>
  352 + * @param {Object} options - 条码配置选项
  353 + */
  354 + calculateCanvasSize(options) {
  355 + // 基础宽度计算
  356 + let width = options.width
  357 + let height = options.height
  358 +
  359 + // 考虑边距
  360 + const marginLeft = options.marginLeft
  361 + const marginRight = options.marginRight
  362 + const marginTop = options.marginTop
  363 + const marginBottom = options.marginBottom
  364 +
  365 + // 考虑文本高度
  366 + let textHeight = 0
  367 + if (options.displayValue !== false) {
  368 + textHeight = options.fontSize + options.textMargin
  369 + }
  370 +
  371 + // 根据文本位置调整高度
  372 + if (options.textPosition === 'top' || options.textPosition === 'bottom') {
  373 + height += textHeight
  374 + }
  375 +
  376 + // 添加边距
  377 + width += marginLeft + marginRight
  378 + height += marginTop + marginBottom
  379 +
  380 + this.canvasWidth = Math.max(width, 100)
  381 + this.canvasHeight = Math.max(height, 60 + textHeight)
  382 +
  383 + this.calcSizeDone = true
  384 + },
  385 +
  386 + /**
  387 + * 编码条形码数据
  388 + * @author jry <ijry@qq.com>
  389 + * @param {String|Number} value - 条码值
  390 + * @param {String} format - 条码格式
  391 + * @returns {String|null} 条形码编码数据
  392 + */
  393 + encodeBarcode(value, format) {
  394 + try {
  395 + switch (format) {
  396 + case 'CODE128':
  397 + case 'auto':
  398 + return this.encodeCode128(value)
  399 + case 'CODE39':
  400 + return this.encodeCode39(value)
  401 + case 'EAN13':
  402 + return this.encodeEAN13(value)
  403 + case 'EAN8':
  404 + return this.encodeEAN8(value)
  405 + case 'EAN5':
  406 + case 'EAN2':
  407 + return this.encodeEAN52(value, format)
  408 + case 'UPC':
  409 + case 'UPCA':
  410 + return this.encodeUPCA(value)
  411 + case 'UPCE':
  412 + return this.encodeUPCE(value)
  413 + default:
  414 + // 默认使用CODE128
  415 + return this.encodeCode128(value)
  416 + }
  417 + } catch (error) {
  418 + console.error('条码编码失败:', error)
  419 + throw error
  420 + }
  421 + },
  422 +
  423 + /**
  424 + * 添加右侧安静区(至少2个模块宽度的空白)
  425 + * @author jry <ijry@qq.com>
  426 + * @param {String} data - 要编码的数据
  427 + * @returns {String|null} 编码后的条形码数据
  428 + */
  429 + encodeCode128(data) {
  430 + const CODE128_START_CODE_B = 104
  431 + const CODE128_STOP = 106
  432 +
  433 + // CODE128 Code B 字符集
  434 + const CODE128_CODE_B_CHARS = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
  435 +
  436 + // 条形码模式 (B 模式)
  437 + const codes = []
  438 + let checksum = CODE128_START_CODE_B
  439 +
  440 + // 添加起始字符
  441 + codes.push(CODE128_START_CODE_B)
  442 +
  443 + // 编码每个字符
  444 + for (let i = 0; i < data.length; i++) {
  445 + const char = data[i]
  446 + const code = CODE128_CODE_B_CHARS.indexOf(char)
  447 +
  448 + if (code === -1) {
  449 + throw new Error('Invalid character in CODE128: ' + char)
  450 + }
  451 +
  452 + codes.push(code)
  453 + checksum += code * (i + 1)
  454 + }
  455 +
  456 + // 添加校验字符
  457 + codes.push(checksum % 103)
  458 +
  459 + // 添加结束字符
  460 + codes.push(CODE128_STOP)
  461 +
  462 + // 转换为条形码模式 (1 = 黑条, 0 = 白条)
  463 + let barcode = ''
  464 + for (let i = 0; i < codes.length; i++) {
  465 + const code = codes[i]
  466 + barcode += this.getCode128Pattern(code)
  467 + }
  468 +
  469 + // 添加右侧安静区(至少2个模块宽度的空白)
  470 + barcode += '00000'
  471 +
  472 + return barcode
  473 + },
  474 +
  475 + /**
  476 + * 获取CODE128编码模式
  477 + * @author jry <ijry@qq.com>
  478 + * @param {Number} code - 字符编码
  479 + * @returns {String} 条形码二进制模式
  480 + */
  481 + getCode128Pattern(code) {
  482 + // CODE128编码表
  483 + const patterns = [
  484 + "11011001100", "11001101100", "11001100110", "10010011000",
  485 + "10010001100", "10001001100", "10011001000", "10011000100",
  486 + "10001100100", "11001001000", "11001000100", "11000100100",
  487 + "10110011100", "10011011100", "10011001110", "10111001100",
  488 + "10011101100", "10011100110", "11001110010", "11001011100",
  489 + "11001001110", "11011100100", "11001110100", "11101101110",
  490 + "11101001100", "11100101100", "11100100110", "11101100100",
  491 + "11100110100", "11100110010", "11011011000", "11011000110",
  492 + "11000110110", "10100011000", "10001011000", "10001000110",
  493 + "10110001000", "10001101000", "10001100010", "11010001000",
  494 + "11000101000", "11000100010", "10110111000", "10110001110",
  495 + "10001101110", "10111011000", "10111000110", "10001110110",
  496 + "11101110110", "11010001110", "11000101110", "11011101000",
  497 + "11011100010", "11011101110", "11101011000", "11101000110",
  498 + "11100010110", "11101101000", "11101100010", "11100011010",
  499 + "11101111010", "11001000010", "11110001010", "10100110000",
  500 + "10100001100", "10010110000", "10010000110", "10000101100",
  501 + "10000100110", "10110010000", "10110000100", "10011010000",
  502 + "10011000010", "10000110100", "10000110010", "11000010010",
  503 + "11001010000", "11110111010", "11000010100", "10001111010",
  504 + "10100111100", "10010111100", "10010011110", "10111100100",
  505 + "10011110100", "10011110010", "11110100100", "11110010100",
  506 + "11110010010", "11011011110", "11011110110", "11110110110",
  507 + "10101111000", "10100011110", "10001011110", "10111101000",
  508 + "10111100010", "11110101000", "11110100010", "10111011110",
  509 + "10111101110", "11101011110", "11110101110", "11010000100",
  510 + "11010010000", "11010011100", "11000111010"
  511 + ];
  512 +
  513 + return patterns[code] || "";
  514 + },
  515 +
  516 + /**
  517 + * CODE39编码实现
  518 + * @author jry <ijry@qq.com>
  519 + * @param {String} data - 要编码的数据
  520 + * @returns {String|null} 编码后的条形码数据
  521 + */
  522 + encodeCode39(data) {
  523 + const codes = {
  524 + '0': '101000111011101',
  525 + '1': '111010001010111',
  526 + '2': '101110001010111',
  527 + '3': '111011100010101',
  528 + '4': '101000111010111',
  529 + '5': '111010001110101',
  530 + '6': '101110001110101',
  531 + '7': '101000101110111',
  532 + '8': '111010001011101',
  533 + '9': '101110001011101',
  534 + 'A': '111010100010111',
  535 + 'B': '101110100010111',
  536 + 'C': '111011101000101',
  537 + 'D': '101011100010111',
  538 + 'E': '111010111000101',
  539 + 'F': '101110111000101',
  540 + 'G': '101010001110111',
  541 + 'H': '111010100011101',
  542 + 'I': '101110100011101',
  543 + 'J': '101011100011101',
  544 + 'K': '111010101000111',
  545 + 'L': '101110101000111',
  546 + 'M': '111011101010001',
  547 + 'N': '101011101000111',
  548 + 'O': '111010111010001',
  549 + 'P': '101110111010001',
  550 + 'Q': '101010111000111',
  551 + 'R': '111010101110001',
  552 + 'S': '101110101110001',
  553 + 'T': '101011101110001',
  554 + 'U': '111000101010111',
  555 + 'V': '100011101010111',
  556 + 'W': '111000111010101',
  557 + 'X': '100010111010111',
  558 + 'Y': '111000101110101',
  559 + 'Z': '100011101110101',
  560 + '-': '100010101110111',
  561 + '.': '111000101011101',
  562 + ' ': '100011101011101',
  563 + '*': '100010111011101', // 起始和终止字符
  564 + '$': '100010001000101',
  565 + '/': '100010001010001',
  566 + '+': '100010100010001',
  567 + '%': '101000100010001'
  568 + }
  569 +
  570 + // 转为大写
  571 + data = data.toUpperCase()
  572 +
  573 + // 添加起始和终止字符
  574 + let barcode = codes['*']
  575 +
  576 + // 添加数据字符
  577 + for (let i = 0; i < data.length; i++) {
  578 + const char = data[i]
  579 + if (codes[char]) {
  580 + barcode += '0' // 字符间隔
  581 + barcode += codes[char]
  582 + } else {
  583 + throw new Error('Invalid character in CODE39: ' + char)
  584 + }
  585 + }
  586 +
  587 + // 添加终止字符
  588 + barcode += '0' // 字符间隔
  589 + barcode += codes['*']
  590 +
  591 + return barcode
  592 + },
  593 +
  594 + /**
  595 + * EAN13编码实现
  596 + * @author jry <ijry@qq.com>
  597 + * @param {String} data - 13位数字字符串
  598 + * @returns {String|null} 编码后的条形码数据
  599 + */
  600 + encodeEAN13(data) {
  601 + // 确保数据是13位数字
  602 + if (!/^\d{13}$/.test(data)) {
  603 + throw new Error('EAN13 must be 13 digits')
  604 + }
  605 +
  606 + // 验证校验位
  607 + let sum = 0
  608 + for (let i = 0; i < 12; i++) {
  609 + const digit = parseInt(data[i])
  610 + sum += (i % 2 === 0) ? digit : digit * 3
  611 + }
  612 + const checkDigit = (10 - (sum % 10)) % 10
  613 + if (parseInt(data[12]) !== checkDigit) {
  614 + throw new Error('Invalid EAN13 check digit')
  615 + }
  616 +
  617 + // 左侧数据
  618 + const leftData = data.substring(1, 7)
  619 + const rightData = data.substring(7, 13)
  620 +
  621 + // 起始符
  622 + let barcode = '101'
  623 +
  624 + // 左侧数据编码 (根据第一位数字决定编码方式)
  625 + const firstDigit = parseInt(data[0])
  626 + const leftPatterns = [
  627 + ['LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG',
  628 + 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL']
  629 + ]
  630 +
  631 + const pattern = leftPatterns[0][firstDigit]
  632 +
  633 + // 左侧奇偶编码模式
  634 + const leftOdd = [
  635 + '0001101', '0011001', '0010011', '0111101', '0100011',
  636 + '0110001', '0101111', '0111011', '0110111', '0001011'
  637 + ]
  638 +
  639 + const leftEven = [
  640 + '0100111', '0110011', '0011011', '0100001', '0011101',
  641 + '0111001', '0000101', '0010001', '0001001', '0010111'
  642 + ]
  643 +
  644 + // 编码左侧数据
  645 + for (let i = 0; i < leftData.length; i++) {
  646 + const digit = parseInt(leftData[i])
  647 + if (pattern[i] === 'L') {
  648 + barcode += leftOdd[digit]
  649 + } else {
  650 + barcode += leftEven[digit]
  651 + }
  652 + }
  653 +
  654 + // 中间分隔符
  655 + barcode += '01010'
  656 +
  657 + // 右侧数据编码 (始终使用右编码)
  658 + const rightCodes = [
  659 + '1110010', '1100110', '1101100', '1000010', '1011100',
  660 + '1001110', '1010000', '1000100', '1001000', '1110100'
  661 + ]
  662 +
  663 + for (let i = 0; i < rightData.length; i++) {
  664 + const digit = parseInt(rightData[i])
  665 + barcode += rightCodes[digit]
  666 + }
  667 +
  668 + // 结束符
  669 + barcode += '101'
  670 +
  671 + return barcode
  672 + },
  673 +
  674 + /**
  675 + * EAN8编码实现
  676 + * @author jry <ijry@qq.com>
  677 + * @param {String} data - 8位数字字符串
  678 + * @returns {String|null} 编码后的条形码数据
  679 + */
  680 + encodeEAN8(data) {
  681 + // 确保数据是8位数字
  682 + if (!/^\d{8}$/.test(data)) {
  683 + throw new Error('EAN8 must be 8 digits')
  684 + }
  685 +
  686 + // 验证校验位
  687 + let sum = 0
  688 + for (let i = 0; i < 7; i++) {
  689 + const digit = parseInt(data[i])
  690 + sum += digit * (i % 2 === 0 ? 3 : 1)
  691 + }
  692 + const checkDigit = (10 - (sum % 10)) % 10
  693 + if (parseInt(data[7]) !== checkDigit) {
  694 + throw new Error('Invalid EAN8 check digit')
  695 + }
  696 +
  697 + // 左侧数据(4位)
  698 + const leftData = data.substring(0, 4)
  699 + // 右侧数据(4位)
  700 + const rightData = data.substring(4, 8)
  701 +
  702 + // 起始符
  703 + let barcode = '101'
  704 +
  705 + // 左侧奇偶编码模式
  706 + const leftOdd = [
  707 + '0001101', '0011001', '0010011', '0111101', '0100011',
  708 + '0110001', '0101111', '0111011', '0110111', '0001011'
  709 + ]
  710 +
  711 + // 编码左侧数据
  712 + for (let i = 0; i < leftData.length; i++) {
  713 + const digit = parseInt(leftData[i])
  714 + barcode += leftOdd[digit]
  715 + }
  716 +
  717 + // 中间分隔符
  718 + barcode += '01010'
  719 +
  720 + // 右侧数据编码 (始终使用右编码)
  721 + const rightCodes = [
  722 + '1110010', '1100110', '1101100', '1000010', '1011100',
  723 + '1001110', '1010000', '1000100', '1001000', '1110100'
  724 + ]
  725 +
  726 + // 编码右侧数据
  727 + for (let i = 0; i < rightData.length; i++) {
  728 + const digit = parseInt(rightData[i])
  729 + barcode += rightCodes[digit]
  730 + }
  731 +
  732 + // 结束符
  733 + barcode += '101'
  734 +
  735 + return barcode
  736 + },
  737 +
  738 + /**
  739 + * EAN5/EAN2编码实现
  740 + * @author jry <ijry@qq.com>
  741 + * @param {String} data - 2位或5位数字字符串
  742 + * @param {String} format - 格式类型(EAN5或EAN2)
  743 + * @returns {String|null} 编码后的条形码数据
  744 + */
  745 + encodeEAN52(data, format) {
  746 + const length = format === 'EAN5' ? 5 : 2
  747 + // 确保数据是相应位数的数字
  748 + if (!new RegExp(`^\\d{${length}}$`).test(data)) {
  749 + throw new Error(`${format} must be ${length} digits`)
  750 + }
  751 +
  752 + // EAN5/2编码表
  753 + const codes = [
  754 + '0001101', '0011001', '0010011', '0111101', '0100011',
  755 + '0110001', '0101111', '0111011', '0110111', '0001011'
  756 + ]
  757 +
  758 + // 计算校验和
  759 + let checksum = 0
  760 + for (let i = 0; i < data.length; i++) {
  761 + checksum += parseInt(data[i]) * (i % 2 === 0 ? 3 : 1)
  762 + }
  763 +
  764 + // 根据校验和确定编码模式
  765 + const patterns = format === 'EAN5' ?
  766 + ['00001', '00010', '00100', '01000', '10000', // 0-4
  767 + '00000', '00011', '00101', '00110', '01001', // 5-9
  768 + '01010', '01100', '10001', '10010', '10100', // 10-14
  769 + '11000', '11001', '11010', '11100'] : // 15-18
  770 + ['00', '01', '10', '11'] // EAN2只有4种模式
  771 +
  772 + const pattern = format === 'EAN5' ?
  773 + patterns[checksum % 10] :
  774 + patterns[parseInt(data) % 4]
  775 +
  776 + // 起始符
  777 + let barcode = '1011'
  778 +
  779 + // 编码数据
  780 + for (let i = 0; i < data.length; i++) {
  781 + // 添加分隔符(除了第一个字符)
  782 + if (i > 0) {
  783 + barcode += '01' // 字符间分隔符
  784 + }
  785 +
  786 + // 根据模式确定编码方式
  787 + const digit = parseInt(data[i])
  788 + const code = codes[digit]
  789 +
  790 + barcode += code
  791 + }
  792 +
  793 + return barcode
  794 + },
  795 +
  796 + /**
  797 + * UPCA编码实现
  798 + * @author jry <ijry@qq.com>
  799 + * @param {String} data - 11位或12位数字字符串
  800 + * @returns {String|null} 编码后的条形码数据
  801 + */
  802 + encodeUPCA(data) {
  803 + // 如果是11位,计算校验位
  804 + if (/^\d{11}$/.test(data)) {
  805 + let sum = 0
  806 + for (let i = 0; i < 11; i++) {
  807 + const digit = parseInt(data[i])
  808 + sum += (i % 2 === 0) ? digit * 3 : digit
  809 + }
  810 + const checkDigit = (10 - (sum % 10)) % 10
  811 + data += checkDigit
  812 + }
  813 +
  814 + // 确保数据是12位数字
  815 + if (!/^\d{12}$/.test(data)) {
  816 + throw new Error('UPC-A must be 11 or 12 digits')
  817 + }
  818 +
  819 + // UPCA实际上是EAN13的第一个数字为0的特殊情况
  820 + return this.encodeEAN13('0' + data)
  821 + },
  822 +
  823 + /**
  824 + * UPCE编码实现
  825 + * @author jry <ijry@qq.com>
  826 + * @param {String} data - 6位或8位数字字符串
  827 + * @returns {String|null} 编码后的条形码数据
  828 + */
  829 + encodeUPCE(data) {
  830 + // 如果是7位,计算校验位
  831 + if (/^\d{7}$/.test(data)) {
  832 + let sum = 0
  833 + for (let i = 0; i < 7; i++) {
  834 + const digit = parseInt(data[i])
  835 + sum += (i % 2 === 0) ? digit * 3 : digit
  836 + }
  837 + const checkDigit = (10 - (sum % 10)) % 10
  838 + data += checkDigit
  839 + }
  840 +
  841 + // 确保数据是8位数字
  842 + if (!/^\d{8}$/.test(data)) {
  843 + throw new Error('UPC-E must be 7 or 8 digits')
  844 + }
  845 +
  846 + // 检查是否是有效的UPC-E格式
  847 + if (data[0] !== '0' && data[0] !== '1') {
  848 + throw new Error('UPC-E must start with 0 or 1')
  849 + }
  850 +
  851 + // UPCE编码表
  852 + const leftOdd = [
  853 + '0001101', '0011001', '0010011', '0111101', '0100011',
  854 + '0110001', '0101111', '0111011', '0110111', '0001011'
  855 + ]
  856 +
  857 + const leftEven = [
  858 + '0100111', '0110011', '0011011', '0100001', '0011101',
  859 + '0111001', '0000101', '0010001', '0001001', '0010111'
  860 + ]
  861 +
  862 + // 根据系统数字确定编码模式
  863 + const systemDigit = data[0]
  864 + const checkDigit = data[7]
  865 +
  866 + // 提取中间6位
  867 + const middleData = data.substring(1, 7)
  868 +
  869 + // 确定编码模式
  870 + let pattern
  871 + if (checkDigit === '0' || checkDigit === '1' || checkDigit === '2') {
  872 + pattern = 'EEEEOO' // 0,1,2
  873 + } else if (checkDigit === '3') {
  874 + pattern = 'EEEEOO' // 3
  875 + } else if (checkDigit === '4') {
  876 + pattern = 'EEEOOO' // 4
  877 + } else {
  878 + pattern = 'EEOOOO' // 5,6,7,8,9
  879 + }
  880 +
  881 + // 起始符
  882 + let barcode = '101'
  883 +
  884 + // 编码中间6位数据
  885 + for (let i = 0; i < middleData.length; i++) {
  886 + const digit = parseInt(middleData[i])
  887 + if (pattern[i] === 'E') {
  888 + barcode += leftEven[digit]
  889 + } else {
  890 + barcode += leftOdd[digit]
  891 + }
  892 + }
  893 +
  894 + // 中间分隔符
  895 + barcode += '010101'
  896 +
  897 + // 结束符
  898 + barcode += '101'
  899 +
  900 + return barcode
  901 + },
  902 +
  903 + /**
  904 + * 绘制条形码
  905 + * @author jry <ijry@qq.com>
  906 + * @param {Object} ctx - canvas上下文
  907 + * @param {String} barcodeData - 条形码数据
  908 + * @param {Object} options - 条码配置选项
  909 + */
  910 + drawBarcode(ctx, barcodeData, options) {
  911 + if (!barcodeData) return
  912 +
  913 + const marginLeft = options.marginLeft
  914 + const marginTop = options.marginTop
  915 + const marginBottom = options.marginBottom
  916 + const textHeight = options.displayValue !== false ? options.fontSize + options.textMargin : 0
  917 + const height = options.height
  918 + // 恢复: 根据总长度计算模块宽度
  919 + const moduleWidth = Math.max(1, (this.canvasWidth - marginLeft - (options.marginRight || 10)) / barcodeData.length)
  920 +
  921 + ctx.setFillStyle(options.lineColor)
  922 +
  923 + // 计算条形码绘制的Y坐标
  924 + let barcodeY = marginTop
  925 + // 如果文本在顶部,需要调整条形码绘制位置
  926 + if (options.displayValue !== false && options.textPosition === 'top') {
  927 + barcodeY += textHeight
  928 + }
  929 +
  930 + // 恢复: 使用计算出的模块宽度绘制条形码
  931 + let x = marginLeft
  932 + for (let i = 0; i < barcodeData.length; i++) {
  933 + if (barcodeData[i] === '1') {
  934 + // 使用计算的模块宽度绘制每个条
  935 + ctx.fillRect(x, barcodeY, moduleWidth, height)
  936 + }
  937 + // 每个条/空都占用一个模块宽度
  938 + x += moduleWidth
  939 + }
  940 +
  941 + // 绘制文本
  942 + if (options.displayValue !== false) {
  943 + const text = options.text || this.value
  944 + let textY
  945 +
  946 + ctx.setFillStyle(options.lineColor)
  947 + ctx.setFontSize(options.fontSize)
  948 + ctx.setTextAlign(options.textAlign)
  949 +
  950 + let textX
  951 + switch (options.textAlign) {
  952 + case 'left':
  953 + textX = marginLeft
  954 + break
  955 + case 'right':
  956 + textX = this.canvasWidth - options.marginRight
  957 + break
  958 + default: // center
  959 + textX = this.canvasWidth / 2
  960 + }
  961 +
  962 + // 根据文本位置确定Y坐标
  963 + if (options.textPosition === 'top') {
  964 + textY = marginTop + options.fontSize - 3
  965 + } else { // bottom
  966 + // 修复:正确计算底部文本位置,确保文本完全显示
  967 + textY = barcodeY + height + options.textMargin + options.fontSize
  968 + // 确保文本不会超出画布边界
  969 + if (textY > this.canvasHeight - marginBottom) {
  970 + textY = this.canvasHeight - marginBottom - 2
  971 + }
  972 + }
  973 +
  974 + ctx.fillText(text, textX, textY)
  975 + }
  976 + }
  977 + }
  978 +}
  979 +</script>
  980 +
  981 +<style scoped>
  982 +.u-barcode {
  983 + display: flex;
  984 + flex-direction: row;
  985 + justify-content: center;
  986 + align-items: center;
  987 +}
  988 +
  989 +.error-container {
  990 + display: flex;
  991 + justify-content: center;
  992 + align-items: center;
  993 + background-color: #f0f0f0;
  994 + color: #ff0000;
  995 +}
  996 +
  997 +.error-text {
  998 + font-size: 14px;
  999 +}
  1000 +</style>
... ...
uni_modules/uview-plus/components/u-box/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +
  4 +export const propsBox = defineMixin({
  5 + props: {
  6 + // 背景色
  7 + bgColors: {
  8 + type: [Array],
  9 + default: ['#EEFCFF', '#FCF8FF', '#FDF8F2']
  10 + },
  11 + // 高度
  12 + height: {
  13 + type: [String],
  14 + default: "160px"
  15 + },
  16 + // 圆角
  17 + borderRadius: {
  18 + type: [String],
  19 + default: "6px"
  20 + },
  21 + // 间隔
  22 + gap: {
  23 + type: [String],
  24 + default: "15px"
  25 + },
  26 + }
  27 +})
... ...
uni_modules/uview-plus/components/u-box/u-box.vue 0 → 100644
  1 +<template>
  2 + <view class="u-box" :style="[{height: height}, addStyle(customStyle)]">
  3 + <view class="u-box__left" :style="{borderRadius: borderRadius, backgroundColor: bgColors[0]}">
  4 + <slot name="left">左</slot>
  5 + </view>
  6 + <view class="u-box__gap" :style="{width: gap, height: height}"></view>
  7 + <view class="u-box__right">
  8 + <view class="u-box__right-top" :style="{borderRadius: borderRadius, backgroundColor: bgColors[1]}">
  9 + <slot name="rightTop">右上</slot>
  10 + </view>
  11 + <view class="u-box__right-gap" :style="{height: gap}"></view>
  12 + <view class="u-box__right-bottom" :style="{borderRadius: borderRadius, backgroundColor: bgColors[2]}">
  13 + <slot name="rightBottom">右下</slot>
  14 + </view>
  15 + </view>
  16 + </view>
  17 +</template>
  18 +
  19 +<script>
  20 + import { propsBox } from './props';
  21 + import { mpMixin } from '../../libs/mixin/mpMixin';
  22 + import { mixin } from '../../libs/mixin/mixin';
  23 + import { addStyle } from '../../libs/function/index';
  24 + import test from '../../libs/function/test';
  25 + /**
  26 + * box 盒子
  27 + * @description box盒子一般为左边一个盒子,右侧两个等高的半盒组成,常用于App首页座位重点突出。
  28 + * @tutorial https://uview-plus.jiangruyi.com/components/box.html
  29 + * @property {Array} bgColors 背景色
  30 + * @property {String} height 高度
  31 + * @property {String} borderRadius 圆角
  32 + * @property {Object} customStyle 定义需要用到的外部样式
  33 + *
  34 + * @event {Function} click 点击cell列表时触发
  35 + * @example <up-box colors=['blue', 'red', 'yellow'] height="200px"></up-box>
  36 + */
  37 + export default {
  38 + name: 'up-box',
  39 + data() {
  40 + return {
  41 + }
  42 + },
  43 + mixins: [mpMixin, mixin, propsBox],
  44 + computed: {
  45 + },
  46 + emits: [],
  47 + methods: {
  48 + addStyle,
  49 + }
  50 + }
  51 +</script>
  52 +
  53 +<style lang="scss" scoped>
  54 +
  55 + .u-box {
  56 + /* #ifndef APP-NVUE */
  57 + /* #endif */
  58 + @include flex();
  59 + flex: 1;
  60 +
  61 + &__left {
  62 + @include flex();
  63 + justify-content: center;
  64 + align-items: center;
  65 + flex: 1;
  66 + }
  67 + &__gap {
  68 + @include flex();
  69 + flex-direction: column;
  70 + }
  71 + &__right {
  72 + @include flex();
  73 + flex-direction: column;
  74 + flex: 1;
  75 + }
  76 +
  77 + &__right-top {
  78 + @include flex();
  79 + flex: 1;
  80 + justify-content: center;
  81 + align-items: center;
  82 + }
  83 +
  84 + &__right-bottom {
  85 + @include flex();
  86 + flex: 1;
  87 + justify-content: center;
  88 + align-items: center;
  89 + }
  90 + }
  91 +</style>
... ...
uni_modules/uview-plus/components/u-button/button.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:51:27
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/button.js
  9 + */
  10 +export default {
  11 + // button组件
  12 + button: {
  13 + hairline: false,
  14 + type: 'info',
  15 + size: 'normal',
  16 + shape: 'square',
  17 + plain: false,
  18 + disabled: false,
  19 + loading: false,
  20 + loadingText: '',
  21 + loadingMode: 'spinner',
  22 + loadingSize: 15,
  23 + openType: '',
  24 + formType: '',
  25 + appParameter: '',
  26 + hoverStopPropagation: true,
  27 + lang: 'en',
  28 + sessionFrom: '',
  29 + sendMessageTitle: '',
  30 + sendMessagePath: '',
  31 + sendMessageImg: '',
  32 + showMessageCard: false,
  33 + dataName: '',
  34 + throttleTime: 0,
  35 + hoverStartTime: 0,
  36 + hoverStayTime: 200,
  37 + text: '',
  38 + icon: '',
  39 + iconColor: '',
  40 + color: '',
  41 + stop: true,
  42 + }
  43 +}
... ...
uni_modules/uview-plus/components/u-button/nvue.scss 0 → 100644
  1 +$u-button-active-opacity:0.75 !default;
  2 +$u-button-loading-text-margin-left:4px !default;
  3 +$u-button-text-color: #FFFFFF !default;
  4 +$u-button-text-plain-error-color:$u-error !default;
  5 +$u-button-text-plain-warning-color:$u-warning !default;
  6 +$u-button-text-plain-success-color:$u-success !default;
  7 +$u-button-text-plain-info-color:$u-info !default;
  8 +$u-button-text-plain-primary-color:$u-primary !default;
  9 +.u-button {
  10 + &--active {
  11 + opacity: $u-button-active-opacity;
  12 + }
  13 +
  14 + &--active--plain {
  15 + background-color: rgb(217, 217, 217);
  16 + }
  17 +
  18 + &__loading-text {
  19 + margin-left:$u-button-loading-text-margin-left;
  20 + }
  21 +
  22 + &__text,
  23 + &__loading-text {
  24 + color:$u-button-text-color;
  25 + }
  26 +
  27 + &__text--plain--error {
  28 + color:$u-button-text-plain-error-color;
  29 + }
  30 +
  31 + &__text--plain--warning {
  32 + color:$u-button-text-plain-warning-color;
  33 + }
  34 +
  35 + &__text--plain--success{
  36 + color:$u-button-text-plain-success-color;
  37 + }
  38 +
  39 + &__text--plain--info {
  40 + color:$u-button-text-plain-info-color;
  41 + }
  42 +
  43 + &__text--plain--primary {
  44 + color:$u-button-text-plain-primary-color;
  45 + }
  46 +}
0 47 \ No newline at end of file
... ...
uni_modules/uview-plus/components/u-button/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 是否细边框
  6 + hairline: {
  7 + type: Boolean,
  8 + default: () => defProps.button.hairline
  9 + },
  10 + // 按钮的预置样式,info,primary,error,warning,success
  11 + type: {
  12 + type: String,
  13 + default: () => defProps.button.type
  14 + },
  15 + // 按钮尺寸,large,normal,small,mini
  16 + size: {
  17 + type: String,
  18 + default: () => defProps.button.size
  19 + },
  20 + // 按钮形状,circle(两边为半圆),square(带圆角)
  21 + shape: {
  22 + type: String,
  23 + default: () => defProps.button.shape
  24 + },
  25 + // 按钮是否镂空
  26 + plain: {
  27 + type: Boolean,
  28 + default: () => defProps.button.plain
  29 + },
  30 + // 是否禁止状态
  31 + disabled: {
  32 + type: Boolean,
  33 + default: () => defProps.button.disabled
  34 + },
  35 + // 是否加载中
  36 + loading: {
  37 + type: Boolean,
  38 + default: () => defProps.button.loading
  39 + },
  40 + // 加载中提示文字
  41 + loadingText: {
  42 + type: [String, Number],
  43 + default: () => defProps.button.loadingText
  44 + },
  45 + // 加载状态图标类型
  46 + loadingMode: {
  47 + type: String,
  48 + default: () => defProps.button.loadingMode
  49 + },
  50 + // 加载图标大小
  51 + loadingSize: {
  52 + type: [String, Number],
  53 + default: () => defProps.button.loadingSize
  54 + },
  55 + // 开放能力,具体请看uniapp稳定关于button组件部分说明
  56 + // https://uniapp.dcloud.io/component/button
  57 + openType: {
  58 + type: String,
  59 + default: () => defProps.button.openType
  60 + },
  61 + // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
  62 + // 取值为submit(提交表单),reset(重置表单)
  63 + formType: {
  64 + type: String,
  65 + default: () => defProps.button.formType
  66 + },
  67 + // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
  68 + // 只微信小程序、QQ小程序有效
  69 + appParameter: {
  70 + type: String,
  71 + default: () => defProps.button.appParameter
  72 + },
  73 + // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
  74 + hoverStopPropagation: {
  75 + type: Boolean,
  76 + default: () => defProps.button.hoverStopPropagation
  77 + },
  78 + // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
  79 + lang: {
  80 + type: String,
  81 + default: () => defProps.button.lang
  82 + },
  83 + // 会话来源,open-type="contact"时有效。只微信小程序有效
  84 + sessionFrom: {
  85 + type: String,
  86 + default: () => defProps.button.sessionFrom
  87 + },
  88 + // 会话内消息卡片标题,open-type="contact"时有效
  89 + // 默认当前标题,只微信小程序有效
  90 + sendMessageTitle: {
  91 + type: String,
  92 + default: () => defProps.button.sendMessageTitle
  93 + },
  94 + // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
  95 + // 默认当前分享路径,只微信小程序有效
  96 + sendMessagePath: {
  97 + type: String,
  98 + default: () => defProps.button.sendMessagePath
  99 + },
  100 + // 会话内消息卡片图片,open-type="contact"时有效
  101 + // 默认当前页面截图,只微信小程序有效
  102 + sendMessageImg: {
  103 + type: String,
  104 + default: () => defProps.button.sendMessageImg
  105 + },
  106 + // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
  107 + // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
  108 + showMessageCard: {
  109 + type: Boolean,
  110 + default: () => defProps.button.showMessageCard
  111 + },
  112 + // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
  113 + dataName: {
  114 + type: String,
  115 + default: () => defProps.button.dataName
  116 + },
  117 + // 节流,一定时间内只能触发一次
  118 + throttleTime: {
  119 + type: [String, Number],
  120 + default: () => defProps.button.throttleTime
  121 + },
  122 + // 按住后多久出现点击态,单位毫秒
  123 + hoverStartTime: {
  124 + type: [String, Number],
  125 + default: () => defProps.button.hoverStartTime
  126 + },
  127 + // 手指松开后点击态保留时间,单位毫秒
  128 + hoverStayTime: {
  129 + type: [String, Number],
  130 + default: () => defProps.button.hoverStayTime
  131 + },
  132 + // 按钮文字,之所以通过props传入,是因为slot传入的话
  133 + // nvue中无法控制文字的样式
  134 + text: {
  135 + type: [String, Number],
  136 + default: () => defProps.button.text
  137 + },
  138 + // 按钮图标
  139 + icon: {
  140 + type: String,
  141 + default: () => defProps.button.icon
  142 + },
  143 + // 按钮图标
  144 + iconColor: {
  145 + type: String,
  146 + default: () => defProps.button.icon
  147 + },
  148 + // 按钮颜色,支持传入linear-gradient渐变色
  149 + color: {
  150 + type: String,
  151 + default: () => defProps.button.color
  152 + },
  153 + // 停止冒泡
  154 + stop: {
  155 + type: Boolean,
  156 + default: () => defProps.button.stop
  157 + },
  158 + }
  159 +})
... ...
uni_modules/uview-plus/components/u-button/u-button.vue 0 → 100644
  1 +<template>
  2 + <!-- #ifndef APP-NVUE -->
  3 + <button
  4 + :hover-start-time="Number(hoverStartTime)"
  5 + :hover-stay-time="Number(hoverStayTime)"
  6 + :form-type="formType"
  7 + :open-type="openType"
  8 + :app-parameter="appParameter"
  9 + :hover-stop-propagation="hoverStopPropagation"
  10 + :send-message-title="sendMessageTitle"
  11 + :send-message-path="sendMessagePath"
  12 + :lang="lang"
  13 + :data-name="dataName"
  14 + :session-from="sessionFrom"
  15 + :send-message-img="sendMessageImg"
  16 + :show-message-card="showMessageCard"
  17 + @getphonenumber="getphonenumber"
  18 + @getuserinfo="getuserinfo"
  19 + @error="error"
  20 + @opensetting="opensetting"
  21 + @launchapp="launchapp"
  22 + @agreeprivacyauthorization="agreeprivacyauthorization"
  23 + :hover-class="!disabled && !loading ? 'u-button--active' : ''"
  24 + class="u-button u-reset-button"
  25 + :style="[baseColor, addStyle(customStyle)]"
  26 + @tap="clickHandler"
  27 + :class="bemClass"
  28 + >
  29 + <template v-if="loading">
  30 + <u-loading-icon
  31 + :mode="loadingMode"
  32 + :size="loadingSize * 1.15"
  33 + :color="loadingColor"
  34 + ></u-loading-icon>
  35 + <text
  36 + class="u-button__loading-text"
  37 + :style="[{ fontSize: textSize + 'px' }]"
  38 + >{{ loadingText || text }}</text
  39 + >
  40 + </template>
  41 + <template v-else>
  42 + <up-icon
  43 + v-if="icon"
  44 + :name="icon"
  45 + :color="iconColorCom"
  46 + :size="textSize * 1.35"
  47 + :customStyle="{ marginRight: '2px' }"
  48 + ></up-icon>
  49 + <slot>
  50 + <text
  51 + class="u-button__text"
  52 + :style="[{ fontSize: textSize + 'px' }]"
  53 + >{{ text }}</text
  54 + >
  55 + </slot>
  56 + </template>
  57 + </button>
  58 + <!-- #endif -->
  59 +
  60 + <!-- #ifdef APP-NVUE -->
  61 + <view
  62 + :hover-start-time="Number(hoverStartTime)"
  63 + :hover-stay-time="Number(hoverStayTime)"
  64 + class="u-button"
  65 + :hover-class="
  66 + !disabled && !loading && !color && (plain || type === 'info')
  67 + ? 'u-button--active--plain'
  68 + : !disabled && !loading && !plain
  69 + ? 'u-button--active'
  70 + : ''
  71 + "
  72 + @tap="clickHandler"
  73 + :class="bemClass"
  74 + :style="[baseColor, addStyle(customStyle)]"
  75 + >
  76 + <template v-if="loading">
  77 + <u-loading-icon
  78 + :mode="loadingMode"
  79 + :size="loadingSize * 1.15"
  80 + :color="loadingColor"
  81 + ></u-loading-icon>
  82 + <text
  83 + class="u-button__loading-text"
  84 + :style="[nvueTextStyle]"
  85 + :class="[plain && `u-button__text--plain--${type}`]"
  86 + >{{ loadingText || text }}</text
  87 + >
  88 + </template>
  89 + <template v-else>
  90 + <up-icon
  91 + v-if="icon"
  92 + :name="icon"
  93 + :color="iconColorCom"
  94 + :size="textSize * 1.35"
  95 + ></up-icon>
  96 + <text
  97 + class="u-button__text"
  98 + :style="[
  99 + {
  100 + marginLeft: icon ? '2px' : 0,
  101 + },
  102 + nvueTextStyle,
  103 + ]"
  104 + :class="[plain && `u-button__text--plain--${type}`]"
  105 + >{{ text }}</text
  106 + >
  107 + </template>
  108 + </view>
  109 + <!-- #endif -->
  110 +</template>
  111 +
  112 +<script lang="ts">
  113 +import { buttonMixin } from "../../libs/mixin/button";
  114 +import { openType } from "../../libs/mixin/openType";
  115 +import { mpMixin } from '../../libs/mixin/mpMixin';
  116 +import { mixin } from '../../libs/mixin/mixin';
  117 +import { props } from "./props";
  118 +import { addStyle } from '../../libs/function/index';
  119 +import { throttle } from '../../libs/function/throttle';
  120 +import color from '../../libs/config/color';
  121 +/**
  122 + * button 按钮
  123 + * @description Button 按钮
  124 + * @tutorial https://uview-plus.jiangruyi.com/components/button.html
  125 + *
  126 + * @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
  127 + * @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
  128 + * @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
  129 + * @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
  130 + * @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
  131 + * @property {Boolean} disabled 是否禁用 (默认 false)
  132 + * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
  133 + * @property {String | Number} loadingText 加载中提示文字
  134 + * @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
  135 + * @property {String | Number} loadingSize 加载图标大小 (默认 15 )
  136 + * @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
  137 + * @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
  138 + * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
  139 + * @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
  140 + * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
  141 + * @property {String} sessionFrom 会话来源,openType="contact"时有效
  142 + * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
  143 + * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
  144 + * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
  145 + * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
  146 + * @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
  147 + * @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
  148 + * @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
  149 + * @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
  150 + * @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
  151 + * @property {String} icon 按钮图标
  152 + * @property {String} iconColor 按钮图标颜色
  153 + * @property {String} color 按钮颜色,支持传入linear-gradient渐变色
  154 + * @property {Object} customStyle 定义需要用到的外部样式
  155 + *
  156 + * @event {Function} click 非禁止并且非加载中,才能点击
  157 + * @event {Function} getphonenumber open-type="getPhoneNumber"时有效
  158 + * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
  159 + * @event {Function} error 当使用开放能力时,发生错误的回调
  160 + * @event {Function} opensetting 在打开授权设置页并关闭后回调
  161 + * @event {Function} launchapp 打开 APP 成功的回调
  162 + * @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调
  163 + * @example <u-button>月落</u-button>
  164 + */
  165 +export default {
  166 + name: "u-button",
  167 + // #ifdef MP
  168 + mixins: [mpMixin, mixin, buttonMixin, openType, props],
  169 + // #endif
  170 + // #ifndef MP
  171 + mixins: [mpMixin, mixin, props],
  172 + // #endif
  173 + data() {
  174 + return {};
  175 + },
  176 + computed: {
  177 + // 生成bem风格的类名
  178 + bemClass() {
  179 + // this.bem为一个computed变量,在mixin中
  180 + if (!this.color) {
  181 + return this.bem(
  182 + "button",
  183 + ["type", "shape", "size"],
  184 + ["disabled", "plain", "hairline"]
  185 + );
  186 + } else {
  187 + // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
  188 + return this.bem(
  189 + "button",
  190 + ["shape", "size"],
  191 + ["disabled", "plain", "hairline"]
  192 + );
  193 + }
  194 + },
  195 + loadingColor() {
  196 + if (this.plain) {
  197 + // 如果有设置color值,则用color值,否则使用type主题颜色
  198 + return this.color
  199 + ? this.color
  200 + : color[`u-${this.type}`];
  201 + }
  202 + if (this.type === "info") {
  203 + return "#c9c9c9";
  204 + }
  205 + return "rgb(200, 200, 200)";
  206 + },
  207 + iconColorCom() {
  208 + // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
  209 + // up-icon的color能接受一个主题颜色的值
  210 + if (this.iconColor) return this.iconColor;
  211 + if (this.plain) {
  212 + return this.color ? this.color : this.type;
  213 + } else {
  214 + return this.type === "info" ? "#000000" : "#ffffff";
  215 + }
  216 + },
  217 + baseColor() {
  218 + let style = {};
  219 + if (this.color) {
  220 + // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
  221 + style.color = this.plain ? this.color : "white";
  222 + if (!this.plain) {
  223 + // 非镂空,背景色使用自定义的颜色
  224 + style["background-color"] = this.color;
  225 + }
  226 + if (this.color.indexOf("gradient") !== -1) {
  227 + // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
  228 + // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
  229 + // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
  230 + style.borderTopWidth = 0;
  231 + style.borderRightWidth = 0;
  232 + style.borderBottomWidth = 0;
  233 + style.borderLeftWidth = 0;
  234 + if (!this.plain) {
  235 + style.backgroundImage = this.color;
  236 + }
  237 + } else {
  238 + // 非渐变色,则设置边框相关的属性
  239 + style.borderColor = this.color;
  240 + style.borderWidth = "1px";
  241 + style.borderStyle = "solid";
  242 + }
  243 + }
  244 + return style;
  245 + },
  246 + // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
  247 + nvueTextStyle() {
  248 + let style = {};
  249 + // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
  250 + if (this.type === "info") {
  251 + style.color = "#323233";
  252 + }
  253 + if (this.color) {
  254 + style.color = this.plain ? this.color : "white";
  255 + }
  256 + style.fontSize = this.textSize + "px";
  257 + return style;
  258 + },
  259 + // 字体大小
  260 + textSize() {
  261 + let fontSize = 14,
  262 + { size } = this;
  263 + if (size === "large") fontSize = 16;
  264 + if (size === "normal") fontSize = 14;
  265 + if (size === "small") fontSize = 12;
  266 + if (size === "mini") fontSize = 10;
  267 + return fontSize;
  268 + },
  269 + },
  270 + emits: ['click', 'getphonenumber', 'getuserinfo',
  271 + 'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'],
  272 + methods: {
  273 + addStyle,
  274 + clickHandler(e: any) {
  275 + // 非禁止并且非加载中,才能点击
  276 + if (!this.disabled && !this.loading) {
  277 + // 进行节流控制,每this.throttle毫秒内,只在开始处执行
  278 + throttle(() => {
  279 + this.$emit("click", e);
  280 + }, this.throttleTime);
  281 + }
  282 + // 是否阻止事件传播
  283 + this.stop && this.preventEvent(e)
  284 + },
  285 + // 下面为对接uniapp官方按钮开放能力事件回调的对接
  286 + getphonenumber(res: any) {
  287 + this.$emit("getphonenumber", res);
  288 + },
  289 + getuserinfo(res: any) {
  290 + this.$emit("getuserinfo", res);
  291 + },
  292 + error(res: any) {
  293 + this.$emit("error", res);
  294 + },
  295 + opensetting(res: any) {
  296 + this.$emit("opensetting", res);
  297 + },
  298 + launchapp(res: any) {
  299 + this.$emit("launchapp", res);
  300 + },
  301 + agreeprivacyauthorization(res) {
  302 + this.$emit("agreeprivacyauthorization", res);
  303 + },
  304 + },
  305 +};
  306 +</script>
  307 +
  308 +<style lang="scss" scoped>
  309 +/* #ifndef APP-NVUE */
  310 +@import "./vue.scss";
  311 +/* #endif */
  312 +
  313 +/* #ifdef APP-NVUE */
  314 +@import "./nvue.scss";
  315 +/* #endif */
  316 +
  317 +$u-button-u-button-height: 40px !default;
  318 +$u-button-text-font-size: 15px !default;
  319 +$u-button-loading-text-font-size: 15px !default;
  320 +$u-button-loading-text-margin-left: 4px !default;
  321 +$u-button-large-width: 100% !default;
  322 +$u-button-large-height: 50px !default;
  323 +$u-button-normal-padding: 0 12px !default;
  324 +$u-button-large-padding: 0 15px !default;
  325 +$u-button-normal-font-size: 14px !default;
  326 +$u-button-small-min-width: 60px !default;
  327 +$u-button-small-height: 30px !default;
  328 +$u-button-small-padding: 0px 8px !default;
  329 +$u-button-mini-padding: 0px 8px !default;
  330 +$u-button-small-font-size: 12px !default;
  331 +$u-button-mini-height: 22px !default;
  332 +$u-button-mini-font-size: 10px !default;
  333 +$u-button-mini-min-width: 50px !default;
  334 +$u-button-disabled-opacity: 0.5 !default;
  335 +$u-button-info-color: #323233 !default;
  336 +$u-button-info-background-color: #fff !default;
  337 +$u-button-info-border-color: #ebedf0 !default;
  338 +$u-button-info-border-width: 1px !default;
  339 +$u-button-info-border-style: solid !default;
  340 +$u-button-success-color: #fff !default;
  341 +$u-button-success-background-color: $u-success !default;
  342 +$u-button-success-border-color: $u-button-success-background-color !default;
  343 +$u-button-success-border-width: 1px !default;
  344 +$u-button-success-border-style: solid !default;
  345 +$u-button-primary-color: #fff !default;
  346 +$u-button-primary-background-color: $u-primary !default;
  347 +$u-button-primary-border-color: $u-button-primary-background-color !default;
  348 +$u-button-primary-border-width: 1px !default;
  349 +$u-button-primary-border-style: solid !default;
  350 +$u-button-error-color: #fff !default;
  351 +$u-button-error-background-color: $u-error !default;
  352 +$u-button-error-border-color: $u-button-error-background-color !default;
  353 +$u-button-error-border-width: 1px !default;
  354 +$u-button-error-border-style: solid !default;
  355 +$u-button-warning-color: #fff !default;
  356 +$u-button-warning-background-color: $u-warning !default;
  357 +$u-button-warning-border-color: $u-button-warning-background-color !default;
  358 +$u-button-warning-border-width: 1px !default;
  359 +$u-button-warning-border-style: solid !default;
  360 +$u-button-block-width: 100% !default;
  361 +$u-button-circle-border-top-right-radius: 100px !default;
  362 +$u-button-circle-border-top-left-radius: 100px !default;
  363 +$u-button-circle-border-bottom-left-radius: 100px !default;
  364 +$u-button-circle-border-bottom-right-radius: 100px !default;
  365 +$u-button-square-border-top-right-radius: 3px !default;
  366 +$u-button-square-border-top-left-radius: 3px !default;
  367 +$u-button-square-border-bottom-left-radius: 3px !default;
  368 +$u-button-square-border-bottom-right-radius: 3px !default;
  369 +$u-button-icon-min-width: 1em !default;
  370 +$u-button-plain-background-color: #fff !default;
  371 +$u-button-hairline-border-width: 0.5px !default;
  372 +
  373 +.u-button {
  374 + height: $u-button-u-button-height;
  375 + position: relative;
  376 + align-items: center;
  377 + justify-content: center;
  378 + @include flex;
  379 + /* #ifndef APP-NVUE */
  380 + box-sizing: border-box;
  381 + /* #endif */
  382 + flex-direction: row;
  383 +
  384 + &__text {
  385 + font-size: $u-button-text-font-size;
  386 + }
  387 +
  388 + &__loading-text {
  389 + font-size: $u-button-loading-text-font-size;
  390 + margin-left: $u-button-loading-text-margin-left;
  391 + }
  392 +
  393 + &--large {
  394 + /* #ifndef APP-NVUE */
  395 + width: $u-button-large-width;
  396 + /* #endif */
  397 + height: $u-button-large-height;
  398 + padding: $u-button-large-padding;
  399 + }
  400 +
  401 + &--normal {
  402 + padding: $u-button-normal-padding;
  403 + font-size: $u-button-normal-font-size;
  404 + }
  405 +
  406 + &--small {
  407 + /* #ifndef APP-NVUE */
  408 + min-width: $u-button-small-min-width;
  409 + /* #endif */
  410 + height: $u-button-small-height;
  411 + padding: $u-button-small-padding;
  412 + font-size: $u-button-small-font-size;
  413 + }
  414 +
  415 + &--mini {
  416 + height: $u-button-mini-height;
  417 + font-size: $u-button-mini-font-size;
  418 + /* #ifndef APP-NVUE */
  419 + min-width: $u-button-mini-min-width;
  420 + /* #endif */
  421 + padding: $u-button-mini-padding;
  422 + }
  423 +
  424 + &--disabled {
  425 + opacity: $u-button-disabled-opacity;
  426 + }
  427 +
  428 + &--info {
  429 + color: $u-button-info-color;
  430 + background-color: $u-button-info-background-color;
  431 + border-color: $u-button-info-border-color;
  432 + border-width: $u-button-info-border-width;
  433 + border-style: $u-button-info-border-style;
  434 + }
  435 +
  436 + &--success {
  437 + color: $u-button-success-color;
  438 + background-color: $u-button-success-background-color;
  439 + border-color: $u-button-success-border-color;
  440 + border-width: $u-button-success-border-width;
  441 + border-style: $u-button-success-border-style;
  442 + }
  443 +
  444 + &--primary {
  445 + color: $u-button-primary-color;
  446 + background-color: $u-button-primary-background-color;
  447 + border-color: $u-button-primary-border-color;
  448 + border-width: $u-button-primary-border-width;
  449 + border-style: $u-button-primary-border-style;
  450 + }
  451 +
  452 + &--error {
  453 + color: $u-button-error-color;
  454 + background-color: $u-button-error-background-color;
  455 + border-color: $u-button-error-border-color;
  456 + border-width: $u-button-error-border-width;
  457 + border-style: $u-button-error-border-style;
  458 + }
  459 +
  460 + &--warning {
  461 + color: $u-button-warning-color;
  462 + background-color: $u-button-warning-background-color;
  463 + border-color: $u-button-warning-border-color;
  464 + border-width: $u-button-warning-border-width;
  465 + border-style: $u-button-warning-border-style;
  466 + }
  467 +
  468 + &--block {
  469 + @include flex;
  470 + width: $u-button-block-width;
  471 + }
  472 +
  473 + &--circle {
  474 + border-top-right-radius: $u-button-circle-border-top-right-radius;
  475 + border-top-left-radius: $u-button-circle-border-top-left-radius;
  476 + border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
  477 + border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
  478 + }
  479 +
  480 + &--square {
  481 + border-bottom-left-radius: $u-button-square-border-top-right-radius;
  482 + border-bottom-right-radius: $u-button-square-border-top-left-radius;
  483 + border-top-left-radius: $u-button-square-border-bottom-left-radius;
  484 + border-top-right-radius: $u-button-square-border-bottom-right-radius;
  485 + }
  486 +
  487 + &__icon {
  488 + /* #ifndef APP-NVUE */
  489 + min-width: $u-button-icon-min-width;
  490 + line-height: inherit !important;
  491 + vertical-align: top;
  492 + /* #endif */
  493 + }
  494 +
  495 + &--plain {
  496 + background-color: $u-button-plain-background-color;
  497 + }
  498 +
  499 + &--hairline {
  500 + border-width: $u-button-hairline-border-width !important;
  501 + }
  502 +}
  503 +</style>
... ...
uni_modules/uview-plus/components/u-button/vue.scss 0 → 100644
  1 +// nvue下hover-class无效
  2 +$u-button-before-top:50% !default;
  3 +$u-button-before-left:50% !default;
  4 +$u-button-before-width:100% !default;
  5 +$u-button-before-height:100% !default;
  6 +$u-button-before-transform:translate(-50%, -50%) !default;
  7 +$u-button-before-opacity:0 !default;
  8 +$u-button-before-background-color:#000 !default;
  9 +$u-button-before-border-color:#000 !default;
  10 +$u-button-active-before-opacity:.15 !default;
  11 +$u-button-icon-margin-left:4px !default;
  12 +$u-button-plain-u-button-info-color:$u-info;
  13 +$u-button-plain-u-button-success-color:$u-success;
  14 +$u-button-plain-u-button-error-color:$u-error;
  15 +$u-button-plain-u-button-warning-color:$u-warning;
  16 +
  17 +.u-button {
  18 + width: 100%;
  19 + white-space: nowrap;
  20 +
  21 + &__text {
  22 + white-space: nowrap;
  23 + line-height: 1;
  24 + }
  25 +
  26 + &:before {
  27 + position: absolute;
  28 + top:$u-button-before-top;
  29 + left:$u-button-before-left;
  30 + width:$u-button-before-width;
  31 + height:$u-button-before-height;
  32 + border: inherit;
  33 + border-radius: inherit;
  34 + transform:$u-button-before-transform;
  35 + opacity:$u-button-before-opacity;
  36 + content: " ";
  37 + background-color:$u-button-before-background-color;
  38 + border-color:$u-button-before-border-color;
  39 + }
  40 +
  41 + &--active {
  42 + &:before {
  43 + opacity: .15
  44 + }
  45 + }
  46 +
  47 + &__icon+&__text:not(:empty),
  48 + &__loading-text {
  49 + margin-left:$u-button-icon-margin-left;
  50 + }
  51 +
  52 + &--plain {
  53 + &.u-button--primary {
  54 + color: $u-primary;
  55 + }
  56 + }
  57 +
  58 + &--plain {
  59 + &.u-button--info {
  60 + color:$u-button-plain-u-button-info-color;
  61 + }
  62 + }
  63 +
  64 + &--plain {
  65 + &.u-button--success {
  66 + color:$u-button-plain-u-button-success-color;
  67 + }
  68 + }
  69 +
  70 + &--plain {
  71 + &.u-button--error {
  72 + color:$u-button-plain-u-button-error-color;
  73 + }
  74 + }
  75 +
  76 + &--plain {
  77 + &.u-button--warning {
  78 + color:$u-button-plain-u-button-warning-color;
  79 + }
  80 + }
  81 +}
... ...
uni_modules/uview-plus/components/u-calendar/calendar.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:52:43
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/calendar.js
  9 + */
  10 +import { t } from '../../libs/i18n'
  11 +export default {
  12 + // calendar 组件
  13 + calendar: {
  14 + title: t("up.calendar.chooseDates"),
  15 + showTitle: true,
  16 + showSubtitle: true,
  17 + mode: 'single',
  18 + startText: t("up.common.start"),
  19 + endText: t("up.common.end"),
  20 + customList: [],
  21 + color: '#3c9cff',
  22 + minDate: 0,
  23 + maxDate: 0,
  24 + defaultDate: null,
  25 + maxCount: Number.MAX_SAFE_INTEGER, // Infinity
  26 + rowHeight: 56,
  27 + formatter: null,
  28 + showLunar: false,
  29 + showMark: true,
  30 + confirmText: t("up.common.confirm"),
  31 + confirmDisabledText: t("up.common.confirm"),
  32 + show: false,
  33 + closeOnClickOverlay: false,
  34 + readonly: false,
  35 + showConfirm: true,
  36 + maxRange: Number.MAX_SAFE_INTEGER, // Infinity
  37 + rangePrompt: '',
  38 + showRangePrompt: true,
  39 + allowSameDay: false,
  40 + round: 0,
  41 + monthNum: 3,
  42 + weekText: [t("up.week.one"), t("up.week.two"), t("up.week.three"), t("up.week.four"), t("up.week.five"), t("up.week.six"), t("up.week.seven")],
  43 + forbidDays: [],
  44 + forbidDaysToast: t("up.calendar.disabled"),
  45 + monthFormat: '',
  46 + pageInline: false
  47 + }
  48 +}
... ...
uni_modules/uview-plus/components/u-calendar/header.vue 0 → 100644
  1 +<template>
  2 + <view class="u-calendar-header u-border-bottom">
  3 + <text
  4 + class="u-calendar-header__title"
  5 + v-if="showTitle"
  6 + >{{ title }}</text>
  7 + <text
  8 + class="u-calendar-header__subtitle"
  9 + v-if="showSubtitle"
  10 + >{{ subtitle }}</text>
  11 + <view class="u-calendar-header__weekdays">
  12 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[0] }}</text>
  13 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[1] }}</text>
  14 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[2] }}</text>
  15 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[3] }}</text>
  16 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[4] }}</text>
  17 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[5] }}</text>
  18 + <text class="u-calendar-header__weekdays__weekday">{{ weekText[6] }}</text>
  19 + </view>
  20 + </view>
  21 +</template>
  22 +
  23 +<script>
  24 + import { mpMixin } from '../../libs/mixin/mpMixin';
  25 + import { mixin } from '../../libs/mixin/mixin';
  26 + export default {
  27 + name: 'u-calendar-header',
  28 + mixins: [mpMixin, mixin],
  29 + props: {
  30 + // 标题
  31 + title: {
  32 + type: String,
  33 + default: ''
  34 + },
  35 + // 副标题
  36 + subtitle: {
  37 + type: String,
  38 + default: ''
  39 + },
  40 + // 是否显示标题
  41 + showTitle: {
  42 + type: Boolean,
  43 + default: true
  44 + },
  45 + // 是否显示副标题
  46 + showSubtitle: {
  47 + type: Boolean,
  48 + default: true
  49 + },
  50 + // 星期文本
  51 + weekText: {
  52 + type: Array,
  53 + default: () => {
  54 + return []
  55 + }
  56 + },
  57 + },
  58 + data() {
  59 + return {
  60 +
  61 + }
  62 + },
  63 + methods: {
  64 + name() {
  65 +
  66 + }
  67 + },
  68 + }
  69 +</script>
  70 +
  71 +<style lang="scss" scoped>
  72 +
  73 + .u-calendar-header {
  74 + display: flex;
  75 + flex-direction: column;
  76 + padding-bottom: 4px;
  77 +
  78 + &__title {
  79 + font-size: 16px;
  80 + color: $u-main-color;
  81 + text-align: center;
  82 + height: 42px;
  83 + line-height: 42px;
  84 + font-weight: bold;
  85 + }
  86 +
  87 + &__subtitle {
  88 + font-size: 14px;
  89 + color: $u-main-color;
  90 + height: 40px;
  91 + text-align: center;
  92 + line-height: 40px;
  93 + font-weight: bold;
  94 + }
  95 +
  96 + &__weekdays {
  97 + @include flex;
  98 + justify-content: space-between;
  99 +
  100 + &__weekday {
  101 + font-size: 13px;
  102 + color: $u-main-color;
  103 + line-height: 30px;
  104 + flex: 1;
  105 + text-align: center;
  106 + }
  107 + }
  108 + }
  109 +</style>
... ...
uni_modules/uview-plus/components/u-calendar/month.vue 0 → 100644
  1 +<template>
  2 + <view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
  3 + <view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
  4 + :ref="`u-calendar-month-${index}`" :id="`month-${index}`">
  5 + <text v-if="index !== 0" class="u-calendar-month__title">{{ monthTitle(item) }}</text>
  6 + <view class="u-calendar-month__days">
  7 + <view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
  8 + <text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
  9 + </view>
  10 + <view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
  11 + :style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
  12 + :class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
  13 + <view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
  14 + <text class="u-calendar-month__days__day__select__info"
  15 + :class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__info--disabled' : '']"
  16 + :style="[textStyle(item1)]">{{ item1.day }}</text>
  17 + <text v-if="getBottomInfo(index, index1, item1)"
  18 + class="u-calendar-month__days__day__select__buttom-info"
  19 + :class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__buttom-info--disabled' : '']"
  20 + :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
  21 + <text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
  22 + </view>
  23 + </view>
  24 + </view>
  25 + </view>
  26 + </view>
  27 +</template>
  28 +
  29 +<script>
  30 + // #ifdef APP-NVUE
  31 + // 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
  32 + const dom = uni.requireNativePlugin('dom')
  33 + // #endif
  34 + import { mpMixin } from '../../libs/mixin/mpMixin';
  35 + import { mixin } from '../../libs/mixin/mixin';
  36 + import { addUnit, deepClone, toast, sleep } from '../../libs/function/index';
  37 + import { colorGradient } from '../../libs/function/colorGradient';
  38 + import test from '../../libs/function/test';
  39 + import defProps from '../../libs/config/props';
  40 + import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
  41 + import { t } from '../../libs/i18n'
  42 + export default {
  43 + name: 'u-calendar-month',
  44 + mixins: [mpMixin, mixin],
  45 + props: {
  46 + // 是否显示月份背景色
  47 + showMark: {
  48 + type: Boolean,
  49 + default: true
  50 + },
  51 + // 主题色,对底部按钮和选中日期有效
  52 + color: {
  53 + type: String,
  54 + default: '#3c9cff'
  55 + },
  56 + // 月份数据
  57 + months: {
  58 + type: Array,
  59 + default: () => []
  60 + },
  61 + // 日期选择类型
  62 + mode: {
  63 + type: String,
  64 + default: 'single'
  65 + },
  66 + // 日期行高
  67 + rowHeight: {
  68 + type: [String, Number],
  69 + default: 58
  70 + },
  71 + // mode=multiple时,最多可选多少个日期
  72 + maxCount: {
  73 + type: [String, Number],
  74 + default: Infinity
  75 + },
  76 + // mode=range时,第一个日期底部的提示文字
  77 + startText: {
  78 + type: String,
  79 + default: '开始'
  80 + },
  81 + // mode=range时,最后一个日期底部的提示文字
  82 + endText: {
  83 + type: String,
  84 + default: '结束'
  85 + },
  86 + // 默认选中的日期,mode为multiple或range是必须为数组格式
  87 + defaultDate: {
  88 + type: [Array, String, Date],
  89 + default: null
  90 + },
  91 + // 最小的可选日期
  92 + minDate: {
  93 + type: [String, Number],
  94 + default: 0
  95 + },
  96 + // 最大可选日期
  97 + maxDate: {
  98 + type: [String, Number],
  99 + default: 0
  100 + },
  101 + // 如果没有设置maxDate,则往后推多少个月
  102 + maxMonth: {
  103 + type: [String, Number],
  104 + default: 2
  105 + },
  106 + // 是否为只读状态,只读状态下禁止选择日期
  107 + readonly: {
  108 + type: Boolean,
  109 + default: () => defProps.calendar.readonly
  110 + },
  111 + // 日期区间最多可选天数,默认无限制,mode = range时有效
  112 + maxRange: {
  113 + type: [Number, String],
  114 + default: Infinity
  115 + },
  116 + // 范围选择超过最多可选天数时的提示文案,mode = range时有效
  117 + rangePrompt: {
  118 + type: String,
  119 + default: ''
  120 + },
  121 + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
  122 + showRangePrompt: {
  123 + type: Boolean,
  124 + default: true
  125 + },
  126 + // 是否允许日期范围的起止时间为同一天,mode = range时有效
  127 + allowSameDay: {
  128 + type: Boolean,
  129 + default: false
  130 + },
  131 + forbidDays: {
  132 + type: Array,
  133 + default: () => []
  134 + },
  135 + forbidDaysToast: {
  136 + type: String,
  137 + default: ''
  138 + }
  139 + },
  140 + data() {
  141 + return {
  142 + // 每个日期的宽度
  143 + width: 0,
  144 + // 当前选中的日期item
  145 + item: {},
  146 + selected: []
  147 + }
  148 + },
  149 + watch: {
  150 + selectedChange: {
  151 + immediate: true,
  152 + handler(n) {
  153 + this.setDefaultDate()
  154 + }
  155 + }
  156 + },
  157 + computed: {
  158 + // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
  159 + selectedChange() {
  160 + return [this.minDate, this.maxDate, this.defaultDate]
  161 + },
  162 + dayStyle(index1, index2, item) {
  163 + return (index1, index2, item) => {
  164 + const style = {}
  165 + let week = item.week
  166 + // 不进行四舍五入的形式保留2位小数
  167 + const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
  168 + // 得出每个日期的宽度
  169 + // #ifdef APP-NVUE
  170 + style.width = addUnit(dayWidth, 'px')
  171 + // #endif
  172 + style.height = addUnit(this.rowHeight, 'px')
  173 + if (index2 === 0) {
  174 + // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
  175 + week = (week === 0 ? 7 : week) - 1
  176 + style.marginLeft = addUnit(week * dayWidth, 'px')
  177 + }
  178 + if (this.mode === 'range') {
  179 + // 之所以需要这么写,是因为DCloud公司的iOS客户端导致的bug
  180 + style.paddingLeft = 0
  181 + style.paddingRight = 0
  182 + style.paddingBottom = 0
  183 + style.paddingTop = 0
  184 + }
  185 + return style
  186 + }
  187 + },
  188 + daySelectStyle() {
  189 + return (index1, index2, item) => {
  190 + let date = dayjs(item.date).format("YYYY-MM-DD"),
  191 + style = {}
  192 + // 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
  193 + if (this.selected.some(item => this.dateSame(item, date))) {
  194 + style.backgroundColor = this.color
  195 + }
  196 + if (this.mode === 'single') {
  197 + if (date === this.selected[0]) {
  198 + // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
  199 + style.borderTopLeftRadius = '3px'
  200 + style.borderBottomLeftRadius = '3px'
  201 + style.borderTopRightRadius = '3px'
  202 + style.borderBottomRightRadius = '3px'
  203 + }
  204 + } else if (this.mode === 'range') {
  205 + if (this.selected.length >= 2) {
  206 + const len = this.selected.length - 1
  207 + // 第一个日期设置左上角和左下角的圆角
  208 + if (this.dateSame(date, this.selected[0])) {
  209 + style.borderTopLeftRadius = '3px'
  210 + style.borderBottomLeftRadius = '3px'
  211 + }
  212 + // 最后一个日期设置右上角和右下角的圆角
  213 + if (this.dateSame(date, this.selected[len])) {
  214 + style.borderTopRightRadius = '3px'
  215 + style.borderBottomRightRadius = '3px'
  216 + }
  217 + // 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
  218 + if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
  219 + .selected[len]))) {
  220 + style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
  221 + // 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
  222 + style.opacity = 0.7
  223 + }
  224 + } else if (this.selected.length === 1) {
  225 + // 之所以需要这么写,是因为uni-app的iOS客户端的bug
  226 + // 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
  227 + style.borderTopLeftRadius = '3px'
  228 + style.borderBottomLeftRadius = '3px'
  229 + }
  230 + } else {
  231 + if (this.selected.some(item => this.dateSame(item, date))) {
  232 + style.borderTopLeftRadius = '3px'
  233 + style.borderBottomLeftRadius = '3px'
  234 + style.borderTopRightRadius = '3px'
  235 + style.borderBottomRightRadius = '3px'
  236 + }
  237 + }
  238 + return style
  239 + }
  240 + },
  241 + // 某个日期是否被选中
  242 + textStyle() {
  243 + return (item) => {
  244 + const date = dayjs(item.date).format("YYYY-MM-DD"),
  245 + style = {}
  246 + // 选中的日期,提示文字设置白色
  247 + if (this.selected.some(item => this.dateSame(item, date))) {
  248 + style.color = '#ffffff'
  249 + }
  250 + if (this.mode === 'range') {
  251 + const len = this.selected.length - 1
  252 + // 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
  253 + if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
  254 + .selected[len]))) {
  255 + style.color = this.color
  256 + }
  257 + }
  258 + return style
  259 + }
  260 + },
  261 + // 获取底部的提示文字
  262 + getBottomInfo() {
  263 + return (index1, index2, item) => {
  264 + const date = dayjs(item.date).format("YYYY-MM-DD")
  265 + const bottomInfo = item.bottomInfo
  266 + // 当为日期范围模式时,且选择的日期个数大于0时
  267 + if (this.mode === 'range' && this.selected.length > 0) {
  268 + if (this.selected.length === 1) {
  269 + // 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
  270 + if (this.dateSame(date, this.selected[0])) return this.startText
  271 + else return bottomInfo
  272 + } else {
  273 + const len = this.selected.length - 1
  274 + // 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
  275 + if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
  276 + len === 1) {
  277 + // 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
  278 + return `${this.startText}/${this.endText}`
  279 + } else if (this.dateSame(date, this.selected[0])) {
  280 + return this.startText
  281 + } else if (this.dateSame(date, this.selected[len])) {
  282 + return this.endText
  283 + } else {
  284 + return bottomInfo
  285 + }
  286 + }
  287 + } else {
  288 + return bottomInfo
  289 + }
  290 + }
  291 + }
  292 + },
  293 + mounted() {
  294 + this.init()
  295 + },
  296 + emits: ['monthSelected', 'updateMonthTop'],
  297 + methods: {
  298 + init() {
  299 + // 初始化默认选中
  300 + this.$emit('monthSelected', this.selected)
  301 + this.$nextTick(() => {
  302 + // 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
  303 + // 因为nvue下,$nextTick并不是100%可靠的
  304 + sleep(10).then(() => {
  305 + this.getWrapperWidth()
  306 + this.getMonthRect()
  307 + })
  308 + })
  309 + },
  310 + monthTitle(item) {
  311 + if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
  312 + return item.year + '年' + (item.month < 10 ? '0' + item.month : item.month) + '月'
  313 + } else {
  314 + return (item.month < 10 ? '0' + item.month : item.month) + '/' + item.year
  315 + }
  316 + },
  317 + isForbid(item) {
  318 + let date = dayjs(item.date).format("YYYY-MM-DD")
  319 + if (this.mode !== 'range' && this.forbidDays.includes(date)) {
  320 + return true
  321 + }
  322 + return false
  323 + },
  324 + // 判断两个日期是否相等
  325 + dateSame(date1, date2) {
  326 + return dayjs(date1).isSame(dayjs(date2))
  327 + },
  328 + // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
  329 + getWrapperWidth() {
  330 + // #ifdef APP-NVUE
  331 + dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
  332 + this.width = res.size.width
  333 + })
  334 + // #endif
  335 + // #ifndef APP-NVUE
  336 + this.$uGetRect('.u-calendar-month-wrapper').then(size => {
  337 + this.width = size.width
  338 + })
  339 + // #endif
  340 + },
  341 + getMonthRect() {
  342 + // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
  343 + const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
  344 + `u-calendar-month-${index}`))
  345 + // 一次性返回
  346 + Promise.all(promiseAllArr).then(
  347 + sizes => {
  348 + let height = 1
  349 + const topArr = []
  350 + for (let i = 0; i < this.months.length; i++) {
  351 + // 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
  352 + topArr[i] = height
  353 + height += sizes[i].height
  354 + }
  355 + // 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
  356 + this.$emit('updateMonthTop', topArr)
  357 + })
  358 + },
  359 + // 获取每个月份区域的尺寸
  360 + getMonthRectByPromise(el) {
  361 + // #ifndef APP-NVUE
  362 + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview-plus.jiangruyi.com/js/getRect.html
  363 + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
  364 + return new Promise(resolve => {
  365 + this.$uGetRect(`.${el}`).then(size => {
  366 + resolve(size)
  367 + })
  368 + })
  369 + // #endif
  370 +
  371 + // #ifdef APP-NVUE
  372 + // nvue下,使用dom模块查询元素高度
  373 + // 返回一个promise,让调用此方法的主体能使用then回调
  374 + return new Promise(resolve => {
  375 + dom.getComponentRect(this.$refs[el][0], res => {
  376 + resolve(res.size)
  377 + })
  378 + })
  379 + // #endif
  380 + },
  381 + // 点击某一个日期
  382 + clickHandler(index1, index2, item) {
  383 + if (this.readonly) {
  384 + return;
  385 + }
  386 + this.item = item
  387 + const date = dayjs(item.date).format("YYYY-MM-DD")
  388 + if (item.disabled) return
  389 + if (this.isForbid(item)) {
  390 + uni.showToast({
  391 + title: this.forbidDaysToast
  392 + })
  393 + return
  394 + }
  395 + // 对上一次选择的日期数组进行深度克隆
  396 + let selected = deepClone(this.selected)
  397 + if (this.mode === 'single') {
  398 + // 单选情况下,让数组中的元素为当前点击的日期
  399 + selected = [date]
  400 + } else if (this.mode === 'multiple') {
  401 + if (selected.some(item => this.dateSame(item, date))) {
  402 + // 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
  403 + const itemIndex = selected.findIndex(item => item === date)
  404 + selected.splice(itemIndex, 1)
  405 + } else {
  406 + // 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
  407 + if (selected.length < this.maxCount) selected.push(date)
  408 + }
  409 + } else {
  410 + // 选择区间形式
  411 + if (selected.length === 0 || selected.length >= 2) {
  412 + // 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
  413 + selected = [date]
  414 + } else if (selected.length === 1) {
  415 + // 如果已经选择了开始日期
  416 + const existsDate = selected[0]
  417 + // 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
  418 + if (dayjs(date).isBefore(existsDate)) {
  419 + selected = [date]
  420 + } else if (dayjs(date).isAfter(existsDate)) {
  421 + // 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
  422 + if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
  423 + if(this.rangePrompt) {
  424 + toast(this.rangePrompt)
  425 + } else {
  426 + toast(t("up.calendar.daysExceed", { days: this.maxRange }))
  427 + }
  428 + return
  429 + }
  430 + // 如果当前日期大于已有日期,将当前的添加到数组尾部
  431 + selected.push(date)
  432 + const startDate = selected[0]
  433 + const endDate = selected[1]
  434 + const arr = []
  435 + let i = 0
  436 + do {
  437 + // 将开始和结束日期之间的日期添加到数组中
  438 + arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
  439 + i++
  440 + // 累加的日期小于结束日期时,继续下一次的循环
  441 + } while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
  442 + // 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
  443 + arr.push(endDate)
  444 + selected = arr
  445 + } else {
  446 + // 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
  447 + if (selected[0] === date && !this.allowSameDay) return
  448 + selected.push(date)
  449 + }
  450 + }
  451 + }
  452 + this.setSelected(selected)
  453 + },
  454 + // 设置默认日期
  455 + setDefaultDate() {
  456 + if (!this.defaultDate) {
  457 + // 如果没有设置默认日期,则将当天日期设置为默认选中的日期
  458 + const selected = [dayjs().format("YYYY-MM-DD")]
  459 + return this.setSelected(selected, false)
  460 + }
  461 + let defaultDate = []
  462 + const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
  463 + const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
  464 + if (this.mode === 'single') {
  465 + // 单选模式,可以是字符串或数组,Date对象等
  466 + if (!test.array(this.defaultDate)) {
  467 + defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
  468 + } else {
  469 + defaultDate = [this.defaultDate[0]]
  470 + }
  471 + } else {
  472 + // 如果为非数组,则不执行
  473 + if (!test.array(this.defaultDate)) return
  474 + defaultDate = this.defaultDate
  475 + }
  476 + // 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
  477 + defaultDate = defaultDate.filter(item => {
  478 + return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
  479 + maxDate).add(1, 'day'))
  480 + })
  481 + this.setSelected(defaultDate, false)
  482 + },
  483 + setSelected(selected, event = true) {
  484 + this.selected = selected
  485 + event && this.$emit('monthSelected', this.selected,'tap')
  486 + }
  487 + }
  488 + }
  489 +</script>
  490 +
  491 +<style lang="scss" scoped>
  492 +
  493 + .u-calendar-month-wrapper {
  494 + margin-top: 4px;
  495 + }
  496 +
  497 + .u-calendar-month {
  498 +
  499 + &__title {
  500 + display: flex;
  501 + flex-direction: column;
  502 + font-size: 14px;
  503 + line-height: 42px;
  504 + height: 42px;
  505 + color: $u-main-color;
  506 + text-align: center;
  507 + font-weight: bold;
  508 + }
  509 +
  510 + &__days {
  511 + position: relative;
  512 + @include flex;
  513 + flex-wrap: wrap;
  514 +
  515 + &__month-mark-wrapper {
  516 + position: absolute;
  517 + top: 0;
  518 + bottom: 0;
  519 + left: 0;
  520 + right: 0;
  521 + @include flex;
  522 + justify-content: center;
  523 + align-items: center;
  524 +
  525 + &__text {
  526 + font-size: 155px;
  527 + color: rgba(231, 232, 234, 0.83);
  528 + }
  529 + }
  530 +
  531 + &__day {
  532 + @include flex;
  533 + padding: 2px;
  534 + /* #ifndef APP-NVUE */
  535 + // vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
  536 + width: calc(100% / 7);
  537 + box-sizing: border-box;
  538 + /* #endif */
  539 +
  540 + &__select {
  541 + flex: 1;
  542 + @include flex;
  543 + align-items: center;
  544 + justify-content: center;
  545 + position: relative;
  546 +
  547 + &__dot {
  548 + width: 7px;
  549 + height: 7px;
  550 + border-radius: 100px;
  551 + background-color: $u-error;
  552 + position: absolute;
  553 + top: 12px;
  554 + right: 7px;
  555 + }
  556 +
  557 + &__buttom-info {
  558 + color: $u-content-color;
  559 + text-align: center;
  560 + position: absolute;
  561 + bottom: 5px;
  562 + font-size: 10px;
  563 + text-align: center;
  564 + left: 0;
  565 + right: 0;
  566 +
  567 + &--selected {
  568 + color: #ffffff;
  569 + }
  570 +
  571 + &--disabled {
  572 + color: #cacbcd;
  573 + }
  574 + }
  575 +
  576 + &__info {
  577 + text-align: center;
  578 + font-size: 16px;
  579 +
  580 + &--selected {
  581 + color: #ffffff;
  582 + }
  583 +
  584 + &--disabled {
  585 + color: #cacbcd;
  586 + }
  587 + }
  588 +
  589 + &--selected {
  590 + background-color: $u-primary;
  591 + @include flex;
  592 + justify-content: center;
  593 + align-items: center;
  594 + flex: 1;
  595 + border-radius: 3px;
  596 + }
  597 +
  598 + &--range-selected {
  599 + opacity: 0.3;
  600 + border-radius: 0;
  601 + }
  602 +
  603 + &--range-start-selected {
  604 + border-top-right-radius: 0;
  605 + border-bottom-right-radius: 0;
  606 + }
  607 +
  608 + &--range-end-selected {
  609 + border-top-left-radius: 0;
  610 + border-bottom-left-radius: 0;
  611 + }
  612 + }
  613 + }
  614 + }
  615 + }
  616 +</style>
... ...
uni_modules/uview-plus/components/u-calendar/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +
  4 +export const props = defineMixin({
  5 + props: {
  6 + // 日历顶部标题
  7 + title: {
  8 + type: String,
  9 + default: () => defProps.calendar.title
  10 + },
  11 + // 是否显示标题
  12 + showTitle: {
  13 + type: Boolean,
  14 + default: () => defProps.calendar.showTitle
  15 + },
  16 + // 是否显示副标题
  17 + showSubtitle: {
  18 + type: Boolean,
  19 + default: () => defProps.calendar.showSubtitle
  20 + },
  21 + // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
  22 + mode: {
  23 + type: String,
  24 + default: () => defProps.calendar.mode
  25 + },
  26 + // mode=range时,第一个日期底部的提示文字
  27 + startText: {
  28 + type: String,
  29 + default: () => defProps.calendar.startText
  30 + },
  31 + // mode=range时,最后一个日期底部的提示文字
  32 + endText: {
  33 + type: String,
  34 + default: () => defProps.calendar.endText
  35 + },
  36 + // 自定义列表
  37 + customList: {
  38 + type: Array,
  39 + default: () => defProps.calendar.customList
  40 + },
  41 + // 主题色,对底部按钮和选中日期有效
  42 + color: {
  43 + type: String,
  44 + default: () => defProps.calendar.color
  45 + },
  46 + // 最小的可选日期
  47 + minDate: {
  48 + type: [String, Number],
  49 + default: () => defProps.calendar.minDate
  50 + },
  51 + // 最大可选日期
  52 + maxDate: {
  53 + type: [String, Number],
  54 + default: () => defProps.calendar.maxDate
  55 + },
  56 + // 默认选中的日期,mode为multiple或range是必须为数组格式
  57 + defaultDate: {
  58 + type: [Array, String, Date, null],
  59 + default: () => defProps.calendar.defaultDate
  60 + },
  61 + // mode=multiple时,最多可选多少个日期
  62 + maxCount: {
  63 + type: [String, Number],
  64 + default: () => defProps.calendar.maxCount
  65 + },
  66 + // 日期行高
  67 + rowHeight: {
  68 + type: [String, Number],
  69 + default: () => defProps.calendar.rowHeight
  70 + },
  71 + // 日期格式化函数
  72 + formatter: {
  73 + type: [Function, null],
  74 + default: () => defProps.calendar.formatter
  75 + },
  76 + // 是否显示农历
  77 + showLunar: {
  78 + type: Boolean,
  79 + default: () => defProps.calendar.showLunar
  80 + },
  81 + // 是否显示月份背景色
  82 + showMark: {
  83 + type: Boolean,
  84 + default: () => defProps.calendar.showMark
  85 + },
  86 + // 确定按钮的文字
  87 + confirmText: {
  88 + type: String,
  89 + default: () => defProps.calendar.confirmText
  90 + },
  91 + // 确认按钮处于禁用状态时的文字
  92 + confirmDisabledText: {
  93 + type: String,
  94 + default: () => defProps.calendar.confirmDisabledText
  95 + },
  96 + // 是否显示日历弹窗
  97 + show: {
  98 + type: Boolean,
  99 + default: () => defProps.calendar.show
  100 + },
  101 + // 是否允许点击遮罩关闭日历
  102 + closeOnClickOverlay: {
  103 + type: Boolean,
  104 + default: () => defProps.calendar.closeOnClickOverlay
  105 + },
  106 + // 是否为只读状态,只读状态下禁止选择日期
  107 + readonly: {
  108 + type: Boolean,
  109 + default: () => defProps.calendar.readonly
  110 + },
  111 + // 是否展示确认按钮
  112 + showConfirm: {
  113 + type: Boolean,
  114 + default: () => defProps.calendar.showConfirm
  115 + },
  116 + // 日期区间最多可选天数,默认无限制,mode = range时有效
  117 + maxRange: {
  118 + type: [Number, String],
  119 + default: () => defProps.calendar.maxRange
  120 + },
  121 + // 范围选择超过最多可选天数时的提示文案,mode = range时有效
  122 + rangePrompt: {
  123 + type: String,
  124 + default: () => defProps.calendar.rangePrompt
  125 + },
  126 + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
  127 + showRangePrompt: {
  128 + type: Boolean,
  129 + default: () => defProps.calendar.showRangePrompt
  130 + },
  131 + // 是否允许日期范围的起止时间为同一天,mode = range时有效
  132 + allowSameDay: {
  133 + type: Boolean,
  134 + default: () => defProps.calendar.allowSameDay
  135 + },
  136 + // 圆角值
  137 + round: {
  138 + type: [Boolean, String, Number],
  139 + default: () => defProps.calendar.round
  140 + },
  141 + // 最多展示月份数量
  142 + monthNum: {
  143 + type: [Number, String],
  144 + default: 3
  145 + },
  146 + // 星期文案
  147 + weekText: {
  148 + type: Array,
  149 + default: defProps.calendar.weekText
  150 + },
  151 + forbidDays: {
  152 + type: Array,
  153 + default: defProps.calendar.forbidDays
  154 + },
  155 + forbidDaysToast:{
  156 + type: String,
  157 + default: defProps.calendar.forbidDaysToast
  158 + },
  159 + monthFormat:{
  160 + type: String,
  161 + default: defProps.calendar.monthFormat
  162 + },
  163 + // 是否页面内展示
  164 + pageInline:{
  165 + type: Boolean,
  166 + default: defProps.calendar.pageInline
  167 + }
  168 + }
  169 +})
... ...
uni_modules/uview-plus/components/u-calendar/u-calendar.vue 0 → 100644
  1 +<template>
  2 + <u-popup
  3 + :show="show"
  4 + mode="bottom"
  5 + :closeable="!pageInline"
  6 + @close="close"
  7 + :round="round"
  8 + :pageInline="pageInline"
  9 + :closeOnClickOverlay="closeOnClickOverlay"
  10 + >
  11 + <view class="u-calendar">
  12 + <uHeader
  13 + :title="title"
  14 + :subtitle="subtitle"
  15 + :showSubtitle="showSubtitle"
  16 + :showTitle="showTitle"
  17 + :weekText="weekText"
  18 + ></uHeader>
  19 + <scroll-view
  20 + :style="{
  21 + height: addUnit(listHeight, 'px')
  22 + }"
  23 + scroll-y
  24 + @scroll="onScroll"
  25 + :scroll-top="scrollTop"
  26 + :scrollIntoView="scrollIntoView"
  27 + >
  28 + <uMonth
  29 + :color="color"
  30 + :rowHeight="rowHeight"
  31 + :showMark="showMark"
  32 + :months="months"
  33 + :mode="mode"
  34 + :maxCount="maxCount"
  35 + :startText="startText"
  36 + :endText="endText"
  37 + :defaultDate="defaultDate"
  38 + :minDate="innerMinDate"
  39 + :maxDate="innerMaxDate"
  40 + :maxMonth="monthNum"
  41 + :readonly="readonly"
  42 + :maxRange="maxRange"
  43 + :rangePrompt="rangePrompt"
  44 + :showRangePrompt="showRangePrompt"
  45 + :allowSameDay="allowSameDay"
  46 + :forbidDays="forbidDays"
  47 + :forbidDaysToast="forbidDaysToast"
  48 + :monthFormat="monthFormat"
  49 + ref="month"
  50 + @monthSelected="monthSelected"
  51 + @updateMonthTop="updateMonthTop"
  52 + ></uMonth>
  53 + </scroll-view>
  54 + <slot name="footer" v-if="showConfirm">
  55 + <view class="u-calendar__confirm">
  56 + <u-button
  57 + shape="circle"
  58 + :text="
  59 + buttonDisabled ? confirmDisabledText : confirmText
  60 + "
  61 + :color="color"
  62 + @click="confirm"
  63 + :disabled="buttonDisabled"
  64 + ></u-button>
  65 + </view>
  66 + </slot>
  67 + </view>
  68 + </u-popup>
  69 +</template>
  70 +
  71 +<script>
  72 +import uHeader from './header.vue'
  73 +import uMonth from './month.vue'
  74 +import { props } from './props.js'
  75 +import util from './util.js'
  76 +import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
  77 +import Calendar from '../../libs/util/calendar.js'
  78 +import { mpMixin } from '../../libs/mixin/mpMixin.js'
  79 +import { mixin } from '../../libs/mixin/mixin.js'
  80 +import { addUnit, getPx, range, error, padZero } from '../../libs/function/index';
  81 +import test from '../../libs/function/test';
  82 +/**
  83 + * Calendar 日历
  84 + * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
  85 + * @tutorial https://uview-plus.jiangruyi.com/components/calendar.html
  86 + *
  87 + * @property {String} title 标题内容 (默认 日期选择 )
  88 + * @property {Boolean} showTitle 是否显示标题 (默认 true )
  89 + * @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
  90 + * @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
  91 + * @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' )
  92 + * @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
  93 + * @property {Array} customList 自定义列表
  94 + * @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' )
  95 + * @property {String | Number} minDate 最小的可选日期 (默认 0 )
  96 + * @property {String | Number} maxDate 最大可选日期 (默认 0 )
  97 + * @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式
  98 + * @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
  99 + * @property {String | Number} rowHeight 日期行高 (默认 56 )
  100 + * @property {Function} formatter 日期格式化函数
  101 + * @property {Boolean} showLunar 是否显示农历 (默认 false )
  102 + * @property {Boolean} showMark 是否显示月份背景色 (默认 true )
  103 + * @property {String} confirmText 确定按钮的文字 (默认 '确定' )
  104 + * @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
  105 + * @property {Boolean} show 是否显示日历弹窗 (默认 false )
  106 + * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
  107 + * @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false )
  108 + * @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效
  109 + * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
  110 + * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
  111 + * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
  112 + * @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
  113 + * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
  114 + * @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] )
  115 + *
  116 + * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
  117 + * @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
  118 + * @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
  119 + </u-calendar>
  120 + * */
  121 +export default {
  122 + name: 'u-calendar',
  123 + mixins: [mpMixin, mixin, props],
  124 + components: {
  125 + uHeader,
  126 + uMonth
  127 + },
  128 + data() {
  129 + return {
  130 + // 需要显示的月份的数组
  131 + months: [],
  132 + // 在月份滚动区域中,当前视图中月份的index索引
  133 + monthIndex: 0,
  134 + // 月份滚动区域的高度
  135 + listHeight: 0,
  136 + // month组件中选择的日期数组
  137 + selected: [],
  138 + scrollIntoView: '',
  139 + scrollIntoViewScroll: '',
  140 + scrollTop:0,
  141 + // 过滤处理方法
  142 + innerFormatter: (value) => value
  143 + }
  144 + },
  145 + watch: {
  146 + scrollIntoView: {
  147 + immediate: true,
  148 + handler(n) {
  149 + // console.log('scrollIntoView', n)
  150 + }
  151 + },
  152 + selectedChange: {
  153 + immediate: true,
  154 + handler(n) {
  155 + this.setMonth()
  156 + }
  157 + },
  158 + // 打开弹窗时,设置月份数据
  159 + show: {
  160 + immediate: true,
  161 + handler(n) {
  162 + if (n) {
  163 + this.setMonth()
  164 + } else {
  165 + // 关闭时重置scrollIntoView,否则会出现二次打开日历,当前月份数据显示不正确。
  166 + // scrollIntoView需要有一个值变动过程,才会产生作用。
  167 + this.scrollIntoView = ''
  168 + }
  169 + }
  170 + }
  171 + },
  172 + computed: {
  173 + // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
  174 + innerMaxDate() {
  175 + return test.number(this.maxDate)
  176 + ? Number(this.maxDate)
  177 + : this.maxDate
  178 + },
  179 + innerMinDate() {
  180 + return test.number(this.minDate)
  181 + ? Number(this.minDate)
  182 + : this.minDate
  183 + },
  184 + // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
  185 + selectedChange() {
  186 + return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
  187 + },
  188 + subtitle() {
  189 + // 初始化时,this.months为空数组,所以需要特别判断处理
  190 + if (this.months.length) {
  191 + if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
  192 + return this.months[this.monthIndex].year + '年' + (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '月'
  193 + } else {
  194 + return (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '/' + this.months[this.monthIndex].year
  195 + }
  196 + } else {
  197 + return ''
  198 + }
  199 + },
  200 + buttonDisabled() {
  201 + // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
  202 + if (this.mode === 'range') {
  203 + if (this.selected.length <= 1) {
  204 + return true
  205 + } else {
  206 + return false
  207 + }
  208 + } else {
  209 + return false
  210 + }
  211 + }
  212 + },
  213 + mounted() {
  214 + this.start = Date.now()
  215 + this.init()
  216 + },
  217 + emits: ["confirm", "close"],
  218 + methods: {
  219 + addUnit,
  220 + // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
  221 + setFormatter(e) {
  222 + this.innerFormatter = e
  223 + },
  224 + // month组件内部选择日期后,通过事件通知给父组件
  225 + monthSelected(e,scene ='init') {
  226 + this.selected = e
  227 + if (!this.showConfirm) {
  228 + // 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
  229 + if (
  230 + this.mode === 'multiple' ||
  231 + this.mode === 'single' ||
  232 + (this.mode === 'range' && this.selected.length >= 2)
  233 + ) {
  234 + if( scene === 'init'){
  235 + return
  236 + }
  237 + if( scene === 'tap') {
  238 + this.$emit('confirm', this.selected)
  239 + }
  240 + }
  241 + }
  242 + },
  243 + init() {
  244 + // 校验maxDate,不能小于minDate。
  245 + if (
  246 + this.innerMaxDate &&
  247 + this.innerMinDate &&
  248 + new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
  249 + ) {
  250 + return error('maxDate不能小于minDate时间')
  251 + }
  252 + // 滚动区域的高度
  253 + let bottomPadding = 0;
  254 + if (this.pageInline) {
  255 + bottomPadding = 0
  256 + } else {
  257 + bottomPadding = 30
  258 + }
  259 + this.listHeight = this.rowHeight * 5 + bottomPadding
  260 + this.setMonth()
  261 + },
  262 + close() {
  263 + this.$emit('close')
  264 + },
  265 + // 点击确定按钮
  266 + confirm() {
  267 + if (!this.buttonDisabled) {
  268 + this.$emit('confirm', this.selected)
  269 + }
  270 + },
  271 + // 获得两个日期之间的月份数
  272 + getMonths(minDate, maxDate) {
  273 + const minYear = dayjs(minDate).year()
  274 + const minMonth = dayjs(minDate).month() + 1
  275 + const maxYear = dayjs(maxDate).year()
  276 + const maxMonth = dayjs(maxDate).month() + 1
  277 + return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
  278 + },
  279 + // 设置月份数据
  280 + setMonth() {
  281 + // 最小日期的毫秒数
  282 + const minDate = this.innerMinDate || dayjs().valueOf()
  283 + // 如果没有指定最大日期,则往后推3个月
  284 + const maxDate =
  285 + this.innerMaxDate ||
  286 + dayjs(minDate)
  287 + .add(this.monthNum - 1, 'month')
  288 + .valueOf()
  289 + // 最大最小月份之间的共有多少个月份,
  290 + const months = range(
  291 + 1,
  292 + this.monthNum,
  293 + this.getMonths(minDate, maxDate)
  294 + )
  295 + // 先清空数组
  296 + this.months = []
  297 + for (let i = 0; i < months; i++) {
  298 + this.months.push({
  299 + date: new Array(
  300 + dayjs(minDate).add(i, 'month').daysInMonth()
  301 + )
  302 + .fill(1)
  303 + .map((item, index) => {
  304 + // 日期,取值1-31
  305 + let day = index + 1
  306 + // 星期,0-6,0为周日
  307 + const week = dayjs(minDate)
  308 + .add(i, 'month')
  309 + .date(day)
  310 + .day()
  311 + const date = dayjs(minDate)
  312 + .add(i, 'month')
  313 + .date(day)
  314 + .format('YYYY-MM-DD')
  315 + let bottomInfo = ''
  316 + if (this.showLunar) {
  317 + // 将日期转为农历格式
  318 + const lunar = Calendar.solar2lunar(
  319 + dayjs(date).year(),
  320 + dayjs(date).month() + 1,
  321 + dayjs(date).date()
  322 + )
  323 + bottomInfo = lunar.IDayCn
  324 + }
  325 + let config = {
  326 + day,
  327 + week,
  328 + // 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
  329 + disabled:
  330 + dayjs(date).isBefore(
  331 + dayjs(minDate).format('YYYY-MM-DD')
  332 + ) ||
  333 + dayjs(date).isAfter(
  334 + dayjs(maxDate).format('YYYY-MM-DD')
  335 + ),
  336 + // 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
  337 + date: new Date(date),
  338 + bottomInfo,
  339 + dot: false,
  340 + month:
  341 + dayjs(minDate).add(i, 'month').month() + 1
  342 + }
  343 + const formatter =
  344 + this.formatter || this.innerFormatter
  345 + return formatter(config)
  346 + }),
  347 + // 当前所属的月份
  348 + month: dayjs(minDate).add(i, 'month').month() + 1,
  349 + // 当前年份
  350 + year: dayjs(minDate).add(i, 'month').year()
  351 + })
  352 + }
  353 + },
  354 + // 滚动到默认设置的月份
  355 + scrollIntoDefaultMonth(selected) {
  356 + // 查询默认日期在可选列表的下标
  357 + const _index = this.months.findIndex(({
  358 + year,
  359 + month
  360 + }) => {
  361 + month = padZero(month)
  362 + return `${year}-${month}` === selected
  363 + })
  364 + if (_index !== -1) {
  365 + // #ifndef MP-WEIXIN
  366 + this.$nextTick(() => {
  367 + this.scrollIntoView = `month-${_index}`
  368 + this.scrollIntoViewScroll = this.scrollIntoView
  369 + })
  370 + // #endif
  371 + // #ifdef MP-WEIXIN
  372 + this.scrollTop = this.months[_index].top || 0;
  373 + // #endif
  374 + }
  375 + },
  376 + // scroll-view滚动监听
  377 + onScroll(event) {
  378 + // 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
  379 + const scrollTop = Math.max(0, event.detail.scrollTop)
  380 + // 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
  381 + for (let i = 0; i < this.months.length; i++) {
  382 + if (scrollTop >= (this.months[i].top || this.listHeight)) {
  383 + this.monthIndex = i
  384 + this.scrollIntoViewScroll = `month-${i}`
  385 + }
  386 + }
  387 + },
  388 + // 更新月份的top值
  389 + updateMonthTop(topArr = []) {
  390 + // 设置对应月份的top值,用于onScroll方法更新月份
  391 + topArr.map((item, index) => {
  392 + this.months[index].top = item
  393 + })
  394 +
  395 + // 获取默认日期的下标
  396 + if (!this.defaultDate) {
  397 + // 如果没有设置默认日期,则将当天日期设置为默认选中的日期
  398 + const selected = dayjs().format("YYYY-MM")
  399 + this.scrollIntoDefaultMonth(selected)
  400 + return
  401 + }
  402 + let selected = dayjs().format("YYYY-MM");
  403 + // 单选模式,可以是字符串或数组,Date对象等
  404 + if (!test.array(this.defaultDate)) {
  405 + selected = dayjs(this.defaultDate).format("YYYY-MM")
  406 + } else {
  407 + selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
  408 + }
  409 + this.scrollIntoDefaultMonth(selected)
  410 + }
  411 + }
  412 +}
  413 +</script>
  414 +
  415 +<style lang="scss" scoped>
  416 +.u-calendar {
  417 + &__confirm {
  418 + padding: 7px 18px;
  419 + }
  420 +}
  421 +</style>
... ...
uni_modules/uview-plus/components/u-calendar/util.js 0 → 100644
  1 +import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
  2 +export default {
  3 + methods: {
  4 + // 设置月份数据
  5 + setMonth() {
  6 + // 月初是周几
  7 + const day = dayjs(this.date).date(1).day()
  8 + const start = day == 0 ? 6 : day - 1
  9 +
  10 + // 本月天数
  11 + const days = dayjs(this.date).endOf('month').format('D')
  12 +
  13 + // 上个月天数
  14 + const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
  15 +
  16 + // 日期数据
  17 + const arr = []
  18 + // 清空表格
  19 + this.month = []
  20 +
  21 + // 添加上月数据
  22 + arr.push(
  23 + ...new Array(start).fill(1).map((e, i) => {
  24 + const day = prevDays - start + i + 1
  25 +
  26 + return {
  27 + value: day,
  28 + disabled: true,
  29 + date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
  30 + }
  31 + })
  32 + )
  33 +
  34 + // 添加本月数据
  35 + arr.push(
  36 + ...new Array(days - 0).fill(1).map((e, i) => {
  37 + const day = i + 1
  38 +
  39 + return {
  40 + value: day,
  41 + date: dayjs(this.date).date(day).format('YYYY-MM-DD')
  42 + }
  43 + })
  44 + )
  45 +
  46 + // 添加下个月
  47 + arr.push(
  48 + ...new Array(42 - days - start).fill(1).map((e, i) => {
  49 + const day = i + 1
  50 +
  51 + return {
  52 + value: day,
  53 + disabled: true,
  54 + date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
  55 + }
  56 + })
  57 + )
  58 +
  59 + // 分割数组
  60 + for (let n = 0; n < arr.length; n += 7) {
  61 + this.month.push(
  62 + arr.slice(n, n + 7).map((e, i) => {
  63 + e.index = i + n
  64 +
  65 + // 自定义信息
  66 + const custom = this.customList.find((c) => c.date == e.date)
  67 +
  68 + // 农历
  69 + if (this.lunar) {
  70 + const {
  71 + IDayCn,
  72 + IMonthCn
  73 + } = this.getLunar(e.date)
  74 + e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
  75 + }
  76 +
  77 + return {
  78 + ...e,
  79 + ...custom
  80 + }
  81 + })
  82 + )
  83 + }
  84 + }
  85 + }
  86 +}
... ...
uni_modules/uview-plus/components/u-car-keyboard/carKeyboard.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:53:20
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/carKeyboard.js
  9 + */
  10 +export default {
  11 + // 车牌号键盘
  12 + carKeyboard: {
  13 + random: false
  14 + }
  15 +}
... ...
uni_modules/uview-plus/components/u-car-keyboard/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +
  4 +export const props = defineMixin({
  5 + props: {
  6 + // 是否打乱键盘按键的顺序
  7 + random: {
  8 + type: Boolean,
  9 + default: false
  10 + },
  11 + // 输入一个中文后,是否自动切换到英文
  12 + autoChange: {
  13 + type: Boolean,
  14 + default: false
  15 + }
  16 + }
  17 +})
... ...
uni_modules/uview-plus/components/u-car-keyboard/u-car-keyboard.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-keyboard"
  4 + @touchmove.stop.prevent="noop"
  5 + >
  6 + <view
  7 + v-for="(group, i) in abc ? engKeyBoardList : areaList"
  8 + :key="i"
  9 + class="u-keyboard__button"
  10 + :index="i"
  11 + :class="[i + 1 === 4 && 'u-keyboard__button--center']"
  12 + >
  13 + <view
  14 + v-if="i === 3"
  15 + class="u-keyboard__button__inner-wrapper"
  16 + >
  17 + <view
  18 + class="u-keyboard__button__inner-wrapper__left"
  19 + hover-class="u-hover-class"
  20 + :hover-stay-time="200"
  21 + @tap="changeCarInputMode"
  22 + >
  23 + <text
  24 + class="u-keyboard__button__inner-wrapper__left__lang"
  25 + :class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
  26 + >中</text>
  27 + <text class="u-keyboard__button__inner-wrapper__left__line">/</text>
  28 + <text
  29 + class="u-keyboard__button__inner-wrapper__left__lang"
  30 + :class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
  31 + >英</text>
  32 + </view>
  33 + </view>
  34 + <view
  35 + class="u-keyboard__button__inner-wrapper"
  36 + v-for="(item, j) in group"
  37 + :key="j"
  38 + >
  39 + <view
  40 + class="u-keyboard__button__inner-wrapper__inner"
  41 + :hover-stay-time="200"
  42 + @tap="carInputClick(i, j)"
  43 + hover-class="u-hover-class"
  44 + >
  45 + <text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
  46 + </view>
  47 + </view>
  48 + <view
  49 + v-if="i === 3"
  50 + @touchstart="backspaceClick"
  51 + @touchend="clearTimer"
  52 + class="u-keyboard__button__inner-wrapper"
  53 + >
  54 + <view
  55 + class="u-keyboard__button__inner-wrapper__right"
  56 + hover-class="u-hover-class"
  57 + :hover-stay-time="200"
  58 + >
  59 + <up-icon
  60 + size="28"
  61 + name="backspace"
  62 + color="#303133"
  63 + ></up-icon>
  64 + </view>
  65 + </view>
  66 + </view>
  67 + </view>
  68 +</template>
  69 +
  70 +<script>
  71 + import { props } from './props';
  72 + import { mpMixin } from '../../libs/mixin/mpMixin';
  73 + import { mixin } from '../../libs/mixin/mixin';
  74 + import { randomArray, sleep } from '../../libs/function/index';
  75 + /**
  76 + * keyboard 键盘组件
  77 + * @description 此为uview-plus自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
  78 + * @tutorial https://uview-plus.jiangruyi.com/components/keyboard.html
  79 + * @property {Boolean} random 是否打乱键盘的顺序
  80 + * @event {Function} change 点击键盘触发
  81 + * @event {Function} backspace 点击退格键触发
  82 + * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
  83 + */
  84 + export default {
  85 + name: "u-car-keyboard",
  86 + mixins: [mpMixin, mixin, props],
  87 + data() {
  88 + return {
  89 + // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
  90 + abc: false
  91 + };
  92 + },
  93 + computed: {
  94 + areaList() {
  95 + let data = [
  96 + '京',
  97 + '沪',
  98 + '粤',
  99 + '津',
  100 + '冀',
  101 + '豫',
  102 + '云',
  103 + '辽',
  104 + '黑',
  105 + '湘',
  106 + '皖',
  107 + '鲁',
  108 + '苏',
  109 + '浙',
  110 + '赣',
  111 + '鄂',
  112 + '桂',
  113 + '甘',
  114 + '晋',
  115 + '陕',
  116 + '蒙',
  117 + '吉',
  118 + '闽',
  119 + '贵',
  120 + '渝',
  121 + '川',
  122 + '青',
  123 + '琼',
  124 + '宁',
  125 + '挂',
  126 + '藏',
  127 + '港',
  128 + '澳',
  129 + '新',
  130 + '使',
  131 + '学'
  132 + ];
  133 + let tmp = [];
  134 + // 打乱顺序
  135 + if (this.random) data = randomArray(data);
  136 + // 切割成二维数组
  137 + tmp[0] = data.slice(0, 10);
  138 + tmp[1] = data.slice(10, 20);
  139 + tmp[2] = data.slice(20, 30);
  140 + tmp[3] = data.slice(30, 36);
  141 + return tmp;
  142 + },
  143 + engKeyBoardList() {
  144 + let data = [
  145 + 1,
  146 + 2,
  147 + 3,
  148 + 4,
  149 + 5,
  150 + 6,
  151 + 7,
  152 + 8,
  153 + 9,
  154 + 0,
  155 + 'Q',
  156 + 'W',
  157 + 'E',
  158 + 'R',
  159 + 'T',
  160 + 'Y',
  161 + 'U',
  162 + 'I',
  163 + 'O',
  164 + 'P',
  165 + 'A',
  166 + 'S',
  167 + 'D',
  168 + 'F',
  169 + 'G',
  170 + 'H',
  171 + 'J',
  172 + 'K',
  173 + 'L',
  174 + 'Z',
  175 + 'X',
  176 + 'C',
  177 + 'V',
  178 + 'B',
  179 + 'N',
  180 + 'M'
  181 + ];
  182 + let tmp = [];
  183 + if (this.random) data = randomArray(data);
  184 + tmp[0] = data.slice(0, 10);
  185 + tmp[1] = data.slice(10, 20);
  186 + tmp[2] = data.slice(20, 30);
  187 + tmp[3] = data.slice(30, 36);
  188 + return tmp;
  189 + }
  190 + },
  191 + emits: ["change", "backspace"],
  192 + methods: {
  193 + // 点击键盘按钮
  194 + carInputClick(i, j) {
  195 + let value = '';
  196 + // 不同模式,获取不同数组的值
  197 + if (this.abc) value = this.engKeyBoardList[i][j];
  198 + else value = this.areaList[i][j];
  199 + // 如果允许自动切换,则将中文状态切换为英文
  200 + if (!this.abc && this.autoChange) sleep(200).then(() => this.abc = true)
  201 + this.$emit('change', value);
  202 + },
  203 + // 修改汽车牌键盘的输入模式,中文|英文
  204 + changeCarInputMode() {
  205 + this.abc = !this.abc;
  206 + },
  207 + // 点击退格键
  208 + backspaceClick() {
  209 + this.$emit('backspace');
  210 + clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
  211 + this.timer = null;
  212 + this.timer = setInterval(() => {
  213 + this.$emit('backspace');
  214 + }, 250);
  215 + },
  216 + clearTimer() {
  217 + clearInterval(this.timer);
  218 + this.timer = null;
  219 + },
  220 + }
  221 + };
  222 +</script>
  223 +
  224 +<style lang="scss" scoped>
  225 + $u-car-keyboard-background-color: rgb(224, 228, 230) !default;
  226 + $u-car-keyboard-padding:6px 0 6px !default;
  227 + $u-car-keyboard-button-inner-width:64rpx !default;
  228 + $u-car-keyboard-button-inner-background-color:#FFFFFF !default;
  229 + $u-car-keyboard-button-height:80rpx !default;
  230 + $u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
  231 + $u-car-keyboard-button-border-radius:4px !default;
  232 + $u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
  233 + $u-car-keyboard-button-text-font-size:16px !default;
  234 + $u-car-keyboard-button-text-color:$u-main-color !default;
  235 + $u-car-keyboard-center-inner-margin: 0 4rpx !default;
  236 + $u-car-keyboard-special-button-width:134rpx !default;
  237 + $u-car-keyboard-lang-font-size:16px !default;
  238 + $u-car-keyboard-lang-color:$u-main-color !default;
  239 + $u-car-keyboard-active-color:$u-primary !default;
  240 + $u-car-keyboard-line-font-size:15px !default;
  241 + $u-car-keyboard-line-color:$u-main-color !default;
  242 + $u-car-keyboard-line-margin:0 1px !default;
  243 + $u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
  244 +
  245 + .u-keyboard {
  246 + @include flex(column);
  247 + justify-content: space-around;
  248 + background-color: $u-car-keyboard-background-color;
  249 + align-items: stretch;
  250 + padding: $u-car-keyboard-padding;
  251 +
  252 + &__button {
  253 + @include flex;
  254 + justify-content: center;
  255 + flex: 1;
  256 + /* #ifndef APP-NVUE */
  257 + /* #endif */
  258 +
  259 + &__inner-wrapper {
  260 + box-shadow: $u-car-keyboard-button-inner-box-shadow;
  261 + margin: $u-car-keyboard-button-inner-margin;
  262 + border-radius: $u-car-keyboard-button-border-radius;
  263 +
  264 + &__inner {
  265 + @include flex;
  266 + justify-content: center;
  267 + align-items: center;
  268 + width: $u-car-keyboard-button-inner-width;
  269 + background-color: $u-car-keyboard-button-inner-background-color;
  270 + height: $u-car-keyboard-button-height;
  271 + border-radius: $u-car-keyboard-button-border-radius;
  272 +
  273 + &__text {
  274 + font-size: $u-car-keyboard-button-text-font-size;
  275 + color: $u-car-keyboard-button-text-color;
  276 + }
  277 + }
  278 +
  279 + &__left,
  280 + &__right {
  281 + border-radius: $u-car-keyboard-button-border-radius;
  282 + width: $u-car-keyboard-special-button-width;
  283 + height: $u-car-keyboard-button-height;
  284 + background-color: $u-car-keyboard-u-hover-class-background-color;
  285 + @include flex;
  286 + justify-content: center;
  287 + align-items: center;
  288 + box-shadow: $u-car-keyboard-button-inner-box-shadow;
  289 + }
  290 +
  291 + &__left {
  292 + &__line {
  293 + font-size: $u-car-keyboard-line-font-size;
  294 + color: $u-car-keyboard-line-color;
  295 + margin: $u-car-keyboard-line-margin;
  296 + }
  297 +
  298 + &__lang {
  299 + font-size: $u-car-keyboard-lang-font-size;
  300 + color: $u-car-keyboard-lang-color;
  301 +
  302 + &--active {
  303 + color: $u-car-keyboard-active-color;
  304 + }
  305 + }
  306 + }
  307 + }
  308 + }
  309 + }
  310 +
  311 + .u-hover-class {
  312 + background-color: $u-car-keyboard-u-hover-class-background-color;
  313 + }
  314 +</style>
... ...
uni_modules/uview-plus/components/u-card/card.js 0 → 100644
  1 +/*
  2 + * @Author : jry
  3 + * @Description :
  4 + * @version : 3.0
  5 + * @Date : 2025-04-26 16:37:21
  6 + * @LastAuthor : jry
  7 + * @lastTime : 2025-04-26 16:37:21
  8 + * @FilePath : /uview-plus/libs/config/props/card.js
  9 + */
  10 +export default {
  11 + // card组件的props
  12 + card: {
  13 + full: false,
  14 + title: '',
  15 + titleColor: '#303133',
  16 + titleSize: '15px',
  17 + subTitle: '',
  18 + subTitleColor: '#909399',
  19 + subTitleSize: '13px',
  20 + border: true,
  21 + index: '',
  22 + margin: '15px',
  23 + borderRadius: '8px',
  24 + headStyle: {},
  25 + bodyStyle: {},
  26 + footStyle: {},
  27 + headBorderBottom: true,
  28 + footBorderTop: true,
  29 + thumb: '',
  30 + thumbWidth: '30px',
  31 + thumbCircle: false,
  32 + padding: '15px',
  33 + paddingHead: '',
  34 + paddingBody: '',
  35 + paddingFoot: '',
  36 + showHead: true,
  37 + showFoot: true,
  38 + boxShadow: 'none'
  39 + }
  40 +}
... ...
uni_modules/uview-plus/components/u-card/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +
  4 +export const propsCard = defineMixin({
  5 + props: {
  6 + // 与屏幕两侧是否留空隙
  7 + full: {
  8 + type: Boolean,
  9 + default: () => defProps.card.full
  10 + },
  11 + // 标题
  12 + title: {
  13 + type: String,
  14 + default: () => defProps.card.title
  15 + },
  16 + // 标题颜色
  17 + titleColor: {
  18 + type: String,
  19 + default: () => defProps.card.titleColor
  20 + },
  21 + // 标题字体大小
  22 + titleSize: {
  23 + type: [Number, String],
  24 + default: () => defProps.card.titleSize
  25 + },
  26 + // 副标题
  27 + subTitle: {
  28 + type: String,
  29 + default: () => defProps.card.subTitle
  30 + },
  31 + // 副标题颜色
  32 + subTitleColor: {
  33 + type: String,
  34 + default: () => defProps.card.subTitleColor
  35 + },
  36 + // 副标题字体大小
  37 + subTitleSize: {
  38 + type: [Number, String],
  39 + default: () => defProps.card.subTitleSize
  40 + },
  41 + // 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
  42 + border: {
  43 + type: Boolean,
  44 + default: () => defProps.card.border
  45 + },
  46 + // 用于标识点击了第几个
  47 + index: {
  48 + type: [Number, String, Object],
  49 + default: () => defProps.card.index
  50 + },
  51 + // 用于隔开上下左右的边距,带单位的写法,如:"30px 30px","20px 20px 30px 30px"
  52 + margin: {
  53 + type: String,
  54 + default: () => defProps.card.margin
  55 + },
  56 + // card卡片的圆角
  57 + borderRadius: {
  58 + type: [Number, String],
  59 + default: () => defProps.card.borderRadius
  60 + },
  61 + // 头部自定义样式,对象形式
  62 + headStyle: {
  63 + type: Object,
  64 + default: () => defProps.card.headStyle
  65 + },
  66 + // 主体自定义样式,对象形式
  67 + bodyStyle: {
  68 + type: Object,
  69 + default: () => defProps.card.bodyStyle
  70 + },
  71 + // 底部自定义样式,对象形式
  72 + footStyle: {
  73 + type: Object,
  74 + default: () => defProps.card.footStyle
  75 + },
  76 + // 头部是否下边框
  77 + headBorderBottom: {
  78 + type: Boolean,
  79 + default: () => defProps.card.headBorderBottom
  80 + },
  81 + // 底部是否有上边框
  82 + footBorderTop: {
  83 + type: Boolean,
  84 + default: () => defProps.card.footBorderTop
  85 + },
  86 + // 标题左边的缩略图
  87 + thumb: {
  88 + type: String,
  89 + default: () => defProps.card.thumb
  90 + },
  91 + // 缩略图宽高
  92 + thumbWidth: {
  93 + type: [String, Number],
  94 + default: () => defProps.card.thumbWidth
  95 + },
  96 + // 缩略图是否为圆形
  97 + thumbCircle: {
  98 + type: Boolean,
  99 + default: () => defProps.card.thumbCircle
  100 + },
  101 + // 给head,body,foot的内边距
  102 + padding: {
  103 + type: [String, Number],
  104 + default: () => defProps.card.padding
  105 + },
  106 + paddingHead: {
  107 + type: [String, Number],
  108 + default: () => defProps.card.paddingHead
  109 + },
  110 + paddingBody: {
  111 + type: [String, Number],
  112 + default: () => defProps.card.paddingBody
  113 + },
  114 + paddingFoot: {
  115 + type: [String, Number],
  116 + default: () => defProps.card.paddingFoot
  117 + },
  118 + // 是否显示头部
  119 + showHead: {
  120 + type: Boolean,
  121 + default: () => defProps.card.showHead
  122 + },
  123 + // 是否显示尾部
  124 + showFoot: {
  125 + type: Boolean,
  126 + default: () => defProps.card.showFoot
  127 + },
  128 + // 卡片外围阴影,字符串形式
  129 + boxShadow: {
  130 + type: String,
  131 + default: () => defProps.card.boxShadow
  132 + }
  133 + }
  134 +})
... ...
uni_modules/uview-plus/components/u-card/u-card.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-card"
  4 + @tap.stop="click"
  5 + :class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': getPx(borderRadius) > 0 }"
  6 + :style="{
  7 + borderRadius: addUnit(borderRadius),
  8 + margin: margin,
  9 + boxShadow: boxShadow
  10 + }"
  11 + >
  12 + <view
  13 + v-if="showHead"
  14 + class="u-card__head"
  15 + :style="[{padding: addUnit(paddingHead || padding)}, headStyle]"
  16 + :class="{
  17 + 'u-border-bottom': headBorderBottom
  18 + }"
  19 + @tap="headClick"
  20 + >
  21 + <view v-if="!$slots.head" class="u-flex u-flex-between">
  22 + <view class="u-card__head--left u-flex u-line-1" v-if="title">
  23 + <image
  24 + :src="thumb"
  25 + class="u-card__head--left__thumb"
  26 + mode="aspectFill"
  27 + v-if="thumb"
  28 + :style="{
  29 + height: addUnit(thumbWidth),
  30 + width: addUnit(thumbWidth),
  31 + borderRadius: thumbCircle ? '50px' : '4px'
  32 + }"
  33 + ></image>
  34 + <text
  35 + class="u-card__head--left__title u-line-1"
  36 + :style="{
  37 + fontSize: addUnit(titleSize),
  38 + color: titleColor
  39 + }"
  40 + >
  41 + {{ title }}
  42 + </text>
  43 + </view>
  44 + <view class="u-card__head--right u-line-1" v-if="subTitle">
  45 + <text
  46 + class="u-card__head__title__text"
  47 + :style="{
  48 + fontSize: addUnit(subTitleSize),
  49 + color: subTitleColor
  50 + }"
  51 + >
  52 + {{ subTitle }}
  53 + </text>
  54 + </view>
  55 + </view>
  56 + <slot name="head" v-else />
  57 + </view>
  58 + <view @tap="bodyClick" class="u-card__body"
  59 + :style="[{padding: addUnit(paddingBody || padding)}, bodyStyle]"><slot name="body" /></view>
  60 + <view
  61 + v-if="showFoot"
  62 + class="u-card__foot"
  63 + @tap="footClick"
  64 + :style="[{padding: $slots.foot ? addUnit(paddingFoot || padding) : 0}, footStyle]"
  65 + :class="{
  66 + 'u-border-top': footBorderTop
  67 + }"
  68 + >
  69 + <slot name="foot" />
  70 + </view>
  71 + </view>
  72 +</template>
  73 +
  74 +<script>
  75 + import { propsCard } from './props';
  76 + import { mpMixin } from '../../libs/mixin/mpMixin';
  77 + import { mixin } from '../../libs/mixin/mixin';
  78 + import { addStyle, addUnit, getPx } from '../../libs/function/index';
  79 + /**
  80 + * card 卡片
  81 + * @description 卡片组件一般用于多个列表条目,且风格统一的场景
  82 + * @tutorial https://uview-plus.jiangruyi.com/components/card.html
  83 + * @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false)
  84 + * @property {String} title 头部左边的标题
  85 + * @property {String} title-color 标题颜色(默认#303133)
  86 + * @property {String | Number} title-size 标题字体大小,单位rpx(默认15px)
  87 + * @property {String} sub-title 头部右边的副标题
  88 + * @property {String} sub-title-color 副标题颜色(默认#909399)
  89 + * @property {String | Number} sub-title-size 副标题字体大小(默认13px
  90 + * @property {Boolean} border 是否显示边框(默认true)
  91 + * @property {String | Number} index 用于标识点击了第几个卡片
  92 + * @property {String} box-shadow 卡片外围阴影,字符串形式(默认none)
  93 + * @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30px 20px"(默认15px)
  94 + * @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认8px)
  95 + * @property {Object} head-style 头部自定义样式,对象形式
  96 + * @property {Object} body-style 中部自定义样式,对象形式
  97 + * @property {Object} foot-style 底部自定义样式,对象形式
  98 + * @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true)
  99 + * @property {Boolean} foot-border-top 是否显示底部的上边框(默认true)
  100 + * @property {Boolean} show-head 是否显示头部(默认true)
  101 + * @property {Boolean} show-foot 是否显示尾部(默认true)
  102 + * @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径
  103 + * @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位px(默认30px)
  104 + * @property {Boolean} thumb-circle 缩略图是否为圆形(默认false)
  105 + * @event {Function} click 整个卡片任意位置被点击时触发
  106 + * @event {Function} head-click 卡片头部被点击时触发
  107 + * @event {Function} body-click 卡片主体部分被点击时触发
  108 + * @event {Function} foot-click 卡片底部部分被点击时触发
  109 + * @example <u-card paddingFoot="2px 15px" title="card"></u-card>
  110 + */
  111 + export default {
  112 + name: 'up-card',
  113 + data() {
  114 + return {};
  115 + },
  116 + mixins: [mpMixin, mixin, propsCard],
  117 + emits: ['click', 'head-click', 'body-click', 'foot-click'],
  118 + methods: {
  119 + addStyle,
  120 + addUnit,
  121 + getPx,
  122 + click() {
  123 + this.$emit('click', this.index);
  124 + },
  125 + headClick() {
  126 + this.$emit('head-click', this.index);
  127 + },
  128 + bodyClick() {
  129 + this.$emit('body-click', this.index);
  130 + },
  131 + footClick() {
  132 + this.$emit('foot-click', this.index);
  133 + }
  134 + }
  135 + };
  136 +</script>
  137 +
  138 +<style lang="scss" scoped>
  139 +.u-card {
  140 + position: relative;
  141 + overflow: hidden;
  142 + font-size: 28rpx;
  143 + background-color: #ffffff;
  144 + box-sizing: border-box;
  145 +
  146 + &-full {
  147 + // 如果是与屏幕之间不留空隙,应该设置左右边距为0
  148 + margin-left: 0 !important;
  149 + margin-right: 0 !important;
  150 + width: 100%;
  151 + }
  152 +
  153 + &--border:after {
  154 + border-radius: 16rpx;
  155 + }
  156 +
  157 + &__head {
  158 + &--left {
  159 + color: $u-main-color;
  160 +
  161 + &__thumb {
  162 + margin-right: 16rpx;
  163 + }
  164 +
  165 + &__title {
  166 + max-width: 400rpx;
  167 + }
  168 + }
  169 +
  170 + &--right {
  171 + color: $u-tips-color;
  172 + margin-left: 6rpx;
  173 + }
  174 + }
  175 +
  176 + &__body {
  177 + color: $u-content-color;
  178 + }
  179 +
  180 + &__foot {
  181 + color: $u-tips-color;
  182 + }
  183 +}
  184 +</style>
... ...
uni_modules/uview-plus/components/u-cascader/u-cascader.vue 0 → 100644
  1 +<template>
  2 + <up-popup :show="popupShow" mode="bottom" :popup="false"
  3 + :mask="true" :closeable="true" :safe-area-inset-bottom="true"
  4 + close-icon-color="#ffffff" :z-index="uZIndex"
  5 + :maskCloseAble="maskCloseAble" @close="close">
  6 + <view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection =='column'">
  7 + <up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex">
  8 + <up-steps-item v-for="(item, index) in genTabsList"
  9 + @click="tabsIndex = index" :title="item.name"></up-steps-item>
  10 + </up-steps>
  11 + </view>
  12 + <view class="up-p-t-20 up-m-b-10" v-else>
  13 + <up-tabs v-if="popupShow" :list="genTabsList"
  14 + :scrollable="true" v-model:current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs>
  15 + </view>
  16 + <view class="area-box">
  17 + <view class="u-flex" :class="{ 'change':isChange }"
  18 + :style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}">
  19 + <template v-for="(levelData, levelIndex) in levelList" :key="levelIndex">
  20 + <view v-if="optionsCols == 2 || levelIndex == tabsIndex" class="area-item"
  21 + :style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}">
  22 + <view class="u-padding-10 u-bg-gray" style="height: 100%;">
  23 + <scroll-view :scroll-y="true" style="height: 100%">
  24 + <up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined">
  25 + <up-cell v-for="(item,index) in levelData"
  26 + :title="item[labelKey]" :arrow="false"
  27 + :index="index" :key="index"
  28 + @click="levelChange(levelIndex, index)">
  29 + <template v-slot:right-icon>
  30 + <up-icon v-if="selectedValueIndexs[levelIndex] === index"
  31 + size="17" name="checkbox-mark"></up-icon>
  32 + </template>
  33 + </up-cell>
  34 + </up-cell-group>
  35 + </scroll-view>
  36 + </view>
  37 + </view>
  38 + </template>
  39 + </view>
  40 + </view>
  41 + <!-- 添加按钮区域 -->
  42 + <view class="u-cascader-action up-flex up-flex-between">
  43 + <view class="u-padding-20 up-flex-fill">
  44 + <up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button>
  45 + </view>
  46 + <view class="u-padding-20 up-flex-fill">
  47 + <up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button>
  48 + </view>
  49 + </view>
  50 + </up-popup>
  51 +</template>
  52 +
  53 +<script>
  54 + /**
  55 + * u-cascader 通用无限级联选择器
  56 + * @property {String Number} z-index 弹出时的z-index值(默认1075)
  57 + * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
  58 + * @property {Array} data 级联数据
  59 + * @property {Array} default-value 默认选中的值
  60 + * @property {String} valueKey 指定选项的值为选项对象中的哪个属性值
  61 + * @property {String} labelKey 指定选项标签为选项对象中的哪个属性值
  62 + * @property {String} childrenKey 指定选项的子选项为选项对象中的哪个属性值
  63 + * @property {Boolean} autoClose 是否在选择最后一级时自动关闭并触发confirm(默认false)
  64 + */
  65 + import { t } from '../../libs/i18n'
  66 + export default {
  67 + name: 'up-cascader',
  68 + props: {
  69 + // 通过双向绑定控制组件的弹出与收起
  70 + show: {
  71 + type: Boolean,
  72 + default: false
  73 + },
  74 + // 级联数据
  75 + data: {
  76 + type: Array,
  77 + default() {
  78 + return [];
  79 + }
  80 + },
  81 + // 默认选中的值
  82 + modelValue: {
  83 + type: Array,
  84 + default() {
  85 + return [];
  86 + }
  87 + },
  88 + // 指定选项的值为选项对象中的哪个属性值
  89 + valueKey: {
  90 + type: String,
  91 + default: 'value'
  92 + },
  93 + // 指定选项标签为选项对象中的哪个属性值
  94 + labelKey: {
  95 + type: String,
  96 + default: 'label'
  97 + },
  98 + // 指定选项的子选项为选项对象中的哪个属性值
  99 + childrenKey: {
  100 + type: String,
  101 + default: 'children'
  102 + },
  103 + // 是否允许通过点击遮罩关闭Picker
  104 + maskCloseAble: {
  105 + type: Boolean,
  106 + default: true
  107 + },
  108 + // 弹出的z-index值
  109 + zIndex: {
  110 + type: [String, Number],
  111 + default: 0
  112 + },
  113 + // 是否在选择最后一级时自动关闭并触发confirm
  114 + autoClose: {
  115 + type: Boolean,
  116 + default: false
  117 + },
  118 + // 选中项目的展示方向direction垂直方向适合文字长度过长
  119 + headerDirection: {
  120 + type: String,
  121 + default: 'row'
  122 + },
  123 + // 选项区域列数,支持1列和2列,默认为2列
  124 + optionsCols: {
  125 + type: [Number],
  126 + default: 2
  127 + }
  128 + },
  129 + data() {
  130 + return {
  131 + // 存储每一级的数据
  132 + levelList: [],
  133 + // 存储每一级选中的索引
  134 + selectedValueIndexs: [],
  135 + tabsIndex: 0,
  136 + popupShow: false,
  137 + // 新增confirmValues用于存储确认的值
  138 + confirmValues: []
  139 + }
  140 + },
  141 + watch: {
  142 + data: {
  143 + handler() {
  144 + this.initLevelList();
  145 + },
  146 + immediate: true
  147 + },
  148 + show() {
  149 + this.popupShow = this.show;
  150 + },
  151 + modelValue: {
  152 + handler() {
  153 + this.init();
  154 + },
  155 + immediate: true
  156 + }
  157 + },
  158 + computed: {
  159 + isChange() {
  160 + return this.tabsIndex > 1;
  161 + },
  162 + genTabsList() {
  163 + let tabsList = [{
  164 + name: "请选择"
  165 + }];
  166 +
  167 + // 根据选中的值动态生成tabs
  168 + for (let i = 0; i < this.selectedValueIndexs.length; i++) {
  169 + if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
  170 + const selectedItem = this.levelList[i][this.selectedValueIndexs[i]];
  171 + if (selectedItem) {
  172 + tabsList[i] = {
  173 + name: selectedItem[this.labelKey]
  174 + };
  175 + // 如果还有下一级,则添加"请选择"
  176 + if (i === this.selectedValueIndexs.length - 1 &&
  177 + selectedItem[this.childrenKey] &&
  178 + selectedItem[this.childrenKey].length > 0) {
  179 + tabsList.push({
  180 + name: "请选择"
  181 + });
  182 + }
  183 + }
  184 + }
  185 + }
  186 +
  187 + return tabsList;
  188 + },
  189 + uZIndex() {
  190 + // 如果用户有传递z-index值,优先使用
  191 + return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
  192 + }
  193 + },
  194 + // 新增confirm事件
  195 + emits: ['update:modelValue', 'change', 'confirm'],
  196 + methods: {
  197 + t,
  198 + init() {
  199 + // 初始化选中值
  200 + if (this.modelValue && this.modelValue.length > 0) {
  201 + this.setDefaultValue();
  202 + }
  203 + },
  204 + initLevelList() {
  205 + // 初始化第一级数据
  206 + if (this.data && this.data.length > 0) {
  207 + this.levelList = [this.data];
  208 + this.selectedValueIndexs = [];
  209 + }
  210 + },
  211 + setDefaultValue() {
  212 + // 根据默认值设置选中项
  213 + // 根据modelValue获取indexs给selectedValueIndexs
  214 + this.selectedValueIndexs = [];
  215 + let currentLevelData = this.data;
  216 +
  217 + for (let i = 0; i < this.modelValue.length; i++) {
  218 + const value = this.modelValue[i];
  219 + const index = currentLevelData.findIndex(item => item[this.valueKey] === value);
  220 +
  221 + if (index !== -1) {
  222 + this.selectedValueIndexs.push(index);
  223 + // 更新下一级的数据
  224 + if (currentLevelData[index][this.childrenKey]) {
  225 + currentLevelData = currentLevelData[index][this.childrenKey];
  226 + } else {
  227 + // 如果没有子级数据,则停止处理
  228 + break;
  229 + }
  230 + } else {
  231 + // 如果找不到匹配项,则停止处理
  232 + break;
  233 + }
  234 + }
  235 + },
  236 + close() {
  237 + this.$emit('update:show', false);
  238 + },
  239 + tabsChange(item) {
  240 + },
  241 + levelChange(levelIndex, index) {
  242 + // 设置当前级的选中值
  243 + this.$set(this.selectedValueIndexs, levelIndex, index);
  244 +
  245 + // 清除后续级别的选中值
  246 + for (let i = levelIndex + 1; i < this.selectedValueIndexs.length; i++) {
  247 + this.$set(this.selectedValueIndexs, i, undefined);
  248 + }
  249 +
  250 + // 获取当前选中项
  251 + const currentItem = this.levelList[levelIndex][index];
  252 +
  253 + // 如果有子级数据,则初始化下一级
  254 + if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) {
  255 + // 确保levelList数组足够长
  256 + if (this.levelList.length <= levelIndex + 1) {
  257 + this.levelList.push(currentItem[this.childrenKey]);
  258 + } else {
  259 + this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]);
  260 + }
  261 + // 切换到下一级tab
  262 + this.tabsIndex = levelIndex + 1;
  263 + } else {
  264 + // 没有子级数据,说明是最后一级
  265 + if (this.autoClose) {
  266 + // 如果启用自动关闭,则触发change事件并关闭
  267 + this.emitChange();
  268 + } else {
  269 + // 否则只触发change事件,不关闭
  270 + this.emitChange(false);
  271 + }
  272 + }
  273 + },
  274 + // 修改emitChange方法,增加closePopup参数
  275 + emitChange(closePopup = true) {
  276 + // 构造选中结果
  277 + const result = [];
  278 + for (let i = 0; i < this.selectedValueIndexs.length; i++) {
  279 + if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
  280 + result.push(this.levelList[i][this.selectedValueIndexs[i]][this.valueKey]);
  281 + }
  282 + }
  283 +
  284 + // 更新confirmValues
  285 + this.confirmValues = [...result];
  286 +
  287 + // 触发change事件,返回value数组
  288 + this.$emit('change', this.confirmValues);
  289 +
  290 + // 根据参数决定是否关闭弹窗
  291 + if (closePopup) {
  292 + this.close();
  293 + }
  294 + },
  295 + handleCancel() {
  296 + this.close();
  297 + },
  298 + handleConfirm() {
  299 + // 确认时触发confirm事件
  300 + this.$emit('update:modelValue', this.confirmValues);
  301 + this.$emit('confirm', this.confirmValues);
  302 + this.close();
  303 + }
  304 + }
  305 + }
  306 +</script>
  307 +<style lang="scss">
  308 + .area-box {
  309 + width: 100%;
  310 + overflow: hidden;
  311 + height: 800rpx;
  312 +
  313 + >view {
  314 + width: 150%;
  315 + transition: transform 0.3s ease-in-out 0s;
  316 + transform: translateX(0);
  317 +
  318 + &.change {
  319 + // transform: translateX(-33.3333333%);
  320 + }
  321 + }
  322 +
  323 + .area-item {
  324 + // width: 750rpx;
  325 + height: 800rpx;
  326 + }
  327 + }
  328 +
  329 + // 添加按钮区域样式
  330 + .u-cascader-action {
  331 + border-top: 1px solid #eee;
  332 + }
  333 +</style>
0 334 \ No newline at end of file
... ...
uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue 0 → 100644
  1 +<template>
  2 + <view class="u-cate-tab" :style="{ height: addUnit(height) }">
  3 + <view class="u-cate-tab__wrap">
  4 + <scroll-view class="u-cate-tab__view u-cate-tab__menu-scroll-view"
  5 + scroll-y scroll-with-animation :scroll-top="scrollTop"
  6 + :scroll-into-view="itemId">
  7 + <view v-for="(item, index) in tabList" :key="index" class="u-cate-tab__item"
  8 + :class="[innerCurrent == index ? 'u-cate-tab__item-active' : '']"
  9 + @tap.stop="swichMenu(index)">
  10 + <slot name="tabItem" :item="item">
  11 + </slot>
  12 + <text v-if="!$slots['tabItem']" class="u-line-1">{{item[tabKeyName]}}</text>
  13 + </view>
  14 + </scroll-view>
  15 + <scroll-view :scroll-top="scrollRightTop" scroll-with-animation :scroll-into-view="scrollIntoView"
  16 + scroll-y class="u-cate-tab__right-box" @scroll="rightScroll">
  17 + <view class="u-cate-tab__right-top">
  18 + <slot name="rightTop" :tabList="tabList">
  19 + </slot>
  20 + </view>
  21 + <view class="u-cate-tab__page-view">
  22 + <template :key="index" v-for="(item , index) in tabList">
  23 + <view v-if="mode == 'follow' || ( mode == 'tab' && index == innerCurrent)"
  24 + class="u-cate-tab__page-item" :id="'item' + index">
  25 + <slot name="itemList" :item="item">
  26 + </slot>
  27 + <template v-if="!$slots['itemList']">
  28 + <view class="item-title">
  29 + <text>{{item[tabKeyName]}}</text>
  30 + </view>
  31 + <view class="item-container">
  32 + <template v-for="(item1, index1) in item.children" :key="index1">
  33 + <slot name="pageItem" :pageItem="item1">
  34 + <view class="thumb-box" >
  35 + <image class="item-menu-image" :src="item1.icon" mode=""></image>
  36 + <view class="item-menu-name">{{item1[itemKeyName]}}</view>
  37 + </view>
  38 + </slot>
  39 + </template>
  40 + </view>
  41 + </template>
  42 + </view>
  43 + </template>
  44 + </view>
  45 + </scroll-view>
  46 + </view>
  47 + </view>
  48 +</template>
  49 +<script>
  50 + import { addUnit, sleep } from '../../libs/function/index';
  51 + export default {
  52 + name: 'up-cate-tab',
  53 + props: {
  54 + mode: {
  55 + type: String,
  56 + default: 'follow' // follo跟随联动, tab单一显示。
  57 + },
  58 + height: {
  59 + type: String,
  60 + default: '100%'
  61 + },
  62 + tabList: {
  63 + type: Array,
  64 + default: () => {
  65 + return []
  66 + }
  67 + },
  68 + tabKeyName: {
  69 + type: String,
  70 + default: 'name'
  71 + },
  72 + itemKeyName: {
  73 + type: String,
  74 + default: 'name'
  75 + },
  76 + current: {
  77 + type: Number,
  78 + default: 0
  79 + }
  80 + },
  81 + watch: {
  82 + tabList: {
  83 + deep: true,
  84 + handler(newVal, oldVal) {
  85 + // this.observer();
  86 + sleep(30);
  87 + this.getMenuItemTop();
  88 + this.leftMenuStatus(this.innerCurrent);
  89 + }
  90 + },
  91 + current(nval) {
  92 + this.innerCurrent = nval;
  93 + this.leftMenuStatus(this.innerCurrent);
  94 + },
  95 + height() {
  96 + // console.log('height change');
  97 + this.getMenuItemTop();
  98 + this.leftMenuStatus(this.innerCurrent);
  99 + }
  100 + },
  101 + emits: ['update:current'],
  102 + data() {
  103 + return {
  104 + scrollTop: 0, //tab标题的滚动条位置
  105 + scrollIntoView: '', // 滚动至哪个元素
  106 + oldScrollTop: 0,
  107 + innerCurrent: 0, // 预设当前项的值
  108 + menuHeight: 0, // 左边菜单的高度
  109 + menuItemHeight: 0, // 左边菜单item的高度
  110 + itemId: '', // 栏目右边scroll-view用于滚动的id
  111 + menuItemPos: [],
  112 + rects: [],
  113 + arr: [],
  114 + scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
  115 + timer: null, // 定时器
  116 + }
  117 + },
  118 + mounted() {
  119 + // this.observer();
  120 + this.innerCurrent = this.current;
  121 + this.leftMenuStatus(this.innerCurrent);
  122 + this.getMenuItemTop()
  123 + },
  124 + methods: {
  125 + addUnit,
  126 + // 点击左边的栏目切换
  127 + async swichMenu(index) {
  128 + if (this.mode == 'follow') {
  129 + if(this.arr.length == 0) {
  130 + await this.getMenuItemTop();
  131 + }
  132 + this.scrollIntoView = 'item' + index;
  133 + }
  134 +
  135 + if (index == this.innerCurrent) return;
  136 + this.$nextTick(function(){
  137 + this.innerCurrent = index;
  138 + this.$emit('update:current', index);
  139 + })
  140 + },
  141 + // 获取一个目标元素的高度
  142 + getElRect(elClass, dataVal) {
  143 + return new Promise((resolve, reject) => {
  144 + const query = uni.createSelectorQuery().in(this);
  145 + query.select('.' + elClass).fields({
  146 + size: true
  147 + }, res => {
  148 + // 如果节点尚未生成,res值为null,循环调用执行
  149 + if (!res) {
  150 + setTimeout(() => {
  151 + this.getElRect(elClass);
  152 + }, 10);
  153 + return;
  154 + }
  155 + this[dataVal] = res.height;
  156 + resolve();
  157 + }).exec();
  158 + })
  159 + },
  160 + // 观测元素相交状态
  161 + async observer() {
  162 + await this.$nextTick();
  163 + // 清除之前的观察器
  164 + if (this._observerList) {
  165 + this._observerList.forEach(observer => {
  166 + observer.disconnect();
  167 + });
  168 + }
  169 + this._observerList = [];
  170 +
  171 + this.tabList.map((val, index) => {
  172 + let observer = uni.createIntersectionObserver(this);
  173 + this._observerList.push(observer);
  174 + // 检测相交状态
  175 + observer.relativeTo('.u-cate-tab__right-box', {
  176 + top: 10
  177 + }).observe('#item' + index, (res) => {
  178 + if (res.intersectionRatio > 0) {
  179 + console.log('res', res);
  180 + // 修复:确保正确获取索引
  181 + let id = res.id ? res.id.substring(4) : index;
  182 + this.leftMenuStatus(parseInt(id));
  183 + }
  184 + })
  185 + })
  186 + },
  187 + // 设置左边菜单的滚动状态
  188 + async leftMenuStatus(index) {
  189 + this.innerCurrent = index;
  190 + this.$emit('update:current', index);
  191 + // 如果为0,意味着尚未初始化
  192 + if (this.menuHeight == 0 || this.menuItemHeight == 0) {
  193 + await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
  194 + await this.getElRect('u-cate-tab__item', 'menuItemHeight');
  195 + }
  196 + // console.log(this.menuHeight, this.menuItemHeight)
  197 + // 将菜单活动item垂直居中
  198 + this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
  199 + },
  200 + // 获取右边菜单每个item到顶部的距离
  201 + async getMenuItemTop() {
  202 + // await this.$nextTick();
  203 + // console.log('getMenuItemTop')
  204 + return new Promise(resolve => {
  205 + let selectorQuery = uni.createSelectorQuery().in(this);
  206 + selectorQuery.selectAll('.u-cate-tab__page-item').boundingClientRect((rects) => {
  207 + // 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
  208 + if(!rects.length) {
  209 + setTimeout(() => {
  210 + this.getMenuItemTop();
  211 + }, 100);
  212 + return ;
  213 + }
  214 + // console.log(rects)
  215 + this.rects = rects;
  216 + this.arr = [];
  217 + rects.forEach((rect) => {
  218 + // 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
  219 + this.arr.push(rect.top - rects[0].top);
  220 + })
  221 + // console.log(this.arr)
  222 + resolve();
  223 + }).exec()
  224 + })
  225 + },
  226 + // 右边菜单滚动
  227 + async rightScroll(e) {
  228 + if (this.mode !== 'follow') return;
  229 + this.oldScrollTop = e.detail.scrollTop;
  230 + // console.log(e.detail.scrollTop)
  231 + // console.log(JSON.stringify(this.arr))
  232 + if(this.arr.length == 0) {
  233 + await this.getMenuItemTop();
  234 + }
  235 + if(this.timer) return ;
  236 + if(!this.menuHeight) {
  237 + await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
  238 + }
  239 + setTimeout(() => { // 节流
  240 + this.timer = null;
  241 + // scrollHeight为右边菜单垂直中点位置
  242 + let scrollHeight = e.detail.scrollTop + 1;
  243 + // console.log(e.detail.scrollTop)
  244 + for (let i = 0; i < this.arr.length; i++) {
  245 + let height1 = this.arr[i];
  246 + let height2 = this.arr[i + 1];
  247 + // console.log('i', i)
  248 + // console.log('height1', height1)
  249 + // console.log('height2', height2)
  250 + // 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
  251 + if (!height2 || scrollHeight >= height1 && scrollHeight <= height2) {
  252 + // console.log('scrollHeight', scrollHeight)
  253 + // console.log('height1', height1)
  254 + // console.log('height2', height2)
  255 + this.leftMenuStatus(i);
  256 + return ;
  257 + }
  258 + }
  259 + }, 100)
  260 + }
  261 + }
  262 + }
  263 +</script>
  264 +
  265 +<style lang="scss" scoped>
  266 + .u-cate-tab {
  267 + display: flex;
  268 + flex-direction: column;
  269 + }
  270 +
  271 + .u-cate-tab__wrap {
  272 + flex: 1;
  273 + display: flex;
  274 + flex-direction: row;
  275 + overflow: hidden;
  276 + }
  277 +
  278 + .u-search-inner {
  279 + background-color: rgb(234, 234, 234);
  280 + border-radius: 100rpx;
  281 + display: flex;
  282 + align-items: center;
  283 + padding: 10rpx 16rpx;
  284 + }
  285 +
  286 + .u-search-text {
  287 + font-size: 26rpx;
  288 + color: $u-tips-color;
  289 + margin-left: 10rpx;
  290 + }
  291 +
  292 + .u-cate-tab__view {
  293 + width: 200rpx;
  294 + height: 100%;
  295 + }
  296 +
  297 + .u-cate-tab__item {
  298 + height: 110rpx;
  299 + background: #f6f6f6;
  300 + box-sizing: border-box;
  301 + display: flex;
  302 + align-items: center;
  303 + justify-content: center;
  304 + font-size: 26rpx;
  305 + color: #444;
  306 + font-weight: 400;
  307 + line-height: 1;
  308 + }
  309 +
  310 + .u-cate-tab__item-active {
  311 + position: relative;
  312 + color: #000;
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + background: #fff;
  316 + }
  317 +
  318 + .u-cate-tab__item-active::before {
  319 + content: "";
  320 + position: absolute;
  321 + border-left: 4px solid $u-primary;
  322 + height: 32rpx;
  323 + left: 0;
  324 + top: 39rpx;
  325 + }
  326 +
  327 + .u-cate-tab__view {
  328 + height: 100%;
  329 + }
  330 +
  331 + .u-cate-tab__right-box {
  332 + flex: 1;
  333 + background-color: rgb(250, 250, 250);
  334 + }
  335 +
  336 + .u-cate-tab__page-view {
  337 + padding: 16rpx;
  338 + }
  339 +
  340 + .u-cate-tab__page-item {
  341 + margin-bottom: 30rpx;
  342 + background-color: #fff;
  343 + padding: 16rpx;
  344 + border-radius: 8rpx;
  345 + }
  346 +
  347 + .u-cate-tab__page-item:last-child {
  348 + min-height: 100vh;
  349 + }
  350 +
  351 + .item-title {
  352 + font-size: 26rpx;
  353 + color: $u-main-color;
  354 + font-weight: bold;
  355 + }
  356 +
  357 + .item-menu-name {
  358 + font-weight: normal;
  359 + font-size: 24rpx;
  360 + color: $u-main-color;
  361 + }
  362 +
  363 + .item-container {
  364 + display: flex;
  365 + flex-wrap: wrap;
  366 + }
  367 +
  368 + .thumb-box {
  369 + width: 33.333333%;
  370 + display: flex;
  371 + align-items: center;
  372 + justify-content: center;
  373 + flex-direction: column;
  374 + margin-top: 20rpx;
  375 + }
  376 +
  377 + .item-menu-image {
  378 + width: 120rpx;
  379 + height: 120rpx;
  380 + }
  381 +</style>
... ...
uni_modules/uview-plus/components/u-cell-group/cellGroup.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:54:16
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cellGroup.js
  9 + */
  10 +export default {
  11 + // cell-group组件的props
  12 + cellGroup: {
  13 + title: '',
  14 + border: true,
  15 + customStyle: {}
  16 + }
  17 +}
... ...
uni_modules/uview-plus/components/u-cell-group/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 分组标题
  6 + title: {
  7 + type: String,
  8 + default: () => defProps.cellGroup.title
  9 + },
  10 + // 是否显示外边框
  11 + border: {
  12 + type: Boolean,
  13 + default: () => defProps.cellGroup.border
  14 + }
  15 + }
  16 +})
... ...
uni_modules/uview-plus/components/u-cell-group/u-cell-group.vue 0 → 100644
  1 +<template>
  2 + <view :style="[addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
  3 + <view v-if="title" class="u-cell-group__title">
  4 + <slot name="title">
  5 + <text class="u-cell-group__title__text">{{ title }}</text>
  6 + </slot>
  7 + </view>
  8 + <view class="u-cell-group__wrapper">
  9 + <u-line v-if="border"></u-line>
  10 + <slot />
  11 + </view>
  12 + </view>
  13 +</template>
  14 +
  15 +<script>
  16 + import { props } from './props';
  17 + import { mpMixin } from '../../libs/mixin/mpMixin';
  18 + import { mixin } from '../../libs/mixin/mixin';
  19 + import { addStyle } from '../../libs/function/index';
  20 + /**
  21 + * cellGroup 单元格
  22 + * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
  23 + * @tutorial https://uview-plus.jiangruyi.com/components/cell.html
  24 + *
  25 + * @property {String} title 分组标题
  26 + * @property {Boolean} border 是否显示外边框 (默认 true )
  27 + * @property {Object} customStyle 定义需要用到的外部样式
  28 + *
  29 + * @event {Function} click 点击cell列表时触发
  30 + * @example <u-cell-group title="设置喜好">
  31 + */
  32 + export default {
  33 + name: 'u-cell-group',
  34 + mixins: [mpMixin, mixin, props],
  35 + methods: {
  36 + addStyle
  37 + }
  38 + }
  39 +</script>
  40 +
  41 +<style lang="scss" scoped>
  42 +
  43 + $u-cell-group-title-padding: 16px 16px 8px !default;
  44 + $u-cell-group-title-font-size: 15px !default;
  45 + $u-cell-group-title-line-height: 16px !default;
  46 + $u-cell-group-title-color: $u-main-color !default;
  47 +
  48 + .u-cell-group {
  49 + flex: 1;
  50 +
  51 + &__title {
  52 + padding: $u-cell-group-title-padding;
  53 +
  54 + &__text {
  55 + font-size: $u-cell-group-title-font-size;
  56 + line-height: $u-cell-group-title-line-height;
  57 + color: $u-cell-group-title-color;
  58 + }
  59 + }
  60 +
  61 + &__wrapper {
  62 + position: relative;
  63 + }
  64 + }
  65 +</style>
  66 +
... ...
uni_modules/uview-plus/components/u-cell/cell.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-23 20:53:09
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cell.js
  9 + */
  10 +export default {
  11 + // cell组件的props
  12 + cell: {
  13 + customClass: '',
  14 + title: '',
  15 + label: '',
  16 + value: '',
  17 + icon: '',
  18 + disabled: false,
  19 + border: true,
  20 + center: false,
  21 + url: '',
  22 + linkType: 'navigateTo',
  23 + clickable: false,
  24 + isLink: false,
  25 + required: false,
  26 + arrowDirection: '',
  27 + iconStyle: {},
  28 + rightIconStyle: {},
  29 + rightIcon: 'arrow-right',
  30 + titleStyle: {},
  31 + size: '',
  32 + stop: true,
  33 + name: ''
  34 + }
  35 +}
... ...
uni_modules/uview-plus/components/u-cell/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 标题
  6 + title: {
  7 + type: [String, Number],
  8 + default: () => defProps.cell.title
  9 + },
  10 + // 标题下方的描述信息
  11 + label: {
  12 + type: [String, Number],
  13 + default: () => defProps.cell.label
  14 + },
  15 + // 右侧的内容
  16 + value: {
  17 + type: [String, Number],
  18 + default: () => defProps.cell.value
  19 + },
  20 + // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
  21 + icon: {
  22 + type: String,
  23 + default: () => defProps.cell.icon
  24 + },
  25 + // 是否禁用cell
  26 + disabled: {
  27 + type: Boolean,
  28 + default: () => defProps.cell.disabled
  29 + },
  30 + // 是否显示下边框
  31 + border: {
  32 + type: Boolean,
  33 + default: () => defProps.cell.border
  34 + },
  35 + // 内容是否垂直居中(主要是针对右侧的value部分)
  36 + center: {
  37 + type: Boolean,
  38 + default: () => defProps.cell.center
  39 + },
  40 + // 点击后跳转的URL地址
  41 + url: {
  42 + type: String,
  43 + default: () => defProps.cell.url
  44 + },
  45 + // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
  46 + linkType: {
  47 + type: String,
  48 + default: () => defProps.cell.linkType
  49 + },
  50 + // 是否开启点击反馈(表现为点击时加上灰色背景)
  51 + clickable: {
  52 + type: Boolean,
  53 + default: () => defProps.cell.clickable
  54 + },
  55 + // 是否展示右侧箭头并开启点击反馈
  56 + isLink: {
  57 + type: Boolean,
  58 + default: () => defProps.cell.isLink
  59 + },
  60 + // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
  61 + required: {
  62 + type: Boolean,
  63 + default: () => defProps.cell.required
  64 + },
  65 + // 右侧的图标箭头
  66 + rightIcon: {
  67 + type: String,
  68 + default: () => defProps.cell.rightIcon
  69 + },
  70 + // 右侧箭头的方向,可选值为:left,up,down
  71 + arrowDirection: {
  72 + type: String,
  73 + default: () => defProps.cell.arrowDirection
  74 + },
  75 + // 左侧图标样式
  76 + iconStyle: {
  77 + type: [Object, String],
  78 + default: () => {
  79 + return defProps.cell.iconStyle
  80 + }
  81 + },
  82 + // 右侧箭头图标的样式
  83 + rightIconStyle: {
  84 + type: [Object, String],
  85 + default: () => {
  86 + return defProps.cell.rightIconStyle
  87 + }
  88 + },
  89 + // 标题的样式
  90 + titleStyle: {
  91 + type: [Object, String],
  92 + default: () => {
  93 + return defProps.cell.titleStyle
  94 + }
  95 + },
  96 + // 单位元的大小,可选值为large
  97 + size: {
  98 + type: String,
  99 + default: () => defProps.cell.size
  100 + },
  101 + // 点击cell是否阻止事件传播
  102 + stop: {
  103 + type: Boolean,
  104 + default: () => defProps.cell.stop
  105 + },
  106 + // 标识符,cell被点击时返回
  107 + name: {
  108 + type: [Number, String],
  109 + default: () => defProps.cell.name
  110 + }
  111 + }
  112 +})
... ...
uni_modules/uview-plus/components/u-cell/u-cell.vue 0 → 100644
  1 +<template>
  2 + <view class="u-cell" :class="[customClass]" :style="[addStyle(customStyle)]"
  3 + :hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
  4 + @tap="clickHandler">
  5 + <view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
  6 + <view class="u-cell__body__content">
  7 + <view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
  8 + <slot name="icon" v-if="$slots.icon">
  9 + </slot>
  10 + <up-icon v-else :name="icon"
  11 + :custom-style="iconStyle"
  12 + :size="size === 'large' ? 22 : 18"></up-icon>
  13 + </view>
  14 + <view class="u-cell__title">
  15 + <!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递,这样才能解决collapse组件的slot不失效问题,label暂时未用到。 -->
  16 + <slot name="title" v-if="$slots.title || !title">
  17 + </slot>
  18 + <text v-else class="u-cell__title-text" :style="[titleTextStyle]"
  19 + :class="[required && 'u-cell--required', disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
  20 + <slot name="label">
  21 + <text class="u-cell__label" v-if="label"
  22 + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
  23 + </slot>
  24 + </view>
  25 + </view>
  26 + <slot name="value">
  27 + <text class="u-cell__value"
  28 + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
  29 + v-if="!testEmpty(value)">{{ value }}</text>
  30 + </slot>
  31 + <view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
  32 + :class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
  33 + <up-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon"
  34 + :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
  35 + :size="size === 'large' ? 18 : 16"></up-icon>
  36 + <slot v-else name="right-icon">
  37 + </slot>
  38 + </view>
  39 + <view class="u-cell__right-icon-wrap" v-if="$slots['righticon']"
  40 + :class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
  41 + <slot name="righticon">
  42 + </slot>
  43 + </view>
  44 + </view>
  45 + <u-line v-if="border"></u-line>
  46 + </view>
  47 +</template>
  48 +
  49 +<script>
  50 + import { props } from './props';
  51 + import { mpMixin } from '../../libs/mixin/mpMixin';
  52 + import { mixin } from '../../libs/mixin/mixin';
  53 + import { addStyle } from '../../libs/function/index';
  54 + import test from '../../libs/function/test';
  55 + /**
  56 + * cell 单元格
  57 + * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
  58 + * @tutorial https://uview-plus.jiangruyi.com/components/cell.html
  59 + * @property {String | Number} title 标题
  60 + * @property {String | Number} label 标题下方的描述信息
  61 + * @property {String | Number} value 右侧的内容
  62 + * @property {String} icon 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
  63 + * @property {Boolean} disabled 是否禁用cell
  64 + * @property {Boolean} border 是否显示下边框 (默认 true )
  65 + * @property {Boolean} center 内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
  66 + * @property {String} url 点击后跳转的URL地址
  67 + * @property {String} linkType 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
  68 + * @property {Boolean} clickable 是否开启点击反馈(表现为点击时加上灰色背景) (默认 false )
  69 + * @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 (默认 false )
  70 + * @property {Boolean} required 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
  71 + * @property {String} rightIcon 右侧的图标箭头 (默认 'arrow-right')
  72 + * @property {String} arrowDirection 右侧箭头的方向,可选值为:left,up,down
  73 + * @property {Object | String} rightIconStyle 右侧箭头图标的样式
  74 + * @property {Object | String} titleStyle 标题的样式
  75 + * @property {Object | String} iconStyle 左侧图标样式
  76 + * @property {String} size 单位元的大小,可选值为 large,normal,mini
  77 + * @property {Boolean} stop 点击cell是否阻止事件传播 (默认 true )
  78 + * @property {Object} customStyle 定义需要用到的外部样式
  79 + *
  80 + * @event {Function} click 点击cell列表时触发
  81 + * @example 该组件需要搭配cell-group组件使用,见官方文档示例
  82 + */
  83 + export default {
  84 + name: 'u-cell',
  85 + data() {
  86 + return {
  87 +
  88 + }
  89 + },
  90 + mixins: [mpMixin, mixin, props],
  91 + computed: {
  92 + titleTextStyle() {
  93 + return addStyle(this.titleStyle)
  94 + }
  95 + },
  96 + emits: ['click'],
  97 + methods: {
  98 + addStyle,
  99 + testEmpty: test.empty,
  100 + // 点击cell
  101 + clickHandler(e) {
  102 + if (this.disabled) return
  103 + this.$emit('click', {
  104 + name: this.name
  105 + })
  106 + // 如果配置了url(此props参数通过mixin引入)参数,跳转页面
  107 + this.openPage()
  108 + // 是否阻止事件传播
  109 + this.stop && this.preventEvent(e)
  110 + },
  111 + }
  112 + }
  113 +</script>
  114 +
  115 +<style lang="scss" scoped>
  116 +
  117 + $u-cell-padding: 13px 15px !default;
  118 + $u-cell-font-size: 15px !default;
  119 + $u-cell-line-height: 24px !default;
  120 + $u-cell-color: $u-main-color !default;
  121 + $u-cell-icon-size: 16px !default;
  122 + $u-cell-title-font-size: 15px !default;
  123 + $u-cell-title-line-height: 22px !default;
  124 + $u-cell-title-color: $u-main-color !default;
  125 + $u-cell-label-font-size: 12px !default;
  126 + $u-cell-label-color: $u-tips-color !default;
  127 + $u-cell-label-line-height: 18px !default;
  128 + $u-cell-value-font-size: 14px !default;
  129 + $u-cell-value-color: $u-content-color !default;
  130 + $u-cell-clickable-color: $u-bg-color !default;
  131 + $u-cell-disabled-color: #c8c9cc !default;
  132 + $u-cell-padding-top-large: 13px !default;
  133 + $u-cell-padding-bottom-large: 13px !default;
  134 + $u-cell-value-font-size-large: 15px !default;
  135 + $u-cell-label-font-size-large: 14px !default;
  136 + $u-cell-title-font-size-large: 16px !default;
  137 + $u-cell-left-icon-wrap-margin-right: 4px !default;
  138 + $u-cell-right-icon-wrap-margin-left: 4px !default;
  139 + $u-cell-title-flex:1 !default;
  140 + $u-cell-label-margin-top:5px !default;
  141 +
  142 +
  143 + .u-cell {
  144 + &__body {
  145 + @include flex();
  146 + /* #ifndef APP-NVUE */
  147 + box-sizing: border-box;
  148 + /* #endif */
  149 + padding: $u-cell-padding;
  150 + font-size: $u-cell-font-size;
  151 + color: $u-cell-color;
  152 + // line-height: $u-cell-line-height;
  153 + align-items: center;
  154 +
  155 + &__content {
  156 + @include flex(row);
  157 + align-items: center;
  158 + flex: 1;
  159 + }
  160 +
  161 + &--large {
  162 + padding-top: $u-cell-padding-top-large;
  163 + padding-bottom: $u-cell-padding-bottom-large;
  164 + }
  165 + }
  166 +
  167 + &__left-icon-wrap,
  168 + &__right-icon-wrap {
  169 + @include flex();
  170 + align-items: center;
  171 + // height: $u-cell-line-height;
  172 + font-size: $u-cell-icon-size;
  173 + }
  174 +
  175 + &__left-icon-wrap {
  176 + margin-right: $u-cell-left-icon-wrap-margin-right;
  177 + }
  178 +
  179 + &__right-icon-wrap {
  180 + margin-left: $u-cell-right-icon-wrap-margin-left;
  181 + transition: transform 0.3s;
  182 +
  183 + &--up {
  184 + transform: rotate(-90deg);
  185 + }
  186 +
  187 + &--down {
  188 + transform: rotate(90deg);
  189 + }
  190 + }
  191 +
  192 + &__title {
  193 + flex: $u-cell-title-flex;
  194 + display: flex;
  195 + flex-direction: column;
  196 +
  197 + &-text {
  198 + font-size: $u-cell-title-font-size;
  199 + line-height: $u-cell-title-line-height;
  200 + color: $u-cell-title-color;
  201 +
  202 + &--large {
  203 + font-size: $u-cell-title-font-size-large;
  204 + }
  205 + }
  206 +
  207 + }
  208 +
  209 + &__label {
  210 + margin-top: $u-cell-label-margin-top;
  211 + font-size: $u-cell-label-font-size;
  212 + color: $u-cell-label-color;
  213 + line-height: $u-cell-label-line-height;
  214 +
  215 + &--large {
  216 + font-size: $u-cell-label-font-size-large;
  217 + }
  218 + }
  219 +
  220 + &__value {
  221 + text-align: right;
  222 + /* #ifndef APP-NVUE */
  223 + margin-left: auto;
  224 + /* #endif */
  225 + font-size: $u-cell-value-font-size;
  226 + line-height: $u-cell-line-height;
  227 + color: $u-cell-value-color;
  228 + &--large {
  229 + font-size: $u-cell-value-font-size-large;
  230 + }
  231 + }
  232 +
  233 + &--required {
  234 + /* #ifndef APP-NVUE */
  235 + overflow: visible;
  236 + /* #endif */
  237 + @include flex;
  238 + align-items: center;
  239 + }
  240 +
  241 + &--required:before {
  242 + position: absolute;
  243 + /* #ifndef APP-NVUE */
  244 + content: '*';
  245 + /* #endif */
  246 + left: -8px;
  247 + margin-top: 4rpx;
  248 + font-size: 14px;
  249 + color: $u-error;
  250 + }
  251 +
  252 + &--clickable {
  253 + background-color: $u-cell-clickable-color;
  254 + }
  255 +
  256 + &--disabled {
  257 + color: $u-cell-disabled-color;
  258 + /* #ifndef APP-NVUE */
  259 + cursor: not-allowed;
  260 + /* #endif */
  261 + }
  262 +
  263 + &--center {
  264 + align-items: center;
  265 + }
  266 + }
  267 +</style>
... ...
uni_modules/uview-plus/components/u-checkbox-group/checkboxGroup.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:54:47
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkboxGroup.js
  9 + */
  10 +export default {
  11 + // checkbox-group组件
  12 + checkboxGroup: {
  13 + name: '',
  14 + value: [],
  15 + shape: 'square',
  16 + disabled: false,
  17 + activeColor: '#2979ff',
  18 + inactiveColor: '#c8c9cc',
  19 + size: 18,
  20 + placement: 'row',
  21 + labelSize: 14,
  22 + labelColor: '#303133',
  23 + labelDisabled: false,
  24 + iconColor: '#ffffff',
  25 + iconSize: 12,
  26 + iconPlacement: 'left',
  27 + borderBottom: false
  28 + }
  29 +}
... ...
uni_modules/uview-plus/components/u-checkbox-group/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +
  4 +export const props = defineMixin({
  5 + props: {
  6 + // 标识符
  7 + name: {
  8 + type: String,
  9 + default: () => defProps.checkboxGroup.name
  10 + },
  11 + // #ifdef VUE3
  12 + // 绑定的值
  13 + modelValue: {
  14 + type: Array,
  15 + default: () => defProps.checkboxGroup.value
  16 + },
  17 + // #endif
  18 + // #ifdef VUE2
  19 + // 绑定的值
  20 + value: {
  21 + type: Array,
  22 + default: () => defProps.checkboxGroup.value
  23 + },
  24 + // #endif
  25 + // 形状,circle-圆形,square-方形
  26 + shape: {
  27 + type: String,
  28 + default: () => defProps.checkboxGroup.shape
  29 + },
  30 + // 是否禁用全部checkbox
  31 + disabled: {
  32 + type: Boolean,
  33 + default: () => defProps.checkboxGroup.disabled
  34 + },
  35 +
  36 + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
  37 + activeColor: {
  38 + type: String,
  39 + default: () => defProps.checkboxGroup.activeColor
  40 + },
  41 + // 未选中的颜色
  42 + inactiveColor: {
  43 + type: String,
  44 + default: () => defProps.checkboxGroup.inactiveColor
  45 + },
  46 +
  47 + // 整个组件的尺寸,默认px
  48 + size: {
  49 + type: [String, Number],
  50 + default: () => defProps.checkboxGroup.size
  51 + },
  52 + // 布局方式,row-横向,column-纵向
  53 + placement: {
  54 + type: String,
  55 + default: () => defProps.checkboxGroup.placement
  56 + },
  57 + // label的字体大小,px单位
  58 + labelSize: {
  59 + type: [String, Number],
  60 + default: () => defProps.checkboxGroup.labelSize
  61 + },
  62 + // label的字体颜色
  63 + labelColor: {
  64 + type: [String],
  65 + default: () => defProps.checkboxGroup.labelColor
  66 + },
  67 + // 是否禁止点击文本操作
  68 + labelDisabled: {
  69 + type: Boolean,
  70 + default: () => defProps.checkboxGroup.labelDisabled
  71 + },
  72 + // 图标颜色
  73 + iconColor: {
  74 + type: String,
  75 + default: () => defProps.checkboxGroup.iconColor
  76 + },
  77 + // 图标的大小,单位px
  78 + iconSize: {
  79 + type: [String, Number],
  80 + default: () => defProps.checkboxGroup.iconSize
  81 + },
  82 + // 勾选图标的对齐方式,left-左边,right-右边
  83 + iconPlacement: {
  84 + type: String,
  85 + default: () => defProps.checkboxGroup.iconPlacement
  86 + },
  87 + // 竖向配列时,是否显示下划线
  88 + borderBottom: {
  89 + type: Boolean,
  90 + default: () => defProps.checkboxGroup.borderBottom
  91 + }
  92 + }
  93 +})
... ...
uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-checkbox-group"
  4 + :class="bemClass"
  5 + >
  6 + <slot></slot>
  7 + </view>
  8 +</template>
  9 +
  10 +<script>
  11 + import { props } from './props';
  12 + import { mpMixin } from '../../libs/mixin/mpMixin';
  13 + import { mixin } from '../../libs/mixin/mixin';
  14 + /**
  15 + * checkboxGroup 复选框组
  16 + * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
  17 + * @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html
  18 + * @property {String} name 标识符
  19 + * @property {Array} value 绑定的值
  20 + * @property {String} shape 形状,circle-圆形,square-方形 (默认 'square' )
  21 + * @property {Boolean} disabled 是否禁用全部checkbox (默认 false )
  22 + * @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
  23 + * @property {String} inactiveColor 未选中的颜色 (默认 '#c8c9cc' )
  24 + * @property {String | Number} size 整个组件的尺寸 单位px (默认 18 )
  25 + * @property {String} placement 布局方式,row-横向,column-纵向 (默认 'row' )
  26 + * @property {String | Number} labelSize label的字体大小,px单位 (默认 14 )
  27 + * @property {String} labelColor label的字体颜色 (默认 '#303133' )
  28 + * @property {Boolean} labelDisabled 是否禁止点击文本操作 (默认 false )
  29 + * @property {String} iconColor 图标颜色 (默认 '#ffffff' )
  30 + * @property {String | Number} iconSize 图标的大小,单位px (默认 12 )
  31 + * @property {String} iconPlacement 勾选图标的对齐方式,left-左边,right-右边 (默认 'left' )
  32 + * @property {Boolean} borderBottom placement为row时,是否显示下边框 (默认 false )
  33 + * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
  34 + * @event {Function} input 修改通过v-model绑定的值时触发,回调为一个对象
  35 + * @example <u-checkbox-group></u-checkbox-group>
  36 + */
  37 + export default {
  38 + name: 'u-checkbox-group',
  39 + mixins: [mpMixin, mixin,props],
  40 + computed: {
  41 + // 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
  42 + // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
  43 + // 拉取父组件新的变化后的参数
  44 + parentData() {
  45 + return [
  46 + // #ifdef VUE2
  47 + this.value,
  48 + // #endif
  49 + // #ifdef VUE3
  50 + this.modelValue,
  51 + // #endif
  52 + this.disabled,
  53 + this.inactiveColor,
  54 + this.activeColor,
  55 + this.size,
  56 + this.labelDisabled,
  57 + this.shape,
  58 + this.iconSize,
  59 + this.borderBottom,
  60 + this.placement,
  61 + ];
  62 + },
  63 + bemClass() {
  64 + // this.bem为一个computed变量,在mixin中
  65 + return this.bem('checkbox-group', ['placement'])
  66 + },
  67 + },
  68 + watch: {
  69 + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
  70 + parentData: {
  71 + handler() {
  72 + if (this.children.length) {
  73 + this.children.map((child) => {
  74 + // 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
  75 + typeof child.init === "function" && child.init();
  76 + });
  77 + }
  78 + },
  79 + deep: true,
  80 + },
  81 + },
  82 + data() {
  83 + return {
  84 +
  85 + }
  86 + },
  87 + created() {
  88 + this.children = []
  89 + },
  90 + // #ifdef VUE3
  91 + emits: ['update:modelValue', 'change'],
  92 + // #endif
  93 + methods: {
  94 + // 将其他的checkbox设置为未选中的状态
  95 + unCheckedOther(childInstance) {
  96 + const values = []
  97 + this.children.map(child => {
  98 + // 将被选中的checkbox,放到数组中返回
  99 + if (child.isChecked) {
  100 + values.push(child.name)
  101 + }
  102 + })
  103 +
  104 + // 修改通过v-model绑定的值
  105 + // #ifdef VUE3
  106 + this.$emit("update:modelValue", values);
  107 + // #endif
  108 + // #ifdef VUE2
  109 + this.$emit("input", values);
  110 + // #endif
  111 + // 放在最后更新,否则change事件传出去的values不会更新
  112 + this.$emit('change', values)
  113 + },
  114 + }
  115 + }
  116 +</script>
  117 +
  118 +<style lang="scss" scoped>
  119 +
  120 + .u-checkbox-group {
  121 +
  122 + &--row {
  123 + /* #ifndef APP-NVUE */
  124 + display: flex;
  125 + /* #endif */
  126 + flex-flow: row wrap;
  127 + }
  128 +
  129 + &--column {
  130 + @include flex(column);
  131 + }
  132 + }
  133 +</style>
... ...
uni_modules/uview-plus/components/u-checkbox/checkbox.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-23 21:06:59
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkbox.js
  9 + */
  10 +export default {
  11 + // checkbox组件
  12 + checkbox: {
  13 + name: '',
  14 + shape: '',
  15 + size: '',
  16 + checkbox: false,
  17 + disabled: '',
  18 + activeColor: '',
  19 + inactiveColor: '',
  20 + iconSize: '',
  21 + iconColor: '',
  22 + label: '',
  23 + labelSize: '',
  24 + labelColor: '',
  25 + labelDisabled: ''
  26 + }
  27 +}
... ...
uni_modules/uview-plus/components/u-checkbox/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // checkbox的名称
  6 + name: {
  7 + type: [String, Number, Boolean],
  8 + default: () => defProps.checkbox.name
  9 + },
  10 + // 形状,square为方形,circle为圆型
  11 + shape: {
  12 + type: String,
  13 + default: () => defProps.checkbox.shape
  14 + },
  15 + // 整体的大小
  16 + size: {
  17 + type: [String, Number],
  18 + default: () => defProps.checkbox.size
  19 + },
  20 + // 是否默认选中
  21 + checked: {
  22 + type: Boolean,
  23 + default: () => defProps.checkbox.checked
  24 + },
  25 + // 是否禁用
  26 + disabled: {
  27 + type: [String, Boolean],
  28 + default: () => defProps.checkbox.disabled
  29 + },
  30 + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
  31 + activeColor: {
  32 + type: String,
  33 + default: () => defProps.checkbox.activeColor
  34 + },
  35 + // 未选中的颜色
  36 + inactiveColor: {
  37 + type: String,
  38 + default: () => defProps.checkbox.inactiveColor
  39 + },
  40 + // 图标的大小,单位px
  41 + iconSize: {
  42 + type: [String, Number],
  43 + default: () => defProps.checkbox.iconSize
  44 + },
  45 + // 图标颜色
  46 + iconColor: {
  47 + type: String,
  48 + default: () => defProps.checkbox.iconColor
  49 + },
  50 + // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
  51 + label: {
  52 + type: [String, Number],
  53 + default: () => defProps.checkbox.label
  54 + },
  55 + // label的字体大小,px单位
  56 + labelSize: {
  57 + type: [String, Number],
  58 + default: () => defProps.checkbox.labelSize
  59 + },
  60 + // label的颜色
  61 + labelColor: {
  62 + type: String,
  63 + default: () => defProps.checkbox.labelColor
  64 + },
  65 + // 是否禁止点击提示语选中复选框
  66 + labelDisabled: {
  67 + type: [String, Boolean],
  68 + default: () => defProps.checkbox.labelDisabled
  69 + },
  70 + // 是否独立使用
  71 + usedAlone: {
  72 + type: [Boolean],
  73 + default: () => false
  74 + }
  75 + }
  76 +})
... ...
uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-checkbox cursor-pointer"
  4 + :style="[checkboxStyle]"
  5 + @tap.stop="wrapperClickHandler"
  6 + :class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']"
  7 + >
  8 + <view
  9 + class="u-checkbox__icon-wrap cursor-pointer"
  10 + @tap.stop="iconClickHandler"
  11 + :class="iconClasses"
  12 + :style="[iconWrapStyle]"
  13 + >
  14 + <slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor">
  15 + <up-icon
  16 + class="u-checkbox__icon-wrap__icon"
  17 + name="checkbox-mark"
  18 + :size="elIconSize"
  19 + :color="elIconColor"
  20 + />
  21 + </slot>
  22 + </view>
  23 + <view class="u-checkbox__label-wrap cursor-pointer" @tap.stop="labelClickHandler">
  24 + <slot name="label" :label="label" :elDisabled="elDisabled">
  25 + <text
  26 + :style="{
  27 + color: elDisabled ? elInactiveColor : elLabelColor,
  28 + fontSize: elLabelSize,
  29 + lineHeight: elLabelSize
  30 + }"
  31 + >{{label}}</text>
  32 + </slot>
  33 + </view>
  34 + </view>
  35 +</template>
  36 +
  37 +<script>
  38 + import { props } from './props';
  39 + import { mpMixin } from '../../libs/mixin/mpMixin';
  40 + import { mixin } from '../../libs/mixin/mixin';
  41 + import { addStyle, addUnit, deepMerge, formValidate, error } from '../../libs/function/index';
  42 + import test from '../../libs/function/test';
  43 + /**
  44 + * checkbox 复选框
  45 + * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
  46 + * @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html
  47 + * @property {String | Number | Boolean} name checkbox组件的标示符
  48 + * @property {String} shape 形状,square为方形,circle为圆型
  49 + * @property {String | Number} size 整体的大小
  50 + * @property {Boolean} checked 是否默认选中
  51 + * @property {String | Boolean} disabled 是否禁用
  52 + * @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
  53 + * @property {String} inactiveColor 未选中的颜色
  54 + * @property {String | Number} iconSize 图标的大小,单位px
  55 + * @property {String} iconColor 图标颜色
  56 + * @property {String | Number} label label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
  57 + * @property {String} labelColor label的颜色
  58 + * @property {String | Number} labelSize label的字体大小,px单位
  59 + * @property {String | Boolean} labelDisabled 是否禁止点击提示语选中复选框
  60 + * @property {Object} customStyle 定义需要用到的外部样式
  61 + *
  62 + * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
  63 + * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
  64 + */
  65 + export default {
  66 + name: "u-checkbox",
  67 + mixins: [mpMixin, mixin, props],
  68 + data() {
  69 + return {
  70 + isChecked: false,
  71 + // 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
  72 + // 故只能使用如此方法
  73 + parentData: {
  74 + iconSize: 12,
  75 + labelDisabled: null,
  76 + disabled: null,
  77 + shape: 'square',
  78 + activeColor: null,
  79 + inactiveColor: null,
  80 + size: 18,
  81 + // #ifdef VUE2
  82 + value: null,
  83 + // #endif
  84 + // #ifdef VUE3
  85 + modelValue: null,
  86 + // #endif
  87 + iconColor: null,
  88 + placement: 'row',
  89 + borderBottom: false,
  90 + iconPlacement: 'left'
  91 + }
  92 + }
  93 + },
  94 + computed: {
  95 + // 是否禁用,如果父组件u-radios-group禁用的话,将会忽略子组件的配置
  96 + elDisabled() {
  97 + return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
  98 + },
  99 + // 是否禁用label点击
  100 + elLabelDisabled() {
  101 + return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
  102 + false;
  103 + },
  104 + // 组件尺寸,对应size的值,默认值为21px
  105 + elSize() {
  106 + return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
  107 + },
  108 + // 组件的勾选图标的尺寸,默认12px
  109 + elIconSize() {
  110 + return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
  111 + },
  112 + // 组件选中激活时的颜色
  113 + elActiveColor() {
  114 + return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
  115 + },
  116 + // 组件选未中激活时的颜色
  117 + elInactiveColor() {
  118 + return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
  119 + '#c8c9cc');
  120 + },
  121 + // label的颜色
  122 + elLabelColor() {
  123 + return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
  124 + },
  125 + // 组件的形状
  126 + elShape() {
  127 + return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
  128 + },
  129 + // label大小
  130 + elLabelSize() {
  131 + return addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
  132 + '15'))
  133 + },
  134 + elIconColor() {
  135 + const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
  136 + '#ffffff');
  137 + // 图标的颜色
  138 + if (this.elDisabled) {
  139 + // disabled状态下,已勾选的checkbox图标改为elInactiveColor
  140 + return this.isChecked ? this.elInactiveColor : 'transparent'
  141 + } else {
  142 + return this.isChecked ? iconColor : 'transparent'
  143 + }
  144 + },
  145 + iconClasses() {
  146 + let classes = []
  147 + // 组件的形状
  148 + classes.push('u-checkbox__icon-wrap--' + this.elShape)
  149 + if (this.elDisabled) {
  150 + classes.push('u-checkbox__icon-wrap--disabled')
  151 + }
  152 + if (this.isChecked && this.elDisabled) {
  153 + classes.push('u-checkbox__icon-wrap--disabled--checked')
  154 + }
  155 + // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
  156 + // #ifdef MP-ALIPAY || MP-TOUTIAO
  157 + classes = classes.join(' ')
  158 + // #endif
  159 + return classes
  160 + },
  161 + iconWrapStyle() {
  162 + // checkbox的整体样式
  163 + const style = {}
  164 + style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
  165 + style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
  166 + style.width = addUnit(this.elSize)
  167 + style.height = addUnit(this.elSize)
  168 + // 如果是图标在右边的话,移除它的右边距
  169 + if (!this.usedAlone) {
  170 + if (this.parentData.iconPlacement === 'right') {
  171 + style.marginRight = 0
  172 + }
  173 + }
  174 + return style
  175 + },
  176 + checkboxStyle() {
  177 + const style = {}
  178 + if (!this.usedAlone) {
  179 + if (this.parentData.borderBottom && this.parentData.placement === 'row') {
  180 + error('检测到您将borderBottom设置为true,需要同时将up-checkbox-group的placement设置为column才有效')
  181 + }
  182 + // 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
  183 + if (this.parentData.borderBottom && this.parentData.placement === 'column') {
  184 + style.paddingBottom = '8px'
  185 + }
  186 + }
  187 + return deepMerge(style, addStyle(this.customStyle))
  188 + }
  189 + },
  190 + mounted() {
  191 + this.init()
  192 + },
  193 + emits: ["change", "update:checked"],
  194 + methods: {
  195 + init() {
  196 + if (!this.usedAlone) {
  197 + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
  198 + this.updateParentData()
  199 + if (!this.parent) {
  200 + error('up-checkbox必须搭配up-checkbox-group组件使用')
  201 + }
  202 + let value = '';
  203 + // #ifdef VUE2
  204 + value = this.parentData.value
  205 + // #endif
  206 + // #ifdef VUE3
  207 + value = this.parentData.modelValue
  208 + // #endif
  209 + // 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
  210 + if (this.checked) {
  211 + this.isChecked = true
  212 + } else if (!this.usedAlone && test.array(value)) {
  213 + // 查找数组是是否存在this.name元素值
  214 + this.isChecked = value.some(item => {
  215 + return item === this.name
  216 + })
  217 + }
  218 + } else {
  219 + if (this.checked) {
  220 + this.isChecked = true
  221 + }
  222 + }
  223 + },
  224 + updateParentData() {
  225 + this.getParentData('u-checkbox-group')
  226 + },
  227 + // 横向两端排列时,点击组件即可触发选中事件
  228 + wrapperClickHandler(e) {
  229 + if (!this.usedAlone) {
  230 + this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
  231 + } else {
  232 + this.iconClickHandler(e)
  233 + }
  234 + },
  235 + // 点击图标
  236 + iconClickHandler(e) {
  237 + this.preventEvent(e)
  238 + // 如果整体被禁用,不允许被点击
  239 + if (!this.elDisabled) {
  240 + this.setRadioCheckedStatus()
  241 + }
  242 + },
  243 + // 点击label
  244 + labelClickHandler(e) {
  245 + this.preventEvent(e)
  246 + // 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
  247 + if (!this.elLabelDisabled && !this.elDisabled) {
  248 + this.setRadioCheckedStatus()
  249 + }
  250 + },
  251 + emitEvent() {
  252 + this.$emit('change', this.isChecked, {
  253 + name: this.name
  254 + })
  255 + // 双向绑定
  256 + if (this.usedAlone) {
  257 + this.$emit('update:checked', this.isChecked)
  258 + }
  259 + // 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
  260 + this.$nextTick(() => {
  261 + formValidate(this, 'change')
  262 + })
  263 + },
  264 + // 改变组件选中状态
  265 + // 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例
  266 + // 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
  267 + setRadioCheckedStatus() {
  268 + // 将本组件标记为与原来相反的状态
  269 + this.isChecked = !this.isChecked
  270 + this.emitEvent()
  271 + if (!this.usedAlone) {
  272 + typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
  273 + }
  274 + }
  275 + },
  276 + watch:{
  277 + checked(newValue, oldValue){
  278 + if (newValue !== this.isChecked) {
  279 + this.isChecked = newValue
  280 + }
  281 + }
  282 + }
  283 + }
  284 +</script>
  285 +
  286 +<style lang="scss" scoped>
  287 + $u-checkbox-icon-wrap-margin-right:6px !default;
  288 + $u-checkbox-icon-wrap-font-size:6px !default;
  289 + $u-checkbox-icon-wrap-border-width:1px !default;
  290 + $u-checkbox-icon-wrap-border-color:#c8c9cc !default;
  291 + $u-checkbox-icon-wrap-icon-line-height:0 !default;
  292 + $u-checkbox-icon-wrap-circle-border-radius:100% !default;
  293 + $u-checkbox-icon-wrap-square-border-radius:3px !default;
  294 + $u-checkbox-icon-wrap-checked-color:#fff !default;
  295 + $u-checkbox-icon-wrap-checked-background-color:red !default;
  296 + $u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
  297 + $u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
  298 + $u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
  299 + $u-checkbox-label-margin-left:5px !default;
  300 + $u-checkbox-label-margin-right:12px !default;
  301 + $u-checkbox-label-color:$u-content-color !default;
  302 + $u-checkbox-label-font-size:15px !default;
  303 + $u-checkbox-label-disabled-color:#c8c9cc !default;
  304 +
  305 + .u-checkbox {
  306 + /* #ifndef APP-NVUE */
  307 + @include flex(row);
  308 + /* #endif */
  309 + overflow: hidden;
  310 + flex-direction: row;
  311 + align-items: center;
  312 + margin-bottom: 5px;
  313 + margin-top: 5px;
  314 +
  315 + &-label--left {
  316 + flex-direction: row
  317 + }
  318 +
  319 + &-label--right {
  320 + flex-direction: row-reverse;
  321 + justify-content: space-between
  322 + }
  323 +
  324 + &__icon-wrap {
  325 + /* #ifndef APP-NVUE */
  326 + box-sizing: border-box;
  327 + // nvue下,border-color过渡有问题
  328 + transition-property: border-color, background-color, color;
  329 + transition-duration: 0.2s;
  330 + /* #endif */
  331 + color: $u-content-color;
  332 + @include flex;
  333 + align-items: center;
  334 + justify-content: center;
  335 + color: transparent;
  336 + text-align: center;
  337 + margin-right: $u-checkbox-icon-wrap-margin-right;
  338 +
  339 + font-size: $u-checkbox-icon-wrap-font-size;
  340 + border-width: $u-checkbox-icon-wrap-border-width;
  341 + border-color: $u-checkbox-icon-wrap-border-color;
  342 + border-style: solid;
  343 +
  344 + /* #ifdef MP-TOUTIAO */
  345 + // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
  346 + &__icon {
  347 + line-height: $u-checkbox-icon-wrap-icon-line-height;
  348 + }
  349 +
  350 + /* #endif */
  351 +
  352 + &--circle {
  353 + border-radius: $u-checkbox-icon-wrap-circle-border-radius;
  354 + }
  355 +
  356 + &--square {
  357 + border-radius: $u-checkbox-icon-wrap-square-border-radius;
  358 + }
  359 +
  360 + &--checked {
  361 + color: $u-checkbox-icon-wrap-checked-color;
  362 + background-color: $u-checkbox-icon-wrap-checked-background-color;
  363 + border-color: $u-checkbox-icon-wrap-checked-border-color;
  364 + }
  365 +
  366 + &--disabled {
  367 + background-color: $u-checkbox-icon-wrap-disabled-background-color !important;
  368 + }
  369 +
  370 + &--disabled--checked {
  371 + color: $u-checkbox-icon-wrap-disabled-checked-color !important;
  372 + }
  373 + }
  374 +
  375 + &__label {
  376 + /* #ifndef APP-NVUE */
  377 + word-wrap: break-word;
  378 + /* #endif */
  379 + margin-left: $u-checkbox-label-margin-left;
  380 + margin-right: $u-checkbox-label-margin-right;
  381 + color: $u-checkbox-label-color;
  382 + font-size: $u-checkbox-label-font-size;
  383 +
  384 + &--disabled {
  385 + color: $u-checkbox-label-disabled-color;
  386 + }
  387 + }
  388 + }
  389 +</style>
... ...
uni_modules/uview-plus/components/u-choose/u-choose.vue 0 → 100644
  1 +<style scoped lang="scss">
  2 + .up-choose {
  3 + ::v-deep .up-tag {
  4 + font-weight: 600;
  5 + }
  6 + &:last-child {
  7 + margin-right: 0;
  8 + }
  9 + }
  10 +
  11 + .up-choose-wrap {
  12 + flex-wrap: wrap;
  13 + }
  14 +
  15 + .up-choose-nowrap {
  16 + flex-wrap: nowrap;
  17 + white-space: nowrap;
  18 + }
  19 +</style>
  20 +
  21 +<template>
  22 + <scroll-view
  23 + :scroll-x="wrap === false"
  24 + :class="['up-choose', wrap ? 'up-choose-wrap' : 'up-choose-nowrap']">
  25 + <template :key="item.id" v-for="(item,index) in options">
  26 + <view :style="{width: itemWidth, display: 'inline-block'}">
  27 + <slot :item="item" :index="index">
  28 + <up-tag :type="index == currentIndex ? 'primary' : 'info'"
  29 + size="large" :plain="index == currentIndex ? false : true"
  30 + :class="currentIndex === index ? 'active': ''" :height="itemHeight"
  31 + :style="{width: itemWidth, padding: itemPadding}"
  32 + @click="change(index)">
  33 + {{item[labelName]}}
  34 + </up-tag>
  35 + </slot>
  36 + </view>
  37 + </template>
  38 + </scroll-view>
  39 +</template>
  40 +
  41 +<script>
  42 + export default {
  43 + name: 'up-choose',
  44 + props: {
  45 + options:{
  46 + type: Array,
  47 + default: ()=>{
  48 + return [];
  49 + }
  50 + },
  51 + modelValue: {
  52 + type: [Number,String,Array],
  53 + default: false
  54 + },
  55 + type: {
  56 + type: [String],
  57 + default: 'radio'
  58 + },
  59 + itemWidth: {
  60 + type: [String],
  61 + default: 'auto'
  62 + },
  63 + itemHeight: {
  64 + type: [String],
  65 + default: '50px'
  66 + },
  67 + itemPadding: {
  68 + type: [String],
  69 + default: '8px'
  70 + },
  71 + labelName: {
  72 + type: String,
  73 + default: 'title'
  74 + },
  75 + valueName: {
  76 + type: String,
  77 + default: 'value'
  78 + },
  79 + customClick: {
  80 + type: Boolean,
  81 + default: false
  82 + },
  83 + // 是否换行
  84 + wrap: {
  85 + type: Boolean,
  86 + default: true
  87 + }
  88 + },
  89 + data() {
  90 + return {
  91 + currentIndex: ''
  92 + }
  93 + },
  94 + created: function () {
  95 + this.currentIndex = this.modelValue;
  96 + },
  97 + emits: ['update:modelValue', 'custom-click'],
  98 + methods: {
  99 + change(index){
  100 + if (this.customClick) {
  101 + this.$emit('custom-click', index);
  102 + } else {
  103 + this.currentIndex = index;
  104 + this.$emit('update:modelValue', index);
  105 + }
  106 + }
  107 + }
  108 + }
  109 +</script>
0 110 \ No newline at end of file
... ...
uni_modules/uview-plus/components/u-circle-progress/circleProgress.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:55:02
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/circleProgress.js
  9 + */
  10 +export default {
  11 + // circleProgress 组件
  12 + circleProgress: {
  13 + percentage: 30
  14 + }
  15 +}
... ...
uni_modules/uview-plus/components/u-circle-progress/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + percentage: {
  6 + type: [String, Number],
  7 + default: () => defProps.circleProgress.percentage
  8 + }
  9 + }
  10 +})
... ...
uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue 0 → 100644
  1 +<template>
  2 + <view class="u-circle-progress">
  3 + <view class="u-circle-progress__left">
  4 + <view
  5 + class="u-circle-progress__left__circle"
  6 + :style="[leftSyle]"
  7 + ref="left-circle"
  8 + >
  9 +
  10 + </view>
  11 + </view>
  12 + <view
  13 + class="u-circle-progress__right"
  14 + >
  15 + <view
  16 + class="u-circle-progress__right__circle"
  17 + ref="right-circle"
  18 + :style="[rightSyle]"
  19 + >
  20 +
  21 + </view>
  22 + </view>
  23 + <view class="u-circle-progress__circle">
  24 +
  25 + </view>
  26 + </view>
  27 +</template>
  28 +
  29 +<script>
  30 + import { props } from './props';
  31 + import { mpMixin } from '../../libs/mixin/mpMixin';
  32 + import { mixin } from '../../libs/mixin/mixin';
  33 + import {sleep } from '../../libs/function/index';
  34 + // #ifdef APP-NVUE
  35 + const animation = uni.requireNativePlugin('animation')
  36 + // #endif
  37 + /**
  38 + * CircleProgress 圆形进度条 TODO: 待完善
  39 + * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。
  40 + * @tutorial https://uview-plus.jiangruyi.com/components/circleProgress.html
  41 + * @property {String | Number} percentage 圆环进度百分比值,为数值类型,0-100 (默认 30 )
  42 + * @example
  43 + */
  44 + export default {
  45 + name: 'u-circle-progress',
  46 + mixins: [mpMixin, mixin, props],
  47 + data() {
  48 + return {
  49 + leftBorderColor: 'rgb(200, 200, 200)',
  50 + rightBorderColor: 'rgb(200, 200, 200)',
  51 + }
  52 + },
  53 + computed: {
  54 + leftSyle() {
  55 + const style = {}
  56 + style.borderTopColor = this.leftBorderColor
  57 + style.borderRightColor = this.leftBorderColor
  58 + return style
  59 + },
  60 + rightSyle() {
  61 + const style = {}
  62 + style.borderLeftColor = this.rightBorderColor
  63 + style.borderBottomColor = this.rightBorderColor
  64 + return style
  65 + }
  66 + },
  67 + mounted() {
  68 + sleep().then(() => {
  69 + this.rightBorderColor = 'rgb(66, 185, 131)'
  70 + // this.init()
  71 + })
  72 + },
  73 + methods: {
  74 + init() {
  75 + animation.transition(this.$refs['right-circle'].ref, {
  76 + styles: {
  77 + transform: 'rotate(45deg)',
  78 + transformOrigin: 'center center'
  79 + },
  80 + }, () => {
  81 + this.rightBorderColor = 'rgb(66, 185, 131)'
  82 + // animation.transition(this.$refs['right-circle'].ref, {
  83 + // styles: {
  84 + // transform: 'rotate(225deg)',
  85 + // transformOrigin: 'center center'
  86 + // },
  87 + // duration: 3000,
  88 + // }, () => {
  89 + // animation.transition(this.$refs['left-circle'].ref, {
  90 + // styles: {
  91 + // transform: 'rotate(45deg)',
  92 + // transformOrigin: 'center center'
  93 + // },
  94 + // }, () => {
  95 + // this.leftBorderColor = 'rgb(66, 185, 131)'
  96 + // animation.transition(this.$refs['left-circle'].ref, {
  97 + // styles: {
  98 + // transform: 'rotate(225deg)',
  99 + // transformOrigin: 'center center'
  100 + // },
  101 + // duration: 1500,
  102 + // }, () => {
  103 +
  104 + // })
  105 + // })
  106 + // })
  107 + })
  108 +
  109 + }
  110 + },
  111 + }
  112 +</script>
  113 +
  114 +<style lang="scss" scoped>
  115 +
  116 + .u-circle-progress {
  117 + @include flex(row);
  118 + position: relative;
  119 + border-radius: 100px;
  120 + height: 100px;
  121 + width: 100px;
  122 + // transform: rotate(0deg);
  123 + // background-color: rgb(66, 185, 131);
  124 + background-color: rgb(200, 200, 200);
  125 + overflow: hidden;
  126 + justify-content: space-between;
  127 +
  128 + &__circle {
  129 + border-radius: 100px;
  130 + height: 90px;
  131 + width: 90px;
  132 + transform: translate(-50%, -50%);
  133 + background-color: rgb(255, 255, 255);
  134 + left: 50px;
  135 + top: 50px;
  136 + position: absolute;
  137 + }
  138 +
  139 + &__left {
  140 + position: absolute;
  141 + left: 0;
  142 + width: 50px;
  143 + height: 100px;
  144 + overflow: hidden;
  145 + box-sizing: border-box;
  146 + // background-color: rgb(66, 185, 131);
  147 + // background-color: rgb(200, 200, 200);
  148 + // transform-origin: left center;
  149 +
  150 + &__circle {
  151 + box-sizing: border-box;
  152 + // background-color: red;
  153 + border-left-color: transparent;
  154 + border-bottom-color: transparent;
  155 + border-top-left-radius: 50px;
  156 + border-top-right-radius: 50px;
  157 + border-bottom-right-radius: 50px;
  158 + // border-left-color: rgb(66, 185, 131);
  159 + // border-bottom-color: rgb(66, 185, 131);
  160 + border-top-color: rgb(66, 185, 131);
  161 + border-right-color: rgb(66, 185, 131);
  162 + border-width: 5px;
  163 + width: 100px;
  164 + height: 100px;
  165 + transform: rotate(225deg);
  166 + // border-radius: 100px;
  167 + }
  168 + }
  169 +
  170 + &__right {
  171 + position: absolute;
  172 + right: 0;
  173 + width: 50px;
  174 + height: 100px;
  175 + overflow: hidden;
  176 +
  177 + &__circle {
  178 + position: absolute;
  179 + right: 0;
  180 + box-sizing: border-box;
  181 + // background-color: red;
  182 + border-top-color: transparent;
  183 + border-right-color: transparent;
  184 + border-top-left-radius: 50px;
  185 + border-bottom-left-radius: 50px;
  186 + border-bottom-right-radius: 50px;
  187 + // border-left-color: rgb(66, 185, 131);
  188 + // border-bottom-color: rgb(66, 185, 131);
  189 + border-left-color: rgb(200, 200, 200);
  190 + border-bottom-color: rgb(200, 200, 200);
  191 + border-width: 5px;
  192 + width: 100px;
  193 + height: 100px;
  194 + transform: rotate(45deg);
  195 + transform-origin: center center;
  196 + // border-radius: 100px;
  197 + }
  198 + }
  199 + }
  200 +</style>
... ...
uni_modules/uview-plus/components/u-city-locate/u-city-locate.vue 0 → 100644
  1 +<template>
  2 + <view class="u-city-locate">
  3 + <up-index-list :indexList="indexList">
  4 + <template #header>
  5 + <view class="u-current-city-wrap">
  6 + <view class="u-current-city-title">{{ t("up.cityLocate.locateCity") }}</view>
  7 + <view class="u-current-city-item" @tap="location">
  8 + <view class="u-location-city">{{locationCity}}</view>
  9 + </view>
  10 + </view>
  11 + </template>
  12 + <template :key="index" v-for="(item, index) in cityList">
  13 + <!-- #ifdef APP-NVUE -->
  14 + <up-index-anchor :text="indexList[index]"></up-index-anchor>
  15 + <!-- #endif -->
  16 + <up-index-item>
  17 + <!-- #ifndef APP-NVUE -->
  18 + <up-index-anchor :text="indexList[index]"></up-index-anchor>
  19 + <!-- #endif -->
  20 + <view class="hot-city-list" v-if="index == 0">
  21 + <view class="" v-for="(item1, index1) in item" @tap="selectedCity(item1)">
  22 + <view class="hot-city-item">{{ item1[nameKey] }}</view>
  23 + </view>
  24 + </view>
  25 + <view v-else class="item-list" v-for="(item1, index1) in item" :key="index1">
  26 + <view class="list__item" @tap="selectedCity(item1)">
  27 + <text class="list__item__city-name">{{item1[nameKey]}}</text>
  28 + </view>
  29 + <up-line></up-line>
  30 + </view>
  31 + </up-index-item>
  32 + </template>
  33 + <template #footer>
  34 + <view class="u-safe-area-inset--bottom">
  35 + <text class="list__footer"></text>
  36 + </view>
  37 + </template>
  38 + </up-index-list>
  39 + </view>
  40 +</template>
  41 +
  42 +<script>
  43 + import { t } from '../../libs/i18n'
  44 + export default{
  45 + name: 'u-city-locate',
  46 + props:{
  47 + indexList: {
  48 + type: Array,
  49 + default: ['🔥']
  50 + },
  51 + cityList:{
  52 + type: Array,
  53 + default: () => {
  54 + return [
  55 + [{
  56 + name: '北京',
  57 + value: 'beijing'
  58 + },
  59 + {
  60 + name: '上海',
  61 + value: 'shanghai'
  62 + },
  63 + {
  64 + name: '广州',
  65 + value: 'guangzhou'
  66 + },
  67 + {
  68 + name: '深圳',
  69 + value: 'shenzhen'
  70 + },
  71 + {
  72 + name: '杭州',
  73 + value: 'hangzhou'
  74 + }]
  75 + ]
  76 + }
  77 + },
  78 + locationType: {
  79 + type: String,
  80 + default: 'wgs84'
  81 + },
  82 + currentCity: {
  83 + type: String,
  84 + default: ''
  85 + },
  86 + nameKey: {
  87 + type: String,
  88 + default: 'name'
  89 + }
  90 + },
  91 + computed:{
  92 + },
  93 + watch:{
  94 + currentCity(val) {
  95 + this.locationCity = val;
  96 + }
  97 + },
  98 + data(){
  99 + return{
  100 + locationCity: t("up.cityLocate.locating") + '....'
  101 + }
  102 + },
  103 + emits: ['location-success', 'select-city'],
  104 + methods:{
  105 + t,
  106 + // 获取城市
  107 + selectedCity(city){
  108 + this.locationCity = city[this.nameKey];
  109 + this.$emit('select-city', {
  110 + locationCity: this.locationCity
  111 + });
  112 + },
  113 + // 定位操作
  114 + location(){
  115 + let That = this;
  116 + uni.getLocation({
  117 + type: this.locationType,
  118 + geocode:true,
  119 + success(res){
  120 + console.log(res);
  121 + That.locationCity = res.address && res.address.city;
  122 + That.$emit('location-success', {
  123 + ...res,
  124 + locationCity: That.locationCity
  125 + });
  126 + },
  127 + fail(){
  128 + That.locationCity = t("up.cityLocate.fail");
  129 + }
  130 + });
  131 + },
  132 + },
  133 + // 页面挂载后进行异步操作
  134 + created(){
  135 + },
  136 + mounted(){
  137 + this.location();
  138 + }
  139 + }
  140 +</script>
  141 +
  142 +<style lang="scss">
  143 + .list__item {
  144 + padding: 8px 1px;
  145 + }
  146 + .u-current-city-title {
  147 + color: grey;
  148 + margin-bottom: 5px;
  149 + }
  150 + .u-current-city-item {
  151 + height: 30px;
  152 + }
  153 + .hot-city-list {
  154 + display: flex !important;
  155 + flex-direction: row !important;
  156 + padding: 12px 0;
  157 + .hot-city-item {
  158 + padding: 6px 12px;
  159 + margin: 5px;
  160 + border: 1px solid #ededed;
  161 + }
  162 + }
  163 +</style>
... ...
uni_modules/uview-plus/components/u-code-input/codeInput.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:55:58
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/codeInput.js
  9 + */
  10 +export default {
  11 + // codeInput 组件
  12 + codeInput: {
  13 + adjustPosition: true,
  14 + maxlength: 6,
  15 + dot: false,
  16 + mode: 'box',
  17 + hairline: false,
  18 + space: 10,
  19 + value: '',
  20 + focus: false,
  21 + bold: false,
  22 + color: '#606266',
  23 + fontSize: 18,
  24 + size: 35,
  25 + disabledKeyboard: false,
  26 + borderColor: '#c9cacc',
  27 + disabledDot: true
  28 + }
  29 +}
... ...
uni_modules/uview-plus/components/u-code-input/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 键盘弹起时,是否自动上推页面
  6 + adjustPosition: {
  7 + type: Boolean,
  8 + default: () => defProps.codeInput.adjustPosition
  9 + },
  10 + // 最大输入长度
  11 + maxlength: {
  12 + type: [String, Number],
  13 + default: () => defProps.codeInput.maxlength
  14 + },
  15 + // 是否用圆点填充
  16 + dot: {
  17 + type: Boolean,
  18 + default: () => defProps.codeInput.dot
  19 + },
  20 + // 显示模式,box-盒子模式,line-底部横线模式
  21 + mode: {
  22 + type: String,
  23 + default: () => defProps.codeInput.mode
  24 + },
  25 + // 是否细边框
  26 + hairline: {
  27 + type: Boolean,
  28 + default: () => defProps.codeInput.hairline
  29 + },
  30 + // 字符间的距离
  31 + space: {
  32 + type: [String, Number],
  33 + default: () => defProps.codeInput.space
  34 + },
  35 + // #ifdef VUE3
  36 + // 预置值
  37 + modelValue: {
  38 + type: [String, Number],
  39 + default: () => defProps.codeInput.value
  40 + },
  41 + // #endif
  42 + // #ifdef VUE2
  43 + // 预置值
  44 + value: {
  45 + type: [String, Number],
  46 + default: () => defProps.codeInput.value
  47 + },
  48 + // #endif
  49 + // 是否自动获取焦点
  50 + focus: {
  51 + type: Boolean,
  52 + default: () => defProps.codeInput.focus
  53 + },
  54 + // 字体是否加粗
  55 + bold: {
  56 + type: Boolean,
  57 + default: () => defProps.codeInput.bold
  58 + },
  59 + // 字体颜色
  60 + color: {
  61 + type: String,
  62 + default: () => defProps.codeInput.color
  63 + },
  64 + // 字体大小
  65 + fontSize: {
  66 + type: [String, Number],
  67 + default: () => defProps.codeInput.fontSize
  68 + },
  69 + // 输入框的大小,宽等于高
  70 + size: {
  71 + type: [String, Number],
  72 + default: () => defProps.codeInput.size
  73 + },
  74 + // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
  75 + disabledKeyboard: {
  76 + type: Boolean,
  77 + default: () => defProps.codeInput.disabledKeyboard
  78 + },
  79 + // 边框和线条颜色
  80 + borderColor: {
  81 + type: String,
  82 + default: () => defProps.codeInput.borderColor
  83 + },
  84 + // 是否禁止输入"."符号
  85 + disabledDot: {
  86 + type: Boolean,
  87 + default: () => defProps.codeInput.disabledDot
  88 + }
  89 + }
  90 +})
... ...
uni_modules/uview-plus/components/u-code-input/u-code-input.vue 0 → 100644
  1 +<template>
  2 + <view class="u-code-input">
  3 + <view
  4 + class="u-code-input__item"
  5 + :style="[itemStyle(index)]"
  6 + v-for="(item, index) in codeLength"
  7 + :key="index"
  8 + >
  9 + <view
  10 + class="u-code-input__item__dot"
  11 + v-if="dot && codeArray.length > index"
  12 + ></view>
  13 + <text
  14 + v-else
  15 + :style="{
  16 + fontSize: addUnit(fontSize),
  17 + fontWeight: bold ? 'bold' : 'normal',
  18 + color: color
  19 + }"
  20 + >{{codeArray[index]}}</text>
  21 + <view
  22 + class="u-code-input__item__line"
  23 + v-if="mode === 'line'"
  24 + :style="[lineStyle]"
  25 + ></view>
  26 + <!-- #ifndef APP-NVUE -->
  27 + <view v-if="isFocus && codeArray.length === index"
  28 + :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
  29 + <!-- #endif -->
  30 + <!-- #ifdef APP-NVUE -->
  31 + <view v-if="isFocus && codeArray.length === index"
  32 + :style="{backgroundColor: color, opacity: opacity}" class="u-code-input__item__cursor"></view>
  33 + <!-- #endif -->
  34 + </view>
  35 + <input
  36 + :disabled="disabledKeyboard"
  37 + type="number"
  38 + :focus="focus"
  39 + :value="inputValue"
  40 + :maxlength="maxlength"
  41 + :adjustPosition="adjustPosition"
  42 + class="u-code-input__input"
  43 + @input="inputHandler"
  44 + :style="{
  45 + height: addUnit(size)
  46 + }"
  47 + @focus="isFocus = true"
  48 + @blur="isFocus = false"
  49 + />
  50 + </view>
  51 +</template>
  52 +
  53 +<script>
  54 + import { props } from './props';
  55 + import { mpMixin } from '../../libs/mixin/mpMixin';
  56 + import { mixin } from '../../libs/mixin/mixin';
  57 + import { addUnit, getPx } from '../../libs/function/index';
  58 + /**
  59 + * CodeInput 验证码输入
  60 + * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uview-plus的键盘组件使用
  61 + * @tutorial https://uview-plus.jiangruyi.com/components/codeInput.html
  62 + * @property {String | Number} maxlength 最大输入长度 (默认 6 )
  63 + * @property {Boolean} dot 是否用圆点填充 (默认 false )
  64 + * @property {String} mode 显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
  65 + * @property {Boolean} hairline 是否细边框 (默认 false )
  66 + * @property {String | Number} space 字符间的距离 (默认 10 )
  67 + * @property {String | Number} value 预置值
  68 + * @property {Boolean} focus 是否自动获取焦点 (默认 false )
  69 + * @property {Boolean} bold 字体和输入横线是否加粗 (默认 false )
  70 + * @property {String} color 字体颜色 (默认 '#606266' )
  71 + * @property {String | Number} fontSize 字体大小,单位px (默认 18 )
  72 + * @property {String | Number} size 输入框的大小,宽等于高 (默认 35 )
  73 + * @property {Boolean} disabledKeyboard 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
  74 + * @property {String} borderColor 边框和线条颜色 (默认 '#c9cacc' )
  75 + * @property {Boolean} disabledDot 是否禁止输入"."符号 (默认 true )
  76 + *
  77 + * @event {Function} change 输入内容发生改变时触发,具体见上方说明 value:当前输入的值
  78 + * @event {Function} finish 输入字符个数达maxlength值时触发,见上方说明 value:当前输入的值
  79 + * @example <u-code-input v-model="value4" :focus="true"></u-code-input>
  80 + */
  81 + export default {
  82 + name: 'u-code-input',
  83 + mixins: [mpMixin, mixin, props],
  84 + data() {
  85 + return {
  86 + inputValue: '',
  87 + isFocus: this.focus,
  88 + timer: null,
  89 + opacity: 1
  90 + }
  91 + },
  92 + watch: {
  93 + // #ifdef VUE2
  94 + value: {
  95 + immediate: true,
  96 + handler(val) {
  97 + // 转为字符串,超出部分截掉
  98 + this.inputValue = String(val).substring(0, this.maxlength)
  99 + }
  100 + },
  101 + // #endif
  102 + // #ifdef VUE3
  103 + modelValue: {
  104 + immediate: true,
  105 + handler(val) {
  106 + // 转为字符串,超出部分截掉
  107 + this.inputValue = String(val).substring(0, this.maxlength)
  108 + }
  109 + },
  110 + // #endif
  111 + isFocus: {
  112 + handler(val) {
  113 + // #ifdef APP-NVUE
  114 + if (val) {
  115 + this.timer = setInterval(() => {
  116 + this.opacity = Math.abs(this.opacity - 1)
  117 + }, 600)
  118 + } else {
  119 + clearInterval(this.timer)
  120 + }
  121 + // #endif
  122 + }
  123 + }
  124 + },
  125 + created() {
  126 +
  127 + },
  128 + beforeUnmount() {
  129 + // #ifdef APP-NVUE
  130 + clearInterval(this.timer)
  131 + // #endif
  132 + },
  133 + computed: {
  134 + // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
  135 + codeLength() {
  136 + return new Array(Number(this.maxlength))
  137 + },
  138 + // 循环item的样式
  139 + itemStyle() {
  140 + return index => {
  141 + const style = {
  142 + width: addUnit(this.size),
  143 + height: addUnit(this.size)
  144 + }
  145 + // 盒子模式下,需要额外进行处理
  146 + if (this.mode === 'box') {
  147 + // 设置盒子的边框,如果是细边框,则设置为0.5px宽度
  148 + style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
  149 + // 如果盒子间距为0的话
  150 + if (getPx(this.space) === 0) {
  151 + // 给第一和最后一个盒子设置圆角
  152 + if (index === 0) {
  153 + style.borderTopLeftRadius = '3px'
  154 + style.borderBottomLeftRadius = '3px'
  155 + }
  156 + if (index === this.codeLength.length - 1) {
  157 + style.borderTopRightRadius = '3px'
  158 + style.borderBottomRightRadius = '3px'
  159 + }
  160 + // 最后一个盒子的右边框需要保留
  161 + if (index !== this.codeLength.length - 1) {
  162 + style.borderRight = 'none'
  163 + }
  164 + }
  165 + }
  166 + if (index !== this.codeLength.length - 1) {
  167 + // 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
  168 + style.marginRight = addUnit(this.space)
  169 + } else {
  170 + // 最后一个盒子的有边框需要保留
  171 + style.marginRight = 0
  172 + }
  173 +
  174 + return style
  175 + }
  176 + },
  177 + // 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
  178 + codeArray() {
  179 + return String(this.inputValue).split('')
  180 + },
  181 + // 下划线模式下,横线的样式
  182 + lineStyle() {
  183 + const style = {}
  184 + style.height = this.hairline ? '2px' : '4px'
  185 + style.width = addUnit(this.size)
  186 + // 线条模式下,背景色即为边框颜色
  187 + style.backgroundColor = this.borderColor
  188 + return style
  189 + }
  190 + },
  191 + emits: ["change", 'finish', "update:modelValue"],
  192 + methods: {
  193 + addUnit,
  194 + // 监听输入框的值发生变化
  195 + inputHandler(e) {
  196 + const value = e.detail.value
  197 + this.inputValue = value
  198 + // 是否允许输入“.”符号
  199 + if(this.disabledDot) {
  200 + this.$nextTick(() => {
  201 + this.inputValue = value.replace('.', '')
  202 + })
  203 + }
  204 + // 未达到maxlength之前,发送change事件,达到后发送finish事件
  205 + this.$emit('change', value)
  206 + // 修改通过v-model双向绑定的值
  207 + // #ifdef VUE3
  208 + this.$emit("update:modelValue", value);
  209 + // #endif
  210 + // #ifdef VUE2
  211 + this.$emit("input", value);
  212 + // #endif
  213 + // 达到用户指定输入长度时,发出完成事件
  214 + if (String(value).length >= Number(this.maxlength)) {
  215 + this.$emit('finish', value)
  216 + }
  217 + }
  218 + }
  219 + }
  220 +</script>
  221 +
  222 +<style lang="scss" scoped>
  223 + $u-code-input-cursor-width: 1px;
  224 + $u-code-input-cursor-height: 20px;
  225 + $u-code-input-cursor-animation-duration: 1s;
  226 + $u-code-input-cursor-animation-name: u-cursor-flicker;
  227 +
  228 + .u-code-input {
  229 + @include flex;
  230 + position: relative;
  231 + overflow: hidden;
  232 +
  233 + &__item {
  234 + @include flex;
  235 + justify-content: center;
  236 + align-items: center;
  237 + position: relative;
  238 +
  239 + &__text {
  240 + font-size: 15px;
  241 + color: $u-content-color;
  242 + }
  243 +
  244 + &__dot {
  245 + width: 7px;
  246 + height: 7px;
  247 + border-radius: 100px;
  248 + background-color: $u-content-color;
  249 + }
  250 +
  251 + &__line {
  252 + position: absolute;
  253 + bottom: 0;
  254 + height: 4px;
  255 + border-radius: 100px;
  256 + width: 40px;
  257 + background-color: $u-content-color;
  258 + }
  259 + &__cursor {
  260 + position: absolute;
  261 + /* #ifndef APP-NVUE */
  262 + top: 50%;
  263 + left: 50%;
  264 + opacity: 1;
  265 + transform: translate(-50%,-50%);
  266 + /* #endif */
  267 + width: $u-code-input-cursor-width;
  268 + height: $u-code-input-cursor-height;
  269 + animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
  270 + }
  271 + }
  272 +
  273 + &__input {
  274 + // 之所以需要input输入框,是因为有它才能唤起键盘
  275 + // 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
  276 + position: absolute;
  277 + left: -750rpx;
  278 + width: 1500rpx;
  279 + top: 0;
  280 + background-color: transparent;
  281 + text-align: left;
  282 + }
  283 + }
  284 +
  285 + /* #ifndef APP-NVUE */
  286 + @keyframes u-cursor-flicker {
  287 + 0% {
  288 + opacity: 0;
  289 + }
  290 + 50% {
  291 + opacity: 1;
  292 + }
  293 + 100% {
  294 + opacity: 0;
  295 + }
  296 + }
  297 + /* #endif */
  298 +
  299 +</style>
... ...
uni_modules/uview-plus/components/u-code/code.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:55:27
  8 + * @FilePath : /uview-plus/libs/config/props/code.js
  9 + */
  10 +import { t } from '../../libs/i18n'
  11 +export default {
  12 + // code 组件
  13 + code: {
  14 + seconds: 60,
  15 + startText: t("up.code.send"),
  16 + changeText: t("up.code.resendAfter"),
  17 + endText: t("up.code.resend"),
  18 + keepRunning: false,
  19 + uniqueKey: ''
  20 + }
  21 +}
... ...
uni_modules/uview-plus/components/u-code/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 倒计时总秒数
  6 + seconds: {
  7 + type: [String, Number],
  8 + default: () => defProps.code.seconds
  9 + },
  10 + // 尚未开始时提示
  11 + startText: {
  12 + type: String,
  13 + default: () => defProps.code.startText
  14 + },
  15 + // 正在倒计时中的提示
  16 + changeText: {
  17 + type: String,
  18 + default: () => defProps.code.changeText
  19 + },
  20 + // 倒计时结束时的提示
  21 + endText: {
  22 + type: String,
  23 + default: () => defProps.code.endText
  24 + },
  25 + // 是否在H5刷新或各端返回再进入时继续倒计时
  26 + keepRunning: {
  27 + type: Boolean,
  28 + default: () => defProps.code.keepRunning
  29 + },
  30 + // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
  31 + uniqueKey: {
  32 + type: String,
  33 + default: () => defProps.code.uniqueKey
  34 + }
  35 + }
  36 +})
... ...
uni_modules/uview-plus/components/u-code/u-code.vue 0 → 100644
  1 +<template>
  2 + <view class="u-code">
  3 + <!-- 此组件功能由js完成,无需写html逻辑 -->
  4 + </view>
  5 +</template>
  6 +
  7 +<script>
  8 + import { props } from './props';
  9 + import { mpMixin } from '../../libs/mixin/mpMixin';
  10 + import { mixin } from '../../libs/mixin/mixin';
  11 + /**
  12 + * Code 验证码输入框
  13 + * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景
  14 + * @tutorial https://uview-plus.jiangruyi.com/components/code.html
  15 + * @property {String | Number} seconds 倒计时所需的秒数(默认 60 )
  16 + * @property {String} startText 开始前的提示语,见官网说明(默认 '获取验证码' )
  17 + * @property {String} changeText 倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )
  18 + * @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' )
  19 + * @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
  20 + * @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
  21 + *
  22 + * @event {Function} change 倒计时期间,每秒触发一次
  23 + * @event {Function} start 开始倒计时触发
  24 + * @event {Function} end 结束倒计时触发
  25 + * @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code>
  26 + */
  27 + export default {
  28 + name: "u-code",
  29 + mixins: [mpMixin, mixin,props],
  30 + data() {
  31 + return {
  32 + secNum: this.seconds,
  33 + timer: null,
  34 + canGetCode: true, // 是否可以执行验证码操作
  35 + }
  36 + },
  37 + mounted() {
  38 + this.checkKeepRunning()
  39 + },
  40 + watch: {
  41 + seconds: {
  42 + immediate: true,
  43 + handler(n) {
  44 + this.secNum = n
  45 + }
  46 + }
  47 + },
  48 + emits: ["start", "end", "change"],
  49 + methods: {
  50 + checkKeepRunning() {
  51 + // 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
  52 + let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
  53 + if(!lastTimestamp) return this.changeEvent(this.startText)
  54 + // 当前秒的时间戳
  55 + let nowTimestamp = Math.floor((+ new Date()) / 1000)
  56 + // 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
  57 + if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
  58 + // 剩余尚未执行完的倒计秒数
  59 + this.secNum = lastTimestamp - nowTimestamp
  60 + // 清除本地保存的变量
  61 + uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
  62 + // 开始倒计时
  63 + this.start()
  64 + } else {
  65 + // 如果不存在需要继续上一次的倒计时,执行正常的逻辑
  66 + this.changeEvent(this.startText)
  67 + }
  68 + },
  69 + // 开始倒计时
  70 + start() {
  71 + // 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
  72 + if(this.timer) {
  73 + clearInterval(this.timer)
  74 + this.timer = null
  75 + }
  76 + this.$emit('start')
  77 + this.canGetCode = false
  78 + // 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
  79 + this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
  80 + this.timer = setInterval(() => {
  81 + if (--this.secNum) {
  82 + // 用当前倒计时的秒数替换提示字符串中的"x"字母
  83 + this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
  84 + } else {
  85 + clearInterval(this.timer)
  86 + this.timer = null
  87 + this.changeEvent(this.endText)
  88 + this.secNum = this.seconds
  89 + this.$emit('end')
  90 + this.canGetCode = true
  91 + }
  92 + }, 1000)
  93 + this.setTimeToStorage()
  94 + },
  95 + // 重置,可以让用户再次获取验证码
  96 + reset() {
  97 + this.canGetCode = true
  98 + clearInterval(this.timer)
  99 + this.secNum = this.seconds
  100 + this.changeEvent(this.endText)
  101 + },
  102 + changeEvent(text) {
  103 + this.$emit('change', text)
  104 + },
  105 + // 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
  106 + setTimeToStorage() {
  107 + if(!this.keepRunning || !this.timer) return
  108 + // 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
  109 + // 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
  110 + if(this.secNum > 0 && this.secNum <= this.seconds) {
  111 + // 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
  112 + let nowTimestamp = Math.floor((+ new Date()) / 1000)
  113 + // 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
  114 + uni.setStorage({
  115 + key: this.uniqueKey + '_$uCountDownTimestamp',
  116 + data: nowTimestamp + Number(this.secNum)
  117 + })
  118 + }
  119 + }
  120 + },
  121 + // 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
  122 + beforeUnmount() {
  123 + this.setTimeToStorage()
  124 + clearTimeout(this.timer)
  125 + this.timer = null
  126 + }
  127 + }
  128 +</script>
  129 +
  130 +<style lang="scss" scoped>
  131 +</style>
... ...
uni_modules/uview-plus/components/u-col/col.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:56:12
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/col.js
  9 + */
  10 +export default {
  11 + // col 组件
  12 + col: {
  13 + span: 12,
  14 + offset: 0,
  15 + justify: 'start',
  16 + align: 'stretch',
  17 + textAlign: 'left'
  18 + }
  19 +}
... ...
uni_modules/uview-plus/components/u-col/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 占父容器宽度的多少等分,总分为12份
  6 + span: {
  7 + type: [String, Number],
  8 + default: () => defProps.col.span
  9 + },
  10 + // 指定栅格左侧的间隔数(总12栏)
  11 + offset: {
  12 + type: [String, Number],
  13 + default: () => defProps.col.offset
  14 + },
  15 + // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
  16 + justify: {
  17 + type: String,
  18 + default: () => defProps.col.justify
  19 + },
  20 + // 垂直对齐方式,可选值为top、center、bottom、stretch
  21 + align: {
  22 + type: String,
  23 + default: () => defProps.col.align
  24 + },
  25 + // 文字对齐方式
  26 + textAlign: {
  27 + type: String,
  28 + default: () => defProps.col.textAlign
  29 + }
  30 + }
  31 +})
... ...
uni_modules/uview-plus/components/u-col/u-col.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-col"
  4 + ref="u-col"
  5 + :class="[
  6 + 'u-col-' + span
  7 + ]"
  8 + :style="[colStyle]"
  9 + @tap="clickHandler"
  10 + >
  11 + <slot></slot>
  12 + </view>
  13 +</template>
  14 +
  15 +<script>
  16 + import { props } from './props';
  17 + import { mpMixin } from '../../libs/mixin/mpMixin';
  18 + import { mixin } from '../../libs/mixin/mixin';
  19 + import { addStyle, addUnit, deepMerge, getPx } from '../../libs/function/index';
  20 + /**
  21 + * CodeInput 栅格系统的列
  22 + * @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局
  23 + * @tutorial https://uview-plus.jiangruyi.com/components/Layout.html
  24 + * @property {String | Number} span 栅格占据的列数,总12等份 (默认 12 )
  25 + * @property {String | Number} offset 分栏左边偏移,计算方式与span相同 (默认 0 )
  26 + * @property {String} justify 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) (默认 'start' )
  27 + * @property {String} align 垂直对齐方式,可选值为top、center、bottom、stretch (默认 'stretch' )
  28 + * @property {String} textAlign 文字水平对齐方式 (默认 'left' )
  29 + * @property {Object} customStyle 定义需要用到的外部样式
  30 + * @event {Function} click col被点击,会阻止事件冒泡到row
  31 + * @example <u-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col>
  32 + */
  33 + export default {
  34 + name: 'u-col',
  35 + mixins: [mpMixin, mixin, props],
  36 + data() {
  37 + return {
  38 + width: 0,
  39 + parentData: {
  40 + gutter: 0
  41 + },
  42 + gridNum: 12
  43 + }
  44 + },
  45 + // 微信小程序中 options 选项
  46 + options: {
  47 + virtualHost: true // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等
  48 + },
  49 + computed: {
  50 + uJustify() {
  51 + if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify
  52 + else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify
  53 + else return this.justify
  54 + },
  55 + uAlignItem() {
  56 + if (this.align == 'top') return 'flex-start'
  57 + if (this.align == 'bottom') return 'flex-end'
  58 + else return this.align
  59 + },
  60 + colStyle() {
  61 + const style = {
  62 + // 这里写成"padding: 0 10px"的形式是因为nvue的需要
  63 + paddingLeft: addUnit(getPx(this.parentData.gutter)/2),
  64 + paddingRight: addUnit(getPx(this.parentData.gutter)/2),
  65 + alignItems: this.uAlignItem,
  66 + justifyContent: this.uJustify,
  67 + textAlign: this.textAlign,
  68 + // #ifndef APP-NVUE
  69 + // 在非nvue上,使用百分比形式
  70 + flex: `0 0 ${100 / this.gridNum * this.span}%`,
  71 + marginLeft: 100 / 12 * this.offset + '%',
  72 + // #endif
  73 + // #ifdef APP-NVUE
  74 + // 在nvue上,由于无法使用百分比单位,这里需要获取父组件的宽度,再计算得出该有对应的百分比尺寸
  75 + width: addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),
  76 + marginLeft: addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),
  77 + // #endif
  78 + }
  79 + return deepMerge(style, addStyle(this.customStyle))
  80 + }
  81 + },
  82 + mounted() {
  83 + this.init()
  84 + },
  85 + emits: ["click"],
  86 + methods: {
  87 + async init() {
  88 + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
  89 + this.updateParentData()
  90 + this.width = await this.parent.getComponentWidth()
  91 + },
  92 + updateParentData() {
  93 + this.getParentData('u-row')
  94 + },
  95 + clickHandler(e) {
  96 + this.$emit('click');
  97 + }
  98 + },
  99 + }
  100 +</script>
  101 +
  102 +<style lang="scss" scoped>
  103 +
  104 + .u-col {
  105 + padding: 0;
  106 + /* #ifndef APP-NVUE */
  107 + box-sizing:border-box;
  108 + /* #endif */
  109 + /* #ifdef MP */
  110 + display: block;
  111 + /* #endif */
  112 + }
  113 +
  114 + // nvue下百分比无效
  115 + /* #ifndef APP-NVUE */
  116 + .u-col-0 {
  117 + width: 0;
  118 + }
  119 +
  120 + .u-col-1 {
  121 + width: calc(100%/12);
  122 + }
  123 +
  124 + .u-col-2 {
  125 + width: calc(100%/12 * 2);
  126 + }
  127 +
  128 + .u-col-3 {
  129 + width: calc(100%/12 * 3);
  130 + }
  131 +
  132 + .u-col-4 {
  133 + width: calc(100%/12 * 4);
  134 + }
  135 +
  136 + .u-col-5 {
  137 + width: calc(100%/12 * 5);
  138 + }
  139 +
  140 + .u-col-6 {
  141 + width: calc(100%/12 * 6);
  142 + }
  143 +
  144 + .u-col-7 {
  145 + width: calc(100%/12 * 7);
  146 + }
  147 +
  148 + .u-col-8 {
  149 + width: calc(100%/12 * 8);
  150 + }
  151 +
  152 + .u-col-9 {
  153 + width: calc(100%/12 * 9);
  154 + }
  155 +
  156 + .u-col-10 {
  157 + width: calc(100%/12 * 10);
  158 + }
  159 +
  160 + .u-col-11 {
  161 + width: calc(100%/12 * 11);
  162 + }
  163 +
  164 + .u-col-12 {
  165 + width: calc(100%/12 * 12);
  166 + }
  167 +
  168 + /* #endif */
  169 +</style>
... ...
uni_modules/uview-plus/components/u-collapse-item/collapseItem.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:56:42
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapseItem.js
  9 + */
  10 +export default {
  11 + // collapseItem 组件
  12 + collapseItem: {
  13 + title: '',
  14 + value: '',
  15 + label: '',
  16 + disabled: false,
  17 + isLink: true,
  18 + clickable: true,
  19 + border: true,
  20 + align: 'left',
  21 + name: '',
  22 + icon: '',
  23 + duration: 300,
  24 + showRight: true,
  25 + titleStyle: {},
  26 + iconStyle: {},
  27 + rightIconStyle: {},
  28 + cellCustomStyle: {},
  29 + cellCustomClass: ''
  30 + }
  31 +}
... ...
uni_modules/uview-plus/components/u-collapse-item/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 标题
  6 + title: {
  7 + type: String,
  8 + default: () => defProps.collapseItem.title
  9 + },
  10 + // 标题的样式
  11 + titleStyle: {
  12 + type: [Object, String],
  13 + default: () => {
  14 + return defProps.collapseItem.titleStyle
  15 + }
  16 + },
  17 + // 标题右侧内容
  18 + value: {
  19 + type: String,
  20 + default: () => defProps.collapseItem.value
  21 + },
  22 + // 标题下方的描述信息
  23 + label: {
  24 + type: String,
  25 + default: () => defProps.collapseItem.label
  26 + },
  27 + // 是否禁用折叠面板
  28 + disabled: {
  29 + type: Boolean,
  30 + default: () => defProps.collapseItem.disabled
  31 + },
  32 + // 是否展示右侧箭头并开启点击反馈
  33 + isLink: {
  34 + type: Boolean,
  35 + default: () => defProps.collapseItem.isLink
  36 + },
  37 + // 是否开启点击反馈
  38 + clickable: {
  39 + type: Boolean,
  40 + default: () => defProps.collapseItem.clickable
  41 + },
  42 + // 是否显示内边框
  43 + border: {
  44 + type: Boolean,
  45 + default: () => defProps.collapseItem.border
  46 + },
  47 + // 标题的对齐方式
  48 + align: {
  49 + type: String,
  50 + default: () => defProps.collapseItem.align
  51 + },
  52 + // 唯一标识符
  53 + name: {
  54 + type: [String, Number],
  55 + default: () => defProps.collapseItem.name
  56 + },
  57 + // 标题左侧图片,可为绝对路径的图片或内置图标
  58 + icon: {
  59 + type: String,
  60 + default: () => defProps.collapseItem.icon
  61 + },
  62 + // 面板展开收起的过渡时间,单位ms
  63 + duration: {
  64 + type: Number,
  65 + default: () => defProps.collapseItem.duration
  66 + },
  67 + // 显示右侧图标
  68 + showRight: {
  69 + type: Boolean,
  70 + default: () => defProps.collapseItem.showRight
  71 + },
  72 + // 左侧图标样式
  73 + iconStyle: {
  74 + type: [Object, String],
  75 + default: () => {
  76 + return defProps.collapseItem.iconStyle
  77 + }
  78 + },
  79 + // 右侧箭头图标的样式
  80 + rightIconStyle: {
  81 + type: [Object, String],
  82 + default: () => {
  83 + return defProps.collapseItem.rightIconStyle
  84 + }
  85 + },
  86 + cellCustomStyle: {
  87 + type: [Object, String],
  88 + default: () => {
  89 + return defProps.collapseItem.cellCustomStyle
  90 + }
  91 + },
  92 + cellCustomClass: {
  93 + type: String,
  94 + default: () => defProps.collapseItem.cellCustomClass
  95 + }
  96 + }
  97 +})
... ...
uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue 0 → 100644
  1 +<template>
  2 + <view class="u-collapse-item">
  3 + <u-cell
  4 + :title="$slots.title ? '' : title"
  5 + :value="value"
  6 + :label="label"
  7 + :icon="icon"
  8 + :isLink="isLink"
  9 + :clickable="clickable"
  10 + :border="parentData.border && showBorder"
  11 + @click="clickHandler"
  12 + :arrowDirection="expanded ? 'up' : 'down'"
  13 + :disabled="disabled"
  14 + :customClass="cellCustomClass"
  15 + :customStyle="cellCustomStyle"
  16 + >
  17 + <!-- 微信小程序不支持,因为微信中不支持 <slot name="title" #title />的写法 -->
  18 + <template #title>
  19 + <slot name="title">
  20 + <text v-if="!$slots.title && title">
  21 + {{title}}
  22 + </text>
  23 + </slot>
  24 + </template>
  25 + <template #icon>
  26 + <slot name="icon">
  27 + <up-icon v-if="!$slots.icon && icon" :size="22" :name="icon"></up-icon>
  28 + </slot>
  29 + </template>
  30 + <template #value>
  31 + <slot name="value">
  32 + <text v-if="!$slots.value && value">
  33 + {{value}}
  34 + </text>
  35 + </slot>
  36 + </template>
  37 + <template #right-icon>
  38 + <template v-if="showRight">
  39 + <up-icon v-if="!$slots['right-icon']" :size="16" name="arrow-right"></up-icon>
  40 + <slot name="right-icon">
  41 + </slot>
  42 + </template>
  43 + </template>
  44 + </u-cell>
  45 + <view
  46 + class="u-collapse-item__content"
  47 + :animation="animationData"
  48 + ref="animation"
  49 + >
  50 + <view
  51 + class="u-collapse-item__content__text content-class"
  52 + :id="elId"
  53 + :ref="elId"
  54 + ><slot /></view>
  55 + </view>
  56 + <u-line v-if="parentData.border"></u-line>
  57 + </view>
  58 +</template>
  59 +
  60 +<script>
  61 + import { props } from './props.js';
  62 + import { mpMixin } from '../../libs/mixin/mpMixin';
  63 + import { mixin } from '../../libs/mixin/mixin';
  64 + import { nextTick } from 'vue';
  65 + import { guid, sleep, error } from '../../libs/function/index';
  66 + import test from '../../libs/function/test';
  67 + // #ifdef APP-NVUE
  68 + const animation = uni.requireNativePlugin('animation')
  69 + const dom = uni.requireNativePlugin('dom')
  70 + // #endif
  71 + /**
  72 + * collapseItem 折叠面板Item
  73 + * @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
  74 + * @tutorial https://uview-plus.jiangruyi.com/components/collapse.html
  75 + * @property {String} title 标题
  76 + * @property {String} value 标题右侧内容
  77 + * @property {String} label 标题下方的描述信息
  78 + * @property {Boolean} disbled 是否禁用折叠面板 ( 默认 false )
  79 + * @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 ( 默认 true )
  80 + * @property {Boolean} clickable 是否开启点击反馈 ( 默认 true )
  81 + * @property {Boolean} border 是否显示内边框 ( 默认 true )
  82 + * @property {String} align 标题的对齐方式 ( 默认 'left' )
  83 + * @property {String | Number} name 唯一标识符
  84 + * @property {String} icon 标题左侧图片,可为绝对路径的图片或内置图标
  85 + * @event {Function} change 某个item被打开或者收起时触发
  86 + * @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
  87 + */
  88 + export default {
  89 + name: "u-collapse-item",
  90 + mixins: [mpMixin, mixin, props],
  91 + data() {
  92 + return {
  93 + elId: guid(),
  94 + // uni.createAnimation的导出数据
  95 + animationData: {},
  96 + // 是否展开状态
  97 + expanded: false,
  98 + // 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
  99 + showBorder: false,
  100 + // 是否动画中,如果是则不允许继续触发点击
  101 + animating: false,
  102 + // 父组件u-collapse的参数
  103 + parentData: {
  104 + accordion: false,
  105 + border: false
  106 + }
  107 + };
  108 + },
  109 + watch: {
  110 + expanded(n) {
  111 + clearTimeout(this.timer)
  112 + this.timer = null
  113 + // 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果
  114 + this.timer = setTimeout(() => {
  115 + this.showBorder = n
  116 + }, n ? 10 : 290)
  117 + }
  118 + },
  119 + mounted() {
  120 + this.init()
  121 + // console.log('$slots', this.$slots)
  122 + },
  123 + methods: {
  124 + // 异步获取内容,或者动态修改了内容时,需要重新初始化
  125 + async init() {
  126 + // 初始化数据
  127 + this.updateParentData()
  128 + if (!this.parent) {
  129 + return error('u-collapse-item必须要搭配u-collapse组件使用')
  130 + }
  131 + const {
  132 + value,
  133 + accordion,
  134 + children = []
  135 + } = this.parent
  136 +
  137 + if (accordion) {
  138 + if (test.array(value)) {
  139 + return error('手风琴模式下,u-collapse组件的value参数不能为数组')
  140 + }
  141 + this.expanded = this.name == value
  142 + } else {
  143 + if (!test.array(value) && value !== null) {
  144 + return error('非手风琴模式下,u-collapse组件的value参数必须为数组')
  145 + }
  146 + this.expanded = (value || []).some(item => item == this.name)
  147 + }
  148 + // 设置组件的展开或收起状态
  149 + await nextTick()
  150 + this.setContentAnimate()
  151 + },
  152 + updateParentData() {
  153 + // 此方法在mixin中
  154 + this.getParentData('u-collapse')
  155 + },
  156 + async setContentAnimate() {
  157 + // 每次面板打开或者收起时,都查询元素尺寸
  158 + // 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
  159 + const rect = await this.queryRect()
  160 + const height = this.expanded ? rect.height : 0
  161 + this.animating = true
  162 + // #ifdef APP-NVUE
  163 + const ref = this.$refs['animation'].ref
  164 + animation.transition(ref, {
  165 + styles: {
  166 + height: height + 'px'
  167 + },
  168 + duration: this.duration,
  169 + // 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局
  170 + needLayout: true,
  171 + timingFunction: 'ease-in-out',
  172 + }, () => {
  173 + this.animating = false
  174 + })
  175 + // #endif
  176 +
  177 + // #ifndef APP-NVUE
  178 + const animation = uni.createAnimation({
  179 + timingFunction: 'ease-in-out',
  180 + });
  181 + animation
  182 + .height(height)
  183 + .step({
  184 + duration: this.duration,
  185 + })
  186 + .step()
  187 + // 导出动画数据给面板的animationData值
  188 + this.animationData = animation.export()
  189 + // 标识动画结束
  190 + sleep(this.duration).then(() => {
  191 + this.animating = false
  192 + })
  193 + // #endif
  194 + },
  195 + // 点击collapsehead头部
  196 + clickHandler() {
  197 + if (this.disabled && this.animating) return
  198 + // 设置本组件为相反的状态
  199 + this.parent && this.parent.onChange(this)
  200 + },
  201 + // 查询内容高度
  202 + queryRect() {
  203 + // #ifndef APP-NVUE
  204 + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview-plus.jiangruyi.com/js/getRect.html
  205 + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
  206 + return new Promise(resolve => {
  207 + this.$uGetRect(`#${this.elId}`).then(size => {
  208 + resolve(size)
  209 + })
  210 + })
  211 + // #endif
  212 +
  213 + // #ifdef APP-NVUE
  214 + // nvue下,使用dom模块查询元素高度
  215 + // 返回一个promise,让调用此方法的主体能使用then回调
  216 + return new Promise(resolve => {
  217 + dom.getComponentRect(this.$refs[this.elId], res => {
  218 + resolve(res.size)
  219 + })
  220 + })
  221 + // #endif
  222 + }
  223 + },
  224 + };
  225 +</script>
  226 +
  227 +<style lang="scss" scoped>
  228 +
  229 + .u-collapse-item {
  230 +
  231 + &__content {
  232 + overflow: hidden;
  233 + height: 0;
  234 +
  235 + &__text {
  236 + padding: 12px 15px;
  237 + color: $u-content-color;
  238 + font-size: 14px;
  239 + line-height: 18px;
  240 + }
  241 + }
  242 + }
  243 +</style>
... ...
uni_modules/uview-plus/components/u-collapse/collapse.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:56:30
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapse.js
  9 + */
  10 +export default {
  11 + // collapse 组件
  12 + collapse: {
  13 + value: null,
  14 + accordion: false,
  15 + border: true
  16 + }
  17 +}
... ...
uni_modules/uview-plus/components/u-collapse/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
  6 + value: {
  7 + type: [String, Number, Array, null],
  8 + default: () => defProps.collapse.value
  9 + },
  10 + // 是否手风琴模式
  11 + accordion: {
  12 + type: Boolean,
  13 + default: () => defProps.collapse.accordion
  14 + },
  15 + // 是否显示外边框
  16 + border: {
  17 + type: Boolean,
  18 + default: () => defProps.collapse.border
  19 + }
  20 + }
  21 +})
... ...
uni_modules/uview-plus/components/u-collapse/u-collapse.vue 0 → 100644
  1 +<template>
  2 + <view class="u-collapse">
  3 + <u-line v-if="border"></u-line>
  4 + <slot />
  5 + </view>
  6 +</template>
  7 +
  8 +<script>
  9 + import { props } from './props';
  10 + import { mpMixin } from '../../libs/mixin/mpMixin';
  11 + import { mixin } from '../../libs/mixin/mixin';
  12 + /**
  13 + * collapse 折叠面板
  14 + * @description 通过折叠面板收纳内容区域
  15 + * @tutorial https://uview-plus.jiangruyi.com/components/collapse.html
  16 + * @property {String | Number | Array} value 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
  17 + * @property {Boolean} accordion 是否手风琴模式( 默认 false )
  18 + * @property {Boolean} border 是否显示外边框 ( 默认 true )
  19 + * @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
  20 + * @example <u-collapse></u-collapse>
  21 + */
  22 + export default {
  23 + name: "u-collapse",
  24 + mixins: [mpMixin, mixin,props],
  25 + watch: {
  26 + needInit() {
  27 + this.init()
  28 + },
  29 + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
  30 + parentData() {
  31 + if (this.children.length) {
  32 + this.children.map(child => {
  33 + // 判断子组件(u-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
  34 + typeof(child.updateParentData) === 'function' && child.updateParentData()
  35 + })
  36 + }
  37 + }
  38 + },
  39 + created() {
  40 + this.children = []
  41 + },
  42 + computed: {
  43 + needInit() {
  44 + // 通过computed,同时监听accordion和value值的变化
  45 + // 再通过watch去执行init()方法,进行再一次的初始化
  46 + return [this.accordion, this.value]
  47 + }
  48 + },
  49 + emits: ["open", "close", "change"],
  50 + methods: {
  51 + // 重新初始化一次内部的所有子元素
  52 + init() {
  53 + this.children.map(child => {
  54 + child.init()
  55 + })
  56 + },
  57 + /**
  58 + * collapse-item被点击时触发,由collapse统一处理各子组件的状态
  59 + * @param {Object} target 被操作的面板的实例
  60 + */
  61 + onChange(target) {
  62 + let changeArr = []
  63 + this.children.map((child, index) => {
  64 + // 如果是手风琴模式,将其他的折叠面板收起来
  65 + if (this.accordion) {
  66 + child.expanded = child === target ? !target.expanded : false
  67 + child.setContentAnimate()
  68 + } else {
  69 + if(child === target) {
  70 + child.expanded = !child.expanded
  71 + child.setContentAnimate()
  72 + }
  73 + }
  74 + // 拼接change事件中,数组元素的状态
  75 + changeArr.push({
  76 + // 如果没有定义name属性,则默认返回组件的index索引
  77 + name: child.name || index,
  78 + status: child.expanded ? 'open' : 'close'
  79 + })
  80 + })
  81 +
  82 + this.$emit('change', changeArr)
  83 + this.$emit(target.expanded ? 'open' : 'close', target.name)
  84 + }
  85 + }
  86 + }
  87 +</script>
  88 +
  89 +<style lang="scss" scoped>
  90 +</style>
... ...
uni_modules/uview-plus/components/u-color-picker/u-color-picker.vue 0 → 100644
  1 +<template>
  2 + <view class="up-color-picker">
  3 + <view clas="up-color-picker__trigger" @click="show = true"
  4 + :style="{backgroundColor: value}" >
  5 + <slot></slot>
  6 + </view>
  7 + <up-popup :show="show" mode="bottom" round="10" @close="close" :closeOnClickOverlay="true">
  8 + <view class="up-color-picker__content">
  9 + <view class="up-color-picker__header">
  10 + <text class="up-color-picker__title">选择颜色</text>
  11 + </view>
  12 +
  13 + <!-- 纯色/渐变色切换 -->
  14 + <view class="up-color-picker__switch">
  15 + <up-subsection
  16 + :list="[{ name: '纯色' }, { name: '渐变' }]"
  17 + :current="colorTypeIndex"
  18 + @change="changeColorType"
  19 + fontSize="14"
  20 + ></up-subsection>
  21 + </view>
  22 +
  23 + <!-- 渐变色选择器 -->
  24 + <view v-if="colorTypeIndex == 1" class="up-color-picker__gradient">
  25 + <!-- 渐变色控制条 -->
  26 + <view
  27 + class="up-color-picker__gradient-track"
  28 + :style="{ background: gradientStyle }"
  29 + >
  30 + <view
  31 + class="up-color-picker__gradient-pointer"
  32 + v-for="(item, index) in gradientColors"
  33 + :key="index"
  34 + :style="{ left: getGradientPointerPosition(index) + 'px' }"
  35 + @click="openColorPickerForGradient(index)"
  36 + @touchstart="onPointerTouchStart($event, index)"
  37 + @touchmove.stop="onPointerTouchMove"
  38 + @touchend.stop="onPointerTouchEnd"
  39 + >
  40 + <view class="up-color-picker__gradient-pointer-inner" :style="{ backgroundColor: item.color }"></view>
  41 + </view>
  42 + </view>
  43 +
  44 + <view class="up-color-picker__gradient-controls">
  45 + <up-button
  46 + type="primary"
  47 + size="mini"
  48 + plain
  49 + @click="addGradientColor"
  50 + class="up-color-picker__add-btn"
  51 + >
  52 + 添加颜色
  53 + </up-button>
  54 + </view>
  55 +
  56 + <!-- 圆形方向选择器 -->
  57 + <view class="up-color-picker__gradient-direction">
  58 + <text>方向:</text>
  59 + <view class="up-color-picker__gradient__direction-circle"
  60 + @touchstart="onDirectionTouchStart"
  61 + @touchmove="onDirectionTouchMove"
  62 + @touchend="onDirectionTouchEnd">
  63 + <view
  64 + class="up-color-picker__direction-pointer"
  65 + :style="{
  66 + left: directionPointer.x + 'px',
  67 + top: directionPointer.y + 'px'
  68 + }"
  69 + ></view>
  70 + </view>
  71 + </view>
  72 + </view>
  73 +
  74 + <!-- 纯色选择器 -->
  75 + <view class="up-color-picker__solid">
  76 + <!-- 饱和度和明度选择区域 -->
  77 + <view
  78 + class="up-color-picker__saturation"
  79 + :style="{ backgroundColor: `hsl(${hue}, 100%, 50%)` }"
  80 + @touchstart="onSaturationTouchStart"
  81 + @touchmove="onSaturationTouchMove"
  82 + @touchend="onSaturationTouchEnd"
  83 + >
  84 + <view
  85 + class="up-color-picker__saturation-pointer"
  86 + :style="{
  87 + left: saturationPosition.x + 'px',
  88 + top: saturationPosition.y + 'px'
  89 + }"
  90 + ></view>
  91 + </view>
  92 +
  93 + <!-- 色相选择 -->
  94 + <view
  95 + class="up-color-picker__hue"
  96 + @touchstart="onHueTouchStart"
  97 + @touchmove="onHueTouchMove"
  98 + @touchend="onHueTouchEnd"
  99 + >
  100 + <view
  101 + class="up-color-picker__hue-pointer"
  102 + :style="{ left: huePosition + 'px' }"
  103 + ></view>
  104 + </view>
  105 +
  106 + <!-- 透明度选择 -->
  107 + <view v-if="colorTypeIndex == 0"
  108 + class="up-color-picker__alpha"
  109 + @touchstart="onAlphaTouchStart"
  110 + @touchmove="onAlphaTouchMove"
  111 + @touchend="onAlphaTouchEnd"
  112 + >
  113 + <view class="up-color-picker__alpha-bg"></view>
  114 + <view
  115 + class="up-color-picker__alpha-pointer"
  116 + :style="{ left: alphaPosition + 'px' }"
  117 + ></view>
  118 + </view>
  119 + </view>
  120 +
  121 + <!-- 常用颜色 -->
  122 + <view v-if="commonColors && commonColors.length" class="up-color-picker__common">
  123 + <text class="up-color-picker__common-title">常用颜色</text>
  124 + <view class="up-color-picker__common-list">
  125 + <view
  126 + v-for="(color, index) in commonColors"
  127 + :key="index"
  128 + class="up-color-picker__common-item"
  129 + :style="{ backgroundColor: color }"
  130 + @click="selectCommonColor(color)"
  131 + ></view>
  132 + </view>
  133 + </view>
  134 +
  135 + <!-- 颜色预览和操作按钮 -->
  136 + <view class="up-color-picker__footer">
  137 + <view class="up-color-picker__preview">
  138 + <view
  139 + class="up-color-picker__preview-color"
  140 + :style="{ backgroundColor: displayColor }"
  141 + ></view>
  142 + <text class="up-color-picker__preview-text">{{ displayColor }}</text>
  143 + </view>
  144 + <view class="up-color-picker__actions">
  145 + <up-button
  146 + type="primary"
  147 + size="small"
  148 + @click="confirm"
  149 + class="up-color-picker__btn"
  150 + >
  151 + 确定
  152 + </up-button>
  153 + <up-button
  154 + type="info"
  155 + size="small"
  156 + @click="close"
  157 + class="up-color-picker__btn"
  158 + >
  159 + 取消
  160 + </up-button>
  161 + </view>
  162 + </view>
  163 + </view>
  164 + </up-popup>
  165 + </view>
  166 +</template>
  167 +
  168 +<script>
  169 +export default {
  170 + name: 'up-color-picker',
  171 + props: {
  172 + // 初始颜色值
  173 + modelValue: {
  174 + type: String,
  175 + default: '#ff0000'
  176 + },
  177 + // 常用颜色列表
  178 + commonColors: {
  179 + type: Array,
  180 + default: () => [
  181 + ]
  182 + }
  183 + },
  184 + data() {
  185 + return {
  186 + show: false,
  187 + // 颜色类型索引:0-纯色,1-渐变色
  188 + colorTypeIndex: 0,
  189 + // 纯色相关数据
  190 + hue: 0,
  191 + saturation: 100,
  192 + lightness: 50,
  193 + alpha: 1,
  194 + saturationPosition: { x: 150, y: 0 },
  195 + huePosition: 0,
  196 + alphaPosition: 0, // 将在initColor中设置为最右侧
  197 + // 渐变色相关数据
  198 + gradientColors: [
  199 + { color: '#ff0000', percent: 0 },
  200 + { color: '#0000ff', percent: 1 }
  201 + ],
  202 + gradientDirections: [
  203 + { label: '从左到右', value: 'to right' },
  204 + { label: '从上到下', value: 'to bottom' },
  205 + { label: '从左上到右下', value: 'to bottom right' },
  206 + { label: '从右上到左下', value: 'to bottom left' }
  207 + ],
  208 + currentDirection: { label: '从左到右', value: 'to right' },
  209 + showDirectionPicker: false,
  210 + // 当前选中颜色
  211 + currentColor: '#ff0000',
  212 + // 渐变色控制相关
  213 + draggingPointerIndex: -1,
  214 + directionPointer: { x: 20, y: 20 }, // 默认向右(在圆的右侧)
  215 + // 渐变节点颜色修改相关
  216 + editingGradientIndex: -1,
  217 + // 保存纯色和渐变色的各自状态
  218 + solidColorState: {
  219 + hue: 0,
  220 + saturation: 100,
  221 + lightness: 50,
  222 + alpha: 1,
  223 + saturationPosition: { x: 150, y: 0 },
  224 + huePosition: 0,
  225 + alphaPosition: 0, // 将在initColor中设置为最右侧
  226 + currentColor: '#ff0000'
  227 + },
  228 + gradientColorState: {
  229 + gradientColors: [
  230 + { color: '#ff0000', percent: 0 },
  231 + { color: '#0000ff', percent: 1 }
  232 + ],
  233 + currentDirection: { label: '从左到右', value: 'to right' },
  234 + directionPointer: { x: 100, y: 0 }
  235 + },
  236 + // 区分预览类型:solid-纯色预览,gradient-整体渐变预览,gradient-point-渐变点预览
  237 + previewType: 'solid'
  238 + }
  239 + },
  240 + computed: {
  241 + // 渐变色样式
  242 + gradientStyle() {
  243 + const colors = this.gradientColors.map(item => `${item.color} ${Math.round(item.percent * 100)}%`).join(', ')
  244 + return `linear-gradient(${this.currentDirection.value}, ${colors})`
  245 + },
  246 + // 显示的颜色预览值
  247 + displayColor() {
  248 + if (this.previewType === 'gradient-point' && this.editingGradientIndex >= 0) {
  249 + return this.gradientColors[this.editingGradientIndex].color;
  250 + }
  251 + console.log(this.editingGradientIndex)
  252 + return this.currentColor;
  253 + }
  254 + },
  255 + watch: {
  256 + show(newVal) {
  257 + if (newVal) {
  258 + this.initColor()
  259 + }
  260 + },
  261 + colorTypeIndex(newVal) {
  262 + if (newVal == 0) {
  263 + this.editingGradientIndex = -1
  264 + }
  265 + },
  266 + },
  267 + mounted() {
  268 + this.initColor()
  269 + },
  270 + emits: ['update:modelValue', 'confirm', 'close'],
  271 + methods: {
  272 + // 初始化颜色
  273 + initColor() {
  274 + if (this.modelValue) {
  275 + this.currentColor = this.modelValue
  276 + if (this.modelValue.includes('linear-gradient')) {
  277 + // 解析渐变色
  278 + this.colorTypeIndex = 1
  279 + this.parseGradientColor(this.modelValue)
  280 + this.previewType = 'gradient';
  281 + } else {
  282 + // 解析纯色
  283 + this.colorTypeIndex = 0
  284 + this.parseSolidColor(this.modelValue)
  285 + this.previewType = 'solid';
  286 + }
  287 + }
  288 + // 初始化方向指针位置
  289 + this.initDirectionPointer();
  290 + // 初始化alphaPosition为最右侧
  291 + this.initAlphaPosition();
  292 + },
  293 +
  294 + // 初始化alpha位置为最右侧
  295 + async initAlphaPosition() {
  296 + const query = uni.createSelectorQuery().in(this);
  297 + query.select('.up-color-picker__alpha').boundingClientRect();
  298 + await this.$nextTick();
  299 + query.exec(res => {
  300 + const rect = res[0];
  301 + if (rect) {
  302 + this.alphaPosition = rect.width || 150; // 默认150像素
  303 + } else {
  304 + this.alphaPosition = 150; // 默认值
  305 + }
  306 +
  307 + // 同步solidColorState中的alphaPosition
  308 + this.solidColorState.alphaPosition = this.alphaPosition;
  309 + this.updateSolidColor();
  310 + });
  311 + },
  312 +
  313 + // 初始化方向指针位置
  314 + initDirectionPointer() {
  315 + const angle = this.getDirectionAngle(this.currentDirection.value);
  316 + this.setDirectionPointerByAngle(angle);
  317 + },
  318 +
  319 + // 根据方向值获取角度
  320 + getDirectionAngle(direction) {
  321 + switch(direction) {
  322 + case 'to right': return 0;
  323 + case 'to bottom': return 90;
  324 + case 'to left': return 180;
  325 + case 'to top': return 270;
  326 + case 'to bottom right': return 45;
  327 + case 'to bottom left': return 135;
  328 + case 'to top left': return 225;
  329 + case 'to top right': return 315;
  330 + default: return 0;
  331 + }
  332 + },
  333 +
  334 + // 根据角度设置方向指针位置
  335 + setDirectionPointerByAngle(angle) {
  336 + const radian = angle * Math.PI / 180;
  337 + const radius = 20; // 圆半径
  338 + this.directionPointer = {
  339 + x: radius * Math.cos(radian) + 20, // 20是圆心位置
  340 + y: radius * Math.sin(radian) + 20
  341 + };
  342 + },
  343 +
  344 + // 打开颜色选择器以修改渐变节点颜色
  345 + openColorPickerForGradient(index) {
  346 + this.editingGradientIndex = index;
  347 + // 设置当前颜色为选中节点的颜色
  348 + const color = this.gradientColors[index].color;
  349 + this.currentColor = color;
  350 + // 设置预览类型为渐变点预览
  351 + this.previewType = 'gradient-point';
  352 +
  353 + // 解析颜色并设置选择器位置
  354 + if (!color.includes('linear-gradient')) {
  355 + this.colorTypeIndex = 0;
  356 + this.parseSolidColor(color);
  357 + // 保存当前渐变状态
  358 + this.gradientColorState = {
  359 + gradientColors: [...this.gradientColors],
  360 + currentDirection: {...this.currentDirection},
  361 + directionPointer: {...this.directionPointer}
  362 + };
  363 + }
  364 + },
  365 +
  366 + // 解析纯色
  367 + parseSolidColor(color) {
  368 + // 简化处理,实际项目中可以使用更复杂的颜色解析
  369 + this.currentColor = color
  370 + // 如果正在编辑渐变节点,则更新该节点颜色
  371 + if (this.editingGradientIndex >= 0) {
  372 + this.gradientColors[this.editingGradientIndex].color = color;
  373 + // 切换回渐变模式并恢复渐变状态
  374 + this.colorTypeIndex = 1;
  375 + Object.assign(this, this.solidColorState);
  376 + this.previewType = 'gradient-point';
  377 + } else {
  378 + this.previewType = 'solid';
  379 + }
  380 + },
  381 +
  382 + // 解析渐变色
  383 + parseGradientColor(gradient) {
  384 + // 简化处理,实际项目中可以使用更复杂的渐变解析
  385 + this.currentColor = gradient
  386 + // this.previewType = 'gradient';
  387 + },
  388 +
  389 + // 切换颜色类型
  390 + changeColorType(index) {
  391 + // 保存当前状态
  392 + if (this.colorTypeIndex === 0) {
  393 + // 保存纯色状态
  394 + this.solidColorState = {
  395 + hue: this.hue,
  396 + saturation: this.saturation,
  397 + lightness: this.lightness,
  398 + alpha: this.alpha,
  399 + saturationPosition: {...this.saturationPosition},
  400 + huePosition: this.huePosition,
  401 + alphaPosition: this.alphaPosition,
  402 + currentColor: this.currentColor
  403 + };
  404 + } else {
  405 + // 保存渐变状态
  406 + this.gradientColorState = {
  407 + gradientColors: [...this.gradientColors],
  408 + currentDirection: {...this.currentDirection},
  409 + directionPointer: {...this.directionPointer}
  410 + };
  411 + }
  412 +
  413 + this.colorTypeIndex = index;
  414 +
  415 + // 恢复目标状态
  416 + if (index === 0) {
  417 + Object.assign(this, this.solidColorState);
  418 + this.previewType = 'solid';
  419 + } else {
  420 + Object.assign(this, this.gradientColorState);
  421 + // 确保gradientColors是响应式的
  422 + this.gradientColors = [...this.gradientColorState.gradientColors];
  423 + this.previewType = 'gradient';
  424 + }
  425 + },
  426 +
  427 + // 饱和度和明度触摸开始
  428 + onSaturationTouchStart(e) {
  429 + this.updateSaturationPosition(e)
  430 + },
  431 +
  432 + // 饱和度和明度触摸移动
  433 + onSaturationTouchMove(e) {
  434 + this.updateSaturationPosition(e)
  435 + },
  436 +
  437 + // 饱和度和明度触摸结束
  438 + onSaturationTouchEnd(e) {
  439 + this.updateSaturationPosition(e)
  440 + },
  441 +
  442 + // 更新饱和度和明度位置
  443 + updateSaturationPosition(e) {
  444 + const touch = e.touches[0] || e.changedTouches[0]
  445 + const target = e.currentTarget
  446 + const query = uni.createSelectorQuery().in(this);
  447 + query.select('.up-color-picker__saturation').boundingClientRect()
  448 +
  449 + query.exec(res => {
  450 + const rect = res[0];
  451 + if (rect) {
  452 + let x = touch.clientX - rect.left
  453 + let y = touch.clientY - rect.top
  454 +
  455 + // 边界处理
  456 + x = Math.max(0, Math.min(x, rect.width))
  457 + y = Math.max(0, Math.min(y, rect.height))
  458 +
  459 + this.saturationPosition = { x, y }
  460 + this.updateSolidColor()
  461 + }
  462 + })
  463 + },
  464 +
  465 + // 色相触摸开始
  466 + onHueTouchStart(e) {
  467 + this.updateHuePosition(e)
  468 + },
  469 +
  470 + // 色相触摸移动
  471 + onHueTouchMove(e) {
  472 + this.updateHuePosition(e)
  473 + },
  474 +
  475 + // 色相触摸结束
  476 + onHueTouchEnd(e) {
  477 + this.updateHuePosition(e)
  478 + },
  479 +
  480 + // 更新色相位置
  481 + updateHuePosition(e) {
  482 + const touch = e.touches[0] || e.changedTouches[0]
  483 + const target = e.currentTarget
  484 + const query = uni.createSelectorQuery().in(this);
  485 + query.select('.up-color-picker__hue').boundingClientRect()
  486 +
  487 + query.exec(res => {
  488 + const rect = res[0];
  489 + if (rect) {
  490 + let x = touch.clientX - rect.left
  491 + x = Math.max(0, Math.min(x, rect.width))
  492 + this.huePosition = x
  493 + this.hue = Math.round((x / rect.width) * 360)
  494 + this.updateSolidColor()
  495 + }
  496 + })
  497 + },
  498 +
  499 + // 透明度触摸开始
  500 + onAlphaTouchStart(e) {
  501 + this.updateAlphaPosition(e)
  502 + },
  503 +
  504 + // 透明度触摸移动
  505 + onAlphaTouchMove(e) {
  506 + this.updateAlphaPosition(e)
  507 + },
  508 +
  509 + // 透明度触摸结束
  510 + onAlphaTouchEnd(e) {
  511 + this.updateAlphaPosition(e)
  512 + },
  513 +
  514 + // 更新透明度位置
  515 + updateAlphaPosition(e) {
  516 + const touch = e.touches[0] || e.changedTouches[0]
  517 + const target = e.currentTarget
  518 + const query = uni.createSelectorQuery().in(this);
  519 + query.select('.up-color-picker__alpha').boundingClientRect()
  520 +
  521 + query.exec(res => {
  522 + const rect = res[0];
  523 + if (rect) {
  524 + let x = touch.clientX - rect.left
  525 + x = Math.max(0, Math.min(x, rect.width))
  526 + this.alphaPosition = x
  527 + this.alpha = x / rect.width
  528 + this.updateSolidColor()
  529 + }
  530 + })
  531 + },
  532 +
  533 + // 更新纯色
  534 + updateSolidColor() {
  535 + // 使用实际元素尺寸替代硬编码的150
  536 + const query = uni.createSelectorQuery().in(this);
  537 + query.select('.up-color-picker__saturation').boundingClientRect();
  538 +
  539 + query.exec(res => {
  540 + const rect = res[0];
  541 + const size = rect ? Math.min(rect.width, rect.height) : 150; // 默认150作为后备值
  542 +
  543 + const s = (this.saturationPosition.x / size) * 100
  544 + const l = 100 - (this.saturationPosition.y / size) * 100
  545 + this.saturation = s
  546 + this.lightness = l
  547 +
  548 + // 使用正确的HSL转RGB算法
  549 + if (this.colorTypeIndex == 0) {
  550 + this.currentColor = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha)
  551 + } else if (this.colorTypeIndex == 1) {
  552 + this.gradientColors[this.editingGradientIndex].color
  553 + = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha)
  554 + }
  555 + });
  556 + },
  557 +
  558 + // 添加渐变色
  559 + addGradientColor() {
  560 + if (this.gradientColors.length < 5) {
  561 + this.gradientColors.push({
  562 + color: '#ffffff',
  563 + percent: 1
  564 + })
  565 + }
  566 + },
  567 +
  568 + // 删除渐变色
  569 + removeGradientColor(index) {
  570 + if (this.gradientColors.length > 2) {
  571 + this.gradientColors.splice(index, 1)
  572 + }
  573 + },
  574 +
  575 + // 获取渐变控制点位置
  576 + getGradientPointerPosition(index) {
  577 + const trackWidth = 280; // 需要与样式中的宽度一致
  578 + return this.gradientColors[index].percent * trackWidth;
  579 + },
  580 +
  581 + // 更新渐变色
  582 + updateGradientColor(e) {
  583 + const touch = e.touches[0] || e.changedTouches[0];
  584 + const query = uni.createSelectorQuery().in(this);
  585 + query.select('.up-color-picker__gradient-track').boundingClientRect();
  586 +
  587 + query.exec(res => {
  588 + const rect = res[0];
  589 + if (rect) {
  590 + let x = touch.clientX - rect.left;
  591 + // 边界处理
  592 + x = Math.max(0, Math.min(x, rect.width));
  593 + const percent = x / rect.width;
  594 +
  595 + // 如果正在拖动控制点,则更新该控制点位置
  596 + if (this.draggingPointerIndex >= 0) {
  597 + this.gradientColors[this.draggingPointerIndex].percent = percent;
  598 + // 保持控制点顺序
  599 + this.gradientColors.sort((a, b) => a.percent - b.percent);
  600 + } else {
  601 + // 否则添加新的控制点(此处可选功能)
  602 + }
  603 + }
  604 + });
  605 + },
  606 +
  607 + // 控制点触摸开始
  608 + onPointerTouchStart(e, index) {
  609 + this.draggingPointerIndex = index;
  610 + // 点击控制点时更新预览颜色
  611 + this.currentColor = this.gradientColors[index].color;
  612 + this.previewType = 'gradient-point';
  613 + this.editingGradientIndex = index; // 添加这行以正确设置编辑索引
  614 + // 阻止事件冒泡
  615 + e.stopPropagation();
  616 + },
  617 +
  618 + // 控制点触摸移动
  619 + onPointerTouchMove(e) {
  620 + if (this.draggingPointerIndex === -1) return;
  621 +
  622 + const touch = e.touches[0] || e.changedTouches[0];
  623 + const query = uni.createSelectorQuery().in(this);
  624 + query.select('.up-color-picker__gradient-track').boundingClientRect();
  625 +
  626 + query.exec(res => {
  627 + const rect = res[0];
  628 + if (rect) {
  629 + let x = touch.clientX - rect.left;
  630 + // 边界处理
  631 + x = Math.max(0, Math.min(x, rect.width));
  632 + let percent = x / rect.width;
  633 +
  634 + // 处理边界情况,确保能精确到达两端
  635 + if (x === 0) percent = 0;
  636 + if (x === rect.width) percent = 1;
  637 +
  638 + // 更新控制点位置
  639 + this.gradientColors[this.draggingPointerIndex].percent = percent;
  640 + // 保持控制点顺序
  641 + this.gradientColors.sort((a, b) => a.percent - b.percent);
  642 + // 更新拖拽索引以匹配排序后的索引
  643 + this.draggingPointerIndex = this.gradientColors.findIndex((item, index) => {
  644 + // 使用更精确的比较方法,处理浮点数精度问题
  645 + return Math.abs(item.percent - percent) < 0.0001;
  646 + });
  647 + }
  648 + });
  649 + },
  650 +
  651 + // 控制点触摸结束
  652 + onPointerTouchEnd() {
  653 + this.draggingPointerIndex = -1;
  654 + },
  655 +
  656 + // 方向选择器触摸开始
  657 + onDirectionTouchStart(e) {
  658 + this.updateDirection(e);
  659 + },
  660 +
  661 + // 方向选择器触摸移动
  662 + onDirectionTouchMove(e) {
  663 + this.updateDirection(e);
  664 + },
  665 +
  666 + // 方向选择器触摸结束
  667 + onDirectionTouchEnd(e) {
  668 + this.updateDirection(e);
  669 + },
  670 +
  671 + // 更新方向
  672 + updateDirection(e) {
  673 + const touch = e.touches[0] || e.changedTouches[0];
  674 + const query = uni.createSelectorQuery().in(this);
  675 + query.select('.up-color-picker__gradient__direction-circle').boundingClientRect();
  676 +
  677 + query.exec(res => {
  678 + const rect = res[0];
  679 + if (rect) {
  680 + const centerX = rect.left + rect.width / 2;
  681 + const centerY = rect.top + rect.height / 2;
  682 + const x = touch.clientX - centerX;
  683 + const y = touch.clientY - centerY;
  684 + const distance = Math.sqrt(x * x + y * y);
  685 + const maxDistance = rect.width / 2;
  686 +
  687 + // 限制在圆内
  688 + if (distance <= maxDistance) {
  689 + this.directionPointer = {
  690 + x: x + rect.width / 2,
  691 + y: y + rect.height / 2
  692 + };
  693 + } else {
  694 + // 限制在圆周上
  695 + const ratio = maxDistance / distance;
  696 + this.directionPointer = {
  697 + x: x * ratio + rect.width / 2,
  698 + y: y * ratio + rect.height / 2
  699 + };
  700 + }
  701 +
  702 + // 计算角度并更新方向
  703 + const angle = Math.atan2(y, x) * 180 / Math.PI;
  704 + console.log(angle)
  705 + this.updateGradientDirection(angle);
  706 + }
  707 + });
  708 + },
  709 +
  710 + // 根据角度更新渐变方向
  711 + updateGradientDirection(angle) {
  712 + // 角度转换为标准方向
  713 + if (angle < 0) angle += 360;
  714 +
  715 + if (angle >= 315 || angle < 45) {
  716 + this.currentDirection = { label: '从左到右', value: 'to right' };
  717 + } else if (angle >= 45 && angle < 135) {
  718 + this.currentDirection = { label: '从上到下', value: 'to bottom' };
  719 + } else if (angle >= 135 && angle < 225) {
  720 + this.currentDirection = { label: '从右到左', value: 'to left' };
  721 + } else {
  722 + this.currentDirection = { label: '从下到上', value: 'to top' };
  723 + }
  724 + },
  725 +
  726 + // 确认方向选择
  727 + confirmDirection(e) {
  728 + this.currentDirection = this.gradientDirections[e.index]
  729 + this.showDirectionPicker = false
  730 + },
  731 +
  732 + // 选择常用颜色
  733 + selectCommonColor(color) {
  734 + this.currentColor = color
  735 + if (this.colorTypeIndex === 0) {
  736 + this.parseSolidColor(color)
  737 + } else {
  738 + // 如果是渐变模式,将常用颜色作为渐变的一个节点
  739 + this.gradientColors[this.editingGradientIndex].color = color
  740 + }
  741 + },
  742 +
  743 + // 确认选择
  744 + confirm() {
  745 + let color = this.currentColor
  746 + if (this.colorTypeIndex === 1) {
  747 + color = this.gradientStyle
  748 + }
  749 + this.$emit('update:modelValue', color)
  750 + this.show = false;
  751 + this.$emit('confirm', color)
  752 + // 重置编辑状态
  753 + this.editingGradientIndex = -1;
  754 + this.previewType = this.colorTypeIndex === 0 ? 'solid' : 'gradient';
  755 + this.close()
  756 + },
  757 +
  758 + // 关闭选择器
  759 + close() {
  760 + this.show = false;
  761 + this.$emit('close')
  762 + },
  763 +
  764 + // HSL转RGB辅助函数
  765 + hslToRgb(h, s, l, a = 1) {
  766 + h = h / 360;
  767 + s = s / 100;
  768 + l = l / 100;
  769 +
  770 + let r, g, b;
  771 +
  772 + if (s === 0) {
  773 + r = g = b = l; // achromatic
  774 + } else {
  775 + const hue2rgb = (p, q, t) => {
  776 + if (t < 0) t += 1;
  777 + if (t > 1) t -= 1;
  778 + if (t < 1/6) return p + (q - p) * 6 * t;
  779 + if (t < 1/2) return q;
  780 + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  781 + return p;
  782 + };
  783 +
  784 + const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  785 + const p = 2 * l - q;
  786 + r = hue2rgb(p, q, h + 1/3);
  787 + g = hue2rgb(p, q, h);
  788 + b = hue2rgb(p, q, h - 1/3);
  789 + }
  790 +
  791 + const round = (value) => Math.round(value * 255);
  792 + return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${a.toFixed(2)})`;
  793 + }
  794 + }
  795 +}
  796 +</script>
  797 +
  798 +<style lang="scss" scoped>
  799 +.up-color-picker {
  800 + &__content {
  801 + width: 100%;
  802 + padding: 20px;
  803 + background-color: #fff;
  804 + }
  805 +
  806 + &__header {
  807 + text-align: center;
  808 + margin-bottom: 20px;
  809 + }
  810 +
  811 + &__title {
  812 + font-size: 18px;
  813 + font-weight: bold;
  814 + color: #333;
  815 + }
  816 +
  817 + &__switch {
  818 + margin-bottom: 20px;
  819 + }
  820 +
  821 + &__saturation {
  822 + position: relative;
  823 + width: 100%;
  824 + height: 150px;
  825 + border-radius: 4px;
  826 + margin-bottom: 15px;
  827 + overflow: hidden;
  828 +
  829 + &:after {
  830 + content: '';
  831 + position: absolute;
  832 + top: 0;
  833 + left: 0;
  834 + right: 0;
  835 + bottom: 0;
  836 + background: linear-gradient(to right, #fff, rgba(255,255,255,0));
  837 + }
  838 +
  839 + &:before {
  840 + content: '';
  841 + position: absolute;
  842 + top: 0;
  843 + left: 0;
  844 + right: 0;
  845 + bottom: 0;
  846 + background: linear-gradient(to top, #000, rgba(0,0,0,0));
  847 + }
  848 + }
  849 +
  850 + &__saturation-pointer {
  851 + position: absolute;
  852 + width: 12px;
  853 + height: 12px;
  854 + border: 2px solid #fff;
  855 + border-radius: 50%;
  856 + transform: translate(-50%, -50%);
  857 + box-shadow: 0 0 2px rgba(0,0,0,0.5);
  858 + pointer-events: none;
  859 + }
  860 +
  861 + &__hue,
  862 + &__alpha {
  863 + position: relative;
  864 + width: 100%;
  865 + height: 12px;
  866 + border-radius: 6px;
  867 + margin-bottom: 15px;
  868 + cursor: pointer;
  869 + }
  870 +
  871 + &__hue {
  872 + background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%);
  873 + }
  874 +
  875 + &__alpha {
  876 + position: relative;
  877 + overflow: hidden;
  878 +
  879 + &-bg {
  880 + position: absolute;
  881 + top: 0;
  882 + left: 0;
  883 + right: 0;
  884 + bottom: 0;
  885 + background: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%),
  886 + linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%);
  887 + background-size: 10px 10px;
  888 + background-position: 0 0, 5px 5px;
  889 + }
  890 +
  891 + &:after {
  892 + content: '';
  893 + position: absolute;
  894 + top: 0;
  895 + left: 0;
  896 + right: 0;
  897 + bottom: 0;
  898 + background: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,1));
  899 + }
  900 + }
  901 +
  902 + &__hue-pointer,
  903 + &__alpha-pointer {
  904 + position: absolute;
  905 + width: 4px;
  906 + height: 16px;
  907 + background: #fff;
  908 + border: 1px solid #ccc;
  909 + border-radius: 2px;
  910 + transform: translateX(-50%);
  911 + top: -2px;
  912 + box-shadow: 0 0 2px rgba(0,0,0,0.5);
  913 + pointer-events: none;
  914 + }
  915 +
  916 + &__gradient {
  917 + &-bar {
  918 + width: 100%;
  919 + height: 40px;
  920 + border-radius: 4px;
  921 + }
  922 +
  923 + &-track {
  924 + position: relative;
  925 + margin-top: 20px;
  926 + width: 100%;
  927 + height: 32px;
  928 + border-radius: 4px;
  929 + margin-bottom: 15px;
  930 + cursor: pointer;
  931 + }
  932 +
  933 + &-pointer {
  934 + position: absolute;
  935 + top: -10px;
  936 + transform: translateX(-50%);
  937 + width: 0;
  938 + height: 0;
  939 + border-left: 8px solid transparent;
  940 + border-right: 8px solid transparent;
  941 + border-top: 10px solid #333;
  942 + z-index: 10;
  943 + }
  944 +
  945 + &-pointer-inner {
  946 + position: absolute;
  947 + top: -25px;
  948 + left: -10px;
  949 + width: 20px;
  950 + height: 20px;
  951 + border-radius: 50%;
  952 + border: 2px solid #fff;
  953 + box-shadow: 0 0 2px rgba(0,0,0,0.5);
  954 + }
  955 +
  956 + &-controls {
  957 + display: flex;
  958 + flex-direction: row;
  959 + flex-wrap: wrap;
  960 + align-items: center;
  961 + margin-bottom: 15px;
  962 + }
  963 +
  964 + &-item {
  965 + display: flex;
  966 + flex-direction: row;
  967 + align-items: center;
  968 + margin-right: 10px;
  969 + margin-bottom: 10px;
  970 + }
  971 +
  972 + &-color-preview {
  973 + width: 20px;
  974 + height: 20px;
  975 + border-radius: 50%;
  976 + margin-right: 5px;
  977 + border: 1px solid #eee;
  978 + }
  979 +
  980 + &-percent {
  981 + font-size: 12px;
  982 + color: #666;
  983 + margin-right: 5px;
  984 + }
  985 +
  986 + &-direction {
  987 + display: flex;
  988 + flex-direction: row;
  989 + align-items: center;
  990 + margin: 10px 0;
  991 + }
  992 +
  993 + &__direction-circle {
  994 + width: 40px;
  995 + height: 40px;
  996 + border-radius: 50%;
  997 + background: conic-gradient(
  998 + #ff0000 0deg,
  999 + #ffff00 60deg,
  1000 + #00ff00 120deg,
  1001 + #00ffff 180deg,
  1002 + #0000ff 240deg,
  1003 + #ff00ff 300deg,
  1004 + #ff0000 360deg
  1005 + );
  1006 + position: relative;
  1007 + margin: 10px auto;
  1008 + cursor: pointer;
  1009 + border: 2px solid #eee;
  1010 + }
  1011 +
  1012 + &__direction-pointer {
  1013 + position: absolute;
  1014 + width: 6px;
  1015 + height: 6px;
  1016 + background: #fff;
  1017 + border: 2px solid #333;
  1018 + border-radius: 50%;
  1019 + transform: translate(-50%, -50%);
  1020 + box-shadow: 0 0 2px rgba(0,0,0,0.5);
  1021 + z-index: 10;
  1022 + pointer-events: none;
  1023 + }
  1024 + }
  1025 +
  1026 + &__add-btn {
  1027 + margin-top: 10px;
  1028 + }
  1029 +
  1030 + &__common {
  1031 + margin-top: 20px;
  1032 +
  1033 + &-title {
  1034 + display: block;
  1035 + margin-bottom: 10px;
  1036 + font-size: 14px;
  1037 + color: #666;
  1038 + }
  1039 +
  1040 + &-list {
  1041 + display: flex;
  1042 + flex-direction: row;
  1043 + flex-wrap: wrap;
  1044 + }
  1045 +
  1046 + &-item {
  1047 + width: 24px;
  1048 + height: 24px;
  1049 + border-radius: 50%;
  1050 + margin-right: 10px;
  1051 + margin-bottom: 10px;
  1052 + border: 1px solid #eee;
  1053 + cursor: pointer;
  1054 + }
  1055 + }
  1056 +
  1057 + &__footer {
  1058 + margin-top: 20px;
  1059 + }
  1060 +
  1061 + &__preview {
  1062 + display: flex;
  1063 + flex-direction: row;
  1064 + align-items: center;
  1065 + margin-bottom: 15px;
  1066 + }
  1067 +
  1068 + &__preview-color {
  1069 + width: 40px;
  1070 + height: 40px;
  1071 + border-radius: 4px;
  1072 + border: 1px solid #eee;
  1073 + margin-right: 10px;
  1074 + }
  1075 +
  1076 + &__preview-text {
  1077 + font-size: 14px;
  1078 + color: #333;
  1079 + flex: 1;
  1080 + overflow: hidden;
  1081 + text-overflow: ellipsis;
  1082 + white-space: nowrap;
  1083 + }
  1084 +
  1085 + &__actions {
  1086 + display: flex;
  1087 + flex-direction: row;
  1088 + justify-content: flex-end;
  1089 +
  1090 + & .up-color-picker__btn {
  1091 + margin-left: 10px;
  1092 + }
  1093 + }
  1094 +}
  1095 +</style>
... ...
uni_modules/uview-plus/components/u-column-notice/columnNotice.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:57:16
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/columnNotice.js
  9 + */
  10 +export default {
  11 + // columnNotice 组件
  12 + columnNotice: {
  13 + text: '',
  14 + icon: 'volume',
  15 + mode: '',
  16 + color: '#f9ae3d',
  17 + bgColor: '#fdf6ec',
  18 + fontSize: 14,
  19 + speed: 80,
  20 + step: false,
  21 + duration: 1500,
  22 + disableTouch: true,
  23 + justifyContent: 'flex-start'
  24 + }
  25 +}
... ...
uni_modules/uview-plus/components/u-column-notice/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 显示的内容,字符串
  6 + text: {
  7 + type: [Array],
  8 + default: () => defProps.columnNotice.text
  9 + },
  10 + // 是否显示左侧的音量图标
  11 + icon: {
  12 + type: String,
  13 + default: () => defProps.columnNotice.icon
  14 + },
  15 + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标
  16 + mode: {
  17 + type: String,
  18 + default: () => defProps.columnNotice.mode
  19 + },
  20 + // 文字颜色,各图标也会使用文字颜色
  21 + color: {
  22 + type: String,
  23 + default: () => defProps.columnNotice.color
  24 + },
  25 + // 背景颜色
  26 + bgColor: {
  27 + type: String,
  28 + default: () => defProps.columnNotice.bgColor
  29 + },
  30 + // 字体大小,单位px
  31 + fontSize: {
  32 + type: [String, Number],
  33 + default: () => defProps.columnNotice.fontSize
  34 + },
  35 + // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度
  36 + speed: {
  37 + type: [String, Number],
  38 + default: () => defProps.columnNotice.speed
  39 + },
  40 + // direction = row时,是否使用步进形式滚动
  41 + step: {
  42 + type: Boolean,
  43 + default: () => defProps.columnNotice.step
  44 + },
  45 + // 滚动一个周期的时间长,单位ms
  46 + duration: {
  47 + type: [String, Number],
  48 + default: () => defProps.columnNotice.duration
  49 + },
  50 + // 是否禁止用手滑动切换
  51 + // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
  52 + disableTouch: {
  53 + type: Boolean,
  54 + default: () => defProps.columnNotice.disableTouch
  55 + },
  56 + justifyContent: {
  57 + type: String,
  58 + default: () => defProps.columnNotice.justifyContent
  59 + }
  60 + }
  61 +})
... ...
uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue 0 → 100644
  1 +<template>
  2 + <view
  3 + class="u-notice"
  4 + @tap="clickHandler"
  5 + >
  6 + <slot name="icon">
  7 + <view
  8 + class="u-notice__left-icon"
  9 + v-if="icon"
  10 + >
  11 + <up-icon
  12 + :name="icon"
  13 + :color="color"
  14 + size="19"
  15 + ></up-icon>
  16 + </view>
  17 + </slot>
  18 + <swiper
  19 + :disable-touch="disableTouch"
  20 + :vertical="step ? false : true"
  21 + circular
  22 + :interval="duration"
  23 + :autoplay="true"
  24 + class="u-notice__swiper"
  25 + @change="noticeChange"
  26 + >
  27 + <swiper-item
  28 + v-for="(item, index) in text"
  29 + :key="index"
  30 + class="u-notice__swiper__item"
  31 + :style="{'justifyContent': justifyContent}"
  32 + >
  33 + <text
  34 + class="u-notice__swiper__item__text u-line-1"
  35 + :style="[textStyle]"
  36 + >{{ item }}</text>
  37 + </swiper-item>
  38 + </swiper>
  39 + <view
  40 + class="u-notice__right-icon"
  41 + v-if="['link', 'closable'].includes(mode)"
  42 + >
  43 + <up-icon
  44 + v-if="mode === 'link'"
  45 + name="arrow-right"
  46 + :size="17"
  47 + :color="color"
  48 + ></up-icon>
  49 + <up-icon
  50 + v-if="mode === 'closable'"
  51 + name="close"
  52 + :size="16"
  53 + :color="color"
  54 + @click="close"
  55 + ></up-icon>
  56 + </view>
  57 + </view>
  58 +</template>
  59 +
  60 +<script>
  61 + import { props } from './props';
  62 + import { mpMixin } from '../../libs/mixin/mpMixin';
  63 + import { mixin } from '../../libs/mixin/mixin';
  64 + import { addUnit, error } from '../../libs/function/index';
  65 + import test from '../../libs/function/test';
  66 + /**
  67 + * ColumnNotice 滚动通知中的垂直滚动 内部组件
  68 + * @description 该组件用于滚动通告场景,是其中的垂直滚动方式
  69 + * @tutorial https://uview-plus.jiangruyi.com/components/noticeBar.html
  70 + * @property {Array} text 显示的内容,字符串
  71 + * @property {String} icon 是否显示左侧的音量图标 ( 默认 'volume' )
  72 + * @property {String} mode 通告模式,link-显示右箭头,closable-显示右侧关闭图标
  73 + * @property {String} color 文字颜色,各图标也会使用文字颜色 ( 默认 '#f9ae3d' )
  74 + * @property {String} bgColor 背景颜色 ( 默认 '#fdf6ec' )
  75 + * @property {String | Number} fontSize 字体大小,单位px ( 默认 14 )
  76 + * @property {String | Number} speed 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 ( 默认 80 )
  77 + * @property {Boolean} step direction = row时,是否使用步进形式滚动 ( 默认 false )
  78 + * @property {String | Number} duration 滚动一个周期的时间长,单位ms ( 默认 1500 )
  79 + * @property {Boolean} disableTouch 是否禁止用手滑动切换 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 ( 默认 true )
  80 + * @example
  81 + */
  82 + export default {
  83 + mixins: [mpMixin, mixin, props],
  84 + watch: {
  85 + text: {
  86 + immediate: true,
  87 + handler(newValue, oldValue) {
  88 + if(!test.array(newValue)) {
  89 + error('noticebar组件direction为column时,要求text参数为数组形式')
  90 + }
  91 + }
  92 + }
  93 + },
  94 + computed: {
  95 + // 文字内容的样式
  96 + textStyle() {
  97 + let style = {}
  98 + style.color = this.color
  99 + style.fontSize = addUnit(this.fontSize)
  100 + return style
  101 + },
  102 + // 垂直或者水平滚动
  103 + vertical() {
  104 + if (this.mode == 'horizontal') return false
  105 + else return true
  106 + },
  107 + },
  108 + data() {
  109 + return {
  110 + index:0
  111 + }
  112 + },
  113 + emits: ["click", "close"],
  114 + methods: {
  115 + noticeChange(e){
  116 + this.index = e.detail.current
  117 + },
  118 + // 点击通告栏
  119 + clickHandler() {
  120 + this.$emit('click', this.index)
  121 + },
  122 + // 点击关闭按钮
  123 + close() {
  124 + this.$emit('close')
  125 + }
  126 + }
  127 + };
  128 +</script>
  129 +
  130 +<style lang="scss" scoped>
  131 +
  132 + .u-notice {
  133 + @include flex;
  134 + align-items: center;
  135 + justify-content: space-between;
  136 +
  137 + &__left-icon {
  138 + align-items: center;
  139 + margin-right: 5px;
  140 + }
  141 +
  142 + &__right-icon {
  143 + margin-left: 5px;
  144 + align-items: center;
  145 + }
  146 +
  147 + &__swiper {
  148 + height: 16px;
  149 + @include flex;
  150 + align-items: center;
  151 + flex: 1;
  152 +
  153 + &__item {
  154 + @include flex;
  155 + align-items: center;
  156 + overflow: hidden;
  157 +
  158 + &__text {
  159 + font-size: 14px;
  160 + color: $u-warning;
  161 + }
  162 + }
  163 + }
  164 + }
  165 +</style>
... ...
uni_modules/uview-plus/components/u-copy/u-copy.vue 0 → 100644
  1 +<template>
  2 + <view @click="handleClick">
  3 + <slot>{{ t("up.common.copy") }}</slot>
  4 + </view>
  5 +</template>
  6 +<script>
  7 +import { t } from '../../libs/i18n'
  8 +export default {
  9 + name: "up-copy",
  10 + props: {
  11 + content: {
  12 + type: String,
  13 + default: ''
  14 + },
  15 + alertStyle: {
  16 + type: String,
  17 + default: 'toast'
  18 + },
  19 + notice: {
  20 + type: String,
  21 + default: t("up.common.copy") + t("up.common.success")
  22 + }
  23 + },
  24 + emits: ['success'],
  25 + methods: {
  26 + t,
  27 + handleClick() {
  28 + let content = this.content;
  29 + if (!content) {
  30 + uni.showToast({
  31 + title: t("up.common.none"),
  32 + icon: 'none',
  33 + duration: 2000,
  34 + });
  35 + return false;
  36 + }
  37 + content = typeof content === 'string' ? content : content.toString() // 复制内容,必须字符串,数字需要转换为字符串
  38 + /**
  39 + * 小程序端 和 app端的复制逻辑
  40 + */
  41 + let that = this;
  42 + uni.setClipboardData({
  43 + data: content,
  44 + success: function() {
  45 + if (that.alertStyle == 'modal') {
  46 + uni.showModal({
  47 + title: "up.common.tip",
  48 + content: that.notice
  49 + });
  50 + } else {
  51 + uni.showToast({
  52 + title: that.notice,
  53 + icon: 'none'
  54 + });
  55 + }
  56 + that.$emit('success');
  57 + },
  58 + fail:function(){
  59 + uni.showToast({
  60 + title: t("up.common.copy") + t("up.common.fail"),
  61 + icon: 'none',
  62 + duration:3000,
  63 + });
  64 + }
  65 + });
  66 + }
  67 + }
  68 +}
  69 +</script>
  70 +
  71 +<style lang="scss" scoped>
  72 +</style>
... ...
uni_modules/uview-plus/components/u-count-down/countDown.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 17:11:29
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countDown.js
  9 + */
  10 +export default {
  11 + // u-count-down 计时器组件
  12 + countDown: {
  13 + time: 0,
  14 + format: 'HH:mm:ss',
  15 + autoStart: true,
  16 + millisecond: false
  17 + }
  18 +}
... ...
uni_modules/uview-plus/components/u-count-down/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 倒计时时长,单位ms
  6 + time: {
  7 + type: [String, Number],
  8 + default: () => defProps.countDown.time
  9 + },
  10 + // 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒
  11 + format: {
  12 + type: String,
  13 + default: () => defProps.countDown.format
  14 + },
  15 + // 是否自动开始倒计时
  16 + autoStart: {
  17 + type: Boolean,
  18 + default: () => defProps.countDown.autoStart
  19 + },
  20 + // 是否展示毫秒倒计时
  21 + millisecond: {
  22 + type: Boolean,
  23 + default: () => defProps.countDown.millisecond
  24 + }
  25 + }
  26 +})
... ...
uni_modules/uview-plus/components/u-count-down/u-count-down.vue 0 → 100644
  1 +<template>
  2 + <view class="u-count-down">
  3 + <slot :days="timeData.days" :hours="timeData.hours"
  4 + :minutes="timeData.minutes" :seconds="timeData.seconds">
  5 + <text class="u-count-down__text">{{ formattedTime }}</text>
  6 + </slot>
  7 + </view>
  8 +</template>
  9 +
  10 +<script>
  11 + import { props } from './props';
  12 + import { mpMixin } from '../../libs/mixin/mpMixin';
  13 + import { mixin } from '../../libs/mixin/mixin';
  14 + import {
  15 + isSameSecond,
  16 + parseFormat,
  17 + parseTimeData
  18 + } from './utils';
  19 + /**
  20 + * u-count-down 倒计时
  21 + * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
  22 + * @tutorial https://uview-plus.jiangruyi.com/components/countDown.html
  23 + * @property {String | Number} time 倒计时时长,单位ms (默认 0 )
  24 + * @property {String} format 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 (默认 'HH:mm:ss' )
  25 + * @property {Boolean} autoStart 是否自动开始倒计时 (默认 true )
  26 + * @property {Boolean} millisecond 是否展示毫秒倒计时 (默认 false )
  27 + * @event {Function} finish 倒计时结束时触发
  28 + * @event {Function} change 倒计时变化时触发
  29 + * @event {Function} start 开始倒计时
  30 + * @event {Function} pause 暂停倒计时
  31 + * @event {Function} reset 重设倒计时,若 auto-start 为 true,重设后会自动开始倒计时
  32 + * @example <u-count-down :time="time"></u-count-down>
  33 + */
  34 + export default {
  35 + name: 'u-count-down',
  36 + mixins: [mpMixin, mixin, props],
  37 + data() {
  38 + return {
  39 + timer: null,
  40 + // 各单位(天,时,分等)剩余时间
  41 + timeData: parseTimeData(0),
  42 + // 格式化后的时间,如"03:23:21"
  43 + formattedTime: '0',
  44 + // 倒计时是否正在进行中
  45 + runing: false,
  46 + endTime: 0, // 结束的毫秒时间戳
  47 + remainTime: 0, // 剩余的毫秒时间
  48 + }
  49 + },
  50 + watch: {
  51 + time(n) {
  52 + this.reset()
  53 + }
  54 + },
  55 + mounted() {
  56 + this.init()
  57 + },
  58 + emits: ["change", "finish"],
  59 + methods: {
  60 + init() {
  61 + this.reset()
  62 + },
  63 + // 开始倒计时
  64 + start() {
  65 + if (this.runing) return
  66 + // 标识为进行中
  67 + this.runing = true
  68 + // 结束时间戳 = 此刻时间戳 + 剩余的时间
  69 + this.endTime = Date.now() + this.remainTime
  70 + this.toTick()
  71 + },
  72 + // 根据是否展示毫秒,执行不同操作函数
  73 + toTick() {
  74 + if (this.millisecond) {
  75 + this.microTick()
  76 + } else {
  77 + this.macroTick()
  78 + }
  79 + },
  80 + macroTick() {
  81 + this.clearTimeout()
  82 + // 每隔一定时间,更新一遍定时器的值
  83 + // 同时此定时器的作用也能带来毫秒级的更新
  84 + this.timer = setTimeout(() => {
  85 + // 获取剩余时间
  86 + const remain = this.getRemainTime()
  87 + // 重设剩余时间
  88 + if (!isSameSecond(remain, this.remainTime) || remain === 0) {
  89 + this.setRemainTime(remain)
  90 + }
  91 + // 如果剩余时间不为0,则继续检查更新倒计时
  92 + if (this.remainTime !== 0) {
  93 + this.macroTick()
  94 + }
  95 + }, 30)
  96 + },
  97 + microTick() {
  98 + this.clearTimeout()
  99 + this.timer = setTimeout(() => {
  100 + this.setRemainTime(this.getRemainTime())
  101 + if (this.remainTime !== 0) {
  102 + this.microTick()
  103 + }
  104 + }, 50)
  105 + },
  106 + // 获取剩余的时间
  107 + getRemainTime() {
  108 + // 取最大值,防止出现小于0的剩余时间值
  109 + return Math.max(this.endTime - Date.now(), 0)
  110 + },
  111 + // 设置剩余的时间
  112 + setRemainTime(remain) {
  113 + this.remainTime = remain
  114 + // 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象
  115 + const timeData = parseTimeData(remain)
  116 + this.timeData = timeData;
  117 + this.$emit('change', timeData)
  118 + // 得出格式化后的时间
  119 + this.formattedTime = parseFormat(this.format, timeData)
  120 + // 如果时间已到,停止倒计时
  121 + if (remain <= 0) {
  122 + this.pause()
  123 + this.$emit('finish')
  124 + }
  125 + },
  126 + // 重置倒计时
  127 + reset() {
  128 + this.pause()
  129 + this.remainTime = this.time
  130 + this.setRemainTime(this.remainTime)
  131 + if (this.autoStart) {
  132 + this.start()
  133 + }
  134 + },
  135 + // 暂停倒计时
  136 + pause() {
  137 + this.runing = false;
  138 + this.clearTimeout()
  139 + },
  140 + // 清空定时器
  141 + clearTimeout() {
  142 + clearTimeout(this.timer)
  143 + this.timer = null
  144 + }
  145 + },
  146 + beforeUnmount() {
  147 + this.clearTimeout()
  148 + }
  149 + }
  150 +</script>
  151 +
  152 +<style
  153 + lang="scss"
  154 + scoped
  155 +>
  156 + $u-count-down-text-color:$u-content-color !default;
  157 + $u-count-down-text-font-size:15px !default;
  158 + $u-count-down-text-line-height:22px !default;
  159 +
  160 + .u-count-down {
  161 + &__text {
  162 + color: $u-count-down-text-color;
  163 + font-size: $u-count-down-text-font-size;
  164 + line-height: $u-count-down-text-line-height;
  165 + }
  166 + }
  167 +</style>
... ...
uni_modules/uview-plus/components/u-count-down/utils.js 0 → 100644
  1 +// 补0,如1 -> 01
  2 +function padZero(num, targetLength = 2) {
  3 + let str = `${num}`
  4 + while (str.length < targetLength) {
  5 + str = `0${str}`
  6 + }
  7 + return str
  8 +}
  9 +const SECOND = 1000
  10 +const MINUTE = 60 * SECOND
  11 +const HOUR = 60 * MINUTE
  12 +const DAY = 24 * HOUR
  13 +export function parseTimeData(time) {
  14 + const days = Math.floor(time / DAY)
  15 + const hours = Math.floor((time % DAY) / HOUR)
  16 + const minutes = Math.floor((time % HOUR) / MINUTE)
  17 + const seconds = Math.floor((time % MINUTE) / SECOND)
  18 + const milliseconds = Math.floor(time % SECOND)
  19 + return {
  20 + days,
  21 + hours,
  22 + minutes,
  23 + seconds,
  24 + milliseconds
  25 + }
  26 +}
  27 +export function parseFormat(format, timeData) {
  28 + let {
  29 + days,
  30 + hours,
  31 + minutes,
  32 + seconds,
  33 + milliseconds
  34 + } = timeData
  35 + // 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去
  36 + if (format.indexOf('DD') === -1) {
  37 + hours += days * 24
  38 + } else {
  39 + // 对天补0
  40 + format = format.replace('DD', padZero(days))
  41 + }
  42 + // 其他同理于DD的格式化处理方式
  43 + if (format.indexOf('HH') === -1) {
  44 + minutes += hours * 60
  45 + } else {
  46 + format = format.replace('HH', padZero(hours))
  47 + }
  48 + if (format.indexOf('mm') === -1) {
  49 + seconds += minutes * 60
  50 + } else {
  51 + format = format.replace('mm', padZero(minutes))
  52 + }
  53 + if (format.indexOf('ss') === -1) {
  54 + milliseconds += seconds * 1000
  55 + } else {
  56 + format = format.replace('ss', padZero(seconds))
  57 + }
  58 + return format.replace('SSS', padZero(milliseconds, 3))
  59 +}
  60 +export function isSameSecond(time1, time2) {
  61 + return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
  62 +}
... ...
uni_modules/uview-plus/components/u-count-to/countTo.js 0 → 100644
  1 +/*
  2 + * @Author : LQ
  3 + * @Description :
  4 + * @version : 1.0
  5 + * @Date : 2021-08-20 16:44:21
  6 + * @LastAuthor : LQ
  7 + * @lastTime : 2021-08-20 16:57:32
  8 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countTo.js
  9 + */
  10 +export default {
  11 + // countTo 组件
  12 + countTo: {
  13 + startVal: 0,
  14 + endVal: 0,
  15 + duration: 2000,
  16 + autoplay: true,
  17 + decimals: 0,
  18 + useEasing: true,
  19 + decimal: '.',
  20 + color: '#606266',
  21 + fontSize: 22,
  22 + bold: false,
  23 + separator: ''
  24 + }
  25 +}
... ...
uni_modules/uview-plus/components/u-count-to/props.js 0 → 100644
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + // 开始的数值,默认从0增长到某一个数
  6 + startVal: {
  7 + type: [String, Number],
  8 + default: () => defProps.countTo.startVal
  9 + },
  10 + // 要滚动的目标数值,必须
  11 + endVal: {
  12 + type: [String, Number],
  13 + default: () => defProps.countTo.endVal
  14 + },
  15 + // 滚动到目标数值的动画持续时间,单位为毫秒(ms)
  16 + duration: {
  17 + type: [String, Number],
  18 + default: () => defProps.countTo.duration
  19 + },
  20 + // 设置数值后是否自动开始滚动
  21 + autoplay: {
  22 + type: Boolean,
  23 + default: () => defProps.countTo.autoplay
  24 + },
  25 + // 要显示的小数位数
  26 + decimals: {
  27 + type: [String, Number],
  28 + default: () => defProps.countTo.decimals
  29 + },
  30 + // 是否在即将到达目标数值的时候,使用缓慢滚动的效果
  31 + useEasing: {
  32 + type: Boolean,
  33 + default: () => defProps.countTo.useEasing
  34 + },
  35 + // 十进制分割
  36 + decimal: {
  37 + type: [String, Number],
  38 + default: () => defProps.countTo.decimal
  39 + },
  40 + // 字体颜色
  41 + color: {
  42 + type: String,
  43 + default: () => defProps.countTo.color
  44 + },
  45 + // 字体大小
  46 + fontSize: {
  47 + type: [String, Number],
  48 + default: () => defProps.countTo.fontSize
  49 + },
  50 + // 是否加粗字体
  51 + bold: {
  52 + type: Boolean,
  53 + default: () => defProps.countTo.bold
  54 + },
  55 + // 千位分隔符,类似金额的分割(¥23,321.05中的",")
  56 + separator: {
  57 + type: String,
  58 + default: () => defProps.countTo.separator
  59 + }
  60 + }
  61 +})
... ...
uni_modules/uview-plus/components/u-count-to/u-count-to.vue 0 → 100644
  1 +<template>
  2 + <text
  3 + class="u-count-num"
  4 + :style="{
  5 + fontSize: addUnit(fontSize),
  6 + fontWeight: bold ? 'bold' : 'normal',
  7 + color: color
  8 + }"
  9 + >{{ displayValue }}</text>
  10 +</template>
  11 +
  12 +<script>
  13 +import { props } from './props';
  14 +import { mpMixin } from '../../libs/mixin/mpMixin';
  15 +import { mixin } from '../../libs/mixin/mixin';
  16 +import { addUnit } from '../../libs/function/index';
  17 +/**
  18 + * countTo 数字滚动
  19 + * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
  20 + * @tutorial https://uview-plus.jiangruyi.com/components/countTo.html
  21 + * @property {String | Number} startVal 开始的数值,默认从0增长到某一个数(默认 0 )
  22 + * @property {String | Number} endVal 要滚动的目标数值,必须 (默认 0 )
  23 + * @property {String | Number} duration 滚动到目标数值的动画持续时间,单位为毫秒(ms) (默认 2000 )
  24 + * @property {Boolean} autoplay 设置数值后是否自动开始滚动 (默认 true )
  25 + * @property {String | Number} decimals 要显示的小数位数,见官网说明(默认 0 )
  26 + * @property {Boolean} useEasing 滚动结束时,是否缓动结尾,见官网说明(默认 true )
  27 + * @property {String} decimal 十进制分割 ( 默认 "." )
  28 + * @property {String} color 字体颜色( 默认 '#606266' )
  29 + * @property {String | Number} fontSize 字体大小,单位px( 默认 22 )
  30 + * @property {Boolean} bold 字体是否加粗(默认 false )
  31 + * @property {String} separator 千位分隔符,见官网说明
  32 + * @event {Function} end 数值滚动到目标值时触发
  33 + * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
  34 + */
  35 +export default {
  36 + name: 'u-count-to',
  37 + data() {
  38 + return {
  39 + localStartVal: this.startVal,
  40 + displayValue: this.formatNumber(this.startVal),
  41 + printVal: null,
  42 + paused: false, // 是否暂停
  43 + localDuration: Number(this.duration),
  44 + startTime: null, // 开始的时间
  45 + timestamp: null, // 时间戳
  46 + remaining: null, // 停留的时间
  47 + rAF: null,
  48 + lastTime: 0 // 上一次的时间
  49 + };
  50 + },
  51 + mixins: [mpMixin, mixin,props],
  52 + computed: {
  53 + countDown() {
  54 + return this.startVal > this.endVal;
  55 + }
  56 + },
  57 + watch: {
  58 + startVal() {
  59 + this.autoplay && this.start();
  60 + },
  61 + endVal() {
  62 + this.autoplay && this.start();
  63 + }
  64 + },
  65 + mounted() {
  66 + this.autoplay && this.start();
  67 + },
  68 + emits: ["end"],
  69 + methods: {
  70 + addUnit,
  71 + easingFn(t, b, c, d) {
  72 + return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
  73 + },
  74 + requestAnimationFrame(callback) {
  75 + const currTime = new Date().getTime();
  76 + // 为了使setTimteout的尽可能的接近每秒60帧的效果
  77 + const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
  78 + const id = setTimeout(() => {
  79 + callback(currTime + timeToCall);
  80 + }, timeToCall);
  81 + this.lastTime = currTime + timeToCall;
  82 + return id;
  83 + },
  84 + cancelAnimationFrame(id) {
  85 + clearTimeout(id);
  86 + },
  87 + // 开始滚动数字
  88 + start() {
  89 + this.localStartVal = this.startVal;
  90 + this.startTime = null;
  91 + this.localDuration = this.duration;
  92 + this.paused = false;
  93 + this.rAF = this.requestAnimationFrame(this.count);
  94 + },
  95 + // 暂定状态,重新再开始滚动;或者滚动状态下,暂停
  96 + reStart() {
  97 + if (this.paused) {
  98 + this.resume();
  99 + this.paused = false;
  100 + } else {
  101 + this.stop();
  102 + this.paused = true;
  103 + }
  104 + },
  105 + // 暂停
  106 + stop() {
  107 + this.cancelAnimationFrame(this.rAF);
  108 + },
  109 + // 重新开始(暂停的情况下)
  110 + resume() {
  111 + if (!this.remaining) return
  112 + this.startTime = 0;
  113 + this.localDuration = this.remaining;
  114 + this.localStartVal = this.printVal;
  115 + this.requestAnimationFrame(this.count);
  116 + },
  117 + // 重置
  118 + reset() {
  119 + this.startTime = null;
  120 + this.cancelAnimationFrame(this.rAF);
  121 + this.displayValue = this.formatNumber(this.startVal);
  122 + },
  123 + count(timestamp) {
  124 + if (!this.startTime) this.startTime = timestamp;
  125 + this.timestamp = timestamp;
  126 + const progress = timestamp - this.startTime;
  127 + this.remaining = this.localDuration - progress;
  128 + if (this.useEasing) {
  129 + if (this.countDown) {
  130 + this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
  131 + } else {
  132 + this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
  133 + }
  134 + } else {
  135 + if (this.countDown) {
  136 + this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
  137 + } else {
  138 + this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
  139 + }
  140 + }
  141 + if (this.countDown) {
  142 + this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
  143 + } else {
  144 + this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
  145 + }
  146 + this.displayValue = this.formatNumber(this.printVal) || 0;
  147 + if (progress < this.localDuration) {
  148 + this.rAF = this.requestAnimationFrame(this.count);
  149 + } else {
  150 + this.$emit('end');
  151 + }
  152 + },
  153 + // 判断是否数字
  154 + isNumber(val) {
  155 + return !isNaN(parseFloat(val));
  156 + },
  157 + formatNumber(num) {
  158 + // 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
  159 + num = Number(num);
  160 + num = num.toFixed(Number(this.decimals));
  161 + num += '';
  162 + const x = num.split('.');
  163 + let x1 = x[0];
  164 + const x2 = x.length > 1 ? this.decimal + x[1] : '';
  165 + const rgx = /(\d+)(\d{3})/;
  166 + if (this.separator && !this.isNumber(this.separator)) {
  167 + while (rgx.test(x1)) {
  168 + x1 = x1.replace(rgx, '$1' + this.separator + '$2');
  169 + }
  170 + }
  171 + return x1 + x2;
  172 + },
  173 + destroyed() {
  174 + this.cancelAnimationFrame(this.rAF);
  175 + }
  176 + }
  177 +};
  178 +</script>
  179 +
  180 +<style lang="scss" scoped>
  181 +.u-count-num {
  182 + /* #ifndef APP-NVUE */
  183 + display: inline-flex;
  184 + /* #endif */
  185 + text-align: center;
  186 +}
  187 +</style>
... ...
uni_modules/uview-plus/components/u-coupon/u-coupon.vue 0 → 100644
  1 +<template>
  2 + <view class="up-coupon" :class="[`up-coupon--${shape}`, `up-coupon--${type}`, `up-coupon--${size}`, {'up-coupon--disabled': disabled}]"
  3 + :style="[couponStyle]" @click="handleClick">
  4 + <view class="up-coupon__content">
  5 + <!-- 左侧金额区域 -->
  6 + <view class="up-coupon__amount">
  7 + <slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'left'">
  8 + <text class="up-coupon__amount-unit" v-if="unitPosition === 'left'">{{ unit }}</text>
  9 + </slot>
  10 + <slot name="amount" :amount="amount">
  11 + <text class="up-coupon__amount-value">{{ amount }}</text>
  12 + </slot>
  13 + <slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'right'">
  14 + <text class="up-coupon__amount-unit" v-if="unitPosition === 'right'">{{ unit }}</text>
  15 + </slot>
  16 + <slot name="limit" :limit="limit">
  17 + <text class="up-coupon__amount-limit" v-if="limit">{{ limit }}</text>
  18 + </slot>
  19 + </view>
  20 +
  21 + <!-- 中间描述区域 -->
  22 + <view class="up-coupon__info">
  23 + <slot name="title" :title="title">
  24 + <text class="up-coupon__info-title">{{ title }}</text>
  25 + </slot>
  26 + <slot name="desc" :desc="desc">
  27 + <text class="up-coupon__info-desc" v-if="desc">{{ desc }}</text>
  28 + </slot>
  29 + <slot name="time" :time="time">
  30 + <text class="up-coupon__info-time" v-if="time">{{ time }}</text>
  31 + </slot>
  32 + </view>
  33 +
  34 + <!-- 右侧操作区域 -->
  35 + <view class="up-coupon__action u-padding-right-20">
  36 + <slot name="action" :actionText="actionText" :circle="circle">
  37 + <up-tag type="error" :bgColor="type ? 'transparent' : '#eb433d'"
  38 + :borderColor="type ? '#eee' : '#eb433d'" borderRadius="6px"
  39 + size="medium" class="up-coupon__action-text"
  40 + :shape="circle ? 'circle': 'circle'">{{ actionText }}</up-tag>
  41 + </slot>
  42 + </view>
  43 + </view>
  44 +
  45 + <!-- 红包绳子效果 -->
  46 + <view v-if="shape === 'envelope'" class="up-coupon__rope"></view>
  47 +
  48 + <!-- 默认插槽,可用于添加额外内容 -->
  49 + <slot></slot>
  50 + </view>
  51 +</template>
  52 +
  53 +<script>
  54 + export default {
  55 + name: 'up-coupon',
  56 + props: {
  57 + // 金额
  58 + amount: {
  59 + type: [String, Number],
  60 + default: ''
  61 + },
  62 + // 金额单位
  63 + unit: {
  64 + type: String,
  65 + default: '¥'
  66 + },
  67 + // 单位位置
  68 + unitPosition: {
  69 + type: String,
  70 + default: 'left'
  71 + },
  72 + // 使用限制
  73 + limit: {
  74 + type: String,
  75 + default: ''
  76 + },
  77 + // 标题
  78 + title: {
  79 + type: String,
  80 + default: '优惠券'
  81 + },
  82 + // 描述
  83 + desc: {
  84 + type: String,
  85 + default: ''
  86 + },
  87 + // 有效期
  88 + time: {
  89 + type: String,
  90 + default: ''
  91 + },
  92 + // 操作按钮文字
  93 + actionText: {
  94 + type: String,
  95 + default: '使用'
  96 + },
  97 + // 形状:coupon-优惠券, envelope-红包, card-卡片
  98 + shape: {
  99 + type: String,
  100 + default: 'coupon'
  101 + },
  102 + // 尺寸:small, medium, large
  103 + size: {
  104 + type: String,
  105 + default: 'medium'
  106 + },
  107 + // 是否圆形按钮
  108 + circle: {
  109 + type: Boolean,
  110 + default: false
  111 + },
  112 + // 是否禁用
  113 + disabled: {
  114 + type: Boolean,
  115 + default: false
  116 + },
  117 + // 背景颜色
  118 + bgColor: {
  119 + type: String,
  120 + default: ''
  121 + },
  122 + // 文字颜色
  123 + color: {
  124 + type: String,
  125 + default: ''
  126 + },
  127 + // 内置背景类型
  128 + type: {
  129 + type: String,
  130 + default: ''
  131 + },
  132 + },
  133 + computed: {
  134 + couponStyle() {
  135 + const style = {};
  136 + if (this.bgColor) style.background = this.bgColor;
  137 + if (this.color) style.color = this.color;
  138 + return style;
  139 + },
  140 + dotCount() {
  141 + // 根据尺寸计算锯齿数量
  142 + const map = {
  143 + small: 8,
  144 + medium: 10,
  145 + large: 12
  146 + };
  147 + return map[this.size] || 10;
  148 + }
  149 + },
  150 + methods: {
  151 + handleClick() {
  152 + if (this.disabled) return;
  153 + this.$emit('click');
  154 + }
  155 + }
  156 + }
  157 +</script>
  158 +
  159 +<style lang="scss" scoped>
  160 + .up-coupon {
  161 + position: relative;
  162 + overflow: hidden;
  163 + border-radius: 8rpx;
  164 + background: #ffebf0;
  165 + color: $u-main-color;
  166 +
  167 + &--coupon {
  168 + border-radius: 16rpx;
  169 + overflow: hidden;
  170 +
  171 + &::before {
  172 + content: '';
  173 + position: absolute;
  174 + left: -24rpx;
  175 + top: 50%;
  176 + transform: translateY(-50%);
  177 + width: 48rpx;
  178 + height: 48rpx;
  179 + background-color: #fff;
  180 + border-radius: 50%;
  181 + }
  182 +
  183 + &::after {
  184 + content: '';
  185 + position: absolute;
  186 + right: -24rpx;
  187 + top: 50%;
  188 + transform: translateY(-50%);
  189 + width: 48rpx;
  190 + height: 48rpx;
  191 + background-color: #fff;
  192 + border-radius: 50%;
  193 + }
  194 + }
  195 +
  196 + &--envelope {
  197 + border-radius: 16rpx;
  198 +
  199 + &::before {
  200 + content: '';
  201 + position: absolute;
  202 + left: 0;
  203 + top: 0;
  204 + right: 0;
  205 + height: 20rpx;
  206 + background: repeating-linear-gradient(-45deg, #ffd000, #ffd000 10rpx, #ffa000 10rpx, #ffa000 20rpx);
  207 + }
  208 + }
  209 +
  210 + &--card {
  211 + border-radius: 16rpx;
  212 + }
  213 +
  214 + width: 100%;
  215 +
  216 + &--small {
  217 + // width: 520rpx;
  218 + height: 160rpx;
  219 + }
  220 +
  221 + &--medium {
  222 + // width: 600rpx;
  223 + height: 180rpx;
  224 + }
  225 +
  226 + &--large {
  227 + // width: 700rpx;
  228 + height: 220rpx;
  229 + }
  230 +
  231 + &--disabled {
  232 + opacity: 0.5;
  233 + }
  234 +
  235 + &__content {
  236 + display: flex;
  237 + flex-direction: row;
  238 + align-items: center;
  239 + justify-content: space-between;
  240 + height: 100%;
  241 + padding: 0 30rpx;
  242 + position: relative;
  243 + z-index: 2;
  244 + }
  245 +
  246 + &__amount {
  247 + display: flex;
  248 + flex-direction: column;
  249 + align-items: flex-start;
  250 + padding-left: 10rpx;
  251 + padding-right: 30rpx;
  252 + border-right: 1px dashed #ccc;
  253 +
  254 + &-unit {
  255 + font-size: 24rpx;
  256 + font-weight: normal;
  257 + }
  258 +
  259 + &-value {
  260 + font-size: 56rpx;
  261 + font-weight: bold;
  262 + color: red;
  263 + line-height: 1;
  264 + margin: 10rpx 0;
  265 + }
  266 +
  267 + &-limit {
  268 + font-size: 24rpx;
  269 + opacity: 0.9;
  270 + }
  271 + }
  272 +
  273 + &__info {
  274 + flex: 1;
  275 + display: flex;
  276 + flex-direction: column;
  277 + align-items: flex-start;
  278 + padding-left: 30rpx;
  279 +
  280 + &-title {
  281 + font-size: 32rpx;
  282 + font-weight: bold;
  283 + margin-bottom: 10rpx;
  284 + }
  285 +
  286 + &-desc {
  287 + font-size: 24rpx;
  288 + opacity: 0.9;
  289 + margin-bottom: 10rpx;
  290 + }
  291 +
  292 + &-time {
  293 + font-size: 20rpx;
  294 + opacity: 0.8;
  295 + }
  296 + }
  297 +
  298 + &__action {
  299 + display: flex;
  300 + flex-direction: row;
  301 + align-items: center;
  302 + justify-content: center;
  303 + }
  304 +
  305 + &__dots {
  306 + position: absolute;
  307 + left: 0;
  308 + top: 0;
  309 + bottom: 0;
  310 + width: 100%;
  311 + display: flex;
  312 + flex-direction: column;
  313 + justify-content: space-between;
  314 + padding: 30rpx 0;
  315 + z-index: 1;
  316 + }
  317 +
  318 + &__dot {
  319 + width: 32rpx;
  320 + height: 32rpx;
  321 + background-color: #fff;
  322 + border-radius: 50%;
  323 + margin: 0 -16rpx;
  324 + z-index: 3;
  325 + }
  326 +
  327 + &__rope {
  328 + position: absolute;
  329 + top: -40rpx;
  330 + left: 50%;
  331 + transform: translateX(-50%);
  332 + width: 80rpx;
  333 + height: 80rpx;
  334 + background: linear-gradient(to right, #ffd000, #ffa000);
  335 + border-radius: 40rpx 40rpx 0 0;
  336 + z-index: 1;
  337 +
  338 + &::before {
  339 + content: '';
  340 + position: absolute;
  341 + top: 0;
  342 + left: -20rpx;
  343 + width: 20rpx;
  344 + height: 40rpx;
  345 + background: linear-gradient(to bottom, #ffd000, #ffa000);
  346 + border-radius: 10rpx 0 0 10rpx;
  347 + }
  348 +
  349 + &::after {
  350 + content: '';
  351 + position: absolute;
  352 + top: 0;
  353 + right: -20rpx;
  354 + width: 20rpx;
  355 + height: 40rpx;
  356 + background: linear-gradient(to bottom, #ffd000, #ffa000);
  357 + border-radius: 0 10rpx 10rpx 0;
  358 + }
  359 + }
  360 +
  361 + // 不同主题样式
  362 + &--primary {
  363 + background: linear-gradient(90deg, #43afff, #3b8cff);
  364 + color: #fff;
  365 + .up-coupon__amount {
  366 + border-right: 1px dashed #eee;
  367 + }
  368 + .up-coupon__amount-value {
  369 + color: #fff;
  370 + }
  371 + }
  372 +
  373 + &--success {
  374 + background: linear-gradient(90deg, #67dda9, #19be6b);
  375 + color: #fff !important;
  376 + .up-coupon__amount {
  377 + border-right: 1px dashed #eee;
  378 + }
  379 + .up-coupon__amount-value {
  380 + color: #fff;
  381 + }
  382 + }
  383 +
  384 + &--warning {
  385 + background: linear-gradient(90deg, #ff9739, #ff6a39);
  386 + color: #fff;
  387 + .up-coupon__amount {
  388 + border-right: 1px dashed #eee;
  389 + }
  390 + .up-coupon__amount-value {
  391 + color: #fff;
  392 + }
  393 + }
  394 +
  395 + &--error {
  396 + background: linear-gradient(90deg, #ff7070, #ff4747);
  397 + color: #fff;
  398 + .up-coupon__amount {
  399 + border-right: 1px dashed #eee;
  400 + }
  401 + .up-coupon__amount-value {
  402 + color: #fff;
  403 + }
  404 + }
  405 + }
  406 +</style>
0 407 \ No newline at end of file
... ...