feat: 引入uView Plus组件库,新增部分静态页面
This commit is contained in:
1
.env
1
.env
@@ -6,6 +6,7 @@ SHOPRO_BASE_URL = http://api.jnmall.zq-hightech.com
|
||||
|
||||
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development)
|
||||
SHOPRO_DEV_BASE_URL = https://icepacker.52cfzy.com
|
||||
# SHOPRO_DEV_BASE_URL = http://delivery-test.huichibao.com
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||
SHOPRO_UPLOAD_TYPE=server
|
||||
|
||||
1
App.vue
1
App.vue
@@ -35,5 +35,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/uni_modules/uview-plus/index.scss";
|
||||
@import '@/sheep/scss/index.scss';
|
||||
</style>
|
||||
|
||||
4
main.js
4
main.js
@@ -1,12 +1,12 @@
|
||||
import App from './App';
|
||||
import { createSSRApp } from 'vue';
|
||||
import { setupPinia } from './sheep/store';
|
||||
|
||||
import uviewPlus from '@/uni_modules/uview-plus'
|
||||
|
||||
export function createApp() {
|
||||
|
||||
const app = createSSRApp(App);
|
||||
|
||||
app.use(uviewPlus);
|
||||
setupPinia(app);
|
||||
|
||||
return {
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
"Payment": {},
|
||||
"Share": {},
|
||||
"VideoPlayer": {},
|
||||
"OAuth": {}
|
||||
"OAuth": {},
|
||||
"Maps": {},
|
||||
"Barcode": {},
|
||||
"Camera": {},
|
||||
"Geolocation": {}
|
||||
},
|
||||
"distribute": {
|
||||
"android": {
|
||||
@@ -122,6 +126,21 @@
|
||||
"appid": "wxae7a0c156da9383b",
|
||||
"UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
"amap": {
|
||||
"name": "amapAvsftDYzi",
|
||||
"appkey_ios": "2ead091d2fc2cdfec417d49aecf00c8b",
|
||||
"appkey_android": "5a660b94974d7f3c62b257e8368893fa"
|
||||
}
|
||||
},
|
||||
"geolocation": {
|
||||
"system": {
|
||||
"__platform__": [
|
||||
"ios",
|
||||
"android"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"orientation": [
|
||||
@@ -189,7 +208,8 @@
|
||||
"urlCheck": false,
|
||||
"minified": true,
|
||||
"postcss": false,
|
||||
"es6": false
|
||||
"es6": false,
|
||||
"mergeVirtualHostAttributes": true
|
||||
},
|
||||
"optimization": {
|
||||
"subPackages": true
|
||||
@@ -209,7 +229,8 @@
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
"usingComponents": true,
|
||||
"mergeVirtualHostAttributes": true
|
||||
},
|
||||
"mp-jd": {
|
||||
"usingComponents": true
|
||||
@@ -226,7 +247,7 @@
|
||||
"async": {
|
||||
"timeout": 20000
|
||||
},
|
||||
"title": "云南江楠商城",
|
||||
"title": "惠吃宝骑手端",
|
||||
"optimization": {
|
||||
"treeShaking": {
|
||||
"enable": true
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.7",
|
||||
"clipboard": "^2.0.11",
|
||||
"dayjs": "^1.11.19",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luch-request": "^3.0.8",
|
||||
|
||||
66
pages.json
66
pages.json
@@ -3,7 +3,10 @@
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^s-(.*)": "@/sheep/components/s-$1/s-$1.vue",
|
||||
"^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue"
|
||||
"^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue",
|
||||
"^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
|
||||
"^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
|
||||
"^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [{
|
||||
@@ -17,7 +20,7 @@
|
||||
"auth": false,
|
||||
"sync": true,
|
||||
"title": "首页",
|
||||
"group": "商城"
|
||||
"group": "配送端"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -29,7 +32,7 @@
|
||||
"meta": {
|
||||
"sync": true,
|
||||
"title": "个人中心",
|
||||
"group": "商城"
|
||||
"group": "配送端"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -54,6 +57,18 @@
|
||||
"title": "用户信息",
|
||||
"group": "用户中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orderRecord",
|
||||
"style": {
|
||||
"navigationBarTitleText": "接单记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "recordList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单记录"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -106,6 +121,51 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/registered",
|
||||
"pages": [
|
||||
{
|
||||
"path": "registerRiders",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册骑手"
|
||||
},
|
||||
"meta": {
|
||||
"sync": true,
|
||||
"title": "注册骑手",
|
||||
"group": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "accountInfo",
|
||||
"style": {
|
||||
"navigationBarTitleText": "工资结算账户信息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "audit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "审核中"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/order",
|
||||
"pages": [
|
||||
{
|
||||
"path": "detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "handoverRecord",
|
||||
"style": {
|
||||
"navigationBarTitleText": "交接记录"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -28,39 +28,41 @@
|
||||
<!-- 订单列表 -->
|
||||
<scroll-view class="order-list" scroll-y="true" :style="{ height: listHeight + 'px' }">
|
||||
<view v-for="order in filteredOrders" :key="order.id" class="order-card">
|
||||
<!-- 头部编号 -->
|
||||
<view class="order-header">
|
||||
<view class="order-badge">{{ order.type === 'pickup' ? '取' : '送' }}</view>
|
||||
<view class="order-title">
|
||||
<text class="shop-name">{{ order.shopName }}</text>
|
||||
<text class="order-id">#{{ order.id }}</text>
|
||||
</view>
|
||||
<view class="order-status">{{ order.statusText }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 地址信息 -->
|
||||
<view class="order-info">
|
||||
<view class="address-row">
|
||||
<view class="icon pickup">取</view>
|
||||
<view class="address-content">
|
||||
<text class="address-title">{{ order.pickupAddress }}</text>
|
||||
<text class="address-sub">商家已出餐 · {{ order.pickupNote || '' }}</text>
|
||||
<view @click="toDetail(order.id)">
|
||||
<!-- 头部编号 -->
|
||||
<view class="order-header">
|
||||
<view class="order-badge">{{ order.type === 'pickup' ? '取' : '送' }}</view>
|
||||
<view class="order-title">
|
||||
<text class="shop-name">{{ order.shopName }}</text>
|
||||
<text class="order-id">#{{ order.id }}</text>
|
||||
</view>
|
||||
<view class="nav-icon" @click="openMap(order.pickupLat, order.pickupLng, order.pickupAddress)">导航</view>
|
||||
<view class="order-status">{{ order.statusText }}</view>
|
||||
</view>
|
||||
<view class="address-row">
|
||||
<view class="icon deliver">送</view>
|
||||
<view class="address-content">
|
||||
<text class="address-title">{{ order.deliveryAddress }}</text>
|
||||
<text class="address-sub">收货人:{{ order.receiverName }} {{ order.receiverPhone ? ('尾号' + (order.receiverPhone + '').slice(-4)) : ''}}</text>
|
||||
</view>
|
||||
<view class="nav-icon" @click="openMap(order.deliveryLat, order.deliveryLng, order.deliveryAddress)">导航</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="order-note" v-if="order.note">
|
||||
<text>顾客:{{ order.note }}</text>
|
||||
<!-- 地址信息 -->
|
||||
<view class="order-info">
|
||||
<view class="address-row">
|
||||
<view class="icon pickup">取</view>
|
||||
<view class="address-content">
|
||||
<text class="address-title">{{ order.pickupAddress }}</text>
|
||||
<text class="address-sub">商家已出餐 · {{ order.pickupNote || '' }}</text>
|
||||
</view>
|
||||
<view class="nav-icon" @click="openMap(order.pickupLat, order.pickupLng, order.pickupAddress)">导航</view>
|
||||
</view>
|
||||
<view class="address-row">
|
||||
<view class="icon deliver">送</view>
|
||||
<view class="address-content">
|
||||
<text class="address-title">{{ order.deliveryAddress }}</text>
|
||||
<text class="address-sub">收货人:{{ order.receiverName }} {{ order.receiverPhone ? ('尾号' + (order.receiverPhone + '').slice(-4)) : ''}}</text>
|
||||
</view>
|
||||
<view class="nav-icon" @click="openMap(order.deliveryLat, order.deliveryLng, order.deliveryAddress)">导航</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="order-note" v-if="order.note">
|
||||
<text>顾客:{{ order.note }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作区 -->
|
||||
@@ -86,6 +88,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
|
||||
// 驿站/骑手信息(从 store 获取或 mock)
|
||||
const driverInfo = ref({
|
||||
@@ -214,20 +217,6 @@ function openManualInput() {
|
||||
});
|
||||
}
|
||||
|
||||
// 入口:计算列表高度适配底部栏
|
||||
onMounted(() => {
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync();
|
||||
const windowHeight = sys.windowHeight || 667;
|
||||
// 留出顶部和底部空间
|
||||
listHeight.value = windowHeight - 200;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
// 顶部安全区处理:参考 pages/index/user.vue 的实现
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
|
||||
const headerStyle = ref({});
|
||||
|
||||
function setHeaderSafeArea() {
|
||||
@@ -246,17 +235,15 @@ function setHeaderSafeArea() {
|
||||
}
|
||||
}
|
||||
|
||||
//跳转订单详情
|
||||
function toDetail(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/detail?orderId=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setHeaderSafeArea();
|
||||
// 计算列表高度适配底部栏
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync();
|
||||
const windowHeight = sys.windowHeight || 667;
|
||||
// 留出顶部和底部空间
|
||||
listHeight.value = windowHeight - 200;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// setHeaderSafeArea();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
<view class="header-wrap" :style="headerStyle">
|
||||
<view class="header-bg"></view>
|
||||
<view class="header-inner">
|
||||
<image class="avatar" :src="user.avatar || defautAvatar"></image>
|
||||
<view class="user-meta">
|
||||
<view class="user-name">{{ user.nickName || '姓名(账号)' }}</view>
|
||||
<image class="avatar" :src="userInfo.avatar || defautAvatar"></image>
|
||||
<view class="user-meta" v-if="userInfo.nickname">
|
||||
<view class="user-name">{{ userInfo.nickname + `(${userInfo.mobile})` }}</view>
|
||||
<view class="user-status" @click="handleStatusToggle">
|
||||
{{ user.isOnline ? '在线' : '离线' }}<uni-icons style="margin-left:10rpx;" type="right" size="13" color="#fff"></uni-icons>
|
||||
{{ userInfo.isOnline ? '在线' : '离线' }}<uni-icons style="margin-left:10rpx;" type="right" size="13" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-meta" v-else>
|
||||
<view class="user-name" @tap="login">请登录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -73,7 +76,8 @@
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
import {
|
||||
onShow,
|
||||
@@ -81,12 +85,15 @@
|
||||
onPullDownRefresh
|
||||
} from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import {
|
||||
showAuthModal,
|
||||
} from '@/sheep/hooks/useModal';
|
||||
|
||||
// 现有 store / 模板数据
|
||||
const template = computed(() => sheep.$store('app').template.user);
|
||||
const isLogin = computed(() => sheep.$store('user').isLogin);
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
const user = ref({});
|
||||
const todayIncome = ref(0);
|
||||
const todayOrders = ref(0);
|
||||
const showBind = ref(false);
|
||||
@@ -115,9 +122,8 @@
|
||||
|
||||
// 页面显示时拉取用户信息并填充统计数据(从 store 获取或使用占位)
|
||||
onShow(async () => {
|
||||
const data = await sheep.$store('user').getInfo();
|
||||
const data = userInfo.value;
|
||||
if (data) {
|
||||
user.value = data;
|
||||
// 兼容后端字段名,优先使用 data.todayIncome / data.income / placeholder
|
||||
todayIncome.value = data.todayIncome ?? data.income ?? 137.9;
|
||||
todayOrders.value = data.todayOrders ?? data.orders ?? 39;
|
||||
@@ -149,13 +155,13 @@
|
||||
|
||||
// 判断用户是否被禁止接单(兼容多种字段)
|
||||
function isUserForbidden() {
|
||||
const u = user.value || {};
|
||||
const u = userInfo.value || {};
|
||||
return !!(u.forbidden || u.isForbidden || u.forbid || u.forbidReceive || u.disableReceive || u.receive === false);
|
||||
}
|
||||
|
||||
// 点击状态:根据当前状态弹不同的确认框
|
||||
function handleStatusToggle() {
|
||||
if (user.value.isOnline) {
|
||||
if (userInfo.value.isOnline) {
|
||||
confirmType.value = 'offline';
|
||||
modalTitle.value = '确认下线?';
|
||||
modalMsg.value = '下线需平台进行核准\n此时正常接单请留意核准信息';
|
||||
@@ -186,10 +192,10 @@
|
||||
showStatusPopup.value = false;
|
||||
if (type === 'online') {
|
||||
// TODO: 调用后端接口变更上线状态
|
||||
user.value.isOnline = true;
|
||||
userInfo.value.isOnline = true;
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已上线');
|
||||
} else if (type === 'offline') {
|
||||
user.value.isOnline = false;
|
||||
userInfo.value.isOnline = false;
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已下线');
|
||||
} else if (type === 'forbidden') {
|
||||
// 仅展示信息,无操作
|
||||
@@ -242,6 +248,11 @@
|
||||
url: '/pages/public/setting'
|
||||
});
|
||||
}
|
||||
|
||||
function login() {
|
||||
showAuthModal();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
619
pages/order/detail.vue
Normal file
619
pages/order/detail.vue
Normal file
@@ -0,0 +1,619 @@
|
||||
<template>
|
||||
<s-layout title="订单详情" class="set-userinfo-wrap">
|
||||
<view class="order-detail-page">
|
||||
<!-- 地图占位(可替换为原生 map 组件或第三方地图组件) -->
|
||||
<view class="map-area">
|
||||
<!-- 真实项目建议使用 <map> 并渲染 polyline/markers -->
|
||||
<image class="map-image" src="/static/img/map-placeholder.png" mode="widthFix" v-if="!mapAvailable" />
|
||||
<map v-else class="map-native" :latitude="order?.pickupLat" :longitude="order?.pickupLng" show-location enable-3D
|
||||
enable-zoom :scale="16"></map>
|
||||
<view class="map-overlay">
|
||||
<view class="eta">距离商家{{ distanceText }},预计{{ etaText }}到达</view>
|
||||
<view class="nav-btn" @click="navigateToShop">导航到商家</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地址块 -->
|
||||
<scroll-view class="content" scroll-y="true">
|
||||
<view class="address-block">
|
||||
<view class="addr-row">
|
||||
<view class="badge pickup">取</view>
|
||||
<view class="addr-body">
|
||||
<text class="addr-title">{{ order?.shopName || '店铺名称' }}</text>
|
||||
<text class="addr-sub">{{ order?.pickupAddress }}</text>
|
||||
</view>
|
||||
<view class="nav-icon" @click="openMap(order?.pickupLat, order?.pickupLng, order?.pickupAddress)">导航</view>
|
||||
</view>
|
||||
|
||||
<view class="addr-row">
|
||||
<view class="badge deliver">送</view>
|
||||
<view class="addr-body">
|
||||
<text class="addr-title">{{ order?.deliveryAddress }}</text>
|
||||
<text
|
||||
class="addr-sub">收货人:{{ order?.receiverName }} {{ order?.receiverPhone ? ('尾号' + (order.receiverPhone + '').slice(-4)) : ''}}</text>
|
||||
</view>
|
||||
<view class="nav-icon" @click="openMap(order?.deliveryLat, order?.deliveryLng, order?.deliveryAddress)">导航
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 顾客备注 -->
|
||||
<view class="note" v-if="order?.note">
|
||||
<text>顾客:{{ order.note }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 商品清单 -->
|
||||
<view class="goods-list">
|
||||
<view class="goods-header">
|
||||
<text>商品清单</text>
|
||||
<text class="item-count">{{ totalCount }}件</text>
|
||||
<text class="total-price">¥{{ totalPrice.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="goods-item" v-for="(g, idx) in order?.items || []" :key="idx">
|
||||
<view class="g-left">
|
||||
<text class="g-name">{{ g.name }}{{ g.spec ? ('·' + g.spec) : '' }}</text>
|
||||
<text class="g-qty">×{{ g.quantity }}</text>
|
||||
</view>
|
||||
<view class="g-right">¥{{ (g.price || 0).toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系/记录等 -->
|
||||
<view class="action-record">
|
||||
<!-- <view class="call" @click="callPhone(order?.receiverPhone)">
|
||||
<text>电话联系</text>
|
||||
</view> -->
|
||||
<view class="record" @click="toRecord">交接记录</view>
|
||||
</view>
|
||||
|
||||
<!-- 占位底部高度,避免内容被底部按钮遮挡 -->
|
||||
<view style="height:140rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<view class="fixed-actions">
|
||||
<view class="left-actions">
|
||||
<view class="icon-phone" @click="callPhone(order?.receiverPhone)"></view>
|
||||
</view>
|
||||
<view class="right-actions">
|
||||
<view class="btn remind">催单</view>
|
||||
<view class="btn remind" @click="openRemindPopup">电话联系</view>
|
||||
<!-- <view class="btn confirm">转单</view> -->
|
||||
<view class="btn confirm" @click="confirmArrive">确认到店</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 催单弹框(uView Plus up-popup) -->
|
||||
<up-popup v-model:show="showRemind" mode="bottom" :closeable="false" border-radius="12">
|
||||
<view class="remind-popup">
|
||||
<view class="remind-row" @click="callShopPhone">
|
||||
<text class="remind-title">联系商家</text>
|
||||
<view class="remind-btn" @click.stop="callShopPhone">拨打电话</view>
|
||||
</view>
|
||||
<view class="remind-row" @click="callCustomerPhone">
|
||||
<text class="remind-title">联系顾客</text>
|
||||
<view class="remind-btn" @click.stop="callCustomerPhone">拨打电话</view>
|
||||
</view>
|
||||
</view>
|
||||
<template #bottom>
|
||||
<view class="remind-cancel" @click="showRemind = false">取消</view>
|
||||
</template>
|
||||
</up-popup>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
computed
|
||||
} from 'vue';
|
||||
import {
|
||||
onLoad
|
||||
} from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const orderId = ref(null);
|
||||
const order = ref(null);
|
||||
const loading = ref(false);
|
||||
const mapAvailable = ref(false); // 如果需要使用 map 组件,置为 true
|
||||
|
||||
// 入口:从页面参数取 orderId,然后加载数据
|
||||
onLoad((options = {}) => {
|
||||
orderId.value = options.id || options.orderId || null;
|
||||
fetchOrder();
|
||||
});
|
||||
|
||||
async function fetchOrder() {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 优先尝试平台统一 request(项目内可能封装在 sheep.request 或 sheep.api)
|
||||
if (sheep && typeof sheep.request === 'function') {
|
||||
const res = await sheep.request({
|
||||
url: '/order/detail',
|
||||
method: 'GET',
|
||||
data: {
|
||||
id: orderId.value
|
||||
}
|
||||
});
|
||||
// 根据封装不同,这里兼容 res.data 或 res
|
||||
order.value = (res && res.data) ? res.data : res;
|
||||
} else if (sheep && sheep.$api && sheep.$api.trade && typeof sheep.$api.trade.detail === 'function') {
|
||||
const res = await sheep.$api.trade.detail({
|
||||
id: orderId.value
|
||||
});
|
||||
order.value = res?.data || res;
|
||||
} else {
|
||||
// 回退 mock 数据,避免界面空白,开发时替换为真实接口
|
||||
order.value = {
|
||||
id: orderId.value || 1001,
|
||||
shopName: '店铺名(示例)',
|
||||
pickupAddress: '广东省广州市天河区学院站荷光路118-121号',
|
||||
pickupLat: 23.1005,
|
||||
pickupLng: 113.3301,
|
||||
deliveryAddress: '广东省广州市天河区华景新城软件园区B栋西梯501',
|
||||
deliveryLat: 23.105,
|
||||
deliveryLng: 113.335,
|
||||
receiverName: '张先生',
|
||||
receiverPhone: '13900001234',
|
||||
note: '依据餐量提供餐具',
|
||||
items: [{
|
||||
name: '商品名称A',
|
||||
spec: '规格1',
|
||||
quantity: 2,
|
||||
price: 23.89
|
||||
},
|
||||
{
|
||||
name: '商品名称B',
|
||||
spec: '规格2',
|
||||
quantity: 1,
|
||||
price: 45.00
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('fetchOrder error', e);
|
||||
// 友好提示
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('获取订单失败,请稍后重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount = computed(() => {
|
||||
if (!order.value || !order.value.items) return 0;
|
||||
return order.value.items.reduce((s, it) => s + (it.quantity || 0), 0);
|
||||
});
|
||||
const totalPrice = computed(() => {
|
||||
if (!order.value || !order.value.items) return 0;
|
||||
return order.value.items.reduce((s, it) => s + ((it.price || 0) * (it.quantity || 0)), 0);
|
||||
});
|
||||
|
||||
// 显示距离与预计时间(示例,真实项目可使用服务端或高德/百度 SDK 计算)
|
||||
const distanceText = computed(() => {
|
||||
// 此处为示例固定值,后续接入定位/路程计算替换
|
||||
return order.value ? '873m' : '--';
|
||||
});
|
||||
const etaText = computed(() => {
|
||||
return order.value ? '预计十分钟到达' : '--';
|
||||
});
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
|
||||
function openMap(lat, lng, name) {
|
||||
if (!lat || !lng) {
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('无法获取坐标');
|
||||
return;
|
||||
}
|
||||
uni.openLocation({
|
||||
latitude: Number(lat),
|
||||
longitude: Number(lng),
|
||||
name: name || '',
|
||||
scale: 18
|
||||
});
|
||||
}
|
||||
|
||||
function navigateToShop() {
|
||||
openMap(order.value?.pickupLat, order.value?.pickupLng, order.value?.pickupAddress);
|
||||
}
|
||||
|
||||
function callPhone(phone) {
|
||||
if (!phone) {
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到联系电话');
|
||||
return;
|
||||
}
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone
|
||||
});
|
||||
}
|
||||
|
||||
// 催单弹框控制(使用 uView Plus 的 up-popup)
|
||||
const showRemind = ref(false);
|
||||
|
||||
function openRemindPopup() {
|
||||
showRemind.value = true;
|
||||
}
|
||||
|
||||
function closeRemindPopup() {
|
||||
showRemind.value = false;
|
||||
}
|
||||
|
||||
function callShopPhone() {
|
||||
// 商家电话优先使用 order.shopPhone,否则尝试 fallback
|
||||
const phone = order.value?.shopPhone || order.value?.shopPhoneNumber || order.value?.receiverPhone || '';
|
||||
if (!phone) {
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到商家电话');
|
||||
return;
|
||||
}
|
||||
callPhone(phone);
|
||||
closeRemindPopup();
|
||||
}
|
||||
|
||||
function callCustomerPhone() {
|
||||
const phone = order.value?.receiverPhone || '';
|
||||
if (!phone) {
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到顾客电话');
|
||||
return;
|
||||
}
|
||||
callPhone(phone);
|
||||
closeRemindPopup();
|
||||
}
|
||||
|
||||
function confirmArrive() {
|
||||
// 确认到店:调用接口或本地改变状态
|
||||
if (!order.value) return;
|
||||
// 示例:调用后端接口(兼容性判断)
|
||||
(async () => {
|
||||
try {
|
||||
if (sheep && typeof sheep.request === 'function') {
|
||||
await sheep.request({
|
||||
url: '/order/confirmArrive',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: order.value.id
|
||||
}
|
||||
});
|
||||
}
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已确认到店');
|
||||
// 可在此刷新订单状态
|
||||
fetchOrder();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('确认失败,请重试');
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
//跳转交接记录
|
||||
function toRecord(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/handoverRecord?orderId=${orderId.value}`
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.order-detail-page {
|
||||
// background: #fff;
|
||||
background: #f7f7f7;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.back {
|
||||
width: 44rpx;
|
||||
font-size: 40rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-right: 44rpx;
|
||||
}
|
||||
|
||||
.map-area {
|
||||
height: 360rpx;
|
||||
background: #f3f3f3;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.map-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.map-native {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-overlay {
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
bottom: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eta {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
background: #fff;
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
color: #1e9fff;
|
||||
border: 1rpx solid #dbeeff;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20rpx;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
background: #f7f7f7;
|
||||
min-height: 200rpx;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.address-block {
|
||||
background: #fff;
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.addr-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.addr-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.badge {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
border-radius: 23rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 20rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.badge.pickup {
|
||||
background: #87d6ff;
|
||||
}
|
||||
|
||||
.badge.deliver {
|
||||
background: #ffd591;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.addr-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.addr-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.addr-sub {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
color: #1e9fff;
|
||||
padding: 6rpx 8rpx;
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-top: 12rpx;
|
||||
background: #fff;
|
||||
padding: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.goods-list {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.goods-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12rpx;
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12rpx;
|
||||
background: #fff;
|
||||
margin-top: 8rpx;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.g-left {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.g-left {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.g-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
max-width: 62%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.g-qty {
|
||||
color: #888;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.g-right {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-record {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.call {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
padding: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
.record {
|
||||
flex: 2;
|
||||
background: #fff;
|
||||
padding: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 140rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
gap: 12rpx;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.left-actions {
|
||||
width: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 16rpx 18rpx;
|
||||
border-radius: 12rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn.remind {
|
||||
background: #f39c12;
|
||||
}
|
||||
|
||||
.btn.confirm {
|
||||
background: #1e9fff;
|
||||
}
|
||||
|
||||
.remind-popup {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.remind-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
background: #f6f6f6;
|
||||
margin: 10rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.remind-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.remind-btn {
|
||||
background: #1e9fff;
|
||||
color: #fff;
|
||||
padding: 10rpx 18rpx;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.remind-cancel {
|
||||
text-align: center;
|
||||
padding: 18rpx 0 70rpx;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
background: #fff;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
</style>
|
||||
155
pages/order/handoverRecord.vue
Normal file
155
pages/order/handoverRecord.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<s-layout title="交接记录" class="set-userinfo-wrap">
|
||||
<view class="transfer-page">
|
||||
<scroll-view class="body" scroll-y>
|
||||
<u-steps direction="column" :current="currentIndex" class="steps-wrap">
|
||||
<u-steps-item v-for="(item, idx) in records" :key="idx">
|
||||
<template #title>
|
||||
<text class="step-title">{{ item.title }}</text>
|
||||
</template>
|
||||
<template #desc>
|
||||
<text class="step-desc">{{ item.time }}</text>
|
||||
</template>
|
||||
<template #content>
|
||||
<view class="step-content">
|
||||
<text class="op-name">{{ item.operator }}</text>
|
||||
<text class="op-note" v-if="item.note"> · {{ item.note }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</u-steps-item>
|
||||
</u-steps>
|
||||
<view v-if="records.length === 0" class="empty">暂无交接记录</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
computed
|
||||
} from 'vue';
|
||||
import {
|
||||
onLoad
|
||||
} from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const records = ref([]);
|
||||
const orderId = ref(null);
|
||||
|
||||
onLoad((options = {}) => {
|
||||
orderId.value = options.id || options.orderId || null;
|
||||
fetchRecords();
|
||||
});
|
||||
|
||||
async function fetchRecords() {
|
||||
try {
|
||||
if (sheep && typeof sheep.request === 'function') {
|
||||
const res = await sheep.request({
|
||||
url: '/order/transferRecords',
|
||||
method: 'GET',
|
||||
data: {
|
||||
id: orderId.value
|
||||
}
|
||||
});
|
||||
records.value = (res && res.data) ? res.data.records || res.data : res.records || res;
|
||||
} else {
|
||||
// mock 数据
|
||||
records.value = [{
|
||||
title: '已接单',
|
||||
time: '2026-01-15 10:02',
|
||||
operator: '系统',
|
||||
note: '订单自动接单'
|
||||
},
|
||||
{
|
||||
title: '到店取货',
|
||||
time: '2026-01-15 10:12',
|
||||
operator: '骑手 张三',
|
||||
note: '已取货'
|
||||
},
|
||||
{
|
||||
title: '转单给同城骑手',
|
||||
time: '2026-01-15 10:20',
|
||||
operator: '客服 小李',
|
||||
note: '因配送区域调整'
|
||||
}
|
||||
];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('fetchRecords error', e);
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('获取交接记录失败');
|
||||
records.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
const currentIndex = computed(() => Math.max(0, records.value.length - 1));
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.transfer-page {
|
||||
background: #fff;
|
||||
/* min-height: 100vh; */
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: 20rpx;
|
||||
/* background: #f7f7f7; */
|
||||
/* min-height: calc(100vh - 88rpx); */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.steps-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
margin-top: 10rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.op-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.op-note {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
</style>
|
||||
223
pages/registered/accountInfo.vue
Normal file
223
pages/registered/accountInfo.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<s-layout title="工资结算账户信息" class="set-userinfo-wrap">
|
||||
<view class="page">
|
||||
<up-form ref="acctForm" :model="form" :rules="rules" labelPosition="left" labelWidth="120">
|
||||
<up-form-item label="开户行城市" prop="bankCity" :required="true">
|
||||
<up-input readonly v-model="bankCityLabel" placeholder="省-市" @tap="regionShow = true" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="开户行别" prop="bankName" :required="true">
|
||||
<up-picker hasInput :columns="bankOptions" v-model="bankNameLabel" @confirm="onBankConfirm">
|
||||
<template #trigger>
|
||||
<up-input readonly v-model="bankNameLabel" placeholder="请选择开户行" />
|
||||
</template>
|
||||
</up-picker>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="开户行网点名称" prop="bankBranch" :required="true">
|
||||
<up-picker hasInput :columns="branchOptions" v-model="bankBranchLabel" @confirm="onBranchConfirm">
|
||||
<template #trigger>
|
||||
<up-input readonly v-model="bankBranchLabel" placeholder="请选择网点名称" />
|
||||
</template>
|
||||
</up-picker>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="银行卡号" prop="cardNo" :required="true">
|
||||
<up-input v-model="form.cardNo" placeholder="请输入银行卡号" type="number" maxlength="23" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="持卡人姓名" prop="cardHolder" :required="true">
|
||||
<up-input v-model="form.cardHolder" placeholder="请输入持卡人姓名" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="银行代码" prop="bankCode">
|
||||
<up-input v-model="form.bankCode" placeholder="请输入银行代码(如有)" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="手机号" prop="phone" :required="true">
|
||||
<view class="code-row">
|
||||
<view style="width:280rpx;">
|
||||
<up-input v-model="form.phone" placeholder="预留手机号码" type="number" maxlength="11" />
|
||||
</view>
|
||||
<view style="width:160rpx;margin-left:10rpx;">
|
||||
<up-button :disabled="countdown > 0" plain @click="sendCode">
|
||||
{{ countdown > 0 ? countdown + 's' : '获取验证码' }}
|
||||
</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="验证码" prop="captcha" :required="true">
|
||||
<up-input v-model="form.captcha" placeholder="输入验证码" maxlength="6" />
|
||||
</up-form-item>
|
||||
|
||||
<view class="agree-row" @click="agree = !agree">
|
||||
<view class="checkbox" :class="{checked: agree}"></view>
|
||||
<text class="agree-text">勾选同意 <text class="link">《骑手协议》</text> 提交成功后将会有专人与您联系</text>
|
||||
</view>
|
||||
|
||||
<view class="submit-row">
|
||||
<up-button type="primary" block @click="onSubmitAccount">提交申请审核</up-button>
|
||||
</view>
|
||||
</up-form>
|
||||
</view>
|
||||
<su-region-picker level="2" :show="regionShow" @confirm="onRegionConfirm" @cancel="regionShow = false" />
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onBeforeMount } from 'vue'
|
||||
|
||||
const form = reactive({
|
||||
bankCity: '',
|
||||
bankName: '',
|
||||
bankBranch: '',
|
||||
cardNo: '',
|
||||
cardHolder: '',
|
||||
bankCode: '',
|
||||
phone: '',
|
||||
captcha: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
bankCity: [{ required: true, message: '请选择开户城市' }],
|
||||
bankName: [{ required: true, message: '请选择开户行别' }],
|
||||
bankBranch: [{ required: true, message: '请选择网点名称' }],
|
||||
cardNo: [{ required: true, message: '请输入银行卡号' }],
|
||||
cardHolder: [{ required: true, message: '请输入持卡人姓名' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号' },
|
||||
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号码' },
|
||||
],
|
||||
captcha: [{ required: true, message: '请输入验证码' }],
|
||||
}
|
||||
|
||||
const acctForm = ref(null)
|
||||
const regionShow = ref(false)
|
||||
const bankOptions = [
|
||||
['中国工商银行', '中国建设银行', '中国农业银行', '中国银行', '交通银行', '招商银行']
|
||||
]
|
||||
const branchOptions = [
|
||||
['请选择网点']
|
||||
]
|
||||
const bankNameLabel = ref([])
|
||||
const bankBranchLabel = ref([])
|
||||
const bankCityLabel = ref('')
|
||||
const countdown = ref(0)
|
||||
let timer = null
|
||||
const agree = ref(false)
|
||||
|
||||
function onBankConfirm(selected) {
|
||||
const first = Array.isArray(selected) ? selected[0] : selected
|
||||
bankNameLabel.value = first?.value || first || ''
|
||||
form.bankName = bankNameLabel.value
|
||||
// 模拟获取分支列表,根据银行设置简单示例
|
||||
branchOptions[0] = bankNameLabel.value ? [`${bankNameLabel.value} 总行`, `${bankNameLabel.value} 广州分行`, `${bankNameLabel.value} 天河支行`] : ['请选择网点']
|
||||
}
|
||||
|
||||
function onBranchConfirm(selected) {
|
||||
const first = Array.isArray(selected) ? selected[0] : selected
|
||||
bankBranchLabel.value = first?.value || first || ''
|
||||
form.bankBranch = bankBranchLabel.value
|
||||
}
|
||||
|
||||
function onRegionConfirm(result) {
|
||||
console.log("result", result);
|
||||
form.bankCity = result
|
||||
bankCityLabel.value = `${result.province_name || ''} ${result.city_name || ''}`.trim()
|
||||
regionShow.value = false
|
||||
}
|
||||
|
||||
function sendCode() {
|
||||
// 简单校验手机号
|
||||
if (!/^1\d{10}$/.test(form.phone)) {
|
||||
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (countdown.value > 0) return
|
||||
// 触发发送(此处模拟)
|
||||
uni.showToast({ title: '验证码已发送', icon: 'none' })
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value <= 1) {
|
||||
clearInterval(timer)
|
||||
countdown.value = 0
|
||||
timer = null
|
||||
} else {
|
||||
countdown.value -= 1
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function onSubmitAccount() {
|
||||
try {
|
||||
await acctForm.value.validate()
|
||||
if (!agree.value) {
|
||||
uni.showToast({ title: '请先同意骑手协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 提交逻辑(示例):打印并提示
|
||||
console.log('结算表单', JSON.parse(JSON.stringify(form)))
|
||||
uni.showToast({ title: '提交申请成功', icon: 'none' })
|
||||
// 跳转到审核中页面
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({ url: '/pages/registered/audit' })
|
||||
}, 600)
|
||||
} catch (e) {
|
||||
console.warn('结算表单校验未通过', e)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 尝试从注册页恢复部分信息(如持卡人姓名)
|
||||
try {
|
||||
const saved = uni.getStorageSync('riderFormData') || null
|
||||
if (saved) {
|
||||
form.cardHolder = saved.realName || ''
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
padding: 38rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.code-row {
|
||||
display: flex;
|
||||
// gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.agree-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
gap: 12rpx;
|
||||
color: #999;
|
||||
}
|
||||
.checkbox {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.checkbox.checked {
|
||||
background: #09aaff;
|
||||
border-color: #09aaff;
|
||||
}
|
||||
.agree-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.link {
|
||||
color: #09aaff;
|
||||
}
|
||||
.submit-row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
69
pages/registered/audit.vue
Normal file
69
pages/registered/audit.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<s-layout class="audit-wrap">
|
||||
<view class="audit-content">
|
||||
<view class="icon-wrap" aria-hidden="true">
|
||||
<svg viewBox="0 0 64 64" class="clock-svg" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32" cy="32" r="30" fill="#09aaff"/>
|
||||
<path d="M32 18v14l10 6" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
</view>
|
||||
<view class="title">审核中</view>
|
||||
<view class="desc">审核结果将以短信进行通知,通过后分配订单哦~</view>
|
||||
<view class="btn-row">
|
||||
<up-button type="primary" plain @click="onDone">完成</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { } from 'vue'
|
||||
|
||||
function onDone() {
|
||||
// 返回首页(重启栈)
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.audit-wrap {
|
||||
background: #fff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.audit-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding-top: 140rpx;
|
||||
}
|
||||
.icon-wrap {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.clock-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 0 40rpx;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
.btn-row {
|
||||
width: 520rpx;
|
||||
}
|
||||
</style>
|
||||
691
pages/registered/registerRiders.vue
Normal file
691
pages/registered/registerRiders.vue
Normal file
@@ -0,0 +1,691 @@
|
||||
<template>
|
||||
<s-layout title="注册骑手" class="set-userinfo-wrap">
|
||||
<view class="page">
|
||||
<up-form ref="riderForm" :model="form" :rules="rules" labelPosition="left" labelWidth="90">
|
||||
<view class="section-title">基础信息</view>
|
||||
|
||||
<up-form-item label="真实姓名" prop="realName" :required="true">
|
||||
<up-input v-model="form.realName" placeholder="请输入您的姓名" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="身份证号" prop="idNo" :required="true">
|
||||
<up-input v-model="form.idNo" placeholder="数字开头18位号码" maxlength="18" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="生效日期" prop="birthDate" :required="true">
|
||||
<up-datetime-picker hasInput v-model="form.birthDate" mode="date" placeholder="请选择身份证生效日期" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="失效日期" prop="expiryDate" :required="true">
|
||||
<view class="expiry-row">
|
||||
<up-radio-group v-model="form.expiryMode" direction="horizontal">
|
||||
<up-radio name="long" label="长期有效"></up-radio>
|
||||
<up-radio name="date" label="选择失效日期"></up-radio>
|
||||
</up-radio-group>
|
||||
<up-datetime-picker v-if="form.expiryMode === 'date'" hasInput
|
||||
v-model="form.expiryDate" mode="date" placeholder="选择失效日期" />
|
||||
</view>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="性别" prop="gender" :required="true">
|
||||
<up-radio-group v-model="form.gender" direction="horizontal">
|
||||
<up-radio name="male" label="男"></up-radio>
|
||||
<up-radio name="female" label="女"></up-radio>
|
||||
</up-radio-group>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="紧急联系人姓名" prop="emergencyName" :required="true">
|
||||
<up-input v-model="form.emergencyName" placeholder="请输入" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="紧急联系人手机" prop="emergencyPhone" :required="true">
|
||||
<up-input v-model="form.emergencyPhone" placeholder="请输入" type="tel" maxlength="11" />
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="上传身份证正反面" prop="idImages">
|
||||
<view class="upload-row">
|
||||
<view class="upload-box">
|
||||
<up-upload :max-count="1" :show-file-list="false" @change="onUploadFront">
|
||||
<view class="upload-placeholder" v-if="!frontImage">
|
||||
<up-icon name="+" />
|
||||
</view>
|
||||
<up-image v-else :src="frontImage" mode="aspectFill" class="thumb" />
|
||||
</up-upload>
|
||||
<text class="hint">国徽面</text>
|
||||
</view>
|
||||
<view class="upload-box">
|
||||
<up-upload :max-count="1" :show-file-list="false" @change="onUploadBack">
|
||||
<view class="upload-placeholder" v-if="!backImage">
|
||||
<up-icon name="+" />
|
||||
</view>
|
||||
<up-image v-else :src="backImage" mode="aspectFill" class="thumb" />
|
||||
</up-upload>
|
||||
<text class="hint">人像面</text>
|
||||
</view>
|
||||
</view>
|
||||
</up-form-item>
|
||||
|
||||
<view class="section-title">接单选择</view>
|
||||
<up-form-item label="职业身份" prop="occupation" :required="true">
|
||||
<up-radio-group v-model="form.occupation" direction="horizontal">
|
||||
<up-radio name="student" label="在校学生"></up-radio>
|
||||
<up-radio name="worker" label="社会人员/职工"></up-radio>
|
||||
</up-radio-group>
|
||||
</up-form-item>
|
||||
|
||||
<!-- 学生视图:兼职意愿、可兼职时段、健康证 -->
|
||||
<template v-if="form.occupation === 'student'">
|
||||
<up-form-item label="兼职意愿" prop="partTimeIntent" :required="true">
|
||||
<up-picker hasInput :columns="partTimeOptions" v-model="partTimeLabel" @confirm="onPartTimeConfirm"></up-picker>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="请选择可兼职时段" prop="partTimePeriods">
|
||||
<view class="choose-row" @click="timeShow1 = true">
|
||||
<text class="muted">{{ partTimePeriodsLabel }}</text>
|
||||
<text class="status" :class="{empty: form.partTimePeriods.length===0}">{{ form.partTimePeriods.length===0 ? '待完善 >' : '已完善' }}</text>
|
||||
</view>
|
||||
</up-form-item>
|
||||
|
||||
<up-popup :show="timeShow1" mode="bottom" @close="timeShow1 = false">
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text>选择可兼职时段</text>
|
||||
</view>
|
||||
<view class="popup-body">
|
||||
<!-- 网格:每行为一天,三段时段按钮可切换选中 -->
|
||||
<view class="time-grid">
|
||||
<view class="time-row" v-for="(day, dayIndex) in days" :key="dayIndex">
|
||||
<view class="day-label">{{ day }}</view>
|
||||
<view class="slots">
|
||||
<view
|
||||
class="slot-btn"
|
||||
:class="{selected: isSlotSelected(dayIndex, 0)}"
|
||||
@click="toggleSlot(dayIndex, 0)"
|
||||
key="m"
|
||||
>08:00~13:00</view>
|
||||
<view
|
||||
class="slot-btn"
|
||||
:class="{selected: isSlotSelected(dayIndex, 1)}"
|
||||
@click="toggleSlot(dayIndex, 1)"
|
||||
key="a"
|
||||
>13:00~17:30</view>
|
||||
<view
|
||||
class="slot-btn"
|
||||
:class="{selected: isSlotSelected(dayIndex, 2)}"
|
||||
@click="toggleSlot(dayIndex, 2)"
|
||||
key="e"
|
||||
>17:00~22:30</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-footer">
|
||||
<up-button plain @click="onCancelTime">取消</up-button>
|
||||
<up-button type="primary" @click="onSaveTime">保存</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<!-- 社会人员/职工视图:类别、意向城市、健康证 -->
|
||||
<template v-else>
|
||||
<up-form-item label="类别" prop="category" :required="true">
|
||||
<up-picker hasInput :columns="categoryOptions" @confirm="onCategoryConfirm">
|
||||
<template #trigger>
|
||||
<up-input readonly v-model="categoryLabel" placeholder="请选择类别" />
|
||||
</template>
|
||||
</up-picker>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="意向城市" prop="city" :required="true">
|
||||
<up-input readonly v-model="cityLabel" placeholder="请选择" @tap="regionShow = true" />
|
||||
</up-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 健康证(学生/社会人员皆可上传) -->
|
||||
<up-form-item label="健康证" prop="healthCert">
|
||||
<view class="health-row" @click="openHealthPopup">
|
||||
<view class="health-left">上传健康证</view>
|
||||
<view class="health-right">
|
||||
<up-icon name="arrow-right" color="#6c6c6c" size="21"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</up-form-item>
|
||||
|
||||
<view class="submit-row">
|
||||
<up-button type="primary" @click="onSubmit">保存并下一步,完善工资结算信息</up-button>
|
||||
</view>
|
||||
</up-form>
|
||||
</view>
|
||||
|
||||
<!-- 健康证信息弹框 -->
|
||||
<up-popup :show="popupShow" mode="bottom" @close="onCancelHealth" :round="16" safeAreaInsetBottom>
|
||||
<view class="health-popup">
|
||||
<view class="health-popup-header">
|
||||
<text class="title">上传健康证</text>
|
||||
</view>
|
||||
<view class="health-popup-body">
|
||||
<view class="field-row">
|
||||
<text class="label">编号:</text>
|
||||
<up-input v-model="healthNumber" placeholder="请输入健康证编号" class="field-input" />
|
||||
</view>
|
||||
<view class="field-row">
|
||||
<text class="label">类别:</text>
|
||||
<up-input v-model="healthCategory" placeholder="请输入健康证类别" class="field-input" />
|
||||
</view>
|
||||
<view class="field-row date-row">
|
||||
<text class="label">有效日期:</text>
|
||||
<up-datetime-picker hasInput v-model="healthValidStart" mode="date" placeholder="年/月/日" class="date-input" />
|
||||
<text class="dash"> — </text>
|
||||
<up-datetime-picker hasInput v-model="healthValidEnd" mode="date" placeholder="年/月/日" class="date-input" />
|
||||
</view>
|
||||
|
||||
<view class="upload-list">
|
||||
<text class="upload-title">上传健康证:</text>
|
||||
<view class="upload-row-inner">
|
||||
<view v-for="(img, idx) in healthImages" :key="idx" class="upload-item">
|
||||
<up-image :src="img" class="upload-thumb" mode="aspectFill" />
|
||||
<view class="remove-btn" @click.stop="removeHealthImage(idx)">×</view>
|
||||
</view>
|
||||
<up-upload :max-count="1" :show-file-list="false" @change="onUploadHealth">
|
||||
<view class="upload-add" v-if="healthImages.length < 4">
|
||||
<up-icon name="+" />
|
||||
</view>
|
||||
</up-upload>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="health-popup-footer">
|
||||
<up-button plain @click="onCancelHealth">取消</up-button>
|
||||
<up-button type="primary" class="save-btn" @click="onSaveHealth">保存</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
<su-region-picker :show="regionShow" @confirm="onRegionConfirm" @cancel="regionShow = false" />
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed, watch, onBeforeMount } from 'vue'
|
||||
import AreaApi from '@/sheep/api/system/area';
|
||||
// import SuRegionPicker from 'sheep/ui/su-region-picker/su-region-picker.vue'
|
||||
|
||||
const form = reactive({
|
||||
realName: '',
|
||||
idNo: '',
|
||||
birthDate: '',
|
||||
expiryMode: 'long',
|
||||
expiryDate: '',
|
||||
gender: 'male',
|
||||
emergencyName: '',
|
||||
emergencyPhone: '',
|
||||
idImages: [],
|
||||
// 接单选择相关字段
|
||||
occupation: 'student',
|
||||
partTimeIntent: '',
|
||||
partTimePeriods: [],
|
||||
healthCert: '',
|
||||
// 健康证详情字段(弹窗保存到这里)
|
||||
healthCertNumber: '',
|
||||
healthCertCategory: '',
|
||||
healthCertValidStart: '',
|
||||
healthCertValidEnd: '',
|
||||
category: '',
|
||||
city: '',
|
||||
})
|
||||
|
||||
const timeShow1 = ref(false)
|
||||
const partTimeOptions = reactive([
|
||||
['长期(至少1学期)', '非长期(临时/偶尔兼职)']
|
||||
])
|
||||
const partTimeLabel = ref([])
|
||||
const categoryOptions = [
|
||||
['全职', '兼职']
|
||||
]
|
||||
const categoryLabel = ref([])
|
||||
const cityOptions = [
|
||||
{ text: '请选择城市', value: '' },
|
||||
{ text: '北京市', value: 'beijing' },
|
||||
{ text: '上海市', value: 'shanghai' },
|
||||
]
|
||||
const cityLabel = ref('')
|
||||
const regionShow = ref(false)
|
||||
// 可选时段网格数据与选择状态
|
||||
const days = ['周日','周一','周二','周三','周四','周五','周六']
|
||||
const timeSlots = ['08:00~13:00','13:00~17:30','17:00~22:30']
|
||||
// 使用二维布尔数组表示选择状态:selectedGrid[dayIndex][slotIndex] = true/false
|
||||
const selectedGrid = reactive(Array.from({ length: days.length }, () => [false, false, false]))
|
||||
// 弹窗临时副本,打开时拷贝 selectedGrid 到 tempSelected,用于取消恢复
|
||||
const tempSelected = reactive(Array.from({ length: days.length }, () => [false, false, false]))
|
||||
|
||||
const popupShow = ref(false)
|
||||
|
||||
function isSlotSelected(dayIndex, slotIndex) {
|
||||
return tempSelected[dayIndex][slotIndex]
|
||||
}
|
||||
|
||||
function toggleSlot(dayIndex, slotIndex) {
|
||||
tempSelected[dayIndex][slotIndex] = !tempSelected[dayIndex][slotIndex]
|
||||
}
|
||||
|
||||
function onCancelTime() {
|
||||
// 恢复原选中状态并关闭弹窗
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
for (let j = 0; j < timeSlots.length; j++) {
|
||||
tempSelected[i][j] = selectedGrid[i][j]
|
||||
}
|
||||
}
|
||||
timeShow1.value = false
|
||||
}
|
||||
|
||||
function onSaveTime() {
|
||||
// 将 tempSelected 同步到 selectedGrid 和 form.partTimePeriods(保存为可读文本)
|
||||
const selections = []
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
for (let j = 0; j < timeSlots.length; j++) {
|
||||
selectedGrid[i][j] = tempSelected[i][j]
|
||||
if (tempSelected[i][j]) {
|
||||
selections.push(`${days[i]} ${timeSlots[j]}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
form.partTimePeriods = selections
|
||||
timeShow1.value = false
|
||||
}
|
||||
|
||||
// 打开弹窗时将当前选择拷贝到 tempSelected
|
||||
watch(timeShow1, (val) => {
|
||||
if (val) {
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
for (let j = 0; j < timeSlots.length; j++) {
|
||||
tempSelected[i][j] = selectedGrid[i][j]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const rules = {
|
||||
realName: [{ required: true, message: '请输入真实姓名' }],
|
||||
idNo: [
|
||||
{ required: true, message: '请输入身份证号' },
|
||||
{ pattern: /^[0-9A-Za-z]{15,18}$/, message: '请输入正确的身份证号' },
|
||||
],
|
||||
birthDate: [{ required: true, message: '请选择生效日期' }],
|
||||
expiryDate: [
|
||||
{
|
||||
validator(value) {
|
||||
if (form.expiryMode === 'date' && !value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
message: '请选择失效日期',
|
||||
},
|
||||
],
|
||||
gender: [{ required: true, message: '请选择性别' }],
|
||||
emergencyName: [{ required: true, message: '请输入紧急联系人姓名' }],
|
||||
emergencyPhone: [
|
||||
{ required: true, message: '请输入紧急联系人手机' },
|
||||
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号码' },
|
||||
],
|
||||
}
|
||||
|
||||
const riderForm = ref(null)
|
||||
const frontImage = ref('')
|
||||
const backImage = ref('')
|
||||
const healthCert = ref('')
|
||||
// 健康证弹窗临时状态
|
||||
const healthImages = reactive([])
|
||||
const healthNumber = ref('')
|
||||
const healthCategory = ref('')
|
||||
const healthValidStart = ref('')
|
||||
const healthValidEnd = ref('')
|
||||
const partTimePeriodsLabel = computed(() => {
|
||||
return form.partTimePeriods.length ? form.partTimePeriods.join('、') : ''
|
||||
})
|
||||
|
||||
function onUploadFront(event) {
|
||||
const file = Array.isArray(event) ? event[0] : (event.detail || event)
|
||||
const url = file?.url || file?.path || file?.thumb || ''
|
||||
frontImage.value = url
|
||||
form.idImages = [url, form.idImages[1] || '']
|
||||
}
|
||||
|
||||
function onUploadBack(event) {
|
||||
const file = Array.isArray(event) ? event[0] : (event.detail || event)
|
||||
const url = file?.url || file?.path || file?.thumb || ''
|
||||
backImage.value = url
|
||||
form.idImages = [form.idImages[0] || '', url]
|
||||
}
|
||||
|
||||
function onUploadHealth(event) {
|
||||
const file = Array.isArray(event) ? event[0] : (event.detail || event)
|
||||
const url = file?.url || file?.path || file?.thumb || ''
|
||||
if (url) {
|
||||
// 限制最多 4 张预览图
|
||||
if (healthImages.length < 4) {
|
||||
healthImages.push(url)
|
||||
}
|
||||
healthCert.value = url
|
||||
// 保持兼容:form.healthCert 存首张图片(若需要可改为数组)
|
||||
form.healthCert = url
|
||||
}
|
||||
}
|
||||
|
||||
function onPartTimeConfirm(selected) {
|
||||
const first = Array.isArray(selected) ? selected[0] : selected
|
||||
if (first && (first.value || first.text)) {
|
||||
form.partTimeIntent = first.value || first.text
|
||||
partTimeLabel.value = first.text || first.value
|
||||
}
|
||||
}
|
||||
|
||||
function onCategoryConfirm(selected) {
|
||||
const first = Array.isArray(selected) ? selected[0] : selected
|
||||
if (first && (first.value || first.text)) {
|
||||
form.category = first.value || first.text
|
||||
categoryLabel.value = first.text || first.value
|
||||
}
|
||||
}
|
||||
|
||||
function onCityConfirm(selected) {
|
||||
const first = Array.isArray(selected) ? selected[0] : selected
|
||||
if (first && (first.value || first.text)) {
|
||||
form.city = first.value || first.text
|
||||
cityLabel.value = first.text || first.value
|
||||
}
|
||||
}
|
||||
|
||||
function onRegionConfirm(result) {
|
||||
// result: { province_name, province_id, city_name, city_id, district_name, district_id }
|
||||
form.city = result
|
||||
cityLabel.value = `${result.province_name || ''} ${result.city_name || ''} ${result.district_name || ''}`.trim()
|
||||
regionShow.value = false
|
||||
}
|
||||
|
||||
function openHealthPopup() {
|
||||
// 从表单恢复到弹窗临时状态
|
||||
healthImages.splice(0, healthImages.length)
|
||||
if (form.healthCert) {
|
||||
healthImages.push(form.healthCert)
|
||||
}
|
||||
healthNumber.value = form.healthCertNumber || ''
|
||||
healthCategory.value = form.healthCertCategory || ''
|
||||
healthValidStart.value = form.healthCertValidStart || ''
|
||||
healthValidEnd.value = form.healthCertValidEnd || ''
|
||||
popupShow.value = true
|
||||
}
|
||||
|
||||
function removeHealthImage(index) {
|
||||
if (index >= 0 && index < healthImages.length) {
|
||||
healthImages.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function onCancelHealth() {
|
||||
// 直接关闭弹窗,放弃临时更改
|
||||
popupShow.value = false
|
||||
}
|
||||
|
||||
function onSaveHealth() {
|
||||
// 保存弹窗数据回表单
|
||||
form.healthCert = healthImages.length ? healthImages[0] : ''
|
||||
healthCert.value = form.healthCert
|
||||
form.healthCertNumber = healthNumber.value
|
||||
form.healthCertCategory = healthCategory.value
|
||||
form.healthCertValidStart = healthValidStart.value
|
||||
form.healthCertValidEnd = healthValidEnd.value
|
||||
popupShow.value = false
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
await riderForm.value.validate()
|
||||
console.log('提交表单', JSON.parse(JSON.stringify(form)))
|
||||
// 将已填写的注册信息临时存储,供结算页继续使用
|
||||
try {
|
||||
uni.setStorageSync('riderFormData', JSON.parse(JSON.stringify(form)))
|
||||
} catch (err) {
|
||||
console.warn('存储注册信息失败', err)
|
||||
}
|
||||
// 跳转到工资结算账户信息页面
|
||||
uni.navigateTo({ url: '/pages/registered/accountInfo' })
|
||||
} catch (e) {
|
||||
console.warn('表单校验未通过', e)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!!uni.getStorageSync('areaData')) {
|
||||
return;
|
||||
}
|
||||
// 提前加载省市区数据
|
||||
AreaApi.getAreaTree().then((res) => {
|
||||
if (res.code === 0) {
|
||||
uni.setStorageSync('areaData', res.data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
padding: 38rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.section-title {
|
||||
padding: 10px 0;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
}
|
||||
.expiry-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.upload-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
.upload-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.upload-placeholder {
|
||||
width: 88px;
|
||||
height: 60px;
|
||||
border: 1px dashed #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.thumb {
|
||||
width: 88px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.hint {
|
||||
margin-top: 6px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
.submit-row {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.time-grid {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.time-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.day-label {
|
||||
width: 90rpx;
|
||||
color: #333;
|
||||
}
|
||||
.slots {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
.slot-btn {
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.slot-btn.selected {
|
||||
background: #09aaff;
|
||||
color: #fff;
|
||||
}
|
||||
.popup-footer {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.popup-content {
|
||||
padding: 50rpx 20rpx 20rpx;
|
||||
}
|
||||
.popup-body {
|
||||
padding: 10rpx 15rpx 20rpx;
|
||||
}
|
||||
/* 健康证相关样式 */
|
||||
.health-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
.health-left {
|
||||
color: #333;
|
||||
}
|
||||
.health-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
.health-thumbs {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
align-items: center;
|
||||
}
|
||||
.health-thumb-wrap {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
.health-thumb {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.health-add {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border: 1px dashed #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
color: #999;
|
||||
}
|
||||
.health-popup {
|
||||
padding: 30rpx 30rpx 30rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.health-popup-header .title {
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.health-popup-body {
|
||||
padding: 10rpx 0 20rpx;
|
||||
}
|
||||
.field-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 38rpx;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.label {
|
||||
width: 90rpx;
|
||||
color: #666;
|
||||
}
|
||||
.field-input {
|
||||
flex: 1;
|
||||
}
|
||||
.date-row .date-input {
|
||||
width: 35%;
|
||||
}
|
||||
.dash {
|
||||
width: 10rpx;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
.upload-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.upload-title {
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
.upload-row-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.upload-item {
|
||||
position: relative;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
.upload-thumb {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.remove-btn {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.upload-add {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border: 1px dashed #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
color: #999;
|
||||
}
|
||||
.health-popup-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
.save-btn {
|
||||
width: 240rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
214
pages/user/orderRecord.vue
Normal file
214
pages/user/orderRecord.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<s-layout title="接单记录" class="order-record-page">
|
||||
<view class="page-content">
|
||||
<view class="top-select" @tap="showPicker = true">
|
||||
<view class="select-style">
|
||||
<text style="margin-right:15rpx;">{{ selectedLabel }}</text>
|
||||
<up-icon name="arrow-down" color="#757575" size="15"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- up-picker -->
|
||||
<up-picker
|
||||
:show="showPicker"
|
||||
:columns="columns"
|
||||
:defaultIndex="[defaultIndex]"
|
||||
@confirm="onConfirm"
|
||||
@cancel="showPicker = false"
|
||||
@close="showPicker = false"
|
||||
></up-picker>
|
||||
|
||||
<!-- 顶部提示 -->
|
||||
<view class="notice">数据统计均截止昨日23:59,可能存在延迟,请耐心等待</view>
|
||||
|
||||
<!-- 单量卡片 -->
|
||||
<view class="card card-volume">
|
||||
<view class="card-header">
|
||||
<view class="card-title">单量</view>
|
||||
<view class="card-link" @click="toList">
|
||||
查看 <up-icon name="arrow-right" color="#757575" size="15"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body volume-body">
|
||||
<text class="volume-number">10<span class="unit">单</span></text>
|
||||
<text class="volume-sub">已完成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转单记录卡片 -->
|
||||
<!-- <view class="card card-transfer">
|
||||
<view class="card-header">
|
||||
<view class="left">
|
||||
<text class="card-title">转单记录</text>
|
||||
</view>
|
||||
<view class="card-link">
|
||||
查看 <up-icon name="arrow-right" color="#757575" size="15"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body transfer-body">
|
||||
<view class="col">
|
||||
<text class="col-number">0<span class="unit">单</span></text>
|
||||
<text class="col-label">待处理订单</text>
|
||||
</view>
|
||||
<view class="col">
|
||||
<text class="col-number">0<span class="unit">单</span></text>
|
||||
<text class="col-label">我转出的</text>
|
||||
</view>
|
||||
<view class="col">
|
||||
<text class="col-number">0<span class="unit">单</span></text>
|
||||
<text class="col-label">我接收的</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// picker 显示控制
|
||||
const showPicker = ref(false);
|
||||
// 默认选中标签
|
||||
const selectedLabel = ref('今日');
|
||||
// 默认选中的索引(0:今日)
|
||||
const defaultIndex = 0;
|
||||
|
||||
// 列数据,u-picker 接受 columns 为数组的数组
|
||||
const columns = [
|
||||
[
|
||||
{ text: '今日', value: 'today' },
|
||||
{ text: '昨日', value: 'yesterday' },
|
||||
{ text: '近7天', value: 'last7' },
|
||||
{ text: '本月', value: 'month' },
|
||||
],
|
||||
];
|
||||
|
||||
function onConfirm(e) {
|
||||
// e.value 为选中的值数组(每列一个)
|
||||
const value = e && e.value && e.value[0];
|
||||
if (value) {
|
||||
selectedLabel.value = value.text || String(value);
|
||||
}
|
||||
showPicker.value = false;
|
||||
}
|
||||
|
||||
function toList() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/recordList'
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.top-select {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 0 0 25rpx;
|
||||
}
|
||||
|
||||
.select-style {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.notice {
|
||||
background: #f5f5f5;
|
||||
color: #999;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: #e74c3c;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.volume-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 8px 0 4px;
|
||||
}
|
||||
|
||||
.volume-number {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 18px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.volume-sub {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.transfer-body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.col-number {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.col-label {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
200
pages/user/recordList.vue
Normal file
200
pages/user/recordList.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<s-layout title="订单记录" class="record-list-page">
|
||||
<view class="page-wrap">
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view class="order-card" v-for="order in orders" :key="order.id">
|
||||
<!-- 收入 -->
|
||||
<view class="card-top">
|
||||
<text class="income">本单收入 {{ order.income }}元</text>
|
||||
</view>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 内容头 -->
|
||||
<view class="card-header">
|
||||
<view class="left">
|
||||
<text class="label">订单号:</text>
|
||||
<text class="order-no">{{ order.orderNo }}</text>
|
||||
</view>
|
||||
<text class="status">{{ order.statusText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 可能的提示 -->
|
||||
<view v-if="order.notice" class="notice-orange">
|
||||
{{ order.notice }}
|
||||
</view>
|
||||
|
||||
<!-- 地址信息 -->
|
||||
<view class="card-body">
|
||||
<!-- 起点 -->
|
||||
<view class="addr-row">
|
||||
<text class="distance">{{ order.pickDistance }}</text>
|
||||
<view class="addr-content">
|
||||
<text class="place-title">{{ order.pickName }}</text>
|
||||
<text class="place-addr">{{ order.pickAddr }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 终点 -->
|
||||
<view class="addr-row to">
|
||||
<text class="distance">{{ order.deliverDistance }}</text>
|
||||
<view class="addr-content">
|
||||
<text class="place-title">{{ order.deliverName }}</text>
|
||||
<text class="place-addr">{{ order.deliverAddr }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<view class="card-footer">
|
||||
<text class="customer">{{ order.customer }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 静态示例数据,后续可对接接口
|
||||
const orders = ref([
|
||||
{
|
||||
id: 1,
|
||||
income: '12.5',
|
||||
orderNo: '2021021115544',
|
||||
statusText: '已完成',
|
||||
notice: '',
|
||||
pickDistance: '873m',
|
||||
pickName: '乐易购-学院站',
|
||||
pickAddr: '广东省广州市天河区学院站荷光路',
|
||||
deliverDistance: '1.2km',
|
||||
deliverName: '广东省广州市天河区**********',
|
||||
deliverAddr: '张氏(先生) 屋号1254',
|
||||
customer: '',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
income: '12.5',
|
||||
orderNo: '2021021115545',
|
||||
statusText: '顾客取消订单',
|
||||
notice: '提示:已取餐订单,顾客退款不影响配送费结算',
|
||||
pickDistance: '873m',
|
||||
pickName: '乐易购-学院站',
|
||||
pickAddr: '广东省广州市天河区学院站荷光路',
|
||||
deliverDistance: '1.2km',
|
||||
deliverName: '广东省广州市天河区**********',
|
||||
deliverAddr: '张氏(先生) 屋号1254',
|
||||
customer: '',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
income: '12.5',
|
||||
orderNo: '2021021115546',
|
||||
statusText: '顾客取消订单',
|
||||
notice: '',
|
||||
pickDistance: '873m',
|
||||
pickName: '乐易购-学院站',
|
||||
pickAddr: '广东省广州市天河区学院站荷光路',
|
||||
deliverDistance: '1.2km',
|
||||
deliverName: '广东省广州市天河区**********',
|
||||
deliverAddr: '张氏(先生) 屋号1254',
|
||||
customer: '',
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-wrap {
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
}
|
||||
.list {
|
||||
min-height: 200px;
|
||||
}
|
||||
.order-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 14px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
}
|
||||
.card-top {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.income {
|
||||
color: #e74c3c;
|
||||
font-weight: 600;
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: #eee;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.card-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.order-no {
|
||||
color: #222;
|
||||
font-weight: 600;
|
||||
}
|
||||
.status {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
.notice-orange {
|
||||
background: #f3a23a;
|
||||
color: #fff;
|
||||
padding: 8px 12px;
|
||||
margin: 6px 14px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.card-body {
|
||||
padding: 10px 14px 6px;
|
||||
}
|
||||
.addr-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.addr-row.to {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.distance {
|
||||
color: #999;
|
||||
width: 42px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.addr-content {
|
||||
flex: 1;
|
||||
}
|
||||
.place-title {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.place-addr {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
.card-footer {
|
||||
padding: 10px 14px 14px;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,19 @@ const AuthUtil = {
|
||||
},
|
||||
});
|
||||
},
|
||||
// 账号登录
|
||||
loginAccount: (data) => {
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'POST',
|
||||
data,
|
||||
custom: {
|
||||
showSuccess: true,
|
||||
loadingMsg: '登录中',
|
||||
successMsg: '登录成功',
|
||||
},
|
||||
});
|
||||
},
|
||||
// 使用手机 + 验证码登录
|
||||
smsLogin: (data) => {
|
||||
return request({
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="ss-m-b-20">
|
||||
<!-- <view class="head-title-active head-title-line" @tap="showAuthModal('smsLogin')">
|
||||
短信登录
|
||||
</view> -->
|
||||
<view class="head-title head-title-animation">登录账号</view>
|
||||
</view>
|
||||
<view class="head-subtitle">如无账号请联系平台开通</view>
|
||||
<!-- <view class="head-subtitle">如果未设置过密码,请点击忘记密码</view> -->
|
||||
</view>
|
||||
|
||||
@@ -24,11 +20,11 @@
|
||||
>
|
||||
<uni-forms-item name="mobile" label="账号">
|
||||
<uni-easyinput placeholder="请输入手机号" v-model="state.model.mobile" :inputBorder="false">
|
||||
<!-- <template v-slot:right>
|
||||
<template v-slot:right>
|
||||
<button class="ss-reset-button forgot-btn" @tap="showAuthModal('resetPassword')">
|
||||
忘记密码
|
||||
</button>
|
||||
</template> -->
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
@@ -45,6 +41,14 @@
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<view class="text-center">
|
||||
<text class="head-title-active head-title-line" @tap="showAuthModal('smsLogin')">
|
||||
验证码登录
|
||||
</text>
|
||||
<text class="head-title-active head-title-line" style="margin-left:25rpx;" @click="toRegister">
|
||||
骑手注册
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -69,7 +73,8 @@
|
||||
// 数据
|
||||
const state = reactive({
|
||||
model: {
|
||||
mobile: '', // 账号
|
||||
username: '', // 账号
|
||||
mobile: '',
|
||||
password: '', // 密码
|
||||
},
|
||||
rules: {
|
||||
@@ -97,10 +102,18 @@
|
||||
|
||||
// 提交数据
|
||||
const { code, data } = await AuthUtil.login(state.model);
|
||||
// const { code, data } = await AuthUtil.loginAccount(state.model);
|
||||
if (code === 0) {
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
|
||||
const toRegister = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/registered/registerRiders'
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="head-title ss-m-b-20">重置密码</view>
|
||||
<view class="head-title ss-m-b-20 text-center">重置密码</view>
|
||||
<view class="head-subtitle">为了您的账号安全,设置密码前请先进行安全验证</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="ss-flex ss-m-b-20">
|
||||
<view class="ss-m-b-20 ">
|
||||
<view class="head-title head-title-line head-title-animation">短信登录</view>
|
||||
<view class="head-title-active ss-m-r-40" @tap="showAuthModal('accountLogin')">
|
||||
账号登录
|
||||
</view>
|
||||
</view>
|
||||
<view class="head-subtitle">未注册的手机号,验证后自动注册账号</view>
|
||||
</view>
|
||||
@@ -55,6 +52,10 @@
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
|
||||
<view class="head-title-active ss-m-r-40 text-center" @tap="showAuthModal('accountLogin')">
|
||||
账号登录
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -154,3 +154,7 @@
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -10,23 +10,24 @@
|
||||
/>
|
||||
|
||||
<!-- 2. 短信登录 smsLogin -->
|
||||
<!-- <sms-login
|
||||
<smsLogin
|
||||
v-if="authType === 'smsLogin'"
|
||||
:agreeStatus="state.protocol"
|
||||
@onConfirm="onConfirm"
|
||||
/> -->
|
||||
/>
|
||||
|
||||
|
||||
<!-- 3. 忘记密码 resetPassword-->
|
||||
<reset-password v-if="authType === 'resetPassword'" />
|
||||
|
||||
<!-- 4. 绑定手机号 changeMobile -->
|
||||
<change-mobile v-if="authType === 'changeMobile'" />
|
||||
<!-- <change-mobile v-if="authType === 'changeMobile'" /> -->
|
||||
|
||||
<!-- 5. 修改密码 changePassword-->
|
||||
<changePassword v-if="authType === 'changePassword'" />
|
||||
|
||||
<!-- 6. 微信小程序授权 -->
|
||||
<mp-authorization v-if="authType === 'mpAuthorization'" />
|
||||
<!-- <mp-authorization v-if="authType === 'mpAuthorization'" /> -->
|
||||
|
||||
<!-- 7. 第三方登录 -->
|
||||
<view
|
||||
@@ -34,8 +35,8 @@
|
||||
class="auto-login-box ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
>
|
||||
<!-- 7.1 微信小程序的快捷登录 -->
|
||||
<view v-if="sheep.$platform.name === 'WechatMiniProgram'" class="ss-flex register-box">
|
||||
<!-- <view class="register-title">还没有账号?</view> -->
|
||||
<!-- <view v-if="sheep.$platform.name === 'WechatMiniProgram'" class="ss-flex register-box">
|
||||
<view class="register-title">还没有账号?</view>
|
||||
<view class="register-title">已经拥有账号可以,</view>
|
||||
<button
|
||||
class="ss-reset-button login-btn"
|
||||
@@ -45,7 +46,7 @@
|
||||
快捷登录
|
||||
</button>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 7.2 微信的公众号、App、小程序的登录,基于 openid + code -->
|
||||
<!-- <button
|
||||
|
||||
@@ -19,17 +19,20 @@
|
||||
@pickstart="pickstart"
|
||||
@pickend="pickend"
|
||||
>
|
||||
<!-- 省级选择列 -->
|
||||
<picker-view-column>
|
||||
<view class="ui-column-item" v-for="province in provinceList" :key="province.id">
|
||||
<view :style="getSizeByNameLength(province.name)">{{ province.name }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<!-- 市级选择列 -->
|
||||
<picker-view-column v-if="props.level >= 2">
|
||||
<view class="ui-column-item" v-for="city in cityList" :key="city.id">
|
||||
<view :style="getSizeByNameLength(city.name)">{{ city.name }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<!-- 区级选择列 -->
|
||||
<picker-view-column v-if="props.level >= 3">
|
||||
<view class="ui-column-item" v-for="district in districtList" :key="district.id">
|
||||
<view :style="getSizeByNameLength(district.name)">{{ district.name }}</view>
|
||||
</view>
|
||||
@@ -56,6 +59,7 @@
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认1075)
|
||||
* @property {Array} default-selector 数组形式,其中每一项表示选择了range对应项中的第几个
|
||||
* @property {String} range-key 当range参数的元素为对象时,指定Object中的哪个key的值作为选择器显示内容
|
||||
* @property {Number} level 选择器层级:1-只显示省,2-显示省市,3-显示省市区(默认3)
|
||||
* @event {Function} confirm 点击确定按钮,返回当前选择的值
|
||||
* @event {Function} cancel 点击取消按钮,返回当前选择的值
|
||||
*/
|
||||
@@ -85,6 +89,12 @@
|
||||
type: String,
|
||||
default: '确认',
|
||||
},
|
||||
// 选择器层级:1-只显示省,2-显示省市,3-显示省市区
|
||||
level: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
validator: (value) => [1, 2, 3].includes(value),
|
||||
},
|
||||
});
|
||||
const areaData = uni.getStorageSync('areaData');
|
||||
|
||||
@@ -98,7 +108,7 @@
|
||||
}
|
||||
};
|
||||
const state = reactive({
|
||||
currentIndex: [0, 0, 0],
|
||||
currentIndex: Array(props.level).fill(0),
|
||||
moving: false, // 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
|
||||
});
|
||||
const emits = defineEmits(['confirm', 'cancel', 'change']);
|
||||
@@ -106,10 +116,12 @@
|
||||
const provinceList = areaData;
|
||||
|
||||
const cityList = computed(() => {
|
||||
return areaData[state.currentIndex[0]].children;
|
||||
const province = areaData?.[state.currentIndex[0]];
|
||||
return province?.children || [];
|
||||
});
|
||||
const districtList = computed(() => {
|
||||
return cityList.value[state.currentIndex[1]]?.children;
|
||||
const city = cityList.value?.[state.currentIndex[1]];
|
||||
return city?.children || [];
|
||||
});
|
||||
// 标识滑动开始,只有微信小程序才有这样的事件
|
||||
const pickstart = () => {
|
||||
@@ -132,21 +144,28 @@
|
||||
|
||||
// 用户更改picker的列选项
|
||||
const change = (e) => {
|
||||
if (
|
||||
state.currentIndex[0] === e.detail.value[0] &&
|
||||
state.currentIndex[1] === e.detail.value[1]
|
||||
) {
|
||||
// 不更改省市区列表
|
||||
state.currentIndex[2] = e.detail.value[2];
|
||||
return;
|
||||
} else {
|
||||
// 更改省市区列表
|
||||
if (state.currentIndex[0] !== e.detail.value[0]) {
|
||||
e.detail.value[1] = 0;
|
||||
const newIndex = [...e.detail.value];
|
||||
let shouldResetSubsequent = false;
|
||||
|
||||
// 检查每一列是否发生变化,如果上级发生变化,需要重置下级
|
||||
for (let i = 0; i < props.level; i++) {
|
||||
if (state.currentIndex[i] !== newIndex[i]) {
|
||||
shouldResetSubsequent = true;
|
||||
// 重置当前列之后的所有列
|
||||
for (let j = i + 1; j < props.level; j++) {
|
||||
newIndex[j] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
e.detail.value[2] = 0;
|
||||
state.currentIndex = e.detail.value;
|
||||
}
|
||||
|
||||
if (shouldResetSubsequent) {
|
||||
state.currentIndex = newIndex;
|
||||
} else {
|
||||
// 只有最后一列变化,不需要重置
|
||||
state.currentIndex = newIndex;
|
||||
}
|
||||
|
||||
emits('change', state.currentIndex);
|
||||
};
|
||||
|
||||
@@ -156,18 +175,29 @@
|
||||
if (state.moving) return;
|
||||
// #endif
|
||||
let index = state.currentIndex;
|
||||
let province = provinceList[index[0]];
|
||||
let city = cityList.value[index[1]];
|
||||
let district = districtList.value[index[2]];
|
||||
let province = provinceList?.[index[0]];
|
||||
if (!province) return;
|
||||
let result = {
|
||||
province_name: province.name,
|
||||
province_id: province.id,
|
||||
city_name: city.name,
|
||||
city_id: city.id,
|
||||
district_name: district.name,
|
||||
district_id: district.id,
|
||||
};
|
||||
|
||||
if (props.level >= 2) {
|
||||
let city = cityList.value?.[index[1]];
|
||||
if (city?.name) {
|
||||
result.city_name = city.name;
|
||||
result.city_id = city.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.level >= 3) {
|
||||
let district = districtList.value?.[index[2]];
|
||||
if (district?.name) {
|
||||
result.district_name = district.name;
|
||||
result.district_id = district.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (event) emits(event, result);
|
||||
};
|
||||
</script>
|
||||
|
||||
1
uni.scss
1
uni.scss
@@ -6,6 +6,7 @@
|
||||
*
|
||||
*/
|
||||
@import '@/sheep/scss/_var.scss';
|
||||
@import '@/uni_modules/uview-plus/theme.scss';
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
|
||||
21
uni_modules/uview-plus/LICENSE
Normal file
21
uni_modules/uview-plus/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 https://uiadmin.net/uview-plus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
71
uni_modules/uview-plus/README.md
Normal file
71
uni_modules/uview-plus/README.md
Normal file
@@ -0,0 +1,71 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://uiadmin.net/uview-plus/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
|
||||
</p>
|
||||
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3>
|
||||
<h3 align="center">多平台快速开发的UI框架</h3>
|
||||
|
||||
[](https://github.com/ijry/uview-plus)
|
||||
[](https://github.com/ijry/uview-plus)
|
||||
[](https://github.com/ijry/uview-plus/issues)
|
||||
[](https://gitee.com/jry/uview-plus/releases)
|
||||
[](https://en.wikipedia.org/wiki/MIT_License)
|
||||
|
||||
## 说明
|
||||
|
||||
uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x(已经发布https://ext.dcloud.net.cn/plugin?name=uview-ultra)的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
|
||||
|
||||
## 可视化设计
|
||||
|
||||
uview-plus现已推出免费可视化设计,可以方便的进行页面可视化设计,导出源码即可使用。极大提高前端页面开发效率;如产品经理设计师直接使用更可作为高保真高可用原型制作工具,让设计稿即代码,无需传统的设计稿开发还原步骤。
|
||||
|
||||
<img src="https://s3.bmp.ovh/imgs/2024/11/24/fd58d00071e6e5df.png" width="900" height="auto" >
|
||||
<img src="https://s3.bmp.ovh/imgs/2024/11/24/8e85a519fe627fb1.png" width="900" height="auto" >
|
||||
|
||||
|
||||
## 文档
|
||||
[官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
|
||||
[备用文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus)
|
||||
|
||||
|
||||
## 预览
|
||||
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" >
|
||||
|
||||
## 链接
|
||||
|
||||
- [官方文档](https://uview-plus.jiangruyi.com)
|
||||
- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html)
|
||||
- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html)
|
||||
- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html)
|
||||
|
||||
|
||||
## 关于PR
|
||||
|
||||
> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
|
||||
> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
|
||||
|
||||
## 安装
|
||||
|
||||
#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus)
|
||||
|
||||
请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容
|
||||
|
||||
## 快速上手
|
||||
|
||||
请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 使用方法
|
||||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<u-button text="按钮"></u-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 版权信息
|
||||
uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。
|
||||
|
||||
1491
uni_modules/uview-plus/changelog.md
Normal file
1491
uni_modules/uview-plus/changelog.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<view class="u-action-sheet-data">
|
||||
<view class="u-action-sheet-data__trigger">
|
||||
<slot name="trigger"></slot>
|
||||
<up-input
|
||||
v-if="!$slots['trigger']"
|
||||
:modelValue="current"
|
||||
disabled
|
||||
disabledColor="#ffffff"
|
||||
:placeholder="title"
|
||||
border="none"
|
||||
></up-input>
|
||||
<view @click="show = true"
|
||||
class="u-action-sheet-data__trigger__cover"></view>
|
||||
</view>
|
||||
<up-action-sheet
|
||||
:show="show"
|
||||
:actions="options"
|
||||
:title="title"
|
||||
safeAreaInsetBottom
|
||||
:description="description"
|
||||
@close="show = false"
|
||||
@select="select"
|
||||
>
|
||||
</up-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
current: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.modelValue) {
|
||||
this.options.forEach((ele) => {
|
||||
if (ele[this.valueKey] == this.modelValue) {
|
||||
this.current = ele[this.labelKey]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
watch: {
|
||||
modelValue() {
|
||||
this.options.forEach((ele) => {
|
||||
if (ele[this.valueKey] == this.modelValue) {
|
||||
this.current = ele[this.labelKey]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideKeyboard() {
|
||||
uni.hideKeyboard()
|
||||
},
|
||||
select(e) {
|
||||
this.$emit('update:modelValue', e[this.valueKey])
|
||||
this.current = e[this.labelKey]
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-action-sheet-data {
|
||||
&__trigger {
|
||||
position: relative;
|
||||
&__cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 10:52:35
|
||||
* @FilePath : /uview-plus/libs/config/props/actionSheet.js
|
||||
*/
|
||||
export default {
|
||||
// action-sheet组件
|
||||
actionSheet: {
|
||||
show: false,
|
||||
title: '',
|
||||
description: '',
|
||||
actions: [],
|
||||
index: '',
|
||||
cancelText: '',
|
||||
closeOnClickAction: true,
|
||||
safeAreaInsetBottom: true,
|
||||
openType: '',
|
||||
closeOnClickOverlay: true,
|
||||
round: 0,
|
||||
wrapMaxHeight: '600px'
|
||||
}
|
||||
}
|
||||
70
uni_modules/uview-plus/components/u-action-sheet/props.js
Normal file
70
uni_modules/uview-plus/components/u-action-sheet/props.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 10:52:35
|
||||
* @FilePath : /uview-plus/libs/config/props/props.js
|
||||
*/
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 操作菜单是否展示 (默认false)
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.show
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.title
|
||||
},
|
||||
// 选项上方的描述信息
|
||||
description: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.description
|
||||
},
|
||||
// 数据
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => defProps.actionSheet.actions
|
||||
},
|
||||
// 取消按钮的文字,不为空时显示按钮
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.cancelText
|
||||
},
|
||||
// 点击某个菜单项时是否关闭弹窗
|
||||
closeOnClickAction: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.closeOnClickAction
|
||||
},
|
||||
// 处理底部安全区(默认true)
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.safeAreaInsetBottom
|
||||
},
|
||||
// 小程序的打开方式
|
||||
openType: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.openType
|
||||
},
|
||||
// 点击遮罩是否允许关闭 (默认true)
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.closeOnClickOverlay
|
||||
},
|
||||
// 圆角值
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: () => defProps.actionSheet.round
|
||||
},
|
||||
// 选项区域最大高度
|
||||
wrapMaxHeight: {
|
||||
type: [String],
|
||||
default: () => defProps.actionSheet.wrapMaxHeight
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
mode="bottom"
|
||||
@close="closeHandler"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:round="round"
|
||||
>
|
||||
<view class="u-action-sheet">
|
||||
<!-- 顶部标题区域 -->
|
||||
<view
|
||||
class="u-action-sheet__header"
|
||||
v-if="title"
|
||||
>
|
||||
<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
|
||||
<view
|
||||
class="u-action-sheet__header__icon-wrap"
|
||||
@tap.stop="cancel"
|
||||
>
|
||||
<up-icon
|
||||
name="close"
|
||||
size="17"
|
||||
color="#c8c9cc"
|
||||
bold
|
||||
></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 描述信息 -->
|
||||
<text
|
||||
class="u-action-sheet__description"
|
||||
:style="[{
|
||||
marginTop: `${title && description ? 0 : '18px'}`
|
||||
}]"
|
||||
v-if="description"
|
||||
>{{description}}</text>
|
||||
<slot>
|
||||
<!-- 分割线 -->
|
||||
<u-line v-if="description"></u-line>
|
||||
<!-- 操作项列表 -->
|
||||
<scroll-view scroll-y class="u-action-sheet__item-wrap" :style="{maxHeight: wrapMaxHeight}">
|
||||
<view :key="index" v-for="(item, index) in actions">
|
||||
<!-- #ifdef MP -->
|
||||
<button
|
||||
class="u-reset-button"
|
||||
:openType="item.openType"
|
||||
@getuserinfo="onGetUserInfo"
|
||||
@contact="onContact"
|
||||
@getphonenumber="onGetPhoneNumber"
|
||||
@error="onError"
|
||||
@launchapp="onLaunchApp"
|
||||
@opensetting="onOpenSetting"
|
||||
:lang="lang"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-title="sendMessageTitle"
|
||||
:send-message-path="sendMessagePath"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
:app-parameter="appParameter"
|
||||
@tap="selectHandler(index)"
|
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||
>
|
||||
<!-- #endif -->
|
||||
<view
|
||||
class="u-action-sheet__item-wrap__item"
|
||||
@tap.stop="selectHandler(index)"
|
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||
:hover-stay-time="150"
|
||||
:style="getItemHoverStyle(index)"
|
||||
>
|
||||
<template v-if="!item.loading">
|
||||
<text
|
||||
class="u-action-sheet__item-wrap__item__name"
|
||||
:style="[itemStyle(index)]"
|
||||
>{{ item.name }}</text>
|
||||
<text
|
||||
v-if="item.subname"
|
||||
class="u-action-sheet__item-wrap__item__subname"
|
||||
>{{ item.subname }}</text>
|
||||
</template>
|
||||
<!-- 加载状态图标 -->
|
||||
<u-loading-icon
|
||||
v-else
|
||||
custom-class="van-action-sheet__loading"
|
||||
size="18"
|
||||
mode="circle"
|
||||
/>
|
||||
</view>
|
||||
<!-- #ifdef MP -->
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- 选项间分割线 -->
|
||||
<u-line v-if="index !== actions.length - 1"></u-line>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</slot>
|
||||
<!-- 取消按钮前的分割区域 -->
|
||||
<u-gap
|
||||
bgColor="#eaeaec"
|
||||
height="6"
|
||||
v-if="cancelText"
|
||||
></u-gap>
|
||||
<!-- 取消按钮 -->
|
||||
<view class="u-action-sheet__item-wrap__item u-action-sheet__cancel"
|
||||
hover-class="u-action-sheet--hover" @tap="cancel" v-if="cancelText">
|
||||
<text
|
||||
@touchmove.stop.prevent
|
||||
:hover-stay-time="150"
|
||||
class="u-action-sheet__cancel-text"
|
||||
>{{cancelText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { openType } from '../../libs/mixin/openType'
|
||||
import { buttonMixin } from '../../libs/mixin/button'
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit } from '../../libs/function/index';
|
||||
/**
|
||||
* ActionSheet 操作菜单
|
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/actionSheet.html
|
||||
*
|
||||
* @property {Boolean} show 操作菜单是否展示 (默认 false )
|
||||
* @property {String} title 操作菜单标题
|
||||
* @property {String} description 选项上方的描述信息
|
||||
* @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
|
||||
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
|
||||
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
|
||||
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
|
||||
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
|
||||
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
|
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
|
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
|
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效
|
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
|
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
|
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
|
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
|
||||
*
|
||||
* @event {Function} select 点击ActionSheet列表项时触发
|
||||
* @event {Function} close 点击取消按钮时触发
|
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
|
||||
* @event {Function} contact 客服消息回调,openType="contact"时有效
|
||||
* @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
|
||||
* @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
|
||||
* @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
|
||||
* @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
|
||||
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
|
||||
*/
|
||||
export default {
|
||||
name: "u-action-sheet",
|
||||
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
|
||||
mixins: [openType, buttonMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
if (this.actions[index].color) style.color = this.actions[index].color
|
||||
if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize)
|
||||
// 选项被禁用的样式
|
||||
if (this.actions[index].disabled) style.color = '#c0c4cc'
|
||||
return style;
|
||||
}
|
||||
},
|
||||
},
|
||||
emits: ["close", "select", "update:show"],
|
||||
methods: {
|
||||
// 关闭操作菜单事件处理
|
||||
closeHandler() {
|
||||
// 允许点击遮罩关闭时,才发出close事件
|
||||
if(this.closeOnClickOverlay) {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
// 点击取消按钮
|
||||
cancel() {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
},
|
||||
// 选择操作项处理
|
||||
selectHandler(index) {
|
||||
const item = this.actions[index]
|
||||
if (item && !item.disabled && !item.loading) {
|
||||
this.$emit('select', item)
|
||||
if (this.closeOnClickAction) {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
},
|
||||
// 动态处理Hover时候第一个item的圆角
|
||||
getItemHoverStyle(index) {
|
||||
if (index === 0 && this.round && !this.title && !this.description) {
|
||||
return {
|
||||
borderTopLeftRadius: `${this.round}px`,
|
||||
borderTopRightRadius: `${this.round}px`,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$u-action-sheet-reset-button-width:100% !default;
|
||||
$u-action-sheet-title-font-size: 16px !default;
|
||||
$u-action-sheet-title-padding: 12px 30px !default;
|
||||
$u-action-sheet-title-color: $u-main-color !default;
|
||||
$u-action-sheet-header-icon-wrap-right:15px !default;
|
||||
$u-action-sheet-header-icon-wrap-top:15px !default;
|
||||
$u-action-sheet-description-font-size:13px !default;
|
||||
$u-action-sheet-description-color:14px !default;
|
||||
$u-action-sheet-description-margin: 18px 15px !default;
|
||||
$u-action-sheet-item-wrap-item-padding:17px !default;
|
||||
$u-action-sheet-item-wrap-name-font-size:16px !default;
|
||||
$u-action-sheet-item-wrap-subname-font-size:13px !default;
|
||||
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
|
||||
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
|
||||
$u-action-sheet-cancel-text-font-size:16px !default;
|
||||
$u-action-sheet-cancel-text-color:$u-content-color !default;
|
||||
$u-action-sheet-cancel-text-font-size:15px !default;
|
||||
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
|
||||
|
||||
.u-reset-button {
|
||||
width: $u-action-sheet-reset-button-width;
|
||||
}
|
||||
|
||||
.u-action-sheet {
|
||||
text-align: center;
|
||||
&__header {
|
||||
position: relative;
|
||||
padding: $u-action-sheet-title-padding;
|
||||
&__title {
|
||||
font-size: $u-action-sheet-title-font-size;
|
||||
color: $u-action-sheet-title-color;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
position: absolute;
|
||||
right: $u-action-sheet-header-icon-wrap-right;
|
||||
top: $u-action-sheet-header-icon-wrap-top;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $u-action-sheet-description-font-size;
|
||||
color: $u-tips-color;
|
||||
margin: $u-action-sheet-description-margin;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__item-wrap {
|
||||
|
||||
&__item {
|
||||
padding: $u-action-sheet-item-wrap-item-padding;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__name {
|
||||
font-size: $u-action-sheet-item-wrap-name-font-size;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__subname {
|
||||
font-size: $u-action-sheet-item-wrap-subname-font-size;
|
||||
color: $u-action-sheet-item-wrap-subname-color;
|
||||
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel-text {
|
||||
font-size: $u-action-sheet-cancel-text-font-size;
|
||||
color: $u-action-sheet-cancel-text-color;
|
||||
text-align: center;
|
||||
// padding: $u-action-sheet-cancel-text-font-size;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: $u-action-sheet-cancel-text-hover-background-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<style scoped lang="scss">
|
||||
.agreement-content {
|
||||
width: 100%;;
|
||||
display: inline-block;
|
||||
flex-direction: column;
|
||||
.agreement-url {
|
||||
display: inline-block;
|
||||
color: blue;
|
||||
// #ifdef H5
|
||||
cursor: pointer;
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<view class="up-agreement">
|
||||
<up-modal v-model:show="show" showCancelButton @confirm="confirm" @cancel="close" confirmText="阅读并同意">
|
||||
<view class="agreement-content">
|
||||
<slot>
|
||||
我们非常重视您的个人信息和隐私保护。为了更好地保障您的个人权益,在您使用我们的产品前,
|
||||
请务必审慎阅读《<text class="agreement-url" @click="urlClick('urlProtocol')">用户协议</text>》
|
||||
和《<text class="agreement-url" @click="urlClick('urlPrivacy')">隐私政策</text>》内的所有条款,
|
||||
尤其是:1.我们对您的个人信息的收集/保存/使用/对外提供/保护等规则条款,以及您的用户权利等条款;2. 约定我们的限制责任、免责
|
||||
条款;3.其他以颜色或加粗进行标识的重要条款。如您对以上协议有任何疑问,请先不要同意,您点击“同意并继续”的行为即表示您已阅读
|
||||
完毕并同意以上协议的全部内容。
|
||||
</slot>
|
||||
</view>
|
||||
</up-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'up-agreement',
|
||||
props: {
|
||||
urlProtocol: {
|
||||
type: String,
|
||||
default: '/pages/user_agreement/agreement/info?title=用户协议'
|
||||
},
|
||||
urlPrivacy: {
|
||||
type: String,
|
||||
default: '/pages/user_agreement/agreement/info?title=隐私政策'
|
||||
},
|
||||
},
|
||||
emits: ['confirm'],
|
||||
data() {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
// #ifdef H5
|
||||
window.opener = null;
|
||||
window.close();
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.quit();
|
||||
// #endif
|
||||
},
|
||||
confirm() {
|
||||
this.show = false;
|
||||
this.$emit('confirm', 1);
|
||||
},
|
||||
showModal() {
|
||||
this.show = true;
|
||||
},
|
||||
urlClick(type) {
|
||||
uni.navigateTo({
|
||||
url: this[type]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
28
uni_modules/uview-plus/components/u-album/album.js
Normal file
28
uni_modules/uview-plus/components/u-album/album.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 16:32:24
|
||||
* @FilePath : /uview-plus/libs/config/props/album.js
|
||||
*/
|
||||
export default {
|
||||
// album 组件
|
||||
album: {
|
||||
urls: [],
|
||||
keyName: '',
|
||||
singleSize: 180,
|
||||
multipleSize: 70,
|
||||
space: 6,
|
||||
singleMode: 'scaleToFill',
|
||||
multipleMode: 'aspectFill',
|
||||
maxCount: 9,
|
||||
previewFullImage: true,
|
||||
rowCount: 3,
|
||||
showMore: true,
|
||||
autoWrap: false,
|
||||
unit: 'px',
|
||||
stop: true,
|
||||
}
|
||||
}
|
||||
95
uni_modules/uview-plus/components/u-album/props.js
Normal file
95
uni_modules/uview-plus/components/u-album/props.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* @Author : jry
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-16 16:35:24
|
||||
* @FilePath : /uview-plus/components/u-album/props.js
|
||||
*/
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 图片地址,Array<String>|Array<Object>形式
|
||||
urls: {
|
||||
type: Array,
|
||||
default: () => defProps.album.urls
|
||||
},
|
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: {
|
||||
type: String,
|
||||
default: () => defProps.album.keyName
|
||||
},
|
||||
// 单图时,图片长边的长度
|
||||
singleSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.singleSize
|
||||
},
|
||||
// 多图时,图片边长
|
||||
multipleSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.multipleSize
|
||||
},
|
||||
// 多图时,图片水平和垂直之间的间隔
|
||||
space: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.space
|
||||
},
|
||||
// 单图时,图片缩放裁剪的模式
|
||||
singleMode: {
|
||||
type: String,
|
||||
default: () => defProps.album.singleMode
|
||||
},
|
||||
// 多图时,图片缩放裁剪的模式
|
||||
multipleMode: {
|
||||
type: String,
|
||||
default: () => defProps.album.multipleMode
|
||||
},
|
||||
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.maxCount
|
||||
},
|
||||
// 是否可以预览图片
|
||||
previewFullImage: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.previewFullImage
|
||||
},
|
||||
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
|
||||
rowCount: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.rowCount
|
||||
},
|
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.showMore
|
||||
},
|
||||
// 图片形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.image.shape
|
||||
},
|
||||
// 圆角,单位任意
|
||||
radius: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.image.radius
|
||||
},
|
||||
// 自适应换行
|
||||
autoWrap: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.autoWrap
|
||||
},
|
||||
// 单位
|
||||
unit: {
|
||||
type: [String],
|
||||
default: () => defProps.album.unit
|
||||
},
|
||||
// 阻止点击冒泡
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.stop
|
||||
}
|
||||
}
|
||||
})
|
||||
344
uni_modules/uview-plus/components/u-album/u-album.vue
Normal file
344
uni_modules/uview-plus/components/u-album/u-album.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<view class="u-album">
|
||||
<!-- 相册行容器,每行显示 rowCount 个图片 -->
|
||||
<view
|
||||
class="u-album__row"
|
||||
ref="u-album__row"
|
||||
v-for="(arr, index) in showUrls"
|
||||
:forComputedUse="albumWidth"
|
||||
:key="index"
|
||||
:style="{flexWrap: autoWrap ? 'wrap' : 'nowrap'}"
|
||||
>
|
||||
<!-- 图片包装容器 -->
|
||||
<view
|
||||
class="u-album__row__wrapper"
|
||||
v-for="(item, index1) in arr"
|
||||
:key="index1"
|
||||
:style="[imageStyle(index + 1, index1 + 1)]"
|
||||
@tap="onPreviewTap($event, getSrc(item))"
|
||||
>
|
||||
<!-- 图片显示 -->
|
||||
<image
|
||||
:src="getSrc(item)"
|
||||
:mode="
|
||||
urls.length === 1
|
||||
? imageHeight > 0
|
||||
? singleMode
|
||||
: 'widthFix'
|
||||
: multipleMode
|
||||
"
|
||||
:style="[
|
||||
{
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
borderRadius: shape == 'circle' ? '10000px' : addUnit(radius)
|
||||
}
|
||||
]"
|
||||
></image>
|
||||
<!-- 超出最大显示数量时的更多提示 -->
|
||||
<view
|
||||
v-if="
|
||||
showMore &&
|
||||
urls.length > rowCount * showUrls.length &&
|
||||
index === showUrls.length - 1 &&
|
||||
index1 === showUrls[showUrls.length - 1].length - 1
|
||||
"
|
||||
class="u-album__row__wrapper__text"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '50%' : addUnit(radius),
|
||||
}"
|
||||
>
|
||||
<up-text
|
||||
:text="`+${urls.length - maxCount}`"
|
||||
color="#fff"
|
||||
:size="multipleSize * 0.3"
|
||||
align="center"
|
||||
customStyle="justify-content: center"
|
||||
></up-text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, sleep } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
// #ifdef APP-NVUE
|
||||
// 不支持百分比单位,这里需要通过dom查询组件的宽度
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Album 相册
|
||||
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/album.html
|
||||
*
|
||||
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
|
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
|
||||
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
|
||||
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
|
||||
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
|
||||
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
|
||||
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
|
||||
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
|
||||
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
|
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||
* @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
|
||||
* @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
|
||||
* @property {Boolean} autoWrap 自适应换行模式,不受rowCount限制,图片会自动换行 (默认 false )
|
||||
* @property {String} unit 图片单位 (默认 px )
|
||||
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
|
||||
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-album',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 单图的宽度
|
||||
singleWidth: 0,
|
||||
// 单图的高度
|
||||
singleHeight: 0,
|
||||
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
|
||||
singlePercent: 0.6
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urls: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
// 当只有一张图片时,获取图片尺寸信息
|
||||
if (newVal.length === 1) {
|
||||
this.getImageRect()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 计算图片样式
|
||||
* @param {Number} index1 - 行索引
|
||||
* @param {Number} index2 - 列索引
|
||||
* @returns {Object} 图片样式对象
|
||||
*/
|
||||
imageStyle() {
|
||||
return (index1, index2) => {
|
||||
const { space, rowCount, multipleSize, urls } = this,
|
||||
rowLen = this.showUrls.length,
|
||||
allLen = this.urls.length
|
||||
const style = {
|
||||
marginRight: addUnit(space),
|
||||
marginBottom: addUnit(space)
|
||||
}
|
||||
// 如果为最后一行,则每个图片都无需下边框
|
||||
if (index1 === rowLen && !this.autoWrap) style.marginBottom = 0
|
||||
// 每行的最右边一张和总长度的最后一张无需右边框
|
||||
if (!this.autoWrap) {
|
||||
if (
|
||||
index2 === rowCount ||
|
||||
(index1 === rowLen &&
|
||||
index2 === this.showUrls[index1 - 1].length)
|
||||
)
|
||||
style.marginRight = 0
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 将图片地址数组划分为二维数组,用于按行显示
|
||||
* @returns {Array} 二维数组,每个子数组代表一行图片
|
||||
*/
|
||||
showUrls() {
|
||||
if (this.autoWrap) {
|
||||
// 自动换行模式下,所有图片放在一行中显示
|
||||
return [ this.urls.slice(0, this.maxCount) ];
|
||||
} else {
|
||||
// 固定行数模式下,按 rowCount 分割图片
|
||||
const arr = []
|
||||
this.urls.map((item, index) => {
|
||||
// 限制最大展示数量
|
||||
if (index + 1 <= this.maxCount) {
|
||||
// 计算该元素为第几个素组内
|
||||
const itemIndex = Math.floor(index / this.rowCount)
|
||||
// 判断对应的索引是否存在
|
||||
if (!arr[itemIndex]) {
|
||||
arr[itemIndex] = []
|
||||
}
|
||||
arr[itemIndex].push(item)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 计算图片宽度
|
||||
* @returns {String} 图片宽度样式值
|
||||
*/
|
||||
imageWidth() {
|
||||
return addUnit(
|
||||
this.urls.length === 1 ? this.singleWidth : this.multipleSize, this.unit
|
||||
)
|
||||
},
|
||||
/**
|
||||
* 计算图片高度
|
||||
* @returns {String} 图片高度样式值
|
||||
*/
|
||||
imageHeight() {
|
||||
return addUnit(
|
||||
this.urls.length === 1 ? this.singleHeight : this.multipleSize, this.unit
|
||||
)
|
||||
},
|
||||
/**
|
||||
* 计算相册总宽度,用于外部组件对齐
|
||||
* 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
|
||||
* @returns {Number} 相册宽度
|
||||
*/
|
||||
albumWidth() {
|
||||
let width = 0
|
||||
if (this.urls.length === 1) {
|
||||
width = this.singleWidth
|
||||
} else {
|
||||
width =
|
||||
this.showUrls[0].length * this.multipleSize +
|
||||
this.space * (this.showUrls[0].length - 1)
|
||||
}
|
||||
this.$emit('albumWidth', width)
|
||||
return width
|
||||
}
|
||||
},
|
||||
emits: ['preview', 'albumWidth'],
|
||||
methods: {
|
||||
addUnit,
|
||||
/**
|
||||
* 点击图片预览
|
||||
* @param {Event} e - 点击事件对象
|
||||
* @param {String} url - 当前点击图片的地址
|
||||
*/
|
||||
onPreviewTap(e, url) {
|
||||
// 获取所有图片地址
|
||||
const urls = this.urls.map((item) => {
|
||||
return this.getSrc(item)
|
||||
})
|
||||
if (this.previewFullImage) {
|
||||
// 使用系统默认预览图片功能
|
||||
uni.previewImage({
|
||||
current: url,
|
||||
urls
|
||||
})
|
||||
// 是否阻止事件传播
|
||||
this.stop && this.preventEvent(e)
|
||||
} else {
|
||||
// 发送自定义预览事件
|
||||
this.$emit('preview', {
|
||||
urls,
|
||||
currentIndex: urls.indexOf(url)
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取图片地址
|
||||
* @param {String|Object} item - 图片项,可以是字符串或对象
|
||||
* @returns {String} 图片地址
|
||||
*/
|
||||
getSrc(item) {
|
||||
return test.object(item)
|
||||
? (this.keyName && item[this.keyName]) || item.src
|
||||
: item
|
||||
},
|
||||
/**
|
||||
* 单图时,获取图片的尺寸
|
||||
* 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
|
||||
* 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
|
||||
*/
|
||||
getImageRect() {
|
||||
const src = this.getSrc(this.urls[0])
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
let singleSize = this.singleSize;
|
||||
// 单位
|
||||
let unit = '';
|
||||
if (Number.isNaN(Number(this.singleSize))) {
|
||||
// 大小中有字符 则记录字符
|
||||
unit = this.singleSize.replace(/\d+/g, ''); // 单位
|
||||
singleSize = Number(this.singleSize.replace(/\D+/g, ''), 10); // 具体值
|
||||
}
|
||||
|
||||
// 判断图片横向还是竖向展示方式
|
||||
const isHorizotal = res.width >= res.height
|
||||
this.singleWidth = isHorizotal
|
||||
? singleSize
|
||||
: (res.width / res.height) * singleSize
|
||||
this.singleHeight = !isHorizotal
|
||||
? singleSize
|
||||
: (res.height / res.width) * this.singleWidth
|
||||
|
||||
// 如果有单位统一设置单位
|
||||
if(unit != null && unit !== ''){
|
||||
this.singleWidth = this.singleWidth + unit
|
||||
this.singleHeight = this.singleHeight + unit
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 获取图片信息失败时,通过组件宽度计算
|
||||
this.getComponentWidth()
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取组件的宽度,用于计算单图显示尺寸
|
||||
*/
|
||||
async getComponentWidth() {
|
||||
// 延时一定时间,以获取dom尺寸
|
||||
await sleep(30)
|
||||
// #ifndef APP-NVUE
|
||||
// H5、小程序等平台通过 $uGetRect 获取组件宽度
|
||||
this.$uGetRect('.u-album__row').then((size) => {
|
||||
this.singleWidth = size.width * this.singlePercent
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// NVUE 平台通过 dom 插件获取组件宽度
|
||||
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
|
||||
const ref = this.$refs['u-album__row'][0]
|
||||
ref &&
|
||||
dom.getComponentRect(ref, (res) => {
|
||||
this.singleWidth = res.size.width * this.singlePercent
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-album {
|
||||
@include flex(column);
|
||||
|
||||
&__row {
|
||||
@include flex(row);
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
@include flex(row);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
26
uni_modules/uview-plus/components/u-alert/alert.js
Normal file
26
uni_modules/uview-plus/components/u-alert/alert.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-17 17:23:53
|
||||
* @FilePath : /uview-plus/libs/config/props/alert.js
|
||||
*/
|
||||
export default {
|
||||
// alert警告组件
|
||||
alert: {
|
||||
title: '',
|
||||
type: 'warning',
|
||||
description: '',
|
||||
closable: false,
|
||||
showIcon: false,
|
||||
effect: 'light',
|
||||
center: false,
|
||||
fontSize: 14,
|
||||
transitionMode: 'fade',
|
||||
duration: 0,
|
||||
icon: '',
|
||||
value: true
|
||||
}
|
||||
}
|
||||
75
uni_modules/uview-plus/components/u-alert/props.js
Normal file
75
uni_modules/uview-plus/components/u-alert/props.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* @Author : jry
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-08-17 17:23:53
|
||||
* @FilePath : /uview-plus/libs/config/props/props.js
|
||||
*/
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 显示文字
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.alert.title
|
||||
},
|
||||
// 主题,success/warning/info/error
|
||||
type: {
|
||||
type: String,
|
||||
default: () => defProps.alert.type
|
||||
},
|
||||
// 辅助性文字
|
||||
description: {
|
||||
type: String,
|
||||
default: () => defProps.alert.description
|
||||
},
|
||||
// 是否可关闭
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: () => defProps.alert.closable
|
||||
},
|
||||
// 是否显示图标
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: () => defProps.alert.showIcon
|
||||
},
|
||||
// 浅或深色调,light-浅色,dark-深色
|
||||
effect: {
|
||||
type: String,
|
||||
default: () => defProps.alert.effect
|
||||
},
|
||||
// 文字是否居中
|
||||
center: {
|
||||
type: Boolean,
|
||||
default: () => defProps.alert.center
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.alert.fontSize
|
||||
},
|
||||
// 动画类型
|
||||
transitionMode: {
|
||||
type: [String],
|
||||
default: () => defProps.alert.transitionMode
|
||||
},
|
||||
// 自动定时关闭毫秒
|
||||
duration: {
|
||||
type: [Number],
|
||||
default: () => defProps.alert.duration
|
||||
},
|
||||
// 自定义图标
|
||||
icon: {
|
||||
type: [String],
|
||||
default: () => defProps.alert.icon
|
||||
},
|
||||
// 是否显示
|
||||
modelValue: {
|
||||
type: [Boolean],
|
||||
default: () => defProps.alert.value
|
||||
}
|
||||
}
|
||||
})
|
||||
293
uni_modules/uview-plus/components/u-alert/u-alert.vue
Normal file
293
uni_modules/uview-plus/components/u-alert/u-alert.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<up-transition
|
||||
:mode="transitionMode"
|
||||
:show="show"
|
||||
>
|
||||
<view
|
||||
class="u-alert"
|
||||
:class="[`u-alert--${type}--${effect}`]"
|
||||
@tap.stop="clickHandler"
|
||||
:style="[addStyle(customStyle)]"
|
||||
>
|
||||
<!-- 左侧图标 -->
|
||||
<view
|
||||
class="u-alert__icon"
|
||||
v-if="showIcon"
|
||||
>
|
||||
<up-icon
|
||||
:name="iconName"
|
||||
size="18"
|
||||
:color="iconColor"
|
||||
></up-icon>
|
||||
</view>
|
||||
<!-- 内容区域 -->
|
||||
<view
|
||||
class="u-alert__content"
|
||||
:style="[{
|
||||
paddingRight: closable ? '20px' : 0
|
||||
}]"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<text
|
||||
class="u-alert__content__title"
|
||||
v-if="title"
|
||||
:style="[{
|
||||
fontSize: addUnit(fontSize),
|
||||
textAlign: center ? 'center' : 'left'
|
||||
}]"
|
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||
>{{ title }}</text>
|
||||
<!-- 描述信息 -->
|
||||
<text
|
||||
class="u-alert__content__desc"
|
||||
v-if="description"
|
||||
:style="[{
|
||||
fontSize: addUnit(fontSize),
|
||||
textAlign: center ? 'center' : 'left'
|
||||
}]"
|
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||
>{{ description }}</text>
|
||||
</view>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
class="u-alert__close"
|
||||
v-if="closable"
|
||||
@tap.stop="closeHandler"
|
||||
>
|
||||
<slot name="close">
|
||||
<up-icon
|
||||
name="close"
|
||||
:color="iconColor"
|
||||
size="15"
|
||||
></up-icon>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</up-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, addStyle } from '../../libs/function/index';
|
||||
/**
|
||||
* Alert 警告提示
|
||||
* @description 警告提示,展现需要关注的信息。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/alertTips.html
|
||||
*
|
||||
* @property {String} title 显示的文字
|
||||
* @property {String} type 使用预设的颜色 (默认 'warning' )
|
||||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
|
||||
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
|
||||
* @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
|
||||
* @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
|
||||
* @property {Boolean} center 文字是否居中 (默认 false )
|
||||
* @property {String | Number} fontSize 字体大小 (默认 14 )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @property {String} transitionMode 过渡动画模式 (默认 'fade' )
|
||||
* @property {String | Number} duration 自动关闭延时(毫秒),设置为0或负数则不自动关闭 (默认 0 )
|
||||
* @property {String} icon 自定义图标名称,优先级高于type默认图标
|
||||
* @property {Boolean} modelValue/v-model 绑定值,控制是否显示 (默认 true )
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @event {Function} close 点击关闭按钮时触发
|
||||
* @event {Function} closed 关闭动画结束时触发
|
||||
* @example <up-alert :title="title" type = "warning" :closable="closable" :description = "description"></up-alert>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-alert',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 控制组件显示隐藏
|
||||
show: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 根据不同的主题类型返回对应的图标颜色
|
||||
iconColor() {
|
||||
return this.effect === 'light' ? this.type : '#fff'
|
||||
},
|
||||
// 不同主题对应不同的图标
|
||||
iconName() {
|
||||
// 如果用户自定义了图标,则优先使用自定义图标
|
||||
if (this.icon) return this.icon;
|
||||
|
||||
switch (this.type) {
|
||||
case 'success':
|
||||
return 'checkmark-circle-fill';
|
||||
break;
|
||||
case 'error':
|
||||
return 'close-circle-fill';
|
||||
break;
|
||||
case 'warning':
|
||||
return 'error-circle-fill';
|
||||
break;
|
||||
case 'info':
|
||||
return 'info-circle-fill';
|
||||
break;
|
||||
case 'primary':
|
||||
return 'more-circle-fill';
|
||||
break;
|
||||
default:
|
||||
return 'error-circle-fill';
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ["click","close", "closed", "update:modelValue"],
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
this.show = newVal;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
show: {
|
||||
handler(newVal) {
|
||||
this.$emit('update:modelValue', newVal);
|
||||
|
||||
// 如果是从显示到隐藏,且启用了自动关闭功能
|
||||
if (!newVal && this.duration > 0) {
|
||||
this.$emit('closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果设置了自动关闭时间,则在指定时间后自动关闭
|
||||
if (this.duration > 0) {
|
||||
setTimeout(() => {
|
||||
this.closeHandler();
|
||||
}, this.duration);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addUnit,
|
||||
addStyle,
|
||||
// 点击内容区域触发click事件
|
||||
clickHandler() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// 点击关闭按钮触发close事件并隐藏组件
|
||||
closeHandler() {
|
||||
this.show = false
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-alert {
|
||||
position: relative;
|
||||
background-color: $u-primary;
|
||||
padding: 8px 10px;
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
|
||||
&--primary--dark {
|
||||
background-color: $u-primary;
|
||||
}
|
||||
|
||||
&--primary--light {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
&--error--dark {
|
||||
background-color: $u-error;
|
||||
}
|
||||
|
||||
&--error--light {
|
||||
background-color: #FEF0F0;
|
||||
}
|
||||
|
||||
&--success--dark {
|
||||
background-color: $u-success;
|
||||
}
|
||||
|
||||
&--success--light {
|
||||
background-color: #f5fff0;
|
||||
}
|
||||
|
||||
&--warning--dark {
|
||||
background-color: $u-warning;
|
||||
}
|
||||
|
||||
&--warning--light {
|
||||
background-color: #FDF6EC;
|
||||
}
|
||||
|
||||
&--info--dark {
|
||||
background-color: $u-info;
|
||||
}
|
||||
|
||||
&--info--light {
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
@include flex(column);
|
||||
flex: 1;
|
||||
|
||||
&__title {
|
||||
color: $u-main-color;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
color: $u-main-color;
|
||||
font-size: 14px;
|
||||
flex-wrap: wrap;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&__title--dark,
|
||||
&__desc--dark {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--primary--light,
|
||||
&__text--primary--light {
|
||||
color: $u-primary;
|
||||
}
|
||||
|
||||
&__text--success--light,
|
||||
&__text--success--light {
|
||||
color: $u-success;
|
||||
}
|
||||
|
||||
&__text--warning--light,
|
||||
&__text--warning--light {
|
||||
color: $u-warning;
|
||||
}
|
||||
|
||||
&__text--error--light,
|
||||
&__text--error--light {
|
||||
color: $u-error;
|
||||
}
|
||||
|
||||
&__text--info--light,
|
||||
&__text--info--light {
|
||||
color: $u-info;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/avatarGroup.js
|
||||
*/
|
||||
export default {
|
||||
// avatarGroup 组件
|
||||
avatarGroup: {
|
||||
urls: [],
|
||||
maxCount: 5,
|
||||
shape: 'circle',
|
||||
mode: 'scaleToFill',
|
||||
showMore: true,
|
||||
size: 40,
|
||||
keyName: '',
|
||||
gap: 0.5,
|
||||
extraValue: 0
|
||||
}
|
||||
}
|
||||
54
uni_modules/uview-plus/components/u-avatar-group/props.js
Normal file
54
uni_modules/uview-plus/components/u-avatar-group/props.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 头像图片组
|
||||
urls: {
|
||||
type: Array,
|
||||
default: () => defProps.avatarGroup.urls
|
||||
},
|
||||
// 最多展示的头像数量
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.avatarGroup.maxCount
|
||||
},
|
||||
// 头像形状
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.avatarGroup.shape
|
||||
},
|
||||
// 图片裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => defProps.avatarGroup.mode
|
||||
},
|
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: () => defProps.avatarGroup.showMore
|
||||
},
|
||||
// 头像大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.avatarGroup.size
|
||||
},
|
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: {
|
||||
type: String,
|
||||
default: () => defProps.avatarGroup.keyName
|
||||
},
|
||||
// 头像之间的遮挡比例
|
||||
gap: {
|
||||
type: [String, Number],
|
||||
validator(value) {
|
||||
return value >= 0 && value <= 1
|
||||
},
|
||||
default: () => defProps.avatarGroup.gap
|
||||
},
|
||||
// 需额外显示的值
|
||||
extraValue: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.avatarGroup.extraValue
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<view class="u-avatar-group">
|
||||
<view
|
||||
class="u-avatar-group__item"
|
||||
v-for="(item, index) in showUrl"
|
||||
:key="index"
|
||||
:style="{
|
||||
marginLeft: index === 0 ? 0 : addUnit(-size * gap)
|
||||
}"
|
||||
>
|
||||
<u-avatar
|
||||
:size="size"
|
||||
:shape="shape"
|
||||
:mode="mode"
|
||||
:src="testObject(item) ? keyName && item[keyName] || item.url : item"
|
||||
></u-avatar>
|
||||
<view
|
||||
class="u-avatar-group__item__show-more"
|
||||
v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
|
||||
@tap="clickHandler"
|
||||
>
|
||||
<up-text
|
||||
color="#ffffff"
|
||||
:size="size * 0.4"
|
||||
:text="`+${extraValue || urls.length - showUrl.length}`"
|
||||
align="center"
|
||||
customStyle="justify-content: center"
|
||||
></up-text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
/**
|
||||
* AvatarGroup 头像组
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/avatar.html
|
||||
*
|
||||
* @property {Array} urls 头像图片组 (默认 [] )
|
||||
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
|
||||
* @property {String} shape 头像形状( 'circle' (默认) | 'square' )
|
||||
* @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
|
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||
* @property {String | Number} size 头像大小 (默认 40 )
|
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
* @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
|
||||
* @property {String | Number} extraValue 需额外显示的值
|
||||
* @event {Function} showMore 头像组更多点击
|
||||
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-avatar-group',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showUrl() {
|
||||
return this.urls.slice(0, this.maxCount)
|
||||
}
|
||||
},
|
||||
emits: ["showMore"],
|
||||
methods: {
|
||||
addUnit,
|
||||
testObject: test.object,
|
||||
clickHandler() {
|
||||
this.$emit('showMore')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-avatar-group {
|
||||
@include flex;
|
||||
|
||||
&__item {
|
||||
margin-left: -10px;
|
||||
position: relative;
|
||||
|
||||
&--no-indent {
|
||||
// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&__show-more {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
28
uni_modules/uview-plus/components/u-avatar/avatar.js
Normal file
28
uni_modules/uview-plus/components/u-avatar/avatar.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/avatar.js
|
||||
*/
|
||||
export default {
|
||||
// avatar 组件
|
||||
avatar: {
|
||||
src: '',
|
||||
shape: 'circle',
|
||||
size: 40,
|
||||
mode: 'scaleToFill',
|
||||
text: '',
|
||||
bgColor: '#c0c4cc',
|
||||
color: '#ffffff',
|
||||
fontSize: 18,
|
||||
icon: '',
|
||||
mpAvatar: false,
|
||||
randomBgColor: false,
|
||||
defaultUrl: '',
|
||||
colorIndex: '',
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
81
uni_modules/uview-plus/components/u-avatar/props.js
Normal file
81
uni_modules/uview-plus/components/u-avatar/props.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
import test from '../../libs/function/test';
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 头像图片路径(不能为相对路径)
|
||||
src: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.src
|
||||
},
|
||||
// 头像形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.shape
|
||||
},
|
||||
// 头像尺寸
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.avatar.size
|
||||
},
|
||||
// 裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.mode
|
||||
},
|
||||
// 显示的文字
|
||||
text: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.text
|
||||
},
|
||||
// 背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.bgColor
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.color
|
||||
},
|
||||
// 文字大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.avatar.fontSize
|
||||
},
|
||||
// 显示的图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.icon
|
||||
},
|
||||
// 显示小程序头像,只对百度,微信,QQ小程序有效
|
||||
mpAvatar: {
|
||||
type: Boolean,
|
||||
default: () => defProps.avatar.mpAvatar
|
||||
},
|
||||
// 是否使用随机背景色
|
||||
randomBgColor: {
|
||||
type: Boolean,
|
||||
default: () => defProps.avatar.randomBgColor
|
||||
},
|
||||
// 加载失败的默认头像(组件有内置默认图片)
|
||||
defaultUrl: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.defaultUrl
|
||||
},
|
||||
// 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||
colorIndex: {
|
||||
type: [String, Number],
|
||||
// 校验参数规则,索引在0-19之间
|
||||
validator(n) {
|
||||
return test.range(n, [0, 19]) || n === ''
|
||||
},
|
||||
default: () => defProps.avatar.colorIndex
|
||||
},
|
||||
// 组件标识符
|
||||
name: {
|
||||
type: String,
|
||||
default: () => defProps.avatar.name
|
||||
}
|
||||
}
|
||||
})
|
||||
179
uni_modules/uview-plus/components/u-avatar/u-avatar.vue
Normal file
179
uni_modules/uview-plus/components/u-avatar/u-avatar.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-avatar"
|
||||
:class="[`u-avatar--${shape}`]"
|
||||
:style="[{
|
||||
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : random(0, 19)] : bgColor) : 'transparent',
|
||||
width: addUnit(size),
|
||||
height: addUnit(size),
|
||||
}, addStyle(customStyle)]"
|
||||
@tap="clickHandler"
|
||||
>
|
||||
<slot>
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
|
||||
<open-data
|
||||
v-if="mpAvatar && allowMp"
|
||||
type="userAvatarUrl"
|
||||
:style="[{
|
||||
width: addUnit(size),
|
||||
height: addUnit(size)
|
||||
}]"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
|
||||
<template v-if="mpAvatar && allowMp"></template>
|
||||
<!-- #endif -->
|
||||
<up-icon
|
||||
v-else-if="icon"
|
||||
:name="icon"
|
||||
:size="fontSize"
|
||||
:color="color"
|
||||
></up-icon>
|
||||
<up-text
|
||||
v-else-if="text"
|
||||
:text="text"
|
||||
:size="fontSize"
|
||||
:color="color"
|
||||
align="center"
|
||||
customStyle="justify-content: center"
|
||||
></up-text>
|
||||
<image
|
||||
class="u-avatar__image"
|
||||
v-else
|
||||
:class="[`u-avatar__image--${shape}`]"
|
||||
:src="avatarUrl || defaultUrl"
|
||||
:mode="mode"
|
||||
@error="errorHandler"
|
||||
:style="[{
|
||||
width: addUnit(size),
|
||||
height: addUnit(size)
|
||||
}]"
|
||||
></image>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle, addUnit, random } from '../../libs/function/index';
|
||||
const base64Avatar =
|
||||
"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";
|
||||
/**
|
||||
* Avatar 头像
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/avatar.html
|
||||
*
|
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
|
||||
* @property {String} shape 头像形状 ( circle (默认) | square)
|
||||
* @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 )
|
||||
* @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' )
|
||||
* @property {String} text 用文字替代图片,级别优先于src
|
||||
* @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' )
|
||||
* @property {String} color 文字颜色 (默认 '#ffffff' )
|
||||
* @property {String | Number} fontSize 文字大小 (默认 18 )
|
||||
* @property {String} icon 显示的图标
|
||||
* @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false )
|
||||
* @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false )
|
||||
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
|
||||
* @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||
* @property {String} name 组件标识符 (默认 'level' )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 点击组件时触发 index: 用户传递的标识符
|
||||
* @example <u-avatar :src="src" mode="square"></u-avatar>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-avatar',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色
|
||||
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
|
||||
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
|
||||
'#73d1f1',
|
||||
'#80a7dc'
|
||||
],
|
||||
avatarUrl: this.src,
|
||||
allowMp: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值
|
||||
// 而组件内部不能直接修改props的值,所以需要一个中间变量
|
||||
src: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.avatarUrl = newVal
|
||||
// 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示
|
||||
if(!newVal) {
|
||||
this.errorHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageStyle() {
|
||||
const style = {}
|
||||
return style
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
emits: ["click"],
|
||||
methods: {
|
||||
addStyle,
|
||||
addUnit,
|
||||
random,
|
||||
init() {
|
||||
// 目前只有这几个小程序平台具有open-data标签
|
||||
// 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑
|
||||
// 故目前自动获取小程序头像只支持这几个平台
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
|
||||
this.allowMp = true
|
||||
// #endif
|
||||
},
|
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||
isImg() {
|
||||
return this.src.indexOf('/') !== -1
|
||||
},
|
||||
// 图片加载时失败时触发
|
||||
errorHandler() {
|
||||
this.avatarUrl = this.defaultUrl || base64Avatar
|
||||
},
|
||||
clickHandler(e) {
|
||||
this.$emit('click', this.name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-avatar {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--circle {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__image {
|
||||
&--circle {
|
||||
border-radius: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
uni_modules/uview-plus/components/u-back-top/backtop.js
Normal file
27
uni_modules/uview-plus/components/u-back-top/backtop.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/backtop.js
|
||||
*/
|
||||
export default {
|
||||
// backtop组件
|
||||
backtop: {
|
||||
mode: 'circle',
|
||||
icon: 'arrow-upward',
|
||||
text: '',
|
||||
duration: 100,
|
||||
scrollTop: 0,
|
||||
top: 400,
|
||||
bottom: 100,
|
||||
right: 20,
|
||||
zIndex: 9,
|
||||
iconStyle: {
|
||||
color: '#909399',
|
||||
fontSize: '19px'
|
||||
}
|
||||
}
|
||||
}
|
||||
56
uni_modules/uview-plus/components/u-back-top/props.js
Normal file
56
uni_modules/uview-plus/components/u-back-top/props.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 返回顶部的形状,circle-圆形,square-方形
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => defProps.backtop.mode
|
||||
},
|
||||
// 自定义图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => defProps.backtop.icon
|
||||
},
|
||||
// 提示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: () => defProps.backtop.text
|
||||
},
|
||||
// 返回顶部滚动时间
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.backtop.duration
|
||||
},
|
||||
// 滚动距离
|
||||
scrollTop: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.backtop.scrollTop
|
||||
},
|
||||
// 距离顶部多少距离显示,单位px
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.backtop.top
|
||||
},
|
||||
// 返回顶部按钮到底部的距离,单位px
|
||||
bottom: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.backtop.bottom
|
||||
},
|
||||
// 返回顶部按钮到右边的距离,单位px
|
||||
right: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.backtop.right
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.backtop.zIndex
|
||||
},
|
||||
// 图标的样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default: () => defProps.backtop.iconStyle
|
||||
}
|
||||
}
|
||||
})
|
||||
132
uni_modules/uview-plus/components/u-back-top/u-back-top.vue
Normal file
132
uni_modules/uview-plus/components/u-back-top/u-back-top.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
:customStyle="backTopStyle"
|
||||
:show="show"
|
||||
>
|
||||
<view
|
||||
class="u-back-top"
|
||||
:style="[contentStyle]"
|
||||
v-if="!$slots.default && !$slots.$default"
|
||||
@click="backToTop"
|
||||
>
|
||||
<up-icon
|
||||
:name="icon"
|
||||
:custom-style="iconStyle"
|
||||
></up-icon>
|
||||
<text
|
||||
v-if="text"
|
||||
class="u-back-top__text"
|
||||
>{{text}}</text>
|
||||
</view>
|
||||
<slot v-else />
|
||||
</u-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, addStyle, getPx, deepMerge, error } from '../../libs/function/index';
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
/**
|
||||
* backTop 返回顶部
|
||||
* @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/backTop.html
|
||||
*
|
||||
* @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
|
||||
* @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
|
||||
* @property {String} text 提示文字
|
||||
* @property {String | Number} duration 返回顶部滚动时间 (默认 100)
|
||||
* @property {String | Number} scrollTop 滚动距离 (默认 0 )
|
||||
* @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
|
||||
* @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
|
||||
* @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
|
||||
* @property {String | Number} zIndex 层级 (默认 9 )
|
||||
* @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @example <u-back-top :scrollTop="scrollTop"></u-back-top>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-back-top',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
computed: {
|
||||
backTopStyle() {
|
||||
// 动画组件样式
|
||||
const style = {
|
||||
bottom: addUnit(this.bottom),
|
||||
right: addUnit(this.right),
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
position: 'fixed',
|
||||
zIndex: 10,
|
||||
}
|
||||
return style
|
||||
},
|
||||
show() {
|
||||
return getPx(this.scrollTop) > getPx(this.top)
|
||||
},
|
||||
contentStyle() {
|
||||
const style = {}
|
||||
let radius = 0
|
||||
// 是否圆形
|
||||
if(this.mode === 'circle') {
|
||||
radius = '100px'
|
||||
} else {
|
||||
radius = '4px'
|
||||
}
|
||||
// 为了兼容安卓nvue,只能这么分开写
|
||||
style.borderTopLeftRadius = radius
|
||||
style.borderTopRightRadius = radius
|
||||
style.borderBottomLeftRadius = radius
|
||||
style.borderBottomRightRadius = radius
|
||||
return deepMerge(style, addStyle(this.customStyle))
|
||||
}
|
||||
},
|
||||
emits: ["click"],
|
||||
methods: {
|
||||
backToTop() {
|
||||
// #ifdef APP-NVUE
|
||||
if (!this.$parent.$refs['u-back-top']) {
|
||||
error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
|
||||
}
|
||||
dom.scrollToElement(this.$parent.$refs['u-back-top'], {
|
||||
offset: 0
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: this.duration
|
||||
});
|
||||
// #endif
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$u-back-top-flex:1 !default;
|
||||
$u-back-top-height:100% !default;
|
||||
$u-back-top-background-color:#E1E1E1 !default;
|
||||
$u-back-top-tips-font-size:12px !default;
|
||||
.u-back-top {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex:$u-back-top-flex;
|
||||
height: $u-back-top-height;
|
||||
justify-content: center;
|
||||
background-color: $u-back-top-background-color;
|
||||
|
||||
&__tips {
|
||||
font-size:$u-back-top-tips-font-size;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
uni_modules/uview-plus/components/u-badge/badge.js
Normal file
27
uni_modules/uview-plus/components/u-badge/badge.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/badge.js
|
||||
*/
|
||||
export default {
|
||||
// 徽标数组件
|
||||
badge: {
|
||||
isDot: false,
|
||||
value: '',
|
||||
show: true,
|
||||
max: 999,
|
||||
type: 'error',
|
||||
showZero: false,
|
||||
bgColor: null,
|
||||
color: null,
|
||||
shape: 'circle',
|
||||
numberType: 'overflow',
|
||||
offset: [],
|
||||
inverted: false,
|
||||
absolute: false
|
||||
}
|
||||
}
|
||||
79
uni_modules/uview-plus/components/u-badge/props.js
Normal file
79
uni_modules/uview-plus/components/u-badge/props.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 是否显示圆点
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: () => defProps.badge.isDot
|
||||
},
|
||||
// 显示的内容
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.badge.value
|
||||
},
|
||||
// 显示的内容
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.badge.modelValue
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: () => defProps.badge.show
|
||||
},
|
||||
// 最大值,超过最大值会显示 '{max}+'
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.badge.max
|
||||
},
|
||||
// 主题类型,error|warning|success|primary
|
||||
type: {
|
||||
type: String,
|
||||
default: () => defProps.badge.type
|
||||
},
|
||||
// 当数值为 0 时,是否展示 Badge
|
||||
showZero: {
|
||||
type: Boolean,
|
||||
default: () => defProps.badge.showZero
|
||||
},
|
||||
// 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
bgColor: {
|
||||
type: [String, null],
|
||||
default: () => defProps.badge.bgColor
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: [String, null],
|
||||
default: () => defProps.badge.color
|
||||
},
|
||||
// 徽标形状,circle-四角均为圆角,horn-左下角为直角
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.badge.shape
|
||||
},
|
||||
// 设置数字的显示方式,overflow|ellipsis|limit
|
||||
// overflow会根据max字段判断,超出显示`${max}+`
|
||||
// ellipsis会根据max判断,超出显示`${max}...`
|
||||
// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
|
||||
numberType: {
|
||||
type: String,
|
||||
default: () => defProps.badge.numberType
|
||||
},
|
||||
// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||
offset: {
|
||||
type: Array,
|
||||
default: () => defProps.badge.offset
|
||||
},
|
||||
// 是否反转背景和字体颜色
|
||||
inverted: {
|
||||
type: Boolean,
|
||||
default: () => defProps.badge.inverted
|
||||
},
|
||||
// 是否绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: () => defProps.badge.absolute
|
||||
}
|
||||
}
|
||||
})
|
||||
176
uni_modules/uview-plus/components/u-badge/u-badge.vue
Normal file
176
uni_modules/uview-plus/components/u-badge/u-badge.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<text
|
||||
v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
|
||||
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
|
||||
:style="[addStyle(customStyle), badgeStyle]"
|
||||
class="u-badge"
|
||||
>{{ isDot ? '' :showValue }}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle, addUnit } from '../../libs/function/index';
|
||||
/**
|
||||
* badge 徽标数
|
||||
* @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/badge.html
|
||||
*
|
||||
* @property {Boolean} isDot 是否显示圆点 (默认 false )
|
||||
* @property {String | Number} value 显示的内容
|
||||
* @property {Boolean} show 是否显示 (默认 true )
|
||||
* @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
|
||||
* @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
|
||||
* @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
|
||||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
* @property {String} color 字体颜色 (默认 '#ffffff' )
|
||||
* @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
|
||||
* @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
|
||||
* @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||
* @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
|
||||
* @property {Boolean} absolute 是否绝对定位(默认 false )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @example <u-badge :type="type" :count="count"></u-badge>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-badge',
|
||||
mixins: [mpMixin, props, mixin],
|
||||
computed: {
|
||||
// 是否将badge中心与父组件右上角重合
|
||||
boxStyle() {
|
||||
let style = {};
|
||||
return style;
|
||||
},
|
||||
// 整个组件的样式
|
||||
badgeStyle() {
|
||||
const style = {}
|
||||
if(this.color) {
|
||||
style.color = this.color
|
||||
}
|
||||
if (this.bgColor && !this.inverted) {
|
||||
style.backgroundColor = this.bgColor
|
||||
}
|
||||
if (this.absolute) {
|
||||
style.position = 'absolute'
|
||||
// 如果有设置offset参数
|
||||
if(this.offset.length) {
|
||||
// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
|
||||
const top = this.offset[0]
|
||||
const right = this.offset[1] || top
|
||||
style.top = addUnit(top)
|
||||
style.right = addUnit(right)
|
||||
}
|
||||
}
|
||||
return style
|
||||
},
|
||||
showValue() {
|
||||
switch (this.numberType) {
|
||||
case "overflow":
|
||||
return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
|
||||
break;
|
||||
case "ellipsis":
|
||||
return Number(this.value) > Number(this.max) ? "..." : this.value
|
||||
break;
|
||||
case "limit":
|
||||
return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
|
||||
Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
|
||||
1e3 * 100) / 100 + "k" : this.value
|
||||
break;
|
||||
default:
|
||||
return Number(this.value)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addStyle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$u-badge-primary: $u-primary !default;
|
||||
$u-badge-error: $u-error !default;
|
||||
$u-badge-success: $u-success !default;
|
||||
$u-badge-info: $u-info !default;
|
||||
$u-badge-warning: $u-warning !default;
|
||||
$u-badge-dot-radius: 100px !default;
|
||||
$u-badge-dot-size: 8px !default;
|
||||
$u-badge-dot-right: 4px !default;
|
||||
$u-badge-dot-top: 0 !default;
|
||||
$u-badge-text-font-size: 11px !default;
|
||||
$u-badge-text-right: 10px !default;
|
||||
$u-badge-text-padding: 2px 5px !default;
|
||||
$u-badge-text-align: center !default;
|
||||
$u-badge-text-color: #FFFFFF !default;
|
||||
|
||||
.u-badge {
|
||||
border-top-right-radius: $u-badge-dot-radius;
|
||||
border-top-left-radius: $u-badge-dot-radius;
|
||||
border-bottom-left-radius: $u-badge-dot-radius;
|
||||
border-bottom-right-radius: $u-badge-dot-radius;
|
||||
@include flex;
|
||||
line-height: $u-badge-text-font-size;
|
||||
text-align: $u-badge-text-align;
|
||||
font-size: $u-badge-text-font-size;
|
||||
color: $u-badge-text-color;
|
||||
|
||||
&--dot {
|
||||
height: $u-badge-dot-size;
|
||||
width: $u-badge-dot-size;
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&--not-dot {
|
||||
padding: $u-badge-text-padding;
|
||||
}
|
||||
|
||||
&--horn {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background-color: $u-badge-primary;
|
||||
}
|
||||
|
||||
&--primary--inverted {
|
||||
color: $u-badge-primary;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: $u-badge-error;
|
||||
}
|
||||
|
||||
&--error--inverted {
|
||||
color: $u-badge-error;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background-color: $u-badge-success;
|
||||
}
|
||||
|
||||
&--success--inverted {
|
||||
color: $u-badge-success;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background-color: $u-badge-info;
|
||||
}
|
||||
|
||||
&--info--inverted {
|
||||
color: $u-badge-info;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: $u-badge-warning;
|
||||
}
|
||||
|
||||
&--warning--inverted {
|
||||
color: $u-badge-warning;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1000
uni_modules/uview-plus/components/u-barcode/u-barcode.vue
Normal file
1000
uni_modules/uview-plus/components/u-barcode/u-barcode.vue
Normal file
File diff suppressed because it is too large
Load Diff
27
uni_modules/uview-plus/components/u-box/props.js
Normal file
27
uni_modules/uview-plus/components/u-box/props.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const propsBox = defineMixin({
|
||||
props: {
|
||||
// 背景色
|
||||
bgColors: {
|
||||
type: [Array],
|
||||
default: ['#EEFCFF', '#FCF8FF', '#FDF8F2']
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: [String],
|
||||
default: "160px"
|
||||
},
|
||||
// 圆角
|
||||
borderRadius: {
|
||||
type: [String],
|
||||
default: "6px"
|
||||
},
|
||||
// 间隔
|
||||
gap: {
|
||||
type: [String],
|
||||
default: "15px"
|
||||
},
|
||||
}
|
||||
})
|
||||
91
uni_modules/uview-plus/components/u-box/u-box.vue
Normal file
91
uni_modules/uview-plus/components/u-box/u-box.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view class="u-box" :style="[{height: height}, addStyle(customStyle)]">
|
||||
<view class="u-box__left" :style="{borderRadius: borderRadius, backgroundColor: bgColors[0]}">
|
||||
<slot name="left">左</slot>
|
||||
</view>
|
||||
<view class="u-box__gap" :style="{width: gap, height: height}"></view>
|
||||
<view class="u-box__right">
|
||||
<view class="u-box__right-top" :style="{borderRadius: borderRadius, backgroundColor: bgColors[1]}">
|
||||
<slot name="rightTop">右上</slot>
|
||||
</view>
|
||||
<view class="u-box__right-gap" :style="{height: gap}"></view>
|
||||
<view class="u-box__right-bottom" :style="{borderRadius: borderRadius, backgroundColor: bgColors[2]}">
|
||||
<slot name="rightBottom">右下</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { propsBox } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
/**
|
||||
* box 盒子
|
||||
* @description box盒子一般为左边一个盒子,右侧两个等高的半盒组成,常用于App首页座位重点突出。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/box.html
|
||||
* @property {Array} bgColors 背景色
|
||||
* @property {String} height 高度
|
||||
* @property {String} borderRadius 圆角
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 点击cell列表时触发
|
||||
* @example <up-box colors=['blue', 'red', 'yellow'] height="200px"></up-box>
|
||||
*/
|
||||
export default {
|
||||
name: 'up-box',
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mixins: [mpMixin, mixin, propsBox],
|
||||
computed: {
|
||||
},
|
||||
emits: [],
|
||||
methods: {
|
||||
addStyle,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
@include flex();
|
||||
flex: 1;
|
||||
|
||||
&__left {
|
||||
@include flex();
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
&__gap {
|
||||
@include flex();
|
||||
flex-direction: column;
|
||||
}
|
||||
&__right {
|
||||
@include flex();
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__right-top {
|
||||
@include flex();
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__right-bottom {
|
||||
@include flex();
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
uni_modules/uview-plus/components/u-button/button.js
Normal file
43
uni_modules/uview-plus/components/u-button/button.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/button.js
|
||||
*/
|
||||
export default {
|
||||
// button组件
|
||||
button: {
|
||||
hairline: false,
|
||||
type: 'info',
|
||||
size: 'normal',
|
||||
shape: 'square',
|
||||
plain: false,
|
||||
disabled: false,
|
||||
loading: false,
|
||||
loadingText: '',
|
||||
loadingMode: 'spinner',
|
||||
loadingSize: 15,
|
||||
openType: '',
|
||||
formType: '',
|
||||
appParameter: '',
|
||||
hoverStopPropagation: true,
|
||||
lang: 'en',
|
||||
sessionFrom: '',
|
||||
sendMessageTitle: '',
|
||||
sendMessagePath: '',
|
||||
sendMessageImg: '',
|
||||
showMessageCard: false,
|
||||
dataName: '',
|
||||
throttleTime: 0,
|
||||
hoverStartTime: 0,
|
||||
hoverStayTime: 200,
|
||||
text: '',
|
||||
icon: '',
|
||||
iconColor: '',
|
||||
color: '',
|
||||
stop: true,
|
||||
}
|
||||
}
|
||||
46
uni_modules/uview-plus/components/u-button/nvue.scss
Normal file
46
uni_modules/uview-plus/components/u-button/nvue.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
$u-button-active-opacity:0.75 !default;
|
||||
$u-button-loading-text-margin-left:4px !default;
|
||||
$u-button-text-color: #FFFFFF !default;
|
||||
$u-button-text-plain-error-color:$u-error !default;
|
||||
$u-button-text-plain-warning-color:$u-warning !default;
|
||||
$u-button-text-plain-success-color:$u-success !default;
|
||||
$u-button-text-plain-info-color:$u-info !default;
|
||||
$u-button-text-plain-primary-color:$u-primary !default;
|
||||
.u-button {
|
||||
&--active {
|
||||
opacity: $u-button-active-opacity;
|
||||
}
|
||||
|
||||
&--active--plain {
|
||||
background-color: rgb(217, 217, 217);
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
margin-left:$u-button-loading-text-margin-left;
|
||||
}
|
||||
|
||||
&__text,
|
||||
&__loading-text {
|
||||
color:$u-button-text-color;
|
||||
}
|
||||
|
||||
&__text--plain--error {
|
||||
color:$u-button-text-plain-error-color;
|
||||
}
|
||||
|
||||
&__text--plain--warning {
|
||||
color:$u-button-text-plain-warning-color;
|
||||
}
|
||||
|
||||
&__text--plain--success{
|
||||
color:$u-button-text-plain-success-color;
|
||||
}
|
||||
|
||||
&__text--plain--info {
|
||||
color:$u-button-text-plain-info-color;
|
||||
}
|
||||
|
||||
&__text--plain--primary {
|
||||
color:$u-button-text-plain-primary-color;
|
||||
}
|
||||
}
|
||||
159
uni_modules/uview-plus/components/u-button/props.js
Normal file
159
uni_modules/uview-plus/components/u-button/props.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 是否细边框
|
||||
hairline: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.hairline
|
||||
},
|
||||
// 按钮的预置样式,info,primary,error,warning,success
|
||||
type: {
|
||||
type: String,
|
||||
default: () => defProps.button.type
|
||||
},
|
||||
// 按钮尺寸,large,normal,small,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: () => defProps.button.size
|
||||
},
|
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.button.shape
|
||||
},
|
||||
// 按钮是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.plain
|
||||
},
|
||||
// 是否禁止状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.disabled
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.loading
|
||||
},
|
||||
// 加载中提示文字
|
||||
loadingText: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.button.loadingText
|
||||
},
|
||||
// 加载状态图标类型
|
||||
loadingMode: {
|
||||
type: String,
|
||||
default: () => defProps.button.loadingMode
|
||||
},
|
||||
// 加载图标大小
|
||||
loadingSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.button.loadingSize
|
||||
},
|
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: {
|
||||
type: String,
|
||||
default: () => defProps.button.openType
|
||||
},
|
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: {
|
||||
type: String,
|
||||
default: () => defProps.button.formType
|
||||
},
|
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: () => defProps.button.appParameter
|
||||
},
|
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.hoverStopPropagation
|
||||
},
|
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: {
|
||||
type: String,
|
||||
default: () => defProps.button.lang
|
||||
},
|
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
default: () => defProps.button.sessionFrom
|
||||
},
|
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
default: () => defProps.button.sendMessageTitle
|
||||
},
|
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
default: () => defProps.button.sendMessagePath
|
||||
},
|
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
default: () => defProps.button.sendMessageImg
|
||||
},
|
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.showMessageCard
|
||||
},
|
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: {
|
||||
type: String,
|
||||
default: () => defProps.button.dataName
|
||||
},
|
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.button.throttleTime
|
||||
},
|
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.button.hoverStartTime
|
||||
},
|
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.button.hoverStayTime
|
||||
},
|
||||
// 按钮文字,之所以通过props传入,是因为slot传入的话
|
||||
// nvue中无法控制文字的样式
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.button.text
|
||||
},
|
||||
// 按钮图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => defProps.button.icon
|
||||
},
|
||||
// 按钮图标
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: () => defProps.button.icon
|
||||
},
|
||||
// 按钮颜色,支持传入linear-gradient渐变色
|
||||
color: {
|
||||
type: String,
|
||||
default: () => defProps.button.color
|
||||
},
|
||||
// 停止冒泡
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: () => defProps.button.stop
|
||||
},
|
||||
}
|
||||
})
|
||||
503
uni_modules/uview-plus/components/u-button/u-button.vue
Normal file
503
uni_modules/uview-plus/components/u-button/u-button.vue
Normal file
@@ -0,0 +1,503 @@
|
||||
<template>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<button
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
:app-parameter="appParameter"
|
||||
:hover-stop-propagation="hoverStopPropagation"
|
||||
:send-message-title="sendMessageTitle"
|
||||
:send-message-path="sendMessagePath"
|
||||
:lang="lang"
|
||||
:data-name="dataName"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
@getphonenumber="getphonenumber"
|
||||
@getuserinfo="getuserinfo"
|
||||
@error="error"
|
||||
@opensetting="opensetting"
|
||||
@launchapp="launchapp"
|
||||
@agreeprivacyauthorization="agreeprivacyauthorization"
|
||||
:hover-class="!disabled && !loading ? 'u-button--active' : ''"
|
||||
class="u-button u-reset-button"
|
||||
:style="[baseColor, addStyle(customStyle)]"
|
||||
@tap="clickHandler"
|
||||
:class="bemClass"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<u-loading-icon
|
||||
:mode="loadingMode"
|
||||
:size="loadingSize * 1.15"
|
||||
:color="loadingColor"
|
||||
></u-loading-icon>
|
||||
<text
|
||||
class="u-button__loading-text"
|
||||
:style="[{ fontSize: textSize + 'px' }]"
|
||||
>{{ loadingText || text }}</text
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<up-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
:customStyle="{ marginRight: '2px' }"
|
||||
></up-icon>
|
||||
<slot>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[{ fontSize: textSize + 'px' }]"
|
||||
>{{ text }}</text
|
||||
>
|
||||
</slot>
|
||||
</template>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
class="u-button"
|
||||
:hover-class="
|
||||
!disabled && !loading && !color && (plain || type === 'info')
|
||||
? 'u-button--active--plain'
|
||||
: !disabled && !loading && !plain
|
||||
? 'u-button--active'
|
||||
: ''
|
||||
"
|
||||
@tap="clickHandler"
|
||||
:class="bemClass"
|
||||
:style="[baseColor, addStyle(customStyle)]"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<u-loading-icon
|
||||
:mode="loadingMode"
|
||||
:size="loadingSize * 1.15"
|
||||
:color="loadingColor"
|
||||
></u-loading-icon>
|
||||
<text
|
||||
class="u-button__loading-text"
|
||||
:style="[nvueTextStyle]"
|
||||
:class="[plain && `u-button__text--plain--${type}`]"
|
||||
>{{ loadingText || text }}</text
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<up-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
></up-icon>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[
|
||||
{
|
||||
marginLeft: icon ? '2px' : 0,
|
||||
},
|
||||
nvueTextStyle,
|
||||
]"
|
||||
:class="[plain && `u-button__text--plain--${type}`]"
|
||||
>{{ text }}</text
|
||||
>
|
||||
</template>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { buttonMixin } from "../../libs/mixin/button";
|
||||
import { openType } from "../../libs/mixin/openType";
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { props } from "./props";
|
||||
import { addStyle } from '../../libs/function/index';
|
||||
import { throttle } from '../../libs/function/throttle';
|
||||
import color from '../../libs/config/color';
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/button.html
|
||||
*
|
||||
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
|
||||
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
|
||||
* @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
|
||||
* @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
|
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
|
||||
* @property {Boolean} disabled 是否禁用 (默认 false)
|
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
|
||||
* @property {String | Number} loadingText 加载中提示文字
|
||||
* @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
|
||||
* @property {String | Number} loadingSize 加载图标大小 (默认 15 )
|
||||
* @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
* @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
|
||||
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
|
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
|
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效
|
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
|
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
|
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
|
||||
* @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
* @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
|
||||
* @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
|
||||
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
|
||||
* @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
|
||||
* @property {String} icon 按钮图标
|
||||
* @property {String} iconColor 按钮图标颜色
|
||||
* @property {String} color 按钮颜色,支持传入linear-gradient渐变色
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 非禁止并且非加载中,才能点击
|
||||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
|
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
|
||||
* @event {Function} error 当使用开放能力时,发生错误的回调
|
||||
* @event {Function} opensetting 在打开授权设置页并关闭后回调
|
||||
* @event {Function} launchapp 打开 APP 成功的回调
|
||||
* @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调
|
||||
* @example <u-button>月落</u-button>
|
||||
*/
|
||||
export default {
|
||||
name: "u-button",
|
||||
// #ifdef MP
|
||||
mixins: [mpMixin, mixin, buttonMixin, openType, props],
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
mixins: [mpMixin, mixin, props],
|
||||
// #endif
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
// 生成bem风格的类名
|
||||
bemClass() {
|
||||
// this.bem为一个computed变量,在mixin中
|
||||
if (!this.color) {
|
||||
return this.bem(
|
||||
"button",
|
||||
["type", "shape", "size"],
|
||||
["disabled", "plain", "hairline"]
|
||||
);
|
||||
} else {
|
||||
// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
|
||||
return this.bem(
|
||||
"button",
|
||||
["shape", "size"],
|
||||
["disabled", "plain", "hairline"]
|
||||
);
|
||||
}
|
||||
},
|
||||
loadingColor() {
|
||||
if (this.plain) {
|
||||
// 如果有设置color值,则用color值,否则使用type主题颜色
|
||||
return this.color
|
||||
? this.color
|
||||
: color[`u-${this.type}`];
|
||||
}
|
||||
if (this.type === "info") {
|
||||
return "#c9c9c9";
|
||||
}
|
||||
return "rgb(200, 200, 200)";
|
||||
},
|
||||
iconColorCom() {
|
||||
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
|
||||
// up-icon的color能接受一个主题颜色的值
|
||||
if (this.iconColor) return this.iconColor;
|
||||
if (this.plain) {
|
||||
return this.color ? this.color : this.type;
|
||||
} else {
|
||||
return this.type === "info" ? "#000000" : "#ffffff";
|
||||
}
|
||||
},
|
||||
baseColor() {
|
||||
let style = {};
|
||||
if (this.color) {
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
style.color = this.plain ? this.color : "white";
|
||||
if (!this.plain) {
|
||||
// 非镂空,背景色使用自定义的颜色
|
||||
style["background-color"] = this.color;
|
||||
}
|
||||
if (this.color.indexOf("gradient") !== -1) {
|
||||
// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
|
||||
// weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
|
||||
// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
|
||||
style.borderTopWidth = 0;
|
||||
style.borderRightWidth = 0;
|
||||
style.borderBottomWidth = 0;
|
||||
style.borderLeftWidth = 0;
|
||||
if (!this.plain) {
|
||||
style.backgroundImage = this.color;
|
||||
}
|
||||
} else {
|
||||
// 非渐变色,则设置边框相关的属性
|
||||
style.borderColor = this.color;
|
||||
style.borderWidth = "1px";
|
||||
style.borderStyle = "solid";
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
|
||||
nvueTextStyle() {
|
||||
let style = {};
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
if (this.type === "info") {
|
||||
style.color = "#323233";
|
||||
}
|
||||
if (this.color) {
|
||||
style.color = this.plain ? this.color : "white";
|
||||
}
|
||||
style.fontSize = this.textSize + "px";
|
||||
return style;
|
||||
},
|
||||
// 字体大小
|
||||
textSize() {
|
||||
let fontSize = 14,
|
||||
{ size } = this;
|
||||
if (size === "large") fontSize = 16;
|
||||
if (size === "normal") fontSize = 14;
|
||||
if (size === "small") fontSize = 12;
|
||||
if (size === "mini") fontSize = 10;
|
||||
return fontSize;
|
||||
},
|
||||
},
|
||||
emits: ['click', 'getphonenumber', 'getuserinfo',
|
||||
'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'],
|
||||
methods: {
|
||||
addStyle,
|
||||
clickHandler(e: any) {
|
||||
// 非禁止并且非加载中,才能点击
|
||||
if (!this.disabled && !this.loading) {
|
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
|
||||
throttle(() => {
|
||||
this.$emit("click", e);
|
||||
}, this.throttleTime);
|
||||
}
|
||||
// 是否阻止事件传播
|
||||
this.stop && this.preventEvent(e)
|
||||
},
|
||||
// 下面为对接uniapp官方按钮开放能力事件回调的对接
|
||||
getphonenumber(res: any) {
|
||||
this.$emit("getphonenumber", res);
|
||||
},
|
||||
getuserinfo(res: any) {
|
||||
this.$emit("getuserinfo", res);
|
||||
},
|
||||
error(res: any) {
|
||||
this.$emit("error", res);
|
||||
},
|
||||
opensetting(res: any) {
|
||||
this.$emit("opensetting", res);
|
||||
},
|
||||
launchapp(res: any) {
|
||||
this.$emit("launchapp", res);
|
||||
},
|
||||
agreeprivacyauthorization(res) {
|
||||
this.$emit("agreeprivacyauthorization", res);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "./vue.scss";
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
@import "./nvue.scss";
|
||||
/* #endif */
|
||||
|
||||
$u-button-u-button-height: 40px !default;
|
||||
$u-button-text-font-size: 15px !default;
|
||||
$u-button-loading-text-font-size: 15px !default;
|
||||
$u-button-loading-text-margin-left: 4px !default;
|
||||
$u-button-large-width: 100% !default;
|
||||
$u-button-large-height: 50px !default;
|
||||
$u-button-normal-padding: 0 12px !default;
|
||||
$u-button-large-padding: 0 15px !default;
|
||||
$u-button-normal-font-size: 14px !default;
|
||||
$u-button-small-min-width: 60px !default;
|
||||
$u-button-small-height: 30px !default;
|
||||
$u-button-small-padding: 0px 8px !default;
|
||||
$u-button-mini-padding: 0px 8px !default;
|
||||
$u-button-small-font-size: 12px !default;
|
||||
$u-button-mini-height: 22px !default;
|
||||
$u-button-mini-font-size: 10px !default;
|
||||
$u-button-mini-min-width: 50px !default;
|
||||
$u-button-disabled-opacity: 0.5 !default;
|
||||
$u-button-info-color: #323233 !default;
|
||||
$u-button-info-background-color: #fff !default;
|
||||
$u-button-info-border-color: #ebedf0 !default;
|
||||
$u-button-info-border-width: 1px !default;
|
||||
$u-button-info-border-style: solid !default;
|
||||
$u-button-success-color: #fff !default;
|
||||
$u-button-success-background-color: $u-success !default;
|
||||
$u-button-success-border-color: $u-button-success-background-color !default;
|
||||
$u-button-success-border-width: 1px !default;
|
||||
$u-button-success-border-style: solid !default;
|
||||
$u-button-primary-color: #fff !default;
|
||||
$u-button-primary-background-color: $u-primary !default;
|
||||
$u-button-primary-border-color: $u-button-primary-background-color !default;
|
||||
$u-button-primary-border-width: 1px !default;
|
||||
$u-button-primary-border-style: solid !default;
|
||||
$u-button-error-color: #fff !default;
|
||||
$u-button-error-background-color: $u-error !default;
|
||||
$u-button-error-border-color: $u-button-error-background-color !default;
|
||||
$u-button-error-border-width: 1px !default;
|
||||
$u-button-error-border-style: solid !default;
|
||||
$u-button-warning-color: #fff !default;
|
||||
$u-button-warning-background-color: $u-warning !default;
|
||||
$u-button-warning-border-color: $u-button-warning-background-color !default;
|
||||
$u-button-warning-border-width: 1px !default;
|
||||
$u-button-warning-border-style: solid !default;
|
||||
$u-button-block-width: 100% !default;
|
||||
$u-button-circle-border-top-right-radius: 100px !default;
|
||||
$u-button-circle-border-top-left-radius: 100px !default;
|
||||
$u-button-circle-border-bottom-left-radius: 100px !default;
|
||||
$u-button-circle-border-bottom-right-radius: 100px !default;
|
||||
$u-button-square-border-top-right-radius: 3px !default;
|
||||
$u-button-square-border-top-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-right-radius: 3px !default;
|
||||
$u-button-icon-min-width: 1em !default;
|
||||
$u-button-plain-background-color: #fff !default;
|
||||
$u-button-hairline-border-width: 0.5px !default;
|
||||
|
||||
.u-button {
|
||||
height: $u-button-u-button-height;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include flex;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
|
||||
&__text {
|
||||
font-size: $u-button-text-font-size;
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
font-size: $u-button-loading-text-font-size;
|
||||
margin-left: $u-button-loading-text-margin-left;
|
||||
}
|
||||
|
||||
&--large {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: $u-button-large-width;
|
||||
/* #endif */
|
||||
height: $u-button-large-height;
|
||||
padding: $u-button-large-padding;
|
||||
}
|
||||
|
||||
&--normal {
|
||||
padding: $u-button-normal-padding;
|
||||
font-size: $u-button-normal-font-size;
|
||||
}
|
||||
|
||||
&--small {
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-small-min-width;
|
||||
/* #endif */
|
||||
height: $u-button-small-height;
|
||||
padding: $u-button-small-padding;
|
||||
font-size: $u-button-small-font-size;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
height: $u-button-mini-height;
|
||||
font-size: $u-button-mini-font-size;
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-mini-min-width;
|
||||
/* #endif */
|
||||
padding: $u-button-mini-padding;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: $u-button-disabled-opacity;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $u-button-info-color;
|
||||
background-color: $u-button-info-background-color;
|
||||
border-color: $u-button-info-border-color;
|
||||
border-width: $u-button-info-border-width;
|
||||
border-style: $u-button-info-border-style;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-button-success-color;
|
||||
background-color: $u-button-success-background-color;
|
||||
border-color: $u-button-success-border-color;
|
||||
border-width: $u-button-success-border-width;
|
||||
border-style: $u-button-success-border-style;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: $u-button-primary-color;
|
||||
background-color: $u-button-primary-background-color;
|
||||
border-color: $u-button-primary-border-color;
|
||||
border-width: $u-button-primary-border-width;
|
||||
border-style: $u-button-primary-border-style;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-button-error-color;
|
||||
background-color: $u-button-error-background-color;
|
||||
border-color: $u-button-error-border-color;
|
||||
border-width: $u-button-error-border-width;
|
||||
border-style: $u-button-error-border-style;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-button-warning-color;
|
||||
background-color: $u-button-warning-background-color;
|
||||
border-color: $u-button-warning-border-color;
|
||||
border-width: $u-button-warning-border-width;
|
||||
border-style: $u-button-warning-border-style;
|
||||
}
|
||||
|
||||
&--block {
|
||||
@include flex;
|
||||
width: $u-button-block-width;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-top-right-radius: $u-button-circle-border-top-right-radius;
|
||||
border-top-left-radius: $u-button-circle-border-top-left-radius;
|
||||
border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
|
||||
border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-bottom-left-radius: $u-button-square-border-top-right-radius;
|
||||
border-bottom-right-radius: $u-button-square-border-top-left-radius;
|
||||
border-top-left-radius: $u-button-square-border-bottom-left-radius;
|
||||
border-top-right-radius: $u-button-square-border-bottom-right-radius;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-icon-min-width;
|
||||
line-height: inherit !important;
|
||||
vertical-align: top;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background-color: $u-button-plain-background-color;
|
||||
}
|
||||
|
||||
&--hairline {
|
||||
border-width: $u-button-hairline-border-width !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
uni_modules/uview-plus/components/u-button/vue.scss
Normal file
81
uni_modules/uview-plus/components/u-button/vue.scss
Normal file
@@ -0,0 +1,81 @@
|
||||
// nvue下hover-class无效
|
||||
$u-button-before-top:50% !default;
|
||||
$u-button-before-left:50% !default;
|
||||
$u-button-before-width:100% !default;
|
||||
$u-button-before-height:100% !default;
|
||||
$u-button-before-transform:translate(-50%, -50%) !default;
|
||||
$u-button-before-opacity:0 !default;
|
||||
$u-button-before-background-color:#000 !default;
|
||||
$u-button-before-border-color:#000 !default;
|
||||
$u-button-active-before-opacity:.15 !default;
|
||||
$u-button-icon-margin-left:4px !default;
|
||||
$u-button-plain-u-button-info-color:$u-info;
|
||||
$u-button-plain-u-button-success-color:$u-success;
|
||||
$u-button-plain-u-button-error-color:$u-error;
|
||||
$u-button-plain-u-button-warning-color:$u-warning;
|
||||
|
||||
.u-button {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
&__text {
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top:$u-button-before-top;
|
||||
left:$u-button-before-left;
|
||||
width:$u-button-before-width;
|
||||
height:$u-button-before-height;
|
||||
border: inherit;
|
||||
border-radius: inherit;
|
||||
transform:$u-button-before-transform;
|
||||
opacity:$u-button-before-opacity;
|
||||
content: " ";
|
||||
background-color:$u-button-before-background-color;
|
||||
border-color:$u-button-before-border-color;
|
||||
}
|
||||
|
||||
&--active {
|
||||
&:before {
|
||||
opacity: .15
|
||||
}
|
||||
}
|
||||
|
||||
&__icon+&__text:not(:empty),
|
||||
&__loading-text {
|
||||
margin-left:$u-button-icon-margin-left;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--primary {
|
||||
color: $u-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--info {
|
||||
color:$u-button-plain-u-button-info-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--success {
|
||||
color:$u-button-plain-u-button-success-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--error {
|
||||
color:$u-button-plain-u-button-error-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--warning {
|
||||
color:$u-button-plain-u-button-warning-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
uni_modules/uview-plus/components/u-calendar/calendar.js
Normal file
48
uni_modules/uview-plus/components/u-calendar/calendar.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/calendar.js
|
||||
*/
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
// calendar 组件
|
||||
calendar: {
|
||||
title: t("up.calendar.chooseDates"),
|
||||
showTitle: true,
|
||||
showSubtitle: true,
|
||||
mode: 'single',
|
||||
startText: t("up.common.start"),
|
||||
endText: t("up.common.end"),
|
||||
customList: [],
|
||||
color: '#3c9cff',
|
||||
minDate: 0,
|
||||
maxDate: 0,
|
||||
defaultDate: null,
|
||||
maxCount: Number.MAX_SAFE_INTEGER, // Infinity
|
||||
rowHeight: 56,
|
||||
formatter: null,
|
||||
showLunar: false,
|
||||
showMark: true,
|
||||
confirmText: t("up.common.confirm"),
|
||||
confirmDisabledText: t("up.common.confirm"),
|
||||
show: false,
|
||||
closeOnClickOverlay: false,
|
||||
readonly: false,
|
||||
showConfirm: true,
|
||||
maxRange: Number.MAX_SAFE_INTEGER, // Infinity
|
||||
rangePrompt: '',
|
||||
showRangePrompt: true,
|
||||
allowSameDay: false,
|
||||
round: 0,
|
||||
monthNum: 3,
|
||||
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")],
|
||||
forbidDays: [],
|
||||
forbidDaysToast: t("up.calendar.disabled"),
|
||||
monthFormat: '',
|
||||
pageInline: false
|
||||
}
|
||||
}
|
||||
109
uni_modules/uview-plus/components/u-calendar/header.vue
Normal file
109
uni_modules/uview-plus/components/u-calendar/header.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<view class="u-calendar-header u-border-bottom">
|
||||
<text
|
||||
class="u-calendar-header__title"
|
||||
v-if="showTitle"
|
||||
>{{ title }}</text>
|
||||
<text
|
||||
class="u-calendar-header__subtitle"
|
||||
v-if="showSubtitle"
|
||||
>{{ subtitle }}</text>
|
||||
<view class="u-calendar-header__weekdays">
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[0] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[1] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[2] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[3] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[4] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[5] }}</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">{{ weekText[6] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
export default {
|
||||
name: 'u-calendar-header',
|
||||
mixins: [mpMixin, mixin],
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 副标题
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示副标题
|
||||
showSubtitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 星期文本
|
||||
weekText: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
name() {
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-calendar-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 4px;
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 14px;
|
||||
color: $u-main-color;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__weekdays {
|
||||
@include flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&__weekday {
|
||||
font-size: 13px;
|
||||
color: $u-main-color;
|
||||
line-height: 30px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
616
uni_modules/uview-plus/components/u-calendar/month.vue
Normal file
616
uni_modules/uview-plus/components/u-calendar/month.vue
Normal file
@@ -0,0 +1,616 @@
|
||||
<template>
|
||||
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
|
||||
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
|
||||
:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
|
||||
<text v-if="index !== 0" class="u-calendar-month__title">{{ monthTitle(item) }}</text>
|
||||
<view class="u-calendar-month__days">
|
||||
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
|
||||
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
|
||||
</view>
|
||||
<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
|
||||
:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
|
||||
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
|
||||
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
|
||||
<text class="u-calendar-month__days__day__select__info"
|
||||
:class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__info--disabled' : '']"
|
||||
:style="[textStyle(item1)]">{{ item1.day }}</text>
|
||||
<text v-if="getBottomInfo(index, index1, item1)"
|
||||
class="u-calendar-month__days__day__select__buttom-info"
|
||||
:class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__buttom-info--disabled' : '']"
|
||||
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
|
||||
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, deepClone, toast, sleep } from '../../libs/function/index';
|
||||
import { colorGradient } from '../../libs/function/colorGradient';
|
||||
import test from '../../libs/function/test';
|
||||
import defProps from '../../libs/config/props';
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
name: 'u-calendar-month',
|
||||
mixins: [mpMixin, mixin],
|
||||
props: {
|
||||
// 是否显示月份背景色
|
||||
showMark: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 主题色,对底部按钮和选中日期有效
|
||||
color: {
|
||||
type: String,
|
||||
default: '#3c9cff'
|
||||
},
|
||||
// 月份数据
|
||||
months: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 日期选择类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
// 日期行高
|
||||
rowHeight: {
|
||||
type: [String, Number],
|
||||
default: 58
|
||||
},
|
||||
// mode=multiple时,最多可选多少个日期
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: Infinity
|
||||
},
|
||||
// mode=range时,第一个日期底部的提示文字
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
// mode=range时,最后一个日期底部的提示文字
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
defaultDate: {
|
||||
type: [Array, String, Date],
|
||||
default: null
|
||||
},
|
||||
// 最小的可选日期
|
||||
minDate: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 最大可选日期
|
||||
maxDate: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 如果没有设置maxDate,则往后推多少个月
|
||||
maxMonth: {
|
||||
type: [String, Number],
|
||||
default: 2
|
||||
},
|
||||
// 是否为只读状态,只读状态下禁止选择日期
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.readonly
|
||||
},
|
||||
// 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
maxRange: {
|
||||
type: [Number, String],
|
||||
default: Infinity
|
||||
},
|
||||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
rangePrompt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
|
||||
showRangePrompt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许日期范围的起止时间为同一天,mode = range时有效
|
||||
allowSameDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
forbidDays: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
forbidDaysToast: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 每个日期的宽度
|
||||
width: 0,
|
||||
// 当前选中的日期item
|
||||
item: {},
|
||||
selected: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedChange: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setDefaultDate()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
|
||||
selectedChange() {
|
||||
return [this.minDate, this.maxDate, this.defaultDate]
|
||||
},
|
||||
dayStyle(index1, index2, item) {
|
||||
return (index1, index2, item) => {
|
||||
const style = {}
|
||||
let week = item.week
|
||||
// 不进行四舍五入的形式保留2位小数
|
||||
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
|
||||
// 得出每个日期的宽度
|
||||
// #ifdef APP-NVUE
|
||||
style.width = addUnit(dayWidth, 'px')
|
||||
// #endif
|
||||
style.height = addUnit(this.rowHeight, 'px')
|
||||
if (index2 === 0) {
|
||||
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
|
||||
week = (week === 0 ? 7 : week) - 1
|
||||
style.marginLeft = addUnit(week * dayWidth, 'px')
|
||||
}
|
||||
if (this.mode === 'range') {
|
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端导致的bug
|
||||
style.paddingLeft = 0
|
||||
style.paddingRight = 0
|
||||
style.paddingBottom = 0
|
||||
style.paddingTop = 0
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
daySelectStyle() {
|
||||
return (index1, index2, item) => {
|
||||
let date = dayjs(item.date).format("YYYY-MM-DD"),
|
||||
style = {}
|
||||
// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.backgroundColor = this.color
|
||||
}
|
||||
if (this.mode === 'single') {
|
||||
if (date === this.selected[0]) {
|
||||
// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
} else if (this.mode === 'range') {
|
||||
if (this.selected.length >= 2) {
|
||||
const len = this.selected.length - 1
|
||||
// 第一个日期设置左上角和左下角的圆角
|
||||
if (this.dateSame(date, this.selected[0])) {
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
}
|
||||
// 最后一个日期设置右上角和右下角的圆角
|
||||
if (this.dateSame(date, this.selected[len])) {
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
|
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
|
||||
.selected[len]))) {
|
||||
style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
|
||||
// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
|
||||
style.opacity = 0.7
|
||||
}
|
||||
} else if (this.selected.length === 1) {
|
||||
// 之所以需要这么写,是因为uni-app的iOS客户端的bug
|
||||
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
}
|
||||
} else {
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 某个日期是否被选中
|
||||
textStyle() {
|
||||
return (item) => {
|
||||
const date = dayjs(item.date).format("YYYY-MM-DD"),
|
||||
style = {}
|
||||
// 选中的日期,提示文字设置白色
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.color = '#ffffff'
|
||||
}
|
||||
if (this.mode === 'range') {
|
||||
const len = this.selected.length - 1
|
||||
// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
|
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
|
||||
.selected[len]))) {
|
||||
style.color = this.color
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 获取底部的提示文字
|
||||
getBottomInfo() {
|
||||
return (index1, index2, item) => {
|
||||
const date = dayjs(item.date).format("YYYY-MM-DD")
|
||||
const bottomInfo = item.bottomInfo
|
||||
// 当为日期范围模式时,且选择的日期个数大于0时
|
||||
if (this.mode === 'range' && this.selected.length > 0) {
|
||||
if (this.selected.length === 1) {
|
||||
// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
|
||||
if (this.dateSame(date, this.selected[0])) return this.startText
|
||||
else return bottomInfo
|
||||
} else {
|
||||
const len = this.selected.length - 1
|
||||
// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
|
||||
if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
|
||||
len === 1) {
|
||||
// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
|
||||
return `${this.startText}/${this.endText}`
|
||||
} else if (this.dateSame(date, this.selected[0])) {
|
||||
return this.startText
|
||||
} else if (this.dateSame(date, this.selected[len])) {
|
||||
return this.endText
|
||||
} else {
|
||||
return bottomInfo
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return bottomInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
emits: ['monthSelected', 'updateMonthTop'],
|
||||
methods: {
|
||||
init() {
|
||||
// 初始化默认选中
|
||||
this.$emit('monthSelected', this.selected)
|
||||
this.$nextTick(() => {
|
||||
// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
|
||||
// 因为nvue下,$nextTick并不是100%可靠的
|
||||
sleep(10).then(() => {
|
||||
this.getWrapperWidth()
|
||||
this.getMonthRect()
|
||||
})
|
||||
})
|
||||
},
|
||||
monthTitle(item) {
|
||||
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
|
||||
return item.year + '年' + (item.month < 10 ? '0' + item.month : item.month) + '月'
|
||||
} else {
|
||||
return (item.month < 10 ? '0' + item.month : item.month) + '/' + item.year
|
||||
}
|
||||
},
|
||||
isForbid(item) {
|
||||
let date = dayjs(item.date).format("YYYY-MM-DD")
|
||||
if (this.mode !== 'range' && this.forbidDays.includes(date)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 判断两个日期是否相等
|
||||
dateSame(date1, date2) {
|
||||
return dayjs(date1).isSame(dayjs(date2))
|
||||
},
|
||||
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
|
||||
getWrapperWidth() {
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
|
||||
this.width = res.size.width
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
this.$uGetRect('.u-calendar-month-wrapper').then(size => {
|
||||
this.width = size.width
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
getMonthRect() {
|
||||
// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
|
||||
const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
|
||||
`u-calendar-month-${index}`))
|
||||
// 一次性返回
|
||||
Promise.all(promiseAllArr).then(
|
||||
sizes => {
|
||||
let height = 1
|
||||
const topArr = []
|
||||
for (let i = 0; i < this.months.length; i++) {
|
||||
// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
|
||||
topArr[i] = height
|
||||
height += sizes[i].height
|
||||
}
|
||||
// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
|
||||
this.$emit('updateMonthTop', topArr)
|
||||
})
|
||||
},
|
||||
// 获取每个月份区域的尺寸
|
||||
getMonthRectByPromise(el) {
|
||||
// #ifndef APP-NVUE
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview-plus.jiangruyi.com/js/getRect.html
|
||||
// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
|
||||
return new Promise(resolve => {
|
||||
this.$uGetRect(`.${el}`).then(size => {
|
||||
resolve(size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// nvue下,使用dom模块查询元素高度
|
||||
// 返回一个promise,让调用此方法的主体能使用then回调
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(this.$refs[el][0], res => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 点击某一个日期
|
||||
clickHandler(index1, index2, item) {
|
||||
if (this.readonly) {
|
||||
return;
|
||||
}
|
||||
this.item = item
|
||||
const date = dayjs(item.date).format("YYYY-MM-DD")
|
||||
if (item.disabled) return
|
||||
if (this.isForbid(item)) {
|
||||
uni.showToast({
|
||||
title: this.forbidDaysToast
|
||||
})
|
||||
return
|
||||
}
|
||||
// 对上一次选择的日期数组进行深度克隆
|
||||
let selected = deepClone(this.selected)
|
||||
if (this.mode === 'single') {
|
||||
// 单选情况下,让数组中的元素为当前点击的日期
|
||||
selected = [date]
|
||||
} else if (this.mode === 'multiple') {
|
||||
if (selected.some(item => this.dateSame(item, date))) {
|
||||
// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
|
||||
const itemIndex = selected.findIndex(item => item === date)
|
||||
selected.splice(itemIndex, 1)
|
||||
} else {
|
||||
// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
|
||||
if (selected.length < this.maxCount) selected.push(date)
|
||||
}
|
||||
} else {
|
||||
// 选择区间形式
|
||||
if (selected.length === 0 || selected.length >= 2) {
|
||||
// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
|
||||
selected = [date]
|
||||
} else if (selected.length === 1) {
|
||||
// 如果已经选择了开始日期
|
||||
const existsDate = selected[0]
|
||||
// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
|
||||
if (dayjs(date).isBefore(existsDate)) {
|
||||
selected = [date]
|
||||
} else if (dayjs(date).isAfter(existsDate)) {
|
||||
// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
|
||||
if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
|
||||
if(this.rangePrompt) {
|
||||
toast(this.rangePrompt)
|
||||
} else {
|
||||
toast(t("up.calendar.daysExceed", { days: this.maxRange }))
|
||||
}
|
||||
return
|
||||
}
|
||||
// 如果当前日期大于已有日期,将当前的添加到数组尾部
|
||||
selected.push(date)
|
||||
const startDate = selected[0]
|
||||
const endDate = selected[1]
|
||||
const arr = []
|
||||
let i = 0
|
||||
do {
|
||||
// 将开始和结束日期之间的日期添加到数组中
|
||||
arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
|
||||
i++
|
||||
// 累加的日期小于结束日期时,继续下一次的循环
|
||||
} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
|
||||
// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
|
||||
arr.push(endDate)
|
||||
selected = arr
|
||||
} else {
|
||||
// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
|
||||
if (selected[0] === date && !this.allowSameDay) return
|
||||
selected.push(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setSelected(selected)
|
||||
},
|
||||
// 设置默认日期
|
||||
setDefaultDate() {
|
||||
if (!this.defaultDate) {
|
||||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
|
||||
const selected = [dayjs().format("YYYY-MM-DD")]
|
||||
return this.setSelected(selected, false)
|
||||
}
|
||||
let defaultDate = []
|
||||
const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
|
||||
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
|
||||
if (this.mode === 'single') {
|
||||
// 单选模式,可以是字符串或数组,Date对象等
|
||||
if (!test.array(this.defaultDate)) {
|
||||
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
|
||||
} else {
|
||||
defaultDate = [this.defaultDate[0]]
|
||||
}
|
||||
} else {
|
||||
// 如果为非数组,则不执行
|
||||
if (!test.array(this.defaultDate)) return
|
||||
defaultDate = this.defaultDate
|
||||
}
|
||||
// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
|
||||
defaultDate = defaultDate.filter(item => {
|
||||
return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
|
||||
maxDate).add(1, 'day'))
|
||||
})
|
||||
this.setSelected(defaultDate, false)
|
||||
},
|
||||
setSelected(selected, event = true) {
|
||||
this.selected = selected
|
||||
event && this.$emit('monthSelected', this.selected,'tap')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-calendar-month-wrapper {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.u-calendar-month {
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
line-height: 42px;
|
||||
height: 42px;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__days {
|
||||
position: relative;
|
||||
@include flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__month-mark-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
font-size: 155px;
|
||||
color: rgba(231, 232, 234, 0.83);
|
||||
}
|
||||
}
|
||||
|
||||
&__day {
|
||||
@include flex;
|
||||
padding: 2px;
|
||||
/* #ifndef APP-NVUE */
|
||||
// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
|
||||
width: calc(100% / 7);
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
|
||||
&__select {
|
||||
flex: 1;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
&__dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 100px;
|
||||
background-color: $u-error;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
&__buttom-info {
|
||||
color: $u-content-color;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
&--selected {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #cacbcd;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
|
||||
&--selected {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #cacbcd;
|
||||
}
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: $u-primary;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&--range-selected {
|
||||
opacity: 0.3;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&--range-start-selected {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&--range-end-selected {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
169
uni_modules/uview-plus/components/u-calendar/props.js
Normal file
169
uni_modules/uview-plus/components/u-calendar/props.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 日历顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.title
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showTitle
|
||||
},
|
||||
// 是否显示副标题
|
||||
showSubtitle: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showSubtitle
|
||||
},
|
||||
// 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.mode
|
||||
},
|
||||
// mode=range时,第一个日期底部的提示文字
|
||||
startText: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.startText
|
||||
},
|
||||
// mode=range时,最后一个日期底部的提示文字
|
||||
endText: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.endText
|
||||
},
|
||||
// 自定义列表
|
||||
customList: {
|
||||
type: Array,
|
||||
default: () => defProps.calendar.customList
|
||||
},
|
||||
// 主题色,对底部按钮和选中日期有效
|
||||
color: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.color
|
||||
},
|
||||
// 最小的可选日期
|
||||
minDate: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendar.minDate
|
||||
},
|
||||
// 最大可选日期
|
||||
maxDate: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendar.maxDate
|
||||
},
|
||||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
defaultDate: {
|
||||
type: [Array, String, Date, null],
|
||||
default: () => defProps.calendar.defaultDate
|
||||
},
|
||||
// mode=multiple时,最多可选多少个日期
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendar.maxCount
|
||||
},
|
||||
// 日期行高
|
||||
rowHeight: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.calendar.rowHeight
|
||||
},
|
||||
// 日期格式化函数
|
||||
formatter: {
|
||||
type: [Function, null],
|
||||
default: () => defProps.calendar.formatter
|
||||
},
|
||||
// 是否显示农历
|
||||
showLunar: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showLunar
|
||||
},
|
||||
// 是否显示月份背景色
|
||||
showMark: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showMark
|
||||
},
|
||||
// 确定按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.confirmText
|
||||
},
|
||||
// 确认按钮处于禁用状态时的文字
|
||||
confirmDisabledText: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.confirmDisabledText
|
||||
},
|
||||
// 是否显示日历弹窗
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.show
|
||||
},
|
||||
// 是否允许点击遮罩关闭日历
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.closeOnClickOverlay
|
||||
},
|
||||
// 是否为只读状态,只读状态下禁止选择日期
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.readonly
|
||||
},
|
||||
// 是否展示确认按钮
|
||||
showConfirm: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showConfirm
|
||||
},
|
||||
// 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
maxRange: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.calendar.maxRange
|
||||
},
|
||||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
rangePrompt: {
|
||||
type: String,
|
||||
default: () => defProps.calendar.rangePrompt
|
||||
},
|
||||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
|
||||
showRangePrompt: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.showRangePrompt
|
||||
},
|
||||
// 是否允许日期范围的起止时间为同一天,mode = range时有效
|
||||
allowSameDay: {
|
||||
type: Boolean,
|
||||
default: () => defProps.calendar.allowSameDay
|
||||
},
|
||||
// 圆角值
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: () => defProps.calendar.round
|
||||
},
|
||||
// 最多展示月份数量
|
||||
monthNum: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 星期文案
|
||||
weekText: {
|
||||
type: Array,
|
||||
default: defProps.calendar.weekText
|
||||
},
|
||||
forbidDays: {
|
||||
type: Array,
|
||||
default: defProps.calendar.forbidDays
|
||||
},
|
||||
forbidDaysToast:{
|
||||
type: String,
|
||||
default: defProps.calendar.forbidDaysToast
|
||||
},
|
||||
monthFormat:{
|
||||
type: String,
|
||||
default: defProps.calendar.monthFormat
|
||||
},
|
||||
// 是否页面内展示
|
||||
pageInline:{
|
||||
type: Boolean,
|
||||
default: defProps.calendar.pageInline
|
||||
}
|
||||
}
|
||||
})
|
||||
421
uni_modules/uview-plus/components/u-calendar/u-calendar.vue
Normal file
421
uni_modules/uview-plus/components/u-calendar/u-calendar.vue
Normal file
@@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
mode="bottom"
|
||||
:closeable="!pageInline"
|
||||
@close="close"
|
||||
:round="round"
|
||||
:pageInline="pageInline"
|
||||
:closeOnClickOverlay="closeOnClickOverlay"
|
||||
>
|
||||
<view class="u-calendar">
|
||||
<uHeader
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:showSubtitle="showSubtitle"
|
||||
:showTitle="showTitle"
|
||||
:weekText="weekText"
|
||||
></uHeader>
|
||||
<scroll-view
|
||||
:style="{
|
||||
height: addUnit(listHeight, 'px')
|
||||
}"
|
||||
scroll-y
|
||||
@scroll="onScroll"
|
||||
:scroll-top="scrollTop"
|
||||
:scrollIntoView="scrollIntoView"
|
||||
>
|
||||
<uMonth
|
||||
:color="color"
|
||||
:rowHeight="rowHeight"
|
||||
:showMark="showMark"
|
||||
:months="months"
|
||||
:mode="mode"
|
||||
:maxCount="maxCount"
|
||||
:startText="startText"
|
||||
:endText="endText"
|
||||
:defaultDate="defaultDate"
|
||||
:minDate="innerMinDate"
|
||||
:maxDate="innerMaxDate"
|
||||
:maxMonth="monthNum"
|
||||
:readonly="readonly"
|
||||
:maxRange="maxRange"
|
||||
:rangePrompt="rangePrompt"
|
||||
:showRangePrompt="showRangePrompt"
|
||||
:allowSameDay="allowSameDay"
|
||||
:forbidDays="forbidDays"
|
||||
:forbidDaysToast="forbidDaysToast"
|
||||
:monthFormat="monthFormat"
|
||||
ref="month"
|
||||
@monthSelected="monthSelected"
|
||||
@updateMonthTop="updateMonthTop"
|
||||
></uMonth>
|
||||
</scroll-view>
|
||||
<slot name="footer" v-if="showConfirm">
|
||||
<view class="u-calendar__confirm">
|
||||
<u-button
|
||||
shape="circle"
|
||||
:text="
|
||||
buttonDisabled ? confirmDisabledText : confirmText
|
||||
"
|
||||
:color="color"
|
||||
@click="confirm"
|
||||
:disabled="buttonDisabled"
|
||||
></u-button>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uHeader from './header.vue'
|
||||
import uMonth from './month.vue'
|
||||
import { props } from './props.js'
|
||||
import util from './util.js'
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
|
||||
import Calendar from '../../libs/util/calendar.js'
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin.js'
|
||||
import { mixin } from '../../libs/mixin/mixin.js'
|
||||
import { addUnit, getPx, range, error, padZero } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/calendar.html
|
||||
*
|
||||
* @property {String} title 标题内容 (默认 日期选择 )
|
||||
* @property {Boolean} showTitle 是否显示标题 (默认 true )
|
||||
* @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
|
||||
* @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
|
||||
* @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' )
|
||||
* @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
|
||||
* @property {Array} customList 自定义列表
|
||||
* @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' )
|
||||
* @property {String | Number} minDate 最小的可选日期 (默认 0 )
|
||||
* @property {String | Number} maxDate 最大可选日期 (默认 0 )
|
||||
* @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
* @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
|
||||
* @property {String | Number} rowHeight 日期行高 (默认 56 )
|
||||
* @property {Function} formatter 日期格式化函数
|
||||
* @property {Boolean} showLunar 是否显示农历 (默认 false )
|
||||
* @property {Boolean} showMark 是否显示月份背景色 (默认 true )
|
||||
* @property {String} confirmText 确定按钮的文字 (默认 '确定' )
|
||||
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
|
||||
* @property {Boolean} show 是否显示日历弹窗 (默认 false )
|
||||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
|
||||
* @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false )
|
||||
* @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
|
||||
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
|
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
|
||||
* @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
|
||||
* @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] )
|
||||
*
|
||||
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
|
||||
* @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
|
||||
* @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
|
||||
</u-calendar>
|
||||
* */
|
||||
export default {
|
||||
name: 'u-calendar',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
components: {
|
||||
uHeader,
|
||||
uMonth
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 需要显示的月份的数组
|
||||
months: [],
|
||||
// 在月份滚动区域中,当前视图中月份的index索引
|
||||
monthIndex: 0,
|
||||
// 月份滚动区域的高度
|
||||
listHeight: 0,
|
||||
// month组件中选择的日期数组
|
||||
selected: [],
|
||||
scrollIntoView: '',
|
||||
scrollIntoViewScroll: '',
|
||||
scrollTop:0,
|
||||
// 过滤处理方法
|
||||
innerFormatter: (value) => value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollIntoView: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
// console.log('scrollIntoView', n)
|
||||
}
|
||||
},
|
||||
selectedChange: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setMonth()
|
||||
}
|
||||
},
|
||||
// 打开弹窗时,设置月份数据
|
||||
show: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
if (n) {
|
||||
this.setMonth()
|
||||
} else {
|
||||
// 关闭时重置scrollIntoView,否则会出现二次打开日历,当前月份数据显示不正确。
|
||||
// scrollIntoView需要有一个值变动过程,才会产生作用。
|
||||
this.scrollIntoView = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
|
||||
innerMaxDate() {
|
||||
return test.number(this.maxDate)
|
||||
? Number(this.maxDate)
|
||||
: this.maxDate
|
||||
},
|
||||
innerMinDate() {
|
||||
return test.number(this.minDate)
|
||||
? Number(this.minDate)
|
||||
: this.minDate
|
||||
},
|
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
|
||||
selectedChange() {
|
||||
return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
|
||||
},
|
||||
subtitle() {
|
||||
// 初始化时,this.months为空数组,所以需要特别判断处理
|
||||
if (this.months.length) {
|
||||
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
|
||||
return this.months[this.monthIndex].year + '年' + (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '月'
|
||||
} else {
|
||||
return (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '/' + this.months[this.monthIndex].year
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
buttonDisabled() {
|
||||
// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
|
||||
if (this.mode === 'range') {
|
||||
if (this.selected.length <= 1) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.start = Date.now()
|
||||
this.init()
|
||||
},
|
||||
emits: ["confirm", "close"],
|
||||
methods: {
|
||||
addUnit,
|
||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
||||
setFormatter(e) {
|
||||
this.innerFormatter = e
|
||||
},
|
||||
// month组件内部选择日期后,通过事件通知给父组件
|
||||
monthSelected(e,scene ='init') {
|
||||
this.selected = e
|
||||
if (!this.showConfirm) {
|
||||
// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
|
||||
if (
|
||||
this.mode === 'multiple' ||
|
||||
this.mode === 'single' ||
|
||||
(this.mode === 'range' && this.selected.length >= 2)
|
||||
) {
|
||||
if( scene === 'init'){
|
||||
return
|
||||
}
|
||||
if( scene === 'tap') {
|
||||
this.$emit('confirm', this.selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
init() {
|
||||
// 校验maxDate,不能小于minDate。
|
||||
if (
|
||||
this.innerMaxDate &&
|
||||
this.innerMinDate &&
|
||||
new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
|
||||
) {
|
||||
return error('maxDate不能小于minDate时间')
|
||||
}
|
||||
// 滚动区域的高度
|
||||
let bottomPadding = 0;
|
||||
if (this.pageInline) {
|
||||
bottomPadding = 0
|
||||
} else {
|
||||
bottomPadding = 30
|
||||
}
|
||||
this.listHeight = this.rowHeight * 5 + bottomPadding
|
||||
this.setMonth()
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击确定按钮
|
||||
confirm() {
|
||||
if (!this.buttonDisabled) {
|
||||
this.$emit('confirm', this.selected)
|
||||
}
|
||||
},
|
||||
// 获得两个日期之间的月份数
|
||||
getMonths(minDate, maxDate) {
|
||||
const minYear = dayjs(minDate).year()
|
||||
const minMonth = dayjs(minDate).month() + 1
|
||||
const maxYear = dayjs(maxDate).year()
|
||||
const maxMonth = dayjs(maxDate).month() + 1
|
||||
return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
|
||||
},
|
||||
// 设置月份数据
|
||||
setMonth() {
|
||||
// 最小日期的毫秒数
|
||||
const minDate = this.innerMinDate || dayjs().valueOf()
|
||||
// 如果没有指定最大日期,则往后推3个月
|
||||
const maxDate =
|
||||
this.innerMaxDate ||
|
||||
dayjs(minDate)
|
||||
.add(this.monthNum - 1, 'month')
|
||||
.valueOf()
|
||||
// 最大最小月份之间的共有多少个月份,
|
||||
const months = range(
|
||||
1,
|
||||
this.monthNum,
|
||||
this.getMonths(minDate, maxDate)
|
||||
)
|
||||
// 先清空数组
|
||||
this.months = []
|
||||
for (let i = 0; i < months; i++) {
|
||||
this.months.push({
|
||||
date: new Array(
|
||||
dayjs(minDate).add(i, 'month').daysInMonth()
|
||||
)
|
||||
.fill(1)
|
||||
.map((item, index) => {
|
||||
// 日期,取值1-31
|
||||
let day = index + 1
|
||||
// 星期,0-6,0为周日
|
||||
const week = dayjs(minDate)
|
||||
.add(i, 'month')
|
||||
.date(day)
|
||||
.day()
|
||||
const date = dayjs(minDate)
|
||||
.add(i, 'month')
|
||||
.date(day)
|
||||
.format('YYYY-MM-DD')
|
||||
let bottomInfo = ''
|
||||
if (this.showLunar) {
|
||||
// 将日期转为农历格式
|
||||
const lunar = Calendar.solar2lunar(
|
||||
dayjs(date).year(),
|
||||
dayjs(date).month() + 1,
|
||||
dayjs(date).date()
|
||||
)
|
||||
bottomInfo = lunar.IDayCn
|
||||
}
|
||||
let config = {
|
||||
day,
|
||||
week,
|
||||
// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
|
||||
disabled:
|
||||
dayjs(date).isBefore(
|
||||
dayjs(minDate).format('YYYY-MM-DD')
|
||||
) ||
|
||||
dayjs(date).isAfter(
|
||||
dayjs(maxDate).format('YYYY-MM-DD')
|
||||
),
|
||||
// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
|
||||
date: new Date(date),
|
||||
bottomInfo,
|
||||
dot: false,
|
||||
month:
|
||||
dayjs(minDate).add(i, 'month').month() + 1
|
||||
}
|
||||
const formatter =
|
||||
this.formatter || this.innerFormatter
|
||||
return formatter(config)
|
||||
}),
|
||||
// 当前所属的月份
|
||||
month: dayjs(minDate).add(i, 'month').month() + 1,
|
||||
// 当前年份
|
||||
year: dayjs(minDate).add(i, 'month').year()
|
||||
})
|
||||
}
|
||||
},
|
||||
// 滚动到默认设置的月份
|
||||
scrollIntoDefaultMonth(selected) {
|
||||
// 查询默认日期在可选列表的下标
|
||||
const _index = this.months.findIndex(({
|
||||
year,
|
||||
month
|
||||
}) => {
|
||||
month = padZero(month)
|
||||
return `${year}-${month}` === selected
|
||||
})
|
||||
if (_index !== -1) {
|
||||
// #ifndef MP-WEIXIN
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView = `month-${_index}`
|
||||
this.scrollIntoViewScroll = this.scrollIntoView
|
||||
})
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
this.scrollTop = this.months[_index].top || 0;
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
// scroll-view滚动监听
|
||||
onScroll(event) {
|
||||
// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
|
||||
const scrollTop = Math.max(0, event.detail.scrollTop)
|
||||
// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
|
||||
for (let i = 0; i < this.months.length; i++) {
|
||||
if (scrollTop >= (this.months[i].top || this.listHeight)) {
|
||||
this.monthIndex = i
|
||||
this.scrollIntoViewScroll = `month-${i}`
|
||||
}
|
||||
}
|
||||
},
|
||||
// 更新月份的top值
|
||||
updateMonthTop(topArr = []) {
|
||||
// 设置对应月份的top值,用于onScroll方法更新月份
|
||||
topArr.map((item, index) => {
|
||||
this.months[index].top = item
|
||||
})
|
||||
|
||||
// 获取默认日期的下标
|
||||
if (!this.defaultDate) {
|
||||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
|
||||
const selected = dayjs().format("YYYY-MM")
|
||||
this.scrollIntoDefaultMonth(selected)
|
||||
return
|
||||
}
|
||||
let selected = dayjs().format("YYYY-MM");
|
||||
// 单选模式,可以是字符串或数组,Date对象等
|
||||
if (!test.array(this.defaultDate)) {
|
||||
selected = dayjs(this.defaultDate).format("YYYY-MM")
|
||||
} else {
|
||||
selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
|
||||
}
|
||||
this.scrollIntoDefaultMonth(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-calendar {
|
||||
&__confirm {
|
||||
padding: 7px 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
86
uni_modules/uview-plus/components/u-calendar/util.js
Normal file
86
uni_modules/uview-plus/components/u-calendar/util.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
|
||||
export default {
|
||||
methods: {
|
||||
// 设置月份数据
|
||||
setMonth() {
|
||||
// 月初是周几
|
||||
const day = dayjs(this.date).date(1).day()
|
||||
const start = day == 0 ? 6 : day - 1
|
||||
|
||||
// 本月天数
|
||||
const days = dayjs(this.date).endOf('month').format('D')
|
||||
|
||||
// 上个月天数
|
||||
const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
|
||||
|
||||
// 日期数据
|
||||
const arr = []
|
||||
// 清空表格
|
||||
this.month = []
|
||||
|
||||
// 添加上月数据
|
||||
arr.push(
|
||||
...new Array(start).fill(1).map((e, i) => {
|
||||
const day = prevDays - start + i + 1
|
||||
|
||||
return {
|
||||
value: day,
|
||||
disabled: true,
|
||||
date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 添加本月数据
|
||||
arr.push(
|
||||
...new Array(days - 0).fill(1).map((e, i) => {
|
||||
const day = i + 1
|
||||
|
||||
return {
|
||||
value: day,
|
||||
date: dayjs(this.date).date(day).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 添加下个月
|
||||
arr.push(
|
||||
...new Array(42 - days - start).fill(1).map((e, i) => {
|
||||
const day = i + 1
|
||||
|
||||
return {
|
||||
value: day,
|
||||
disabled: true,
|
||||
date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 分割数组
|
||||
for (let n = 0; n < arr.length; n += 7) {
|
||||
this.month.push(
|
||||
arr.slice(n, n + 7).map((e, i) => {
|
||||
e.index = i + n
|
||||
|
||||
// 自定义信息
|
||||
const custom = this.customList.find((c) => c.date == e.date)
|
||||
|
||||
// 农历
|
||||
if (this.lunar) {
|
||||
const {
|
||||
IDayCn,
|
||||
IMonthCn
|
||||
} = this.getLunar(e.date)
|
||||
e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
|
||||
}
|
||||
|
||||
return {
|
||||
...e,
|
||||
...custom
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/carKeyboard.js
|
||||
*/
|
||||
export default {
|
||||
// 车牌号键盘
|
||||
carKeyboard: {
|
||||
random: false
|
||||
}
|
||||
}
|
||||
17
uni_modules/uview-plus/components/u-car-keyboard/props.js
Normal file
17
uni_modules/uview-plus/components/u-car-keyboard/props.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 是否打乱键盘按键的顺序
|
||||
random: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入一个中文后,是否自动切换到英文
|
||||
autoChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-keyboard"
|
||||
@touchmove.stop.prevent="noop"
|
||||
>
|
||||
<view
|
||||
v-for="(group, i) in abc ? engKeyBoardList : areaList"
|
||||
:key="i"
|
||||
class="u-keyboard__button"
|
||||
:index="i"
|
||||
:class="[i + 1 === 4 && 'u-keyboard__button--center']"
|
||||
>
|
||||
<view
|
||||
v-if="i === 3"
|
||||
class="u-keyboard__button__inner-wrapper"
|
||||
>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper__left"
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="200"
|
||||
@tap="changeCarInputMode"
|
||||
>
|
||||
<text
|
||||
class="u-keyboard__button__inner-wrapper__left__lang"
|
||||
:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
|
||||
>中</text>
|
||||
<text class="u-keyboard__button__inner-wrapper__left__line">/</text>
|
||||
<text
|
||||
class="u-keyboard__button__inner-wrapper__left__lang"
|
||||
:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
|
||||
>英</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper"
|
||||
v-for="(item, j) in group"
|
||||
:key="j"
|
||||
>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper__inner"
|
||||
:hover-stay-time="200"
|
||||
@tap="carInputClick(i, j)"
|
||||
hover-class="u-hover-class"
|
||||
>
|
||||
<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="i === 3"
|
||||
@touchstart="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
class="u-keyboard__button__inner-wrapper"
|
||||
>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper__right"
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="200"
|
||||
>
|
||||
<up-icon
|
||||
size="28"
|
||||
name="backspace"
|
||||
color="#303133"
|
||||
></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { randomArray, sleep } from '../../libs/function/index';
|
||||
/**
|
||||
* keyboard 键盘组件
|
||||
* @description 此为uview-plus自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/keyboard.html
|
||||
* @property {Boolean} random 是否打乱键盘的顺序
|
||||
* @event {Function} change 点击键盘触发
|
||||
* @event {Function} backspace 点击退格键触发
|
||||
* @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
|
||||
*/
|
||||
export default {
|
||||
name: "u-car-keyboard",
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
|
||||
abc: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'挂',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学'
|
||||
];
|
||||
let tmp = [];
|
||||
// 打乱顺序
|
||||
if (this.random) data = randomArray(data);
|
||||
// 切割成二维数组
|
||||
tmp[0] = data.slice(0, 10);
|
||||
tmp[1] = data.slice(10, 20);
|
||||
tmp[2] = data.slice(20, 30);
|
||||
tmp[3] = data.slice(30, 36);
|
||||
return tmp;
|
||||
},
|
||||
engKeyBoardList() {
|
||||
let data = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
];
|
||||
let tmp = [];
|
||||
if (this.random) data = randomArray(data);
|
||||
tmp[0] = data.slice(0, 10);
|
||||
tmp[1] = data.slice(10, 20);
|
||||
tmp[2] = data.slice(20, 30);
|
||||
tmp[3] = data.slice(30, 36);
|
||||
return tmp;
|
||||
}
|
||||
},
|
||||
emits: ["change", "backspace"],
|
||||
methods: {
|
||||
// 点击键盘按钮
|
||||
carInputClick(i, j) {
|
||||
let value = '';
|
||||
// 不同模式,获取不同数组的值
|
||||
if (this.abc) value = this.engKeyBoardList[i][j];
|
||||
else value = this.areaList[i][j];
|
||||
// 如果允许自动切换,则将中文状态切换为英文
|
||||
if (!this.abc && this.autoChange) sleep(200).then(() => this.abc = true)
|
||||
this.$emit('change', value);
|
||||
},
|
||||
// 修改汽车牌键盘的输入模式,中文|英文
|
||||
changeCarInputMode() {
|
||||
this.abc = !this.abc;
|
||||
},
|
||||
// 点击退格键
|
||||
backspaceClick() {
|
||||
this.$emit('backspace');
|
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
|
||||
this.timer = null;
|
||||
this.timer = setInterval(() => {
|
||||
this.$emit('backspace');
|
||||
}, 250);
|
||||
},
|
||||
clearTimer() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
|
||||
$u-car-keyboard-padding:6px 0 6px !default;
|
||||
$u-car-keyboard-button-inner-width:64rpx !default;
|
||||
$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
|
||||
$u-car-keyboard-button-height:80rpx !default;
|
||||
$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
|
||||
$u-car-keyboard-button-border-radius:4px !default;
|
||||
$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
|
||||
$u-car-keyboard-button-text-font-size:16px !default;
|
||||
$u-car-keyboard-button-text-color:$u-main-color !default;
|
||||
$u-car-keyboard-center-inner-margin: 0 4rpx !default;
|
||||
$u-car-keyboard-special-button-width:134rpx !default;
|
||||
$u-car-keyboard-lang-font-size:16px !default;
|
||||
$u-car-keyboard-lang-color:$u-main-color !default;
|
||||
$u-car-keyboard-active-color:$u-primary !default;
|
||||
$u-car-keyboard-line-font-size:15px !default;
|
||||
$u-car-keyboard-line-color:$u-main-color !default;
|
||||
$u-car-keyboard-line-margin:0 1px !default;
|
||||
$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
|
||||
|
||||
.u-keyboard {
|
||||
@include flex(column);
|
||||
justify-content: space-around;
|
||||
background-color: $u-car-keyboard-background-color;
|
||||
align-items: stretch;
|
||||
padding: $u-car-keyboard-padding;
|
||||
|
||||
&__button {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
|
||||
&__inner-wrapper {
|
||||
box-shadow: $u-car-keyboard-button-inner-box-shadow;
|
||||
margin: $u-car-keyboard-button-inner-margin;
|
||||
border-radius: $u-car-keyboard-button-border-radius;
|
||||
|
||||
&__inner {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: $u-car-keyboard-button-inner-width;
|
||||
background-color: $u-car-keyboard-button-inner-background-color;
|
||||
height: $u-car-keyboard-button-height;
|
||||
border-radius: $u-car-keyboard-button-border-radius;
|
||||
|
||||
&__text {
|
||||
font-size: $u-car-keyboard-button-text-font-size;
|
||||
color: $u-car-keyboard-button-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__left,
|
||||
&__right {
|
||||
border-radius: $u-car-keyboard-button-border-radius;
|
||||
width: $u-car-keyboard-special-button-width;
|
||||
height: $u-car-keyboard-button-height;
|
||||
background-color: $u-car-keyboard-u-hover-class-background-color;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: $u-car-keyboard-button-inner-box-shadow;
|
||||
}
|
||||
|
||||
&__left {
|
||||
&__line {
|
||||
font-size: $u-car-keyboard-line-font-size;
|
||||
color: $u-car-keyboard-line-color;
|
||||
margin: $u-car-keyboard-line-margin;
|
||||
}
|
||||
|
||||
&__lang {
|
||||
font-size: $u-car-keyboard-lang-font-size;
|
||||
color: $u-car-keyboard-lang-color;
|
||||
|
||||
&--active {
|
||||
color: $u-car-keyboard-active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-hover-class {
|
||||
background-color: $u-car-keyboard-u-hover-class-background-color;
|
||||
}
|
||||
</style>
|
||||
40
uni_modules/uview-plus/components/u-card/card.js
Normal file
40
uni_modules/uview-plus/components/u-card/card.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* @Author : jry
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2025-04-26 16:37:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-04-26 16:37:21
|
||||
* @FilePath : /uview-plus/libs/config/props/card.js
|
||||
*/
|
||||
export default {
|
||||
// card组件的props
|
||||
card: {
|
||||
full: false,
|
||||
title: '',
|
||||
titleColor: '#303133',
|
||||
titleSize: '15px',
|
||||
subTitle: '',
|
||||
subTitleColor: '#909399',
|
||||
subTitleSize: '13px',
|
||||
border: true,
|
||||
index: '',
|
||||
margin: '15px',
|
||||
borderRadius: '8px',
|
||||
headStyle: {},
|
||||
bodyStyle: {},
|
||||
footStyle: {},
|
||||
headBorderBottom: true,
|
||||
footBorderTop: true,
|
||||
thumb: '',
|
||||
thumbWidth: '30px',
|
||||
thumbCircle: false,
|
||||
padding: '15px',
|
||||
paddingHead: '',
|
||||
paddingBody: '',
|
||||
paddingFoot: '',
|
||||
showHead: true,
|
||||
showFoot: true,
|
||||
boxShadow: 'none'
|
||||
}
|
||||
}
|
||||
134
uni_modules/uview-plus/components/u-card/props.js
Normal file
134
uni_modules/uview-plus/components/u-card/props.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const propsCard = defineMixin({
|
||||
props: {
|
||||
// 与屏幕两侧是否留空隙
|
||||
full: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.full
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.card.title
|
||||
},
|
||||
// 标题颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: () => defProps.card.titleColor
|
||||
},
|
||||
// 标题字体大小
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.card.titleSize
|
||||
},
|
||||
// 副标题
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: () => defProps.card.subTitle
|
||||
},
|
||||
// 副标题颜色
|
||||
subTitleColor: {
|
||||
type: String,
|
||||
default: () => defProps.card.subTitleColor
|
||||
},
|
||||
// 副标题字体大小
|
||||
subTitleSize: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.card.subTitleSize
|
||||
},
|
||||
// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.border
|
||||
},
|
||||
// 用于标识点击了第几个
|
||||
index: {
|
||||
type: [Number, String, Object],
|
||||
default: () => defProps.card.index
|
||||
},
|
||||
// 用于隔开上下左右的边距,带单位的写法,如:"30px 30px","20px 20px 30px 30px"
|
||||
margin: {
|
||||
type: String,
|
||||
default: () => defProps.card.margin
|
||||
},
|
||||
// card卡片的圆角
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.card.borderRadius
|
||||
},
|
||||
// 头部自定义样式,对象形式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default: () => defProps.card.headStyle
|
||||
},
|
||||
// 主体自定义样式,对象形式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default: () => defProps.card.bodyStyle
|
||||
},
|
||||
// 底部自定义样式,对象形式
|
||||
footStyle: {
|
||||
type: Object,
|
||||
default: () => defProps.card.footStyle
|
||||
},
|
||||
// 头部是否下边框
|
||||
headBorderBottom: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.headBorderBottom
|
||||
},
|
||||
// 底部是否有上边框
|
||||
footBorderTop: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.footBorderTop
|
||||
},
|
||||
// 标题左边的缩略图
|
||||
thumb: {
|
||||
type: String,
|
||||
default: () => defProps.card.thumb
|
||||
},
|
||||
// 缩略图宽高
|
||||
thumbWidth: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.card.thumbWidth
|
||||
},
|
||||
// 缩略图是否为圆形
|
||||
thumbCircle: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.thumbCircle
|
||||
},
|
||||
// 给head,body,foot的内边距
|
||||
padding: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.card.padding
|
||||
},
|
||||
paddingHead: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.card.paddingHead
|
||||
},
|
||||
paddingBody: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.card.paddingBody
|
||||
},
|
||||
paddingFoot: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.card.paddingFoot
|
||||
},
|
||||
// 是否显示头部
|
||||
showHead: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.showHead
|
||||
},
|
||||
// 是否显示尾部
|
||||
showFoot: {
|
||||
type: Boolean,
|
||||
default: () => defProps.card.showFoot
|
||||
},
|
||||
// 卡片外围阴影,字符串形式
|
||||
boxShadow: {
|
||||
type: String,
|
||||
default: () => defProps.card.boxShadow
|
||||
}
|
||||
}
|
||||
})
|
||||
184
uni_modules/uview-plus/components/u-card/u-card.vue
Normal file
184
uni_modules/uview-plus/components/u-card/u-card.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-card"
|
||||
@tap.stop="click"
|
||||
:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': getPx(borderRadius) > 0 }"
|
||||
:style="{
|
||||
borderRadius: addUnit(borderRadius),
|
||||
margin: margin,
|
||||
boxShadow: boxShadow
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-if="showHead"
|
||||
class="u-card__head"
|
||||
:style="[{padding: addUnit(paddingHead || padding)}, headStyle]"
|
||||
:class="{
|
||||
'u-border-bottom': headBorderBottom
|
||||
}"
|
||||
@tap="headClick"
|
||||
>
|
||||
<view v-if="!$slots.head" class="u-flex u-flex-between">
|
||||
<view class="u-card__head--left u-flex u-line-1" v-if="title">
|
||||
<image
|
||||
:src="thumb"
|
||||
class="u-card__head--left__thumb"
|
||||
mode="aspectFill"
|
||||
v-if="thumb"
|
||||
:style="{
|
||||
height: addUnit(thumbWidth),
|
||||
width: addUnit(thumbWidth),
|
||||
borderRadius: thumbCircle ? '50px' : '4px'
|
||||
}"
|
||||
></image>
|
||||
<text
|
||||
class="u-card__head--left__title u-line-1"
|
||||
:style="{
|
||||
fontSize: addUnit(titleSize),
|
||||
color: titleColor
|
||||
}"
|
||||
>
|
||||
{{ title }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="u-card__head--right u-line-1" v-if="subTitle">
|
||||
<text
|
||||
class="u-card__head__title__text"
|
||||
:style="{
|
||||
fontSize: addUnit(subTitleSize),
|
||||
color: subTitleColor
|
||||
}"
|
||||
>
|
||||
{{ subTitle }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<slot name="head" v-else />
|
||||
</view>
|
||||
<view @tap="bodyClick" class="u-card__body"
|
||||
:style="[{padding: addUnit(paddingBody || padding)}, bodyStyle]"><slot name="body" /></view>
|
||||
<view
|
||||
v-if="showFoot"
|
||||
class="u-card__foot"
|
||||
@tap="footClick"
|
||||
:style="[{padding: $slots.foot ? addUnit(paddingFoot || padding) : 0}, footStyle]"
|
||||
:class="{
|
||||
'u-border-top': footBorderTop
|
||||
}"
|
||||
>
|
||||
<slot name="foot" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { propsCard } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle, addUnit, getPx } from '../../libs/function/index';
|
||||
/**
|
||||
* card 卡片
|
||||
* @description 卡片组件一般用于多个列表条目,且风格统一的场景
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/card.html
|
||||
* @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false)
|
||||
* @property {String} title 头部左边的标题
|
||||
* @property {String} title-color 标题颜色(默认#303133)
|
||||
* @property {String | Number} title-size 标题字体大小,单位rpx(默认15px)
|
||||
* @property {String} sub-title 头部右边的副标题
|
||||
* @property {String} sub-title-color 副标题颜色(默认#909399)
|
||||
* @property {String | Number} sub-title-size 副标题字体大小(默认13px
|
||||
* @property {Boolean} border 是否显示边框(默认true)
|
||||
* @property {String | Number} index 用于标识点击了第几个卡片
|
||||
* @property {String} box-shadow 卡片外围阴影,字符串形式(默认none)
|
||||
* @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30px 20px"(默认15px)
|
||||
* @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认8px)
|
||||
* @property {Object} head-style 头部自定义样式,对象形式
|
||||
* @property {Object} body-style 中部自定义样式,对象形式
|
||||
* @property {Object} foot-style 底部自定义样式,对象形式
|
||||
* @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true)
|
||||
* @property {Boolean} foot-border-top 是否显示底部的上边框(默认true)
|
||||
* @property {Boolean} show-head 是否显示头部(默认true)
|
||||
* @property {Boolean} show-foot 是否显示尾部(默认true)
|
||||
* @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径
|
||||
* @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位px(默认30px)
|
||||
* @property {Boolean} thumb-circle 缩略图是否为圆形(默认false)
|
||||
* @event {Function} click 整个卡片任意位置被点击时触发
|
||||
* @event {Function} head-click 卡片头部被点击时触发
|
||||
* @event {Function} body-click 卡片主体部分被点击时触发
|
||||
* @event {Function} foot-click 卡片底部部分被点击时触发
|
||||
* @example <u-card paddingFoot="2px 15px" title="card"></u-card>
|
||||
*/
|
||||
export default {
|
||||
name: 'up-card',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mixins: [mpMixin, mixin, propsCard],
|
||||
emits: ['click', 'head-click', 'body-click', 'foot-click'],
|
||||
methods: {
|
||||
addStyle,
|
||||
addUnit,
|
||||
getPx,
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
},
|
||||
headClick() {
|
||||
this.$emit('head-click', this.index);
|
||||
},
|
||||
bodyClick() {
|
||||
this.$emit('body-click', this.index);
|
||||
},
|
||||
footClick() {
|
||||
this.$emit('foot-click', this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-full {
|
||||
// 如果是与屏幕之间不留空隙,应该设置左右边距为0
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--border:after {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
&__head {
|
||||
&--left {
|
||||
color: $u-main-color;
|
||||
|
||||
&__thumb {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
&__title {
|
||||
max-width: 400rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
color: $u-tips-color;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__foot {
|
||||
color: $u-tips-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
335
uni_modules/uview-plus/components/u-cascader/u-cascader.vue
Normal file
335
uni_modules/uview-plus/components/u-cascader/u-cascader.vue
Normal file
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<up-popup :show="popupShow" mode="bottom" :popup="false"
|
||||
:mask="true" :closeable="true" :safe-area-inset-bottom="true"
|
||||
close-icon-color="#ffffff" :z-index="uZIndex"
|
||||
:maskCloseAble="maskCloseAble" @close="close">
|
||||
<view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection =='column'">
|
||||
<up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex">
|
||||
<up-steps-item v-for="(item, index) in genTabsList"
|
||||
@click="tabsIndex = index" :title="item.name"></up-steps-item>
|
||||
</up-steps>
|
||||
</view>
|
||||
<view class="up-p-t-20 up-m-b-10" v-else>
|
||||
<up-tabs v-if="popupShow" :list="genTabsList"
|
||||
:scrollable="true" v-model:current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs>
|
||||
</view>
|
||||
<view class="area-box">
|
||||
<view class="u-flex" :class="{ 'change':isChange }"
|
||||
:style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}">
|
||||
<template v-for="(levelData, levelIndex) in levelList" :key="levelIndex">
|
||||
<view v-if="optionsCols == 2 || levelIndex == tabsIndex" class="area-item"
|
||||
:style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}">
|
||||
<view class="u-padding-10 u-bg-gray" style="height: 100%;">
|
||||
<scroll-view :scroll-y="true" style="height: 100%">
|
||||
<up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined">
|
||||
<up-cell v-for="(item,index) in levelData"
|
||||
:title="item[labelKey]" :arrow="false"
|
||||
:index="index" :key="index"
|
||||
@click="levelChange(levelIndex, index)">
|
||||
<template v-slot:right-icon>
|
||||
<up-icon v-if="selectedValueIndexs[levelIndex] === index"
|
||||
size="17" name="checkbox-mark"></up-icon>
|
||||
</template>
|
||||
</up-cell>
|
||||
</up-cell-group>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 添加按钮区域 -->
|
||||
<view class="u-cascader-action up-flex up-flex-between">
|
||||
<view class="u-padding-20 up-flex-fill">
|
||||
<up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button>
|
||||
</view>
|
||||
<view class="u-padding-20 up-flex-fill">
|
||||
<up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* u-cascader 通用无限级联选择器
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认1075)
|
||||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
|
||||
* @property {Array} data 级联数据
|
||||
* @property {Array} default-value 默认选中的值
|
||||
* @property {String} valueKey 指定选项的值为选项对象中的哪个属性值
|
||||
* @property {String} labelKey 指定选项标签为选项对象中的哪个属性值
|
||||
* @property {String} childrenKey 指定选项的子选项为选项对象中的哪个属性值
|
||||
* @property {Boolean} autoClose 是否在选择最后一级时自动关闭并触发confirm(默认false)
|
||||
*/
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
name: 'up-cascader',
|
||||
props: {
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 级联数据
|
||||
data: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 默认选中的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 指定选项的值为选项对象中的哪个属性值
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// 指定选项标签为选项对象中的哪个属性值
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
// 指定选项的子选项为选项对象中的哪个属性值
|
||||
childrenKey: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭Picker
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否在选择最后一级时自动关闭并触发confirm
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选中项目的展示方向direction垂直方向适合文字长度过长
|
||||
headerDirection: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 选项区域列数,支持1列和2列,默认为2列
|
||||
optionsCols: {
|
||||
type: [Number],
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 存储每一级的数据
|
||||
levelList: [],
|
||||
// 存储每一级选中的索引
|
||||
selectedValueIndexs: [],
|
||||
tabsIndex: 0,
|
||||
popupShow: false,
|
||||
// 新增confirmValues用于存储确认的值
|
||||
confirmValues: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
handler() {
|
||||
this.initLevelList();
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
show() {
|
||||
this.popupShow = this.show;
|
||||
},
|
||||
modelValue: {
|
||||
handler() {
|
||||
this.init();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isChange() {
|
||||
return this.tabsIndex > 1;
|
||||
},
|
||||
genTabsList() {
|
||||
let tabsList = [{
|
||||
name: "请选择"
|
||||
}];
|
||||
|
||||
// 根据选中的值动态生成tabs
|
||||
for (let i = 0; i < this.selectedValueIndexs.length; i++) {
|
||||
if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
|
||||
const selectedItem = this.levelList[i][this.selectedValueIndexs[i]];
|
||||
if (selectedItem) {
|
||||
tabsList[i] = {
|
||||
name: selectedItem[this.labelKey]
|
||||
};
|
||||
// 如果还有下一级,则添加"请选择"
|
||||
if (i === this.selectedValueIndexs.length - 1 &&
|
||||
selectedItem[this.childrenKey] &&
|
||||
selectedItem[this.childrenKey].length > 0) {
|
||||
tabsList.push({
|
||||
name: "请选择"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tabsList;
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
// 新增confirm事件
|
||||
emits: ['update:modelValue', 'change', 'confirm'],
|
||||
methods: {
|
||||
t,
|
||||
init() {
|
||||
// 初始化选中值
|
||||
if (this.modelValue && this.modelValue.length > 0) {
|
||||
this.setDefaultValue();
|
||||
}
|
||||
},
|
||||
initLevelList() {
|
||||
// 初始化第一级数据
|
||||
if (this.data && this.data.length > 0) {
|
||||
this.levelList = [this.data];
|
||||
this.selectedValueIndexs = [];
|
||||
}
|
||||
},
|
||||
setDefaultValue() {
|
||||
// 根据默认值设置选中项
|
||||
// 根据modelValue获取indexs给selectedValueIndexs
|
||||
this.selectedValueIndexs = [];
|
||||
let currentLevelData = this.data;
|
||||
|
||||
for (let i = 0; i < this.modelValue.length; i++) {
|
||||
const value = this.modelValue[i];
|
||||
const index = currentLevelData.findIndex(item => item[this.valueKey] === value);
|
||||
|
||||
if (index !== -1) {
|
||||
this.selectedValueIndexs.push(index);
|
||||
// 更新下一级的数据
|
||||
if (currentLevelData[index][this.childrenKey]) {
|
||||
currentLevelData = currentLevelData[index][this.childrenKey];
|
||||
} else {
|
||||
// 如果没有子级数据,则停止处理
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 如果找不到匹配项,则停止处理
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
tabsChange(item) {
|
||||
},
|
||||
levelChange(levelIndex, index) {
|
||||
// 设置当前级的选中值
|
||||
this.$set(this.selectedValueIndexs, levelIndex, index);
|
||||
|
||||
// 清除后续级别的选中值
|
||||
this.selectedValueIndexs.splice(levelIndex + 1);
|
||||
this.tabsIndex = Math.min(this.tabsIndex, levelIndex);
|
||||
|
||||
// 清除后续级别的列表
|
||||
this.levelList.splice(levelIndex + 1);
|
||||
|
||||
// 获取当前选中项
|
||||
const currentItem = this.levelList[levelIndex][index];
|
||||
|
||||
// 如果有子级数据,则初始化下一级
|
||||
if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) {
|
||||
// 确保levelList数组足够长
|
||||
if (this.levelList.length <= levelIndex + 1) {
|
||||
this.levelList.push(currentItem[this.childrenKey]);
|
||||
} else {
|
||||
this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]);
|
||||
}
|
||||
// 切换到下一级tab
|
||||
this.tabsIndex = levelIndex + 1;
|
||||
} else {
|
||||
// 没有子级数据,说明是最后一级
|
||||
if (this.autoClose) {
|
||||
// 如果启用自动关闭,则触发change事件并关闭
|
||||
this.emitChange();
|
||||
} else {
|
||||
// 否则只触发change事件,不关闭
|
||||
this.emitChange(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 修改emitChange方法,增加closePopup参数
|
||||
emitChange(closePopup = true) {
|
||||
// 构造选中结果
|
||||
const result = [];
|
||||
for (let i = 0; i < this.selectedValueIndexs.length; i++) {
|
||||
if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
|
||||
result.push(this.levelList[i][this.selectedValueIndexs[i]][this.valueKey]);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新confirmValues
|
||||
this.confirmValues = [...result];
|
||||
|
||||
// 触发change事件,返回value数组
|
||||
this.$emit('change', this.confirmValues);
|
||||
|
||||
// 根据参数决定是否关闭弹窗
|
||||
if (closePopup) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.close();
|
||||
},
|
||||
handleConfirm() {
|
||||
// 确认时触发confirm事件
|
||||
this.$emit('update:modelValue', this.confirmValues);
|
||||
this.$emit('confirm', this.confirmValues);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.area-box {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 800rpx;
|
||||
|
||||
>view {
|
||||
width: 150%;
|
||||
transition: transform 0.3s ease-in-out 0s;
|
||||
transform: translateX(0);
|
||||
|
||||
&.change {
|
||||
// transform: translateX(-33.3333333%);
|
||||
}
|
||||
}
|
||||
|
||||
.area-item {
|
||||
// width: 750rpx;
|
||||
height: 800rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加按钮区域样式
|
||||
.u-cascader-action {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
381
uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue
Normal file
381
uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue
Normal file
@@ -0,0 +1,381 @@
|
||||
<template>
|
||||
<view class="u-cate-tab" :style="{ height: addUnit(height) }">
|
||||
<view class="u-cate-tab__wrap">
|
||||
<scroll-view class="u-cate-tab__view u-cate-tab__menu-scroll-view"
|
||||
scroll-y scroll-with-animation :scroll-top="scrollTop"
|
||||
:scroll-into-view="itemId">
|
||||
<view v-for="(item, index) in tabList" :key="index" class="u-cate-tab__item"
|
||||
:class="[innerCurrent == index ? 'u-cate-tab__item-active' : '']"
|
||||
@tap.stop="swichMenu(index)">
|
||||
<slot name="tabItem" :item="item">
|
||||
</slot>
|
||||
<text v-if="!$slots['tabItem']" class="u-line-1">{{item[tabKeyName]}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view :scroll-top="scrollRightTop" scroll-with-animation :scroll-into-view="scrollIntoView"
|
||||
scroll-y class="u-cate-tab__right-box" @scroll="rightScroll">
|
||||
<view class="u-cate-tab__right-top">
|
||||
<slot name="rightTop" :tabList="tabList">
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-cate-tab__page-view">
|
||||
<template :key="index" v-for="(item , index) in tabList">
|
||||
<view v-if="mode == 'follow' || ( mode == 'tab' && index == innerCurrent)"
|
||||
class="u-cate-tab__page-item" :id="'item' + index">
|
||||
<slot name="itemList" :item="item">
|
||||
</slot>
|
||||
<template v-if="!$slots['itemList']">
|
||||
<view class="item-title">
|
||||
<text>{{item[tabKeyName]}}</text>
|
||||
</view>
|
||||
<view class="item-container">
|
||||
<template v-for="(item1, index1) in item.children" :key="index1">
|
||||
<slot name="pageItem" :pageItem="item1">
|
||||
<view class="thumb-box" >
|
||||
<image class="item-menu-image" :src="item1.icon" mode=""></image>
|
||||
<view class="item-menu-name">{{item1[itemKeyName]}}</view>
|
||||
</view>
|
||||
</slot>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { addUnit, sleep } from '../../libs/function/index';
|
||||
export default {
|
||||
name: 'up-cate-tab',
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'follow' // follo跟随联动, tab单一显示。
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
tabList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
tabKeyName: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
itemKeyName: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tabList: {
|
||||
deep: true,
|
||||
handler(newVal, oldVal) {
|
||||
// this.observer();
|
||||
sleep(30);
|
||||
this.getMenuItemTop();
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
}
|
||||
},
|
||||
current(nval) {
|
||||
this.innerCurrent = nval;
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
},
|
||||
height() {
|
||||
// console.log('height change');
|
||||
this.getMenuItemTop();
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
}
|
||||
},
|
||||
emits: ['update:current'],
|
||||
data() {
|
||||
return {
|
||||
scrollTop: 0, //tab标题的滚动条位置
|
||||
scrollIntoView: '', // 滚动至哪个元素
|
||||
oldScrollTop: 0,
|
||||
innerCurrent: 0, // 预设当前项的值
|
||||
menuHeight: 0, // 左边菜单的高度
|
||||
menuItemHeight: 0, // 左边菜单item的高度
|
||||
itemId: '', // 栏目右边scroll-view用于滚动的id
|
||||
menuItemPos: [],
|
||||
rects: [],
|
||||
arr: [],
|
||||
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
|
||||
timer: null, // 定时器
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// this.observer();
|
||||
this.innerCurrent = this.current;
|
||||
this.leftMenuStatus(this.innerCurrent);
|
||||
this.getMenuItemTop()
|
||||
},
|
||||
methods: {
|
||||
addUnit,
|
||||
// 点击左边的栏目切换
|
||||
async swichMenu(index) {
|
||||
if (this.mode == 'follow') {
|
||||
if(this.arr.length == 0) {
|
||||
await this.getMenuItemTop();
|
||||
}
|
||||
this.scrollIntoView = 'item' + index;
|
||||
}
|
||||
|
||||
if (index == this.innerCurrent) return;
|
||||
this.$nextTick(function(){
|
||||
this.innerCurrent = index;
|
||||
this.$emit('update:current', index);
|
||||
})
|
||||
},
|
||||
// 获取一个目标元素的高度
|
||||
getElRect(elClass, dataVal) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('.' + elClass).fields({
|
||||
size: true
|
||||
}, res => {
|
||||
// 如果节点尚未生成,res值为null,循环调用执行
|
||||
if (!res) {
|
||||
setTimeout(() => {
|
||||
this.getElRect(elClass);
|
||||
}, 10);
|
||||
return;
|
||||
}
|
||||
this[dataVal] = res.height;
|
||||
resolve();
|
||||
}).exec();
|
||||
})
|
||||
},
|
||||
// 观测元素相交状态
|
||||
async observer() {
|
||||
await this.$nextTick();
|
||||
// 清除之前的观察器
|
||||
if (this._observerList) {
|
||||
this._observerList.forEach(observer => {
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
this._observerList = [];
|
||||
|
||||
this.tabList.map((val, index) => {
|
||||
let observer = uni.createIntersectionObserver(this);
|
||||
this._observerList.push(observer);
|
||||
// 检测相交状态
|
||||
observer.relativeTo('.u-cate-tab__right-box', {
|
||||
top: 10
|
||||
}).observe('#item' + index, (res) => {
|
||||
if (res.intersectionRatio > 0) {
|
||||
console.log('res', res);
|
||||
// 修复:确保正确获取索引
|
||||
let id = res.id ? res.id.substring(4) : index;
|
||||
this.leftMenuStatus(parseInt(id));
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 设置左边菜单的滚动状态
|
||||
async leftMenuStatus(index) {
|
||||
this.innerCurrent = index;
|
||||
this.$emit('update:current', index);
|
||||
// 如果为0,意味着尚未初始化
|
||||
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
|
||||
await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
|
||||
await this.getElRect('u-cate-tab__item', 'menuItemHeight');
|
||||
}
|
||||
// console.log(this.menuHeight, this.menuItemHeight)
|
||||
// 将菜单活动item垂直居中
|
||||
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
|
||||
},
|
||||
// 获取右边菜单每个item到顶部的距离
|
||||
async getMenuItemTop() {
|
||||
// await this.$nextTick();
|
||||
// console.log('getMenuItemTop')
|
||||
return new Promise(resolve => {
|
||||
let selectorQuery = uni.createSelectorQuery().in(this);
|
||||
selectorQuery.selectAll('.u-cate-tab__page-item').boundingClientRect((rects) => {
|
||||
// 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
|
||||
if(!rects.length) {
|
||||
setTimeout(() => {
|
||||
this.getMenuItemTop();
|
||||
}, 100);
|
||||
return ;
|
||||
}
|
||||
// console.log(rects)
|
||||
this.rects = rects;
|
||||
this.arr = [];
|
||||
rects.forEach((rect) => {
|
||||
// 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
|
||||
this.arr.push(rect.top - rects[0].top);
|
||||
})
|
||||
// console.log(this.arr)
|
||||
resolve();
|
||||
}).exec()
|
||||
})
|
||||
},
|
||||
// 右边菜单滚动
|
||||
async rightScroll(e) {
|
||||
if (this.mode !== 'follow') return;
|
||||
this.oldScrollTop = e.detail.scrollTop;
|
||||
// console.log(e.detail.scrollTop)
|
||||
// console.log(JSON.stringify(this.arr))
|
||||
if(this.arr.length == 0) {
|
||||
await this.getMenuItemTop();
|
||||
}
|
||||
if(this.timer) return ;
|
||||
if(!this.menuHeight) {
|
||||
await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight');
|
||||
}
|
||||
setTimeout(() => { // 节流
|
||||
this.timer = null;
|
||||
// scrollHeight为右边菜单垂直中点位置
|
||||
let scrollHeight = e.detail.scrollTop + 1;
|
||||
// console.log(e.detail.scrollTop)
|
||||
for (let i = 0; i < this.arr.length; i++) {
|
||||
let height1 = this.arr[i];
|
||||
let height2 = this.arr[i + 1];
|
||||
// console.log('i', i)
|
||||
// console.log('height1', height1)
|
||||
// console.log('height2', height2)
|
||||
// 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
|
||||
if (!height2 || scrollHeight >= height1 && scrollHeight <= height2) {
|
||||
// console.log('scrollHeight', scrollHeight)
|
||||
// console.log('height1', height1)
|
||||
// console.log('height2', height2)
|
||||
this.leftMenuStatus(i);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-cate-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.u-cate-tab__wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-search-inner {
|
||||
background-color: rgb(234, 234, 234);
|
||||
border-radius: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10rpx 16rpx;
|
||||
}
|
||||
|
||||
.u-search-text {
|
||||
font-size: 26rpx;
|
||||
color: $u-tips-color;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.u-cate-tab__view {
|
||||
width: 200rpx;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.u-cate-tab__item {
|
||||
height: 110rpx;
|
||||
background: #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
color: #444;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-cate-tab__item-active {
|
||||
position: relative;
|
||||
color: #000;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.u-cate-tab__item-active::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-left: 4px solid $u-primary;
|
||||
height: 32rpx;
|
||||
left: 0;
|
||||
top: 39rpx;
|
||||
}
|
||||
|
||||
.u-cate-tab__view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.u-cate-tab__right-box {
|
||||
flex: 1;
|
||||
background-color: rgb(250, 250, 250);
|
||||
}
|
||||
|
||||
.u-cate-tab__page-view {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.u-cate-tab__page-item {
|
||||
margin-bottom: 30rpx;
|
||||
background-color: #fff;
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.u-cate-tab__page-item:last-child {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 26rpx;
|
||||
color: $u-main-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-menu-name {
|
||||
font-weight: normal;
|
||||
font-size: 24rpx;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.item-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.thumb-box {
|
||||
width: 33.333333%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.item-menu-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
17
uni_modules/uview-plus/components/u-cell-group/cellGroup.js
Normal file
17
uni_modules/uview-plus/components/u-cell-group/cellGroup.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/cellGroup.js
|
||||
*/
|
||||
export default {
|
||||
// cell-group组件的props
|
||||
cellGroup: {
|
||||
title: '',
|
||||
border: true,
|
||||
customStyle: {}
|
||||
}
|
||||
}
|
||||
16
uni_modules/uview-plus/components/u-cell-group/props.js
Normal file
16
uni_modules/uview-plus/components/u-cell-group/props.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 分组标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.cellGroup.title
|
||||
},
|
||||
// 是否显示外边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cellGroup.border
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view :style="[addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
|
||||
<view v-if="title" class="u-cell-group__title">
|
||||
<slot name="title">
|
||||
<text class="u-cell-group__title__text">{{ title }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-cell-group__wrapper">
|
||||
<u-line v-if="border"></u-line>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle } from '../../libs/function/index';
|
||||
/**
|
||||
* cellGroup 单元格
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/cell.html
|
||||
*
|
||||
* @property {String} title 分组标题
|
||||
* @property {Boolean} border 是否显示外边框 (默认 true )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 点击cell列表时触发
|
||||
* @example <u-cell-group title="设置喜好">
|
||||
*/
|
||||
export default {
|
||||
name: 'u-cell-group',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
methods: {
|
||||
addStyle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$u-cell-group-title-padding: 16px 16px 8px !default;
|
||||
$u-cell-group-title-font-size: 15px !default;
|
||||
$u-cell-group-title-line-height: 16px !default;
|
||||
$u-cell-group-title-color: $u-main-color !default;
|
||||
|
||||
.u-cell-group {
|
||||
flex: 1;
|
||||
|
||||
&__title {
|
||||
padding: $u-cell-group-title-padding;
|
||||
|
||||
&__text {
|
||||
font-size: $u-cell-group-title-font-size;
|
||||
line-height: $u-cell-group-title-line-height;
|
||||
color: $u-cell-group-title-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
35
uni_modules/uview-plus/components/u-cell/cell.js
Normal file
35
uni_modules/uview-plus/components/u-cell/cell.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/cell.js
|
||||
*/
|
||||
export default {
|
||||
// cell组件的props
|
||||
cell: {
|
||||
customClass: '',
|
||||
title: '',
|
||||
label: '',
|
||||
value: '',
|
||||
icon: '',
|
||||
disabled: false,
|
||||
border: true,
|
||||
center: false,
|
||||
url: '',
|
||||
linkType: 'navigateTo',
|
||||
clickable: false,
|
||||
isLink: false,
|
||||
required: false,
|
||||
arrowDirection: '',
|
||||
iconStyle: {},
|
||||
rightIconStyle: {},
|
||||
rightIcon: 'arrow-right',
|
||||
titleStyle: {},
|
||||
size: '',
|
||||
stop: true,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
112
uni_modules/uview-plus/components/u-cell/props.js
Normal file
112
uni_modules/uview-plus/components/u-cell/props.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.cell.title
|
||||
},
|
||||
// 标题下方的描述信息
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.cell.label
|
||||
},
|
||||
// 右侧的内容
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.cell.value
|
||||
},
|
||||
// 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => defProps.cell.icon
|
||||
},
|
||||
// 是否禁用cell
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.disabled
|
||||
},
|
||||
// 是否显示下边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.border
|
||||
},
|
||||
// 内容是否垂直居中(主要是针对右侧的value部分)
|
||||
center: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.center
|
||||
},
|
||||
// 点击后跳转的URL地址
|
||||
url: {
|
||||
type: String,
|
||||
default: () => defProps.cell.url
|
||||
},
|
||||
// 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
|
||||
linkType: {
|
||||
type: String,
|
||||
default: () => defProps.cell.linkType
|
||||
},
|
||||
// 是否开启点击反馈(表现为点击时加上灰色背景)
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.clickable
|
||||
},
|
||||
// 是否展示右侧箭头并开启点击反馈
|
||||
isLink: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.isLink
|
||||
},
|
||||
// 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.required
|
||||
},
|
||||
// 右侧的图标箭头
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: () => defProps.cell.rightIcon
|
||||
},
|
||||
// 右侧箭头的方向,可选值为:left,up,down
|
||||
arrowDirection: {
|
||||
type: String,
|
||||
default: () => defProps.cell.arrowDirection
|
||||
},
|
||||
// 左侧图标样式
|
||||
iconStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.cell.iconStyle
|
||||
}
|
||||
},
|
||||
// 右侧箭头图标的样式
|
||||
rightIconStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.cell.rightIconStyle
|
||||
}
|
||||
},
|
||||
// 标题的样式
|
||||
titleStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.cell.titleStyle
|
||||
}
|
||||
},
|
||||
// 单位元的大小,可选值为large
|
||||
size: {
|
||||
type: String,
|
||||
default: () => defProps.cell.size
|
||||
},
|
||||
// 点击cell是否阻止事件传播
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: () => defProps.cell.stop
|
||||
},
|
||||
// 标识符,cell被点击时返回
|
||||
name: {
|
||||
type: [Number, String],
|
||||
default: () => defProps.cell.name
|
||||
}
|
||||
}
|
||||
})
|
||||
267
uni_modules/uview-plus/components/u-cell/u-cell.vue
Normal file
267
uni_modules/uview-plus/components/u-cell/u-cell.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<view class="u-cell" :class="[customClass]" :style="[addStyle(customStyle)]"
|
||||
:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
|
||||
@tap="clickHandler">
|
||||
<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
|
||||
<view class="u-cell__body__content">
|
||||
<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
|
||||
<slot name="icon" v-if="$slots.icon">
|
||||
</slot>
|
||||
<up-icon v-else :name="icon"
|
||||
:custom-style="iconStyle"
|
||||
:size="size === 'large' ? 22 : 18"></up-icon>
|
||||
</view>
|
||||
<view class="u-cell__title">
|
||||
<!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递,这样才能解决collapse组件的slot不失效问题,label暂时未用到。 -->
|
||||
<slot name="title" v-if="$slots.title || !title">
|
||||
</slot>
|
||||
<text v-else class="u-cell__title-text" :style="[titleTextStyle]"
|
||||
:class="[required && 'u-cell--required', disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
|
||||
<slot name="label">
|
||||
<text class="u-cell__label" v-if="label"
|
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<slot name="value">
|
||||
<text class="u-cell__value"
|
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
|
||||
v-if="!testEmpty(value)">{{ value }}</text>
|
||||
</slot>
|
||||
<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
|
||||
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
|
||||
<up-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon"
|
||||
:custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
|
||||
:size="size === 'large' ? 18 : 16"></up-icon>
|
||||
<slot v-else name="right-icon">
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-cell__right-icon-wrap" v-if="$slots['righticon']"
|
||||
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
|
||||
<slot name="righticon">
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<u-line v-if="border"></u-line>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
/**
|
||||
* cell 单元格
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/cell.html
|
||||
* @property {String | Number} title 标题
|
||||
* @property {String | Number} label 标题下方的描述信息
|
||||
* @property {String | Number} value 右侧的内容
|
||||
* @property {String} icon 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
|
||||
* @property {Boolean} disabled 是否禁用cell
|
||||
* @property {Boolean} border 是否显示下边框 (默认 true )
|
||||
* @property {Boolean} center 内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
|
||||
* @property {String} url 点击后跳转的URL地址
|
||||
* @property {String} linkType 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
|
||||
* @property {Boolean} clickable 是否开启点击反馈(表现为点击时加上灰色背景) (默认 false )
|
||||
* @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 (默认 false )
|
||||
* @property {Boolean} required 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
|
||||
* @property {String} rightIcon 右侧的图标箭头 (默认 'arrow-right')
|
||||
* @property {String} arrowDirection 右侧箭头的方向,可选值为:left,up,down
|
||||
* @property {Object | String} rightIconStyle 右侧箭头图标的样式
|
||||
* @property {Object | String} titleStyle 标题的样式
|
||||
* @property {Object | String} iconStyle 左侧图标样式
|
||||
* @property {String} size 单位元的大小,可选值为 large,normal,mini
|
||||
* @property {Boolean} stop 点击cell是否阻止事件传播 (默认 true )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 点击cell列表时触发
|
||||
* @example 该组件需要搭配cell-group组件使用,见官方文档示例
|
||||
*/
|
||||
export default {
|
||||
name: 'u-cell',
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
mixins: [mpMixin, mixin, props],
|
||||
computed: {
|
||||
titleTextStyle() {
|
||||
return addStyle(this.titleStyle)
|
||||
}
|
||||
},
|
||||
emits: ['click'],
|
||||
methods: {
|
||||
addStyle,
|
||||
testEmpty: test.empty,
|
||||
// 点击cell
|
||||
clickHandler(e) {
|
||||
if (this.disabled) return
|
||||
this.$emit('click', {
|
||||
name: this.name
|
||||
})
|
||||
// 如果配置了url(此props参数通过mixin引入)参数,跳转页面
|
||||
this.openPage()
|
||||
// 是否阻止事件传播
|
||||
this.stop && this.preventEvent(e)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$u-cell-padding: 13px 15px !default;
|
||||
$u-cell-font-size: 15px !default;
|
||||
$u-cell-line-height: 24px !default;
|
||||
$u-cell-color: $u-main-color !default;
|
||||
$u-cell-icon-size: 16px !default;
|
||||
$u-cell-title-font-size: 15px !default;
|
||||
$u-cell-title-line-height: 22px !default;
|
||||
$u-cell-title-color: $u-main-color !default;
|
||||
$u-cell-label-font-size: 12px !default;
|
||||
$u-cell-label-color: $u-tips-color !default;
|
||||
$u-cell-label-line-height: 18px !default;
|
||||
$u-cell-value-font-size: 14px !default;
|
||||
$u-cell-value-color: $u-content-color !default;
|
||||
$u-cell-clickable-color: $u-bg-color !default;
|
||||
$u-cell-disabled-color: #c8c9cc !default;
|
||||
$u-cell-padding-top-large: 13px !default;
|
||||
$u-cell-padding-bottom-large: 13px !default;
|
||||
$u-cell-value-font-size-large: 15px !default;
|
||||
$u-cell-label-font-size-large: 14px !default;
|
||||
$u-cell-title-font-size-large: 16px !default;
|
||||
$u-cell-left-icon-wrap-margin-right: 4px !default;
|
||||
$u-cell-right-icon-wrap-margin-left: 4px !default;
|
||||
$u-cell-title-flex:1 !default;
|
||||
$u-cell-label-margin-top:5px !default;
|
||||
|
||||
|
||||
.u-cell {
|
||||
&__body {
|
||||
@include flex();
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
padding: $u-cell-padding;
|
||||
font-size: $u-cell-font-size;
|
||||
color: $u-cell-color;
|
||||
// line-height: $u-cell-line-height;
|
||||
align-items: center;
|
||||
|
||||
&__content {
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--large {
|
||||
padding-top: $u-cell-padding-top-large;
|
||||
padding-bottom: $u-cell-padding-bottom-large;
|
||||
}
|
||||
}
|
||||
|
||||
&__left-icon-wrap,
|
||||
&__right-icon-wrap {
|
||||
@include flex();
|
||||
align-items: center;
|
||||
// height: $u-cell-line-height;
|
||||
font-size: $u-cell-icon-size;
|
||||
}
|
||||
|
||||
&__left-icon-wrap {
|
||||
margin-right: $u-cell-left-icon-wrap-margin-right;
|
||||
}
|
||||
|
||||
&__right-icon-wrap {
|
||||
margin-left: $u-cell-right-icon-wrap-margin-left;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&--up {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
&--down {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex: $u-cell-title-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-text {
|
||||
font-size: $u-cell-title-font-size;
|
||||
line-height: $u-cell-title-line-height;
|
||||
color: $u-cell-title-color;
|
||||
|
||||
&--large {
|
||||
font-size: $u-cell-title-font-size-large;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-top: $u-cell-label-margin-top;
|
||||
font-size: $u-cell-label-font-size;
|
||||
color: $u-cell-label-color;
|
||||
line-height: $u-cell-label-line-height;
|
||||
|
||||
&--large {
|
||||
font-size: $u-cell-label-font-size-large;
|
||||
}
|
||||
}
|
||||
|
||||
&__value {
|
||||
text-align: right;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin-left: auto;
|
||||
/* #endif */
|
||||
font-size: $u-cell-value-font-size;
|
||||
line-height: $u-cell-line-height;
|
||||
color: $u-cell-value-color;
|
||||
&--large {
|
||||
font-size: $u-cell-value-font-size-large;
|
||||
}
|
||||
}
|
||||
|
||||
&--required {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: visible;
|
||||
/* #endif */
|
||||
@include flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--required:before {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
content: '*';
|
||||
/* #endif */
|
||||
left: -8px;
|
||||
margin-top: 4rpx;
|
||||
font-size: 14px;
|
||||
color: $u-error;
|
||||
}
|
||||
|
||||
&--clickable {
|
||||
background-color: $u-cell-clickable-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: $u-cell-disabled-color;
|
||||
/* #ifndef APP-NVUE */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&--center {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/checkboxGroup.js
|
||||
*/
|
||||
export default {
|
||||
// checkbox-group组件
|
||||
checkboxGroup: {
|
||||
name: '',
|
||||
value: [],
|
||||
shape: 'square',
|
||||
disabled: false,
|
||||
activeColor: '#2979ff',
|
||||
inactiveColor: '#c8c9cc',
|
||||
size: 18,
|
||||
placement: 'row',
|
||||
labelSize: 14,
|
||||
labelColor: '#303133',
|
||||
labelDisabled: false,
|
||||
iconColor: '#ffffff',
|
||||
iconSize: 12,
|
||||
iconPlacement: 'left',
|
||||
borderBottom: false
|
||||
}
|
||||
}
|
||||
93
uni_modules/uview-plus/components/u-checkbox-group/props.js
Normal file
93
uni_modules/uview-plus/components/u-checkbox-group/props.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 标识符
|
||||
name: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.name
|
||||
},
|
||||
// #ifdef VUE3
|
||||
// 绑定的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => defProps.checkboxGroup.value
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
// 绑定的值
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => defProps.checkboxGroup.value
|
||||
},
|
||||
// #endif
|
||||
// 形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.shape
|
||||
},
|
||||
// 是否禁用全部checkbox
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => defProps.checkboxGroup.disabled
|
||||
},
|
||||
|
||||
// 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.activeColor
|
||||
},
|
||||
// 未选中的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.inactiveColor
|
||||
},
|
||||
|
||||
// 整个组件的尺寸,默认px
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkboxGroup.size
|
||||
},
|
||||
// 布局方式,row-横向,column-纵向
|
||||
placement: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.placement
|
||||
},
|
||||
// label的字体大小,px单位
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkboxGroup.labelSize
|
||||
},
|
||||
// label的字体颜色
|
||||
labelColor: {
|
||||
type: [String],
|
||||
default: () => defProps.checkboxGroup.labelColor
|
||||
},
|
||||
// 是否禁止点击文本操作
|
||||
labelDisabled: {
|
||||
type: Boolean,
|
||||
default: () => defProps.checkboxGroup.labelDisabled
|
||||
},
|
||||
// 图标颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.iconColor
|
||||
},
|
||||
// 图标的大小,单位px
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkboxGroup.iconSize
|
||||
},
|
||||
// 勾选图标的对齐方式,left-左边,right-右边
|
||||
iconPlacement: {
|
||||
type: String,
|
||||
default: () => defProps.checkboxGroup.iconPlacement
|
||||
},
|
||||
// 竖向配列时,是否显示下划线
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: () => defProps.checkboxGroup.borderBottom
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-checkbox-group"
|
||||
:class="bemClass"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
/**
|
||||
* checkboxGroup 复选框组
|
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html
|
||||
* @property {String} name 标识符
|
||||
* @property {Array} value 绑定的值
|
||||
* @property {String} shape 形状,circle-圆形,square-方形 (默认 'square' )
|
||||
* @property {Boolean} disabled 是否禁用全部checkbox (默认 false )
|
||||
* @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
|
||||
* @property {String} inactiveColor 未选中的颜色 (默认 '#c8c9cc' )
|
||||
* @property {String | Number} size 整个组件的尺寸 单位px (默认 18 )
|
||||
* @property {String} placement 布局方式,row-横向,column-纵向 (默认 'row' )
|
||||
* @property {String | Number} labelSize label的字体大小,px单位 (默认 14 )
|
||||
* @property {String} labelColor label的字体颜色 (默认 '#303133' )
|
||||
* @property {Boolean} labelDisabled 是否禁止点击文本操作 (默认 false )
|
||||
* @property {String} iconColor 图标颜色 (默认 '#ffffff' )
|
||||
* @property {String | Number} iconSize 图标的大小,单位px (默认 12 )
|
||||
* @property {String} iconPlacement 勾选图标的对齐方式,left-左边,right-右边 (默认 'left' )
|
||||
* @property {Boolean} borderBottom placement为row时,是否显示下边框 (默认 false )
|
||||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
|
||||
* @event {Function} input 修改通过v-model绑定的值时触发,回调为一个对象
|
||||
* @example <u-checkbox-group></u-checkbox-group>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-checkbox-group',
|
||||
mixins: [mpMixin, mixin,props],
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [
|
||||
// #ifdef VUE2
|
||||
this.value,
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.modelValue,
|
||||
// #endif
|
||||
this.disabled,
|
||||
this.inactiveColor,
|
||||
this.activeColor,
|
||||
this.size,
|
||||
this.labelDisabled,
|
||||
this.shape,
|
||||
this.iconSize,
|
||||
this.borderBottom,
|
||||
this.placement,
|
||||
];
|
||||
},
|
||||
bemClass() {
|
||||
// this.bem为一个computed变量,在mixin中
|
||||
return this.bem('checkbox-group', ['placement'])
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData: {
|
||||
handler() {
|
||||
if (this.children.length) {
|
||||
this.children.map((child) => {
|
||||
// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof child.init === "function" && child.init();
|
||||
});
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
// #ifdef VUE3
|
||||
emits: ['update:modelValue', 'change'],
|
||||
// #endif
|
||||
methods: {
|
||||
// 将其他的checkbox设置为未选中的状态
|
||||
unCheckedOther(childInstance) {
|
||||
const values = []
|
||||
this.children.map(child => {
|
||||
// 将被选中的checkbox,放到数组中返回
|
||||
if (child.isChecked) {
|
||||
values.push(child.name)
|
||||
}
|
||||
})
|
||||
|
||||
// 修改通过v-model绑定的值
|
||||
// #ifdef VUE3
|
||||
this.$emit("update:modelValue", values);
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
this.$emit("input", values);
|
||||
// #endif
|
||||
// 放在最后更新,否则change事件传出去的values不会更新
|
||||
this.$emit('change', values)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-checkbox-group {
|
||||
|
||||
&--row {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
&--column {
|
||||
@include flex(column);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
uni_modules/uview-plus/components/u-checkbox/checkbox.js
Normal file
27
uni_modules/uview-plus/components/u-checkbox/checkbox.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/checkbox.js
|
||||
*/
|
||||
export default {
|
||||
// checkbox组件
|
||||
checkbox: {
|
||||
name: '',
|
||||
shape: '',
|
||||
size: '',
|
||||
checkbox: false,
|
||||
disabled: '',
|
||||
activeColor: '',
|
||||
inactiveColor: '',
|
||||
iconSize: '',
|
||||
iconColor: '',
|
||||
label: '',
|
||||
labelSize: '',
|
||||
labelColor: '',
|
||||
labelDisabled: ''
|
||||
}
|
||||
}
|
||||
76
uni_modules/uview-plus/components/u-checkbox/props.js
Normal file
76
uni_modules/uview-plus/components/u-checkbox/props.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// checkbox的名称
|
||||
name: {
|
||||
type: [String, Number, Boolean],
|
||||
default: () => defProps.checkbox.name
|
||||
},
|
||||
// 形状,square为方形,circle为圆型
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.checkbox.shape
|
||||
},
|
||||
// 整体的大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkbox.size
|
||||
},
|
||||
// 是否默认选中
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: () => defProps.checkbox.checked
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: [String, Boolean],
|
||||
default: () => defProps.checkbox.disabled
|
||||
},
|
||||
// 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkbox.activeColor
|
||||
},
|
||||
// 未选中的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkbox.inactiveColor
|
||||
},
|
||||
// 图标的大小,单位px
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkbox.iconSize
|
||||
},
|
||||
// 图标颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkbox.iconColor
|
||||
},
|
||||
// label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkbox.label
|
||||
},
|
||||
// label的字体大小,px单位
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.checkbox.labelSize
|
||||
},
|
||||
// label的颜色
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: () => defProps.checkbox.labelColor
|
||||
},
|
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: {
|
||||
type: [String, Boolean],
|
||||
default: () => defProps.checkbox.labelDisabled
|
||||
},
|
||||
// 是否独立使用
|
||||
usedAlone: {
|
||||
type: [Boolean],
|
||||
default: () => false
|
||||
}
|
||||
}
|
||||
})
|
||||
389
uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue
Normal file
389
uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue
Normal file
@@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-checkbox cursor-pointer"
|
||||
:style="[checkboxStyle]"
|
||||
@tap.stop="wrapperClickHandler"
|
||||
:class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']"
|
||||
>
|
||||
<view
|
||||
class="u-checkbox__icon-wrap cursor-pointer"
|
||||
@tap.stop="iconClickHandler"
|
||||
:class="iconClasses"
|
||||
:style="[iconWrapStyle]"
|
||||
>
|
||||
<slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor">
|
||||
<up-icon
|
||||
class="u-checkbox__icon-wrap__icon"
|
||||
name="checkbox-mark"
|
||||
:size="elIconSize"
|
||||
:color="elIconColor"
|
||||
/>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-checkbox__label-wrap cursor-pointer" @tap.stop="labelClickHandler">
|
||||
<slot name="label" :label="label" :elDisabled="elDisabled">
|
||||
<text
|
||||
:style="{
|
||||
color: elDisabled ? elInactiveColor : elLabelColor,
|
||||
fontSize: elLabelSize,
|
||||
lineHeight: elLabelSize
|
||||
}"
|
||||
>{{label}}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle, addUnit, deepMerge, formValidate, error } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
/**
|
||||
* checkbox 复选框
|
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html
|
||||
* @property {String | Number | Boolean} name checkbox组件的标示符
|
||||
* @property {String} shape 形状,square为方形,circle为圆型
|
||||
* @property {String | Number} size 整体的大小
|
||||
* @property {Boolean} checked 是否默认选中
|
||||
* @property {String | Boolean} disabled 是否禁用
|
||||
* @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
|
||||
* @property {String} inactiveColor 未选中的颜色
|
||||
* @property {String | Number} iconSize 图标的大小,单位px
|
||||
* @property {String} iconColor 图标颜色
|
||||
* @property {String | Number} label label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
|
||||
* @property {String} labelColor label的颜色
|
||||
* @property {String | Number} labelSize label的字体大小,px单位
|
||||
* @property {String | Boolean} labelDisabled 是否禁止点击提示语选中复选框
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
|
||||
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
|
||||
*/
|
||||
export default {
|
||||
name: "u-checkbox",
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
isChecked: false,
|
||||
// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
|
||||
// 故只能使用如此方法
|
||||
parentData: {
|
||||
iconSize: 12,
|
||||
labelDisabled: null,
|
||||
disabled: null,
|
||||
shape: 'square',
|
||||
activeColor: null,
|
||||
inactiveColor: null,
|
||||
size: 18,
|
||||
// #ifdef VUE2
|
||||
value: null,
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: null,
|
||||
// #endif
|
||||
iconColor: null,
|
||||
placement: 'row',
|
||||
borderBottom: false,
|
||||
iconPlacement: 'left'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用,如果父组件u-radios-group禁用的话,将会忽略子组件的配置
|
||||
elDisabled() {
|
||||
return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
|
||||
},
|
||||
// 是否禁用label点击
|
||||
elLabelDisabled() {
|
||||
return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
|
||||
false;
|
||||
},
|
||||
// 组件尺寸,对应size的值,默认值为21px
|
||||
elSize() {
|
||||
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
|
||||
},
|
||||
// 组件的勾选图标的尺寸,默认12px
|
||||
elIconSize() {
|
||||
return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
|
||||
},
|
||||
// 组件选中激活时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
|
||||
},
|
||||
// 组件选未中激活时的颜色
|
||||
elInactiveColor() {
|
||||
return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
|
||||
'#c8c9cc');
|
||||
},
|
||||
// label的颜色
|
||||
elLabelColor() {
|
||||
return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
|
||||
},
|
||||
// 组件的形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
|
||||
},
|
||||
// label大小
|
||||
elLabelSize() {
|
||||
return addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
|
||||
'15'))
|
||||
},
|
||||
elIconColor() {
|
||||
const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
|
||||
'#ffffff');
|
||||
// 图标的颜色
|
||||
if (this.elDisabled) {
|
||||
// disabled状态下,已勾选的checkbox图标改为elInactiveColor
|
||||
return this.isChecked ? this.elInactiveColor : 'transparent'
|
||||
} else {
|
||||
return this.isChecked ? iconColor : 'transparent'
|
||||
}
|
||||
},
|
||||
iconClasses() {
|
||||
let classes = []
|
||||
// 组件的形状
|
||||
classes.push('u-checkbox__icon-wrap--' + this.elShape)
|
||||
if (this.elDisabled) {
|
||||
classes.push('u-checkbox__icon-wrap--disabled')
|
||||
}
|
||||
if (this.isChecked && this.elDisabled) {
|
||||
classes.push('u-checkbox__icon-wrap--disabled--checked')
|
||||
}
|
||||
// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
|
||||
// #ifdef MP-ALIPAY || MP-TOUTIAO
|
||||
classes = classes.join(' ')
|
||||
// #endif
|
||||
return classes
|
||||
},
|
||||
iconWrapStyle() {
|
||||
// checkbox的整体样式
|
||||
const style = {}
|
||||
style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
|
||||
style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
|
||||
style.width = addUnit(this.elSize)
|
||||
style.height = addUnit(this.elSize)
|
||||
// 如果是图标在右边的话,移除它的右边距
|
||||
if (!this.usedAlone) {
|
||||
if (this.parentData.iconPlacement === 'right') {
|
||||
style.marginRight = 0
|
||||
}
|
||||
}
|
||||
return style
|
||||
},
|
||||
checkboxStyle() {
|
||||
const style = {}
|
||||
if (!this.usedAlone) {
|
||||
if (this.parentData.borderBottom && this.parentData.placement === 'row') {
|
||||
error('检测到您将borderBottom设置为true,需要同时将up-checkbox-group的placement设置为column才有效')
|
||||
}
|
||||
// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
|
||||
if (this.parentData.borderBottom && this.parentData.placement === 'column') {
|
||||
style.paddingBottom = '8px'
|
||||
}
|
||||
}
|
||||
return deepMerge(style, addStyle(this.customStyle))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
emits: ["change", "update:checked"],
|
||||
methods: {
|
||||
init() {
|
||||
if (!this.usedAlone) {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
|
||||
this.updateParentData()
|
||||
if (!this.parent) {
|
||||
error('up-checkbox必须搭配up-checkbox-group组件使用')
|
||||
}
|
||||
let value = '';
|
||||
// #ifdef VUE2
|
||||
value = this.parentData.value
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
value = this.parentData.modelValue
|
||||
// #endif
|
||||
// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
|
||||
if (this.checked) {
|
||||
this.isChecked = true
|
||||
} else if (!this.usedAlone && test.array(value)) {
|
||||
// 查找数组是是否存在this.name元素值
|
||||
this.isChecked = value.some(item => {
|
||||
return item === this.name
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (this.checked) {
|
||||
this.isChecked = true
|
||||
}
|
||||
}
|
||||
},
|
||||
updateParentData() {
|
||||
this.getParentData('u-checkbox-group')
|
||||
},
|
||||
// 横向两端排列时,点击组件即可触发选中事件
|
||||
wrapperClickHandler(e) {
|
||||
if (!this.usedAlone) {
|
||||
this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
|
||||
} else {
|
||||
this.iconClickHandler(e)
|
||||
}
|
||||
},
|
||||
// 点击图标
|
||||
iconClickHandler(e) {
|
||||
this.preventEvent(e)
|
||||
// 如果整体被禁用,不允许被点击
|
||||
if (!this.elDisabled) {
|
||||
this.setRadioCheckedStatus()
|
||||
}
|
||||
},
|
||||
// 点击label
|
||||
labelClickHandler(e) {
|
||||
this.preventEvent(e)
|
||||
// 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
|
||||
if (!this.elLabelDisabled && !this.elDisabled) {
|
||||
this.setRadioCheckedStatus()
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', this.isChecked, {
|
||||
name: this.name
|
||||
})
|
||||
// 双向绑定
|
||||
if (this.usedAlone) {
|
||||
this.$emit('update:checked', this.isChecked)
|
||||
}
|
||||
// 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
|
||||
this.$nextTick(() => {
|
||||
formValidate(this, 'change')
|
||||
})
|
||||
},
|
||||
// 改变组件选中状态
|
||||
// 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例
|
||||
// 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
|
||||
setRadioCheckedStatus() {
|
||||
// 将本组件标记为与原来相反的状态
|
||||
this.isChecked = !this.isChecked
|
||||
this.emitEvent()
|
||||
if (!this.usedAlone) {
|
||||
typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
checked(newValue, oldValue){
|
||||
if (newValue !== this.isChecked) {
|
||||
this.isChecked = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$u-checkbox-icon-wrap-margin-right:6px !default;
|
||||
$u-checkbox-icon-wrap-font-size:6px !default;
|
||||
$u-checkbox-icon-wrap-border-width:1px !default;
|
||||
$u-checkbox-icon-wrap-border-color:#c8c9cc !default;
|
||||
$u-checkbox-icon-wrap-icon-line-height:0 !default;
|
||||
$u-checkbox-icon-wrap-circle-border-radius:100% !default;
|
||||
$u-checkbox-icon-wrap-square-border-radius:3px !default;
|
||||
$u-checkbox-icon-wrap-checked-color:#fff !default;
|
||||
$u-checkbox-icon-wrap-checked-background-color:red !default;
|
||||
$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
|
||||
$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
|
||||
$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
|
||||
$u-checkbox-label-margin-left:5px !default;
|
||||
$u-checkbox-label-margin-right:12px !default;
|
||||
$u-checkbox-label-color:$u-content-color !default;
|
||||
$u-checkbox-label-font-size:15px !default;
|
||||
$u-checkbox-label-disabled-color:#c8c9cc !default;
|
||||
|
||||
.u-checkbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
@include flex(row);
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
|
||||
&-label--left {
|
||||
flex-direction: row
|
||||
}
|
||||
|
||||
&-label--right {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
// nvue下,border-color过渡有问题
|
||||
transition-property: border-color, background-color, color;
|
||||
transition-duration: 0.2s;
|
||||
/* #endif */
|
||||
color: $u-content-color;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
margin-right: $u-checkbox-icon-wrap-margin-right;
|
||||
|
||||
font-size: $u-checkbox-icon-wrap-font-size;
|
||||
border-width: $u-checkbox-icon-wrap-border-width;
|
||||
border-color: $u-checkbox-icon-wrap-border-color;
|
||||
border-style: solid;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: $u-checkbox-icon-wrap-icon-line-height;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: $u-checkbox-icon-wrap-circle-border-radius;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: $u-checkbox-icon-wrap-square-border-radius;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: $u-checkbox-icon-wrap-checked-color;
|
||||
background-color: $u-checkbox-icon-wrap-checked-background-color;
|
||||
border-color: $u-checkbox-icon-wrap-checked-border-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: $u-checkbox-icon-wrap-disabled-background-color !important;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: $u-checkbox-icon-wrap-disabled-checked-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
/* #ifndef APP-NVUE */
|
||||
word-wrap: break-word;
|
||||
/* #endif */
|
||||
margin-left: $u-checkbox-label-margin-left;
|
||||
margin-right: $u-checkbox-label-margin-right;
|
||||
color: $u-checkbox-label-color;
|
||||
font-size: $u-checkbox-label-font-size;
|
||||
|
||||
&--disabled {
|
||||
color: $u-checkbox-label-disabled-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
109
uni_modules/uview-plus/components/u-choose/u-choose.vue
Normal file
109
uni_modules/uview-plus/components/u-choose/u-choose.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<style scoped lang="scss">
|
||||
.up-choose {
|
||||
::v-deep .up-tag {
|
||||
font-weight: 600;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.up-choose-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.up-choose-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<scroll-view
|
||||
:scroll-x="wrap === false"
|
||||
:class="['up-choose', wrap ? 'up-choose-wrap' : 'up-choose-nowrap']">
|
||||
<template :key="item.id" v-for="(item,index) in options">
|
||||
<view :style="{width: itemWidth, display: 'inline-block'}">
|
||||
<slot :item="item" :index="index">
|
||||
<up-tag :type="index == currentIndex ? 'primary' : 'info'"
|
||||
size="large" :plain="index == currentIndex ? false : true"
|
||||
:class="currentIndex === index ? 'active': ''" :height="itemHeight"
|
||||
:style="{width: itemWidth, padding: itemPadding}"
|
||||
@click="change(index)">
|
||||
{{item[labelName]}}
|
||||
</up-tag>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'up-choose',
|
||||
props: {
|
||||
options:{
|
||||
type: Array,
|
||||
default: ()=>{
|
||||
return [];
|
||||
}
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number,String,Array],
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: [String],
|
||||
default: 'radio'
|
||||
},
|
||||
itemWidth: {
|
||||
type: [String],
|
||||
default: 'auto'
|
||||
},
|
||||
itemHeight: {
|
||||
type: [String],
|
||||
default: '50px'
|
||||
},
|
||||
itemPadding: {
|
||||
type: [String],
|
||||
default: '8px'
|
||||
},
|
||||
labelName: {
|
||||
type: String,
|
||||
default: 'title'
|
||||
},
|
||||
valueName: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
customClick: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: ''
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.currentIndex = this.modelValue;
|
||||
},
|
||||
emits: ['update:modelValue', 'custom-click'],
|
||||
methods: {
|
||||
change(index){
|
||||
if (this.customClick) {
|
||||
this.$emit('custom-click', index);
|
||||
} else {
|
||||
this.currentIndex = index;
|
||||
this.$emit('update:modelValue', index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/circleProgress.js
|
||||
*/
|
||||
export default {
|
||||
// circleProgress 组件
|
||||
circleProgress: {
|
||||
percentage: 30
|
||||
}
|
||||
}
|
||||
10
uni_modules/uview-plus/components/u-circle-progress/props.js
Normal file
10
uni_modules/uview-plus/components/u-circle-progress/props.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
percentage: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.circleProgress.percentage
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view class="u-circle-progress">
|
||||
<view class="u-circle-progress__left">
|
||||
<view
|
||||
class="u-circle-progress__left__circle"
|
||||
:style="[leftSyle]"
|
||||
ref="left-circle"
|
||||
>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-circle-progress__right"
|
||||
>
|
||||
<view
|
||||
class="u-circle-progress__right__circle"
|
||||
ref="right-circle"
|
||||
:style="[rightSyle]"
|
||||
>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-circle-progress__circle">
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import {sleep } from '../../libs/function/index';
|
||||
// #ifdef APP-NVUE
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
// #endif
|
||||
/**
|
||||
* CircleProgress 圆形进度条 TODO: 待完善
|
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/circleProgress.html
|
||||
* @property {String | Number} percentage 圆环进度百分比值,为数值类型,0-100 (默认 30 )
|
||||
* @example
|
||||
*/
|
||||
export default {
|
||||
name: 'u-circle-progress',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
leftBorderColor: 'rgb(200, 200, 200)',
|
||||
rightBorderColor: 'rgb(200, 200, 200)',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
leftSyle() {
|
||||
const style = {}
|
||||
style.borderTopColor = this.leftBorderColor
|
||||
style.borderRightColor = this.leftBorderColor
|
||||
return style
|
||||
},
|
||||
rightSyle() {
|
||||
const style = {}
|
||||
style.borderLeftColor = this.rightBorderColor
|
||||
style.borderBottomColor = this.rightBorderColor
|
||||
return style
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
sleep().then(() => {
|
||||
this.rightBorderColor = 'rgb(66, 185, 131)'
|
||||
// this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
animation.transition(this.$refs['right-circle'].ref, {
|
||||
styles: {
|
||||
transform: 'rotate(45deg)',
|
||||
transformOrigin: 'center center'
|
||||
},
|
||||
}, () => {
|
||||
this.rightBorderColor = 'rgb(66, 185, 131)'
|
||||
// animation.transition(this.$refs['right-circle'].ref, {
|
||||
// styles: {
|
||||
// transform: 'rotate(225deg)',
|
||||
// transformOrigin: 'center center'
|
||||
// },
|
||||
// duration: 3000,
|
||||
// }, () => {
|
||||
// animation.transition(this.$refs['left-circle'].ref, {
|
||||
// styles: {
|
||||
// transform: 'rotate(45deg)',
|
||||
// transformOrigin: 'center center'
|
||||
// },
|
||||
// }, () => {
|
||||
// this.leftBorderColor = 'rgb(66, 185, 131)'
|
||||
// animation.transition(this.$refs['left-circle'].ref, {
|
||||
// styles: {
|
||||
// transform: 'rotate(225deg)',
|
||||
// transformOrigin: 'center center'
|
||||
// },
|
||||
// duration: 1500,
|
||||
// }, () => {
|
||||
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-circle-progress {
|
||||
@include flex(row);
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
// transform: rotate(0deg);
|
||||
// background-color: rgb(66, 185, 131);
|
||||
background-color: rgb(200, 200, 200);
|
||||
overflow: hidden;
|
||||
justify-content: space-between;
|
||||
|
||||
&__circle {
|
||||
border-radius: 100px;
|
||||
height: 90px;
|
||||
width: 90px;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgb(255, 255, 255);
|
||||
left: 50px;
|
||||
top: 50px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&__left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 50px;
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
// background-color: rgb(66, 185, 131);
|
||||
// background-color: rgb(200, 200, 200);
|
||||
// transform-origin: left center;
|
||||
|
||||
&__circle {
|
||||
box-sizing: border-box;
|
||||
// background-color: red;
|
||||
border-left-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-top-left-radius: 50px;
|
||||
border-top-right-radius: 50px;
|
||||
border-bottom-right-radius: 50px;
|
||||
// border-left-color: rgb(66, 185, 131);
|
||||
// border-bottom-color: rgb(66, 185, 131);
|
||||
border-top-color: rgb(66, 185, 131);
|
||||
border-right-color: rgb(66, 185, 131);
|
||||
border-width: 5px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
transform: rotate(225deg);
|
||||
// border-radius: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
&__right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 50px;
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
|
||||
&__circle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
// background-color: red;
|
||||
border-top-color: transparent;
|
||||
border-right-color: transparent;
|
||||
border-top-left-radius: 50px;
|
||||
border-bottom-left-radius: 50px;
|
||||
border-bottom-right-radius: 50px;
|
||||
// border-left-color: rgb(66, 185, 131);
|
||||
// border-bottom-color: rgb(66, 185, 131);
|
||||
border-left-color: rgb(200, 200, 200);
|
||||
border-bottom-color: rgb(200, 200, 200);
|
||||
border-width: 5px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center center;
|
||||
// border-radius: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<view class="u-city-locate">
|
||||
<up-index-list :indexList="indexList">
|
||||
<template #header>
|
||||
<view class="u-current-city-wrap">
|
||||
<view class="u-current-city-title">{{ t("up.cityLocate.locateCity") }}</view>
|
||||
<view class="u-current-city-item" @tap="location">
|
||||
<view class="u-location-city">{{locationCity}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template :key="index" v-for="(item, index) in cityList">
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<up-index-anchor :text="indexList[index]"></up-index-anchor>
|
||||
<!-- #endif -->
|
||||
<up-index-item>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<up-index-anchor :text="indexList[index]"></up-index-anchor>
|
||||
<!-- #endif -->
|
||||
<view class="hot-city-list" v-if="index == 0">
|
||||
<view class="" v-for="(item1, index1) in item" @tap="selectedCity(item1)">
|
||||
<view class="hot-city-item">{{ item1[nameKey] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="item-list" v-for="(item1, index1) in item" :key="index1">
|
||||
<view class="list__item" @tap="selectedCity(item1)">
|
||||
<text class="list__item__city-name">{{item1[nameKey]}}</text>
|
||||
</view>
|
||||
<up-line></up-line>
|
||||
</view>
|
||||
</up-index-item>
|
||||
</template>
|
||||
<template #footer>
|
||||
<view class="u-safe-area-inset--bottom">
|
||||
<text class="list__footer"></text>
|
||||
</view>
|
||||
</template>
|
||||
</up-index-list>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { t } from '../../libs/i18n'
|
||||
export default{
|
||||
name: 'u-city-locate',
|
||||
props:{
|
||||
indexList: {
|
||||
type: Array,
|
||||
default: ['🔥']
|
||||
},
|
||||
cityList:{
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [
|
||||
[{
|
||||
name: '北京',
|
||||
value: 'beijing'
|
||||
},
|
||||
{
|
||||
name: '上海',
|
||||
value: 'shanghai'
|
||||
},
|
||||
{
|
||||
name: '广州',
|
||||
value: 'guangzhou'
|
||||
},
|
||||
{
|
||||
name: '深圳',
|
||||
value: 'shenzhen'
|
||||
},
|
||||
{
|
||||
name: '杭州',
|
||||
value: 'hangzhou'
|
||||
}]
|
||||
]
|
||||
}
|
||||
},
|
||||
locationType: {
|
||||
type: String,
|
||||
default: 'wgs84'
|
||||
},
|
||||
currentCity: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
nameKey: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
},
|
||||
watch:{
|
||||
currentCity(val) {
|
||||
this.locationCity = val;
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
locationCity: t("up.cityLocate.locating") + '....'
|
||||
}
|
||||
},
|
||||
emits: ['location-success', 'select-city'],
|
||||
methods:{
|
||||
t,
|
||||
// 获取城市
|
||||
selectedCity(city){
|
||||
this.locationCity = city[this.nameKey];
|
||||
this.$emit('select-city', {
|
||||
locationCity: this.locationCity
|
||||
});
|
||||
},
|
||||
// 定位操作
|
||||
location(){
|
||||
let That = this;
|
||||
uni.getLocation({
|
||||
type: this.locationType,
|
||||
geocode:true,
|
||||
success(res){
|
||||
console.log(res);
|
||||
That.locationCity = res.address && res.address.city;
|
||||
That.$emit('location-success', {
|
||||
...res,
|
||||
locationCity: That.locationCity
|
||||
});
|
||||
},
|
||||
fail(){
|
||||
That.locationCity = t("up.cityLocate.fail");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
// 页面挂载后进行异步操作
|
||||
created(){
|
||||
},
|
||||
mounted(){
|
||||
this.location();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.list__item {
|
||||
padding: 8px 1px;
|
||||
}
|
||||
.u-current-city-title {
|
||||
color: grey;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.u-current-city-item {
|
||||
height: 30px;
|
||||
}
|
||||
.hot-city-list {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
padding: 12px 0;
|
||||
.hot-city-item {
|
||||
padding: 6px 12px;
|
||||
margin: 5px;
|
||||
border: 1px solid #ededed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
uni_modules/uview-plus/components/u-code-input/codeInput.js
Normal file
29
uni_modules/uview-plus/components/u-code-input/codeInput.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/codeInput.js
|
||||
*/
|
||||
export default {
|
||||
// codeInput 组件
|
||||
codeInput: {
|
||||
adjustPosition: true,
|
||||
maxlength: 6,
|
||||
dot: false,
|
||||
mode: 'box',
|
||||
hairline: false,
|
||||
space: 10,
|
||||
value: '',
|
||||
focus: false,
|
||||
bold: false,
|
||||
color: '#606266',
|
||||
fontSize: 18,
|
||||
size: 35,
|
||||
disabledKeyboard: false,
|
||||
borderColor: '#c9cacc',
|
||||
disabledDot: true
|
||||
}
|
||||
}
|
||||
90
uni_modules/uview-plus/components/u-code-input/props.js
Normal file
90
uni_modules/uview-plus/components/u-code-input/props.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 键盘弹起时,是否自动上推页面
|
||||
adjustPosition: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.adjustPosition
|
||||
},
|
||||
// 最大输入长度
|
||||
maxlength: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.codeInput.maxlength
|
||||
},
|
||||
// 是否用圆点填充
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.dot
|
||||
},
|
||||
// 显示模式,box-盒子模式,line-底部横线模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => defProps.codeInput.mode
|
||||
},
|
||||
// 是否细边框
|
||||
hairline: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.hairline
|
||||
},
|
||||
// 字符间的距离
|
||||
space: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.codeInput.space
|
||||
},
|
||||
// #ifdef VUE3
|
||||
// 预置值
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.codeInput.value
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
// 预置值
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.codeInput.value
|
||||
},
|
||||
// #endif
|
||||
// 是否自动获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.focus
|
||||
},
|
||||
// 字体是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.bold
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: () => defProps.codeInput.color
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.codeInput.fontSize
|
||||
},
|
||||
// 输入框的大小,宽等于高
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.codeInput.size
|
||||
},
|
||||
// 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
|
||||
disabledKeyboard: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.disabledKeyboard
|
||||
},
|
||||
// 边框和线条颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: () => defProps.codeInput.borderColor
|
||||
},
|
||||
// 是否禁止输入"."符号
|
||||
disabledDot: {
|
||||
type: Boolean,
|
||||
default: () => defProps.codeInput.disabledDot
|
||||
}
|
||||
}
|
||||
})
|
||||
299
uni_modules/uview-plus/components/u-code-input/u-code-input.vue
Normal file
299
uni_modules/uview-plus/components/u-code-input/u-code-input.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<view class="u-code-input">
|
||||
<view
|
||||
class="u-code-input__item"
|
||||
:style="[itemStyle(index)]"
|
||||
v-for="(item, index) in codeLength"
|
||||
:key="index"
|
||||
>
|
||||
<view
|
||||
class="u-code-input__item__dot"
|
||||
v-if="dot && codeArray.length > index"
|
||||
></view>
|
||||
<text
|
||||
v-else
|
||||
:style="{
|
||||
fontSize: addUnit(fontSize),
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: color
|
||||
}"
|
||||
>{{codeArray[index]}}</text>
|
||||
<view
|
||||
class="u-code-input__item__line"
|
||||
v-if="mode === 'line'"
|
||||
:style="[lineStyle]"
|
||||
></view>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view v-if="isFocus && codeArray.length === index"
|
||||
:style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view v-if="isFocus && codeArray.length === index"
|
||||
:style="{backgroundColor: color, opacity: opacity}" class="u-code-input__item__cursor"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<input
|
||||
:disabled="disabledKeyboard"
|
||||
type="number"
|
||||
:focus="focus"
|
||||
:value="inputValue"
|
||||
:maxlength="maxlength"
|
||||
:adjustPosition="adjustPosition"
|
||||
class="u-code-input__input"
|
||||
@input="inputHandler"
|
||||
:style="{
|
||||
height: addUnit(size)
|
||||
}"
|
||||
@focus="isFocus = true"
|
||||
@blur="isFocus = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addUnit, getPx } from '../../libs/function/index';
|
||||
/**
|
||||
* CodeInput 验证码输入
|
||||
* @description 该组件一般用于验证用户短信验证码的场景,也可以结合uview-plus的键盘组件使用
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/codeInput.html
|
||||
* @property {String | Number} maxlength 最大输入长度 (默认 6 )
|
||||
* @property {Boolean} dot 是否用圆点填充 (默认 false )
|
||||
* @property {String} mode 显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
|
||||
* @property {Boolean} hairline 是否细边框 (默认 false )
|
||||
* @property {String | Number} space 字符间的距离 (默认 10 )
|
||||
* @property {String | Number} value 预置值
|
||||
* @property {Boolean} focus 是否自动获取焦点 (默认 false )
|
||||
* @property {Boolean} bold 字体和输入横线是否加粗 (默认 false )
|
||||
* @property {String} color 字体颜色 (默认 '#606266' )
|
||||
* @property {String | Number} fontSize 字体大小,单位px (默认 18 )
|
||||
* @property {String | Number} size 输入框的大小,宽等于高 (默认 35 )
|
||||
* @property {Boolean} disabledKeyboard 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
|
||||
* @property {String} borderColor 边框和线条颜色 (默认 '#c9cacc' )
|
||||
* @property {Boolean} disabledDot 是否禁止输入"."符号 (默认 true )
|
||||
*
|
||||
* @event {Function} change 输入内容发生改变时触发,具体见上方说明 value:当前输入的值
|
||||
* @event {Function} finish 输入字符个数达maxlength值时触发,见上方说明 value:当前输入的值
|
||||
* @example <u-code-input v-model="value4" :focus="true"></u-code-input>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-code-input',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
inputValue: '',
|
||||
isFocus: this.focus,
|
||||
timer: null,
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// #ifdef VUE2
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
// 转为字符串,超出部分截掉
|
||||
this.inputValue = String(val).substring(0, this.maxlength)
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
// 转为字符串,超出部分截掉
|
||||
this.inputValue = String(val).substring(0, this.maxlength)
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
isFocus: {
|
||||
handler(val) {
|
||||
// #ifdef APP-NVUE
|
||||
if (val) {
|
||||
this.timer = setInterval(() => {
|
||||
this.opacity = Math.abs(this.opacity - 1)
|
||||
}, 600)
|
||||
} else {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
beforeUnmount() {
|
||||
// #ifdef APP-NVUE
|
||||
clearInterval(this.timer)
|
||||
// #endif
|
||||
},
|
||||
computed: {
|
||||
// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
|
||||
codeLength() {
|
||||
return new Array(Number(this.maxlength))
|
||||
},
|
||||
// 循环item的样式
|
||||
itemStyle() {
|
||||
return index => {
|
||||
const style = {
|
||||
width: addUnit(this.size),
|
||||
height: addUnit(this.size)
|
||||
}
|
||||
// 盒子模式下,需要额外进行处理
|
||||
if (this.mode === 'box') {
|
||||
// 设置盒子的边框,如果是细边框,则设置为0.5px宽度
|
||||
style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
|
||||
// 如果盒子间距为0的话
|
||||
if (getPx(this.space) === 0) {
|
||||
// 给第一和最后一个盒子设置圆角
|
||||
if (index === 0) {
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
}
|
||||
if (index === this.codeLength.length - 1) {
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
// 最后一个盒子的右边框需要保留
|
||||
if (index !== this.codeLength.length - 1) {
|
||||
style.borderRight = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index !== this.codeLength.length - 1) {
|
||||
// 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
|
||||
style.marginRight = addUnit(this.space)
|
||||
} else {
|
||||
// 最后一个盒子的有边框需要保留
|
||||
style.marginRight = 0
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
|
||||
codeArray() {
|
||||
return String(this.inputValue).split('')
|
||||
},
|
||||
// 下划线模式下,横线的样式
|
||||
lineStyle() {
|
||||
const style = {}
|
||||
style.height = this.hairline ? '2px' : '4px'
|
||||
style.width = addUnit(this.size)
|
||||
// 线条模式下,背景色即为边框颜色
|
||||
style.backgroundColor = this.borderColor
|
||||
return style
|
||||
}
|
||||
},
|
||||
emits: ["change", 'finish', "update:modelValue"],
|
||||
methods: {
|
||||
addUnit,
|
||||
// 监听输入框的值发生变化
|
||||
inputHandler(e) {
|
||||
const value = e.detail.value
|
||||
this.inputValue = value
|
||||
// 是否允许输入“.”符号
|
||||
if(this.disabledDot) {
|
||||
this.$nextTick(() => {
|
||||
this.inputValue = value.replace('.', '')
|
||||
})
|
||||
}
|
||||
// 未达到maxlength之前,发送change事件,达到后发送finish事件
|
||||
this.$emit('change', value)
|
||||
// 修改通过v-model双向绑定的值
|
||||
// #ifdef VUE3
|
||||
this.$emit("update:modelValue", value);
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
this.$emit("input", value);
|
||||
// #endif
|
||||
// 达到用户指定输入长度时,发出完成事件
|
||||
if (String(value).length >= Number(this.maxlength)) {
|
||||
this.$emit('finish', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$u-code-input-cursor-width: 1px;
|
||||
$u-code-input-cursor-height: 20px;
|
||||
$u-code-input-cursor-animation-duration: 1s;
|
||||
$u-code-input-cursor-animation-name: u-cursor-flicker;
|
||||
|
||||
.u-code-input {
|
||||
@include flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__item {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&__text {
|
||||
font-size: 15px;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 100px;
|
||||
background-color: $u-content-color;
|
||||
}
|
||||
|
||||
&__line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 4px;
|
||||
border-radius: 100px;
|
||||
width: 40px;
|
||||
background-color: $u-content-color;
|
||||
}
|
||||
&__cursor {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
opacity: 1;
|
||||
transform: translate(-50%,-50%);
|
||||
/* #endif */
|
||||
width: $u-code-input-cursor-width;
|
||||
height: $u-code-input-cursor-height;
|
||||
animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
// 之所以需要input输入框,是因为有它才能唤起键盘
|
||||
// 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
|
||||
position: absolute;
|
||||
left: -750rpx;
|
||||
width: 1500rpx;
|
||||
top: 0;
|
||||
background-color: transparent;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
@keyframes u-cursor-flicker {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
</style>
|
||||
21
uni_modules/uview-plus/components/u-code/code.js
Normal file
21
uni_modules/uview-plus/components/u-code/code.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/code.js
|
||||
*/
|
||||
import { t } from '../../libs/i18n'
|
||||
export default {
|
||||
// code 组件
|
||||
code: {
|
||||
seconds: 60,
|
||||
startText: t("up.code.send"),
|
||||
changeText: t("up.code.resendAfter"),
|
||||
endText: t("up.code.resend"),
|
||||
keepRunning: false,
|
||||
uniqueKey: ''
|
||||
}
|
||||
}
|
||||
36
uni_modules/uview-plus/components/u-code/props.js
Normal file
36
uni_modules/uview-plus/components/u-code/props.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 倒计时总秒数
|
||||
seconds: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.code.seconds
|
||||
},
|
||||
// 尚未开始时提示
|
||||
startText: {
|
||||
type: String,
|
||||
default: () => defProps.code.startText
|
||||
},
|
||||
// 正在倒计时中的提示
|
||||
changeText: {
|
||||
type: String,
|
||||
default: () => defProps.code.changeText
|
||||
},
|
||||
// 倒计时结束时的提示
|
||||
endText: {
|
||||
type: String,
|
||||
default: () => defProps.code.endText
|
||||
},
|
||||
// 是否在H5刷新或各端返回再进入时继续倒计时
|
||||
keepRunning: {
|
||||
type: Boolean,
|
||||
default: () => defProps.code.keepRunning
|
||||
},
|
||||
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: () => defProps.code.uniqueKey
|
||||
}
|
||||
}
|
||||
})
|
||||
131
uni_modules/uview-plus/components/u-code/u-code.vue
Normal file
131
uni_modules/uview-plus/components/u-code/u-code.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<view class="u-code">
|
||||
<!-- 此组件功能由js完成,无需写html逻辑 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
/**
|
||||
* Code 验证码输入框
|
||||
* @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/code.html
|
||||
* @property {String | Number} seconds 倒计时所需的秒数(默认 60 )
|
||||
* @property {String} startText 开始前的提示语,见官网说明(默认 '获取验证码' )
|
||||
* @property {String} changeText 倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )
|
||||
* @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' )
|
||||
* @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
|
||||
* @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
*
|
||||
* @event {Function} change 倒计时期间,每秒触发一次
|
||||
* @event {Function} start 开始倒计时触发
|
||||
* @event {Function} end 结束倒计时触发
|
||||
* @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code>
|
||||
*/
|
||||
export default {
|
||||
name: "u-code",
|
||||
mixins: [mpMixin, mixin,props],
|
||||
data() {
|
||||
return {
|
||||
secNum: this.seconds,
|
||||
timer: null,
|
||||
canGetCode: true, // 是否可以执行验证码操作
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkKeepRunning()
|
||||
},
|
||||
watch: {
|
||||
seconds: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.secNum = n
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ["start", "end", "change"],
|
||||
methods: {
|
||||
checkKeepRunning() {
|
||||
// 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
|
||||
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
|
||||
if(!lastTimestamp) return this.changeEvent(this.startText)
|
||||
// 当前秒的时间戳
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
|
||||
if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
|
||||
// 剩余尚未执行完的倒计秒数
|
||||
this.secNum = lastTimestamp - nowTimestamp
|
||||
// 清除本地保存的变量
|
||||
uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
|
||||
// 开始倒计时
|
||||
this.start()
|
||||
} else {
|
||||
// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
|
||||
this.changeEvent(this.startText)
|
||||
}
|
||||
},
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
|
||||
if(this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.$emit('start')
|
||||
this.canGetCode = false
|
||||
// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
|
||||
this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
|
||||
this.timer = setInterval(() => {
|
||||
if (--this.secNum) {
|
||||
// 用当前倒计时的秒数替换提示字符串中的"x"字母
|
||||
this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
|
||||
} else {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
this.changeEvent(this.endText)
|
||||
this.secNum = this.seconds
|
||||
this.$emit('end')
|
||||
this.canGetCode = true
|
||||
}
|
||||
}, 1000)
|
||||
this.setTimeToStorage()
|
||||
},
|
||||
// 重置,可以让用户再次获取验证码
|
||||
reset() {
|
||||
this.canGetCode = true
|
||||
clearInterval(this.timer)
|
||||
this.secNum = this.seconds
|
||||
this.changeEvent(this.endText)
|
||||
},
|
||||
changeEvent(text) {
|
||||
this.$emit('change', text)
|
||||
},
|
||||
// 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
|
||||
setTimeToStorage() {
|
||||
if(!this.keepRunning || !this.timer) return
|
||||
// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
|
||||
// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
|
||||
if(this.secNum > 0 && this.secNum <= this.seconds) {
|
||||
// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
|
||||
uni.setStorage({
|
||||
key: this.uniqueKey + '_$uCountDownTimestamp',
|
||||
data: nowTimestamp + Number(this.secNum)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
// 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
|
||||
beforeUnmount() {
|
||||
this.setTimeToStorage()
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
19
uni_modules/uview-plus/components/u-col/col.js
Normal file
19
uni_modules/uview-plus/components/u-col/col.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/col.js
|
||||
*/
|
||||
export default {
|
||||
// col 组件
|
||||
col: {
|
||||
span: 12,
|
||||
offset: 0,
|
||||
justify: 'start',
|
||||
align: 'stretch',
|
||||
textAlign: 'left'
|
||||
}
|
||||
}
|
||||
31
uni_modules/uview-plus/components/u-col/props.js
Normal file
31
uni_modules/uview-plus/components/u-col/props.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 占父容器宽度的多少等分,总分为12份
|
||||
span: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.col.span
|
||||
},
|
||||
// 指定栅格左侧的间隔数(总12栏)
|
||||
offset: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.col.offset
|
||||
},
|
||||
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
|
||||
justify: {
|
||||
type: String,
|
||||
default: () => defProps.col.justify
|
||||
},
|
||||
// 垂直对齐方式,可选值为top、center、bottom、stretch
|
||||
align: {
|
||||
type: String,
|
||||
default: () => defProps.col.align
|
||||
},
|
||||
// 文字对齐方式
|
||||
textAlign: {
|
||||
type: String,
|
||||
default: () => defProps.col.textAlign
|
||||
}
|
||||
}
|
||||
})
|
||||
169
uni_modules/uview-plus/components/u-col/u-col.vue
Normal file
169
uni_modules/uview-plus/components/u-col/u-col.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-col"
|
||||
ref="u-col"
|
||||
:class="[
|
||||
'u-col-' + span
|
||||
]"
|
||||
:style="[colStyle]"
|
||||
@tap="clickHandler"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { addStyle, addUnit, deepMerge, getPx } from '../../libs/function/index';
|
||||
/**
|
||||
* CodeInput 栅格系统的列
|
||||
* @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/Layout.html
|
||||
* @property {String | Number} span 栅格占据的列数,总12等份 (默认 12 )
|
||||
* @property {String | Number} offset 分栏左边偏移,计算方式与span相同 (默认 0 )
|
||||
* @property {String} justify 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) (默认 'start' )
|
||||
* @property {String} align 垂直对齐方式,可选值为top、center、bottom、stretch (默认 'stretch' )
|
||||
* @property {String} textAlign 文字水平对齐方式 (默认 'left' )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @event {Function} click col被点击,会阻止事件冒泡到row
|
||||
* @example <u-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-col',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
width: 0,
|
||||
parentData: {
|
||||
gutter: 0
|
||||
},
|
||||
gridNum: 12
|
||||
}
|
||||
},
|
||||
// 微信小程序中 options 选项
|
||||
options: {
|
||||
virtualHost: true // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等
|
||||
},
|
||||
computed: {
|
||||
uJustify() {
|
||||
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify
|
||||
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify
|
||||
else return this.justify
|
||||
},
|
||||
uAlignItem() {
|
||||
if (this.align == 'top') return 'flex-start'
|
||||
if (this.align == 'bottom') return 'flex-end'
|
||||
else return this.align
|
||||
},
|
||||
colStyle() {
|
||||
const style = {
|
||||
// 这里写成"padding: 0 10px"的形式是因为nvue的需要
|
||||
paddingLeft: addUnit(getPx(this.parentData.gutter)/2),
|
||||
paddingRight: addUnit(getPx(this.parentData.gutter)/2),
|
||||
alignItems: this.uAlignItem,
|
||||
justifyContent: this.uJustify,
|
||||
textAlign: this.textAlign,
|
||||
// #ifndef APP-NVUE
|
||||
// 在非nvue上,使用百分比形式
|
||||
flex: `0 0 ${100 / this.gridNum * this.span}%`,
|
||||
marginLeft: 100 / 12 * this.offset + '%',
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
// 在nvue上,由于无法使用百分比单位,这里需要获取父组件的宽度,再计算得出该有对应的百分比尺寸
|
||||
width: addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),
|
||||
marginLeft: addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),
|
||||
// #endif
|
||||
}
|
||||
return deepMerge(style, addStyle(this.customStyle))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
emits: ["click"],
|
||||
methods: {
|
||||
async init() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
|
||||
this.updateParentData()
|
||||
this.width = await this.parent.getComponentWidth()
|
||||
},
|
||||
updateParentData() {
|
||||
this.getParentData('u-row')
|
||||
},
|
||||
clickHandler(e) {
|
||||
this.$emit('click');
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-col {
|
||||
padding: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing:border-box;
|
||||
/* #endif */
|
||||
/* #ifdef MP */
|
||||
display: block;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// nvue下百分比无效
|
||||
/* #ifndef APP-NVUE */
|
||||
.u-col-0 {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.u-col-1 {
|
||||
width: calc(100%/12);
|
||||
}
|
||||
|
||||
.u-col-2 {
|
||||
width: calc(100%/12 * 2);
|
||||
}
|
||||
|
||||
.u-col-3 {
|
||||
width: calc(100%/12 * 3);
|
||||
}
|
||||
|
||||
.u-col-4 {
|
||||
width: calc(100%/12 * 4);
|
||||
}
|
||||
|
||||
.u-col-5 {
|
||||
width: calc(100%/12 * 5);
|
||||
}
|
||||
|
||||
.u-col-6 {
|
||||
width: calc(100%/12 * 6);
|
||||
}
|
||||
|
||||
.u-col-7 {
|
||||
width: calc(100%/12 * 7);
|
||||
}
|
||||
|
||||
.u-col-8 {
|
||||
width: calc(100%/12 * 8);
|
||||
}
|
||||
|
||||
.u-col-9 {
|
||||
width: calc(100%/12 * 9);
|
||||
}
|
||||
|
||||
.u-col-10 {
|
||||
width: calc(100%/12 * 10);
|
||||
}
|
||||
|
||||
.u-col-11 {
|
||||
width: calc(100%/12 * 11);
|
||||
}
|
||||
|
||||
.u-col-12 {
|
||||
width: calc(100%/12 * 12);
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 3.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : jry
|
||||
* @lastTime : 2025-12-19 08:55:21
|
||||
* @FilePath : /uview-plus/libs/config/props/collapseItem.js
|
||||
*/
|
||||
export default {
|
||||
// collapseItem 组件
|
||||
collapseItem: {
|
||||
title: '',
|
||||
value: '',
|
||||
label: '',
|
||||
disabled: false,
|
||||
isLink: true,
|
||||
clickable: true,
|
||||
border: true,
|
||||
align: 'left',
|
||||
name: '',
|
||||
icon: '',
|
||||
duration: 300,
|
||||
showRight: true,
|
||||
titleStyle: {},
|
||||
iconStyle: {},
|
||||
rightIconStyle: {},
|
||||
cellCustomStyle: {},
|
||||
cellCustomClass: ''
|
||||
}
|
||||
}
|
||||
97
uni_modules/uview-plus/components/u-collapse-item/props.js
Normal file
97
uni_modules/uview-plus/components/u-collapse-item/props.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.collapseItem.title
|
||||
},
|
||||
// 标题的样式
|
||||
titleStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.collapseItem.titleStyle
|
||||
}
|
||||
},
|
||||
// 标题右侧内容
|
||||
value: {
|
||||
type: String,
|
||||
default: () => defProps.collapseItem.value
|
||||
},
|
||||
// 标题下方的描述信息
|
||||
label: {
|
||||
type: String,
|
||||
default: () => defProps.collapseItem.label
|
||||
},
|
||||
// 是否禁用折叠面板
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => defProps.collapseItem.disabled
|
||||
},
|
||||
// 是否展示右侧箭头并开启点击反馈
|
||||
isLink: {
|
||||
type: Boolean,
|
||||
default: () => defProps.collapseItem.isLink
|
||||
},
|
||||
// 是否开启点击反馈
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: () => defProps.collapseItem.clickable
|
||||
},
|
||||
// 是否显示内边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: () => defProps.collapseItem.border
|
||||
},
|
||||
// 标题的对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: () => defProps.collapseItem.align
|
||||
},
|
||||
// 唯一标识符
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.collapseItem.name
|
||||
},
|
||||
// 标题左侧图片,可为绝对路径的图片或内置图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: () => defProps.collapseItem.icon
|
||||
},
|
||||
// 面板展开收起的过渡时间,单位ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: () => defProps.collapseItem.duration
|
||||
},
|
||||
// 显示右侧图标
|
||||
showRight: {
|
||||
type: Boolean,
|
||||
default: () => defProps.collapseItem.showRight
|
||||
},
|
||||
// 左侧图标样式
|
||||
iconStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.collapseItem.iconStyle
|
||||
}
|
||||
},
|
||||
// 右侧箭头图标的样式
|
||||
rightIconStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.collapseItem.rightIconStyle
|
||||
}
|
||||
},
|
||||
cellCustomStyle: {
|
||||
type: [Object, String],
|
||||
default: () => {
|
||||
return defProps.collapseItem.cellCustomStyle
|
||||
}
|
||||
},
|
||||
cellCustomClass: {
|
||||
type: String,
|
||||
default: () => defProps.collapseItem.cellCustomClass
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<view class="u-collapse-item">
|
||||
<u-cell
|
||||
:title="$slots.title ? '' : title"
|
||||
:value="value"
|
||||
:label="label"
|
||||
:icon="icon"
|
||||
:isLink="isLink"
|
||||
:clickable="clickable"
|
||||
:border="parentData.border && showBorder"
|
||||
@click="clickHandler"
|
||||
:arrowDirection="expanded ? 'up' : 'down'"
|
||||
:disabled="disabled"
|
||||
:customClass="cellCustomClass"
|
||||
:customStyle="cellCustomStyle"
|
||||
>
|
||||
<!-- 微信小程序不支持,因为微信中不支持 <slot name="title" #title />的写法 -->
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
<text v-if="!$slots.title && title">
|
||||
{{title}}
|
||||
</text>
|
||||
</slot>
|
||||
</template>
|
||||
<template #icon>
|
||||
<slot name="icon">
|
||||
<up-icon v-if="!$slots.icon && icon" :size="22" :name="icon"></up-icon>
|
||||
</slot>
|
||||
</template>
|
||||
<template #value>
|
||||
<slot name="value">
|
||||
<text v-if="!$slots.value && value">
|
||||
{{value}}
|
||||
</text>
|
||||
</slot>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<template v-if="showRight">
|
||||
<up-icon v-if="!$slots['right-icon']" :size="16" name="arrow-right"></up-icon>
|
||||
<slot name="right-icon">
|
||||
</slot>
|
||||
</template>
|
||||
</template>
|
||||
</u-cell>
|
||||
<view
|
||||
class="u-collapse-item__content"
|
||||
:animation="animationData"
|
||||
ref="animation"
|
||||
>
|
||||
<view
|
||||
class="u-collapse-item__content__text content-class"
|
||||
:id="elId"
|
||||
:ref="elId"
|
||||
><slot /></view>
|
||||
</view>
|
||||
<u-line v-if="parentData.border"></u-line>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { props } from './props.js';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
import { nextTick } from 'vue';
|
||||
import { guid, sleep, error } from '../../libs/function/index';
|
||||
import test from '../../libs/function/test';
|
||||
// #ifdef APP-NVUE
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
/**
|
||||
* collapseItem 折叠面板Item
|
||||
* @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
|
||||
* @tutorial https://uview-plus.jiangruyi.com/components/collapse.html
|
||||
* @property {String} title 标题
|
||||
* @property {String} value 标题右侧内容
|
||||
* @property {String} label 标题下方的描述信息
|
||||
* @property {Boolean} disbled 是否禁用折叠面板 ( 默认 false )
|
||||
* @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 ( 默认 true )
|
||||
* @property {Boolean} clickable 是否开启点击反馈 ( 默认 true )
|
||||
* @property {Boolean} border 是否显示内边框 ( 默认 true )
|
||||
* @property {String} align 标题的对齐方式 ( 默认 'left' )
|
||||
* @property {String | Number} name 唯一标识符
|
||||
* @property {String} icon 标题左侧图片,可为绝对路径的图片或内置图标
|
||||
* @event {Function} change 某个item被打开或者收起时触发
|
||||
* @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
|
||||
*/
|
||||
export default {
|
||||
name: "u-collapse-item",
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
elId: guid(),
|
||||
// uni.createAnimation的导出数据
|
||||
animationData: {},
|
||||
// 是否展开状态
|
||||
expanded: false,
|
||||
// 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
|
||||
showBorder: false,
|
||||
// 是否动画中,如果是则不允许继续触发点击
|
||||
animating: false,
|
||||
// 父组件u-collapse的参数
|
||||
parentData: {
|
||||
accordion: false,
|
||||
border: false
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
expanded(n) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
// 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果
|
||||
this.timer = setTimeout(() => {
|
||||
this.showBorder = n
|
||||
}, n ? 10 : 290)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
// console.log('$slots', this.$slots)
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容,或者动态修改了内容时,需要重新初始化
|
||||
async init() {
|
||||
// 初始化数据
|
||||
this.updateParentData()
|
||||
if (!this.parent) {
|
||||
return error('u-collapse-item必须要搭配u-collapse组件使用')
|
||||
}
|
||||
const {
|
||||
value,
|
||||
accordion,
|
||||
children = []
|
||||
} = this.parent
|
||||
|
||||
if (accordion) {
|
||||
if (test.array(value)) {
|
||||
return error('手风琴模式下,u-collapse组件的value参数不能为数组')
|
||||
}
|
||||
this.expanded = this.name == value
|
||||
} else {
|
||||
if (!test.array(value) && value !== null) {
|
||||
return error('非手风琴模式下,u-collapse组件的value参数必须为数组')
|
||||
}
|
||||
this.expanded = (value || []).some(item => item == this.name)
|
||||
}
|
||||
// 设置组件的展开或收起状态
|
||||
await nextTick()
|
||||
this.setContentAnimate()
|
||||
},
|
||||
updateParentData() {
|
||||
// 此方法在mixin中
|
||||
this.getParentData('u-collapse')
|
||||
},
|
||||
async setContentAnimate() {
|
||||
// 每次面板打开或者收起时,都查询元素尺寸
|
||||
// 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
|
||||
const rect = await this.queryRect()
|
||||
const height = this.expanded ? rect.height : 0
|
||||
this.animating = true
|
||||
// #ifdef APP-NVUE
|
||||
const ref = this.$refs['animation'].ref
|
||||
animation.transition(ref, {
|
||||
styles: {
|
||||
height: height + 'px'
|
||||
},
|
||||
duration: this.duration,
|
||||
// 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局
|
||||
needLayout: true,
|
||||
timingFunction: 'ease-in-out',
|
||||
}, () => {
|
||||
this.animating = false
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
const animation = uni.createAnimation({
|
||||
timingFunction: 'ease-in-out',
|
||||
});
|
||||
animation
|
||||
.height(height)
|
||||
.step({
|
||||
duration: this.duration,
|
||||
})
|
||||
.step()
|
||||
// 导出动画数据给面板的animationData值
|
||||
this.animationData = animation.export()
|
||||
// 标识动画结束
|
||||
sleep(this.duration).then(() => {
|
||||
this.animating = false
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 点击collapsehead头部
|
||||
clickHandler() {
|
||||
if (this.disabled && this.animating) return
|
||||
// 设置本组件为相反的状态
|
||||
this.parent && this.parent.onChange(this)
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
// #ifndef APP-NVUE
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview-plus.jiangruyi.com/js/getRect.html
|
||||
// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
|
||||
return new Promise(resolve => {
|
||||
this.$uGetRect(`#${this.elId}`).then(size => {
|
||||
resolve(size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// nvue下,使用dom模块查询元素高度
|
||||
// 返回一个promise,让调用此方法的主体能使用then回调
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(this.$refs[this.elId], res => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.u-collapse-item {
|
||||
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
|
||||
&__text {
|
||||
padding: 12px 15px;
|
||||
color: $u-content-color;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user