Commit a2702f6d63c98787da3cd7a4105a83ccd77fef59
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.
App.vue
| 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
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) => { |
| 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) => { |
| 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) => { |
| 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 '@/api/workbench'; |
| 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 () => { |
| 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 () => { |
| 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 | +[](https://github.com/ijry/uview-plus) | |
| 8 | +[](https://github.com/ijry/uview-plus) | |
| 9 | +[](https://github.com/ijry/uview-plus/issues) | |
| 10 | +[](https://gitee.com/jry/uview-plus/releases) | |
| 11 | +[](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
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 | ... | ... |