uview-plus组件库全面升级更新,订单结算判断支付方式是否可用代码调整,公众号关注二维码修改

This commit is contained in:
2025-10-21 10:44:31 +08:00
parent 5d98b7efc2
commit 5f3a307fec
395 changed files with 31264 additions and 2477 deletions

View File

@@ -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>

View File

@@ -1,11 +1,11 @@
/*
* @Author : LQ
* @Description :
* @version : 1.0
* @version : 3.0
* @Date : 2021-08-20 16:44:21
* @LastAuthor : LQ
* @lastTime : 2021-08-20 16:44:35
* @FilePath : /u-view2.0/uview-ui/libs/config/props/actionSheet.js
* @LastAuthor : jry
* @lastTime : 2025-08-16 10:52:35
* @FilePath : /uview-plus/libs/config/props/actionSheet.js
*/
export default {
// action-sheet组件

View File

@@ -1,3 +1,11 @@
/*
* @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'

View File

@@ -1,4 +1,3 @@
<template>
<u-popup
:show="show"
@@ -8,6 +7,7 @@
:round="round"
>
<view class="u-action-sheet">
<!-- 顶部标题区域 -->
<view
class="u-action-sheet__header"
v-if="title"
@@ -17,14 +17,15 @@
class="u-action-sheet__header__icon-wrap"
@tap.stop="cancel"
>
<u-icon
<up-icon
name="close"
size="17"
color="#c8c9cc"
bold
></u-icon>
></up-icon>
</view>
</view>
<!-- 描述信息 -->
<text
class="u-action-sheet__description"
:style="[{
@@ -33,7 +34,9 @@
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 -->
@@ -62,6 +65,7 @@
@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
@@ -73,6 +77,7 @@
class="u-action-sheet__item-wrap__item__subname"
>{{ item.subname }}</text>
</template>
<!-- 加载状态图标 -->
<u-loading-icon
v-else
custom-class="van-action-sheet__loading"
@@ -83,15 +88,18 @@
<!-- #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
@@ -168,6 +176,7 @@
},
emits: ["close", "select", "update:show"],
methods: {
// 关闭操作菜单事件处理
closeHandler() {
// 允许点击遮罩关闭时才发出close事件
if(this.closeOnClickOverlay) {
@@ -180,6 +189,7 @@
this.$emit('update:show', false)
this.$emit('close')
},
// 选择操作项处理
selectHandler(index) {
const item = this.actions[index]
if (item && !item.disabled && !item.loading) {
@@ -190,12 +200,21 @@
}
}
},
// 动态处理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>
@import "../../libs/css/components.scss";
$u-action-sheet-reset-button-width:100% !default;
$u-action-sheet-title-font-size: 16px !default;
$u-action-sheet-title-padding: 12px 30px !default;
@@ -280,4 +299,4 @@
background-color: $u-action-sheet-cancel-text-hover-background-color;
}
}
</style>
</style>

View File

@@ -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>

View File

@@ -3,9 +3,9 @@
* @Description :
* @version : 1.0
* @Date : 2021-08-20 16:44:21
* @LastAuthor : LQ
* @lastTime : 2021-08-20 16:47:24
* @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js
* @LastAuthor : jry
* @lastTime : 2025-08-16 16:32:24
* @FilePath : /uview-plus/libs/config/props/album.js
*/
export default {
// album 组件

View File

@@ -1,5 +1,14 @@
/*
* @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>形式

View File

@@ -1,5 +1,6 @@
<template>
<view class="u-album">
<!-- 相册行容器每行显示 rowCount 个图片 -->
<view
class="u-album__row"
ref="u-album__row"
@@ -8,13 +9,15 @@
: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="previewFullImage ? onPreviewTap($event, getSrc(item)) : ''"
@tap="onPreviewTap($event, getSrc(item))"
>
<!-- 图片显示 -->
<image
:src="getSrc(item)"
:mode="
@@ -32,6 +35,7 @@
}
]"
></image>
<!-- 超出最大显示数量时的更多提示 -->
<view
v-if="
showMore &&
@@ -64,7 +68,7 @@ import { mixin } from '../../libs/mixin/mixin';
import { addUnit, sleep } from '../../libs/function/index';
import test from '../../libs/function/test';
// #ifdef APP-NVUE
// 由于weex为阿里的KPI业绩考核的产物所以不支持百分比单位这里需要通过dom查询组件的宽度
// 不支持百分比单位这里需要通过dom查询组件的宽度
const dom = uni.requireNativePlugin('dom')
// #endif
@@ -108,14 +112,20 @@ export default {
urls: {
immediate: true,
handler(newVal) {
// 当只有一张图片时,获取图片尺寸信息
if (newVal.length === 1) {
this.getImageRect()
}
}
}
},
emits: ["albumWidth"],
computed: {
/**
* 计算图片样式
* @param {Number} index1 - 行索引
* @param {Number} index2 - 列索引
* @returns {Object} 图片样式对象
*/
imageStyle() {
return (index1, index2) => {
const { space, rowCount, multipleSize, urls } = this,
@@ -139,11 +149,16 @@ export default {
return style
}
},
// 将数组划分为二维数组
/**
* 将图片地址数组划分为二维数组,用于按行显示
* @returns {Array} 二维数组,每个子数组代表一行图片
*/
showUrls() {
if (this.autoWrap) {
// 自动换行模式下,所有图片放在一行中显示
return [ this.urls.slice(0, this.maxCount) ];
} else {
// 固定行数模式下,按 rowCount 分割图片
const arr = []
this.urls.map((item, index) => {
// 限制最大展示数量
@@ -160,18 +175,29 @@ export default {
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长度等变化时重新计算图片的宽度
// 因为用户在某些特殊的情况下,需要让文字与相册宽度相等,所以这里事件的形式对外发送
/**
* 计算相册宽度,用于外部组件对齐
* 此变量无实际用途仅仅是为了利用computed特性让其在urls长度等变化时重新计算图片的宽度
* @returns {Number} 相册宽度
*/
albumWidth() {
let width = 0
if (this.urls.length === 1) {
@@ -185,59 +211,100 @@ export default {
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)
})
uni.previewImage({
current: url,
urls
})
// 是否阻止事件传播
this.stop && this.preventEvent(e)
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)
/**
* 单图时,获取图片的尺寸
* 在小程序中需要将网络图片的的域名添加到小程序的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
? this.singleSize
: (res.width / res.height) * this.singleSize
? singleSize
: (res.width / res.height) * singleSize
this.singleHeight = !isHorizotal
? this.singleSize
? singleSize
: (res.height / res.width) * this.singleWidth
// 如果有单位统一设置单位
if(unit != null && unit !== ''){
this.singleWidth = this.singleWidth + unit
this.singleHeight = this.singleHeight + unit
}
},
fail: () => {
// 获取图片信息失败时,通过组件宽度计算
this.getComponentWidth()
}
})
},
// 获取组件的宽度
/**
* 获取组件的宽度,用于计算单图显示尺寸
*/
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 &&
@@ -251,8 +318,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-album {
@include flex(column);
@@ -276,4 +341,4 @@ export default {
}
}
}
</style>
</style>

View File

@@ -1,11 +1,11 @@
/*
* @Author : LQ
* @Description :
* @version : 1.0
* @version : 3.0
* @Date : 2021-08-20 16:44:21
* @LastAuthor : LQ
* @lastTime : 2021-08-20 16:48:53
* @FilePath : /u-view2.0/uview-ui/libs/config/props/alert.js
* @LastAuthor : jry
* @lastTime : 2025-08-17 17:23:53
* @FilePath : /uview-plus/libs/config/props/alert.js
*/
export default {
// alert警告组件
@@ -17,6 +17,10 @@ export default {
showIcon: false,
effect: 'light',
center: false,
fontSize: 14
fontSize: 14,
transitionMode: 'fade',
duration: 0,
icon: '',
value: true
}
}

View File

@@ -1,5 +1,14 @@
/*
* @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: {
// 显示文字
@@ -41,6 +50,26 @@ export const props = defineMixin({
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
}
}
})

View File

@@ -1,6 +1,6 @@
<template>
<u-transition
mode="fade"
<up-transition
:mode="transitionMode"
:show="show"
>
<view
@@ -9,22 +9,25 @@
@tap.stop="clickHandler"
:style="[addStyle(customStyle)]"
>
<!-- 左侧图标 -->
<view
class="u-alert__icon"
v-if="showIcon"
>
<u-icon
<up-icon
:name="iconName"
size="18"
:color="iconColor"
></u-icon>
></up-icon>
</view>
<!-- 内容区域 -->
<view
class="u-alert__content"
:style="[{
paddingRight: closable ? '20px' : 0
}]"
>
<!-- 标题 -->
<text
class="u-alert__content__title"
v-if="title"
@@ -34,6 +37,7 @@
}]"
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
>{{ title }}</text>
<!-- 描述信息 -->
<text
class="u-alert__content__desc"
v-if="description"
@@ -44,19 +48,22 @@
: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"
>
<u-icon
name="close"
:color="iconColor"
size="15"
></u-icon>
<slot name="close">
<up-icon
name="close"
:color="iconColor"
size="15"
></up-icon>
</slot>
</view>
</view>
</u-transition>
</up-transition>
</template>
<script>
@@ -70,7 +77,7 @@
* @tutorial https://ijry.github.io/uview-plus/components/alertTips.html
*
* @property {String} title 显示的文字
* @property {String} type 使用预设的颜色 (默认 'warning'
* @property {String} type 使用预设的颜色 (默认 'warning'
* @property {String} description 辅助性文字颜色比title浅一点字号也小一点可选
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false
* @property {Boolean} showIcon 是否显示左边的辅助图标 默认 false
@@ -78,24 +85,34 @@
* @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 点击关闭按钮时触发
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
* @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';
@@ -117,25 +134,50 @@
}
}
},
emits: ["click","close"],
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')
this.$emit('close');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-alert {
position: relative;

View File

@@ -79,7 +79,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-avatar-group {
@include flex;

View File

@@ -23,12 +23,12 @@
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
<template v-if="mpAvatar && allowMp"></template>
<!-- #endif -->
<u-icon
<up-icon
v-else-if="icon"
:name="icon"
:size="fontSize"
:color="color"
></u-icon>
></up-icon>
<up-text
v-else-if="text"
:text="text"
@@ -151,7 +151,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-avatar {
@include flex;

View File

@@ -10,10 +10,10 @@
v-if="!$slots.default && !$slots.$default"
@click="backToTop"
>
<u-icon
<up-icon
:name="icon"
:custom-style="iconStyle"
></u-icon>
></up-icon>
<text
v-if="text"
class="u-back-top__text"
@@ -111,7 +111,6 @@
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-back-top-flex:1 !default;
$u-back-top-height:100% !default;
$u-back-top-background-color:#E1E1E1 !default;

View File

@@ -89,7 +89,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-badge-primary: $u-primary !default;
$u-badge-error: $u-error !default;

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-box {
/* #ifndef APP-NVUE */

View File

@@ -39,13 +39,13 @@
>
</template>
<template v-else>
<u-icon
<up-icon
v-if="icon"
:name="icon"
:color="iconColorCom"
:size="textSize * 1.35"
:customStyle="{ marginRight: '2px' }"
></u-icon>
></up-icon>
<slot>
<text
class="u-button__text"
@@ -87,12 +87,12 @@
>
</template>
<template v-else>
<u-icon
<up-icon
v-if="icon"
:name="icon"
:color="iconColorCom"
:size="textSize * 1.35"
></u-icon>
></up-icon>
<text
class="u-button__text"
:style="[
@@ -206,7 +206,7 @@ export default {
},
iconColorCom() {
// 如果是镂空状态设置了color就用color值否则使用主题颜色
// u-icon的color能接受一个主题颜色的值
// up-icon的color能接受一个主题颜色的值
if (this.iconColor) return this.iconColor;
if (this.plain) {
return this.color ? this.color : this.type;
@@ -306,8 +306,6 @@ export default {
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
/* #ifndef APP-NVUE */
@import "./vue.scss";
/* #endif */

View File

@@ -7,15 +7,16 @@
* @lastTime : 2021-08-20 16:52:43
* @FilePath : /u-view2.0/uview-ui/libs/config/props/calendar.js
*/
import { t } from '../../libs/i18n'
export default {
// calendar 组件
calendar: {
title: '日期选择',
title: t("up.calendar.chooseDates"),
showTitle: true,
showSubtitle: true,
mode: 'single',
startText: '开始',
endText: '结束',
startText: t("up.common.start"),
endText: t("up.common.end"),
customList: [],
color: '#3c9cff',
minDate: 0,
@@ -26,8 +27,8 @@ export default {
formatter: null,
showLunar: false,
showMark: true,
confirmText: '确定',
confirmDisabledText: '确定',
confirmText: t("up.common.confirm"),
confirmDisabledText: t("up.common.confirm"),
show: false,
closeOnClickOverlay: false,
readonly: false,
@@ -38,6 +39,10 @@ export default {
allowSameDay: false,
round: 0,
monthNum: 3,
weekText: ['一', '二', '三', '四', '五', '六', '日']
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
}
}

View File

@@ -49,9 +49,9 @@
},
// 星期文本
weekText: {
type: Boolean,
type: Array,
default: () => {
return ['一', '二', '三', '四', '五', '六', '日']
return []
}
},
},
@@ -69,7 +69,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-calendar-header {
display: flex;

View File

@@ -2,7 +2,7 @@
<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">{{ item.year }}{{ item.month }}</text>
<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>
@@ -12,11 +12,11 @@
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
<text class="u-calendar-month__days__day__select__info"
:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
:class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__info--disabled' : '']"
:style="[textStyle(item1)]">{{ item1.day }}</text>
<text v-if="getBottomInfo(index, index1, item1)"
class="u-calendar-month__days__day__select__buttom-info"
:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
:class="[(item1.disabled || isForbid(item1) ) ? 'u-calendar-month__days__day__select__buttom-info--disabled' : '']"
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
</view>
@@ -37,7 +37,8 @@
import { colorGradient } from '../../libs/function/colorGradient';
import test from '../../libs/function/test';
import defProps from '../../libs/config/props';
import dayjs from 'dayjs/esm/index'
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
import { t } from '../../libs/i18n'
export default {
name: 'u-calendar-month',
mixins: [mpMixin, mixin],
@@ -126,6 +127,14 @@
allowSameDay: {
type: Boolean,
default: false
},
forbidDays: {
type: Array,
default: () => []
},
forbidDaysToast: {
type: String,
default: ''
}
},
data() {
@@ -160,14 +169,14 @@
// #ifdef APP-NVUE
style.width = addUnit(dayWidth, 'px')
// #endif
style.height = addUnit(this.rowHeight)
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
// 之所以需要这么写是因为DCloud公司的iOS客户端导致的bug
style.paddingLeft = 0
style.paddingRight = 0
style.paddingBottom = 0
@@ -213,7 +222,7 @@
style.opacity = 0.7
}
} else if (this.selected.length === 1) {
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
// 之所以需要这么写,是因为uni-app的iOS客户端的bug
// 进行还原操作否则在nvue的iOSuni-app有bug会导致诡异的表现
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
@@ -284,6 +293,7 @@
mounted() {
this.init()
},
emits: ['monthSelected', 'updateMonthTop'],
methods: {
init() {
// 初始化默认选中
@@ -297,6 +307,20 @@
})
})
},
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))
@@ -362,6 +386,12 @@
this.item = item
const date = dayjs(item.date).format("YYYY-MM-DD")
if (item.disabled) return
if (this.isForbid(item)) {
uni.showToast({
title: this.forbidDaysToast
})
return
}
// 对上一次选择的日期数组进行深度克隆
let selected = deepClone(this.selected)
if (this.mode === 'single') {
@@ -393,7 +423,7 @@
if(this.rangePrompt) {
toast(this.rangePrompt)
} else {
toast(`选择天数不能超过 ${this.maxRange}`)
toast(t("up.calendar.daysExceed", { days: this.maxRange }))
}
return
}
@@ -459,7 +489,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-calendar-month-wrapper {
margin-top: 4px;

View File

@@ -147,6 +147,23 @@ export const props = defineMixin({
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
}
}
})

View File

@@ -2,9 +2,10 @@
<u-popup
:show="show"
mode="bottom"
closeable
:closeable="!pageInline"
@close="close"
:round="round"
:pageInline="pageInline"
:closeOnClickOverlay="closeOnClickOverlay"
>
<view class="u-calendar">
@@ -17,7 +18,7 @@
></uHeader>
<scroll-view
:style="{
height: addUnit(listHeight)
height: addUnit(listHeight, 'px')
}"
scroll-y
@scroll="onScroll"
@@ -42,6 +43,9 @@
:rangePrompt="rangePrompt"
:showRangePrompt="showRangePrompt"
:allowSameDay="allowSameDay"
:forbidDays="forbidDays"
:forbidDaysToast="forbidDaysToast"
:monthFormat="monthFormat"
ref="month"
@monthSelected="monthSelected"
@updateMonthTop="updateMonthTop"
@@ -69,11 +73,11 @@ import uHeader from './header.vue'
import uMonth from './month.vue'
import { props } from './props.js'
import util from './util.js'
import dayjs from 'dayjs/esm/index'
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, range, error, padZero } from '../../libs/function/index';
import { addUnit, getPx, range, error, padZero } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* Calendar 日历
@@ -184,9 +188,11 @@ export default {
subtitle() {
// 初始化时this.months为空数组所以需要特别判断处理
if (this.months.length) {
return `${this.months[this.monthIndex].year}${
this.months[this.monthIndex].month
}`
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 ''
}
@@ -244,7 +250,13 @@ export default {
return error('maxDate不能小于minDate时间')
}
// 滚动区域的高度
this.listHeight = this.rowHeight * 5 + 30
let bottomPadding = 0;
if (this.pageInline) {
bottomPadding = 0
} else {
bottomPadding = 30
}
this.listHeight = this.rowHeight * 5 + bottomPadding
this.setMonth()
},
close() {
@@ -401,8 +413,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-calendar {
&__confirm {
padding: 7px 18px;

View File

@@ -1,4 +1,4 @@
import dayjs from 'dayjs/esm/index'
import dayjs from '../u-datetime-picker/dayjs.esm.min.js';
export default {
methods: {
// 设置月份数据

View File

@@ -56,11 +56,11 @@
hover-class="u-hover-class"
:hover-stay-time="200"
>
<u-icon
<up-icon
size="28"
name="backspace"
color="#303133"
></u-icon>
></up-icon>
</view>
</view>
</view>
@@ -222,7 +222,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$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;

View 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'
}
}

View File

@@ -6,135 +6,129 @@ export const propsCard = defineMixin({
// 与屏幕两侧是否留空隙
full: {
type: Boolean,
default: false
default: () => defProps.card.full
},
// 标题
title: {
type: String,
default: ''
default: () => defProps.card.title
},
// 标题颜色
titleColor: {
type: String,
default: '#303133'
default: () => defProps.card.titleColor
},
// 标题字体大小
titleSize: {
type: [Number, String],
default: '15px'
default: () => defProps.card.titleSize
},
// 副标题
subTitle: {
type: String,
default: ''
default: () => defProps.card.subTitle
},
// 副标题颜色
subTitleColor: {
type: String,
default: '#909399'
default: () => defProps.card.subTitleColor
},
// 副标题字体大小
subTitleSize: {
type: [Number, String],
default: '13'
default: () => defProps.card.subTitleSize
},
// 是否显示外部边框只对full=false时有效(卡片与边框有空隙时)
border: {
type: Boolean,
default: true
default: () => defProps.card.border
},
// 用于标识点击了第几个
index: {
type: [Number, String, Object],
default: ''
default: () => defProps.card.index
},
// 用于隔开上下左右的边距,带单位的写法,如:"30px 30px""20px 20px 30px 30px"
margin: {
type: String,
default: '15px'
default: () => defProps.card.margin
},
// card卡片的圆角
borderRadius: {
type: [Number, String],
default: '8px'
default: () => defProps.card.borderRadius
},
// 头部自定义样式,对象形式
headStyle: {
type: Object,
default() {
return {};
}
default: () => defProps.card.headStyle
},
// 主体自定义样式,对象形式
bodyStyle: {
type: Object,
default() {
return {};
}
default: () => defProps.card.bodyStyle
},
// 底部自定义样式,对象形式
footStyle: {
type: Object,
default() {
return {};
}
default: () => defProps.card.footStyle
},
// 头部是否下边框
headBorderBottom: {
type: Boolean,
default: true
default: () => defProps.card.headBorderBottom
},
// 底部是否有上边框
footBorderTop: {
type: Boolean,
default: true
default: () => defProps.card.footBorderTop
},
// 标题左边的缩略图
thumb: {
type: String,
default: ''
default: () => defProps.card.thumb
},
// 缩略图宽高
thumbWidth: {
type: [String, Number],
default: '30px'
default: () => defProps.card.thumbWidth
},
// 缩略图是否为圆形
thumbCircle: {
type: Boolean,
default: false
default: () => defProps.card.thumbCircle
},
// 给headbodyfoot的内边距
padding: {
type: [String, Number],
default: '15px'
default: () => defProps.card.padding
},
paddingHead: {
type: [String, Number],
default: ''
default: () => defProps.card.paddingHead
},
paddingBody: {
type: [String, Number],
default: ''
default: () => defProps.card.paddingBody
},
paddingFoot: {
type: [String, Number],
default: ''
default: () => defProps.card.paddingFoot
},
// 是否显示头部
showHead: {
type: Boolean,
default: true
default: () => defProps.card.showHead
},
// 是否显示尾部
showFoot: {
type: Boolean,
default: true
default: () => defProps.card.showFoot
},
// 卡片外围阴影,字符串形式
boxShadow: {
type: String,
default: 'none'
default: () => defProps.card.boxShadow
}
}
})

View File

@@ -136,8 +136,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-card {
position: relative;
overflow: hidden;

View File

@@ -0,0 +1,333 @@
<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);
// 清除后续级别的选中值
for (let i = levelIndex + 1; i < this.selectedValueIndexs.length; i++) {
this.$set(this.selectedValueIndexs, i, undefined);
}
// 获取当前选中项
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>

View File

@@ -1,49 +1,64 @@
<template>
<view class="u-cate-tab">
<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="[current == index ? 'u-cate-tab__item-active' : '']"
: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-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">
<view class="u-cate-tab__page-item" :id="'item' + index"
v-for="(item , index) in tabList" :key="index">
<slot name="itemList" :item="item">
</slot>
<template v-if="!$slots['itemList']">
<view class="item-title">
<text>{{item[tabKeyName]}}</text>
</view>
<view class="item-container">
<template v-for="(item1, index1) in item.children" :key="index1">
<slot name="pageItem" :pageItem="item1">
<view class="thumb-box" >
<image class="item-menu-image" :src="item1.icon" mode=""></image>
<view class="item-menu-name">{{item1[itemKeyName]}}</view>
</view>
</slot>
</template>
</view>
</template>
</view>
<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: () => {
@@ -57,18 +72,39 @@
itemKeyName: {
type: String,
default: 'name'
},
current: {
type: Number,
default: 0
}
},
watch: {
tabList() {
this.getMenuItemTop()
}
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,
current: 0, // 预设当前项的值
innerCurrent: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
@@ -79,26 +115,32 @@
timer: null, // 定时器
}
},
onMounted() {
mounted() {
// this.observer();
this.innerCurrent = this.current;
this.leftMenuStatus(this.innerCurrent);
this.getMenuItemTop()
},
methods: {
addUnit,
// 点击左边的栏目切换
async swichMenu(index) {
if(this.arr.length == 0) {
await this.getMenuItemTop();
if (this.mode == 'follow') {
if(this.arr.length == 0) {
await this.getMenuItemTop();
}
this.scrollIntoView = 'item' + index;
}
if (index == this.current) return;
this.scrollRightTop = this.oldScrollTop;
if (index == this.innerCurrent) return;
this.$nextTick(function(){
this.scrollRightTop = this.arr[index];
this.current = index;
this.leftMenuStatus(index);
this.innerCurrent = index;
this.$emit('update:current', index);
})
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({
size: true
@@ -117,54 +159,73 @@
},
// 观测元素相交状态
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);
// 检测右边scroll-view的id为itemxx的元素与u-cate-tab__right-box的相交状态
// 如果跟.u-cate-tab__right-box底部相交就动态设置左边栏目的活动状态
this._observerList.push(observer);
// 检测相交状态
observer.relativeTo('.u-cate-tab__right-box', {
top: 0
}).observe('#item' + index, res => {
top: 10
}).observe('#item' + index, (res) => {
if (res.intersectionRatio > 0) {
let id = res.id.substring(4);
this.leftMenuStatus(id);
console.log('res', res);
// 修复:确保正确获取索引
let id = res.id ? res.id.substring(4) : index;
this.leftMenuStatus(parseInt(id));
}
})
})
},
// 设置左边菜单的滚动状态
async leftMenuStatus(index) {
this.current = 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到顶部的距离
getMenuItemTop() {
new Promise(resolve => {
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();
}, 10);
}, 100);
return ;
}
// console.log(rects)
this.rects = rects;
this.arr = [];
rects.forEach((rect) => {
// 这里减去rects[0].top是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
this.arr.push(rect.top - rects[0].top);
resolve();
})
// 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))
@@ -195,7 +256,7 @@
return ;
}
}
}, 10)
}, 100)
}
}
}
@@ -210,6 +271,7 @@
.u-cate-tab__wrap {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
}

View File

@@ -39,7 +39,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-cell-group-title-padding: 16px 16px 8px !default;
$u-cell-group-title-font-size: 15px !default;

View File

@@ -7,9 +7,9 @@
<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
<slot name="icon" v-if="$slots.icon">
</slot>
<u-icon v-else :name="icon"
<up-icon v-else :name="icon"
:custom-style="iconStyle"
:size="size === 'large' ? 22 : 18"></u-icon>
:size="size === 'large' ? 22 : 18"></up-icon>
</view>
<view class="u-cell__title">
<!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递这样才能解决collapse组件的slot不失效问题label暂时未用到 -->
@@ -30,9 +30,9 @@
</slot>
<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
<u-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon"
<up-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon"
:custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
:size="size === 'large' ? 18 : 16"></u-icon>
:size="size === 'large' ? 18 : 16"></up-icon>
<slot v-else name="right-icon">
</slot>
</view>
@@ -113,7 +113,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-cell-padding: 13px 15px !default;
$u-cell-font-size: 15px !default;

View File

@@ -100,8 +100,7 @@
values.push(child.name)
}
})
// 发出事件
this.$emit('change', values)
// 修改通过v-model绑定的值
// #ifdef VUE3
this.$emit("update:modelValue", values);
@@ -109,13 +108,14 @@
// #ifdef VUE2
this.$emit("input", values);
// #endif
// 放在最后更新否则change事件传出去的values不会更新
this.$emit('change', values)
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-checkbox-group {

View File

@@ -12,7 +12,7 @@
:style="[iconWrapStyle]"
>
<slot name="icon" :elIconSize="elIconSize" :elIconColor="elIconColor">
<u-icon
<up-icon
class="u-checkbox__icon-wrap__icon"
name="checkbox-mark"
:size="elIconSize"
@@ -177,7 +177,7 @@
const style = {}
if (!this.usedAlone) {
if (this.parentData.borderBottom && this.parentData.placement === 'row') {
error('检测到您将borderBottom设置为true需要同时将u-checkbox-group的placement设置为column才有效')
error('检测到您将borderBottom设置为true需要同时将up-checkbox-group的placement设置为column才有效')
}
// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
if (this.parentData.borderBottom && this.parentData.placement === 'column') {
@@ -197,13 +197,14 @@
// 支付宝小程序不支持provide/inject所以使用这个方法获取整个父组件在created定义避免循环引用
this.updateParentData()
if (!this.parent) {
error('u-checkbox必须搭配u-checkbox-group组件使用')
error('up-checkbox必须搭配up-checkbox-group组件使用')
}
let value = '';
// #ifdef VUE2
const value = this.parentData.value
value = this.parentData.value
// #endif
// #ifdef VUE3
const value = this.parentData.modelValue
value = this.parentData.modelValue
// #endif
// 设置初始化时是否默认选中的状态父组件u-checkbox-group的value可能是array所以额外判断
if (this.checked) {
@@ -248,7 +249,9 @@
}
},
emitEvent() {
this.$emit('change', this.isChecked)
this.$emit('change', this.isChecked, {
name: this.name
})
// 双向绑定
if (this.usedAlone) {
this.$emit('update:checked', this.isChecked)
@@ -281,7 +284,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-checkbox-icon-wrap-margin-right:6px !default;
$u-checkbox-icon-wrap-font-size:6px !default;
$u-checkbox-icon-wrap-border-width:1px !default;

View 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: width, 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>

View File

@@ -112,7 +112,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-circle-progress {
@include flex(row);

View File

@@ -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>

View File

@@ -220,7 +220,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-code-input-cursor-width: 1px;
$u-code-input-cursor-height: 20px;
$u-code-input-cursor-animation-duration: 1s;

View File

@@ -5,16 +5,16 @@
* @Date : 2021-08-20 16:44:21
* @LastAuthor : LQ
* @lastTime : 2021-08-20 16:55:27
* @FilePath : /u-view2.0/uview-ui/libs/config/props/code.js
* @FilePath : /uview-plus/libs/config/props/code.js
*/
import { t } from '../../libs/i18n'
export default {
// code 组件
code: {
seconds: 60,
startText: '获取验证码',
changeText: 'X秒重新获取',
endText: '重新获取',
startText: t("up.code.send"),
changeText: t("up.code.resendAfter"),
endText: t("up.code.resend"),
keepRunning: false,
uniqueKey: ''
}

View File

@@ -128,5 +128,4 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@@ -100,7 +100,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-col {
padding: 0;

View File

@@ -21,6 +21,11 @@ export default {
name: '',
icon: '',
duration: 300,
showRight: true
showRight: true,
titleStyle: {},
iconStyle: {},
rightIconStyle: {},
cellCustomStyle: {},
cellCustomClass: ''
}
}

View File

@@ -7,6 +7,13 @@ export const props = defineMixin({
type: String,
default: () => defProps.collapseItem.title
},
// 标题的样式
titleStyle: {
type: [Object, String],
default: () => {
return defProps.collapseItem.titleStyle
}
},
// 标题右侧内容
value: {
type: String,
@@ -62,5 +69,29 @@ export const props = defineMixin({
type: Boolean,
default: () => defProps.collapseItem.showRight
},
// 左侧图标样式
iconStyle: {
type: [Object, String],
default: () => {
return defProps.collapseItem.iconStyle
}
},
// 右侧箭头图标的样式
rightIconStyle: {
type: [Object, String],
default: () => {
return defProps.collapseItem.rightIconStyle
}
},
cellCustomStyle: {
type: [Object, String],
default: () => {
return defProps.collapseItem.cellCustomStyle
}
},
cellCustomClass: {
type: String,
default: () => defProps.collapseItem.cellCustomClass
}
}
})

View File

@@ -11,6 +11,8 @@
@click="clickHandler"
:arrowDirection="expanded ? 'up' : 'down'"
:disabled="disabled"
:customClass="cellCustomClass"
:customStyle="cellCustomStyle"
>
<!-- 微信小程序不支持因为微信中不支持 <slot name="title" #title />的写法 -->
<template #title>
@@ -22,7 +24,7 @@
</template>
<template #icon>
<slot name="icon">
<u-icon v-if="!$slots.icon && icon" :size="22" :name="icon"></u-icon>
<up-icon v-if="!$slots.icon && icon" :size="22" :name="icon"></up-icon>
</slot>
</template>
<template #value>
@@ -34,7 +36,7 @@
</template>
<template #right-icon>
<template v-if="showRight">
<u-icon v-if="!$slots['right-icon']" :size="16" name="arrow-right"></u-icon>
<up-icon v-if="!$slots['right-icon']" :size="16" name="arrow-right"></up-icon>
<slot name="right-icon">
</slot>
</template>
@@ -223,7 +225,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-collapse-item {

View File

@@ -87,5 +87,4 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -8,11 +8,11 @@
class="u-notice__left-icon"
v-if="icon"
>
<u-icon
<up-icon
:name="icon"
:color="color"
size="19"
></u-icon>
></up-icon>
</view>
</slot>
<swiper
@@ -40,19 +40,19 @@
class="u-notice__right-icon"
v-if="['link', 'closable'].includes(mode)"
>
<u-icon
<up-icon
v-if="mode === 'link'"
name="arrow-right"
:size="17"
:color="color"
></u-icon>
<u-icon
></up-icon>
<up-icon
v-if="mode === 'closable'"
name="close"
:size="16"
:color="color"
@click="close"
></u-icon>
></up-icon>
</view>
</view>
</template>
@@ -128,7 +128,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-notice {
@include flex;

View File

@@ -1,9 +1,10 @@
<template>
<view @click="handleClick">
<slot>复制</slot>
<slot>{{ t("up.common.copy") }}</slot>
</view>
</template>
<script>
import { t } from '../../libs/i18n'
export default {
name: "up-copy",
props: {
@@ -17,16 +18,17 @@ export default {
},
notice: {
type: String,
default: '复制成功'
default: t("up.common.copy") + t("up.common.success")
}
},
emits: ['success'],
methods: {
t,
handleClick() {
let content = this.content;
if (!content) {
uni.showToast({
title: '暂无',
title: t("up.common.none"),
icon: 'none',
duration: 2000,
});
@@ -42,7 +44,7 @@ export default {
success: function() {
if (that.alertStyle == 'modal') {
uni.showModal({
title: '提示',
title: "up.common.tip",
content: that.notice
});
} else {
@@ -55,7 +57,7 @@ export default {
},
fail:function(){
uni.showToast({
title: '复制失败',
title: t("up.common.copy") + t("up.common.fail"),
icon: 'none',
duration:3000,
});

View File

@@ -1,6 +1,7 @@
<template>
<view class="u-count-down">
<slot>
<slot :days="timeData.days" :hours="timeData.hours"
:minutes="timeData.minutes" :seconds="timeData.seconds">
<text class="u-count-down__text">{{ formattedTime }}</text>
</slot>
</view>
@@ -112,6 +113,7 @@
this.remainTime = remain
// 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象
const timeData = parseTimeData(remain)
this.timeData = timeData;
this.$emit('change', timeData)
// 得出格式化后的时间
this.formattedTime = parseFormat(this.format, timeData)
@@ -151,7 +153,6 @@
lang="scss"
scoped
>
@import "../../libs/css/components.scss";
$u-count-down-text-color:$u-content-color !default;
$u-count-down-text-font-size:15px !default;
$u-count-down-text-line-height:22px !default;

View File

@@ -178,8 +178,6 @@ export default {
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-count-num {
/* #ifndef APP-NVUE */
display: inline-flex;

View File

@@ -0,0 +1,406 @@
<template>
<view class="up-coupon" :class="[`up-coupon--${shape}`, `up-coupon--${type}`, `up-coupon--${size}`, {'up-coupon--disabled': disabled}]"
:style="[couponStyle]" @click="handleClick">
<view class="up-coupon__content">
<!-- 左侧金额区域 -->
<view class="up-coupon__amount">
<slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'left'">
<text class="up-coupon__amount-unit" v-if="unitPosition === 'left'">{{ unit }}</text>
</slot>
<slot name="amount" :amount="amount">
<text class="up-coupon__amount-value">{{ amount }}</text>
</slot>
<slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'right'">
<text class="up-coupon__amount-unit" v-if="unitPosition === 'right'">{{ unit }}</text>
</slot>
<slot name="limit" :limit="limit">
<text class="up-coupon__amount-limit" v-if="limit">{{ limit }}</text>
</slot>
</view>
<!-- 中间描述区域 -->
<view class="up-coupon__info">
<slot name="title" :title="title">
<text class="up-coupon__info-title">{{ title }}</text>
</slot>
<slot name="desc" :desc="desc">
<text class="up-coupon__info-desc" v-if="desc">{{ desc }}</text>
</slot>
<slot name="time" :time="time">
<text class="up-coupon__info-time" v-if="time">{{ time }}</text>
</slot>
</view>
<!-- 右侧操作区域 -->
<view class="up-coupon__action u-padding-right-20">
<slot name="action" :actionText="actionText" :circle="circle">
<up-tag type="error" :bgColor="type ? 'transparent' : '#eb433d'"
:borderColor="type ? '#eee' : '#eb433d'" borderRadius="6px"
size="medium" class="up-coupon__action-text"
:shape="circle ? 'circle': 'circle'">{{ actionText }}</up-tag>
</slot>
</view>
</view>
<!-- 红包绳子效果 -->
<view v-if="shape === 'envelope'" class="up-coupon__rope"></view>
<!-- 默认插槽可用于添加额外内容 -->
<slot></slot>
</view>
</template>
<script>
export default {
name: 'up-coupon',
props: {
// 金额
amount: {
type: [String, Number],
default: ''
},
// 金额单位
unit: {
type: String,
default: '¥'
},
// 单位位置
unitPosition: {
type: String,
default: 'left'
},
// 使用限制
limit: {
type: String,
default: ''
},
// 标题
title: {
type: String,
default: '优惠券'
},
// 描述
desc: {
type: String,
default: ''
},
// 有效期
time: {
type: String,
default: ''
},
// 操作按钮文字
actionText: {
type: String,
default: '使用'
},
// 形状coupon-优惠券, envelope-红包, card-卡片
shape: {
type: String,
default: 'coupon'
},
// 尺寸small, medium, large
size: {
type: String,
default: 'medium'
},
// 是否圆形按钮
circle: {
type: Boolean,
default: false
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 背景颜色
bgColor: {
type: String,
default: ''
},
// 文字颜色
color: {
type: String,
default: ''
},
// 内置背景类型
type: {
type: String,
default: ''
},
},
computed: {
couponStyle() {
const style = {};
if (this.bgColor) style.background = this.bgColor;
if (this.color) style.color = this.color;
return style;
},
dotCount() {
// 根据尺寸计算锯齿数量
const map = {
small: 8,
medium: 10,
large: 12
};
return map[this.size] || 10;
}
},
methods: {
handleClick() {
if (this.disabled) return;
this.$emit('click');
}
}
}
</script>
<style lang="scss" scoped>
.up-coupon {
position: relative;
overflow: hidden;
border-radius: 8rpx;
background: #ffebf0;
color: $u-main-color;
&--coupon {
border-radius: 16rpx;
overflow: hidden;
&::before {
content: '';
position: absolute;
left: -24rpx;
top: 50%;
transform: translateY(-50%);
width: 48rpx;
height: 48rpx;
background-color: #fff;
border-radius: 50%;
}
&::after {
content: '';
position: absolute;
right: -24rpx;
top: 50%;
transform: translateY(-50%);
width: 48rpx;
height: 48rpx;
background-color: #fff;
border-radius: 50%;
}
}
&--envelope {
border-radius: 16rpx;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
height: 20rpx;
background: repeating-linear-gradient(-45deg, #ffd000, #ffd000 10rpx, #ffa000 10rpx, #ffa000 20rpx);
}
}
&--card {
border-radius: 16rpx;
}
width: 100%;
&--small {
// width: 520rpx;
height: 160rpx;
}
&--medium {
// width: 600rpx;
height: 180rpx;
}
&--large {
// width: 700rpx;
height: 220rpx;
}
&--disabled {
opacity: 0.5;
}
&__content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 30rpx;
position: relative;
z-index: 2;
}
&__amount {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-left: 10rpx;
padding-right: 30rpx;
border-right: 1px dashed #ccc;
&-unit {
font-size: 24rpx;
font-weight: normal;
}
&-value {
font-size: 56rpx;
font-weight: bold;
color: red;
line-height: 1;
margin: 10rpx 0;
}
&-limit {
font-size: 24rpx;
opacity: 0.9;
}
}
&__info {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
padding-left: 30rpx;
&-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
&-desc {
font-size: 24rpx;
opacity: 0.9;
margin-bottom: 10rpx;
}
&-time {
font-size: 20rpx;
opacity: 0.8;
}
}
&__action {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
&__dots {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 30rpx 0;
z-index: 1;
}
&__dot {
width: 32rpx;
height: 32rpx;
background-color: #fff;
border-radius: 50%;
margin: 0 -16rpx;
z-index: 3;
}
&__rope {
position: absolute;
top: -40rpx;
left: 50%;
transform: translateX(-50%);
width: 80rpx;
height: 80rpx;
background: linear-gradient(to right, #ffd000, #ffa000);
border-radius: 40rpx 40rpx 0 0;
z-index: 1;
&::before {
content: '';
position: absolute;
top: 0;
left: -20rpx;
width: 20rpx;
height: 40rpx;
background: linear-gradient(to bottom, #ffd000, #ffa000);
border-radius: 10rpx 0 0 10rpx;
}
&::after {
content: '';
position: absolute;
top: 0;
right: -20rpx;
width: 20rpx;
height: 40rpx;
background: linear-gradient(to bottom, #ffd000, #ffa000);
border-radius: 0 10rpx 10rpx 0;
}
}
// 不同主题样式
&--primary {
background: linear-gradient(90deg, #43afff, #3b8cff);
color: #fff;
.up-coupon__amount {
border-right: 1px dashed #eee;
}
.up-coupon__amount-value {
color: #fff;
}
}
&--success {
background: linear-gradient(90deg, #67dda9, #19be6b);
color: #fff !important;
.up-coupon__amount {
border-right: 1px dashed #eee;
}
.up-coupon__amount-value {
color: #fff;
}
}
&--warning {
background: linear-gradient(90deg, #ff9739, #ff6a39);
color: #fff;
.up-coupon__amount {
border-right: 1px dashed #eee;
}
.up-coupon__amount-value {
color: #fff;
}
}
&--error {
background: linear-gradient(90deg, #ff7070, #ff4747);
color: #fff;
.up-coupon__amount {
border-right: 1px dashed #eee;
}
.up-coupon__amount-value {
color: #fff;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
* @lastTime : 2021-08-20 16:57:48
* @FilePath : /u-view2.0/uview-ui/libs/config/props/datetimePicker.js
*/
import { t } from '../../libs/i18n'
export default {
// datetimePicker 组件
datetimePicker: {
@@ -26,12 +27,18 @@ export default {
formatter: null,
loading: false,
itemHeight: 44,
cancelText: '取消',
confirmText: '确认',
cancelText: t("up.common.cancel"),
confirmText: t("up.common.confirm"),
cancelColor: '#909193',
confirmColor: '#3c9cff',
visibleItemCount: 5,
closeOnClickOverlay: false,
defaultIndex: []
defaultIndex: [],
inputBorder: 'surround',
disabled: false,
disabledColor: '',
placeholder: t("up.common.pleaseChoose"),
inputProps: {},
pageInline: false
}
}

File diff suppressed because one or more lines are too long

View File

@@ -5,15 +5,29 @@ export const props = defineMixin({
// 是否显示input
hasInput: {
type: Boolean,
default: () => false
default: false
},
inputProps: {
type: Object,
default: () => {
return {}
}
},
inputBorder: {
type: String,
default: () => defProps.input.inputBorder
},
disabled: {
type: Boolean,
default: () => false
default: () => defProps.input.disabled
},
disabledColor:{
type: String,
default: () => defProps.input.disabledColor
},
placeholder: {
type: String,
default: () => '请选择'
default: () => defProps.input.placeholder
},
format: {
type: String,
@@ -149,6 +163,11 @@ export const props = defineMixin({
defaultIndex: {
type: Array,
default: () => defProps.datetimePicker.defaultIndex
},
// 是否页面内展示
pageInline:{
type: Boolean,
default: () => defProps.datetimePicker.pageInline
}
}
})

View File

@@ -1,23 +1,21 @@
<template>
<view class="u-datetime-picker">
<view v-if="hasInput" class="u-datetime-picker__has-input"
@click="onShowByClickInput"
@click="onShowByClickInput"
>
<slot name="trigger" :value="inputValue">
<up-input
:placeholder="placeholder"
:readonly="!!showByClickInput"
border="surround"
v-model="inputValue"
:disabled="disabled"
v-bind="inputPropsInner"
></up-input>
<div class="input-cover">
</div>
<cover-view class="input-cover">
</cover-view>
</slot>
</view>
<u-picker
ref="picker"
:show="show || (hasInput && showByClickInput)"
:show="pageInline || show || (hasInput && showByClickInput)"
:popupMode="popupMode"
:closeOnClickOverlay="closeOnClickOverlay"
:columns="columns"
@@ -31,6 +29,7 @@
:cancelColor="cancelColor"
:confirmColor="confirmColor"
:toolbarRightSlot="toolbarRightSlot"
:pageInline="pageInline"
@close="close"
@cancel="cancel"
@confirm="confirm"
@@ -60,7 +59,7 @@
import { props } from './props';
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import dayjs from 'dayjs/esm/index';
import dayjs from './dayjs.esm.min.js';
import { range, error, padZero } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
@@ -111,6 +110,12 @@
watch: {
show(newValue, oldValue) {
if (newValue) {
// #ifdef VUE3
this.innerValue = this.correctValue(this.modelValue)
// #endif
// #ifdef VUE2
this.innerValue = this.correctValue(this.value)
// #endif
this.updateColumnValue(this.innerValue)
}
},
@@ -133,7 +138,17 @@
computed: {
// 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
propsChange() {
return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]
return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, this.modelValue]
},
// input的props
inputPropsInner() {
return {
border: this.inputBorder,
placeholder: this.placeholder,
disabled: this.disabled,
disabledColor: this.disabledColor,
...this.inputProps
}
}
},
mounted() {
@@ -162,6 +177,9 @@
case 'year-month':
format = 'YYYY-MM'
break;
case 'datehour':
format = 'YYYY-MM-DD HH'
break;
case 'datetime':
format = 'YYYY-MM-DD HH:mm'
break;
@@ -428,7 +446,10 @@
},
// 根据minDate、maxDate、minHour、maxHour等边界值判断各列的开始和结束边界值
getBoundary(type, innerValue) {
const value = new Date(innerValue)
let value = new Date(innerValue)
if(isNaN(value.getTime())){
value = new Date()
}
const boundary = new Date(this[`${type}Date`])
const year = dayjs(boundary).year()
let month = 1
@@ -467,14 +488,13 @@
if(!this.disabled){
this.showByClickInput = !this.showByClickInput
}
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-datetime-picker {
flex: 1;
&__has-input {

View File

@@ -14,11 +14,13 @@
v-if="dot"
class="u-divider__dot"
></text>
<text
v-else-if="text"
class="u-divider__text"
:style="[textStyle]"
>{{text}}</text>
<slot>
<text
v-if="!dot && text"
class="u-divider__text"
:style="[textStyle]"
>{{text}}</text>
</slot>
<u-line
:color="lineColor"
:customStyle="rightLineStyle"
@@ -95,7 +97,6 @@
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-divider-margin:15px 0 !default;
$u-divider-text-margin:0 15px !default;
$u-divider-dot-font-size:12px !default;

View File

@@ -0,0 +1,367 @@
<template>
<view class="u-dragsort"
:class="[direction == 'horizontal' ? 'u-dragsort--horizontal' : '', direction == 'all' ? 'u-dragsort--all' : '']">
<movable-area class="u-dragsort-area" :style="movableAreaStyle">
<movable-view v-for="(item, index) in list" :key="item.id" :id="`u-dragsort-item-${index}`"
class="u-dragsort-item" :class="{ 'dragging': dragIndex === index }"
:direction="direction === 'all' ? 'all' : direction" :x="item.x" :y="item.y" :inertia="false"
:disabled="!draggable || (item.draggable === false)" @change="onChange(index, $event)"
@touchstart="onTouchStart(index)" @touchend="onTouchEnd" @touchcancel="onTouchEnd">
<view class="u-dragsort-item-content">
<slot :item="item" :index="index">
{{ item.label }}
</slot>
</view>
</movable-view>
</movable-area>
</view>
</template>
<script>
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import { addStyle, addUnit, sleep } from '../../libs/function/index';
export default {
name: 'u-dragsort',
// #ifdef MP
mixins: [mpMixin, mixin,],
// #endif
// #ifndef MP
mixins: [mixin],
// #endif
props: {
initialList: {
type: Array,
required: true,
default: () => []
},
draggable: {
type: Boolean,
default: true
},
direction: {
type: String,
default: 'vertical',
validator: value => ['vertical', 'horizontal', 'all'].includes(value)
},
// 新增列数属性用于all模式
columns: {
type: Number,
default: 3
}
},
data() {
return {
list: [],
dragIndex: -1,
itemHeight: 40,
itemWidth: 80,
areaWidth: 0, // 可拖动区域宽度
areaHeight: 0, // 可拖动区域高度
originalPositions: [], // 保存原始位置
currentPosition: {
x: 0,
y: 0
}
};
},
computed: {
movableAreaStyle() {
if (this.direction === 'vertical') {
return {
height: `${this.list.length * this.itemHeight}px`,
width: '100%'
};
} else if (this.direction === 'horizontal') {
return {
height: '100%',
width: `${this.list.length * this.itemWidth}px`
};
} else {
// all模式计算网格布局所需的高度
const rows = Math.ceil(this.list.length / this.columns);
return {
height: `${rows * this.itemHeight}px`,
width: '100%'
};
}
}
},
emits: ['drag-end'],
async mounted() {
await this.$nextTick();
this.initList();
this.calculateItemSize();
this.calculateAreaSize();
},
methods: {
initList() {
// 初始化列表项的位置
this.list = this.initialList.map((item, index) => {
let x = 0, y = 0;
if (this.direction === 'horizontal') {
x = index * this.itemWidth;
y = 0;
} else if (this.direction === 'vertical') {
x = 0;
y = index * this.itemHeight;
} else {
// all模式网格布局
const col = index % this.columns;
const row = Math.floor(index / this.columns);
x = col * this.itemWidth;
y = row * this.itemHeight;
}
return {
...item,
x,
y
};
});
// 保存初始位置
this.saveOriginalPositions();
},
saveOriginalPositions() {
// 保存当前位置作为原始位置
this.originalPositions = this.list.map(item => ({
x: item.x,
y: item.y
}));
},
async calculateItemSize() {
// 计算项目尺寸
await sleep(30);
return new Promise((resolve) => {
uni.createSelectorQuery()
.in(this)
.select('.u-dragsort-item-content')
.boundingClientRect(res => {
if (res) {
this.itemHeight = res.height || 40;
this.itemWidth = res.width || 80;
// 更新所有项目的位置
this.updatePositions();
// 保存原始位置
this.saveOriginalPositions();
}
resolve(res);
})
.exec();
});
},
async calculateAreaSize() {
// 计算可拖动区域尺寸
await sleep(30);
return new Promise((resolve) => {
uni.createSelectorQuery()
.in(this)
.select('.u-dragsort-area')
.boundingClientRect(res => {
if (res) {
this.areaWidth = res.width || 300;
this.areaHeight = res.height || 300;
}
resolve(res);
})
.exec();
});
},
updatePositions() {
// 更新所有项目的位置
this.list.forEach((item, index) => {
if (this.direction === 'vertical') {
item.y = index * this.itemHeight;
item.x = 0;
} else if (this.direction === 'horizontal') {
item.x = index * this.itemWidth;
item.y = 0;
} else {
// all模式网格布局
const col = index % this.columns;
const row = Math.floor(index / this.columns);
item.x = col * this.itemWidth;
item.y = row * this.itemHeight;
}
});
},
onTouchStart(index) {
this.dragIndex = index;
// 保存当前位置作为原始位置
this.saveOriginalPositions();
},
onChange(index, event) {
if (!event.detail.source || event.detail.source !== 'touch') return;
this.currentPosition.x = event.detail.x;
this.currentPosition.y = event.detail.y;
// all模式下使用更智能的位置计算
if (this.direction === 'all') {
this.handleAllModeChange(index);
} else {
// 原有的垂直和水平模式逻辑
let itemSize = 0;
let targetIndex = -1;
if (this.direction === 'vertical') {
itemSize = this.itemHeight;
targetIndex = Math.max(0, Math.min(
Math.round(this.currentPosition.y / itemSize),
this.list.length - 1
));
} else if (this.direction === 'horizontal') {
itemSize = this.itemWidth;
targetIndex = Math.max(0, Math.min(
Math.round(this.currentPosition.x / itemSize),
this.list.length - 1
));
}
// 如果位置发生变化,则重新排序
if (targetIndex !== index) {
this.reorderItems(index, targetIndex);
}
}
},
handleAllModeChange(index) {
// 在all模式下根据当前位置计算最近的网格位置
const col = Math.max(0, Math.min(Math.round(this.currentPosition.x / this.itemWidth), this.columns - 1));
const row = Math.max(0, Math.round(this.currentPosition.y / this.itemHeight));
// 计算目标索引
let targetIndex = row * this.columns + col;
targetIndex = Math.max(0, Math.min(targetIndex, this.list.length - 1));
// 如果位置发生变化,则重新排序
if (targetIndex !== index) {
this.reorderItems(index, targetIndex);
}
},
reorderItems(fromIndex, toIndex) {
const movedItem = this.list.splice(fromIndex, 1)[0];
this.list.splice(toIndex, 0, movedItem);
// 震动反馈
if (uni.vibrateShort) {
uni.vibrateShort();
}
// 更新当前拖拽项目的新索引
this.dragIndex = toIndex;
// 更新所有项目的位置
this.updatePositions();
// 保存当前位置作为原始位置
this.saveOriginalPositions();
},
onTouchEnd() {
// 0.001是为了解决拖动过快等某些极限场景下位置还原不生效问题
if (this.direction === 'horizontal') {
this.list[this.dragIndex].x = this.currentPosition.x + 0.001;
} else if (this.direction === 'vertical' || this.direction === 'all') {
this.list[this.dragIndex].y = this.currentPosition.y + 0.001;
this.list[this.dragIndex].x = this.currentPosition.x + 0.001;
}
// 重置到位置,需要延迟触发动,否则无效。
sleep(50).then(() => {
this.list.forEach((item, index) => {
item.x = this.originalPositions[index].x;
item.y = this.originalPositions[index].y;
});
this.dragIndex = -1;
this.$emit('drag-end', [...this.list]);
});
}
},
watch: {
initialList: {
handler() {
this.$nextTick(() => {
this.initList();
});
},
deep: true
},
direction: {
handler() {
this.$nextTick(() => {
this.initList();
this.calculateItemSize();
this.calculateAreaSize();
});
}
},
columns: {
handler() {
if (this.direction === 'all') {
this.$nextTick(() => {
this.initList();
this.updatePositions();
this.saveOriginalPositions();
});
}
}
}
}
};
</script>
<style scoped lang="scss">
.u-dragsort {
width: 100%;
.u-dragsort-area {
width: 100%;
position: relative;
}
.u-dragsort-item {
position: absolute;
width: 100%;
&.dragging {
z-index: 1000;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.u-dragsort-item-content {
padding: 0px;
text-align: center;
box-sizing: border-box;
padding-bottom: 6px;
border-radius: 8rpx;
transition: all 0.3s ease;
}
}
&.u-dragsort--horizontal {
.u-dragsort-area {
display: flex;
white-space: nowrap;
height: auto;
}
.u-dragsort-item {
display: flex;
width: auto;
height: 100%;
}
}
&.u-dragsort--all {
.u-dragsort-area {
height: auto;
}
.u-dragsort-item {
width: auto;
height: auto;
}
}
}
</style>

View File

@@ -114,7 +114,6 @@
</script>
<style scoped lang="scss">
@import "../../libs/css/components.scss";
.u-dropdown-item__scroll {
background: #ffffff;
}

View File

@@ -8,24 +8,24 @@
<view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index" @tap.stop="menuClick(index)">
<view class="u-flex u-flex-row">
<text class="u-dropdown__menu__item__text" :style="{
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndexList.includes(index)) ? activeColor : inactiveColor,
fontSize: addUnit(titleSize)
}">{{item.title}}</text>
<view class="u-dropdown__menu__item__arrow" :class="{
'u-dropdown__menu__item__arrow--rotate': index === current
}">
<u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="addUnit(menuIconSize)" :color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon>
<up-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="addUnit(menuIconSize)" :color="index === current || highlightIndexList.includes(index) ? activeColor : '#c0c4cc'"></up-icon>
</view>
</view>
</view>
</view>
<view class="u-dropdown__content" :style="[contentStyle, {
transition: `opacity ${duration / 1000}s linear`,
top: addUnit(height),
height: contentHeight + 'px'
transition: `opacity ${duration / 1000}s, z-index ${duration / 1000}s linear`,
top: addUnit(height)
}]"
@tap="maskClick" @touchmove.stop.prevent>
<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle, {
}]">
<slot></slot>
</view>
<view class="u-dropdown__content__mask"></view>
@@ -71,8 +71,8 @@
zIndex: -1,
opacity: 0
},
// 让某菜单保持高亮的状态
highlightIndex: 99999,
// 让某菜单保持高亮的状态
highlightIndexList: [],
contentHeight: 0
}
},
@@ -129,6 +129,7 @@
// 展开时,设置下拉内容的样式
this.contentStyle = {
zIndex: 11,
height: this.contentHeight + 'px'
}
// 标记展开状态以及当前展开项的索引
this.active = true;
@@ -147,10 +148,11 @@
this.active = false;
this.current = 99999;
// 下拉内容的样式进行调整不透明度设置为0
this.contentStyle = {
zIndex: -1,
opacity: 0
}
this.contentStyle.zIndex = -1;
this.contentStyle.opacity = 0;
setTimeout(() => {
this.contentStyle.height = 0;
}, this.duration)
},
// 点击遮罩
maskClick() {
@@ -158,9 +160,13 @@
if (!this.closeOnClickMask) return;
this.close();
},
// 外部手动设置某菜单高亮
highlight(index = undefined) {
this.highlightIndex = index !== undefined ? index : 99999;
// 外部手动设置某菜单高亮
highlight(indexParams = undefined) {
if (Array.isArray(indexParams)) {
this.highlightIndexList = [...indexParams];
return;
}
this.highlightIndexList = indexParams !== undefined ? [indexParams] : [];
},
// 获取下拉菜单内容的高度
getContentHeight() {
@@ -181,7 +187,6 @@
</script>
<style scoped lang="scss">
@import "../../libs/css/components.scss";
.u-dropdown {
flex: 1;

View File

@@ -4,13 +4,13 @@
:style="[emptyStyle]"
v-if="show"
>
<u-icon
<up-icon
v-if="!isSrc"
:name="mode === 'message' ? 'chat' : `empty-${mode}`"
:size="iconSize"
:color="iconColor"
margin-top="14"
></u-icon>
></up-icon>
<image
v-else
:style="{
@@ -35,6 +35,7 @@
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import { addUnit, addStyle, deepMerge } from '../../libs/function/index';
import { t } from '../../libs/i18n'
/**
* empty 内容为空
* @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
@@ -62,21 +63,21 @@
data() {
return {
icons: {
car: '购物车为空',
page: '页面不存在',
search: '没有搜索结果',
address: '没有收货地址',
wifi: '没有WiFi',
order: '订单为空',
coupon: '没有优惠券',
favor: '暂无收藏',
permission: '无权限',
history: '无历史记录',
news: '无新闻列表',
message: '消息列表为空',
list: '列表为空',
data: '数据为空',
comment: '暂无评论',
car: t("up.empty.car"),
page: t("up.empty.page"),
search: t("up.empty.search"),
address: t("up.empty.address"),
wifi: t("up.empty.wifi"),
order: t("up.empty.order"),
coupon: t("up.empty.coupon"),
favor: t("up.empty.favor"),
permission: t("up.empty.permission"),
history: t("up.empty.history"),
news: t("up.empty.news"),
message: t("up.empty.message"),
list: t("up.empty.list"),
data: t("up.empty.data"),
comment: t("up.empty.comment"),
}
}
},
@@ -107,7 +108,6 @@
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-empty-text-margin-top:20rpx !default;
$u-empty-slot-margin-top:20rpx !default;

View File

@@ -9,6 +9,7 @@
<view class="u-float-button__main" @click="clickHandler" :style="{
backgroundColor: backgroundColor,
color: color,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
@@ -24,10 +25,11 @@
bottom: height
}">
<slot name="list">
<template v-for="item in list">
<template :key="index" v-for="(item, index) in list">
<view class="u-float-button__item" :style="{
backgroundColor: item?.backgroundColor ? item?.backgroundColor : backgroundColor,
color: item?.color ? item?.color : color,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
@@ -147,8 +149,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-float-button {
z-index: 999;
.show-list {

View File

@@ -15,7 +15,7 @@
v-if="required || leftIcon || label"
:style="{
width: addUnit(labelWidth || parentData.labelWidth),
marginBottom: parentData.labelPosition === 'left' ? 0 : '5px',
marginBottom: (labelPosition || parentData.labelPosition) === 'left' ? 0 : '5px',
}"
>
<!-- 为了块对齐 -->
@@ -29,10 +29,10 @@
class="u-form-item__body__left__content__icon"
v-if="leftIcon"
>
<u-icon
<up-icon
:name="leftIcon"
:custom-style="leftIconStyle"
></u-icon>
></up-icon>
</view>
<text
class="u-form-item__body__left__content__label"
@@ -62,7 +62,7 @@
v-if="!!message && parentData.errorType === 'message'"
class="u-form-item__body__right__message"
:style="{
marginLeft: addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))
marginLeft: addUnit((labelPosition || parentData.labelPosition) === 'top' ? 0 : (labelWidth || parentData.labelWidth))
}"
>{{ message }}</text>
</slot>
@@ -185,7 +185,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-form-item {
@include flex(column);
@@ -240,10 +239,8 @@
&__slot {
flex: 1;
/* #ifndef MP */
@include flex;
align-items: center;
/* #endif */
}
&__icon {

View File

@@ -25,7 +25,7 @@
* @property {String | Number} labelWidth 提示文字的宽度单位px ( 默认 45
* @property {String} labelAlign lable字体的对齐方式 ( 默认 left'
* @property {Object} labelStyle lable的样式对象形式
* @example <up-formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></up-form>
* @example <up-form labelPosition="left" :model="model1" :rules="rules" ref="form1"></up-form>
*/
export default {
name: "u-form",
@@ -126,7 +126,7 @@
});
},
// 对部分表单字段进行校验
async validateField(value, callback, event = null) {
async validateField(value, callback, event = null,options) {
// $nextTick是必须的否则model的变更可能会延后于此方法的执行
this.$nextTick(() => {
// 校验错误信息返回给回调方法用于存放所有form-item的错误信息
@@ -191,9 +191,11 @@
errorsRes.push(...errors);
childErrors.push(...errors);
}
child.message =
childErrors[0]?.message ? childErrors[0].message : null;
//没有配置或者配置了showErrorMsg为true时候才修改子组件message默认没有配置
if(!options||options?.showErrorMsg==true){
child.message =
childErrors[0]?.message ? childErrors[0].message : null;
}
if (i == (rules.length - 1)) {
resolve(errorsRes)
}
@@ -217,8 +219,12 @@
});
});
},
// 校验全部数据
validate(callback) {
/**
* 校验全部数据
* @param {Object} options
* @param {Boolean} options.showErrorMsg -是否显示校验信息,
*/
validate(options) {
// 开发环境才提示,生产环境不会提示
if (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) {
error('未设置rules请看文档说明如果已经设置请刷新页面。');
@@ -240,7 +246,7 @@
} else {
resolve(true)
}
});
},null,options);
});
});
},

View File

@@ -37,5 +37,4 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@@ -0,0 +1,434 @@
<template>
<view class="up-goods-sku">
<view @click="open">
<slot name="trigger"></slot>
</view>
<up-popup
v-model:show="show"
mode="bottom"
:closeable="pageInline ? false : closeable"
:pageInline="pageInline"
:border-radius="20"
@close="close"
>
<view class="up-goods-sku-container" :style="{padding: pageInline ? '0px' : ''}">
<view class="up-goods-sku__header">
<slot name="header">
<view class="up-goods-sku__header__image">
<image :src="goodsInfo.image || goodsInfo.picture" mode="aspectFill"></image>
</view>
<view class="up-goods-sku__header__info">
<view class="up-goods-sku__header__info__price">
<text class="up-goods-sku__header__info__price__symbol">¥</text>
<text class="up-goods-sku__header__info__price__value">{{ price }}</text>
</view>
<view class="up-goods-sku__header__info__stock">{{ t('up.goodsSku.stock') }} {{ stock }} {{ t('up.goodsSku.amount') }}</view>
<view class="up-goods-sku__header__info__selected">{{ t('up.goodsSku.choosed') }}: {{ selectedSkuText }}</view>
</view>
</slot>
</view>
<scroll-view class="up-goods-sku__content" scroll-y>
<view v-for="(treeItem, index) in skuTree" :key="index" class="up-goods-sku__content__item">
<view class="up-goods-sku__content__item__title">{{ treeItem.label }}</view>
<view class="up-goods-sku__content__item__list">
<view
v-for="(leafItem, leafIndex) in treeItem.children"
:key="leafIndex"
class="up-goods-sku__content__item__list__item"
:class="{
'up-goods-sku__content__item__list__item--active': isSelected(treeItem.name, leafItem.id),
'up-goods-sku__content__item__list__item--disabled': isDisabled(treeItem.name, leafItem.id)
}"
@click="onSkuClick(treeItem.name, leafItem)"
>
<text>{{ leafItem.name }}</text>
</view>
</view>
</view>
<view class="up-goods-sku__content__count">
<view class="up-goods-sku__content__count__title">{{ t('"up.goodsSku.buyAmount"') }}</view>
<view class="up-goods-sku__content__count__control">
<up-number-box
v-model="buyNum"
:min="1"
:max="maxBuyNum"
:disabled="!canBuy"
@change="onNumChange"
></up-number-box>
</view>
</view>
</scroll-view>
<view class="up-goods-sku__footer">
<up-button
type="primary"
:disabled="!canBuy"
@click="onConfirm"
>
{{ confirmText }}
</up-button>
</view>
</view>
</up-popup>
</view>
</template>
<script>
import { t } from '../../libs/i18n'
export default {
name: 'up-goods-sku',
props: {
// 商品信息
goodsInfo: {
type: Object,
default: () => ({})
},
// SKU树形结构
skuTree: {
type: Array,
default: () => []
},
// SKU列表
skuList: {
type: Array,
default: () => []
},
// 最大购买数量
maxBuy: {
type: Number,
default: 999
},
// 确认按钮文字
confirmText: {
type: String,
default: '确定'
},
// 是否显示关闭弹窗按钮
closeable: {
type: Boolean,
default: true
},
// 是否页面内联模式
pageInline: {
type: Boolean,
default: false
}
},
data() {
return {
show: false,
// 已选择的SKU
selectedSku: {},
// 购买数量
buyNum: 1
}
},
computed: {
// 当前价格
price() {
const selectedSkuComb = this.getSelectedSkuComb()
if (selectedSkuComb) {
return selectedSkuComb.price || selectedSkuComb.price_fee
}
return this.goodsInfo.price || this.goodsInfo.price_fee || 0
},
// 当前库存
stock() {
const selectedSkuComb = this.getSelectedSkuComb()
if (selectedSkuComb) {
return selectedSkuComb.stock || selectedSkuComb.quantity
}
return this.goodsInfo.stock || this.goodsInfo.quantity || 0
},
// 最大购买数量
maxBuyNum() {
const stock = this.stock
return stock > this.maxBuy ? this.maxBuy : stock
},
// 是否可以购买
canBuy() {
const selectedSkuCount = Object.keys(this.selectedSku).length
const skuTreeCount = this.skuTree.length
return selectedSkuCount === skuTreeCount && this.buyNum > 0 && this.stock > 0
},
// 已选SKU文字描述
selectedSkuText() {
const selected = []
Object.keys(this.selectedSku).forEach(key => {
const value = this.selectedSku[key]
if (value) {
this.skuTree.forEach(treeItem => {
if (treeItem.name === key) {
treeItem.children.forEach(leafItem => {
if (leafItem.id === value) {
selected.push(leafItem.name)
}
})
}
})
}
})
return selected.join(', ')
}
},
watch: {
},
emits: ['open', 'confirm', 'close'],
created() {
if (this.pageInline) {
this.show = true;
}
},
methods: {
t,
// 判断SKU是否被选中
isSelected(skuKey, skuValueId) {
return this.selectedSku[skuKey] === skuValueId
},
// 判断SKU是否禁用
isDisabled(skuKey, skuValueId) {
// 构造一个临时的已选中SKU对象
const tempSelected = { ...this.selectedSku, [skuKey]: skuValueId }
// 检查是否还有未选择的SKU维度
const selectedCount = Object.keys(tempSelected).filter(key => tempSelected[key]).length
const totalSkuCount = this.skuTree.length
// 如果所有SKU都已选择则检查组合是否存在
if (selectedCount === totalSkuCount) {
return !this.getSkuComb(tempSelected)
}
// 检查当前选择的SKU是否会导致无法组成有效组合
for (let i = 0; i < this.skuList.length; i++) {
const sku = this.skuList[i]
let match = true
// 检查已选中的SKU是否匹配
for (const key in tempSelected) {
if (tempSelected[key] && sku[key] !== tempSelected[key]) {
match = false
break
}
}
if (match) {
return false
}
}
return true
},
// SKU点击事件
onSkuClick(skuKey, skuValue) {
// 如果是禁用状态,直接返回
if (this.isDisabled(skuKey, skuValue.id)) {
return
}
// 如果已选中,则取消选中
if (this.selectedSku[skuKey] === skuValue.id) {
this.$set(this.selectedSku, skuKey, '')
} else {
this.$set(this.selectedSku, skuKey, skuValue.id)
}
},
// 数量改变事件
onNumChange(e) {
this.buyNum = e.value
},
// 获取选中的SKU组合
getSelectedSkuComb() {
return this.getSkuComb(this.selectedSku)
},
// 根据已选SKU获取组合信息
getSkuComb(selectedSku) {
const selected = { ...selectedSku }
// 过滤掉空值
Object.keys(selected).forEach(key => {
if (!selected[key]) {
delete selected[key]
}
})
// 检查是否所有SKU都已选择
if (Object.keys(selected).length !== this.skuTree.length) {
return null
}
// 查找匹配的SKU组合
for (let i = 0; i < this.skuList.length; i++) {
const sku = this.skuList[i]
let match = true
for (const key in selected) {
if (sku[key] !== selected[key]) {
match = false
break
}
}
if (match) {
return sku
}
}
return null
},
// 重置选择
reset() {
this.selectedSku = {}
this.buyNum = 1
},
open() {
this.show = true;
this.$emit('open')
},
// 关闭弹窗
close() {
this.false = true;
this.$emit('close')
},
// 确认选择
onConfirm() {
if (!this.canBuy) {
return
}
const selectedSkuComb = this.getSelectedSkuComb()
this.$emit('confirm', {
sku: selectedSkuComb,
goodsInfo: this.goodsInfo,
num: this.buyNum,
selectedText: this.selectedSkuText
})
}
}
}
</script>
<style lang="scss" scoped>
.up-goods-sku {
background-color: #fff;
overflow: hidden;
.up-goods-sku-container {
padding: 4rpx 30rpx;
}
&__header {
display: flex;
flex-direction: row;
padding: 30rpx 0;
position: relative;
&__image {
width: 180rpx;
height: 180rpx;
border-radius: 10rpx;
overflow: hidden;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
&__info {
flex: 1;
&__price {
display: flex;
flex-direction: row;
align-items: baseline;
margin-bottom: 20rpx;
&__symbol {
font-size: 24rpx;
color: #fa3534;
margin-right: 4rpx;
}
&__value {
font-size: 36rpx;
color: #fa3534;
font-weight: bold;
}
}
&__stock {
font-size: 26rpx;
color: #999;
margin-bottom: 20rpx;
}
&__selected {
font-size: 26rpx;
color: #333;
}
}
}
&__content {
max-height: 600rpx;
padding: 0 30rpx 30rpx 0;
&__item {
margin-bottom: 30rpx;
&__title {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
&__list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
&__item {
padding: 10rpx 20rpx;
border: 2rpx solid #eee;
border-radius: 10rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
font-size: 26rpx;
color: #333;
&--active {
border-color: #fa3534;
color: #fa3534;
}
&--disabled {
color: #ccc;
border-color: #eee;
}
}
}
}
&__count {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
&__title {
font-size: 28rpx;
color: #333;
}
}
}
&__footer {
padding: 20rpx 0rpx 40rpx 0;
}
}
</style>

View File

@@ -172,7 +172,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-grid-item-hover-class-opcatiy:.5 !default;
$u-grid-item-margin-top:1rpx !default;
$u-grid-item-border-right-width:0.5px !default;

View File

@@ -89,7 +89,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-grid-width:100% !default;
.u-grid {
/* #ifdef APP-NVUE */
@@ -106,7 +105,7 @@
// 在uni-app中应尽量避免使用flex布局以外的方式,因为nvue/uvue等方案都支持flex布局
// 这里使用grid布局使用为目前20240409uni-app在抖音小程序开启virtualHost时有bug存在事件失效问题。
/* #ifndef APP-NVUE */
display: grid;
display: grid !important;
grid-gap: v-bind(gap);
grid-template-columns: repeat(v-bind(col), 1fr);
/* #endif */

View File

@@ -35,24 +35,14 @@
</template>
<script>
// #ifdef APP-NVUE
// nvue通过weex的dom模块引入字体相关文档地址如下
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
const fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf'
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "uicon-iconfont",
'src': `url('${fontUrl}')`
})
// #endif
// 引入图标名称已经对应的unicode
import icons from './icons'
import icons from './icons';
import { props } from './props';
import config from '../../libs/config/config';
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import { addUnit, addStyle } from '../../libs/function/index';
import config from '../../libs/config/config';
import fontUtil from './util';
/**
* icon 图标
* @description 基于字体的图标集,包含了大多数常见场景的图标。
@@ -82,18 +72,12 @@
export default {
name: 'u-icon',
beforeCreate() {
// #ifdef APP-NVUE
if (this.customFontFamily) {
domModule.addRule('fontFace', {
'fontFamily': `${this.customPrefix}-${this.customFontFamily}`,
'src': `url('${this.customFontUrl}')`
})
if (!fontUtil.params.loaded) {
fontUtil.loadFont();
}
// #endif
},
data() {
return {
}
},
emits: ['click'],
@@ -102,7 +86,7 @@
uClasses() {
let classes = []
classes.push(this.customPrefix + '-' + this.name)
// uView的自定义图标类名为u-iconfont
// uview-plus内置图标类名为u-iconfont
if (this.customPrefix == 'uicon') {
classes.push('u-iconfont')
} else {
@@ -127,6 +111,9 @@
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
top: addUnit(this.top)
}
if (this.customPrefix !== 'uicon') {
style.fontFamily = this.customPrefix
}
// 非主题色值时,才当作颜色值
if (this.color && !config.type.includes(this.color)) style.color = this.color
@@ -147,7 +134,7 @@
icon() {
// 使用自定义图标的时候页面上会把name属性也展示出来所以在这里处理一下
if (this.customPrefix !== "uicon") {
return this.customIcons[this.customPrefix + '-' + this.name] || this.name;
return config.customIcons[this.name] || this.name;
}
// 如果内置的图标中找不到对应的图标就直接返回name值因为用户可能传入的是unicode代码
return icons['uicon-' + this.name] || this.name
@@ -166,8 +153,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
// 变量定义
$u-icon-primary: $u-primary !default;
$u-icon-success: $u-success !default;
@@ -176,13 +161,12 @@
$u-icon-error: $u-error !default;
$u-icon-label-line-height:1 !default;
/* #ifndef APP-NVUE */
// 非nvue下加载字体
/* #ifdef APP || MP-QQ || MP-TOUTIAO || MP-BAIDU || MP-KUAISHOU || MP-XHS */
// 2025/04/09在App/微信/支付宝/鸿蒙元服务已改用uni.loadFontFace加载字体
@font-face {
font-family: 'uicon-iconfont';
src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
}
/* #endif */
.u-icon {

View File

@@ -0,0 +1,68 @@
import config from '../../libs/config/config';
let params = {
loaded: false
};
// 加载字体方法
const loadFont = () => {
// console.log('加载字体图标');
// 全局加载不稳定默认关闭需要开启可以配置loadFontOnce。
if (config.loadFontOnce) {
params.loaded = true;
}
// #ifdef APP-NVUE
// nvue通过weex的dom模块引入字体相关文档地址如下
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
const domModule = weex.requireModule('dom');
domModule.addRule('fontFace', {
'fontFamily': "uicon-iconfont",
'src': `url('${config.iconUrl}')`
});
if (config.customIcon.family) {
domModule.addRule('fontFace', {
'fontFamily': config.customIcon.family,
'src': `url('${config.customIcon.url}')`
});
}
// #endif
// #ifdef APP || H5 || MP-WEIXIN || MP-ALIPAY
uni.loadFontFace({
global: true, // 是否全局生效。微信小程序 '2.10.0'起支持全局生效,需在 app.vue 中调用。
family: 'uicon-iconfont',
source: 'url("' + config.iconUrl + '")',
success() {
// console.log('内置字体图标加载成功');
},
fail() {
// console.error('内置字体图标加载出错');
}
});
if (config.customIcon.family) {
uni.loadFontFace({
global: true, // 是否全局生效。微信小程序 '2.10.0'起支持全局生效,需在 app.vue 中调用。
family: config.customIcon.family,
source: 'url("' + config.customIcon.url + '")',
success() {
// console.log('扩展字体图标加载成功');
},
fail() {
// console.error('扩展字体图标加载出错');
}
});
}
// #endif
// #ifdef APP-NVUE
// if (this.customFontFamily) {
// domModule.addRule('fontFace', {
// 'fontFamily': `${this.customPrefix}-${this.customFontFamily}`,
// 'src': `url('${this.customFontUrl}')`
// })
// }
// #endif
return true;
};
export default {
params: params,
loadFont
}

View File

@@ -36,22 +36,25 @@
}"
>
<slot name="loading">
<u-icon
<up-icon
:name="loadingIcon"
></u-icon>
></up-icon>
</slot>
</view>
<view
v-if="showError && isError && !loading"
class="u-image__error"
:style="{
borderRadius: shape == 'circle' ? '50%' : addUnit(radius)
borderRadius: shape == 'circle' ? '50%' : addUnit(radius),
backgroundColor: this.bgColor,
width: addUnit(width),
height: addUnit(height)
}"
>
<slot name="error">
<u-icon
<up-icon
:name="errorIcon"
></u-icon>
></up-icon>
</slot>
</view>
</view>
@@ -132,12 +135,12 @@
// #endif
// #ifndef APP-NVUE
if (this.loading || this.isError || this.width == '100%' || this.mode != 'heightFix') {
style.width = this.width;
style.width = addUnit(this.width);
} else {
style.width = 'fit-content';
}
if (this.loading || this.isError || this.height == '100%' || this.mode != 'widthFix') {
style.height = this.height;
style.height = addUnit(this.height);
} else {
style.height = 'fit-content';
}
@@ -153,12 +156,12 @@
// #endif
// #ifndef APP-NVUE
if (this.loading || this.isError || this.width == '100%' || this.mode != 'heightFix') {
style.width = this.width;
style.width = addUnit(this.width);
} else {
style.width = 'fit-content';
}
if (this.loading || this.isError || this.height == '100%' || this.mode != 'widthFix') {
style.height = this.height;
style.height = addUnit(this.height);
} else {
style.height = 'fit-content';
}
@@ -220,17 +223,15 @@
// 移除图片的背景色
removeBgColor() {
// 淡入动画过渡完成后将背景设置为透明色否则png图片会看到灰色的背景
this.backgroundStyle = {
backgroundColor: this.bgColor || '#ffffff'
};
// this.backgroundStyle = {
// backgroundColor: this.bgColor || '#ffffff'
// };
}
}
};
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-image-error-top:0px !default;
$u-image-error-left:0px !default;
$u-image-error-width:100% !default;

View File

@@ -13,7 +13,7 @@ export default {
text: '',
color: '#606266',
size: 14,
bgColor: '#dedede',
bgColor: '#f1f1f1',
height: 32
}
}

View File

@@ -4,6 +4,7 @@
<!-- #endif -->
<view
class="u-index-anchor u-border-bottom"
:class="{ 'u-index-anchor--sticky': parentSticky }"
:ref="`u-index-anchor-${text}`"
:style="{
height: addUnit(height),
@@ -16,7 +17,7 @@
fontSize: addUnit(size),
color: color
}"
>{{ text }}</text>
>{{ text.name || text }}</text>
</view>
<!-- #ifdef APP-NVUE -->
</header>
@@ -69,15 +70,24 @@
return error('u-index-anchor必须要搭配u-index-item组件使用')
}
// 设置u-index-item的id为anchor的text标识符因为非nvue下滚动列表需要依赖scroll-view滚动到元素的特性
indexListItem.id = this.text.charCodeAt(0)
if (typeof this.text == 'string') {
indexListItem.id = this.text.charCodeAt(0)
} else {
indexListItem.id = this.text.name.charCodeAt(0)
}
// #endif
}
},
computed: {
parentSticky() {
const indexList = $parent.call(this, "u-index-list");
return indexList ? indexList.sticky : true;
},
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-index-anchor {
position: sticky;
@@ -87,6 +97,11 @@
padding-left: 15px;
z-index: 1;
&--sticky {
position: sticky;
top: 0;
}
&__text {
@include flex;
align-items: center;

View File

@@ -85,6 +85,5 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@@ -15,6 +15,7 @@ export default {
indexList: [],
sticky: true,
customNavHeight: 0,
safeBottomFix: false
safeBottomFix: false,
itemMargin: '0rpx'
}
}

View File

@@ -32,5 +32,10 @@ export const props = defineMixin({
type: Boolean,
default: () => defProps.indexList.safeBottomFix
},
//自定义下边距
itemMargin: {
type: String,
default: () => defProps.indexList.itemMargin
},
}
})

View File

@@ -66,7 +66,7 @@
<text
class="u-index-list__letter__item__index"
:style="{color: activeIndex === index ? '#fff' : inactiveColor}"
>{{ item }}</text>
>{{ item.key || item }}</text>
</view>
</view>
<u-transition
@@ -87,7 +87,7 @@
width: addUnit(indicatorHeight)
}"
>
<text class="u-index-list__indicator__text">{{ uIndexList[activeIndex] }}</text>
<text class="u-index-list__indicator__text">{{ uIndexList[activeIndex]?.key || uIndexList[activeIndex] }}</text>
</view>
</u-transition>
</view>
@@ -284,8 +284,10 @@
return new Promise(resolve => {
// 延时一定时间以获取dom尺寸
// #ifndef APP-NVUE
this.$uGetRect('.u-index-list__scroll-view').then(size => {
resolve(size)
this.$nextTick(() => {
this.$uGetRect('.u-index-list__scroll-view').then(size => {
resolve(size)
})
})
// #endif
@@ -389,9 +391,14 @@
// 如果偏移量太小,前后得出的会是同一个索引字母,为了防抖,进行返回
if (currentIndex === this.activeIndex) return
this.activeIndex = currentIndex
this.$emit('select', this.uIndexList[currentIndex])
// #ifndef APP-NVUE || MP-WEIXIN
// 在非nvue中由于anchor和item都在u-index-item中所以需要对index-item进行偏移
this.scrollIntoView = `u-index-item-${this.uIndexList[currentIndex].charCodeAt(0)}`
if (typeof this.uIndexList[currentIndex] == 'string') {
this.scrollIntoView = `u-index-item-${this.uIndexList[currentIndex].charCodeAt(0)}`
} else {
this.scrollIntoView = `u-index-item-${this.uIndexList[currentIndex].name.charCodeAt(0)}`
}
// #endif
// #ifdef MP-WEIXIN
@@ -406,12 +413,13 @@
const anchors = this.anchors
// 由于list组件无法获取cell的top值这里通过header slot和各个item之间的height模拟出类似非nvue下的位置信息
let children = this.children.map((item, index) => {
const childHeight = item.height + getPx(this.itemMargin)
const child = {
height: item.height,
height: childHeight,
top: top
}
// 进行累加给下一个item提供计算依据
top = top + item.height
top = top + childHeight
// #ifdef APP-NVUE
// 只有nvue下需要将锚点的高度也累加非nvue下锚点高度是包含在index-item中的。
top = top + anchors[index].height
@@ -419,7 +427,7 @@
return child
})
// console.log('this.children[currentIndex].top', children[currentIndex].top)
if (children[currentIndex]?.top) {
if (children[currentIndex]?.top || children[currentIndex].top === 0) {
this.scrollTop = children[currentIndex].top - getPx(customNavHeight)
}
// #endif
@@ -489,12 +497,13 @@
const anchors = this.anchors
// 由于list组件无法获取cell的top值这里通过header slot和各个item之间的height模拟出类似非nvue下的位置信息
children = this.children.map((item, index) => {
const childHeight = item.height + getPx(this.itemMargin)
const child = {
height: item.height,
height: childHeight,
top: top
}
// 进行累加给下一个item提供计算依据
top = top + item.height
top = top + childHeight
// #ifdef APP-NVUE
// 只有nvue下需要将锚点的高度也累加非nvue下锚点高度是包含在index-item中的。
top = top + anchors[index].height
@@ -531,7 +540,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-index-list {

View File

@@ -3,9 +3,9 @@
* @Description :
* @version : 1.0
* @Date : 2021-08-20 16:44:21
* @LastAuthor : LQ
* @lastTime : 2021-08-20 17:13:55
* @FilePath : /u-view2.0/uview-ui/libs/config/props/input.js
* @LastAuthor : jry
* @lastTime : 2025-08-20 10:21:55
* @FilePath : /uview-plus/libs/config/props/input.js
*/
export default {
// index 组件
@@ -43,6 +43,8 @@ export default {
border: 'surround',
readonly: false,
shape: 'square',
formatter: null
formatter: null,
cursorColor: '',
passwordVisibilityToggle: true
}
}

View File

@@ -44,7 +44,12 @@ export const props = defineMixin({
// 是否显示清除控件
clearable: {
type: Boolean,
default: () => defProps.input.clearable
default: false
},
// 是否仅在聚焦时显示清除控件
onlyClearableOnFocused: {
type: Boolean,
default: true
},
// 是否密码类型
password: {
@@ -193,6 +198,16 @@ export const props = defineMixin({
ignoreCompositionEvent: {
type: Boolean,
default: true
},
// 光标颜色
cursorColor: {
type: String,
default: () => defProps.input.cursorColor
},
// 密码类型可见性切换
passwordVisibilityToggle: {
type: Boolean,
default: () => defProps.input.passwordVisibilityToggle
}
}
})

View File

@@ -6,11 +6,11 @@
v-if="prefixIcon || $slots.prefix"
>
<slot name="prefix">
<u-icon
<up-icon
:name="prefixIcon"
size="18"
:customStyle="prefixIconStyle"
></u-icon>
></up-icon>
</slot>
</view>
<view class="u-input__content__field-wrapper" @tap="clickHandler">
@@ -21,7 +21,7 @@
ref="input-native"
class="u-input__content__field-wrapper__field"
:style="[inputStyle]"
:type="type"
:type="showPassword && 'password' == type ? 'text' : type"
:focus="focus"
:cursor="cursor"
:value="innerValue"
@@ -34,11 +34,12 @@
:confirm-type="confirmType"
:confirm-hold="confirmHold"
:hold-keyboard="holdKeyboard"
:cursor-color="cursorColor"
:cursor-spacing="cursorSpacing"
:adjust-position="adjustPosition"
:selection-end="selectionEnd"
:selection-start="selectionStart"
:password="password || type === 'password' || false"
:password="isPassword"
:ignoreCompositionEvent="ignoreCompositionEvent"
@input="onInput"
@blur="onBlur"
@@ -53,23 +54,32 @@
v-if="isShowClear"
@click="onClear"
>
<u-icon
<up-icon
name="close"
size="11"
color="#ffffff"
customStyle="line-height: 12px"
></u-icon>
></up-icon>
</view>
<view
class="u-input__content__subfix-password-shower"
v-if="(type == 'password' || password) && passwordVisibilityToggle"
>
<up-icon @click="showPassword = !showPassword"
:name="showPassword ? 'eye-off' : 'eye-fill'"
size="18"
></up-icon>
</view>
<view
class="u-input__content__subfix-icon"
v-if="suffixIcon || $slots.suffix"
>
<slot name="suffix">
<u-icon
<up-icon
:name="suffixIcon"
size="18"
:customStyle="suffixIconStyle"
></u-icon>
></up-icon>
</slot>
</view>
</view>
@@ -105,6 +115,7 @@ import { addStyle, addUnit, deepMerge, formValidate, $parent, sleep, os } from '
* @property {Boolean} autoBlur 键盘收起时是否自动失去焦点目前仅App3.0.0+有效 默认 false
* @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距仅微信小程序且type=textarea时有效 默认 false
* @property {String Number} cursor 指定focus时光标的位置 默认 140
* @property {String } cursorColor 光标颜色
* @property {String Number} cursorSpacing 输入框聚焦时底部与键盘的距离 默认 30
* @property {String Number} selectionStart 光标起始位置自动聚集时有效需与selection-end搭配使用 默认 -1
* @property {String Number} selectionEnd 光标结束位置自动聚集时有效需与selection-start搭配使用 默认 -1
@@ -140,7 +151,8 @@ export default {
// value绑定值的变化是由内部还是外部引起的
changeFromInner: false,
// 过滤处理方法
innerFormatter: value => value
innerFormatter: value => value,
showPassword: false
};
},
created() {
@@ -176,10 +188,32 @@ export default {
}
},
computed: {
// 是否密码
isPassword() {
let ret = false;
if(this.password) {
ret = true;
} else if (this.type == 'password') {
ret = true;
} else {
ret = false;
}
if (this.showPassword) {
ret = false;
}
return ret;
},
// 是否显示清除控件
isShowClear() {
const { clearable, readonly, focused, innerValue } = this;
return !!clearable && !readonly && !!focused && innerValue !== "";
const { clearable, readonly, focused, innerValue, onlyClearableOnFocused } = this;
if (!clearable || readonly) {
return false;
}
if (onlyClearableOnFocused) {
return !!focused && innerValue !== "";
} else {
return innerValue !== "";
}
},
// 组件的类名
inputClass() {
@@ -206,7 +240,7 @@ export default {
if (this.border === "none") {
style.padding = "0";
} else {
// 由于uni-app的iOS开发者能力有限,导致需要分开写才有效
// 由于uni-app的iOS端限制,导致需要分开写才有效
style.paddingTop = "6px";
style.paddingBottom = "6px";
style.paddingLeft = "9px";
@@ -334,8 +368,6 @@ export default {
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-input {
@include flex(row);
align-items: center;

View File

@@ -7,6 +7,7 @@
* @lastTime : 2021-08-20 17:07:49
* @FilePath : /u-view2.0/uview-ui/libs/config/props/keyboard.js
*/
import { t } from '../../libs/i18n'
export default {
// 键盘组件
keyboard: {
@@ -23,8 +24,8 @@ export default {
show: false,
overlay: true,
zIndex: 10075,
cancelText: '取消',
confirmText: '确定',
cancelText: t("up.common.cancel"),
confirmText: t("up.common.confirm"),
autoChange: false
}
}

View File

@@ -132,7 +132,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-keyboard {

View File

@@ -19,7 +19,8 @@
<script>
import {
addUnit,
guid
guid,
rpx2px
} from '../../libs/function/index.js';
/**
* lazyLoad 懒加载
@@ -114,7 +115,7 @@
// 将threshold从rpx转为px
getThreshold() {
// 先取绝对值因为threshold可能是负数最后根据this.threshold是正数或者负数重新还原
let thresholdPx = uni.upx2px(Math.abs(this.threshold));
let thresholdPx = rpx2px(Math.abs(this.threshold));
return this.threshold < 0 ? -thresholdPx : thresholdPx;
},
// 计算图片的高度可能为auto带%,或者直接数值
@@ -223,6 +224,10 @@
if (res.intersectionRatio > 0) {
// 懒加载状态改变
this.isShow = true;
// 图片为空时显示错误
if (!this.image) {
this.loadError();
}
// 如果图片已经加载,去掉监听,减少性能的消耗
this.disconnectObserver('contentObserver');
}
@@ -238,8 +243,6 @@
</script>
<style scoped lang="scss">
@import "../../libs/css/components.scss";
.u-wrap {
background-color: #eee;
overflow: hidden;

View File

@@ -14,6 +14,7 @@ export default {
inactiveColor: '#ececec',
percentage: 0,
showText: true,
height: 12
height: 12,
fromRight: false,
}
}

View File

@@ -25,6 +25,11 @@ export const props = defineMixin({
height: {
type: [String, Number],
default: () => defProps.lineProgress.height
},
// 是否从右往左加载
fromRight: {
type: Boolean,
default: () => defProps.lineProgress.fromRight
}
}
})

View File

@@ -62,6 +62,11 @@
style.width = this.lineWidth
style.backgroundColor = this.activeColor
style.height = addUnit(this.height)
if (this.fromRight) {
style.right = 0;
} else {
style.left = 0;
}
return style
},
innserPercentage() {
@@ -108,7 +113,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-line-progress {
align-items: stretch;
@@ -127,7 +131,6 @@
&__line {
position: absolute;
top: 0;
left: 0;
bottom: 0;
align-items: center;
@include flex(row);

View File

@@ -55,7 +55,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-line {
/* #ifndef APP-NVUE */

View File

@@ -8,6 +8,7 @@
* @FilePath : /u-view2.0/uview-ui/libs/config/props/link.js
*/
import config from '../../libs/config/config'
import { t } from '../../libs/i18n'
const {
color
@@ -19,7 +20,7 @@ export default {
fontSize: 15,
underLine: false,
href: '',
mpTips: '链接已复制,请在浏览器打开',
mpTips: t("up.link.copyed"),
lineColor: '',
text: ''
}

View File

@@ -73,7 +73,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-link-line-height:1 !default;
.u-link {

View File

@@ -113,7 +113,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-list-item {}
</style>

View File

@@ -174,7 +174,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-list {
@include flex(column);

View File

@@ -193,7 +193,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-loading-icon-color: #c8c9cc !default;
$u-loading-icon-text-margin-left:4px !default;
$u-loading-icon-text-color:$u-content-color !default;

View File

@@ -7,10 +7,11 @@
* @lastTime : 2021-08-20 17:00:23
* @FilePath : /u-view2.0/uview-ui/libs/config/props/loadingPage.js
*/
import { t } from '../../libs/i18n'
export default {
// loading-page组件
loadingPage: {
loadingText: '正在加载',
loadingText: t("up.common.loading2"),
image: '',
loadingMode: 'circle',
loading: false,

View File

@@ -83,8 +83,6 @@ export default {
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$text-color: rgb(200, 200, 200) !default;
$text-size: 19px !default;
$u-loading-icon-margin-bottom: 10px !default;

View File

@@ -7,6 +7,7 @@
* @lastTime : 2021-08-20 17:15:26
* @FilePath : /u-view2.0/uview-ui/libs/config/props/loadmore.js
*/
import { t } from '../../libs/i18n'
export default {
// loadmore 组件
loadmore: {
@@ -17,9 +18,9 @@ export default {
iconSize: 17,
color: '#606266',
loadingIcon: 'spinner',
loadmoreText: '加载更多',
loadingText: '正在加载...',
nomoreText: '没有更多了',
loadmoreText: t("up.loadmoe.loadmore"),
loadingText: t("up.common.loading2") + '...',
nomoreText: t("up.loadmoe.nomore"),
isDot: false,
iconColor: '#b7b7b7',
marginTop: 10,

View File

@@ -123,7 +123,6 @@
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-loadmore {
@include flex(row);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,300 @@
<template>
<view class="up-markdown" :class="theme">
<up-parse :content="parsedContent" :previewImg="previewImg"></up-parse>
</view>
</template>
<script>
import { marked } from './marked.esm.js';
export default {
name: 'up-markdown',
props: {
// markdown内容
content: {
type: String,
default: ''
},
// 是否启用图片预览
previewImg: {
type: Boolean,
default: true
},
// 是否显示代码块行号
showLineNumber: {
type: Boolean,
default: false
},
// 主题样式 'light' | 'dark'
theme: {
type: String,
default: 'light'
}
},
data() {
return {
parsedContent: ''
};
},
watch: {
content: {
handler(newVal) {
this.parseMarkdown(newVal);
},
immediate: true
}
},
methods: {
// 解析markdown内容
parseMarkdown(content) {
if (!content) {
this.parsedContent = '';
return;
}
// 使用marked解析markdown
let parsed = marked(content);
// 处理代码块
parsed = this.handleCodeBlock(parsed);
// 应用主题样式
parsed = this.applyTheme(parsed);
this.parsedContent = parsed;
},
// 处理代码块
handleCodeBlock(html) {
// 添加代码块样式和行号
return html.replace(/<pre><code([^>]*)>([^<]+)<\/code><\/pre>/g, (match, lang, code) => {
const language = lang.match(/class="language-([^"]+)"/);
const langClass = language ? `language-${language[1]}` : '';
let result = `<pre class="up-markdown-code ${langClass}">`;
if (this.showLineNumber) {
// 添加行号
const lines = code.split('\n').filter(line => line.trim() !== '');
result += '<span class="up-markdown-line-numbers">';
lines.push('');
lines.forEach((_, index) => {
result += `<span class="up-markdown-line-number">${index + 1}</span>`;
});
result += '</span>';
}
result += `<code class='code-lang ${langClass}'>${code}</code></pre>`;
return result;
});
},
// 应用主题样式
applyTheme(html) {
// 可以根据theme属性添加不同的样式类
return html;
}
}
};
</script>
<style lang="scss" scoped>
.up-markdown {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 16px;
line-height: 1.6;
color: #333;
padding: 16px;
word-wrap: break-word;
/* 标题样式 */
::v-deep h1 {
font-size: 32px;
margin: 8px 0;
font-weight: bold;
}
::v-deep h2 {
font-size: 24px;
margin: 8px 0;
font-weight: bold;
}
::v-deep h3 {
font-size: 18px;
margin: 7px 0;
font-weight: bold;
}
::v-deep h4 {
font-size: 16px;
margin: 7px 0;
font-weight: bold;
}
::v-deep h5 {
font-size: 13px;
margin: 6px 0;
font-weight: bold;
}
::v-deep h6 {
font-size: 10px;
margin: 5px 0;
font-weight: bold;
}
/* 段落样式 */
::v-deep p {
margin: 16px 0;
}
/* 链接样式 */
::v-deep a {
color: #007AFF;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
/* 列表样式 */
::v-deep ul,
::v-deep ol {
margin: 16px 0;
padding-left: 32px;
li {
margin: 8px 0;
}
}
::v-deep ul li {
list-style-type: disc;
}
::v-deep ol li {
list-style-type: decimal;
}
/* 引用样式 */
::v-deep blockquote {
margin: 8px 0;
padding: 0 10px;
border-left: 4px solid #ccc;
color: #666;
}
/* 代码样式 */
::v-deep &-code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 14px;
background-color: #f6f8fa;
padding: 3px 6px;
border-radius: 3px;
display: flex;
}
::v-deep .code-lang {
width: 100%;
overflow-x: auto;
}
::v-deep pre {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 14px;
background-color: #f6f8fa;
padding: 16px;
overflow: auto;
border-radius: 6px;
margin: 16px 0;
::v-deep code {
background: none;
padding: 0;
}
}
/* 表格样式 */
::v-deep table {
border-collapse: collapse;
margin: 16px 0;
width: 100%;
th,
td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
th {
font-weight: 600;
}
tr:nth-child(2n) {
background-color: #f6f8fa;
}
}
/* 图片样式 */
::v-deep img {
max-width: 100%;
box-sizing: content-box;
background-color: #fff;
margin: 16px 0;
}
/* 分割线样式 */
::v-deep hr {
height: 1px;
padding: 0;
margin: 24px 0;
background-color: #e1e4e8;
border: 0;
}
/* 深色主题 */
&.dark {
color: #ccc;
background-color: #1e1e1e;
::v-deep &-code {
background-color: #2d2d2d;
color: #dcdcdc;
}
::v-deep pre {
background-color: #2d2d2d;
color: #dcdcdc;
}
::v-deep blockquote {
margin: 8px 0;
padding: 0 10px;
border-left: 4px solid #ccc;
color: #bbb;
}
::v-deep a {
color: #4da6ff;
}
}
}
/* 代码块行号样式 */
::v-deep .up-markdown-line-numbers {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-right: 10px;
margin-right: 10px;
border-right: 1px solid #ddd;
user-select: none;
.up-markdown-line-number {
color: #999;
font-size: 14px;
line-height: 1.6;
}
}
</style>

View File

@@ -7,14 +7,15 @@
* @lastTime : 2021-08-20 17:15:59
* @FilePath : /u-view2.0/uview-ui/libs/config/props/modal.js
*/
import { t } from '../../libs/i18n'
export default {
// modal 组件
modal: {
show: false,
title: '',
content: '',
confirmText: '确认',
cancelText: '取消',
confirmText: t("up.common.confirm"),
cancelText: t("up.common.cancel"),
showConfirmButton: true,
showCancelButton: false,
confirmColor: '#2979ff',
@@ -26,6 +27,10 @@ export default {
negativeTop: 0,
width: '650rpx',
confirmButtonShape: '',
contentTextAlign: 'left'
duration: 400,
contentTextAlign: 'left',
asyncCloseTip: t("up.common.inOperatio") + '...',
asyncCancelClose: false,
contentStyle: {}
}
}

View File

@@ -82,10 +82,30 @@ export const props = defineMixin({
type: String,
default: () => defProps.modal.confirmButtonShape
},
// 弹窗动画过度时间
duration: {
type: [Number],
default: defProps.modal.duration
},
// 文案对齐方式
contentTextAlign: {
type: String,
default: () => defProps.modal.contentTextAlign
},
// 异步确定时如果点击了取消时候的提示文案
asyncCloseTip: {
type: String,
default: () => defProps.modal.asyncCloseTip
},
// 是否异步关闭,只对取消按钮有效
asyncCancelClose: {
type: Boolean,
default: () => defProps.modal.asyncCancelClose
},
// 内容样式
contentStyle: {
type: Object,
default: () => defProps.modal.contentStyle
}
}
})

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