优化组件/更新

This commit is contained in:
gyq
2025-12-03 10:13:55 +08:00
parent 92f9776a35
commit 09b6e36a52
261 changed files with 22080 additions and 7238 deletions

View File

@@ -9,6 +9,7 @@
"jsbn": "^1.1.0",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"marked": "4.x",
"pinia-plugin-unistorage": "^0.1.2",
"to-arraybuffer": "^1.0.1",
"uview-plus": "^3.3.32",

View File

@@ -132,7 +132,6 @@ import CouponList from "./components/coupon-list.vue";
import { ref, onMounted, watch, reactive, computed } from "vue";
import { useSuperVipStore } from "@/store/market.js";
import * as memberApi from "@/http/api/market/member.js";
import { options } from "../../../cashier_wx/uni_modules/uview-plus/components/u-markdown/marked.esm";
const times = [
{

View File

@@ -70,6 +70,13 @@
"navigationBarTitleText": "全部操作"
}
},
{
"path": "pages/permission/secondary_page",
"pageId": "PAGES_SECONDARY_PAGE",
"style": {
"navigationBarTitleText": "更多操作"
}
},
{
"path": "pages/shopSetUp/decoration",
"pageId": "PAGES_DECORATION",
@@ -390,35 +397,35 @@
{
"root": "pageSalesSummary",
"pages": [{
"pageId": "PAGES_DATA_SUMMARY",
"path": "index",
"style": {
"navigationBarTitleText": "数据统计"
"pageId": "PAGES_DATA_SUMMARY",
"path": "index",
"style": {
"navigationBarTitleText": "数据统计"
}
},
{
"pageId": "PAGES_SALES_SUMMARY",
"path": "sales",
"style": {
"navigationBarTitleText": "销量统计"
}
},
{
"pageId": "PAGES_PRODUCT_SALES_RANKING",
"path": "productSalesRanking",
"style": {
"navigationBarTitleText": "商品销售排行"
}
},
{
"pageId": "PAGES_TABLE_SALES_RANKING",
"path": "table",
"style": {
"navigationBarTitleText": "桌台统计"
}
}
},
{
"pageId": "PAGES_SALES_SUMMARY",
"path": "sales",
"style": {
"navigationBarTitleText": "销量统计"
}
},
{
"pageId": "PAGES_PRODUCT_SALES_RANKING",
"path": "productSalesRanking",
"style": {
"navigationBarTitleText": "商品销售排行"
}
},
{
"pageId": "PAGES_TABLE_SALES_RANKING",
"path": "table",
"style": {
"navigationBarTitleText": "桌台统计"
}
}
]
]
},
{
"root": "pageLineUp",

View File

@@ -7,25 +7,21 @@
</view>
</view> -->
<view class="u-menu-wrap">
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="scrollTop"
:scroll-into-view="itemId">
<view v-for="(item,index) in tabbar" :key="index" class="u-tab-item"
:class="[current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{item.title}}</text>
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="scrollTop" :scroll-into-view="itemId">
<view v-for="(item, index) in tabbar" :key="index" class="u-tab-item" :class="[current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{ item.title }}</text>
</view>
</scroll-view>
<scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="right-box"
@scroll="rightScroll">
<scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="right-box" @scroll="rightScroll">
<view class="page-view">
<view class="class-item" :id="'item' + index" v-for="(item , index) in tabbar" :key="index">
<view class="item-title color-main">
<text>{{item.title}}</text>
<view class="class-item" :id="'item' + index" v-for="(item, index) in tabbar" :key="index">
<view class="item-title">
<text>{{ item.title }}</text>
</view>
<view class="item-container u-m-t-32">
<view class="thumb-box" @click="toPage(item1, index1)"
v-for="(item1, index1) in item.children" :key="index1">
<view class="thumb-box" @click="toPage(item1, index1)" v-for="(item1, index1) in item.children" :key="index1">
<image class="item-menu-image" :src="item1.miniIcon" mode=""></image>
<view class="item-menu-name u-m-t-16">{{item1.title}}</view>
<view class="item-menu-name u-m-t-16">{{ item1.title }}</view>
</view>
</view>
</view>
@@ -35,125 +31,137 @@
</view>
</template>
<script>
import {
useMenusStore
} from '@/store/menus.js';
const menusStore = useMenusStore()
export default {
data() {
return {
scrollTop: 0, //tab标题的滚动条位置
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
tabbar: [],
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
timer: null, // 定时器
isTabClickOver: true
import { useMenusStore } from '@/store/menus.js';
const menusStore = useMenusStore();
export default {
data() {
return {
scrollTop: 0, //tab标题的滚动条位置
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
tabbar: [],
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
timer: null, // 定时器
isTabClickOver: true
};
},
onLoad() {
this.init();
},
onReady() {
this.getMenuItemTop();
},
methods: {
toPage(item, index) {
console.log(item);
if (item.miniPath == 'pages/permission/secondary_page') {
uni.setStorageSync('secondaryPageMenus', item);
}
uni.navigateTo({
url: '/' + item.miniPath.replace(/^\/+|\/+$/g, ''),
fail(err) {
uni.showToast({
icon: 'none',
title: '无该页面路径或无权限访问该页面'
});
}
});
},
onLoad() {
this.init()
},
onReady() {
this.getMenuItemTop()
},
methods: {
toPage(item, index) {
console.log(item);
uni.navigateTo({
url: '/' + item.miniPath.replace(/^\/+|\/+$/g, ''),
fail(err) {
uni.showToast({
icon: 'none',
title: '无该页面路径或无权限访问该页面'
})
}
})
},
async init() {
const res = await menusStore.getMenus()
this.tabbar = res.filter(v => v.type == 0 && v.children.length && !v.hidden).map(v => {
async init() {
const res = await menusStore.getMenus();
this.tabbar = res
.filter((v) => v.type == 0 && v.children.length && !v.hidden)
.map((v) => {
return {
...v,
children: v.children.filter(child => child.type == 0 && !child.hidden)
}
})
console.log(this.tabbar);
},
// 点击左边的栏目切换
async swichMenu(index) {
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (index == this.current) return;
this.isTabClickOver = false;
this.scrollRightTop = this.oldScrollTop;
this.$nextTick(function() {
this.scrollRightTop = this.arr[index];
this.current = index;
this.leftMenuStatus(index);
})
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({
size: true
}, res => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
this.getElRect(elClass);
}, 10);
return;
children: v.children.filter((child) => child.type == 0 && !child.hidden)
};
});
console.log(this.tabbar);
},
// 点击左边的栏目切换
async swichMenu(index) {
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (index == this.current) return;
this.isTabClickOver = false;
this.scrollRightTop = this.oldScrollTop;
this.$nextTick(function () {
this.scrollRightTop = this.arr[index];
this.current = index;
this.leftMenuStatus(index);
});
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query
.select('.' + elClass)
.fields(
{
size: true
},
(res) => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
this.getElRect(elClass);
}, 10);
return;
}
this[dataVal] = res.height;
resolve();
}
this[dataVal] = res.height;
resolve();
}).exec();
})
},
// 观测元素相交状态
async observer() {
this.tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
// 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
// 如果跟.right-box底部相交就动态设置左边栏目的活动状态
observer.relativeTo('.right-box', {
)
.exec();
});
},
// 观测元素相交状态
async observer() {
this.tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
// 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
// 如果跟.right-box底部相交就动态设置左边栏目的活动状态
observer
.relativeTo('.right-box', {
top: 0
}).observe('#item' + index, res => {
})
.observe('#item' + index, (res) => {
if (res.intersectionRatio > 0) {
let id = res.id.substring(4);
this.leftMenuStatus(id);
}
})
})
},
// 设置左边菜单的滚动状态
async leftMenuStatus(index) {
if (!this.isTabClickOver) {
return;
}
this.current = index;
// 如果为0意味着尚未初始化
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menu-scroll-view', 'menuHeight');
await this.getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
},
// 获取右边菜单每个item到顶部的距离
getMenuItemTop() {
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
});
});
},
// 设置左边菜单的滚动状态
async leftMenuStatus(index) {
if (!this.isTabClickOver) {
return;
}
this.current = index;
// 如果为0意味着尚未初始化
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menu-scroll-view', 'menuHeight');
await this.getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
},
// 获取右边菜单每个item到顶部的距离
getMenuItemTop() {
new Promise((resolve) => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery
.selectAll('.class-item')
.boundingClientRect((rects) => {
// 如果节点尚未生成rects值为[](因为用selectAll所以返回的是数组),循环调用执行
if (!rects.length) {
setTimeout(() => {
@@ -165,172 +173,174 @@
// 这里减去rects[0].top是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
this.arr.push(rect.top - rects[0].top);
resolve();
})
}).exec()
})
},
// 右边菜单滚动
async rightScroll(e) {
this.oldScrollTop = e.detail.scrollTop;
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (this.timer) return;
if (!this.menuHeight) {
await this.getElRect('menu-scroll-view', 'menuHeight');
}
if (e.detail.scrollTop == 0) {
this.isTabClickOver = true;
// return swichMenu(0)
}
setTimeout(() => { // 节流
this.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
for (let i = 0; i < this.arr.length; i++) {
let height1 = this.arr[i];
let height2 = this.arr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
if (this.isTabClickOver) {
this.leftMenuStatus(i);
} else {
this.isTabClickOver = true;
}
return;
}
}
}, 10)
});
})
.exec();
});
},
// 右边菜单滚动
async rightScroll(e) {
this.oldScrollTop = e.detail.scrollTop;
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (this.timer) return;
if (!this.menuHeight) {
await this.getElRect('menu-scroll-view', 'menuHeight');
}
if (e.detail.scrollTop == 0) {
this.isTabClickOver = true;
// return swichMenu(0)
}
setTimeout(() => {
// 节流
this.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
for (let i = 0; i < this.arr.length; i++) {
let height1 = this.arr[i];
let height2 = this.arr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || (scrollHeight >= height1 && scrollHeight < height2)) {
if (this.isTabClickOver) {
this.leftMenuStatus(i);
} else {
this.isTabClickOver = true;
}
return;
}
}
}, 10);
}
}
};
</script>
<style lang="scss" scoped>
.u-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.u-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.u-search-box {
padding: 18rpx 30rpx;
}
.u-search-box {
padding: 18rpx 30rpx;
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-search-inner {
background-color: rgb(234, 234, 234);
border-radius: 100rpx;
display: flex;
align-items: center;
padding: 10rpx 16rpx;
}
.u-search-inner {
background-color: rgb(234, 234, 234);
border-radius: 100rpx;
display: flex;
align-items: center;
padding: 10rpx 16rpx;
}
.u-search-text {
font-size: 26rpx;
color: $u-tips-color;
margin-left: 10rpx;
}
.u-search-text {
font-size: 26rpx;
color: $u-tips-color;
margin-left: 10rpx;
}
.u-tab-view {
width: 240rpx;
height: 100%;
}
.u-tab-view {
width: 240rpx;
height: 100%;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 1;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 1;
}
.u-tab-item-active {
position: relative;
color: $my-main-color;
font-size: 30rpx;
font-weight: 600;
background: #fff;
}
.u-tab-item-active {
position: relative;
color: $my-main-color;
font-size: 30rpx;
font-weight: 600;
background: #fff;
}
.u-tab-item-active::before {
content: "";
position: absolute;
border-left: 4px solid $u-primary;
left: 0;
top: 0;
bottom: 0;
}
.u-tab-item-active::before {
content: '';
position: absolute;
border-left: 4px solid $u-primary;
left: 0;
top: 0;
bottom: 0;
}
.u-tab-view {
height: 100%;
background-color: #f6f6f6;
}
.u-tab-view {
height: 100%;
background-color: #f6f6f6;
}
.right-box {
// background-color: rgb(250, 250, 250);
}
.right-box {
// background-color: rgb(250, 250, 250);
}
.page-view {
padding: 30rpx 0;
}
.page-view {
padding: 30rpx 0;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
border-radius: 8rpx;
margin-left: 24rpx;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
border-radius: 8rpx;
margin-left: 24rpx;
}
.class-item:last-child {
min-height: 100vh;
}
.class-item:last-child {
min-height: 100vh;
}
.item-title {
font-size: 28rpx;
font-weight: bold;
}
.item-title {
font-size: 28rpx;
font-weight: bold;
}
.item-menu-name {
font-weight: normal;
font-size: 20rpx;
color: #333;
}
.item-menu-name {
font-weight: normal;
font-size: 20rpx;
color: #333;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.thumb-box {
width: 158rpx;
height: 170rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 20rpx;
margin-right: 24rpx;
}
.thumb-box {
width: 158rpx;
height: 170rpx;
// background: #ffffff;
// box-shadow: 0rpx 4rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
// border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 20rpx;
margin-right: 24rpx;
}
.item-menu-image {
width: 64rpx;
height: 64rpx;
}
</style>
.item-menu-image {
width: 94rpx;
height: 94rpx;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<view class="container">
<view class="row">
<view class="header">
<text class="t">{{ menus.title }}</text>
</view>
<view class="menu-wrap">
<view class="item" v-for="(val, i) in menus.children" :key="i" @click="to(val)">
<image :src="val.miniIcon" mode="aspectFit" class="icon"></image>
<view class="info">
<view class="title">
<text class="t">{{ val.title }}</text>
</view>
<view class="intro">
<text class="t">{{ val.intro || '-' }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
function to(item) {
uni.navigateTo({
url: item.miniPath
});
}
const menus = ref({});
onLoad(() => {
let pathObj = uni.getStorageSync('secondaryPageMenus');
pathObj.children = pathObj.children.filter((item) => {
return item.miniPath !== '';
});
console.log(pathObj);
menus.value = pathObj;
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.container {
padding: 0 28upx 28upx;
.row {
.header {
padding: 28upx 0;
.t {
font-size: 32upx;
font-weight: bold;
}
}
.menu-wrap {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-column-gap: 28upx;
grid-row-gap: 28upx;
.item {
display: flex;
background-color: #fff;
padding: 20upx;
border-radius: 20upx;
.icon {
$size: 80upx;
width: $size;
height: $size;
flex-shrink: 0;
}
.info {
display: flex;
padding-left: 20upx;
flex-direction: column;
.title {
.t {
font-size: 28upx;
font-weight: bold;
}
}
.intro {
.t {
font-size: 28upx;
color: #999;
}
}
}
}
}
}
}
</style>

1527
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,27 +43,32 @@ export const useMenusStore = defineStore('menus', {
return {
originMenus: [],
menuList: [],
allPages: allPages,//小程序全部页面
adminPages:[]//后台返回的全部页面
allPages: allPages, //小程序全部页面
adminPages: [] //后台返回的全部页面
};
},
actions: {
async getMenus() {
const data = await menusApi.getMenus()
this.originMenus = data;
const arr = flattenNestedChildren(data).filter(v =>{
return v.miniPath
const arr = flattenNestedChildren(data).filter(v => {
return v.miniPath
})
this.adminPages=arr;
this.adminPages = arr;
this.menuList = this.allPages.filter(v => {
const findItem = arr.find(item => item.miniPath == v.pageId || item.miniPath
.replace(/^\/+|\/+$/g, '') == v.allPath)
if (v.title == '经营工具') {
console.log('经营工具===', findItem);
}
return findItem
})
return this.originMenus
return this.originMenus
}
},
unistorage: true, // 开启后对 state 的数据读写都将持久化
}
);
});

View File

@@ -1,2 +1,4 @@
## 1.0.12024-08-09
更新文档
## 1.0.02023-09-19
first

View File

@@ -1,10 +1,10 @@
{
"id": "iRainna-dayjs",
"displayName": "dayjs",
"version": "1.0.0",
"description": "iRainna-dayjs",
"version": "1.0.1",
"description": "可直接在uniapp中使用dayjs的内置方法",
"keywords": [
"iRainna-dayjs"
"dayjs"
],
"repository": "",
"engines": {
@@ -35,7 +35,8 @@
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {

View File

@@ -1 +1,10 @@
# iRainna-dayjs
# Dayjs
Dayjs是一个极简的JavaScript库可以解析、验证、操作和显示日期和时间。
`uniapp-vue3`推荐使用[rainui](https://ext.dcloud.net.cn/plugin?id=19701)内置dayjs、loadsh、animate.css方便高效开发

View File

@@ -1,2 +1,69 @@
## 1.0.62025-06-10
+ 【重要】新增支持鸿蒙运行环境。
+ 【重要】新增支持微信小程序运行环境。
## 1.0.52024-06-27
+ 修复时区偏移量计算bug。
## 1.0.42024-06-02
+ 修复 `isBefore``isSame``isAfter``isSameOrBefore``isSameOrAfter``isBetween` 等比较方法时总是以当前时间为基准比较的bug。
+ 修复安卓部分方法指定日期参数无效的bug。
## 1.0.32024-05-06
+ 【重要】不再支持编译器 4.1 以下版本,请及时更新编译器为 4.1 以上版本以更好兼容后续更新迭代。
+ 【重要】适配支持 `app-js` 引擎版本。(即目前ios的js引擎版本)。
+ 【重要】`diff` api 默认单位调整为毫秒,和 [dayjs](https://day.js.org/docs/zh-CN/display/difference) 实现保持一致。
+ `diff` 方法支持传入构造的 `Dayjs` 对象,示例如下:
```
import { dayjs } from '@/uni_modules/kux-dayjs';
const date1 = dayjs('2024-05-06 12:23:53');
console.log(date1.diff(dayjs('2024-05-06'), 'M')); // 结果为743
```
+ 修复 `startOf` 和 `endOf` 时间基准总是为当前时间的问题。
+ 调整函数签名解决编译器4.1以上版本不兼容的问题。
+ 优化其他已知问题。
## 1.0.22024-03-23
+ 支持通过字符串形式初始化实例,目前支持的字符串格式如下:
+ YYYY-MM-DD
+ YYYY/MM/DD
+ YYYY-MM-DD HH
+ YYYY-MM-DD HH:MM
+ YYYY-MM-DD HH:MM:SS
+ YYYY-MM-DD HH:MM:SS.millis
+ YYYY/MM/DD HH
+ YYYY/MM/DD HH:MM
+ YYYY/MM/DD HH:MM:SS
+ YYYY/MM/DD HH:MM:SS.millis
+ ISO 8601 格式(包括 UTC 时间)
示例代码如下:
```
console.log(dayjs('2023-12-13').format('YYYY-MM-DD'));
console.log(dayjs('2024/01/01').format('YYYY-MM-DD'));
console.log(dayjs('2023-12-13 12:23').format('YYYY-MM-DD HH:mm'));
console.log(dayjs('2023-12-13 12:23:45').format('YYYY-MM-DD HH:mm:ss'));
console.log(dayjs('2023-12-12 19:35:35.123').format('YYYY-MM-DD HH:mm:ss.SSS'));
console.log(dayjs('2023-12-13T10:16:18.000Z').format('YYYY-MM-DD HH:mm:ss'));
console.log(dayjs('2023-12-13T12:25:36.567+08:00').format('YYYY-MM-DD HH:mm:ss.SSS'));
```
+ 支持通过时间戳初始化实例,由于 `uts` 联合类型限制,所以目前通过字符串形式的时间戳传入参数,示例代码如下:
```
console.log(dayjs(`${dayjs().valueOf()}`, true).format('YYYY-MM-DD HH:mm:ss'));
console.log(dayjs('1683234305000', true).format('YY-MM-DD HH:mm:ss'));
```
+ 补全类和函数类型签名
> **说明**
>
> `HBuilderX` 版本 4.0 及以上才支持。
+ 修复部分场景下毫秒丢失的问题。
## 1.0.12024-01-30
+ 支持web版本【hbx4.0及以上支持】
+ `解析` 增加 `YY` 年份两位数选项,示例 YY23
## 1.0.02023-12-14
初始发布

View File

@@ -59,6 +59,7 @@ export type InfoType = {
isLeap : boolean;
}
// #ifndef APP-HARMONY
export type LunarInfoType = {
lYear : number;
lMonth : number;
@@ -79,6 +80,30 @@ export type LunarInfoType = {
Term ?: string;
astro ?: string
}
// #endif
// #ifdef APP-HARMONY
export interface LunarInfoType {
lYear : number;
lMonth : number;
lDay : number;
IMonthCn : string;
IDayCn : string;
cYear : number;
cMonth : number;
cDay : number;
gzYear ?: string;
gzMonth ?: string;
gzDay ?: string;
isToday : boolean;
isLeap : boolean;
nWeek ?: number;
ncWeek ?: string;
isTerm ?: boolean;
Term ?: string;
astro ?: string
}
// #endif
export class Lunar {

View File

@@ -1,63 +1,159 @@
import { Lunar, LunarInfoType } from './calendar';
export type DateType = {
fullDate: string;
year: number;
month: number;
date: number;
hour: number;
minute: number;
second: number;
millisecond: number;
day: number;
isToday: boolean;
lunar: string;
fullDate : string;
year : number;
month : number;
date : number;
hour : number;
minute : number;
second : number;
millisecond : number;
day : number;
isToday : boolean;
lunar : string;
};
export type DateInfo = {
year : number,
month : number,
day : number,
hours : number,
minutes : number,
seconds : number,
milliseconds : number
};
export class DateUtil {
private lunar: Lunar;
constructor () {
private lunar : Lunar;
private static readonly dateRegex = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[ T](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,3}))?)?)?(?:[ ]?(Z|[+-]\d{2}:\d{2}))?$/;
constructor() {
this.lunar = new Lunar();
};
/**
* 计算阴历日期显示
*/
getlunar (year : number, month : number, date : number) : LunarInfoType {
getlunar(year : number, month : number, date : number) : LunarInfoType {
return this.lunar.solar2lunar(year, month, date)
}
/**
* 解析时间戳
*/
parseTimestamp(timestamp : number) : DateInfo {
let mTimestamp : number = timestamp;
// 定义每个时间单位的毫秒数
const millisecondsPerSecond : number = 1000;
const millisecondsPerMinute = 60 * millisecondsPerSecond;
const millisecondsPerHour = 60 * millisecondsPerMinute;
const millisecondsPerDay = 24 * millisecondsPerHour;
// 计算总天数
let totalDays = Math.floor(mTimestamp / millisecondsPerDay);
mTimestamp -= totalDays * millisecondsPerDay;
// 计算年、月、日
const startYear : number = 1970;
let year = startYear;
let month : number = 0;
let day : number = 0;
// 计算当前年是否为闰年
const isLeapYear = (year : number) : boolean => (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
// 计算年份
while (true) {
const daysInYear = isLeapYear(year) ? 366 : 365;
if (totalDays >= daysInYear) {
totalDays -= daysInYear;
year++;
} else {
break;
}
}
// 计算月份
const daysInMonth = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for (month = 0; month < daysInMonth.length; month++) {
if (totalDays < daysInMonth[month]) {
break;
}
totalDays -= daysInMonth[month];
}
month++; // 月份是从0开始的所以需要+1
day = totalDays + 1; // 加1是因为总天数已经减去了一整天的毫秒数
// 计算小时、分钟、秒
const hours = Math.floor(mTimestamp / millisecondsPerHour) % 24;
mTimestamp -= hours * millisecondsPerHour;
const minutes = Math.floor(mTimestamp / millisecondsPerMinute) % 60;
mTimestamp -= minutes * millisecondsPerMinute;
const seconds = Math.floor(mTimestamp / millisecondsPerSecond) % 60;
const milliseconds = mTimestamp % millisecondsPerSecond;
return {
year,
month,
day,
hours,
minutes,
seconds,
milliseconds
} as DateInfo;
}
/**
* 获取任意时间
*/
getDate (date: string): DateType {
let dd: Date = new Date();
let hour = 0;
let minute = 0;
let second = 0;
let milllisceond = 0;
if (date !== '') {
const datePart = date.split(" ");
const dateData = datePart[0].split("-");
const year = parseInt(dateData[0]);
const month = parseInt(dateData[1]);
const day = parseInt(dateData[2]);
if (datePart.length > 1) {
const timeData = datePart[1].split(":");
hour = parseInt(timeData[0]);
minute = parseInt(timeData[1]);
const secondPart = timeData[2].split(".");
second = parseInt(secondPart[0]);
if (secondPart.length > 1) {
milllisceond = parseInt(secondPart[1]);
}
getDate(date : string) : DateType {
let dd : Date = new Date();
// 使用正则表达式解析日期和时间
const match = date.match(DateUtil.dateRegex);
if (match != null && match[1] != null && match[2] != null && match[3] != null && match.length > 0) {
// 解构匹配结果
// const [_, year, month, day, hour, minute, second, milllisceond] = match;
const yearR = parseInt(match[1]!, 10);
const monthR = parseInt(match[2]!, 10);
const dayR = parseInt(match[3]!, 10);
let hourR = 0;
if (match.length >= 5 && match[4] != null) {
hourR = parseInt(match[4]!, 10);
}
let minuteR = 0;
if (match.length >= 6 && match[5] != null) {
minuteR = parseInt(match[5]!, 10);
}
let secondR = 0;
if (match.length >= 7 && match[6] != null) {
secondR = parseInt(match[6]!, 10);
}
let millisceondR = 0;
if (match.length >= 8 && match[7] != null) {
millisceondR = parseInt(match[7]!, 10);
}
let timezoneOffset: string | null = null;
if (match.length >= 9 && match[8] != null) {
timezoneOffset = match[8];
}
// 创建 Date 对象
dd = new Date(yearR, monthR - 1, dayR, hourR, minuteR, secondR, millisceondR);
// 应用时区偏移
if (timezoneOffset != null && timezoneOffset != 'Z') {
const offsetSign = (timezoneOffset.split(''))[0] == '+' ? 1 : -1;
const offsetHours = 8 - parseInt(timezoneOffset.substring(1, 3), 10);
const offsetMinutes = parseInt(timezoneOffset.substring(4, 6), 10);
const offsetMilliseconds = (offsetHours * 60 + offsetMinutes) * 60 * 1000 * offsetSign;
dd = new Date(dd.getTime() + offsetMilliseconds);
}
dd = new Date(year, month - 1, day, hour, minute, second, milllisceond);
}
const y = dd.getFullYear();
const m = dd.getMonth() + 1;
const d = dd.getDate();
@@ -65,11 +161,11 @@ export class DateUtil {
const M = dd.getMinutes();
const s = dd.getSeconds();
const ms = dd.getMilliseconds();
let nowDate = y + '-' + m + '-' + d;
const lunarData = this.getlunar(y, m, d);
const data: DateType = {
const data : DateType = {
fullDate: nowDate,
year: y,
month: m,
@@ -82,7 +178,7 @@ export class DateUtil {
lunar: lunarData.IDayCn,
isToday: this.getlunar(y, m, d).isToday
};
return data;
};
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
// #ifdef APP-ANDROID
import MathFloor from 'kotlin.math.floor';
// #endif
export const floor = (value: number): number => {
// #ifndef APP-ANDROID
return Math.floor(value);
// #endif
// #ifdef APP-ANDROID
return Number.from(MathFloor(value.toDouble()));
// #endif
}

View File

@@ -1,18 +1,19 @@
{
"id": "kux-dayjs",
"displayName": "kux-dayjs",
"version": "1.0.0",
"description": "一个极简的 `uts`API 设计完全参考 `dayjs` 的设计、所以上手该库基本零成本了,方便开发者们验证、操作和显示日期和时间。",
"version": "1.0.6",
"description": "一个极简的 uts 库API 设计完全参考 dayjs 的设计、所以上手该库基本零成本了,方便开发者们验证、操作和显示日期和时间。",
"keywords": [
"时间",
"日期",
"格式化",
"day",
"date"
"kux-dayjs",
"dayjs",
"时间格式化",
"日期"
],
"repository": "https://gitcode.net/kviewui/kviewui-x",
"repository": "https://gitcode.com/kviewui/kux-dayjs",
"engines": {
"HBuilderX": "^3.6.8"
"HBuilderX": "^3.6.8",
"uni-app": "^4.66",
"uni-app-x": "^4.66"
},
"dcloudext": {
"type": "uts",
@@ -32,54 +33,69 @@
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"tcb": "",
"aliyun": "√",
"alipay": "x"
},
"client": {
"Vue": {
"vue2": "n",
"vue3": "y"
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"App": {
"app-android": {
"minVersion": "19"
},
"app-ios": "n"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "n",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
}
}

View File

@@ -1,13 +1,17 @@
# kux-dayjs
`KuxDayjs` 是一个极简的 `uts`API 设计完全参考 `dayjs` 的设计、所以上手该库基本零成本了,方便开发者们验证、操作和显示日期和时间。
## 开源地址:[https://gitcode.com/kviewui/kux-dayjs](https://gitcode.com/kviewui/kux-dayjs)
## 目录结构
<ol>
<div class="kux-ol" style="list-style-type: none;">
<ol style="list-style-type: none;">
<li><a href="#import">导入插件</a></li>
<li><a href="#parse">解析</a>
<ol>
<li><a href="#parse_shili">实例</a></li>
<li><a href="#parse_dangqianshijian">当前时间</a></li>
<li><a href="#parse_string">字符串</a></li>
<li><a href="#parse_clone">克隆复制</a></li>
</ol>
</li>
@@ -64,33 +68,88 @@
<li><a href="#display_isLeapYear">是否闰年</a></li>
</ol>
</li>
<li><a href="#customType">自定义类型</a>
<ol>
<li><a href="#customType_InfoType">InfoType</a></li>
<li><a href="#customType_LunarType">LunarType</a></li>
<li><a href="#customType_DatetimeUnit">DatetimeUnit</a></li>
<li><a href="#customType_DiffUnit">DiffUnit</a></li>
<li><a href="#customType_DateFormat">DateFormat</a></li>
<li><a href="#customType_RelativeTime">RelativeTime</a></li>
<li><a href="#customType_FromToOptions">FromToOptions</a></li>
<li><a href="#customType_DatetimeOptions">DatetimeOptions</a></li>
<li><a href="#customType_IsBetweenContains">IsBetweenContains</a></li>
</ol>
</li>
</ol>
</div>
### 导入插件
<a id="import"></a>
### 导入插件
```ts
import { dayjs } from '@/uni_modules/kux-dayjs';
```
### 解析
<a id="parse"></a>
### 解析
#### 实例
<a id="parse_shili"></a>
#### 实例
代替修改本地Date.prototype`KuxDayjs``Date` 对象进行了封装,只需要调用 `dayjs()` 即可
`KuxDayjs` 对象是不可变的,也就是说,以某种方式改变 `KuxDayjs` 对象的所有API操作都将返回它的一个新实例。
#### 当前时间
<a id="parse_dangqianshijian"></a>
#### 当前时间
直接调用 `dayjs()` 将返回一个包含当前日期和时间的 `KuxDayjs` 对象。
```ts
const now = dayjs();
```
>目前仅支持传入 `YYYY-MM-DD HH:mm:ss.S` 格式的日期时间字符串
>
> `1.0.2` 及以上版本支持字符串解析。请看 [字符串](#parse_string)
<a id="parse_string"></a>
#### 字符串
`1.0.2` 及以上版本开始支持字符串解析,支持各种主流字符串格式,具体如下:
+ YYYY-MM-DD
+ YYYY/MM/DD
+ YYYY-MM-DD HH
+ YYYY-MM-DD HH:MM
+ YYYY-MM-DD HH:MM:SS
+ YYYY-MM-DD HH:MM:SS.millis
+ YYYY/MM/DD HH
+ YYYY/MM/DD HH:MM
+ YYYY/MM/DD HH:MM:SS
+ YYYY/MM/DD HH:MM:SS.millis
+ ISO 8601 格式(包括 UTC 时间)
示例代码
```
console.log(dayjs('2023-12-13').format('YYYY-MM-DD'));
console.log(dayjs('2024/01/01').format('YYYY-MM-DD'));
console.log(dayjs('2023-12-13 12:23').format('YYYY-MM-DD HH:mm'));
console.log(dayjs('2023-12-13 12:23:45').format('YYYY-MM-DD HH:mm:ss'));
console.log(dayjs('2023-12-12 19:35:35.123').format('YYYY-MM-DD HH:mm:ss.SSS'));
console.log(dayjs('2023-12-13T10:16:18.000Z').format('YYYY-MM-DD HH:mm:ss'));
console.log(dayjs('2023-12-13T12:25:36.567+08:00').format('YYYY-MM-DD HH:mm:ss.SSS'));
console.log(dayjs(`${dayjs().valueOf()}`, true).format('YYYY-MM-DD HH:mm:ss'));
console.log(dayjs('1683234305000', true).format('YY-MM-DD HH:mm:ss'));
```
> **参数说明**
>
> 初始化参数目前为三个,见下面表格
>
> | 参数名 | 类型 | 必填 | 默认值 | 说明
> | --- | --- | --- | --- | ---
> | date | string | 否 | | 日期时间字符串,支持字符串格式的时间戳解析
> | isTimestamp | boolean | 否 | false | 是否为时间戳,是的话内部会自动转为整数作为时间戳解析
> | isCST | boolean | 否 | true | 是否是中国标准时间既UTC时间+8小时`isTimestamp` 为 `true` 时生效,默认为 `true`
#### 克隆复制
<a id="parse_clone"></a>
#### 克隆复制
所有的 `KuxDayjs` 对象都是不可变的。 但如果有必要,使用 `dayjs().clone()` 可以复制出一个当前对象。
```ts
const a = dayjs();
@@ -98,11 +157,11 @@ const b = a.clone();
// a 和 b 是两个独立的 KuxDayjs 对象
```
### 取值/赋值
<a id="getset"></a>
### 取值/赋值
#### 毫秒
<a id="getset_millisecond"></a>
#### 毫秒
获取或设置毫秒。<br/>
传入0到999的数字。 如果超出这个范围,它会进位到秒。
```ts
@@ -110,8 +169,8 @@ dayjs().millisecond()
dayjs().millisecond(1)
```
#### 秒
<a id="getset_second"></a>
#### 秒
获取或设置秒。<br/>
传入0到59的数字。 如果超出这个范围,它会进位到分钟。
```ts
@@ -119,8 +178,8 @@ dayjs().second()
dayjs().second(1)
```
#### 分钟
<a id="getset_minute"></a>
#### 分钟
获取或设置分钟。<br/>
传入0到59的数字。 如果超出这个范围,它会进位到小时。
```ts
@@ -128,8 +187,8 @@ dayjs().minute()
dayjs().minute(59)
```
#### 小时
<a id="getset_hour"></a>
#### 小时
获取或设置小时。<br/>
传入0到23的数字。 如果超出这个范围,它会进位到天数。
```ts
@@ -137,8 +196,8 @@ dayjs().hour()
dayjs().hour(12)
```
#### 日期
<a id="getset_date"></a>
#### 日期
获取或设置日期。<br/>
传入1到31的数字。 如果超出这个范围,它会进位到月份。
```ts
@@ -147,8 +206,8 @@ dayjs().date(1)
```
> ****注意****<br/>`dayjs().date()` 是该月的日期。`dayjs().day()` 是星期几。
#### 星期
<a id="getset_day"></a>
#### 星期
获取或设置星期几。<br/>
传入 number 从0(星期天)到6(星期六)。 如果超出这个范围,它会进位到其他周。
```ts
@@ -157,8 +216,8 @@ dayjs().day(0)
```
> ****注意****<br/>`dayjs().date()` 是该月的日期。`dayjs().day()` 是星期几。
#### 时间
<a id="getset_time"></a>
#### 时间
获取或设置时间。<br/>
传入一个整数,表示从 1970-1-1 00:00:00 UTC 开始计时的毫秒数。 传入参数就是设置操作,否则为获取操作。
```ts
@@ -166,8 +225,8 @@ dayjs().time()
dayjs().time(1)
```
#### 月
<a id="getset_month"></a>
#### 月
获取或设置月份。<br/>
传入0到11的 number。 如果超出这个范围,它会进位到年份。
```ts
@@ -176,24 +235,24 @@ dayjs().month(0)
```
> ****注意****<br/>月份是从 0 开始计算的,即 1 月是 0。
#### 年
<a id="getset_year"></a>
#### 年
获取或设置年份。<br/>
```ts
dayjs().year()
dayjs().year(2000)
```
### 操作
<a id="manipulate"></a>
### 操作
您可能需要一些方法来操作 `KuxDayjs` 对象。<br/>
`KuxDayjs` 支持像这样的链式调用。
```ts
dayjs('2019-01-25').add(1, 'day').subtract(1, 'year').year(2009);
```
#### 增加
<a id="manipulate_add"></a>
#### 增加
返回增加一定时间的复制的 `KuxDayjs` 对象。
```ts
dayjs().add(7, 'day')
@@ -208,8 +267,8 @@ dayjs().add(7, 'day')
+ second 秒,缩写 `s`
+ millisecond 毫秒,缩写 `ms`
#### 减去
<a id="manipulate_subtract"></a>
#### 减去
返回减去一定时间的复制的 `KuxDayjs` 对象。
```ts
dayjs().subtract(7, 'day')
@@ -224,15 +283,15 @@ dayjs().subtract(7, 'day')
+ second 秒,缩写 `s`
+ millisecond 毫秒,缩写 `ms`
#### 今天
<a id="manipulate_today"></a>
#### 今天
返回设置时间为今天的复制的 `KuxDayjs` 对象。
```ts
dayjs().today()
```
#### 时间的开始
<a id="manipulate_startOf"></a>
#### 时间的开始
返回复制的 `KuxDayjs` 对象,并设置到一个时间的开始。
```ts
dayjs().startOf('year')
@@ -249,8 +308,8 @@ dayjs().startOf('year')
>****注意****<br/>暂时不支持跨年操作。
#### 时间的结束
<a id="manipulate_endOf"></a>
#### 时间的结束
返回复制的 `KuxDayjs` 对象,并设置到一个时间的末尾。
```ts
dayjs().endOf('month')
@@ -267,12 +326,12 @@ dayjs().endOf('month')
>****注意****<br/>暂时不支持跨年操作。
### 显示
<a id="display"></a>
### 显示
当解析和操作完成后,您需要一些方式来展示 `KuxDayjs` 对象。
#### 格式化
<a id="display_format"></a>
#### 格式化
根据传入的占位符返回格式化后的日期。
```ts
dayjs().format('YYYY-MM-DD HH:mm:ss')
@@ -284,6 +343,7 @@ dayjs().format('YYYY-MM-DD HH:mm:ss A')
| 标识 | 示例 | 描述 |
| --- | --- | --- |
| YYYY | 2023 | 年份,四位数 |
| YY | 23 | 年份,两位数 |
| MM | 12 | 月份 |
| DD | 01 | 日,两位数 |
| HH | 22 | 小时,两位数 |
@@ -294,8 +354,8 @@ dayjs().format('YYYY-MM-DD HH:mm:ss A')
| a | am/pm | 上/下午,小写 |
| AA | 上/下午 | 上/下午 |
#### 相对当前时间(前)
<a id="display_fromNow"></a>
#### 相对当前时间(前)
返回现在到当前实例的相对时间。
```ts
dayjs('2021-12-12').fromNow(); // 1年前
@@ -308,22 +368,22 @@ dayjs('2021-12-12').fromNow(); // 1年前
| relativeTime | `{s: '', m: '', mm: '', h: '', hh: '', d: '', dd: '', mon: '', mons: '', y: '', yy: ''}` | 自定义格式,变量说明见下方说明 |
#### RelativeTime 说明
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| 0 to 44seconds | s | %s | 几秒前 | %s seconds ago |
| 45 to 89 seconds | m | %m | 1分钟前 | %m minute ago |
| 90 seconds to 44 minutes | mm | %mm | 几分钟前 | %mm minutes ago |
| 45 to 89 minutes | h | %h | 1小时前 | %h hour ago |
| 90 minutes to 21 hours | hh | %hh | 几小时前 | %hh hours ago |
| 22 to 35 hours | d | d | %d | 1天前 | %d day ago |
| 22 to 35 hours | d | %d | 1天前 | %d day ago |
| 36 hours to 25 days | dd | %dd | 几天前 | %dd days ago |
| 26 to 45 days | mon | %mon | 1个月前 | %mon month ago |
| 46 days to 10 months | mons | %mons | 几个月前 | %mons months ago |
| 11 months to 17 months | y | %y | 1年前 | %y year ago |
| 18 months + | yy | %yy | 几年前 | %yy years ago |
#### 相对指定时间(前)
<a id="display_from"></a>
#### 相对指定时间(前)
返回 X 到当前实例的相对时间。
```ts
const datetime = dayjs('2021-12-12 15:43:58');
@@ -338,22 +398,22 @@ datetime.from(a); // 1年前
| relativeTime | `{s: '', m: '', mm: '', h: '', hh: '', d: '', dd: '', mon: '', mons: '', y: '', yy: ''}` | 自定义格式,变量说明见下方说明 |
#### RelativeTime 说明
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| 0 to 44seconds | s | %s | 几秒前 | %s seconds ago |
| 45 to 89 seconds | m | %m | 1分钟前 | %m minute ago |
| 90 seconds to 44 minutes | mm | %mm | 几分钟前 | %mm minutes ago |
| 45 to 89 minutes | h | %h | 1小时前 | %h hour ago |
| 90 minutes to 21 hours | hh | %hh | 几小时前 | %hh hours ago |
| 22 to 35 hours | d | d | %d | 1天前 | %d day ago |
| 22 to 35 hours | d | %d | 1天前 | %d day ago |
| 36 hours to 25 days | dd | %dd | 几天前 | %dd days ago |
| 26 to 45 days | mon | %mon | 1个月前 | %mon month ago |
| 46 days to 10 months | mons | %mons | 几个月前 | %mons months ago |
| 11 months to 17 months | y | %y | 1年前 | %y year ago |
| 18 months + | yy | %yy | 几年前 | %yy years ago |
#### 相对当前时间(后)
<a id="display_toNow"></a>
#### 相对当前时间(后)
返回当前实例到现在的相对时间。
```ts
dayjs('2021-12-12').toNow(); // 1年后
@@ -366,22 +426,22 @@ dayjs('2021-12-12').toNow(); // 1年后
| relativeTime | `{s: '', m: '', mm: '', h: '', hh: '', d: '', dd: '', mon: '', mons: '', y: '', yy: ''}` | 自定义格式,变量说明见下方说明 |
#### RelativeTime 说明
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| 0 to 44seconds | s | %s | 几秒后 | %s seconds after |
| 45 to 89 seconds | m | %m | 1分钟后 | %m minute after |
| 90 seconds to 44 minutes | mm | %mm | 几分钟后 | %mm minutes after |
| 45 to 89 minutes | h | %h | 1小时后 | %h hour after |
| 90 minutes to 21 hours | hh | %hh | 几小时后 | %hh hours after |
| 22 to 35 hours | d | d | %d | 1天后 | %d day after |
| 22 to 35 hours | d | %d | 1天后 | %d day after |
| 36 hours to 25 days | dd | %dd | 几天后 | %dd days after |
| 26 to 45 days | mon | %mon | 1个月后 | %mon month after |
| 46 days to 10 months | mons | %mons | 几个月后 | %mons months after |
| 11 months to 17 months | y | %y | 1年后 | %y year after |
| 18 months + | yy | %yy | 几年后 | %yy years after |
#### 相对指定时间(后)
<a id="display_to"></a>
#### 相对指定时间(后)
返回当前实例到现在的相对时间。
```ts
const datetime = dayjs('2021-12-12 15:43:58');
@@ -396,22 +456,22 @@ console.log(datetime.to(a)); // 输出 1年后
| relativeTime | `{s: '', m: '', mm: '', h: '', hh: '', d: '', dd: '', mon: '', mons: '', y: '', yy: ''}` | 自定义格式,变量说明见下方说明 |
#### RelativeTime 说明
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| 范围 | 键 | 使用变量 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| 0 to 44seconds | s | %s | 几秒后 | %s seconds after |
| 45 to 89 seconds | m | %m | 1分钟后 | %m minute after |
| 90 seconds to 44 minutes | mm | %mm | 几分钟后 | %mm minutes after |
| 45 to 89 minutes | h | %h | 1小时后 | %h hour after |
| 90 minutes to 21 hours | hh | %hh | 几小时后 | %hh hours after |
| 22 to 35 hours | d | d | %d | 1天后 | %d day after |
| 22 to 35 hours | d | %d | 1天后 | %d day after |
| 36 hours to 25 days | dd | %dd | 几天后 | %dd days after |
| 26 to 45 days | mon | %mon | 1个月后 | %mon month after |
| 46 days to 10 months | mons | %mons | 几个月后 | %mons months after |
| 11 months to 17 months | y | %y | 1年后 | %y year after |
| 18 months + | yy | %yy | 几年后 | %yy years after |
#### 日历时间
<a id="display_calendar"></a>
#### 日历时间
传入指定年月获取指定年月的日历面板数据,指定年月省略时默认获取当年年月的数据。返回 `DateFormat[]``DateFormat` 见下方说明。
```ts
dayjs().calendar();
@@ -458,8 +518,8 @@ dayjs().calendar();
| lunarD | `number` | 农历日期数字
| isLeap | `boolean` | 是否闰月
#### 差异Diff
<a id="display_diff"></a>
#### 差异Diff
返回指定单位下两个日期时间之间的差异。
```ts
const date1 = dayjs('2019-01-25');
@@ -486,70 +546,69 @@ date1.diff('2018-06-05', 'month', true) // 7.645161290322581;
+ second 秒,缩写 `s`
+ millisecond 毫秒,缩写 `ms`
#### Unix时间戳毫秒
<a id="display_valueOf"></a>
#### Unix时间戳毫秒
返回当前实例的 UNIX 时间戳13位数字毫秒
```ts
dayjs('2019-01-25').valueOf() // 1548381600000
```
#### Unix时间戳
<a id="display_unix"></a>
#### Unix时间戳
返回当前实例的 UNIX 时间戳10位数字
```ts
dayjs('2019-01-25').valueOf() // 1548381600
```
此值不包含毫秒信息,会进位到秒。
#### 获取月天数
<a id="display_daysInMonth"></a>
#### 获取月天数
获取当前月份包含的天数。
```ts
const date = dayjs('2023-12-12'); // 31
```
#### 转Date
<a id="display_toDate"></a>
#### 转Date
`KuxDayjs` 中获取原生的Date对象。
```ts
dayjs('2023-12-12').toDate();
```
#### 转数组
<a id="display_toArray"></a>
#### 转数组
返回一个包含各个时间信息的 Array。
```ts
const datetime = dayjs('2023-12-13 10:16:18');// [2023,12,13,10,16,18,0]
```
#### 转JSON
<a id="display_toJSON"></a>
#### 转JSON
序列化为 `ISO 8601` 格式的字符串。
```ts
dayjs('2023-12-13 10:16:18').toJSON(); // 2023-12-13T10:16:18.000Z
```
#### 转对象
<a id="display_toObject"></a>
#### 转对象
返回包含时间信息的 Object。
```ts
dayjs('2023-12-13 10:16:18').toObject(); // {"date":13,"hours":10,"milliseconds":0,"minutes":16,"months":12,"seconds":18,"years":2023}
```
#### 转字符串
<a id="display_toRFCString"></a>
#### 转字符串
返回包含时间信息的 `RFC 822``RFC 5322` 格式字符串。
```ts
dayjs('2023-12-13 10:16:18').toRFCString(); // Wed, 13 Dec 2023 10:16:18 GMT
```
### 查询
<a id="query"></a>
### 查询
`KuxDayjs` 对象还有很多查询的方法。
#### 是否之前
<a id="query_isBefore"></a>
#### 是否之前
表示 `KuxDayjs` 对象是否在另一个提供的日期时间之前。
```ts
dayjs('2023-12-13 10:16:18').isBefore('2024-12-12'); // true
@@ -567,8 +626,8 @@ dayjs('2023-12-13 10:16:18').isBefore('2024-12-12', 'year'); // true
+ second - 秒,缩写为 `s`
+ millisecond - 毫秒,缩写为 `ms`
#### 是否相同
<a id="query_isSame"></a>
#### 是否相同
表示 `KuxDayjs` 对象是否和另一个提供的日期时间相同。
```ts
dayjs('2023-12-13 10:16:18').isBefore('2023-12-12'); // false
@@ -586,8 +645,8 @@ dayjs('2023-12-13 10:16:18').isBefore('2023-12-12', 'year'); // true
+ second - 秒,缩写为 `s`
+ millisecond - 毫秒,缩写为 `ms`
#### 是否之后
<a id="query_isAfter"></a>
#### 是否之后
表示 `KuxDayjs` 对象是否在另一个提供的日期时间之后。
```ts
dayjs('2023-12-13 10:16:18').isAfter('2023-12-12'); // true
@@ -605,8 +664,8 @@ dayjs('2023-12-13 10:16:18').isAfter('2023-12-12', 'year'); // true
+ second - 秒,缩写为 `s`
+ millisecond - 毫秒,缩写为 `ms`
#### 是否相同或之前
<a id="query_isSameOrBefore"></a>
#### 是否相同或之前
表示 `KuxDayjs` 对象是和另一个提供的日期时间相同或在其之前。
```ts
dayjs('2023-12-13 10:16:18').isSameOrBefore('2023-12-12'); // false
@@ -624,8 +683,8 @@ dayjs('2023-12-13 10:16:18').isSameOrBefore('2023-12-12', 'year'); // true
+ second - 秒,缩写为 `s`
+ millisecond - 毫秒,缩写为 `ms`
#### 是否相同或之后
<a id="query_isSameOrAfter"></a>
#### 是否相同或之后
表示 `KuxDayjs` 对象是和另一个提供的日期时间相同或在其之前。
```ts
dayjs('2023-12-13 10:16:18').isSameOrAfter('2023-12-12'); // true
@@ -643,8 +702,8 @@ dayjs('2023-12-13 10:16:18').isSameOrAfter('2023-12-12', 'year'); // true
+ second - 秒,缩写为 `s`
+ millisecond - 毫秒,缩写为 `ms`
#### 是否两者之间
<a id="query_isBetween"></a>
#### 是否两者之间
表示 `KuxDayjs` 对象是否在其他两个的日期时间之间。
```ts
dayjs('2023-12-13').isBetween('2023-12-13', '2023-12-14'); // 默认毫秒
@@ -671,6 +730,7 @@ dayjs('2023-12-13').isBetween('2023-12-13', '2023-12-14', 'day', '[');
+ `]` - 向后包含,等同于 `>=`
+ `[]` - 前后都包含
<a id="display_isDayjs"></a>
#### 是否是KuxDayjs
这表示一个变量是否为 `KuxDayjs` 对象。
```ts
@@ -678,8 +738,155 @@ dayjs().isDayjs(dayjs()); // true
dayjs().isDayjs(new Date()); // false
```
<a id="display_isLeapYear"></a>
#### 是否闰年
查询 `KuxDayjs` 对象的年份是否是闰年。
```ts
dayjs('2000-01-01').isLeapYear(); // true
```
```
<a id="customType"></a>
### 自定义类型
因为 `uts` 强类型,所以有些 API 参数需要指定参数类型,以上所有 API 涉及的自定义类型都在下面列出,如果有需要可以自己手动导入类型使用。
<a id="customType_InfoType"></a>
#### InfoType
```ts
export type InfoType = {
lunarY : number;
lunarM : number;
lunarD : number;
isLeap : boolean;
}
```
<a id="customType_LunarType"></a>
#### LunarType
```ts
export type LunarType = {
month: string;
date: string;
};
```
<a id="customType_DatetimeUnit"></a>
#### DatetimeUnit
```ts
export type DatetimeUnit =
| 'day'
| 'd'
| 'month'
| 'M'
| 'year'
| 'y'
| 'hour'
| 'h'
| 'minute'
| 'm'
| 'second'
| 's'
| 'millisecond'
| 'ms'
```
<a id="customType_DiffUnit"></a>
#### DiffUnit
```ts
export type DiffUnit =
| 'week'
| 'w'
| 'day'
| 'd'
| 'month'
| 'M'
| 'year'
| 'y'
| 'hour'
| 'h'
| 'minute'
| 'm'
| 'second'
| 's'
| 'millisecond'
| 'ms'
```
<a id="customType_DateFormat"></a>
#### DateFormat
```ts
export type DateFormat = {
year: number;
month: number;
date: number;
render: any;
lunar: LunarType;
fullLunar: InfoType;
diffDays: number;
isToday: boolean;
fullDate: string;
};
```
<a id="customType_RelativeTime"></a>
#### RelativeTime
```ts
export type RelativeTime = {
s?: string;
m?: string;
mm?: string;
h?: string;
hh?: string;
d?: string;
dd?: string;
mon?: string;
mons?: string;
y?: string;
yy?: string;
};
```
<a id="customType_FromToOptions"></a>
#### FromToOptions
```ts
export type FromToOptions = {
isSuffix?: boolean;
relativeTime?: RelativeTime;
};
```
<a id="customType_DatetimeOptions"></a>
#### DatetimeOptions
```ts
export type DatetimeOptions = {
years: number;
months: number;
date: number;
hours: number;
minutes: number;
seconds: number;
milliseconds: number;
};
```
<a id="customType_IsBetweenContains"></a>
#### IsBetweenContains
```ts
export type IsBetweenContains =
| '['
| ']'
| '[]'
| ''
```
---
### 结语
#### kux 不生产代码只做代码的搬运工致力于提供uts 的 js 生态轮子实现,欢迎各位大佬在插件市场搜索使用 kux 生态插件:[https://ext.dcloud.net.cn/search?q=kux](https://ext.dcloud.net.cn/search?q=kux)
___
### 友情推荐
+ [GVIM即时通讯模版](https://ext.dcloud.net.cn/plugin?id=16419)GVIM即时通讯模版基于uni-app x开发的一款即时通讯模版
+ [t-uvue-ui](https://ext.dcloud.net.cn/plugin?id=15571)T-UVUE-UI是基于UNI-APP X开发的前端UI框架
+ [UxFrame 低代码高性能UI框架](https://ext.dcloud.net.cn/plugin?id=16148)【F2图表、双滑块slider、炫酷效果tabbar、拖拽排序、日历拖拽选择、签名...】UniAppX 高质量UI库
+ [wx-ui 基于uni-app x开发的高性能混合UI库](https://ext.dcloud.net.cn/plugin?id=15579)基于uni-app x开发的高性能混合UI库集成 uts api 和 uts component提供了一套完整、高效且易于使用的UI组件和API让您以更少的时间成本轻松完成高性能应用开发。
+ [firstui-uvue](https://ext.dcloud.net.cn/plugin?id=16294)FirstUIunix组件库一款适配 uni-app x 的轻量、简洁、高效、全面的移动端组件库。
+ [easyXUI 不仅仅是UI 更是为UniApp X设计的电商模板库](https://ext.dcloud.net.cn/plugin?id=15602)easyX 不仅仅是UI库更是一个轻量、可定制的UniAPP X电商业务模板库可作为官方组件库的补充,始终坚持简单好用、易上手

View File

@@ -0,0 +1 @@
export * from '../../common/index';

View File

@@ -0,0 +1 @@
export * from '../../common/index';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
export * from '../../common/index';

View File

@@ -0,0 +1 @@
export * from '../../common/index';

View File

@@ -1,3 +1,28 @@
## 1.1.32025-06-03
- feat: 兼容uniappx 鸿蒙next
- feat: uniappx 增加`fixedBoxWidth`属性,用于固定裁剪框宽度
## 1.1.22025-03-24
- fix: 修复uniappx 初始化设置图片时在APP端无法响尺寸的问题
## 1.1.12025-03-07
- fix: 修复uniappx 无法拖动裁剪框高度的问题
## 1.1.02025-02-22
- chore: 更新文档
## 1.0.92025-02-14
- fix: uniapp x ios setLineDash不能为0
## 1.0.82025-01-22
- feat: 非uniapp x 增加canvasId属性
## 1.0.72025-01-04
- fix: 修复 vue2 无法生成图片的问题
## 1.0.62024-12-28
- fix: 修复 vue3 再次调用的时候生成空白的图片
## 1.0.52024-12-17
- fix: vue2 修复css变量问题
## 1.0.42024-12-06
- chore: 类型
## 1.0.32024-12-06
- chore: 更新文档
## 1.0.22024-12-06
- feat: 兼容uniappx, 目前在uniappx app 上无法使用本地缓存的图片这个来自于官方BUG预计在hbx4.42以后修复
## 1.0.12023-06-12
- fix: 修复vue3 QQ小程序无法生成问题
## 1.0.02023-06-01

View File

@@ -1,8 +1,21 @@
$clipper-edge-border-width: 6rpx !default;
$clipper-confirm-color: #07c160 !default;
@import '~@/uni_modules/lime-style/index.scss';
$prefix: l !default;
$clipper: #{$prefix}-clipper;
@font-face {
font-family: clipper-icon;
src: url('https://at.alicdn.com/t/c/font_4769200_ijsa6pjss7d.ttf?t=1733274494453')
// src: url('data:application/octet-stream;base64,AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AAAAVE9TLzI9gUo9AAABjAAAAGBjbWFwhWDsFAAAAfgAAAF+Z2x5ZsX2J6QAAAOAAAABJGhlYWQqCQFBAAAA4AAAADZoaGVhB94DhAAAALwAAAAkaG10eAwAAAAAAAHsAAAADGxvY2EARACSAAADeAAAAAhtYXhwAREAPQAAARgAAAAgbmFtZRCjPLAAAASkAAACZ3Bvc3TvWVFJAAAHDAAAADgAAQAAA4D/gABcBAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAMAAQAAAAEAAI0L+7JfDzz1AAsEAAAAAADjdV5nAAAAAON1XmcAAAAABAADdgAAAAgAAgAAAAAAAAABAAAAAwAxAAQAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAQEAAGQAAUAAAKJAswAAACPAokCzAAAAesAMgEIAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAwOds520DgP+AAAAD3ACAAAAAAQAAAAAAAAAAAAAAAAACBAAAAAQAAAAEAAAAAAAABQAAAAMAAAAsAAAABAAAAVYAAQAAAAAAUAADAAEAAAAsAAMACgAAAVYABAAkAAAABAAEAAEAAOdt//8AAOds//8AAAABAAQAAAACAAEAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAACgAAAAAAAAAAgAA52wAAOdsAAAAAgAA520AAOdtAAAAAQAAAAAAAABEAJIABAAAAAADiQN2ABEAFQAhACcAACUhIiY1ETQ2MyEyFhURFgYHBiUhESElIzQuASM1MhcWFxYlJzcXBxcCSP5mFh0gEwGaFh0CBAUK/mMBXP6kArxSRnZEXE9MLS7+67GxPXp6HSATAT4WHSAT/skHEwgYUgEAMEV1RlIuLUxPEbW1Pnd+AAAAAAMAAAAAA3gCzAAOAB8AMAAAARYUBwYiJjU0Njc2MzIWAREhETc2Mh8BNz4BMzIXJhcTFhURFAYjISImNRE0NjMhMgGMFBQVPSkLChQfERcBx/1wPg0pDj3GCREMFBIFYoQNGxT9cBQbGxQCkxECCRU/DxUpGw4cChQK/vkBj/5nPQ4OPe8ICRECcwE9DRX92xUbGxUCJRUbAAAAAAAAEgDeAAEAAAAAAAAAEwAAAAEAAAAAAAEACAATAAEAAAAAAAIABwAbAAEAAAAAAAMACAAiAAEAAAAAAAQACAAqAAEAAAAAAAUACwAyAAEAAAAAAAYACAA9AAEAAAAAAAoAKwBFAAEAAAAAAAsAEwBwAAMAAQQJAAAAJgCDAAMAAQQJAAEAEACpAAMAAQQJAAIADgC5AAMAAQQJAAMAEADHAAMAAQQJAAQAEADXAAMAAQQJAAUAFgDnAAMAAQQJAAYAEAD9AAMAAQQJAAoAVgENAAMAAQQJAAsAJgFjQ3JlYXRlZCBieSBpY29uZm9udGljb25mb250UmVndWxhcmljb25mb250aWNvbmZvbnRWZXJzaW9uIDEuMGljb25mb250R2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwByAGUAYQB0AGUAZAAgAGIAeQAgAGkAYwBvAG4AZgBvAG4AdABpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAACAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBAgEDAQQABnJvdGF0ZQVwaG90bwAA');
}
$clipper-edge-border-width: create-var(clipper-edge-border-width, 6rpx); //6rpx !default;
$clipper-confirm-color: create-var(clipper-confirm-color, #07c160); //#07c160 !default;
$clipper-z-index: create-var(clipper-z-index, 99); //99 !default;
$clipper-mask-color: create-var(clipper-mask-color, rgba(0, 0, 0, 0.5)); //99 !default;
.flex-auto {
flex:auto
flex: auto
}
.bg-transparent {
@@ -11,30 +24,48 @@ $clipper-confirm-color: #07c160 !default;
}
.lime-clipper {
width: 100vw;
height: calc( 100vh - var(--window-top));
width: 100%;
// height: calc(100vh - var(--window-top));
bottom: 0;
/* #ifdef APP-ANDROID || APP-IOS || APP-HARMONY */
top: 0;
/* #endif */
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
top: var(--window-top);
/* #endif */
background-color: rgba(0, 0, 0, 0.9);
position: fixed;
top: var(--window-top);
left: 300vw;
z-index: 1;
left: 3000%;
z-index: $clipper-z-index;
&.open {
left: 0;
}
&-mask {
position: relative;
z-index: 2;
overflow: visible;
flex: 1;
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
pointer-events: none;
/* #endif */
}
&__content {
pointer-events: none;
position: absolute;
border: 1rpx solid rgba(255,255,255,.3);
box-sizing: border-box;
box-shadow: rgba(0, 0, 0, 0.5) 0 0 0 80vh;
background: transparent;
// box-shadow: $clipper-mask-color 0 0 0 80rpx;
box-shadow: 0 0 0 800rpx $clipper-mask-color ;
background: transparent;
overflow: visible;
// transition-duration 0.35s
// transition-property left,top
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
border: 1rpx solid rgba(255,255,255,.3);
&::before,&::after {
content: '';
position: absolute;
@@ -56,16 +87,20 @@ $clipper-confirm-color: #07c160 !default;
border-top:none;
border-bottom: none;
}
/* #endif */
}
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
&__edge {
overflow: visible;
position: absolute;
// left 6rpx
width: 34rpx;
height: 34rpx;
// background: red;
border: $clipper-edge-border-width solid #ffffff;
pointer-events: auto;
&::before {
content: '';
position: absolute;
@@ -73,10 +108,9 @@ $clipper-confirm-color: #07c160 !default;
height: 40rpx;
background-color: transparent;
}
&:nth-child(1) {
left: - $clipper-edge-border-width;
top: - $clipper-edge-border-width;
left: calc(#{$clipper-edge-border-width} * -1);
top: calc(#{$clipper-edge-border-width} * -1);
border-bottom-width: 0 !important;
border-right-width: 0 !important;
&:before {
@@ -86,8 +120,8 @@ $clipper-confirm-color: #07c160 !default;
}
&:nth-child(2) {
right: - $clipper-edge-border-width;
top: - $clipper-edge-border-width;
right: calc(#{$clipper-edge-border-width} * -1);
top: calc(#{$clipper-edge-border-width} * -1);
border-bottom-width: 0 !important;
border-left-width: 0 !important;
&:before {
@@ -98,8 +132,8 @@ $clipper-confirm-color: #07c160 !default;
}
&:nth-child(3) {
left: - $clipper-edge-border-width;
bottom: - $clipper-edge-border-width;
left: calc(#{$clipper-edge-border-width} * -1);
bottom: calc(#{$clipper-edge-border-width} * -1);
border-top-width: 0 !important;
border-right-width: 0 !important;
&:before {
@@ -109,8 +143,8 @@ $clipper-confirm-color: #07c160 !default;
}
&:nth-child(4) {
right: - $clipper-edge-border-width;
bottom: - $clipper-edge-border-width;
right: calc(#{$clipper-edge-border-width} * -1);
bottom: calc(#{$clipper-edge-border-width} * -1);
border-top-width: 0 !important;
border-left-width: 0 !important;
&:before {
@@ -118,28 +152,34 @@ $clipper-confirm-color: #07c160 !default;
left: 50%;
}
}
}
/* #endif */
&-image {
width: 100%;
max-width: inherit;
border-style: none;
position: absolute;
top: 0;
left: 0;
z-index: 1;
transform-origin: center;
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
max-width: inherit;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: center;
/* #endif */
}
&-canvas {
position: fixed;
z-index: 10;
left: -200vw;
top: -200vw;
z-index: 100;
left: -200%;
top: -200%;
pointer-events: none;
// left:0;
// top:50%;
// background-color: red;
}
&-tools {
@@ -148,16 +188,23 @@ $clipper-confirm-color: #07c160 !default;
bottom: 10px;
width: 100%;
z-index: 99;
color: #fff;
&__btns {
font-weight: bold;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 20rpx 40rpx;
box-sizing: border-box;
.text {
color: #fff;
min-width: 60rpx;
// #ifndef UNI-APP-X
display: block;
// #endif
}
.cancel {
font-weight: bold;
width: 112rpx;
height: 60rpx;
text-align: center;
@@ -165,19 +212,23 @@ $clipper-confirm-color: #07c160 !default;
}
.confirm {
font-weight: bold;
width: 112rpx;
height: 60rpx;
line-height: 60rpx;
background: var(--lime-clipper-confirm-color, $clipper-confirm-color);
background: $clipper-confirm-color;
border-radius: 6rpx;
text-align: center;
}
image {
display: block;
width: 60rpx;
height: 60rpx;
}
.rotate,.photo {
font-family: clipper-icon;
font-size: 60rpx;
}
// image {
// // display: block;
// width: 60rpx;
// height: 60rpx;
// }
}
}
}

View File

@@ -0,0 +1,986 @@
<template>
<view class="lime-clipper open">
<view class="lime-clipper-mask"
ref="clipperMaskRef"
@touchstart="clipTouchStart"
@touchmove="clipTouchMove"
@touchend="clipTouchEnd">
<!-- #ifndef APP -->
<view class="lime-clipper__content" ref="clipperRef" :style="clipStyle">
<view class="lime-clipper__edge" :class="'nth-child-' + (index + 1)" v-for="(item, index) in [0, 0, 0, 0]" :key="index"></view>
</view>
<!-- #endif -->
</view>
<image
ref="imageRef"
class="lime-clipper-image"
@error="imageError"
@load="imageLoad"
v-if="state.image != null"
:src="state.image"
:style="imageStyle"
@touchstart="imageTouchStart"
@touchmove="imageTouchMove"
@touchend="imageTouchEnd"/>
<canvas
ref="canvasRef"
canvas-id="lime-clipper"
id="lime-clipper"
disable-scroll
:style="{
width: state.canvasWidth + 'px',
height: state.canvasHeight + 'px',
}"
class="lime-clipper-canvas">
</canvas>
<view class="lime-clipper-tools" v-if="(isShowCancelBtn || isShowPhotoBtn || isShowRotateBtn || isShowConfirmBtn)">
<view class="lime-clipper-tools__btns" v-if="(isShowCancelBtn || isShowPhotoBtn || isShowRotateBtn || isShowConfirmBtn)">
<view v-if="isShowCancelBtn" @tap="cancel">
<slot name="cancel">
<text class="text cancel">取消</text>
</slot>
</view>
<view v-if="isShowPhotoBtn" @tap="uploadImage">
<slot name="photo">
<text class="text icon photo">&#xe76c;</text>
</slot>
</view>
<view v-if="isShowRotateBtn" @tap="rotate">
<slot name="rotate">
<text class="text icon rotate">&#xe76d;</text>
</slot>
</view>
<view v-if="isShowConfirmBtn" @tap="confirm" >
<slot name="confirm">
<text class="text confirm" :style="[confirmBgColor != null ? {background: confirmBgColor}: {}]">确定</text>
</slot>
</view>
</view>
<slot></slot>
</view>
</view>
</template>
<script setup lang="uts">
/**
* Clipper 图片裁剪组件
* @description 用于实现图片裁剪功能的交互式组件,支持缩放、旋转、比例锁定等高级功能
* <br> 插件类型LClipperComponentPublicInstance
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-clipper
*
* @property {string} customStyle 自定义容器样式支持CSS字符串
* @property {number} zIndex 画布层级默认999
* @property {string} imageUrl 源图片地址(支持本地/网络路径)
* @property {'jpg' | 'png'} fileType 输出图片格式默认jpg
* @property {number} quality 图片压缩质量0-1默认0.8
* @property {number} fixedBoxWidth 固定裁剪区域宽度单位rpx
* @property {number} width 裁剪区域默认宽度单位rpx
* @property {number} height 裁剪区域默认高度单位rpx
* @property {number} minWidth 最小裁剪宽度默认50px
* @property {number} maxWidth 最大裁剪宽度(默认:屏幕宽度)
* @property {number} destWidth 输出图片宽度(默认按裁剪尺寸)
* @property {number} destHeight 输出图片高度(默认按裁剪尺寸)
* @property {number} minHeight 最小裁剪高度默认50px
* @property {number} maxHeight 最大裁剪高度(默认:屏幕高度)
* @property {boolean} isLockWidth 锁定裁剪宽度(禁止调整)
* @property {boolean} isLockHeight 锁定裁剪高度(禁止调整)
* @property {boolean} isLockRatio 锁定宽高比默认false
* @property {number} scaleRatio 初始缩放比例默认1.0
* @property {number} minRatio 最小缩放比例默认0.5
* @property {number} maxRatio 最大缩放比例默认3.0
* @property {boolean} isDisableScale 禁用缩放功能
* @property {boolean} isDisableRotate 禁用旋转功能
* @property {boolean} isLimitMove 限制移动不超出图片范围
* @property {boolean} isShowPhotoBtn 显示相册选择按钮
* @property {boolean} isShowRotateBtn 显示旋转按钮
* @property {boolean} isShowConfirmBtn 显示确认按钮
* @property {boolean} isShowCancelBtn 显示取消按钮
* @property {number} rotateAngle 初始旋转角度(单位度)
* @property {any} source 自定义图片源(预留扩展)
* @property {string} confirmBgColor 确认按钮背景色
* @property {string} canvasId 画布唯一标识(多实例时必填)
* @event {Function} ready 准完成触发
* @event {Function} change 变化时触发
* @event {Function} rotate 旋转时触发
* @event {Function} cancel 取消时触发
* @event {Function} success 点击确定时成功生成图片后触发
*/
import { ClipperProps, ClipperState, ClipBoxSizes, Point, ClipperclipStart, Rectangle } from './type';
import {
getPointPositionInRectangle,
isPointInRotatedRectangle,
calcImageSize,
calcImageScale,
calcImageOffset,
calcPythagoreanTheorem,
imageTouchMoveOfCalcOffset,
clamp,
determineDirection,
clipTouchMoveOfCalculate } from './utils.uts';
const emit = defineEmits(['ready', 'change', 'rotate', 'cancel', 'input', 'success'])
const props = withDefaults(defineProps<ClipperProps>(), {
fileType: 'png',
quality: 1,
// fixedBoxWidth: 400,
width: 400,
height: 400,
minWidth: 200,
minHeight: 200,
maxWidth: 600,
maxHeight: 600,
isLockWidth: false,
isLockHeight: false,
isLockRatio: true,
scaleRatio: 1,
minRatio: 0.5,
maxRatio: 2,
isDisableScale: false,
isDisableRotate: false,
isLimitMove: false,
isShowPhotoBtn: true,
isShowRotateBtn: true,
isShowConfirmBtn: true,
isShowCancelBtn: true,
rotateAngle: 90,
// source: ():UTSJSONObject => ({})
source: {
album: '从相册中选择',
camera: '拍照',
// #ifdef MP-WEIXIN
message: '从微信中选择'
// #endif
} //as UTSJSONObject
})
const state = reactive<ClipperState>({
canvasWidth: 0,
canvasHeight: 0,
clipX: -1,
clipY: -1,
clipWidth: 0,
clipHeight: 0,
animation: false,
imageWidth: 0,
imageHeight: 0,
imageTop: 0,
imageLeft: 0,
scale: 1,
angle: 0,
image: '',
imageInit: false,
originY: -1,
originX: -1,
// flagClipTouch: false,
})
let touchRelative:Point[] = [];
let clipStart:ClipperclipStart = {
width: 0,
height: 0,
x: 0,
y: 0,
clipY: 0,
clipX: 0,
corner: 0
}
let hypotenuseLength = 0;
let throttleTimer = -1;
let timeClipCenter = -1;
let animationTimer = -1;
let flagEndTouch = false;
let flagClipTouch = false;
let throttleFlag = true;
let _image = '';
let ctx : CanvasRenderingContext2D | null = null;
let canvas : UniCanvasElement | null = null;
let canvasContext : CanvasContext | null = null;
const instance = getCurrentInstance()!
const canvasId = 'lime-clipper'
const canvasRef = ref<UniCanvasElement | null>(null)
let { windowHeight, windowWidth, pixelRatio} = uni.getWindowInfo();
const clipBoxSizes = computed(():ClipBoxSizes=>{
const width = uni.rpx2px(props.width);
const height = uni.rpx2px(props.height);
const minWidth = uni.rpx2px(props.minWidth);
const minHeight = uni.rpx2px(props.minHeight);
const maxWidth = uni.rpx2px(props.maxWidth);
const maxHeight = uni.rpx2px(props.maxHeight);
const fixedBoxWidth = uni.rpx2px(props.fixedBoxWidth ?? 0);
// #ifdef APP || WEB
// #endif
// #ifndef APP || WEB
// const width = uni.upx2px(props.width);
// const height = uni.upx2px(props.height);
// const minWidth = uni.upx2px(props.minWidth);
// const minHeight = uni.upx2px(props.minHeight);
// const maxWidth = uni.upx2px(props.maxWidth);
// const maxHeight = uni.upx2px(props.maxHeight);
// #endif
return {
fixedBoxWidth,
width,
height,
minWidth,
minHeight,
maxWidth,
maxHeight
} as ClipBoxSizes
})
const clipStyle = computed(():Map<string, any>=>{
const style = new Map<string, any>();
// #ifndef APP
style.set('width', state.clipWidth + 'px')
style.set('height', state.clipHeight + 'px')
style.set('transition-property', state.animation ? '': 'background')
style.set('left', state.clipX + 'px')
style.set('top', state.clipY + 'px')
// #endif
return style
})
const imageStyle = computed(():Map<string, any>=>{
const style = new Map<string, any>();
// #ifndef APP
style.set('width', state.imageWidth != 0 ? state.imageWidth + 'px': 'auto')
style.set('height', state.imageHeight != 0 ? state.imageHeight + 'px': 'auto')
style.set('transform', `translateX(${state.imageLeft - state.imageWidth / 2}px) translateY(${state.imageTop - state.imageHeight / 2}px) scale(${state.scale}) rotate(${state.angle}deg)`)
style.set('transition-duration', state.animation ? '0.35s': '0s')
// #endif
return style
})
const hidpi = (width: number, height: number) => {
if(canvas == null) return
// 处理高清屏逻辑
const dpr = pixelRatio;
canvas!.width = width * dpr;
canvas!.height = height * dpr;
ctx!.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
}
const setClipInfo = () => {
let clipWidth = clipBoxSizes.value.width;
let clipHeight = clipBoxSizes.value.height;
if(props.fixedBoxWidth != null) {
clipWidth = clipBoxSizes.value.fixedBoxWidth
clipHeight = clipBoxSizes.value.width / clipHeight * clipWidth
}
const clipY = (windowHeight - clipHeight) * 0.5;
const clipX = (windowWidth - clipWidth) * 0.5;
const imageLeft = windowWidth * 0.5;
const imageTop = windowHeight * 0.5;
state.clipWidth = clipWidth;
state.clipHeight = clipHeight;
state.clipX = clipX;
state.clipY = clipY;
state.canvasHeight = clipHeight;
state.canvasWidth = clipWidth;
state.imageLeft = imageLeft;
state.imageTop = imageTop;
if (canvasRef.value == null) return
// 异步调用方式, 跨平台写法
nextTick(()=>{
uni.createCanvasContextAsync({
id: canvasId,
component: instance.proxy!,
success: (context : CanvasContext) => {
canvasContext = context;
ctx = context.getContext('2d')!;
canvas = ctx!.canvas;
hidpi(clipWidth, clipHeight)
}
})
})
}
const setClipCenter = () => {
let clipY = (windowHeight - state.clipHeight) * 0.5;
let clipX = (windowWidth - state.clipWidth) * 0.5;
state.imageTop = state.imageTop - state.clipY + clipY;
state.imageLeft = state.imageLeft - state.clipX + clipX;
state.clipY = clipY;
state.clipX = clipX;
}
const calcClipSize = () => {
if(state.clipWidth > windowWidth) {
state.clipWidth = windowWidth
} else if(state.clipWidth + state.clipX > windowWidth) {
state.clipX = windowWidth - state.clipX
}
if(state.clipHeight > windowHeight) {
state.clipHeight = windowHeight
} else if(state.clipHeight + state.clipY > windowHeight) {
state.clipY = windowHeight - state.clipY
}
}
const cutDetectionPosition = () => {
// 辅助函数,用于确保裁剪区域在窗口内
const ensureWithinBounds = (value: number, maxValue: number, size: number):number => {
if (value < 0) {
return 0;
} else if (value > maxValue - size) {
return maxValue - size;
}
return value;
};
// 计算中心位置
const centerPosition = (maxValue: number, size: number):number => (maxValue - size) * 0.5;
const newClipY = state.clipY == -1 ? centerPosition(windowHeight, state.clipHeight) : ensureWithinBounds(state.clipY, windowHeight, state.clipHeight);
const newClipX = state.clipX == -1 ? centerPosition(windowWidth, state.clipWidth) : ensureWithinBounds(state.clipX, windowWidth, state.clipWidth);
state.clipY = newClipY
state.clipX = newClipX
}
const imgMarginDetectionPosition = (scale: number) => {
if (!props.isLimitMove) return;
const [left, top, currentScale] = calcImageOffset(
state.imageLeft,
state.imageTop,
state.imageWidth,
state.imageHeight,
state.clipX,
state.clipY,
state.clipWidth,
state.clipHeight,
state.angle,
scale);
state.imageLeft = left
state.imageTop = top
state.scale = currentScale
}
const imgMarginDetectionScale = (scale: number) => {
if (!props.isLimitMove) return;
const currentScale = calcImageScale(
state.imageWidth,
state.imageHeight,
state.clipWidth,
state.clipHeight,
state.angle,
scale);
imgMarginDetectionPosition(currentScale)
}
const imgComputeSize = (width: number, height: number) => {
const [imageWidth, imageHeight] = calcImageSize(
width, height,
clipBoxSizes.value.width, clipBoxSizes.value.height,
state.clipWidth, state.clipHeight);
state.imageWidth = imageWidth;
state.imageHeight = imageHeight;
state.originX = imageWidth / 2;
state.originY = imageHeight / 2;
}
const imageReset = () => {
state.scale = 1;
state.angle = 0;
state.imageTop = windowHeight * 0.5;
state.imageLeft = windowWidth * 0.5;
}
const moveStop = () => {
clearTimeout(timeClipCenter);
timeClipCenter = setTimeout(() => {
if (!state.animation) {
state.animation = true
state.imageInit = true
}
nextTick(setClipCenter)
}, 800);
}
let touchTarget:'image' | 'clip' | null = null
const restoreToFixedWidth = ()=> {
const fixedWidth = uni.rpx2px(props.fixedBoxWidth??0)
// 1. 计算宽度变化比例
const scaleRatio = fixedWidth / state.clipWidth
// 2. 保存当前裁剪框中心点
const oldImageCenterX = state.imageLeft;
const oldImageCenterY = state.imageTop;
const oldClipCenterX = state.clipX + state.clipWidth / 2;
const oldClipCenterY = state.clipY + state.clipHeight / 2;
// 3. 计算图片中心点相对于裁剪框中心点的偏移向量
const imageOffsetX = oldImageCenterX - oldClipCenterX;
const imageOffsetY = oldImageCenterY - oldClipCenterY;
// 4. 计算当前裁剪框的宽高比例
const aspectRatio = state.clipHeight / state.clipWidth
// 应用动画效果
state.animation = true
// 5. 更新裁剪框尺寸(宽度固定,高度保持比例)
state.clipWidth = fixedWidth
state.clipHeight = fixedWidth * aspectRatio
// 6. 更新裁剪框位置(保持中心点不变)
const newClipX = (windowWidth - fixedWidth) / 2;
const newClipY = (windowHeight - state.clipHeight) / 2;
// 7. 新裁剪框的中心点
const newClipCenterX = newClipX + fixedWidth / 2;
const newClipCenterY = newClipY + state.clipHeight / 2;
const currentRatio = state.scale * scaleRatio
// 8. 计算图片缩放比例变化量
state.scale = Math.min(currentRatio, props.maxRatio);
// 9. 计算图片位置的变化:
// a. 缩放导致的偏移(缩放中心是裁剪框中心)
// b. 裁剪框位置变化带来的偏移
state.imageTop = currentRatio > props.maxRatio ? oldImageCenterY : newClipCenterY + imageOffsetY * scaleRatio;
state.imageLeft = currentRatio > props.maxRatio ? oldImageCenterX :newClipCenterX + imageOffsetX * scaleRatio;
// 10. 更新裁剪框位置
state.clipX = newClipX;
state.clipY = newClipY;
// 12. 更新canvas尺寸
// state.canvasWidth = fixedWidth;
// state.canvasHeight = state.clipHeight;
}
const imageTouchStart = (e: UniTouchEvent) => {
e.preventDefault();
flagEndTouch = false;
state.animation = false;
const { imageLeft, imageTop } = state;
const [touch0] = e.touches;
// 计算触摸点相对于图片的相对位置
const getTouchRelative = (touch: UniTouch):Point => (
{
x: touch.clientX - imageLeft,
y: touch.clientY - imageTop
} as Point)
// 存储触摸点的相对位置
touchRelative = [getTouchRelative(touch0)];
if(e.touches.length > 1) {
const touch1 = e.touches[1];
const touch1Relative = getTouchRelative(touch1);
// 计算两点之间的距离
const width = Math.abs(touch0.clientX - touch1.clientX);
const height = Math.abs(touch0.clientY - touch1.clientY);
hypotenuseLength = Math.hypot(width, height);
touchRelative.push(touch1Relative);
}
}
const imageTouchMove = (e: UniTouchEvent) => {
e.preventDefault();
if (flagEndTouch || !throttleFlag) return;
throttleFlag = true;
clearTimeout(timeClipCenter);
const [touch0] = e.touches;
const { clientX: clientXForLeft, clientY: clientYForLeft } = touch0;
if(e.touches.length == 1) {
const [imageLeft, imageTop] = imageTouchMoveOfCalcOffset(touchRelative[0], clientXForLeft, clientYForLeft);
state.imageLeft = imageLeft;
state.imageTop = imageTop;
imgMarginDetectionPosition(state.scale)
} else if(e.touches.length > 1) {
const touch1 = e.touches[1];
const { clientX: clientXForRight, clientY: clientYForRight } = touch1;
const width = Math.abs(clientXForLeft - clientXForRight);
const height = Math.abs(clientYForLeft - clientYForRight);
const hypotenuse = Math.hypot(width, height);
let scale = state.scale * (hypotenuse / hypotenuseLength);
if(props.isDisableScale) {
scale = 1;
} else {
scale = clamp(scale, props.minRatio, props.maxRatio);
emit('change', { width: state.imageWidth * scale, height: state.imageHeight * scale });
}
if(state.scale == scale) return
imgMarginDetectionScale(scale);
hypotenuseLength = hypotenuse;
state.scale = scale;
}
}
const imageTouchEnd = (e: UniTouchEvent) => {
flagEndTouch = true;
moveStop()
// flagClipTouch = false
// touchTarget = null
}
const clipTouchStart = (e: UniTouchEvent) => {
e.preventDefault();
if (state.image == null) {
uni.showToast({
title: '请选择图片',
icon: 'none'
});
return;
}
const clientX = e.touches[0].clientX;
const clientY = e.touches[0].clientY;
// #ifdef APP
const point:Point = {x: clientX, y: clientY}
const clipRectangle: Rectangle = {x: state.clipX, y: state.clipY, width: state.clipWidth, height: state.clipHeight}
const corner = getPointPositionInRectangle(
point,
clipRectangle
)
if(corner != null) {
touchTarget = 'clip'
} else {
const imageRectangle: Rectangle = {
x: state.imageLeft - state.imageWidth / 2,
y: state.imageTop - state.imageHeight / 2,
width: state.imageWidth,
height: state.imageHeight}
const isImage = isPointInRotatedRectangle(point, imageRectangle, state.scale, state.angle)
if(isImage) {
touchTarget = 'image'
imageTouchStart(e)
}
}
if(corner == null) return;
// #endif
// #ifndef APP
const corner = determineDirection(state.clipX, state.clipY, state.clipWidth, state.clipHeight, clientX, clientY);
if(corner == -1) return;
// #endif
clearTimeout(timeClipCenter);
clipStart = {
width: state.clipWidth,
height: state.clipHeight,
x: clientX,
y: clientY,
clipX : state.clipX,
clipY : state.clipY,
corner
} as ClipperclipStart
flagClipTouch = true;
flagEndTouch = true;
}
const clipTouchMove = (e: UniTouchEvent) => {
// #ifdef APP
if(touchTarget == 'image') {
imageTouchMove(e)
return
}
if(touchTarget != 'clip') return
// #endif
e.stopPropagation()
e.preventDefault()
if (state.image == null) {
uni.showToast({
title: '请选择图片',
icon: 'none'
});
return;
}
// 只针对单指点击做处理
if (e.touches.length != 1) return;
if(flagClipTouch && throttleFlag) {
const [touch] = e.touches
const { isLockRatio, isLockHeight, isLockWidth } = props;
if (isLockRatio && (isLockWidth || isLockHeight)) return;
// throttleFlag = false;
throttleFlag = true;
const clipData = clipTouchMoveOfCalculate(
state.clipWidth,
state.clipHeight,
state.clipX,
state.clipY,
clipBoxSizes.value.minWidth,
clipBoxSizes.value.maxWidth,
clipBoxSizes.value.minHeight,
clipBoxSizes.value.maxHeight,
clipStart,
props.isLockRatio,
touch);
if(clipData == null) return
const [width, height, clipX, clipY] = clipData;
if(!isLockWidth && !isLockHeight) {
state.clipWidth = width
state.clipHeight = height
state.clipX = clipX
state.clipY = clipY
} else if(!isLockWidth) {
state.clipWidth = width
state.clipX = clipX
} else if(!isLockHeight) {
state.clipHeight = height
state.clipY = clipY
}
imgMarginDetectionScale(state.scale)
}
}
const clipTouchEnd = (e: UniTouchEvent) => {
// #ifdef APP
if(touchTarget == 'image') {
imageTouchEnd(e)
return
}
if(touchTarget != 'clip') return
// #endif
if (props.fixedBoxWidth != null && flagClipTouch) {
restoreToFixedWidth()
}
moveStop()
flagClipTouch = false
touchTarget = null
}
const imageLoad = (e: UniImageLoadEvent) => {
imageReset()
uni.hideLoading();
emit('ready', e);
}
const imageError = (e: UniImageErrorEvent) => {
imageReset()
uni.hideLoading();
}
const uploadImage = (e:UniPointerEvent) => {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album','camera'],
success(res) {
state.image = res.tempFilePaths[0]
}
})
}
const rotate = (e:UniPointerEvent) => {
if (props.isDisableRotate) return;
if (state.image == null) {
uni.showToast({
title: '请选择图片',
icon: 'none'
});
return;
}
// const { rotateAngle } = props;
// const originAngle = state.angle
// const type = event.currentTarget.dataset.type;
// if (type === 'along') {
// this.angle = originAngle + rotateAngle
// } else {
if(!state.animation) {
state.animation = true;
nextTick(()=>{
state.angle = state.angle - props.rotateAngle
})
} else {
state.angle = state.angle - props.rotateAngle
}
// }
emit('rotate', state.angle);
}
const cancel = (e:UniPointerEvent) => {
emit('cancel', false)
emit('input', false)
uni.hideLoading()
}
const confirm = (e:UniPointerEvent) => {
if (state.image == null) {
uni.showToast({
title: '请选择图片',
icon: 'none'
});
return;
}
uni.showLoading({
title: '加载中'
});
const {
canvasHeight,
canvasWidth,
clipHeight,
clipWidth,
scale,
imageLeft,
imageTop,
clipX,
clipY,
angle,
} = state;
const dpr = props.scaleRatio
const draw = () => {
if(ctx == null || canvas == null) return
const img = canvasContext!.createImage()
// #ifdef WEB
// @ts-ignore
img.crossOrigin = 'Anonymous';
// #endif
// @ts-ignore
img.onload = () => {
const imageWidth = state.imageWidth * scale * dpr;
const imageHeight = state.imageHeight * scale * dpr;
const xpos = imageLeft - clipX;
const ypos = imageTop - clipY;
ctx!.translate(xpos * dpr, ypos * dpr);
ctx!.rotate((angle * Math.PI) / 180);
ctx!.drawImage(img, -imageWidth / 2, -imageHeight / 2, imageWidth, imageHeight);
// 鸿蒙next 渲染需要时间,故延长一下
setTimeout(()=>{
const url = canvas!.toDataURL();
uni.hideLoading()
emit('success', {
url,
width: clipWidth * dpr,
height: clipHeight * dpr
});
emit('input', false)
},1000)
}
// @ts-ignore
img.onerror = () => {
uni.hideLoading()
}
// #ifdef MP-WEIXIN
if(_image.startsWith('static')) {
_image = `/${_image}`
}
// #endif
img.src = _image
}
if(canvasWidth != clipWidth || canvasHeight != clipHeight) {
state.canvasWidth = clipWidth
state.canvasHeight = clipHeight
nextTick(()=>{
hidpi(clipWidth, clipHeight)
nextTick(draw)
})
} else {
nextTick(draw)
}
}
// #ifdef APP
const imageRef = ref<UniImageElement | null>(null)
// const imageRef = ref<UniElement | null>(null)
watchEffect(()=>{
if(imageRef.value == null) return;
imageRef.value!.style.setProperty('width', state.imageWidth != 0 ? state.imageWidth + 'px': 'auto')
imageRef.value!.style.setProperty('height', state.imageHeight != 0 ? state.imageHeight + 'px': 'auto')
imageRef.value!.style.setProperty('transform', `translateX(${state.imageLeft - state.imageWidth / 2}px) translateY(${state.imageTop - state.imageHeight / 2}px) scale(${state.scale}) rotate(${state.angle}deg)`)
imageRef.value!.style.setProperty('transition-duration', state.animation ? '0.35s': '0s')
})
const clipperRef = ref<UniElement|null>(null)
const clipperMaskRef = ref<UniElement|null>(null)
let maskCtx:DrawableContext|null = null;
const drawMaskClip = () => {
if(clipperMaskRef.value == null) return
if(maskCtx == null) {
maskCtx = clipperMaskRef.value!.getDrawableContext()
}
const _maskCtx = maskCtx!
const rect = clipperMaskRef.value!.getBoundingClientRect()
_maskCtx.reset()
// 遮罩
_maskCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
_maskCtx.beginPath()
_maskCtx.moveTo(0, 0)
_maskCtx.lineTo(rect.width, 0)
_maskCtx.lineTo(rect.width, rect.height)
_maskCtx.lineTo(0, rect.height)
_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY)
_maskCtx.lineTo(state.clipX, state.clipY)
_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
_maskCtx.lineTo(0, rect.height)
_maskCtx.lineTo(0, 0)
_maskCtx.closePath()
_maskCtx.fill();
// 描边
_maskCtx.beginPath()
_maskCtx.setLineDash([4, 4])
_maskCtx.lineWidth = 1
_maskCtx.strokeStyle = 'rgba(255,255,255,.3)'
_maskCtx.moveTo(state.clipX, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY)
_maskCtx.lineTo(state.clipX, state.clipY)
_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
_maskCtx.stroke()
// y虚线
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX, state.clipY + state.clipHeight / 3)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight / 3)
_maskCtx.stroke()
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX, state.clipY + state.clipHeight / 3 * 2)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight / 3 * 2)
_maskCtx.stroke()
// x虚线
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX + state.clipWidth / 3, state.clipY)
_maskCtx.lineTo(state.clipX + state.clipWidth / 3, state.clipY + state.clipHeight)
_maskCtx.stroke()
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX + state.clipWidth / 3 * 2, state.clipY)
_maskCtx.lineTo(state.clipX + state.clipWidth / 3 * 2, state.clipY + state.clipHeight)
_maskCtx.stroke()
// 左上角
const edgeLength = 20
const edgeWidth = 3
_maskCtx.lineWidth = 3
_maskCtx.strokeStyle = 'rgba(255,255,255,1)'
// #ifdef APP-IOS
_maskCtx.setLineDash([0, 0.0001])//ios 不能为0
// #endif
// #ifdef APP-ANDROID
_maskCtx.setLineDash([0, 0])
// #endif
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX, state.clipY + edgeLength)
_maskCtx.lineTo(state.clipX, state.clipY)
_maskCtx.lineTo(state.clipX + edgeLength, state.clipY)
_maskCtx.stroke()
// 右上角
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX + state.clipWidth - edgeLength, state.clipY)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + edgeLength)
_maskCtx.stroke()
// 右下角
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight - edgeLength)
_maskCtx.lineTo(state.clipX + state.clipWidth, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX + state.clipWidth - edgeLength, state.clipY + state.clipHeight)
_maskCtx.stroke()
// 左下角
_maskCtx.beginPath()
_maskCtx.moveTo(state.clipX + edgeLength, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight)
_maskCtx.lineTo(state.clipX, state.clipY + state.clipHeight - edgeLength)
_maskCtx.stroke()
_maskCtx.update()
}
watchEffect(()=>{
if(clipperMaskRef.value == null) return;
if(maskCtx == null) {
setTimeout(()=>{
drawMaskClip()
},100)
}
drawMaskClip()
// clipperRef.value!.style.setProperty('width', state.clipWidth + 'px')
// clipperRef.value!.style.setProperty('height', state.clipHeight + 'px')
// clipperRef.value!.style.setProperty('transition-property', state.animation ? '': 'background')
// clipperRef.value!.style.setProperty('left', state.clipX + 'px')
// clipperRef.value!.style.setProperty('top', state.clipY + 'px')
// clipperRef.value!.style.setProperty('z-index', 10)
})
// #endif
watch(():string|null => state.image, (src: string|null)=>{
if(src == null) return
state.imageInit = false
uni.showLoading({
title: '请稍候...',
mask: true
});
uni.getImageInfo({
src,
success(res) {
uni.hideLoading()
if(['right', 'left'].includes(res.orientation ?? '')){
imgComputeSize(res.height, res.width)
} else {
imgComputeSize(res.width, res.height)
}
_image = res.path;
if(props.isLimitMove) {
imgMarginDetectionScale(state.scale)
}
},
fail(err) {
uni.hideLoading()
}
})
}, {immediate: true})
// watch(():boolean => state.animation ,(value: boolean) => {
// clearTimeout(animationTimer);
// if(value) {
// animationTimer = setTimeout(() => {
// state.animation = false;
// }, 260);
// }
// })
watch(clipBoxSizes, (_clipBoxSizes: ClipBoxSizes)=> {
state.clipWidth = clamp(clipBoxSizes.value.width, clipBoxSizes.value.minWidth, clipBoxSizes.value.maxWidth)
state.clipHeight = clamp(clipBoxSizes.value.height, clipBoxSizes.value.minHeight, clipBoxSizes.value.maxHeight)
calcClipSize()
})
watch(():number=> state.angle, (angle: number)=>{
state.animation = true//state.imageInit;
moveStop()
if(props.isLimitMove && angle % 90 != 0) {
state.angle = Math.round(angle / 90) * 90
}
imgMarginDetectionScale(state.scale)
})
watch(():boolean => props.isLimitMove, (limit: boolean)=>{
state.animation = true//state.imageInit;
moveStop()
if(limit && state.angle % 90 != 0) {
state.angle = Math.round(state.angle / 90) * 90
}
imgMarginDetectionScale(state.scale)
})
watch(():number[] => [state.clipX, state.clipY], (_:number[])=> {
cutDetectionPosition()
})
onMounted(() => {
nextTick(()=>{
let res = uni.getWindowInfo();
windowHeight = res.windowHeight
windowWidth = res.windowWidth
pixelRatio = res.pixelRatio;
// state.image = props.imageUrl
setClipInfo()
setClipCenter()
calcClipSize()
cutDetectionPosition()
watchEffect(()=>{
state.image = props.imageUrl;
})
})
})
</script>
<style lang="scss">
@import './index';
</style>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,186 @@
<template>
<view class="demo-block">
<text class="demo-block__title-text ultra">图片裁剪</text>
<text class="demo-block__desc-text" style="display: flex;">可用于图片头像等裁剪处理 。</text>
<view class="demo-block__body">
<view class="demo-block card">
<text class="demo-block__title-text large">基本用法</text>
<view class="demo-block__body row">
<image :src="url" v-if="url !=''" mode="widthFix"></image>
<l-clipper
v-if="show"
:fixedBoxWidth="600"
:image-url="imageUrl"
@success="success"
@cancel="show = false"
/>
<!-- <l-clipper v-if="show" :image-url="imageUrl" @success="success" @cancel="show = false"/> -->
<button @tap="onClick">裁剪图片</button>
</view>
</view>
<!-- <view class="demo-block card">
<text class="demo-block__title-text large">插槽</text>
<view class="demo-block__body row">
<image :src="url1" v-if="url1 !=''" mode="widthFix"></image>
<l-clipper
v-if="show1"
:isLockWidth="isLockWidth"
:isLockHeight="isLockHeight"
:isLockRatio="isLockRatio"
:isLimitMove="isLimitMove"
:isDisableScale="isDisableScale"
:isDisableRotate="isDisableRotate"
:isShowCancelBtn="isShowCancelBtn"
:isShowPhotoBtn="isShowPhotoBtn"
:isShowRotateBtn="isShowRotateBtn"
:isShowConfirmBtn="isShowConfirmBtn"
@success="handleClipperSuccess"
@cancel="show = false" >
<view slot="cancel">取消</view>
<view slot="photo">选择图片</view>
<view slot="rotate">旋转</view>
<view slot="confirm">确定</view>
<view class="tools" style="flex-direction: row; flex-wrap: wrap;">
<view>
<text style="color: white;">显示取消按钮{{isShowCancelBtn}}</text>
<switch :checked="isShowCancelBtn" @change="($event: UniSwitchChangeEvent) => {isShowCancelBtn = $event.detail.value}"/>
</view>
<view>
<text style="color: white;">显示选择图片按钮</text>
<switch :checked="isShowPhotoBtn" @change="($event: UniSwitchChangeEvent) => {isShowPhotoBtn = $event.detail.value}" />
</view>
<view>
<text style="color: white;">显示旋转按钮</text>
<switch :checked="isShowRotateBtn" @change="($event: UniSwitchChangeEvent) => {isShowRotateBtn = $event.detail.value}" />
</view>
<view>
<text style="color: white;">显示确定按钮</text>
<switch :checked="isShowConfirmBtn" @change="($event: UniSwitchChangeEvent) => {isShowConfirmBtn = $event.detail.value}" />
</view>
<view>
<text style="color: white;">锁定裁剪框宽度</text>
<switch :checked="isLockWidth" @change="($event: UniSwitchChangeEvent) => {isLockWidth = $event.detail.value}" />
</view>
<view>
<text style="color: white;">锁定裁剪框高度</text>
<switch :checked="isLockHeight" @change="($event: UniSwitchChangeEvent) => {isLockHeight = $event.detail.value}" />
</view>
<view>
<text style="color: white;">锁定裁剪框比例</text>
<switch :checked="isLockRatio" @change="($event: UniSwitchChangeEvent) => {isLockRatio = $event.detail.value}" />
</view>
<view>
<text style="color: white;">限制移动范围</text>
<switch :checked="isLimitMove" @change="($event: UniSwitchChangeEvent) => {isLimitMove = $event.detail.value}" />
</view>
<view>
<text style="color: white;">禁止缩放</text>
<switch :checked="isDisableScale" @change="($event: UniSwitchChangeEvent) => {isDisableScale = $event.detail.value}" />
</view>
<view>
<text style="color: white;">禁止旋转</text>
<switch :checked="isDisableRotate" @change="($event: UniSwitchChangeEvent) => {isDisableRotate = $event.detail.value}" />
</view>
</view>
</l-clipper>
<button @tap="onClick2">裁剪图片</button>
</view>
</view> -->
</view>
</view>
</template>
<script setup lang="uts">
// const imageUrl = 'https://img12.360buyimg.com/pop/s1180x940_jfs/t1/97205/26/1142/87801/5dbac55aEf795d962/48a4d7a63ff80b8b.jpg';
const imageUrl = '/static/mv2.jpg';
const url = ref('')
const url1 = ref('')
const show = ref(false)
const show1 = ref(false)
const isLockWidth = ref(false)
const isLockHeight = ref(false)
const isLockRatio = ref(true)
const isLimitMove = ref(false)
const isDisableScale = ref(false)
const isDisableRotate = ref(false)
const isShowCancelBtn = ref(true)
const isShowPhotoBtn = ref(true)
const isShowRotateBtn = ref(true)
const isShowConfirmBtn = ref(true)
const onClick = ()=>{
show.value = true;
}
const onClick2 = ()=>{
show1.value = true;
}
const success = (res: UTSJSONObject) => {
url.value = `${res['url'] ?? ''}`
show.value = false
}
const handleClipperSuccess = (res: UTSJSONObject) =>{
url1.value = `${res['url'] ?? ''}`
show1.value = false
}
</script>
<style lang="scss">
.demo-block {
margin: 32px 10px 0;
// overflow: visible;
&.card {
background-color: white;
padding: 30rpx;
margin-bottom: 20rpx !important;
}
&__title {
margin: 0;
margin-top: 8px;
&-text {
color: rgba(0, 0, 0, 0.6);
font-weight: 400;
font-size: 14px;
line-height: 16px;
&.large {
color: rgba(0, 0, 0, 0.9);
font-size: 18px;
font-weight: 700;
line-height: 26px;
}
&.ultra {
color: rgba(0, 0, 0, 0.9);
font-size: 24px;
font-weight: 700;
line-height: 32px;
}
}
}
&__desc-text {
color: rgba(0, 0, 0, 0.6);
margin: 8px 16px 0 0;
font-size: 14px;
line-height: 22px;
}
&__body {
margin: 16px 0;
overflow: visible;
.demo-block {
// margin-top: 0px;
margin: 0;
}
}
}
</style>

View File

@@ -10,6 +10,7 @@
export default {
data() {
return {
imageUrl: 'https://img12.360buyimg.com/pop/s1180x940_jfs/t1/97205/26/1142/87801/5dbac55aEf795d962/48a4d7a63ff80b8b.jpg',
url: '',
show: false
}

View File

@@ -0,0 +1 @@
3џ=Gл‡zШWЌfіДFxQAъ¦<ќшр4щLD 2И1*ёфnXm«f±†P

View File

@@ -1,8 +1,8 @@
{
"id": "lime-clipper",
"displayName": "图片剪刀-LimeUI",
"version": "1.0.1",
"description": "一款自我感觉良好的图片裁剪插件",
"displayName": "lime-clipper 图片裁剪",
"version": "1.1.3",
"description": "一款自我感觉良好的图片裁剪插件, 兼容uniapp/uniappx",
"keywords": [
"图片裁剪",
"缩放",
@@ -11,15 +11,17 @@
],
"repository": "https://gitee.com/liangei/lime-clipper",
"engines": {
"HBuilderX": "^3.6.4"
"HBuilderX": "^4.44",
"uni-app": "^4.45",
"uni-app-x": "^4.66"
},
"dcloudext": {
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
"price": "59.99"
},
"sourcecode": {
"price": "0.00"
"price": "60.00"
}
},
"contact": {
@@ -31,44 +33,74 @@
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
"type": "component-vue",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"dependencies": [
"lime-style"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "-",
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
"uni-app-x": {
"web": {
"safari": "",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "",
"harmony": ""
},
"mp": {
"weixin": "√"
}
}
}
}

View File

@@ -1,9 +1,5 @@
# Clipper 图片裁剪
> uniapp 图片裁剪,可用于图片头像等裁剪处理
> [查看更多 站点1](https://limeui.qcoon.cn/#/clipper) <br>
> [查看更多 站点2](http://liangei.gitee.io/limeui/#/clipper) <br>
> Q群1169785031
> 图片裁剪,可用于图片头像等裁剪处理
## 平台兼容
@@ -12,9 +8,10 @@
| √ | √ | √ | 未测 | √ | √ | √ |
## 安装
市场导入**[图片剪刀](https://ext.dcloud.net.cn/plugin?id=3594)uni_modules**版本的即可,无需`import`
插件市场导入即可
## 文档
[clipper](https://limex.qcoon.cn/components/clipper.html)
## 代码演示
### 基本用法
@@ -66,23 +63,13 @@ export default {
### 确定按钮颜色
样式变量名:`--clipper_confirm_color`, `nvue` 无效, `nvue`需要通过挂载全局样式表实现。
可放到全局样式的 `page` 里或节点的 `style`
```html
<l-clipper class="clipper" style="--clipper_confirm_color: linear-gradient(to right, #ff6034, #ee0a24)" />
```
```css
// css 中为组件设置 CSS 变量
.clipper {
--clipper_confirm_color: linear-gradient(to right, #ff6034, #ee0a24)
}
// 全局
page {
--clipper_confirm_color: linear-gradient(to right, #ff6034, #ee0a24)
}
<l-clipper class="clipper" confirm-bg-color="linear-gradient(to right, #ff6034, #ee0a24)" />
```
### 使用插槽
共五个插槽 `cancel` 取消按钮、 `photo` 选择图片按钮、 `rotate` 旋转按钮、 `confirm` 确定按钮和默认插槽。
@@ -108,38 +95,48 @@ page {
<view slot="rotate">旋转</view>
<view slot="confirm">确定</view>
<!-- 默认插槽 -->
<view class="tools">
<view>显示取消按钮
<switch :checked="isShowCancelBtn" @change="isShowCancelBtn = $event.target.value" />
</view>
<view>显示选择图片按钮
<switch :checked="isShowPhotoBtn" @change="isShowPhotoBtn = $event.target.value" />
</view>
<view>显示旋转按钮
<switch :checked="isShowRotateBtn" @change="isShowRotateBtn = $event.target.value" />
</view>
<view>显示确定按钮
<switch :checked="isShowConfirmBtn" @change="isShowConfirmBtn = $event.target.value" />
</view>
<view>锁定裁剪框宽度
<switch :checked="isLockWidth" @change="isLockWidth = $event.target.value" />
</view>
<view>锁定裁剪框高度
<switch :checked="isLockHeight" @change="isLockHeight = $event.target.value" />
</view>
<view>锁定裁剪框比例
<switch :checked="isLockRatio" @change="isLockRatio = $event.target.value" />
</view>
<view>限制移动范围
<switch :checked="isLimitMove" @change="isLimitMove = $event.target.value" />
</view>
<view>禁止缩放
<switch :checked="isDisableScale" @change="isDisableScale = $event.target.value" />
</view>
<view>禁止旋转
<switch :checked="isDisableRotate" @change="isDisableRotate = $event.target.value" />
</view>
<view class="tools" style="flex-direction: row; flex-wrap: wrap;">
<view>
<text style="color: white;">显示取消按钮{{isShowCancelBtn}}</text>
<switch :checked="isShowCancelBtn" @change="($event: UniSwitchChangeEvent) => {isShowCancelBtn = $event.detail.value}"/>
</view>
<view>
<text style="color: white;">显示选择图片按钮</text>
<switch :checked="isShowPhotoBtn" @change="($event: UniSwitchChangeEvent) => {isShowPhotoBtn = $event.detail.value}" />
</view>
<view>
<text style="color: white;">显示旋转按钮</text>
<switch :checked="isShowRotateBtn" @change="($event: UniSwitchChangeEvent) => {isShowRotateBtn = $event.detail.value}" />
</view>
<view>
<text style="color: white;">显示确定按钮</text>
<switch :checked="isShowConfirmBtn" @change="($event: UniSwitchChangeEvent) => {isShowConfirmBtn = $event.detail.value}" />
</view>
<view>
<text style="color: white;">锁定裁剪框宽度</text>
<switch :checked="isLockWidth" @change="($event: UniSwitchChangeEvent) => {isLockWidth = $event.detail.value}" />
</view>
<view>
<text style="color: white;">锁定裁剪框高度</text>
<switch :checked="isLockHeight" @change="($event: UniSwitchChangeEvent) => {isLockHeight = $event.detail.value}" />
</view>
<view>
<text style="color: white;">锁定裁剪框比例</text>
<switch :checked="isLockRatio" @change="($event: UniSwitchChangeEvent) => {isLockRatio = $event.detail.value}" />
</view>
<view>
<text style="color: white;">限制移动范围</text>
<switch :checked="isLimitMove" @change="($event: UniSwitchChangeEvent) => {isLimitMove = $event.detail.value}" />
</view>
<view>
<text style="color: white;">禁止缩放</text>
<switch :checked="isDisableScale" @change="($event: UniSwitchChangeEvent) => {isDisableScale = $event.detail.value}" />
</view>
<view>
<text style="color: white;">禁止旋转</text>
<switch :checked="isDisableRotate" @change="($event: UniSwitchChangeEvent) => {isDisableRotate = $event.detail.value}" />
</view>
</view>
</l-clipper>
<button @tap="show = true">裁剪</button>
```
@@ -191,6 +188,7 @@ export default {
| image-url | 图片路径 | <em>string</em> | |
| quality | 图片的质量,取值范围为 [0, 1]不在范围内时当作1处理 | <em>number</em> | `1` |
| source | `{album: '从相册中选择'}`key为图片来源类型value为选项说明 | <em>Object</em> | |
| fixedBoxWidth | 固定裁剪框宽度,作用是拖拽裁剪框后,依然保持输入的宽度。单位为 `rpx` | <em>number</em> | `-` |
| width | 裁剪框宽度,单位为 `rpx` | <em>number</em> | `400` |
| height | 裁剪框高度 | <em>number</em> | `400` |
| min-width | 裁剪框最小宽度 | <em>number</em> | `200` |
@@ -211,6 +209,7 @@ export default {
| is-show-rotate-btn | 是否显示转按钮 | <em>boolean</em> | `true` |
| is-show-confirm-btn | 是否显示确定按钮 | <em>boolean</em> | `true` |
| is-show-cancel-btn | 是否显示关闭按钮 | <em>boolean</em> | `true` |
| confirm-bg-color | 确定按钮背景色 | <em>string</em> | `` |

View File

@@ -1,3 +1,5 @@
## 1.9.6.62024-09-25
- fix: 修复background-position无效的问题
## 1.9.6.52024-04-14
- fix: 修复`nvue`无法生图的问题
## 1.9.6.42024-03-10

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
{
"id": "lime-painter",
"displayName": "海报画板",
"version": "1.9.6.5",
"version": "1.9.6.6",
"description": "一款canvas海报组件更优雅的海报生成方案有限的支持富文本",
"keywords": [
"海报",

View File

@@ -1,9 +1,7 @@
# Painter 画板 测试版
> uniapp 海报画板,更优雅的海报生成方案
> [查看更多 站点 1](https://limeui.qcoon.cn/#/painter)
> [查看更多 站点 2](http://liangei.gitee.io/limeui/#/painter)
> Q 群1169785031
> [查看更多](https://limeui.qcoon.cn/#/painter)
## 平台兼容

View File

@@ -0,0 +1,48 @@
## 0.2.42025-11-14
- feat: 优化create-var
## 0.2.32025-10-19
- fix: 修复非uts环境使用set的问题
## 0.2.22025-10-15
- fix: 去掉版本判断
## 0.2.12025-10-15
- fix: 修复条件判断问题
## 0.2.02025-10-01
- fix: 修复uniapp主题变化报错问题
## 0.1.92025-09-26
- feat: 更新宫格暗黑hover颜色
## 0.1.82025-09-08
- feat: 去掉vue2的判断
## 0.1.72025-08-15
更新变量
## 0.1.62025-06-20
- feat: 放弃使用rpx
## 0.1.52025-06-20
- fix: 修复 vue2 rpx转成px的问题
## 0.1.42025-06-12
- feat: 增加一些函数
## 0.1.32025-05-07
- feat: 增加`hairline`函数
## 0.1.22025-04-24
- feat: 根据官方建议字体使用px
## 0.1.12025-03-25
- feat: 更换rbgToHsv算法
## 0.1.02025-03-13
- feat: hbx4.56 Vue2 scss 预编译器默认已由 node-sass 更换为 dart-sass
## 0.0.92025-01-16
- feat: 更新
## 0.0.82024-12-15
- fix: 修复vue2 不支持rgba(0,0,0,1%)
## 0.0.72024-12-11
- feat: 增加除法
## 0.0.62024-12-04
- fix: 除法问题
## 0.0.52024-11-20
- feat: 增加flex
## 0.0.42024-11-20
- feat: 增加flex
## 0.0.32024-09-30
- fix: 由于 vue2 h5 css变量不支持rpx故转成px
## 0.0.22024-09-23
- fix: 修复 vue2 math.div 问题
## 0.0.12024-09-02
- init

View File

@@ -0,0 +1,236 @@
/* #ifdef VUE3 */
@use "sass:math";
/* #endif */
$hueStep: 2;
$saturationStep1: 0.16;
$saturationStep2: 0.05;
$brightnessStep1: 0.05;
$brightnessStep2: 0.15;
$lightColorCount: 5;
$darkColorCount: 4;
$darkColorMap: (
(index: 7, opacity: 0.15),
(index: 6, opacity: 0.25),
(index: 5, opacity: 0.3),
(index: 5, opacity: 0.45),
(index: 5, opacity: 0.65),
(index: 5, opacity: 0.85),
(index: 4, opacity: 0.9),
(index: 3, opacity: 0.95),
(index: 2, opacity: 0.97),
(index: 1, opacity: 0.98)
);
@function div($dividend, $divisor) {
/* #ifdef VUE3 */
@return math.div($dividend, $divisor);
/* #endif
/* #ifndef VUE3 */
@return $dividend / $divisor;
/* #endif */
}
// 求一个数的n次幂
@function pow($number, $n) {
$ret: 1;
@if $n >= 0 {
@for $i from 1 through $n {
$ret: $ret * $number;
}
} @else {
@for $i from $n to 0 {
$ret: $ret / $number;
}
}
@return $ret;
}
// 浮点数保留小数位
@function toFixed($float, $digits: 2) {
$pow: pow(10, $digits);
@return div(round($float * $pow) , $pow);
}
// 根据颜色获取对应的hsv在tinycolor中首先进行了归一化处理这里没有
// 返回的结果h是0~360代表的是色相的角度 sv的范围0-1
@function rbgToHsv($limeColor) {
$r: red($limeColor);
$g: green($limeColor);
$b: blue($limeColor);
$max: max($r, $g, $b);
$min: min($r, $g, $b);
$diff: $max - $min;
$h: 0;
@if $max == $min {
$h: 0
} @else if $max == $r {
$h: div(60 * ($g - $b) , $diff) + if($g >= $b, 0, 360);
} @else if $max == $g {
$h: 60 * div($b - $r , $diff) + 120 //($b - $r) / $diff + 120;
} @else if $max == $b{
$h: div(60 * ($r - $g) , $diff) + 240;
}
$s: if($max == 0, 0, div($diff , $max));
$v: div($max , 255);
@return $h, $s, $v;
}
// hsv转化成rgb借鉴了tinycolor的做法避免通过$th的值判断来获取对应的rgb的取值
// $t1~4的计算目前不清楚为什么这样做
@function hsvTorgb($h, $s, $v) {
// $th: floor(div($h , 60));
// $t1: div($h , 60) - $th;
// $t2: $v * (1 - $s);
// $t3: $v * (1 - $t1 * $s);
// $t4: $v * (1 - (1 - $t1) * $s);
// $i: $th + 1;
// $r: nth(($v, $t3, $t2, $t2, $t4, $v), $i);
// $g: nth(($t4, $v, $v, $t3, $t2, $t2), $i);
// $b: nth(($t2, $t2, $t4, $v, $v, $t3), $i);
// @return rgb($r * 255, $g * 255, $b * 255);
$h: $h % 360;
// 2. 计算色相区域 (0~5)对应6个60度区间
$th: floor(div($h, 60)) % 6; // 强制th在0~5之间
// 3. 计算中间变量
$t1: div($h, 60) - $th;
$t2: $v * (1 - $s);
$t3: $v * (1 - $t1 * $s);
$t4: $v * (1 - (1 - $t1) * $s);
// 4. 根据色相区域选择RGB分量
$i: $th + 1; // 索引范围锁定为1~6
// 定义各区域对应的RGB系数
$r-values: ($v, $t3, $t2, $t2, $t4, $v);
$g-values: ($t4, $v, $v, $t3, $t2, $t2);
$b-values: ($t2, $t2, $t4, $v, $v, $t3);
// 5. 获取RGB分量并转换为0~255整数
$r: nth($r-values, $i) * 255;
$g: nth($g-values, $i) * 255;
$b: nth($b-values, $i) * 255;
// 6. 返回RGB颜色值
@return rgb(round($r), round($g), round($b));
}
//转换色相
@function getHue($h, $i, $isLight) {
$hue: null;
@if $h >= 60 and $h <= 240 {
$hue: if($isLight, $h - $hueStep * $i, $h + $hueStep * $i);
} @else {
$hue: if($isLight, $h + $hueStep * $i, $h - $hueStep * $i);
}
$hue: ($hue + 360) % 360;
@return round($hue);
}
// 转换饱和度
@function getSaturation($s, $i, $isLight) {
$saturation: null;
@if $isLight {
$saturation: $s - $saturationStep1 * $i;
} @else if $i == $darkColorCount {
$saturation: $s + $saturationStep1;
} @else {
$saturation: $s + $saturationStep2 * $i;
}
$saturation: min($saturation, 1);
@if $isLight and $i == $lightColorCount and $saturation > 0.1 {
$saturation: 0.1;
}
$saturation: max($saturation, 0.06);
@return toFixed($saturation, 2);
}
// 转换明度
@function getValue($v, $i, $isLight) {
$value: min(
if(
$isLight,
$v + $brightnessStep1 * $i,
$v - $brightnessStep2 * $i
),
1);
@return toFixed($value, 2);
}
@function mix($rgb1, $rgb2, $amount){
$p: $amount;
$r: (red($rgb2) - red($rgb1)) * $p + red($rgb1);
$g: (green($rgb2) - green($rgb1)) * $p + green($rgb1);
$b: (blue($rgb2) - blue($rgb1)) * $p + blue($rgb1);
@return rgb($r, $g, $b)
}
// 根据颜色和对应的色板位置,计算出对应的色板颜色
@function genColor($color, $index, $theme: 'default' , $bgColor: #242424) {
$isLight: if($index <= 6, true, false);
$hsv: rbgToHsv($color);
//这里将i转换成以主色为中心两侧的i值逐渐增大
$i: if($isLight, $lightColorCount + 1 - $index, $index - $lightColorCount - 1);
@if $theme == 'dark' {
$item: nth($darkColorMap, $index);
$index2: map-get($item, index);
$opacity: map-get($item, opacity);
$rgb: genColor($color, $index2 + 1);
// @return $rgb;
@return mix(
$bgColor,
$rgb,
$opacity
)
}
@return hsvTorgb(
getHue(nth($hsv, 1), $i, $isLight),
getSaturation(nth($hsv, 2), $i, $isLight),
getValue(nth($hsv, 3), $i, $isLight)
);
}
@function getSolidColor($base-color, $brightness) {
// 验证输入参数
@if type-of($base-color) != 'color' {
@error "Expected color for $base-color, but got #{type-of($base-color)}: #{$base-color}";
}
@if type-of($brightness) != 'number' {
@error "Expected number for $brightness, but got #{type-of($brightness)}: #{$brightness}";
}
// 获取基础颜色的HSL值
$hue: hue($base-color);
$saturation: saturation($base-color);
$lightness: lightness($base-color);
// 计算新的亮度值 (限制在0-100%范围内)
$new-lightness: clamp(0%, $lightness + $brightness, 100%);
// 使用HSL函数创建新颜色
$new-color: hsl($hue, $saturation, $new-lightness);
@return $new-color;
}

View File

@@ -0,0 +1,18 @@
// 品牌色-主色
$primary-color: #3283ff!default;
// 错误色
$error-color: #FF4D4F!default;
// 警告色
$warning-color: #ffb400!default;// #FF7D00!default;
// 信息色
$info-color: $primary-color!default;
// 成功色
$success-color: #34c471!default;
$blue: #3283ff!default;
$red: #FF4D4F!default;
$orange: #ffb400!default;
$yellow: #fcd53f!default;
$green: #34c471 !default;
$white: #fff;
$black: #000;

View File

@@ -0,0 +1,11 @@
/* #ifdef VUE3 */
@use "sass:math";
// #endif
@function divide($dividend, $divisor) {
/* #ifdef VUE3 */
@return math.div($dividend, $divisor);
/* #endif
/* #ifndef VUE3 */
@return $dividend / $divisor;
/* #endif
}

View File

@@ -0,0 +1,153 @@
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert';
export type DrawBorderOptions = {
direction : 'top' | 'bottom' | 'left' | 'right';
color ?: string;
colorKey ?: string; // 在dom中获取颜色
startOffsetKey?: string; // 在dom哪个属性获取
startOffset ?: number | string; // 支持数字或 CSS 字符串(如 '10px'
endOffset ?: number | string;
lineWidth ?: number;
watchSize ?: boolean; // 是否监听尺寸变化自动重绘
immediate ?: boolean; // 是否立即绘制
bordered?: boolean;
}
export type UseDrawBorderReturn = {
color: Ref<string>,
renderBorder: () => void,
clearBorder: () => void;
dispose: () => void,
}
/**
* 在元素上绘制边框,并支持动态监听尺寸变化
* @param elementRef 目标元素的 Ref
* @param options 边框配置
* @returns 清理函数(用于卸载时取消监听)
*/
export function useDrawBorder(
elementRef : Ref<UniElement | null>,
options : DrawBorderOptions
):UseDrawBorderReturn {
let resizeObserver : UniResizeObserver | null = null;
const { watchSize = true, immediate = true } = options;
const defalutColor = '#e7e7e7'
const color = ref(options.color ?? defalutColor)
const bordered = ref(options.bordered ?? true)
let computedStartOffset = 0
let computedEndOffset = 0
// 绘制边框
const renderBorder = () => {
if (elementRef.value == null) return;
const ctx = elementRef.value!.getDrawableContext();
if (ctx == null) return;
const rect = elementRef.value!.getBoundingClientRect();
ctx.reset();
const {
direction,
startOffset = 0,
endOffset = 0,
lineWidth = 0.5,
colorKey,
startOffsetKey,
} = options;
// 转换单位(如果是字符串,如 '10px'
if(computedStartOffset == 0) {
computedStartOffset = unitConvert((startOffsetKey != null ? elementRef.value?.style.getPropertyValue(startOffsetKey!) ?? startOffset : startOffset))
}
if(computedEndOffset == 0) {
computedEndOffset = unitConvert(endOffset)
}
if(color.value == defalutColor && colorKey != null) {
color.value = elementRef.value?.style.getPropertyValue(colorKey!) ?? defalutColor
// if(color.value.length == 0) {
// color.value = defalutColor
// }
}
ctx.strokeStyle = color.value;
ctx.lineWidth = lineWidth;
// 根据方向计算坐标
switch (direction) {
case 'top':
ctx.moveTo(computedStartOffset, 0);
ctx.lineTo(rect.width - computedEndOffset, 0);
break;
case 'bottom':
ctx.moveTo(computedStartOffset, rect.height - 0.25);
ctx.lineTo(rect.width - computedEndOffset, rect.height - 0.25);
break;
case 'left':
ctx.moveTo(0, computedStartOffset);
ctx.lineTo(0, rect.height - computedEndOffset);
break;
case 'right':
ctx.moveTo(rect.width, computedStartOffset);
ctx.lineTo(rect.width, rect.height - computedEndOffset);
break;
}
ctx.stroke();
ctx.update();
};
const setupResizeObserver = () => {
// 监听尺寸变化(如果启用)
if (watchSize) {
if (resizeObserver == null) {
resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
if(!bordered.value) return
renderBorder();
})
}
watchEffect(()=>{
if (elementRef.value != null) {
resizeObserver!.observe(elementRef.value!);
}
})
}
}
// 清理函数(卸载时取消监听)
const dispose = () => {
if (resizeObserver != null && elementRef.value != null) {
// resizeObserver.unobserve(elementRef.value!);
resizeObserver!.disconnect();
resizeObserver = null;
}
};
const clearBorder = ()=> {
if (elementRef.value == null) return;
const ctx = elementRef.value!.getDrawableContext();
if (ctx == null) return;
bordered.value = false
ctx.reset()
ctx.update()
}
setupResizeObserver()
// 初始绘制
if(immediate) {
renderBorder();
}
return {
renderBorder, // 手动触发绘制
dispose, // 清理监听
clearBorder,
color
} as UseDrawBorderReturn
}

View File

@@ -0,0 +1,7 @@
@import './theme/default';
@import './var';
// @import './mixins/ellipsis';
// @import './mixins/hairline';
@import './mixins/create';
@import './mixins/directionalProperty';
// @import './mixins/useTheme';

View File

@@ -0,0 +1,3 @@
export * from './token/useThemeMode'
export * from './token/useThemeVars'
export * from './token/interface'

View File

View File

@@ -0,0 +1,235 @@
/* #ifdef VUE3 */
@use "sass:math";
/* #endif */
/* #ifndef UNI-APP-X && APP || APP-NVUE */
$use-css-var: true !default;
/* #endif */
/* #ifdef UNI-APP-X && APP || APP-NVUE */
$use-css-var: false !default;
/* #endif */
@function div($dividend, $divisor) {
/* #ifdef VUE3 */
@return math.div($dividend, $divisor);
/* #endif */
/* #ifndef VUE3 */
@return $dividend / $divisor;
/* #endif */
}
@function rpx-to-px($value) {
// 递归处理列表
@if type-of($value) == list {
$new-list: ();
@each $item in $value {
$new-list: append($new-list, rpx-to-px($item));
}
@return $new-list;
}
// 处理数字类型 - 带 rpx 单位
@if type-of($value) == number and unit($value) == 'rpx' {
// 安全处理单位转换
@return calc-strip-unit($value) * 0.5 * 1px;
}
// 处理字符串类型
@if type-of($value) == string {
$string: $value;
$rpx-index: str-index($string, 'rpx');
// 如果字符串以数字开头并以 rpx 结尾,转换为数值
@if $rpx-index == (str-length($string) - 2) {
$num-str: str-slice($string, 1, $rpx-index - 1);
$number: to-number($num-str);
@if type-of($number) == number {
@return $number * 0.5 * 1px;
}
}
// 字符串中可能包含多个 rpx 值
@if $rpx-index {
$result: '';
@while $rpx-index {
// 找到数字部分起点
$num-end: $rpx-index - 1;
$num-start: $num-end;
@while $num-start > 0 and is-numeric-char(str-slice($string, $num-start, $num-start)) {
$num-start: $num-start - 1;
}
// 提取数字部分
$num-str: str-slice($string, $num-start + 1, $num-end);
$number: to-number($num-str);
// 转换为 px 数值
$px-value: $number * 0.5 * 1px;
// 构建结果字符串
$result: $result + str-slice($string, 1, $num_start) + '#{$px_value}';
// 更新剩余字符串
$string: str-slice($string, $rpx-index + 3);
$rpx-index: str-index($string, 'rpx');
}
@return #{$result + $string};
}
}
// 其他类型直接返回
@return $value;
}
// 辅助函数:安全去除单位并返回数值
@function calc-strip-unit($number) {
@if type-of($number) == number {
$unit: unit($number);
$units: ("px": 1px, "rpx": 1rpx, "em": 1em, "rem": 1rem, "%": 1%);
@if map-has-key($units, $unit) {
@return div($number , map-get($units, $unit));
}
@if unitless($number) {
@return $number;
}
}
@return $number;
}
// 辅助函数:检查字符是否为数字
@function is-numeric-char($char) {
$chars: "-.0123456789";
@return str-index($chars, $char) != null;
}
// 辅助函数:将字符串安全转换为数字
@function to-number($string) {
// 如果输入已经是数字,直接返回
@if type-of($string) == number {
@return $string;
}
// 处理带符号的数字
$is-negative: false;
$numeric: "";
$found-number: false;
// 提取所有数字字符
@for $i from 1 through str-length($string) {
$char: str-slice($string, $i, $i);
@if $char == "-" and $numeric == "" {
$is-negative: true;
}
@else if $char == "." and str-index($numeric, ".") == null {
$numeric: $numeric + $char;
}
@else if $char >= "0" and $char <= "9" {
$numeric: $numeric + $char;
$found-number: true;
}
}
// 如果有实际数字内容,转换为数值
@if $found-number {
$result: 0;
$decimal-index: str-index($numeric, ".");
@if $decimal-index {
// 处理带小数的数字
$integer-part: str-slice($numeric, 1, $decimal-index - 1);
$decimal-part: str-slice($numeric, $decimal-index + 1);
@if $integer-part == "" { $integer-part: "0"; }
$result: to-integer($integer-part);
$divisor: 1;
@for $i from 1 through str-length($decimal-part) {
$divisor: $divisor * 10;
$digit: to-integer(str-slice($decimal-part, $i, $i));
$result: $result + ($digit / $divisor);
}
} @else {
// 处理整数
$result: to-integer($numeric);
}
@return if($is-negative, -$result, $result);
}
// 无法转换则返回原字符串
@return $string;
}
// 辅助函数:将整数字符串转换为数字
@function to-integer($string) {
$result: 0;
@for $i from 1 through str-length($string) {
$char: str-slice($string, $i, $i);
$result: $result * 10 + (str-index("0123456789", $char) - 1);
}
@return $result;
}
@function create-var($name, $values...) {
// 将不定数量的参数转换为列表
$value-list: $values;
$css-value: null;
@if length($value-list) == 0 {
// @warn "The list must have at least 1 values.";
@return '';
} @else {
// 初始化CSS变量的值为列表中的第一个值
/* #ifndef VUE2 */
$css-value: nth($value-list, 1);
/* #endif */
/* #ifdef VUE2 */
$css-value: rpx-to-px(nth($value-list, 1));
/* #endif */
}
// 检查列表长度是否大于等于2
@if length($value-list) >= 2 {
// 使用@for循环遍历剩余的值并构建CSS变量的完整值
@for $i from 2 through length($value-list) {
/* #ifndef VUE2 */
$css-value: $css-value + ", " + nth($value-list, $i);
/* #endif */
/* #ifdef VUE2 */
$css-value: $css-value + ", " + rpx-to-px(nth($value-list, $i));
/* #endif */
}
}
// /* #ifndef UNI-APP-X */
// @return var(--l-#{$name}, #{$css-value});
// /* #endif */
// /* #ifdef UNI-APP-X && APP */
// @if $use-css-var {
// @return var(--l-#{$name}, #{$css-value});
// } @else {
// @return $css-value;
// }
// /* #endif */
// /* #ifdef APP-NVUE */
// @return $css-value;
// /* #endif */
// @return var(--l-#{$name}, #{$css-value});
@if $use-css-var {
@return var(--l-#{$name}, #{$css-value});
} @else {
@return $css-value;
}
}

View File

@@ -0,0 +1,401 @@
// =======================================================================
// 方向属性核心生成器 (支持所有方向相关属性)
// =======================================================================
@mixin directional-property(
$property,
$values,
$exclude: ()
) {
// 属性类型分组
$group-standard: padding, margin; // 标准方向属性
$group-radius: border-radius; // 圆角属性
$group-border: border, border-top, border-right, border-bottom, border-left; // 边框属性
$group-outline: outline, outline-top, outline-right, outline-bottom, outline-left; // 轮廓属性
$group-position: inset, inset-block, inset-inline, top, right, bottom, left; // 定位属性
$group-size: block-size, inline-size; // 块/行尺寸属性
// 定义每个方向的值
$top: null;
$right: null;
$bottom: null;
$left: null;
// 确定处理模式
$is-standard: index($group-standard, $property);
$is-radius: index($group-radius, $property);
$is-border: index($group-border, $property);
$is-outline: index($group-outline, $property);
$is-position: index($group-position, $property);
$is-size: index($group-size, $property);
// =====================================================================
// 解析输入值 - 根据属性类型处理
// =====================================================================
@if type-of($values) == 'list' {
$length: length($values);
// 单个值 - 所有方向相同值
@if $length == 1 {
$top: nth($values, 1);
$right: nth($values, 1);
$bottom: nth($values, 1);
$left: nth($values, 1);
}
// 两个值
@else if $length == 2 {
// 特殊处理border-radius
@if $is-radius {
$top: nth($values, 1);
$right: nth($values, 2);
$bottom: nth($values, 1);
$left: nth($values, 2);
}
// 标准处理:上下 | 左右
@else if $is-standard or $is-border or $is-outline or $is-position {
$top: nth($values, 1);
$bottom: nth($values, 1);
$right: nth($values, 2);
$left: nth($values, 2);
}
// 块/行尺寸处理
@else if $is-size {
$top: nth($values, 1);
$right: nth($values, 2);
}
}
// 三个值
@else if $length == 3 {
// border-radius特殊处理
@if $is-radius {
$top: nth($values, 1);
$right: nth($values, 2);
$bottom: nth($values, 3);
$left: nth($values, 2);
}
// 标准处理
@else if $is-standard or $is-border or $is-outline or $is-position {
$top: nth($values, 1);
$right: nth($values, 2);
$left: nth($values, 2);
$bottom: nth($values, 3);
}
}
// 四个值
@else if $length == 4 {
$top: nth($values, 1);
$right: nth($values, 2);
$bottom: nth($values, 3);
$left: nth($values, 4);
}
}
@else {
// 单个值传递
$top: $values;
$right: $values;
$bottom: $values;
$left: $values;
}
// =====================================================================
// 生成CSS属性排除指定方向
// =====================================================================
// 1. 圆角处理
@if $is-radius {
@if not index($exclude, top-left) and $top != null {
border-top-left-radius: $top;
}
@if not index($exclude, top-right) and $right != null {
border-top-right-radius: $right;
}
@if not index($exclude, bottom-right) and $bottom != null {
border-bottom-right-radius: $bottom;
}
@if not index($exclude, bottom-left) and $left != null {
border-bottom-left-radius: $left;
}
}
// 2. 标准方向处理 (padding, margin)
@else if $is-standard {
@if not index($exclude, top) and $top != null {
#{$property}-top: $top;
}
@if not index($exclude, right) and $right != null {
#{$property}-right: $right;
}
@if not index($exclude, bottom) and $bottom != null {
#{$property}-bottom: $bottom;
}
@if not index($exclude, left) and $left != null {
#{$property}-left: $left;
}
}
// 3. 边框处理
@else if $is-border {
// 完整边框设置
@if $property == 'border' {
@if not index($exclude, top) and $top != null {
border-top-width: nth($values, 1);
border-top-style: nth($values, 2);
border-top-color: nth($values, 3);
}
@if not index($exclude, right) and $right != null {
border-right-width: nth($values, 1);
border-right-style: nth($values, 2);
border-right-color: nth($values, 3);
}
@if not index($exclude, bottom) and $bottom != null {
// border-bottom: $bottom;
border-bottom-width: nth($values, 1);
border-bottom-style: nth($values, 2);
border-bottom-color: nth($values, 3);
}
@if not index($exclude, left) and $left != null {
// border-left: $left;
border-left-width: nth($values, 1);
border-left-style: nth($values, 2);
border-left-color: nth($values, 3);
}
}
// 单边边框
@else {
$direction: str-slice($property, 8); // 提取方向
@if not index($exclude, $direction) {
// #{$property}: $top;
#{$property}-width: nth($values, 1);
#{$property}-style: nth($values, 2);
#{$property}-color: nth($values, 3);
}
}
}
// 4. 轮廓处理
@else if $is-outline {
// 完整轮廓设置
@if $property == 'outline' {
@if not index($exclude, top) and $top != null {
outline-top: $top;
}
@if not index($exclude, right) and $right != null {
outline-right: $right;
}
@if not index($exclude, bottom) and $bottom != null {
outline-bottom: $bottom;
}
@if not index($exclude, left) and $left != null {
outline-left: $left;
}
}
// 单边轮廓
@else {
$direction: str-slice($property, 8); // 提取方向
@if not index($exclude, $direction) {
#{$property}: $top;
}
}
}
// 5. 定位处理
@else if $is-position {
// inset简写处理
@if $property == 'inset' {
@if not index($exclude, top) and $top != null {
top: $top;
}
@if not index($exclude, right) and $right != null {
right: $right;
}
@if not index($exclude, bottom) and $bottom != null {
bottom: $bottom;
}
@if not index($exclude, left) and $left != null {
left: $left;
}
}
// inset-block 和 inset-inline
@else if $property == 'inset-block' {
@if not index($exclude, top) and $top != null {
top: $top;
}
@if not index($exclude, bottom) and $bottom != null {
bottom: $bottom;
}
}
@else if $property == 'inset-inline' {
@if not index($exclude, left) and $left != null {
left: $left;
}
@if not index($exclude, right) and $right != null {
right: $right;
}
}
// 单一定位
@else {
@if not index($exclude, $property) {
#{$property}: $top;
}
}
}
// 6. 尺寸处理
@else if $is-size {
@if $property == 'block-size' {
@if not index($exclude, top) and $top != null {
height: $top;
}
}
@else if $property == 'inline-size' {
@if not index($exclude, left) and $left != null {
width: $left;
}
}
}
}
// =======================================================================
// 快捷混合宏:标准属性
// =======================================================================
@mixin padding($values, $exclude: ()) {
@include directional-property(padding, $values, $exclude);
}
@mixin margin($values, $exclude: ()) {
@include directional-property(margin, $values, $exclude);
}
@mixin border-radius($values, $exclude: ()) {
@include directional-property(border-radius, $values, $exclude);
}
// =======================================================================
// 快捷混合宏:边框属性
// =======================================================================
@mixin border($value, $exclude: ()) {
@include directional-property(border, $value, $exclude);
}
@mixin border-top($value, $exclude: ()) {
@include directional-property(border-top, $value, $exclude);
}
@mixin border-right($value, $exclude: ()) {
@include directional-property(border-right, $value, $exclude);
}
@mixin border-bottom($value, $exclude: ()) {
@include directional-property(border-bottom, $value, $exclude);
}
@mixin border-left($value, $exclude: ()) {
@include directional-property(border-left, $value, $exclude);
}
// =======================================================================
// 快捷混合宏:轮廓属性
// =======================================================================
@mixin outline($value, $exclude: ()) {
@include directional-property(outline, $value, $exclude);
}
@mixin outline-top($value, $exclude: ()) {
@include directional-property(outline-top, $value, $exclude);
}
@mixin outline-right($value, $exclude: ()) {
@include directional-property(outline-right, $value, $exclude);
}
@mixin outline-bottom($value, $exclude: ()) {
@include directional-property(outline-bottom, $value, $exclude);
}
@mixin outline-left($value, $exclude: ()) {
@include directional-property(outline-left, $value, $exclude);
}
// =======================================================================
// 快捷混合宏:定位属性
// =======================================================================
@mixin inset($values, $exclude: ()) {
@include directional-property(inset, $values, $exclude);
}
@mixin inset-block($values, $exclude: ()) {
@include directional-property(inset-block, $values, $exclude);
}
@mixin inset-inline($values, $exclude: ()) {
@include directional-property(inset-inline, $values, $exclude);
}
@mixin top($value, $exclude: ()) {
@include directional-property(top, $value, $exclude);
}
@mixin right($value, $exclude: ()) {
@include directional-property(right, $value, $exclude);
}
@mixin bottom($value, $exclude: ()) {
@include directional-property(bottom, $value, $exclude);
}
@mixin left($value, $exclude: ()) {
@include directional-property(left, $value, $exclude);
}
// =======================================================================
// 快捷混合宏:尺寸属性
// =======================================================================
@mixin block-size($value, $exclude: ()) {
@include directional-property(block-size, $value, $exclude);
}
@mixin inline-size($value, $exclude: ()) {
@include directional-property(inline-size, $value, $exclude);
}
// =======================================================================
// 组合混合宏
// =======================================================================
// 带圆角的边框
@mixin bordered($border-value, $radius-value) {
@include border($border-value);
@include border-radius($radius-value);
}
// 绝对定位容器
@mixin absolute-container($inset: 0) {
position: absolute;
@include inset($inset);
}
// 固定定位容器
@mixin fixed-container($inset: 0) {
position: fixed;
@include inset($inset);
}
// =======================================================================
// 圆角辅助混合宏
// =======================================================================
@mixin border-top-radius($value) {
@include border-radius($value 0 0, (bottom-right, bottom-left));
}
@mixin border-right-radius($value) {
@include border-radius(0 $value 0 0, (top-left, bottom-left));
}
@mixin border-bottom-radius($value) {
@include border-radius(0 0 $value, (top-right, top-left));
}
@mixin border-left-radius($value) {
@include border-radius(0 0 0 $value, (top-right, bottom-right));
}

View File

@@ -0,0 +1,22 @@
@mixin ellipsis {
// overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* #ifndef UNI-APP-X && APP || APP-NVUE */
word-wrap: normal;
/* #endif */
}
@mixin ellipsisLn($line) {
// overflow: hidden;
text-overflow: ellipsis;
/* #ifdef UNI-APP-X && APP || APP-NVUE */
lines: $line;
/* #endif */
/* #ifndef UNI-APP-X && APP || APP-NVUE */
-webkit-line-clamp: $line;
display: -webkit-box;
-webkit-box-orient: vertical;
/* #endif */
}

View File

@@ -0,0 +1,20 @@
@mixin flex {
/* #ifndef UNI-APP-X */
display: flex;
/* #endif */
}
@mixin flex-column {
/* #ifndef UNI-APP-X */
flex-direction: column;
/* #endif */
}
@mixin flex-row {
flex-direction: row;
}
@mixin universal {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
}

View File

@@ -0,0 +1,66 @@
// @import '../theme/default.scss';
@mixin hairline-base {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
transform-origin: center; /* cover wechat button:after default transforn-origin */
}
@mixin hairline($color: $border-color-2) {
@include hairline-base;
top: -50%;
right: -50%;
bottom: -50%;
left: -50%;
border: 1px solid $color;
transform: scale(.5);
}
@mixin hairline-top($color: $border-color-1, $left: 0, $right: 0) {
@include hairline-base;
top: 0;
right: $right;
left: $left;
border-top: 1px solid $color;
transform: scaleY(0.5);
}
@mixin hairline-bottom($color: $border-color-1, $left: 0, $right: 0) {
@include hairline-base;
right: $right;
bottom: 0;
left: $left;
border-bottom: 1px solid $color;
transform: scaleY(0.5);
}
@mixin hairline-left($color: $border-bolor-1) {
@include hairline-base;
top: 0;
bottom: 0;
left: 0;
border-left: 1px solid $color;
transform: scaleX(.5);
}
@mixin hairline-right($color: $border-bolor-1) {
@include hairline-base;
top: 0;
bottom: 0;
right: 0;
border-right: 1px solid $color;
transform: scaleX(.5);
}
// @mixin border {
// /* #ifndef UNI-APP-X && APP */
// &:after {
// @content;
// }
// /* #endif */
// /* #ifdef UNI-APP-X && APP */
// @content;
// /* #endif */
// }

View File

@@ -0,0 +1,17 @@
/* #ifdef APP-NVUE || UNI-APP-X && APP */
$is-app: true;
/* #endif */
/* #ifndef APP-NVUE || UNI-APP-X && APP */
$is-app: false;
/* #endif */
@mixin is-app {
@if $is-app {
@content;
}
}
@mixin not-app {
@if not($is-app) {
@content;
}
}

View File

@@ -0,0 +1,60 @@
$limeThemes: light, dark;
$theme: light;
@mixin use-theme($mode: null) {
@if $mode != null {
/* #ifndef UNI-APP-X && APP || APP-NVUE */
@media (prefers-color-scheme: $mode) {
@content;
}
/* #endif */
/* #ifdef UNI-APP-X && APP || APP-NVUE */
&.#{$mode} {
@content;
}
/* #endif */
} @else {
@each $mode in $limeThemes {
$theme: $mode !global;
/* #ifndef UNI-APP-X && APP || APP-NVUE */
@media (prefers-color-scheme: $mode) {
@content;
}
/* #endif */
/* #ifdef UNI-APP-X && APP || APP-NVUE */
&.#{$mode} {
@content;
}
/* #endif */
}
}
}
@mixin theme-dark {
/* #ifndef UNI-APP-X && APP || APP-NVUE */
@media (prefers-color-scheme: dark) {
page {
@content;
}
}
/* #endif */
/* #ifdef UNI-APP-X && APP || APP-NVUE */
.dark {
@content;
}
/* #endif */
/* #ifdef WEB */
:root[data-lime-theme='dark'] page {
@content;
}
/* #endif */
}
@function get-var($themes, $key) {
@return map-get($themes, $key)
}

View File

@@ -0,0 +1,7 @@
@import './flex';
@mixin resize-none {
/* #ifndef UNI-APP-X && APP */
resize: none;
/* #endif */
}

View File

@@ -0,0 +1,103 @@
{
"id": "lime-style",
"displayName": "lime-style",
"version": "0.2.4",
"description": "lime-style",
"keywords": [
"lime-style"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0",
"uni-app": "^4.44",
"uni-app-x": "^4.61"
},
"dcloudext": {
"type": "sdk-js",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "√"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": {
"extVersion": "",
"minVersion": "22"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "√",
"jd": "√",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "22"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
# lime-style
## 更换色系
在uni.scss中增加
```css
// 品牌色-主色
$primary-color: #3283ff;
// 错误色
$error-color: #FF4D4F;
// 警告色
$warning-color: #ffb400;
// 成功色
$success-color: #34c471;
$blue: #3283ff;
```

View File

@@ -0,0 +1,635 @@
@import '../mixins/create.scss';
@import '../color/colorPalette.scss';
@import '../color/colors.scss';
@import '../mixins/useTheme';
$blue-1: #{genColor($blue, 1, dark)};
$blue-2: #{genColor($blue, 2, dark)};
$blue-3: #{genColor($blue, 3, dark)};
$blue-4: #{genColor($blue, 4, dark)};
$blue-5: #{genColor($blue, 5, dark)};
$blue-6: #{genColor($blue, 6, dark)};
$blue-7: #{genColor($blue, 7, dark)};
$blue-8: #{genColor($blue, 8, dark)};
$blue-9: #{genColor($blue, 9, dark)};
$blue-10: #{genColor($blue, 10, dark)};
$info-color-1: #{genColor($info-color, 1, dark)};
$info-color-2: #{genColor($info-color, 2, dark)};
$info-color-3: #{genColor($info-color, 3, dark)};
$info-color-4: #{genColor($info-color, 4, dark)};
$info-color-5: #{genColor($info-color, 5, dark)};
$info-color-6: #{genColor($info-color, 6, dark)};
$info-color-7: #{genColor($info-color, 7, dark)};
$info-color-8: #{genColor($info-color, 8, dark)};
$info-color-9: #{genColor($info-color, 9, dark)};
$info-color-10: #{genColor($info-color, 10, dark)};
$primary-color-1: #{genColor($primary-color, 1, dark)}; // 浅色/白底悬浮
$primary-color-2: #{genColor($primary-color, 2, dark)}; // 文字禁用
$primary-color-3: #{genColor($primary-color, 3, dark)}; // 一般禁用
$primary-color-4: #{genColor($primary-color, 4, dark)}; // 特殊场景 禁用
$primary-color-5: #{genColor($primary-color, 5, dark)}; // 悬浮
$primary-color-6: #{genColor($primary-color, 6, dark)}; // 常规
$primary-color-7: #{genColor($primary-color, 7, dark)}; // 点击
$primary-color-8: #{genColor($primary-color, 8, dark)}; //
$primary-color-9: #{genColor($primary-color, 9, dark)};
$primary-color-10: #{genColor($primary-color, 10, dark)};
$error-color-1: #{genColor($error-color, 1, dark)};
$error-color-2: #{genColor($error-color, 2, dark)};
$error-color-3: #{genColor($error-color, 3, dark)};
$error-color-4: #{genColor($error-color, 4, dark)};
$error-color-5: #{genColor($error-color, 5, dark)};
$error-color-6: #{genColor($error-color, 6, dark)};
$error-color-7: #{genColor($error-color, 7, dark)};
$error-color-8: #{genColor($error-color, 8, dark)};
$error-color-9: #{genColor($error-color, 9, dark)};
$error-color-10: #{genColor($error-color, 10, dark)};
$warning-color-1: #{genColor($warning-color, 1, dark)};
$warning-color-2: #{genColor($warning-color, 2, dark)};
$warning-color-3: #{genColor($warning-color, 3, dark)};
$warning-color-4: #{genColor($warning-color, 4, dark)};
$warning-color-5: #{genColor($warning-color, 5, dark)};
$warning-color-6: #{genColor($warning-color, 6, dark)};
$warning-color-7: #{genColor($warning-color, 7, dark)};
$warning-color-8: #{genColor($warning-color, 8, dark)};
$warning-color-9: #{genColor($warning-color, 9, dark)};
$warning-color-10: #{genColor($warning-color, 10, dark)};
$success-color-1: #{genColor($success-color, 1, dark)}; // 浅色/白底悬浮
$success-color-2: #{genColor($success-color, 2, dark)}; // 文字禁用
$success-color-3: #{genColor($success-color, 3, dark)}; // 一般禁用
$success-color-4: #{genColor($success-color, 4, dark)}; // 特殊场景
$success-color-5: #{genColor($success-color, 5, dark)}; // 悬浮
$success-color-6: #{genColor($success-color, 6, dark)}; // 常规
$success-color-7: #{genColor($success-color, 7, dark)}; // 点击
$success-color-8: #{genColor($success-color, 8, dark)};
$success-color-9: #{genColor($success-color, 9, dark)};
$success-color-10: #{genColor($success-color, 10, dark)};
// $gray-1: #f3f3f3;
// $gray-2: #eeeeee;
// $gray-3: #e7e7e7;
// $gray-4: #dcdcdc;
// $gray-5: #c5c5c5;
// $gray-6: #a6a6a6;
// $gray-7: #8b8b8b;
// $gray-8: #777777;
// $gray-9: #5e5e5e;
// $gray-10: #4b4b4b;
// $gray-11: #383838;
// $gray-12: #2c2c2c;
// $gray-13: #242424;
// $gray-14: #181818;
// 暗色模式的灰度
$gray-1: #181818; // 最深
$gray-2: #242424;
$gray-3: #2c2c2c;
$gray-4: #383838;
$gray-5: #4b4b4b;
$gray-6: #5e5e5e;
$gray-7: #777777;
$gray-8: #8b8b8b;
$gray-9: #a6a6a6;
$gray-10: #c5c5c5;
$gray-11: #dcdcdc;
$gray-12: #e7e7e7;
$gray-13: #eeeeee;
$gray-14: #f3f3f3; // 最浅
$text-color-1: rgba(255,255,255,0.85); //primary
$text-color-2: rgba(255,255,255,0.65); //secondary
$text-color-3: rgba(255,255,255,0.45); //placeholder
$text-color-4: rgba(255,255,255,0.25); //disabled
// 容器
$bg-color-page: $gray-1;//#000000; // 整体背景色 布局
$bg-color-container: $gray-2;//#1d1d1d; // 一级容器背景 组件
$bg-color-elevated: $gray-2;//#1f1f1f; // 二级容器背景 浮层
$bg-color-spotlight: $gray-5; // 引起注意的如 Tooltip
$bg-color-mask: rgba(0, 0, 0, 0.65); // 蒙层
// 填充
$fill-1: rgba(255,255,255,0.18);
$fill-2: rgba(255,255,255,0.12);
$fill-3: rgba(255,255,255,0.08);
$fill-4: rgba(255,255,255,0.04);
// 描边
$border-color-1: $gray-3;//#{getSolidColor(#000, 26%)}; //#424242; // 浅色
$border-color-2: $gray-4;//#{getSolidColor(#000, 19%)}; //#303030; // 一般
$border-color-3: $gray-5;//#{getSolidColor(#000, 15%)}; //$gray-4; // 深/悬浮
$border-color-4: $gray-6;//#{getSolidColor(#000, 12%)}; //$gray-6; // 重/按钮描边
@mixin theme-dark {
// 蓝色系
--l-blue-1: #{$blue-1};
--l-blue-2: #{$blue-2};
--l-blue-3: #{$blue-3};
--l-blue-4: #{$blue-4};
--l-blue-5: #{$blue-5};
--l-blue-6: #{$blue-6};
--l-blue-7: #{$blue-7};
--l-blue-8: #{$blue-8};
--l-blue-9: #{$blue-9};
--l-blue-10: #{$blue-10};
--l-info-color-1: #{$info-color-1};
--l-info-color-2: #{$info-color-2};
--l-info-color-3: #{$info-color-3};
--l-info-color-4: #{$info-color-4};
--l-info-color-5: #{$info-color-5};
--l-info-color-6: #{$info-color-6};
--l-info-color-7: #{$info-color-7};
--l-info-color-8: #{$info-color-8};
--l-info-color-9: #{$info-color-9};
--l-info-color-10: #{$info-color-10};
// 主色系
--l-primary-color-1: #{$primary-color-1};
--l-primary-color-2: #{$primary-color-2};
--l-primary-color-3: #{$primary-color-3};
--l-primary-color-4: #{$primary-color-4};
--l-primary-color-5: #{$primary-color-5};
--l-primary-color-6: #{$primary-color-6};
--l-primary-color-7: #{$primary-color-7};
--l-primary-color-8: #{$primary-color-8};
--l-primary-color-9: #{$primary-color-9};
--l-primary-color-10: #{$primary-color-10};
// 错误色系
--l-error-color-1: #{$error-color-1};
--l-error-color-2: #{$error-color-2};
--l-error-color-3: #{$error-color-3};
--l-error-color-4: #{$error-color-4};
--l-error-color-5: #{$error-color-5};
--l-error-color-6: #{$error-color-6};
--l-error-color-7: #{$error-color-7};
--l-error-color-8: #{$error-color-8};
--l-error-color-9: #{$error-color-9};
--l-error-color-10: #{$error-color-10};
// 警告色系
--l-warning-color-1: #{$warning-color-1};
--l-warning-color-2: #{$warning-color-2};
--l-warning-color-3: #{$warning-color-3};
--l-warning-color-4: #{$warning-color-4};
--l-warning-color-5: #{$warning-color-5};
--l-warning-color-6: #{$warning-color-6};
--l-warning-color-7: #{$warning-color-7};
--l-warning-color-8: #{$warning-color-8};
--l-warning-color-9: #{$warning-color-9};
--l-warning-color-10: #{$warning-color-10};
// 成功色系
--l-success-color-1: #{$success-color-1};
--l-success-color-2: #{$success-color-2};
--l-success-color-3: #{$success-color-3};
--l-success-color-4: #{$success-color-4};
--l-success-color-5: #{$success-color-5};
--l-success-color-6: #{$success-color-6};
--l-success-color-7: #{$success-color-7};
--l-success-color-8: #{$success-color-8};
--l-success-color-9: #{$success-color-9};
--l-success-color-10: #{$success-color-10};
// 灰色系
--l-gray-1: #{$gray-1};
--l-gray-2: #{$gray-2};
--l-gray-3: #{$gray-3};
--l-gray-4: #{$gray-4};
--l-gray-5: #{$gray-5};
--l-gray-6: #{$gray-6};
--l-gray-7: #{$gray-7};
--l-gray-8: #{$gray-8};
--l-gray-9: #{$gray-9};
--l-gray-10: #{$gray-10};
--l-gray-11: #{$gray-11};
--l-gray-12: #{$gray-12};
--l-gray-13: #{$gray-13};
--l-gray-14: #{$gray-14};
// 文字颜色
--l-text-color-1: #{$text-color-1};
--l-text-color-2: #{$text-color-2};
--l-text-color-3: #{$text-color-3};
--l-text-color-4: #{$text-color-4};
// 容器背景色
--l-bg-color-page: #{$bg-color-page};
--l-bg-color-container: #{$bg-color-container};
--l-bg-color-elevated: #{$bg-color-elevated};
--l-bg-color-spotlight: #{$bg-color-spotlight};
--l-bg-color-mask: #{$bg-color-mask};
// 填充色
--l-fill-1: #{$fill-1};
--l-fill-2: #{$fill-2};
--l-fill-3: #{$fill-3};
--l-fill-4: #{$fill-4};
// 描边色
--l-border-color-1: #{$border-color-1};
--l-border-color-2: #{$border-color-2};
--l-border-color-3: #{$border-color-3};
--l-border-color-4: #{$border-color-4};
// loading
--l-loading-text-color: #{$text-color-3};
// load-more
--l-load-more-text-color: #{$text-color-3};
--l-load-more-color: #{$text-color-4};
// 按钮
--l-button-default-solid-text-color: #000;
--l-button-default-border-color: #{$gray-5};
--l-button-default-color: #{$gray-14};
--l-button-default-light-color: #{$gray-4};
--l-button-default-light-hover-color: #{$gray-5};
--l-button-primary-light-color: #{$primary-color-2};
--l-button-primary-light-hover-color: #{$primary-color-3};
--l-button-danger-light-color: #{$error-color-2};
--l-button-danger-light-hover-color: #{$error-color-3};
--l-button-warning-light-color: #{$warning-color-2};
--l-button-warning-light-hover-color: #{$warning-color-3};
--l-button-success-light-color: #{$success-color-2};
--l-button-success-light-hover-color: #{$success-color-3};
--l-button-info-light-color: #{$info-color-1};
--l-button-info-light-hover-color: #{$info-color-2};
--l-button-primary-color: #{$primary-color-6};
--l-button-danger-color: #{$error-color-6};
--l-button-warning-color: #{$warning-color-6};
--l-button-info-color: #{$info-color-6};
// 遮罩
// --l-overlay-bg-color: #{$bg-color-mask};
// 弹窗
--l-popup-bg-color: #{$bg-color-elevated};
--l-popup-close-icon-color: #{$text-color-2};
// 单元格
--l-cell-group-title-color: #{$text-color-3};
--l-cell-group-border-color: #{$border-color-3};
--l-cell-bg-color: #{$bg-color-container};
--l-cell-hover-color: #{$gray-3};
--l-cell-border-color: #{$border-color-2};
--l-cell-note-color: #{$text-color-3};
--l-cell-description-color: #{$text-color-2};
--l-cell-title-color: #{$text-color-1};
--l-cell-right-icon-color: #{$text-color-3};
// 步进器
--l-stepper-button-bg-color: #{$gray-4};
--l-stepper-input-disabled-bg: #{$gray-3};
--l-stepper-input-color: #{$text-color-1};
--l-stepper-input-disabled-color: #{$text-color-4};
--l-stepper-border-color: #{$gray-4};
// tabbar
--l-tabbar-bg-color: #{$bg-color-container};
--l-tabbar-border-color: #{$border-color-1};
--l-tabbar-color: #{$text-color-1};
--l-tabbar-active-color: #{$primary-color-6};
--l-tabbar-active-bg-color: #{$primary-color-1};
// link
--l-link-default-color: #{$text-color-1};
// image
--l-image-loading-bg-color: #{$fill-3};
--l-image-color: #{$text-color-3};
// divider 分割线
--l-divider-color: #{$border-color-2};
--l-divider-text-color: #{$text-color-2};
// card 卡片
--l-card-bg-color: #{$bg-color-container};
--l-card-title-color: #{$text-color-1};
--l-card-extra-color: #{$text-color-3};
--l-card-note-color: #{$text-color-3};
--l-card-border-color: #{$border-color-2};
// grid 宫格
--l-grid-item-bg-color: #{$bg-color-container};
--l-grid-item-image-bg-color: #{$fill-4};
--l-grid-item-text-color: #{$text-color-1};
--l-grid-item-hover-bg-color: #{$fill-3};
--l-grid-item-description-color: #{$text-color-3};
--l-grid-item-border-color: #{$border-color-2};
--l-grid-item-icon-color: #{$text-color-1};
// cascader 级联
--l-cascader-title-color: #{$text-color-1};
--l-cascader-bg-color: #{$bg-color-container};
--l-cascader-cell-title-color: #{$text-color-1};
--l-cascader-disabled-color: #{$text-color-3};
--l-cascader-options-title-color: #{$text-color-3};
--l-cascader-close-icon-color: #{$text-color-2};
// tabs 标签页
--l-tab-nav-bg-color: #{$bg-color-container};
--l-tab-content-bg-color: #{$bg-color-container};
--l-tab-split-color: #{$border-color-1};
--l-tab-item-color: #{$text-color-2};
--l-tab-item-disabled-color: #{$text-color-4};
// checkbox 复选框
--l-checkbox-border-icon-color: #{$gray-8};
--l-checkbox-icon-bg-color: #{$bg-color-container};
--l-checkbox-text-color: #{$text-color-1};
--l-checkbox-icon-disabled-bg-color: #{$gray-3};
--l-checkbox-icon-disabled-color: #{$gray-6};
// radio 单选框
--l-radio-border-icon-color: #{$gray-8};
--l-radio-icon-bg-color: #{$bg-color-container};
--l-radio-text-color: #{$text-color-1};
--l-radio-icon-disabled-bg-color: #{$gray-3};
--l-radio-icon-disabled-color: #{$gray-6};
// search 搜索
--l-search-bg-color: #{$fill-3};
--l-search-icon-color: #{$text-color-4};
--l-search-clear-icon-color: #{$text-color-4};
--l-search-text-color: #{$text-color-1};
--l-search-label-color: #{$text-color-1};
--l-search-placeholder-color: #{$text-color-3};
// switch
--l-switch-checked-disabled-color: #{$primary-color-3};
--l-switch-unchecked-color: #{$gray-5};
--l-switch-unchecked-disabled-color: #{$gray-4};
// input 输入框
--l-input-text-color: #{$text-color-1};
--l-input-tips-color: #{$text-color-3};
--l-input-bg-color: #{$bg-color-container};
--l-input-border-color: #{$border-color-2};
--l-input-placeholder-text-color: #{$text-color-3};
--l-input-prefix-icon-color: #{$text-color-1};
--l-input-suffix-icon-color: #{$text-color-3};
--l-input-label-text-color: #{$text-color-1};
--l-input-suffix-text-color: #{$text-color-1};
--l-input-disabled-text-color: #{$text-color-4};
--l-input-disabled-bg-color: #{$fill-3};
--l-input-border-color: #{$border-color-2};
// textarea
--l-textarea-bg-color: #{$bg-color-container};
--l-textarea-placeholder-color: #{$text-color-3};
--l-textarea-text-color: #{$text-color-1};
--l-textarea-label-color: #{$text-color-1};
--l-textarea-indicator-text-color: #{$text-color-3};
--l-textarea-border-color: #{$border-color-2};
--l-textarea-disabled-text-color: #{$text-color-4};
// upload
--l-upload-bg-color: #{$gray-4};
--l-upload-add-bg-color: #{$gray-4};
--l-upload-add-color: #{$text-color-3};
// picker
--l-picker-bg-color: #{$bg-color-container};
--l-picker-cancel-color: #{$text-color-2};
--l-picker-title-color: #{$text-color-1};
--l-picker-item-active-color: #{$text-color-1};
--l-picker-mask-top-color: #{$gray-2};
--l-picker-loading-mask-color: rgba(0,0,0,.6);
--l-picker-item-color: #{$text-color-1};
--l-picker-indicator-bg-color: #{$fill-3};
// empty
--l-empty-opacity: 0.6;
// segmented
--l-segmented-bg-color: #{$gray-4};
--l-segmented-text-color: #{$text-color-2};
--l-segmented-active-bg-color: #{$gray-5};
// keyboard
--l-keyboard-bg-color: #{$bg-color-elevated};
--l-keyboard-color: #{$text-color-1};
--l-keyboard-key-bg-color: #{$gray-5};
--l-keyboard-key-hover-bg-color: #{$gray-4};
--l-keyboard-icon-bg-color: #{$gray-5};
--l-keyboard-icon-color: #{$text-color-3};
// code-input
--l-code-input-bg-color: #{$gray-4};
--l-code-input-active-bg-color: #{$gray-5};
--l-code-input-info-color: #{$text-color-3};
--l-code-input-dot-color: #{$text-color-1};
--l-code-input-text-color: #{$text-color-1};
--l-code-input-cursor-color: #{$text-color-1};
--l-code-input-active-border-color: #{$gray-6};
// steps
--l-step-description-color: #{$text-color-3};
--l-step-wait-circle-bg-color: #{$gray-4};
--l-step-wait-circle-color: #{$text-color-3};
--l-step-wait-title-color: #{$text-color-3};
--l-step-wait-icon-color: #{$text-color-3};
--l-step-wait-dot-border-color: #{$gray-5};
--l-step-line-color: #{$gray-5};
--l-step-finish-title-color: #{$text-color-1};
--l-step-finish-circle-bg-color: #{$primary-color-2};
--l-step-error-circle-bg-color: #{$error-color-2};
// date-strip
--l-date-strip-bg-color: #{$bg-color-container};
--l-date-strip-color: #{$text-color-1};
--l-date-strip-prefix-color: #{$text-color-3};
--l-date-strip-color: #{$text-color-2};
// slider
--l-slider-rail-color: #{$gray-5};
--l-slider-thumb-border-color: #{$gray-11};
// form
--l-form-item-border-color: #{$border-color-2};
--l-form-bg-color: #{$bg-color-container};
--l-form-item-label-color: #{$text-color-1};
// color-picker
--l-color-picker-color: #{$text-color-1};
--l-color-picker-label-color: #{$text-color-2};
--l-color-picker-field-bg-color: #{$fill-2};
--l-color-picker-divider-color: #{$border-color-2};
// calendar
--l-calendar-bg-color: #{$bg-color-container};
--l-calendar-title-color: #{$text-color-1};
--l-calendar-days-color: #{$text-color-2};
--l-calendar-item-color: #{$text-color-1};
--l-calendar-item-disabled-color: #{$text-color-4};
--l-calendar-item-centre-color: #{$primary-color-2};
--l-calendar-month-mark-color: #{$fill-3};
--l-calendar-switch-mode-icon-color: #{$text-color-2};
--l-calendar-switch-mode-icon-disabled-color: #{$text-color-4};
--l-calendar-header-border-color: #{$border-color-2};
// loading
--l-loading-text-color: #{$text-color-3};
// dialog
--l-dialog-title-color: #{$text-color-1};
--l-dialog-content-color: #{$text-color-2};
--l-dialog-close-color: #{$text-color-3};
// --l-dialog-bg-color: #{$bg-color-container};
--l-dialog-bg-color: #{$gray-3};
--l-dialog-split-color: #{$border-color-2};
// action-sheet
--l-action-sheet-item-disabled-color: #{$text-color-4};
--l-action-sheet-hover-color: #{$gray-3};
--l-action-sheet-border-color: #{$border-color-1};
--l-action-sheet-gap-color: #{$bg-color-page};
--l-action-sheet-color: #{$text-color-1};
--l-action-sheet-description-color: #{$text-color-3};
--l-action-sheet-cancel-color: #{$text-color-1};
--l-action-sheet-cancel-bg-color: #{$bg-color-container};
--l-action-sheet-image-bg-color: #{$fill-3};
--l-action-sheet-title-color: #{$text-color-1};
--l-action-sheet-close-btn-color: #{$text-color-1};
// sorter
--l-sorter-color: #{$text-color-1};
// floating-panel
--l-floating-panel-bg-color: #{$bg-color-elevated};
--l-floating-panel-bar-color: #{$fill-1};
// dropdown-menu
--l-dropdown-menu-color: #{$text-color-1};
--l-dropdown-menu-bg-color: #{$bg-color-container};
--l-dropdown-menu-border-color: #{$border-color-1};
--l-dropdown-menu-disabled-color: #{$border-color-3};
--l-dropdown-menu-arrow-color: #{$gray-6};
--l-dropdown-menu-arrow-active-color: #{$gray-4};
--l-dropdown-item-cell-title-color: #{$text-color-1};
// pull-refresh
--l-pull-refresh-refresher-color: #{$text-color-2};
// circle
--l-circle-trail-color: #{$gray-5};
// collapse
--l-collapse-panel-bg-color: #{$bg-color-container};
--l-collapse-content-text-color: #{$text-color-1};
--l-collapse-right-icon-color: #{$text-color-4};
--l-collapse-border-color: #{$border-color-2};
// count-down
--l-count-down-text-color: #{$text-color-1};
// empty
--l-empty-description-color: #{$text-color-3};
// progress
--l-progress-trail-color: #{$gray-5};
--l-progress-text-color: #{$text-color-2};
// tag
// --l-tag-default-solid-text-color: #000;
--l-tag-text-color: #{$gray-14};
--l-tag-default-color: #{$gray-6};
--l-tag-default-light-color: #{$gray-4};
--l-tag-default-border-color: #{$gray-5};
--l-tag-primary-light-color: #{$primary-color-2};
--l-tag-danger-light-color: #{$error-color-2};
--l-tag-warning-light-color: #{$warning-color-2};
--l-tag-success-light-color: #{$success-color-2};
// amount
--l-amount-color: #{$text-color-1};
// avatar
--l-avatar-bg-color: #{$primary-color-1};
--l-avatar-border-color: #{$gray-2};
// highlight
--l-highlight-normal-color: #{$text-color-1};
// text-ellipsis
--l-text-ellipsis-color: #{$text-color-1};
// read-more
--l-read-more-handle-color: #{$text-color-2};
--l-read-more-read-start: rgba(36, 36, 36, 0.9);
--l-read-more-read-end: rgba(36, 36, 36, 0.3);
--l-read-more-mask-bg-color: linear-gradient(
to top,
rgba(0,0,0,0),
rgba(0,0,0,1),
);
// notice-bar
--l-notice-bar-info-bg-color: #{$info-color-1};
--l-notice-bar-success-bg-color: #{$success-color-1};
--l-notice-bar-warning-bg-color: #{$warning-color-1};
--l-notice-bar-danger-bg-color: #{$error-color-1};
// scrollx
--l-scrollx-track-color: #{$gray-4};
// dateformat
--l-dateformat-color: #{$text-color-1};
// footer
--l-footer-text-color: #{$text-color-3};
--l-footer-link-dividing-line-color: #{$text-color-3};
--l-footer-logo-title-color: #{$text-color-1};
// tree
--l-tree-node-text-color: #{$text-color-1};
--l-tree-arrow-color: #{$gray-6};
// table
--l-table-bg-color: #{$bg-color-container};
--l-table-border-color: #{$border-color-2};
--l-table-td-fixed-bg-color: #{$bg-color-container};
--l-table-tr-fixed-bg-color: #{$bg-color-container};
// sidebar
--l-sidebar-bg-color: #{$gray-3};
--l-sidebar-selected-bg-color: #{$bg-color-container};
--l-sidebar-text-color: #{$text-color-2};
--l-sidebar-disabled-text-color: #{$text-color-4};
// back-top
--l-back-top-bg-color: #{$bg-color-elevated};
--l-back-top-border-color: #{$border-color-2};
--l-back-top-text-color: #{$text-color-2};
// navbar
--l-navbar-bg-color: #{$bg-color-container};
--l-navbar-color: #{$text-color-1};
--l-navbar-capsule-border-color: #{$border-color-2};
// pagination
--l-pagination-bg-color: #{$gray-4};
--l-pagination-disabled-bg-color: #{$gray-3};
--l-pagination-hover-bg-color: #{$gray-5};
--l-pagination-text-color: #{$text-color-2};
--l-pagination-disabled-color: #{$text-color-4};
--l-pagination-simple-text-color: #{$text-color-1};
// indexes
--l-indexes-sidebar-color: #{$text-color-1};
--l-indexes-anchor-color: #{$text-color-1};
--l-indexes-anchor-bg-color: #{$gray-4};
--l-indexes-anchor-active-bg-color: #{$gray-4};
--l-indexes-sidebar-tips-bg-color: #{$primary-color-1};
// typing
--l-typing-bg-color: #{$bg-color-container};
--l-typing-text-color: #{$text-color-1};
// fit-swiper
--l-fit-swiper-indicator-color: #{$gray-5};
}

View File

@@ -0,0 +1,181 @@
@import '../mixins/create.scss';
@import '../color/colorPalette.scss';
@import '../color/colors.scss';
$blue-1: genColor($blue, 1);
$blue-2: genColor($blue, 2);
$blue-3: genColor($blue, 3);
$blue-4: genColor($blue, 4);
$blue-5: genColor($blue, 5);
$blue-6: $blue;
$blue-7: genColor($blue, 7);
$blue-8: genColor($blue, 8);
$blue-9: genColor($blue, 9);
$blue-10: genColor($blue, 10);
$info-color-1: genColor($info-color, 1);
$info-color-2: genColor($info-color, 2);
$info-color-3: genColor($info-color, 3);
$info-color-4: genColor($info-color, 4);
$info-color-5: genColor($info-color, 5);
$info-color-6: $info-color;
$info-color-7: genColor($info-color, 7);
$info-color-8: genColor($info-color, 8);
$info-color-9: genColor($info-color, 9);
$info-color-10: genColor($info-color, 10);
$primary-color-1: create-var('primary-color-1', genColor($primary-color, 1)); // 浅色/白底悬浮
$primary-color-2: create-var('primary-color-2', genColor($primary-color, 2)); // 文字禁用
$primary-color-3: create-var('primary-color-3', genColor($primary-color, 3)); // 一般禁用
$primary-color-4: create-var('primary-color-4', genColor($primary-color, 4)); // 特殊场景 禁用
$primary-color-5: create-var('primary-color-5', genColor($primary-color, 5)); // 悬浮
$primary-color-6: create-var('primary-color-6', $primary-color); // 常规
$primary-color-7: create-var('primary-color-7', genColor($primary-color, 7)); // 点击
$primary-color-8: create-var('primary-color-8', genColor($primary-color, 8)); //
$primary-color-9: create-var('primary-color-9', genColor($primary-color, 9));
$primary-color-10: create-var('primary-color-10', genColor($primary-color, 10));
$error-color-1: create-var('error-color-1', genColor($error-color, 1));
$error-color-2: create-var('error-color-2', genColor($error-color, 2));
$error-color-3: create-var('error-color-3', genColor($error-color, 3));
$error-color-4: create-var('error-color-4', genColor($error-color, 4));
$error-color-5: create-var('error-color-5', genColor($error-color, 5));
$error-color-6: create-var('error-color-6', $error-color);
$error-color-7: create-var('error-color-7', genColor($error-color, 7));
$error-color-8: create-var('error-color-8', genColor($error-color, 8));
$error-color-9: create-var('error-color-9', genColor($error-color, 9));
$error-color-10: create-var('error-color-10', genColor($error-color, 10));
$warning-color-1: create-var('warning-color-1', genColor($warning-color, 1));
$warning-color-2: create-var('warning-color-2', genColor($warning-color, 2));
$warning-color-3: create-var('warning-color-3', genColor($warning-color, 3));
$warning-color-4: create-var('warning-color-4', genColor($warning-color, 4));
$warning-color-5: create-var('warning-color-5', genColor($warning-color, 5));
$warning-color-6: create-var('warning-color-6', $warning-color);
$warning-color-7: create-var('warning-color-7', genColor($warning-color, 7));
$warning-color-8: create-var('warning-color-8', genColor($warning-color, 8));
$warning-color-9: create-var('warning-color-9', genColor($warning-color, 9));
$warning-color-10: create-var('warning-color-10', genColor($warning-color, 10));
$success-color-1: create-var('success-color-1', genColor($success-color, 1)); // 浅色/白底悬浮
$success-color-2: create-var('success-color-2', genColor($success-color, 2)); // 文字禁用
$success-color-3: create-var('success-color-3', genColor($success-color, 3)); // 一般禁用
$success-color-4: create-var('success-color-4', genColor($success-color, 4)); // 特殊场景
$success-color-5: create-var('success-color-5', genColor($success-color, 5)); // 悬浮
$success-color-6: create-var('success-color-6', $success-color); // 常规
$success-color-7: create-var('success-color-7', genColor($success-color, 7)); // 点击
$success-color-8: create-var('success-color-8', genColor($success-color, 8));
$success-color-9: create-var('success-color-9', genColor($success-color, 9));
$success-color-10: create-var('success-color-10', genColor($success-color, 10));
$white: create-var('white', #fff);
$gray-1: create-var('gray-1', #f3f3f3);
$gray-2: create-var('gray-2', #eeeeee);
$gray-3: create-var('gray-3', #e7e7e7);
$gray-4: create-var('gray-4', #dcdcdc);
$gray-5: create-var('gray-5', #c5c5c5);
$gray-6: create-var('gray-6', #a6a6a6);
$gray-7: create-var('gray-7', #8b8b8b);
$gray-8: create-var('gray-8', #777777);
$gray-9: create-var('gray-9', #5e5e5e);
$gray-10: create-var('gray-10', #4b4b4b);
$gray-11: create-var('gray-11', #383838);
$gray-12: create-var('gray-12', #2c2c2c);
$gray-13: create-var('gray-13', #242424);
$gray-14: create-var('gray-14', #181818);
$black: create-var('black', #000);
// $text-color-1: create-var('text-color-1', rgba(0,0,0,0.88)); //primary
// $text-color-2: create-var('text-color-2', rgba(0,0,0,0.65)); //secondary
// $text-color-3: create-var('text-color-3', rgba(0,0,0,0.45)); //placeholder
// $text-color-4: create-var('text-color-4', rgba(0,0,0,0.25)); //disabled
$text-color-1: create-var('text-color-1', #000000E0); // primary (rgba(0,0,0,0.88))
$text-color-2: create-var('text-color-2', #000000A6); // secondary (rgba(0,0,0,0.65))
$text-color-3: create-var('text-color-3', #00000073); // placeholder (rgba(0,0,0,0.45))
$text-color-4: create-var('text-color-4', #00000040); // disabled (rgba(0,0,0,0.25))
// 容器
$bg-color-page: create-var('bg-color-page', $gray-1); // 整体背景色 布局
$bg-color-container: create-var('bg-color-container', #fff); // 一级容器背景 组件
$bg-color-elevated: create-var('bg-color-elevated', #fff); // 二级容器背景 浮层
$bg-color-spotlight: create-var('bg-color-spotlight', rgba(0, 0, 0, 0.85)); // 引起注意的如 Tooltip
$bg-color-mask: create-var('bg-color-mask', rgba(0, 0, 0, 0.45)); // 蒙层
// 填充
// $fill-1: create-var('fill-1', rgba(0, 0, 0, 0.15));
// $fill-2: create-var('fill-2', rgba(0, 0, 0, 0.06));
// $fill-3: create-var('fill-3', rgba(0, 0, 0, 0.04));
// $fill-4: create-var('fill-4', rgba(0, 0, 0, 0.02));
$fill-1: create-var('fill-1', #00000026); // rgba(0,0,0,0.15)
$fill-2: create-var('fill-2', #0000000F); // rgba(0,0,0,0.06)
$fill-3: create-var('fill-3', #0000000A); // rgba(0,0,0,0.04)
$fill-4: create-var('fill-4', #00000005); // rgba(0,0,0,0.02)
// 描边
$border-color-1: create-var('border-color-1', $gray-2); // 浅色
$border-color-2: create-var('border-color-2', $gray-3); // 一般
$border-color-3: create-var('border-color-3', $gray-4); // 深/悬浮
$border-color-4: create-var('border-color-4', $gray-6); // 重/按钮描边
$alpha-disabled: create-var('alpha-disabled', 0.5);
$alpha-pressed: create-var('alpha-pressed', 0.07);
// 投影
/* #ifndef UNI-APP-X && APP */
$shadow-1: create-var(
shadow-1,
0 1px 10px rgba(0, 0, 0, 0.05),
0 4px 5px rgba(0, 0, 0, 0.08),
0 2px 4px -1px rgba(0, 0, 0, 0.12)
);
$shadow-2: create-var(
'shadow-2',
0 1px 10px rgba(0, 0, 0, 0.05),
0 4px 5px rgba(0, 0, 0, 0.08),
0 2px 4px -1px rgba(0, 0, 0, 0.12)
);
$shadow-3: create-var(
shadow-3,
0 6px 30px 5px rgba(0, 0, 0, 0.05),
0 16px 24px 2px rgba(0, 0, 0, 0.04),
0 8px 10px -5px rgba(0, 0, 0, 0.08)
);
/* #endif */
/* #ifdef UNI-APP-X && APP */
$shadow-1: create-var(
shadow-1,
0 1px 10px rgba(0, 0, 0, 0.05)
);
$shadow-2: create-var(
'shadow-2',
0 1px 10px rgba(0, 0, 0, 0.05)
);
$shadow-3: create-var(
shadow-3,
/* #ifdef APP-HARMONY
0 6px 30px 5px $gray-3
/* #endif */
/* #ifndef APP-HARMONY
0 6px 30px 5px rgba(0, 0, 0, 0.05)
/* #endif */
);
/* #endif */
$shadow-4: create-var(shadow-4, 0 2px 8px 0 rgba(0, 0, 0, .06));
// 基础颜色的扩展 用于 聚焦 / 禁用 / 点击 等状态
$primary-color-focus: create-var('primary-color-focus', $primary-color-1);// focus态包括鼠标和键盘
$primary-color-active: create-var('primary-color-active', $primary-color-8);// 点击态
$primary-color-disabled: create-var('primary-color-disabled', $primary-color-3);
$primary-color-light: create-var('primary-color-light', $primary-color-1); // 浅色的选中态
$primary-color-light-active: create-var('primary-color-light-active', $primary-color-2); // 浅色的选中态
$primary-color: create-var(primary-color, $primary-color);
$error-color: create-var(error-color, $error-color);
$warning-color: create-var(warning-color, $warning-color);
$success-color: create-var(success-color, $success-color);
$info-color: create-var(info-color, $info-color);

View File

@@ -0,0 +1,67 @@
import { TinyColor, generate, LGenerateOptions } from '@/uni_modules/lime-color';
import { getAlphaColor, getSolidColor, generateColorPalettes } from './shared'
import { SeedToken } from './interface'
// #ifndef UNI-APP-X
export type UTSJSONObject = Record<string, any>
const UTSJSONObject = Object
// #endif
export function generateNeutralColorPalettes(
bgBaseColor : string | null,
textBaseColor : string | null) : UTSJSONObject {
const colorBgBase = bgBaseColor ?? '#000';
const colorTextBase = textBaseColor ?? '#fff';
return {
'bgColorBase': colorBgBase,
'textColorBase': colorTextBase,
'textColor1': getAlphaColor(colorTextBase, 0.85),
'textColor2': getAlphaColor(colorTextBase, 0.65),
'textColor3': getAlphaColor(colorTextBase, 0.45),
'textColor4': getAlphaColor(colorTextBase, 0.25),
'fill1': getAlphaColor(colorTextBase, 0.18),
'fill2': getAlphaColor(colorTextBase, 0.12),
'fill3': getAlphaColor(colorTextBase, 0.08),
'fill4': getAlphaColor(colorTextBase, 0.04),
'bgColorElevated': getSolidColor(colorBgBase, 12),
'bgColorContainer': getSolidColor(colorBgBase, 8),
'bgColorPage': getSolidColor(colorBgBase, 0),
'bgColorSpotlight': getSolidColor(colorBgBase, 26),
// 'bgColor5': getAlphaColor(colorTextBase, 0.04),
'bgColorMask': getAlphaColor(colorBgBase, 0.45),
'bgColorBlur': getAlphaColor(colorTextBase, 0.04),
'borderColor1': getSolidColor(colorBgBase, 26),
'borderColor2': getSolidColor(colorBgBase, 19)
}
}
export function genColorMapToken(token : SeedToken) : UTSJSONObject {
const colorPrimaryBase = token.primaryColor
const colorSuccessBase = token.successColor
const colorWarningBase = token.warningColor
const colorErrorBase = token.errorColor
const colorInfoBase = token.infoColor
const colorBgBase = token.bgColorBase
const colorTextBase = token.textColorBase
const primaryColors = generateColorPalettes(colorPrimaryBase, 'primaryColor', 'dark');
const successColors = generateColorPalettes(colorSuccessBase, 'successColor', 'dark');
const warningColors = generateColorPalettes(colorWarningBase, 'warningColor', 'dark');
const errorColors = generateColorPalettes(colorErrorBase, 'errorColor', 'dark');
const infoColors = generateColorPalettes(colorInfoBase, 'infoColor', 'dark');
const neutralColors = generateNeutralColorPalettes(colorBgBase, colorTextBase);
return UTSJSONObject.assign(
{},
primaryColors,
successColors,
warningColors,
errorColors,
infoColors,
neutralColors)
}

View File

@@ -0,0 +1,49 @@
// import type { FontMapToken } from '../../interface';
import { getFontSizes } from './genFontSizes';
export const genFontMapToken = (fontSize : number | null) : UTSJSONObject => {
if (fontSize == null) return {}
const fontSizePairs = getFontSizes(fontSize);
const fontSizes = fontSizePairs.map((pair) : number => pair.size);
const lineHeights = fontSizePairs.map((pair) : number => pair.lineHeight);
const fontSizeXS = fontSizes[0];
const fontSizeSM = fontSizes[1];
const fontSizeMD = fontSizes[3];
const fontSizeLG = fontSizes[4];
const lineHeight = lineHeights[2];
const lineHeightSM = lineHeights[1];
const lineHeightMD = lineHeights[3];
const lineHeightLG = lineHeights[4];
return {
fontSize,
fontSizeXS,
fontSizeSM,
fontSizeMD,
fontSizeLG,
fontSizeXL: fontSizes[5],
fontSizeHeading1: fontSizes[7],
fontSizeHeading2: fontSizes[6],
fontSizeHeading3: fontSizes[5],
fontSizeHeading4: fontSizes[4],
fontSizeHeading5: fontSizes[3],
lineHeight,
lineHeightLG,
lineHeightMD,
lineHeightSM,
fontHeight: Math.round(lineHeight * fontSizeMD),
fontHeightLG: Math.round(lineHeightLG * fontSizeLG),
fontHeightSM: Math.round(lineHeightSM * fontSizeSM),
lineHeightHeading1: lineHeights[7],
lineHeightHeading2: lineHeights[6],
lineHeightHeading3: lineHeights[5],
lineHeightHeading4: lineHeights[4],
lineHeightHeading5: lineHeights[3],
};
};

View File

@@ -0,0 +1,30 @@
import { FontSize } from './interface';
export function getLineHeight(fontSize : number) : number {
return (fontSize + 8) / fontSize;
}
// https://zhuanlan.zhihu.com/p/32746810
export function getFontSizes(base : number) : FontSize[] {
const length = 11 // 10
const offset = 2 // 1
// #ifdef APP-ANDROID
const arr = Array.fromNative(new IntArray(length.toInt()));
// #endif
// #ifndef APP-ANDROID
const arr = Array.from({ length });
// #endif
const fontSizes = arr.map((_, index) : number => {
const i = index - offset;
const baseSize = base * Math.pow(Math.E, i / 5);
const intSize = index > 1 ? Math.floor(baseSize) : Math.ceil(baseSize);
// Convert to even
return Math.floor(intSize / 2) * 2;
});
fontSizes[offset] = base;
return fontSizes.map((size) : FontSize => ({
size,
lineHeight: getLineHeight(size),
} as FontSize));
}

View File

@@ -0,0 +1,63 @@
export function genRadius(radiusBase : number):Map<string, number> {
let radiusXL = radiusBase;
let radiusLG = radiusBase;
let radiusMD = radiusBase;
let radiusSM = radiusBase;
let radiusXS = radiusBase;
let radiusOuter = radiusBase;
// radiusSM = radiusBase - 3
// radiusXS = radiusSM - 1
// radiusLG = radiusBase + 3
// radiusXL = radiusLG + 3
// radiusXL
if (radiusBase < 6 && radiusBase >= 5) {
radiusXL = radiusBase + 3;
} else if (radiusBase < 16 && radiusBase >= 6) {
radiusXL = radiusBase + 6;
} else if (radiusBase >= 16) {
radiusXL = 16;
}
// radiusLG
if (radiusBase < 6 && radiusBase >= 5) {
radiusLG = radiusBase + 1;
} else if (radiusBase < 16 && radiusBase >= 6) {
radiusLG = radiusBase + 3;
} else if (radiusBase >= 16) {
radiusLG = 16;
}
// radiusSM
if (radiusBase < 7 && radiusBase >= 5) {
radiusSM = 3;
} else if (radiusBase < 8 && radiusBase >= 7) {
radiusSM = 4;
} else if (radiusBase < 14 && radiusBase >= 8) {
radiusSM = 5;
} else if (radiusBase < 16 && radiusBase >= 14) {
radiusSM = 6;
} else if (radiusBase >= 16) {
radiusSM = 8;
}
// radiusXS
if (radiusBase < 6 && radiusBase >= 2) {
radiusXS = 1;
} else if (radiusBase >= 6) {
radiusXS = 2;
}
return new Map<string, number>([
['borderRadius', radiusBase],
['borderRadiusXS', radiusXS],
['borderRadiusSM', radiusSM],
['borderRadiusMD', radiusMD],
['borderRadiusLG', radiusLG],
['borderRadiusXL', radiusXL],
])
}

View File

@@ -0,0 +1,20 @@
// import {SizeMapToken} from './interface'
/**
* sizeUnit 尺寸变化单位
* sizeStep 尺寸步长
*/
export function genSizeMapToken(sizeUnit : number | null = 4, sizeStep : number | null = 4) : UTSJSONObject {
if (sizeUnit == null || sizeStep == null) return {}
return {
spacerHG: sizeUnit * (sizeStep + 16), // 80
spacerXL: sizeUnit * (sizeStep + 8), // 48
spacerLG: sizeUnit * (sizeStep + 4), // 32
spacerMD: sizeUnit * (sizeStep + 2), // 24
spacerMS: sizeUnit * sizeStep, // 16
spacer: sizeUnit * sizeStep, // 16
spacerSM: sizeUnit * (sizeStep - 1), // 12
spacerXS: sizeUnit * (sizeStep - 2), // 8
spacerTN: sizeUnit * (sizeStep - 3), // 4
};
}

View File

@@ -0,0 +1,184 @@
export type SeedToken = {
// ---------- Color ---------- //
/**
* @nameZH 是否生成深色色板
* @nameEN GenerateDarkPalette
* @desc 是否生成一套完整的深色阶梯色板,以支持深色模式的应用
* @descEN Whether to generate a complete set of dark color palettes to support dark mode applications
*/
useDark ?: boolean;
/**
* @nameZH 品牌主色
* @nameEN Brand Color
* @desc 品牌色是体现产品特性和传播理念最直观的视觉元素之一。在你完成品牌主色的选取之后,我们会自动帮你生成一套完整的色板,并赋予它们有效的设计语义
* @descEN Brand color is one of the most direct visual elements to reflect the characteristics and communication of the product. After you have selected the brand color, we will automatically generate a complete color palette and assign it effective design semantics.
*/
primaryColor ?: string;
/**
* @nameZH 成功色
* @nameEN Success Color
* @desc 用于表示操作成功的 Token 序列,如 Result、Progress 等组件会使用该组梯度变量。
* @descEN Used to represent the token sequence of operation success, such as Result, Progress and other components will use these map tokens.
*/
successColor ?: string;
/**
* @nameZH 警戒色
* @nameEN Warning Color
* @desc 用于表示操作警告的 Token 序列,如 Notification、 Alert等警告类组件或 Input 输入类等组件会使用该组梯度变量。
* @descEN Used to represent the warning map token, such as Notification, Alert, etc. Alert or Control component(like Input) will use these map tokens.
*/
warningColor ?: string;
/**
* @nameZH 错误色
* @nameEN Error Color
* @desc 用于表示操作失败的 Token 序列如失败按钮、错误状态提示Result组件等。
* @descEN Used to represent the visual elements of the operation failure, such as the error Button, error Result component, etc.
*/
errorColor ?: string;
/**
* @nameZH 信息色
* @nameEN Info Color
* @desc 用于表示操作信息的 Token 序列,如 Alert 、Tag、 Progress 等组件都有用到该组梯度变量。
* @descEN Used to represent the operation information of the Token sequence, such as Alert, Tag, Progress, and other components use these map tokens.
*/
infoColor ?: string;
/**
* @nameZH 基础文本色
* @nameEN Seed Text Color
* @desc 用于派生文本色梯度的基础变量v5 中我们添加了一层文本色的派生算法可以产出梯度明确的文本色的梯度变量。但请不要在代码中直接使用该 Seed Token
* @descEN Used to derive the base variable of the text color gradient. In v5, we added a layer of text color derivation algorithm to produce gradient variables of text color gradient. But please do not use this Seed Token directly in the code!
*/
textColorBase ?: string;
/**
* @nameZH 基础背景色
* @nameEN Seed Background Color
* @desc 用于派生背景色梯度的基础变量v5 中我们添加了一层背景色的派生算法可以产出梯度明确的背景色的梯度变量。但请不要在代码中直接使用该 Seed Token
* @descEN Used to derive the base variable of the background color gradient. In v5, we added a layer of background color derivation algorithm to produce map token of background color. But PLEASE DO NOT USE this Seed Token directly in the code!
*/
bgColorBase ?: string;
/**
* @nameZH 超链接颜色
* @nameEN Hyperlink color
* @desc 控制超链接的颜色。
* @descEN Control the color of hyperlink.
*/
linkColor ?: string;
// ---------- Font ---------- //
/**
* @nameZH 字体
* @nameEN Font family for default text
* @desc Lime Ui 的字体家族中优先使用系统默认的界面字体,同时提供了一套利于屏显的备用字体库,来维护在不同平台以及浏览器的显示下,字体始终保持良好的易读性和可读性,体现了友好、稳定和专业的特性。
* @descEN The font family of Lime Ui prioritizes the default interface font of the system, and provides a set of alternative font libraries that are suitable for screen display to maintain the readability and readability of the font under different platforms and browsers, reflecting the friendly, stable and professional characteristics.
*/
fontFamily ?: string;
/**
* @nameZH 代码字体
* @nameEN Font family for code text
* @desc 代码字体,用于 Typography 内的 code、pre 和 kbd 类型的元素
* @descEN Code font, used for code, pre and kbd elements in Typography
*/
fontFamilyCode ?: string;
/**
* @nameZH 默认字号
* @nameEN Default Font Size
* @desc 设计系统中使用最广泛的字体大小,文本梯度也将基于该字号进行派生。
* @descEN The most widely used font size in the design system, from which the text gradient will be derived.
* @default 14
*/
fontSize ?: number;
// ---------- BorderRadius ---------- //
/**
* @nameZH 基础圆角
* @nameEN Base Border Radius
* @descEN Border radius of base components
* @desc 基础组件的圆角大小,例如按钮、输入框、卡片等
*/
borderRadius ?: number;
// ---------- Size ---------- //
/**
* @nameZH 尺寸变化单位
* @nameEN Size Change Unit
* @desc 用于控制组件尺寸的变化单位,在 Lime Ui 中我们的基础单位为 4 ,便于更加细致地控制尺寸梯度
* @descEN The unit of size change, in Lime Ui, our base unit is 4, which is more fine-grained control of the size step
* @default 4
*/
sizeUnit ?: number;
/**
* @nameZH 尺寸步长
* @nameEN Size Base Step
* @desc 用于控制组件尺寸的基础步长,尺寸步长结合尺寸变化单位,就可以派生各种尺寸梯度。通过调整步长即可得到不同的布局模式,例如 V5 紧凑模式下的尺寸步长为 2
* @descEN The base step of size change, the size step combined with the size change unit, can derive various size steps. By adjusting the step, you can get different layout modes, such as the size step of the compact mode of V5 is 2
* @default 4
*/
sizeStep ?: number;
}
export type FontSize = {
size : number
lineHeight : number
}
export type SizeMapToken = {
/**
* @nameZH Huge
* @default 80
*/
sizeHG : number;
/**
* @nameZH XL
* @default 48
*/
sizeXL : number;
/**
* @nameZH LG
* @default 32
*/
sizeLG : number;
/**
* @nameZH MD
* @default 24
*/
sizeMD : number;
/** Same as size by default, but could be larger in compact mode */
sizeMS : number;
/**
* @nameZH 默认
* @desc 默认尺寸
* @default 16
*/
size : number;
/**
* @nameZH SM
* @default 12
*/
sizeSM : number;
/**
* @nameZH XS
* @default 8
*/
sizeXS : number;
/**
* @nameZH Tiny
* @default 4
*/
sizeTN : number;
}
// #ifndef UNI-APP-X
export type VueApp = any
export type UTSJSONObject = Record<string, any>
// #endif

View File

@@ -0,0 +1,66 @@
import { TinyColor, generate } from '@/uni_modules/lime-color';
import { getAlphaColor, getSolidColor, generateColorPalettes } from './shared'
import { SeedToken } from './interface'
// #ifndef UNI-APP-X
export type UTSJSONObject = Record<string, any>
const UTSJSONObject = Object
// #endif
export function generateNeutralColorPalettes(
bgBaseColor : string | null,
textBaseColor : string | null
) : UTSJSONObject {
const colorBgBase = bgBaseColor ?? '#fff';
const colorTextBase = textBaseColor ?? '#000';
return {
'bgColorBase': bgBaseColor,
'textColorBase': textBaseColor,
'textColor1': getAlphaColor(colorTextBase, 0.88),
'textColor2': getAlphaColor(colorTextBase, 0.65),
'textColor3': getAlphaColor(colorTextBase, 0.45),
'textColor4': getAlphaColor(colorTextBase, 0.25),
'fill1': getAlphaColor(colorTextBase, 0.15),
'fill2': getAlphaColor(colorTextBase, 0.06),
'fill3': getAlphaColor(colorTextBase, 0.04),
'fill4': getAlphaColor(colorTextBase, 0.02),
'bgColorPage': getSolidColor(colorBgBase, 4),
'bgColorContainer': getSolidColor(colorBgBase, 0),
'bgColorElevated': getSolidColor(colorBgBase, 0),
'bgColorMask': getAlphaColor(colorTextBase, 0.45),
'bgColorSpotlight': getAlphaColor(colorTextBase, 0.85),
'bgColorBlur': 'transparent',
'borderColor1': getSolidColor(colorBgBase, 15),
'borderColor2': getSolidColor(colorBgBase, 6)
}
};
export function genColorMapToken(token : SeedToken) : UTSJSONObject {
const colorPrimaryBase = token.primaryColor
const colorSuccessBase = token.successColor
const colorWarningBase = token.warningColor
const colorErrorBase = token.errorColor
const colorInfoBase = token.infoColor
const colorBgBase = token.bgColorBase
const colorTextBase = token.textColorBase
const primaryColors = generateColorPalettes(colorPrimaryBase, 'primaryColor');
const successColors = generateColorPalettes(colorSuccessBase, 'successColor');
const warningColors = generateColorPalettes(colorWarningBase, 'warningColor');
const errorColors = generateColorPalettes(colorErrorBase, 'errorColor');
const infoColors = generateColorPalettes(colorInfoBase, 'infoColor');
const neutralColors = generateNeutralColorPalettes(colorBgBase, colorTextBase);
return UTSJSONObject.assign(
{},
primaryColors,
successColors,
warningColors,
errorColors,
infoColors,
neutralColors)
}

View File

@@ -0,0 +1,24 @@
import { TinyColor, generate } from '@/uni_modules/lime-color';
import { LGenerateOptions } from '@/uni_modules/lime-color/utssdk/interface';
export const getAlphaColor = (baseColor : string, alpha : number) : string =>
new TinyColor(baseColor).setAlpha(alpha).toRgbString();
export const getSolidColor = (baseColor : string, brightness : number) : string => {
const instance = new TinyColor(baseColor);
return instance.lighten(brightness).toHexString();
};
export function generateColorPalettes(baseColor : string | null, name : string, theme: string = 'default') : UTSJSONObject {
if (baseColor == null) return {}
const colors = generate(baseColor, { theme } as LGenerateOptions);
const colorPalettes = colors.reduce((prev:UTSJSONObject, color:string, index:number) : UTSJSONObject => {
prev[`${name}${index + 1}`] = color;
return prev
}, {})
// 默认为中间色
colorPalettes[name] = colorPalettes[`${name}${6}`]
return colorPalettes
}

View File

@@ -0,0 +1,126 @@
// @ts-nocheck
// #ifndef UNI-APP-X
import { type ComputedRef, ref, watch, getCurrentScope, onScopeDispose, computed, reactive } from './vue';
// #endif
type ThemeMode = 'light' | 'dark' | 'auto'
const THEME_LOCAL_STORAGE_KEY = 'app-theme-preference'
let limeThemeId = -1
const limeThemeMode = ref<ThemeMode>('auto')
const limeTheme = ref(getTheme())
function setWeb (theme: ThemeMode) {
// #ifdef WEB
const pageHead = document.querySelector(".uni-page-head");
if(!pageHead) return
if (theme == 'dark') {
pageHead.style.backgroundColor = '#181818'
pageHead.style.color = ''
} else {
pageHead.style.backgroundColor = 'rgb(245, 245, 245)'
pageHead.style.color = 'rgb(0, 0, 0)'
}
// #endif
}
export function getTheme(): 'dark' | 'light' {
// #ifndef UNI-APP-X && APP
let mode = uni.getStorageSync(THEME_LOCAL_STORAGE_KEY)
limeThemeMode.value = (mode || 'auto')
const hostTheme = uni.getAppBaseInfo().hostTheme;
return limeThemeMode.value === 'auto' ? (hostTheme || 'light') : limeThemeMode.value
// #endif
// #ifdef UNI-APP-X && APP
let { osTheme, appTheme } = uni.getSystemInfoSync();
return appTheme == "auto" ? osTheme! : appTheme!
// #endif
}
// #ifndef UNI-APP-X
let limeThemeCallBack = (result) => {
limeTheme.value = result.theme
};
// #endif
export function stop() {
// #ifndef UNI-APP-X
uni.offThemeChange(limeThemeCallBack)
// #endif
// #ifdef UNI-APP-X
// #ifndef UNI-APP-X && APP
uni.offHostThemeChange(limeThemeId)
// #endif
// #ifdef UNI-APP-X && APP
uni.offAppThemeChange(limeThemeId)
// #endif
// #endif
}
// 检查系统主题
export const checkSystemTheme = () => {
stop()
limeTheme.value = getTheme()
// #ifndef UNI-APP-X
uni.onThemeChange(limeThemeCallBack);
// #endif
// #ifdef UNI-APP-X
// #ifndef UNI-APP-X && APP
limeThemeId = uni.onHostThemeChange((result) => {
limeTheme.value = result.hostTheme
});
// #endif
// #ifdef UNI-APP-X && APP
limeThemeId = uni.onAppThemeChange((res : AppThemeChangeResult) => {
limeTheme.value = res.appTheme.trim()
})
// #endif
// #endif
}
export const isDarkMode = computed(() => {
return limeTheme.value == "dark";
});
export function setThemeMode(theme: 'dark' | 'light' | 'auto') {
// #ifdef UNI-APP-X && APP
if (limeTheme.value == theme) return;
uni.setAppTheme({
theme,
success: function (result: SetAppThemeSuccessResult) {
console.log("设置appTheme为"+ result.theme +"成功")
limeTheme.value = result.theme
},
fail: function (e : IAppThemeFail) {
console.log("设置appTheme为 auto 失败,原因:", e.errMsg)
}
})
// #endif
// #ifndef UNI-APP-X && APP
limeThemeMode.value = theme
uni.setStorageSync(THEME_LOCAL_STORAGE_KEY, theme)
limeTheme.value = getTheme()
// #endif
// #ifdef WEB
setWeb(limeTheme.value )
// #endif
}
export function toggleTheme() {
setThemeMode(isDarkMode.value ? 'light' : 'dark')
}
// #ifdef WEB
watch(isDarkMode, ()=>{
setWeb(limeTheme.value)
})
// #endif

View File

@@ -0,0 +1,123 @@
// @ts-nocheck
import { isDarkMode } from './useThemeMode'
import type { SeedToken } from './interface';
import { genFontMapToken } from './genFontMapToken';
import { genSizeMapToken } from './genSizeMapToken';
import { genColorMapToken as genLightColorMapToken } from './light';
import { genColorMapToken as genDarkColorMapToken } from './dark';
import { computed, reactive } from 'vue'; // 或相应的响应式系统
// #ifndef UNI-APP-X
export type VueApp = any
export type UTSJSONObject = Record<string, any>
const UTSJSONObject = Object
// #endif
// 内部存储的 tokens
type LimeTokens = {
light : UTSJSONObject
dark : UTSJSONObject
}
// 默认的基础 token
const DEFAULT_SEED_TOKEN : SeedToken = {
primaryColor: '#3283ff'
};
export const themeTokenMaps = reactive<LimeTokens>({
light: {},
dark: {},
})
// 初始化默认 tokens
function initDefaultTokens() {
// Light 主题颜色
const lightColors = {
white: '#fff',
gray1: '#f3f3f3',
gray2: '#eeeeee',
gray3: '#e7e7e7',
gray4: '#dcdcdc',
gray5: '#c5c5c5',
gray6: '#a6a6a6',
gray7: '#8b8b8b',
gray8: '#777777',
gray9: '#5e5e5e',
gray10: '#4b4b4b',
gray11: '#383838',
gray12: '#2c2c2c',
gray13: '#242424',
gray14: '#181818',
black: '#000',
...genLightColorMapToken(DEFAULT_SEED_TOKEN)
};
// Dark 主题颜色
const darkColors = {
white: '#000', // 在暗黑模式下反转
gray1: '#181818',
gray2: '#242424',
gray3: '#2c2c2c',
gray4: '#383838',
gray5: '#4b4b4b',
gray6: '#5e5e5e',
gray7: '#777777',
gray8: '#8b8b8b',
gray9: '#a6a6a6',
gray10: '#c5c5c5',
gray11: '#dcdcdc',
gray12: '#e7e7e7',
gray13: '#eeeeee',
gray14: '#f3f3f3',
black: '#fff', // 在暗黑模式下反转
...genDarkColorMapToken(DEFAULT_SEED_TOKEN)
};
themeTokenMaps.light = UTSJSONObject.assign({}, lightColors, lightColors)
themeTokenMaps.dark = UTSJSONObject.assign({}, darkColors, darkColors)
}
initDefaultTokens();
// 导出的 themeTokens 是计算属性,自动返回当前主题的 tokens
export const themeTokens = computed(():UTSJSONObject => {
return isDarkMode.value ? themeTokenMaps.dark : themeTokenMaps.light;
});
export function useThemeToken(token : SeedToken | null = null) {
if (token != null) {
const lightColors = genLightColorMapToken(token!);
const darkColors = genDarkColorMapToken(token!);
// 只有在提供了相关属性时才重新生成 fonts 和 sizes
const fonts = genFontMapToken(token!.fontSize)
const sizes = genSizeMapToken(token!.sizeUnit, token.sizeStep)
themeTokenMaps.light = UTSJSONObject.assign({}, themeTokenMaps.light, lightColors, fonts, sizes)
themeTokenMaps.dark = UTSJSONObject.assign({}, themeTokenMaps.dark, darkColors, fonts, sizes)
}
}
export function useThemeVars(options : UTSJSONObject | null = null) {
if (options != null) {
const currentTheme = isDarkMode.value ? themeTokenMaps.dark : themeTokenMaps.light;
// #ifdef UNI-APP-X && APP
options.toMap().forEach((value, key) => {
currentTheme.set(key, value);
});
// #endif
// #ifndef UNI-APP-X && APP
Object.entries(options).forEach(([key, value]) => {
// currentTheme.set(key, value);
currentTheme[key] = value
});
// #endif
}
}

View File

@@ -0,0 +1,13 @@
// @ts-nocheck
// #ifndef VUE3
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
export * from '@vue/composition-api'
// #endif
// #ifdef VUE3
export * from 'vue';
// #endif
// #ifdef APP-IOS || APP-ANDROID
export type ComputedRef<T> = ComputedRefImpl<T>
// #endif

View File

@@ -0,0 +1,64 @@
@import './mixins/create.scss';
// 公共前缀
$prefix: l !default;
// Spacer
$spacer: create-var('spacer', 16px) !default; // base
$spacer-tn: create-var('spacer-tn', 4px) !default; // Tiny
$spacer-xs: create-var('spacer-xs', 8px) !default; // Extra Small
$spacer-sm: create-var('spacer-sm', 12px) !default; // Small
$spacer-md: create-var('spacer-md', 24px) !default; // Medium
$spacer-lg: create-var('spacer-lg', 32px) !default; // Large
$spacer-xl: create-var('spacer-xl', 48px) !default; // Extra Large
$spacer-hg: create-var('spacer-hg', 80px) !default; // Huge //Ultra Big
// Font 官方建议字体不跟随页面变化
$font-size: create-var('font-size', 14px) !default;
$font-size-xs: create-var('font-size-xs', 10px) !default;
$font-size-sm: create-var('font-size-sm', 12px) !default;
$font-size-md: create-var('font-size-md', 16px) !default;
$font-size-lg: create-var('font-size-lg',20px) !default;
$font-size-xl: create-var('font-size-xl', 36px) !default;
$font-size-heading-1: create-var('font-size-heading-1', 38px) !default;
$font-size-heading-2: create-var('font-size-heading-2', 30px) !default;
$font-size-heading-3: create-var('font-size-heading-3', 24px) !default;
$font-size-heading-4: create-var('font-size-heading-4', 20px) !default;
$font-size-heading-5: create-var('font-size-heading-5', 16px) !default;
$font-family: create-var('font-family', PingFang SC, Microsoft YaHei, Arial Regular) !default; // 字体-磅数-常规
$font-family-md: create-var('font-family-md', PingFang SC, Microsoft YaHei, Arial Medium) !default; // 字体-磅数-粗体
// 行高
$line-height: create-var('line-height', 1.5714285714285714) !default;
$line-height-sm: create-var('line-height-sm', 1.6666666666666667) !default;
$line-height-md: create-var('line-height-lg', 1.5) !default;
$line-height-lg: create-var('line-height-lg', 1.4) !default;
$line-height-heading-1: create-var('line-height-heading-1', 1.2105263157894737) !default;
$line-height-heading-2: create-var('line-height-heading-2', 1.2666666666666666) !default;
$line-height-heading-3: create-var('line-height-heading-3', 1.3333333333333333) !default;
$line-height-heading-4: create-var('line-height-heading-4', 1.4) !default;
$line-height-heading-5: create-var('line-height-heading-5', 1.5) !default;
// 圆角
$border-radius: create-var('border-radius', 6px) !default;
$border-radius-xs: create-var('border-radius-xs', 2px) !default;
$border-radius-sm: create-var('border-radius-sm', 3px) !default;
$border-radius-md: create-var('border-radius-md', 6px) !default;
$border-radius-lg: create-var('border-radius-lg', 9px) !default;
$border-radius-xl: create-var('border-radius-xl', 12px) !default;
$border-radius-hg: create-var('border-radius-hg', 999px) !default;
// $border-radius-circle: var(--l-border-radius-circle, 50%);
// 动画
$anim-time-fn-easing: create-var('anim-time-fn-easing', cubic-bezier(0.38, 0, 0.24, 1)) !default;
$anim-time-fn-ease-out: create-var('anim-time-fn-ease-out', cubic-bezier(0, 0, 0.15, 1)) !default;
$anim-time-fn-ease-in: create-var('anim-time-fn-ease-in', cubic-bezier(0.82, 0, 1, 0.9)) !default;
$anim-duration-base: create-var('anim-duration-base', 0.2s) !default;
$anim-duration-moderate: create-var('anim-duration-moderate', 0.24s) !default;
$anim-duration-slow: create-var('anim-duration-slow', 0.28s) !default;

View File

@@ -1,8 +1,4 @@
## news
1. 欢迎加入 `QQ` 交流群:`699734691`
![group](https://6874-html-foe72-1259071903.tcb.qcloud.la/assets/group.jpg?sign=558401bccbd56b01debe1bfac6a3b55e&t=1648801090)
2. 示例微信小程序 `富文本插件` 添加 `获取组件包` 功能 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart?id=mp)
![富文本插件](https://6874-html-foe72-1259071903.tcb.qcloud.la/assets/case/%E5%AF%8C%E6%96%87%E6%9C%AC%E6%8F%92%E4%BB%B6.jpg?sign=200e28d06f36049c18d42cdd46270c35&t=1648801110)
## 为减小组件包的大小默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明
## 功能介绍
- 全端支持(含 `v3、NVUE`
@@ -11,10 +7,10 @@
- 支持设置占位图(加载中、出错时、预览时)
- 支持锚点跳转、长按复制等丰富功能
- 支持大部分 *html* 实体
- 丰富的插件(关键词搜索、内容 **编辑** 等)
- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等)
- 效率高、容错性强且轻量化
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
查看 [功能介绍](https://jin-yufeng.github.io/mp-html/#/overview/feature) 了解更多
## 使用方法
- `uni_modules` 方式
@@ -88,13 +84,13 @@
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
查看 [快速开始](https://jin-yufeng.github.io/mp-html/#/overview/quickstart) 了解更多
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210) |
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.github.io/mp-html/#/changelog/changelog#v210) |
| content | String | | 用于渲染的 html 字符串 |
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
| domain | String | | 主域名(用于链接拼接) |
@@ -110,7 +106,7 @@
| tag-style | Object | | 设置标签的默认样式 |
| use-anchor | Boolean | false | 是否使用锚点链接 |
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
查看 [属性](https://jin-yufeng.github.io/mp-html/#/basic/prop) 了解更多
## 组件事件
@@ -121,8 +117,9 @@
| error | 发生渲染错误时 |
| imgtap | 图片被点击时 |
| linktap | 链接被点击时 |
| play | 音视频播放时 |
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
查看 [事件](https://jin-yufeng.github.io/mp-html/#/basic/event) 了解更多
## api
组件实例上提供了一些 `api` 方法可供调用
@@ -135,8 +132,10 @@
| getRect | 获取富文本内容的位置和大小 |
| setContent | 设置富文本内容 |
| imgList | 获取所有图片的数组 |
| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.github.io/mp-html/#/changelog/changelog#v222) |
| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.github.io/mp-html/#/changelog/changelog#v240) |
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
查看 [api](https://jin-yufeng.github.io/mp-html/#/advanced/api) 了解更多
## 插件扩展
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
@@ -144,7 +143,7 @@
| 名称 | 作用 |
|:---:|---|
| audio | 音乐播放器 |
| editable | 富文本 **编辑**[示例项目](https://6874-html-foe72-1259071903.tcb.qcloud.la/editable.zip?sign=cc0017be203fb3dbca62d33a0c15792e&t=1608447445) |
| editable | 富文本 **编辑**[示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip) |
| emoji | 解析 emoji |
| highlight | 代码块高亮显示 |
| markdown | 渲染 markdown |
@@ -152,8 +151,9 @@
| style | 匹配 style 标签中的样式 |
| txv-video | 使用腾讯视频 |
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) |
从插件市场导入的包中 **不含有** 扩展插件,需要使用插件参考以下方法:
从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包
1. 获取完整组件包
```bash
npm install mp-html
@@ -167,19 +167,26 @@
```
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
查看 [插件](https://jin-yufeng.github.io/mp-html/#/advanced/plugin) 了解更多
## 关于 nvue
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
由于渲染方式与其他端不同,有以下限制:
1. 不支持 `lazy-load` 属性
2. 视频不支持全屏播放
3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度
纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
## 问题反馈
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.github.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多
欢迎加入 `QQ` 交流群:
群1已满`699734691`
群2已满`778239129`
群3`960265313`
查看 [问题反馈](https://jin-yufeng.github.io/mp-html/#/question/feedback) 了解更多

View File

@@ -1,3 +1,71 @@
## v2.5.12025-04-20
1. `U` 适配鸿蒙 `APP` [详细](https://github.com/jin-yufeng/mp-html/issues/615)
2. `U` 微信小程序替换废弃 `api` `getSystemInfoSync` [详细](https://github.com/jin-yufeng/mp-html/issues/613)
3. `F` 修复了 `app` 端播放视频可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/617)
4. `F` 修复了 `latex` 插件可能出现 `xxx can be used only in display mode` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/632)
5. `F` 修复了 `uni-app``latex` 公式可能不显示的问题 [#599](https://github.com/jin-yufeng/mp-html/issues/599)、[#627](https://github.com/jin-yufeng/mp-html/issues/627)
## v2.5.02024-04-22
1. `U` `play` 事件增加返回 `src` 等信息 [详细](https://github.com/jin-yufeng/mp-html/issues/526)
2. `U` `preview-img` 属性支持设置为 `all` 开启 `base64` 图片预览 [详细](https://github.com/jin-yufeng/mp-html/issues/536)
3. `U` `editable` 插件增加简易模式(点击文字直接编辑)
4. `U` `latex` 插件支持块级公式 [详细](https://github.com/jin-yufeng/mp-html/issues/582)
5. `F` 修复了表格部分情况下背景丢失的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/587)
6. `F` 修复了部分 `svg` 无法显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/591)
7. `F` 修复了 `h5``app` 端部分情况下样式无法识别的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/518)
8. `F` 修复了 `latex` 插件部分情况下显示不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/580)
9. `F` 修复了 `editable` 插件表格无法删除的问题
10. `F` 修复了 `editable` 插件 `vue3` `h5` 端点击图片报错的问题
11. `F` 修复了 `editable` 插件点击表格没有菜单栏的问题
## v2.4.32024-01-21
1. `A` 增加 [card](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#card) 插件 [详细](https://github.com/jin-yufeng/mp-html/pull/533) by [@whoooami](https://github.com/whoooami)
2. `F` 修复了 `svg` 中包含 `foreignobject` 可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/523)
3. `F` 修复了合并单元格的表格部分情况下显示不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/561)
4. `F` 修复了 `img` 标签设置 `object-fit` 无效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/567)
5. `F` 修复了 `latex` 插件公式会换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/540)
6. `F` 修复了 `editable``audio` 插件共用时点击 `audio` 无法编辑的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/529) by [@whoooami](https://github.com/whoooami)
7. `F` 修复了微信小程序部分情况下图片会报错 `replace of undefined` 的问题
8. `F` 修复了快手小程序图片不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/571)
## v2.4.22023-05-14
1. `A` `editable` 插件支持修改文字颜色 [详细](https://github.com/jin-yufeng/mp-html/issues/254)
2. `F` 修复了 `svg` 中有 `style` 不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/505)
3. `F` 修复了使用旧版编译器可能报错 `Bad attr nodes` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/472)
4. `F` 修复了 `app` 端可能出现无法读取 `lazyLoad` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/513)
5. `F` 修复了 `editable` 插件在点击换图时未拼接 `domain` 的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/497) by [@TwoKe945](https://github.com/TwoKe945)
6. `F` 修复了 `latex` 插件部分情况下不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/515)
7. `F` 修复了 `editable` 插件点击音视频时其他标签框不消失的问题
## v2.4.12022-12-25
1. `F` 修复了没有图片时 `ready` 事件可能不触发的问题
2. `F` 修复了加载过程中可能出现 `Root label not found` 错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/470)
3. `F` 修复了 `audio` 插件退出页面可能会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/457)
4. `F` 修复了 `vue3` 运行到 `app``HBuilder X 3.6.10` 以上报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/480)
5. `F` 修复了 `nvue` 端链接中包含 `%22` 时可能无法显示的问题
6. `F` 修复了 `vue3` 使用 `highlight` 插件可能报错的问题
## v2.4.02022-08-27
1. `A` 增加了 [setPlaybackRate](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#setPlaybackRate) 的 `api`,可以设置音视频的播放速率 [详细](https://github.com/jin-yufeng/mp-html/issues/452)
2. `A` 示例小程序代码开源 [详细](https://github.com/jin-yufeng/mp-html-demo)
3. `U` 优化 `ready` 事件触发时机,未设置懒加载的情况下基本可以准确触发 [详细](https://github.com/jin-yufeng/mp-html/issues/195)
4. `U` `highlight` 插件在编辑状态下不进行高亮处理,便于编辑
5. `F` 修复了 `flex` 布局下图片大小可能不正确的问题
6. `F` 修复了 `selectable` 属性没有设置 `force` 也可能出现渲染异常的问题
7. `F` 修复了表格中的图片大小可能不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/448)
8. `F` 修复了含有合并单元格的表格可能无法设置竖直对齐的问题
9. `F` 修复了 `editable` 插件在 `scroll-view` 中使用时工具条位置可能不正确的问题
10. `F` 修复了 `vue3` 使用 [search](advanced/plugin#search) 插件可能导致错误换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/449)
## v2.3.22022-08-13
1. `A` 增加 [latex](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#latex) 插件,可以渲染数学公式 [详细](https://github.com/jin-yufeng/mp-html/pull/447) by [@Zeng-J](https://github.com/Zeng-J)
2. `U` 优化根节点下有很多标签的长内容渲染速度
3. `U` `highlight` 插件适配 `lang-xxx` 格式
4. `F` 修复了 `table` 标签设置 `border` 属性后可能无法修改边框样式的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/439) by [@zouxingjie](https://github.com/zouxingjie)
5. `F` 修复了 `editable` 插件输入连续空格无效的问题
6. `F` 修复了 `vue3` 图片设置 `inline` 会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/438)
7. `F` 修复了 `vue3` 使用 `table` 可能报错的问题
## v2.3.12022-05-20
1. `U` `app` 端支持使用本地图片
2. `U` 优化了微信小程序 `selectable` 属性在 `ios` 端的处理 [详细](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable)
3. `F` 修复了 `editable` 插件不在顶部时 `tooltip` 位置可能错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/430)
4. `F` 修复了 `vue3` 运行到微信小程序可能报错丢失内容的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/414)
5. `F` 修复了 `vue3` 部分标签可能被错误换行的问题
6. `F` 修复了 `editable` 插件 `app` 端插入视频无法预览的问题
## v2.3.02022-04-01
1. `A` 增加了 `play` 事件,音视频播放时触发,可用于与页面其他音视频进行互斥播放 [详细](basic/event#play)
2. `U` `show-img-menu` 属性支持控制预览时是否长按弹出菜单

View File

@@ -2,7 +2,7 @@
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu]" name="span" />
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
@@ -12,7 +12,7 @@
<script>
/**
* mp-html v2.3.0
* mp-html v2.5.1
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
@@ -128,7 +128,6 @@ export default {
},
beforeDestroy () {
this._hook('onDetached')
clearInterval(this._timer)
},
methods: {
/**
@@ -284,6 +283,28 @@ export default {
// #endif
},
/**
* @description 设置媒体播放速率
* @param {Number} rate 播放速率
*/
setPlaybackRate (rate) {
this.playbackRate = rate
for (let i = (this._videos || []).length; i--;) {
this._videos[i].playbackRate(rate)
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置内容
* @param {String} content html 内容
@@ -308,19 +329,32 @@ export default {
this.$emit('load')
})
// 等待图片加载完毕
let height
clearInterval(this._timer)
this._timer = setInterval(() => {
this.getRect().then(rect => {
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
let height = 0
const callback = rect => {
if (!rect || !rect.height) rect = {}
// 350ms 总高度无变化就触发 ready 事件
if (rect.height === height) {
this.$emit('ready', rect)
clearInterval(this._timer)
} else {
height = rect.height
setTimeout(() => {
this.getRect().then(callback).catch(callback)
}, 350)
}
height = rect.height
}).catch(() => { })
}, 350)
}
this.getRect().then(callback).catch(callback)
} else {
// 未设置懒加载,等待所有图片加载完毕
if (!this.imgList._unloadimgs) {
this.getRect().then(rect => {
this.$emit('ready', rect)
}).catch(() => {
this.$emit('ready', {})
})
}
}
// #endif
},
@@ -340,7 +374,7 @@ export default {
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
@@ -366,7 +400,9 @@ export default {
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => { })
}).catch(() => {
this.$emit('ready', {})
})
break
// 总高度发生变化
case 'onHeightChange':

View File

@@ -3,20 +3,33 @@
<block v-for="(n, i) in childs" v-bind:key="i">
<!-- 图片 -->
<!-- 占位图 -->
<image v-if="n.name==='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
<image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
<!-- 显示图片 -->
<!-- #ifdef H5 || (APP-PLUS && VUE2) -->
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS -->
<image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #ifndef H5 || (APP-PLUS && VUE2) -->
<!-- 表格中的图片使用 rich-text 防止大小不正确 -->
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style||'',src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
<!-- #endif -->
<!-- #ifdef APP-HARMONY -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+ctrl[i]+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS || MP-KUAISHOU -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifdef MP-KUAISHOU -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src" :lazy-load="opts[0]" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap"></image>
<!-- #endif -->
<!-- #ifdef APP-PLUS && VUE3 -->
<image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||''))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- 文本 -->
<!-- #ifndef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
<text v-else-if="n.text" :user-select="n.us" decode>{{n.text}}</text>
<!-- #ifdef MP-WEIXIN -->
<text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
<text v-else-if="n.text" decode>{{n.text}}</text>
<!-- #endif -->
<text v-else-if="n.name==='br'">\n</text>
<!-- 链接 -->
@@ -25,7 +38,7 @@
</view>
<!-- 视频 -->
<!-- #ifdef APP-PLUS -->
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" :data-i="i" @vplay.stop="play" />
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
@@ -57,10 +70,10 @@
<!-- 富文本 -->
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :nodes="[n]" />
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :nodes="[n]" />
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- 继续递归 -->
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
@@ -113,7 +126,10 @@ export default {
},
data () {
return {
ctrl: {}
ctrl: {},
// #ifdef MP-WEIXIN
isiOS: uni.getSystemInfoSync().system.includes('iOS')
// #endif
}
},
props: {
@@ -129,7 +145,7 @@ export default {
},
components: {
// #ifndef H5 && VUE3
// #ifndef ((H5 || APP-PLUS) && VUE3) || APP-HARMONY
node
// #endif
},
@@ -167,14 +183,22 @@ export default {
},
methods:{
// #ifdef MP-WEIXIN
toJSON () { },
toJSON () { return this },
// #endif
/**
* @description 播放视频事件
* @param {Event} e
*/
play (e) {
this.root.$emit('play')
const i = e.currentTarget.dataset.i
const node = this.childs[i]
this.root.$emit('play', {
source: node.name,
attrs: {
...node.attrs,
src: node.src[this.ctrl[i] || 0]
}
})
// #ifndef APP-PLUS
if (this.root.pauseVideo) {
let flag = false
@@ -194,6 +218,9 @@ export default {
// #endif
)
ctx.id = id
if (this.root.playbackRate) {
ctx.playbackRate(this.root.playbackRate)
}
this.root._videos.push(ctx)
}
}
@@ -214,7 +241,14 @@ export default {
// #ifdef H5 || APP-PLUS
node.attrs.src = node.attrs.src || node.attrs['data-src']
// #endif
// #ifndef APP-HARMONY
this.root.$emit('imgtap', node.attrs)
// #endif
// #ifdef APP-HARMONY
this.root.$emit('imgtap', {
...node.attrs
})
// #endif
// 自动预览图片
if (this.root.previewImg) {
uni.previewImage({
@@ -279,6 +313,25 @@ export default {
// 加载完毕,取消加载中占位图
this.$set(this.ctrl, i, 1)
}
this.checkReady()
},
/**
* @description 检查是否所有图片加载完毕
*/
checkReady () {
if (this.root && !this.root.lazyLoad) {
this.root._unloadimgs -= 1
if (!this.root._unloadimgs) {
setTimeout(() => {
this.root.getRect().then(rect => {
this.root.$emit('ready', rect)
}).catch(() => {
this.root.$emit('ready', {})
})
}, 350)
}
}
},
/**
@@ -355,6 +408,7 @@ export default {
if (this.opts[2]) {
this.$set(this.ctrl, i, -1)
}
this.checkReady()
}
if (this.root) {
this.root.$emit('error', {

View File

@@ -71,16 +71,24 @@ const config = {
viewbox: 'viewBox',
attributename: 'attributeName',
repeatcount: 'repeatCount',
repeatdur: 'repeatDur'
repeatdur: 'repeatDur',
foreignobject: 'foreignObject'
}
}
const tagSelector={}
const {
windowWidth,
let windowWidth, system
// #ifdef MP-WEIXIN
if (uni.canIUse('getWindowInfo')) {
windowWidth = uni.getWindowInfo().windowWidth
system = uni.getDeviceInfo().system
} else {
// #endif
const systemInfo = uni.getSystemInfoSync()
windowWidth = systemInfo.windowWidth
// #ifdef MP-WEIXIN
system
// #endif
} = uni.getSystemInfoSync()
system = systemInfo.system
}
// #endif
const blankChar = makeMap(' ,\r,\n,\t,\f')
let idIndex = 0
@@ -138,6 +146,26 @@ function decodeEntity (str, amp) {
return str
}
/**
* @description 合并多个块级标签,加快长内容渲染
* @param {Array} nodes 要合并的标签数组
*/
function mergeNodes (nodes) {
let i = nodes.length - 1
for (let j = i; j >= -1; j--) {
if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {
if (i - j >= 5) {
nodes.splice(j + 1, i - j, {
name: 'div',
attrs: {},
children: nodes.slice(j + 1, i + 1)
})
}
i = j - 1
}
}
}
/**
* @description html 解析器
* @param {Object} vm 组件实例
@@ -146,6 +174,7 @@ function Parser (vm) {
this.options = vm || {}
this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
this.imgList = vm.imgList || []
this.imgList._unloadimgs = 0
this.plugins = vm.plugins || []
this.attrs = Object.create(null)
this.stack = []
@@ -170,6 +199,9 @@ Parser.prototype.parse = function (content) {
while (this.stack.length) {
this.popNode()
}
if (this.nodes.length > 50) {
mergeNodes(this.nodes)
}
return this.nodes
}
@@ -214,9 +246,15 @@ Parser.prototype.getUrl = function (url) {
} else if (domain) {
// 否则补充整个域名
url = domain + url
}
} else if (domain && !url.includes('data:') && !url.includes('://')) {
url = domain + '/' + url
} /* #ifdef APP-PLUS */ else {
url = plus.io.convertLocalFileSystemURL(url)
} /* #endif */
} else if (!url.includes('data:') && !url.includes('://')) {
if (domain) {
url = domain + '/' + url
} /* #ifdef APP-PLUS */ else {
url = plus.io.convertLocalFileSystemURL(url)
} /* #endif */
}
return url
}
@@ -291,6 +329,7 @@ Parser.prototype.onTagName = function (name) {
this.tagName = this.xml ? name : name.toLowerCase()
if (this.tagName === 'svg') {
this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
config.ignoreTags.style = undefined // svg 标签内 style 可用
}
}
@@ -301,6 +340,12 @@ Parser.prototype.onTagName = function (name) {
*/
Parser.prototype.onAttrName = function (name) {
name = this.xml ? name : name.toLowerCase()
// #ifdef (VUE3 && (H5 || APP-PLUS)) || APP-PLUS-NVUE
if (name.includes('?') || name.includes(';')) {
this.attrName = undefined
return
}
// #endif
if (name.substr(0, 5) === 'data-') {
if (name === 'data-src' && !this.attrs.src) {
// data-src 自动转为 src
@@ -427,7 +472,7 @@ Parser.prototype.onOpenTag = function (selfClose) {
node.webp = 'T'
}
// data url 图片如果没有设置 original-src 默认为不可预览的小图片
if (attrs.src.includes('data:') && !attrs['original-src']) {
if (attrs.src.includes('data:') && this.options.previewImg !== 'all' && !attrs['original-src']) {
attrs.ignore = 'T'
}
if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
@@ -435,11 +480,18 @@ Parser.prototype.onOpenTag = function (selfClose) {
const item = this.stack[i]
if (item.name === 'a') {
node.a = item.attrs
break
}
if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {
if (!styleObj.display || styleObj.display.includes('inline')) {
node.t = 'inline-block'
} else {
node.t = styleObj.display
}
styleObj.display = undefined
}
// #ifndef H5 || APP-PLUS
const style = item.attrs.style || ''
if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || !styleObj.width.includes('%'))) {
if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {
styleObj.width = '100% !important'
styleObj.height = ''
for (let j = i + 1; j < this.stack.length; j++) {
@@ -483,6 +535,9 @@ Parser.prototype.onOpenTag = function (selfClose) {
}
// #endif
this.imgList.push(src)
if (!node.t) {
this.imgList._unloadimgs += 1
}
// #ifdef H5 || APP-PLUS
if (this.options.lazyLoad) {
attrs['data-src'] = attrs.src
@@ -511,6 +566,13 @@ Parser.prototype.onOpenTag = function (selfClose) {
if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
node.h = 'T'
}
if (node.w && node.h && styleObj['object-fit']) {
if (styleObj['object-fit'] === 'contain') {
node.m = 'aspectFit'
} else if (styleObj['object-fit'] === 'cover') {
node.m = 'aspectFill'
}
}
} else if (node.name === 'svg') {
siblings.push(node)
this.stack.push(node)
@@ -523,6 +585,11 @@ Parser.prototype.onOpenTag = function (selfClose) {
}
}
attrs.style = attrs.style.substr(1) || undefined
// #ifdef (MP-WEIXIN || MP-QQ) && VUE3
if (!attrs.style) {
delete attrs.style
}
// #endif
} else {
if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
this.pre = node.pre = 1
@@ -556,8 +623,8 @@ Parser.prototype.onCloseTag = function (name) {
siblings.push({
name,
attrs: {
class: tagSelector[name],
style: this.tagStyle[name]
class: tagSelector[name] || '',
style: this.tagStyle[name] || ''
}
})
}
@@ -632,11 +699,19 @@ Parser.prototype.popNode = function () {
return
}
const name = config.svgDict[node.name] || node.name
if (name === 'foreignObject') {
for (const child of (node.children || [])) {
if (child.attrs && !child.attrs.xmlns) {
child.attrs.xmlns = 'http://www.w3.org/1999/xhtml'
break
}
}
}
src += '<' + name
for (const item in node.attrs) {
const val = node.attrs[item]
if (val) {
src += ` ${config.svgDict[item] || item}="${val}"`
src += ` ${config.svgDict[item] || item}="${val.replace(/"/g, '')}"`
}
}
if (!node.children) {
@@ -658,6 +733,7 @@ Parser.prototype.popNode = function () {
node.children = undefined
// #endif
this.xml = false
config.ignoreTags.style = true
return
}
@@ -777,6 +853,8 @@ Parser.prototype.popNode = function () {
let padding = parseFloat(attrs.cellpadding)
let spacing = parseFloat(attrs.cellspacing)
const border = parseFloat(attrs.border)
const bordercolor = styleObj['border-color']
const borderstyle = styleObj['border-style']
if (node.c) {
// padding 和 spacing 默认 2
if (isNaN(padding)) {
@@ -787,11 +865,15 @@ Parser.prototype.popNode = function () {
}
}
if (border) {
attrs.style += ';border:' + border + 'px solid gray'
attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
}
if (node.flag && node.c) {
// 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
styleObj.display = 'grid'
if (styleObj['border-collapse'] === 'collapse') {
styleObj['border-collapse'] = undefined
spacing = 0
}
if (spacing) {
styleObj['grid-gap'] = spacing + 'px'
styleObj.padding = spacing + 'px'
@@ -809,6 +891,23 @@ Parser.prototype.popNode = function () {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].name === 'tr') {
trList.push(nodes[i])
} else if (nodes[i].name === 'colgroup') {
let colI = 1
for (const col of (nodes[i].children || [])) {
if (col.name === 'col') {
const style = col.attrs.style || ''
const start = style.indexOf('width') ? style.indexOf(';width') : 0
// 提取出宽度
if (start !== -1) {
let end = style.indexOf(';', start + 6)
if (end === -1) {
end = style.length
}
width[colI] = style.substring(start ? start + 7 : 6, end)
}
colI += 1
}
}
} else {
traversal(nodes[i].children || [])
}
@@ -825,7 +924,7 @@ Parser.prototype.popNode = function () {
col++
}
let style = td.attrs.style || ''
const start = style.indexOf('width') ? style.indexOf(';width') : 0
let start = style.indexOf('width') ? style.indexOf(';width') : 0
// 提取出 td 的宽度
if (start !== -1) {
let end = style.indexOf(';', start + 6)
@@ -837,7 +936,30 @@ Parser.prototype.popNode = function () {
}
style = style.substr(0, start) + style.substr(end)
}
style += (border ? `;border:${border}px solid gray` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '')
// 设置竖直对齐
style += ';display:flex'
start = style.indexOf('vertical-align')
if (start !== -1) {
const val = style.substr(start + 15, 10)
if (val.includes('middle')) {
style += ';align-items:center'
} else if (val.includes('bottom')) {
style += ';align-items:flex-end'
}
} else {
style += ';align-items:center'
}
// 设置水平对齐
start = style.indexOf('text-align')
if (start !== -1) {
const val = style.substr(start + 11, 10)
if (val.includes('center')) {
style += ';justify-content: center'
} else if (val.includes('right')) {
style += ';justify-content: right'
}
}
style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style
// 处理列合并
if (td.attrs.colspan) {
style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
@@ -890,7 +1012,7 @@ Parser.prototype.popNode = function () {
const td = nodes[i]
if (td.name === 'th' || td.name === 'td') {
if (border) {
td.attrs.style = `border:${border}px solid gray;${td.attrs.style || ''}`
td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
}
if (padding) {
td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
@@ -912,11 +1034,26 @@ Parser.prototype.popNode = function () {
node.children = [table]
attrs = table.attrs
}
} else if ((node.name === 'tbody' || node.name === 'tr') && node.flag && node.c) {
node.flag = undefined;
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].name === 'td') {
// 颜色样式设置给单元格避免丢失
for (const style of ['color', 'background', 'background-color']) {
if (styleObj[style]) {
nodes[i].attrs.style = style + ':' + styleObj[style] + ';' + (nodes[i].attrs.style || '')
}
}
} else {
traversal(nodes[i].children || [])
}
}
})(children)
} else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
for (let i = this.stack.length; i--;) {
if (this.stack[i].name === 'table') {
if (this.stack[i].name === 'table' || this.stack[i].name === 'tbody' || this.stack[i].name === 'tr') {
this.stack[i].flag = 1 // 指示含有合并单元格
break
}
}
} else if (node.name === 'ruby') {
@@ -941,18 +1078,20 @@ Parser.prototype.popNode = function () {
}
}
} else if (node.c) {
node.c = 2
for (let i = node.children.length; i--;) {
const child = node.children[i]
// #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
if (child.name && (config.inlineTags[child.name] || (child.attrs.style || '').includes('inline'))) {
child.c = 1
(function traversal (node) {
node.c = 2
for (let i = node.children.length; i--;) {
const child = node.children[i]
// #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {
traversal(child)
}
// #endif
if (!child.c || child.name === 'table') {
node.c = 1
}
}
// #endif
if (!child.c || child.name === 'table') {
node.c = 1
}
}
})(node)
}
if ((styleObj.display || '').includes('flex') && !node.c) {
@@ -977,22 +1116,8 @@ Parser.prototype.popNode = function () {
node.f = ';max-width:100%'
}
// 优化长内容加载速度
if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
let i = children.length - 1
for (let j = i; j >= -1; j--) {
// 合并多个块级标签
if (j === -1 || children[j].c || !children[j].name || (children[j].name !== 'div' && children[j].name !== 'p' && children[j].name[0] !== 'h') || (children[j].attrs.style || '').includes('inline')) {
if (i - j >= 5) {
children.splice(j + 1, i - j, {
name: 'div',
attrs: {},
children: node.children.slice(j + 1, i + 1)
})
}
i = j - 1
}
}
mergeNodes(children)
}
// #endif
@@ -1012,8 +1137,10 @@ Parser.prototype.popNode = function () {
}
attrs.style = attrs.style.substr(1) || undefined
// #ifdef (MP-WEIXIN || MP-QQ) && VUE3
if (!attrs.style) {
delete attrs.style
for (const key in attrs) {
if (!attrs[key]) {
delete attrs[key]
}
}
// #endif
}
@@ -1040,7 +1167,15 @@ Parser.prototype.onText = function (text) {
}
}
// 去除含有换行符的空串
if (trim === ' ' && flag) return
if (trim === ' ') {
if (flag) return
// #ifdef VUE3
else {
const parent = this.stack[this.stack.length - 1]
if (parent && parent.name[0] === 't') return
}
// #endif
}
text = trim
}
const node = Object.create(null)
@@ -1051,9 +1186,8 @@ Parser.prototype.onText = function (text) {
node.text = decodeEntity(text)
if (this.hook(node)) {
// #ifdef MP-WEIXIN
if (this.options.selectable === 'force' && system.includes('iOS')) {
if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
this.expose()
node.us = 'T'
}
// #endif
const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes

View File

@@ -1,7 +1,7 @@
{
"id": "mp-html",
"displayName": "mp-html 富文本组件【全端支持,可编辑】",
"version": "v2.3.0",
"displayName": "mp-html 富文本组件【全端支持,支持编辑、latex等扩展】",
"version": "v2.5.1",
"description": "一个强大的富文本组件,高效轻量,功能丰富",
"keywords": [
"富文本",
@@ -12,10 +12,6 @@
],
"repository": "https://github.com/jin-yufeng/mp-html",
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -32,18 +28,22 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mp-html"
"npmurl": "https://www.npmjs.com/package/mp-html",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
"app-nvue": "y",
"app-harmony": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "y",

View File

@@ -1 +1 @@
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){o[1]&&(this.src=o[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(r,i,s){for(var c=0;c<r.length;c++)!function(c){var u=r[c],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&amp;/g,"&"));else{var d=u.name;"svg"===d&&(s="http://www.w3.org/2000/svg"),"html"!==d&&"body"!==d||(d="div"),l=s?document.createElementNS(s,d):document.createElement(d);for(var g in u.attrs)l.setAttribute(g,u.attrs[g]);if(u.children&&n(u.children,l,s),"img"===d){if(!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),o[2]){var p=new Image;p.src=l.src,l.src=o[2],p.onload=function(){l.src=this.src},p.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===d)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===d||"audio"===d)a.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),l.onplay=function(){if(uni.postMessage({data:{action:"onPlay"}}),o[3])for(var t=0;t<a.length;t++)a[t]!==this&&a[t].pause()},l.onerror=function(){uni.postMessage({data:{action:"onError",source:d,attrs:t(this)}})};else if("table"===d&&o[4]&&!l.style.cssText.includes("inline")){var f=document.createElement("div");f.style.overflow="auto",f.appendChild(l),l=f}else"svg"===d&&(s=void 0)}i.appendChild(l)}(c)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var o,a=[];window.setContent=function(t,e,r){var i=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(i.style.userSelect="none"),r||(i.innerHTML="",a=[]),o=e;var s=document.createDocumentFragment();n(t,s),i.appendChild(s);var c=i.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),clearInterval(window.timer);var u=!1;window.timer=setInterval(function(){i.scrollHeight!==c?(c=i.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}})):u||(u=!0,uni.postMessage({data:{action:"onReady"}}))},350)},window.onunload=function(){clearInterval(window.timer)};
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){a[1]&&(this.src=a[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(){window.unloadimgs-=1,0===window.unloadimgs&&uni.postMessage({data:{action:"onReady"}})}function o(r,s,c){for(var d=0;d<r.length;d++)!function(d){var u=r[d],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&amp;/g,"&"));else{var g=u.name;"svg"===g&&(c="http://www.w3.org/2000/svg"),"html"!==g&&"body"!==g||(g="div"),l=c?document.createElementNS(c,g):document.createElement(g);for(var p in u.attrs)l.setAttribute(p,u.attrs[p]);if(u.children&&o(u.children,l,c),"img"===g){if(window.unloadimgs+=1,l.onload=n,l.onerror=n,!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),a[2]){var h=new Image;h.src=l.src,l.src=a[2],h.onload=function(){l.src=this.src},h.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===g)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===g||"audio"===g)i.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),l.onplay=function(){if(uni.postMessage({data:{action:"onPlay"}}),a[3])for(var t=0;t<i.length;t++)i[t]!==this&&i[t].pause()},l.onerror=function(){uni.postMessage({data:{action:"onError",source:g,attrs:t(this)}})};else if("table"===g&&a[4]&&!l.style.cssText.includes("inline")){var f=document.createElement("div");f.style.overflow="auto",f.appendChild(l),l=f}else"svg"===g&&(c=void 0)}s.appendChild(l)}(d)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var a,i=[];window.setContent=function(t,e,n){var r=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(r.style.userSelect="none"),n||(r.innerHTML="",i=[]),a=e,window.unloadimgs=0;var s=document.createDocumentFragment();o(t,s),r.appendChild(s);var c=r.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),window.unloadimgs||uni.postMessage({data:{action:"onReady",height:c}}),clearInterval(window.timer),window.timer=setInterval(function(){r.scrollHeight!==c&&(c=r.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}}))},350)},window.onunload=function(){clearInterval(window.timer)};

View File

@@ -1,3 +1,5 @@
## 1.2.22023-01-28
- 修复 运行/打包 控制台警告问题
## 1.2.12022-09-05
- 修复 当 text 超过 max-num 时badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
## 1.2.02021-11-19

View File

@@ -21,7 +21,7 @@
* @value error 红色
* @property {String} inverted = [true|false] 是否无需背景颜色
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
* @value rightTop 右上
* @value rightBottom 右下
* @value leftTop 左上
@@ -192,11 +192,12 @@
display: flex;
overflow: hidden;
box-sizing: border-box;
font-feature-settings: "tnum";
min-width: 20px;
/* #endif */
justify-content: center;
flex-direction: row;
height: 20px;
min-width: 20px;
padding: 0 4px;
line-height: 18px;
color: #fff;
@@ -206,7 +207,6 @@
border: 1px solid #fff;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-feature-settings: "tnum";
font-size: $bage-size;
/* #ifdef H5 */
z-index: 999;

View File

@@ -1,7 +1,7 @@
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.2.1",
"version": "1.2.2",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",

View File

@@ -9,19 +9,17 @@
"uni-ui",
"面包屑导航",
"面包屑"
],
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
"HBuilderX": "^3.1.0",
"uni-app": "^3.1.0",
"uni-app-x": "^3.1.0"
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -38,51 +36,68 @@
"data": "无",
"permissions": "无"
},
"npmurl": ""
"npmurl": "",
"type": "component-vue",
"darkmode": "-",
"i18n": "-",
"widescreen": "-"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"tcb": "",
"aliyun": ""
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
}
}
}

View File

@@ -1,20 +1,30 @@
## 1.4.122024-09-21
- 修复 calendar在选择日期范围后重新选择日期需要点两次的Bug
## 1.4.112024-01-10
- 修复 回到今天时,月份显示不一致问题
## 1.4.102023-04-10
- 修复 某些情况 monthSwitch 未触发的Bug
## 1.4.92023-02-02
- 修复 某些情况切换月份错误的Bug
## 1.4.82023-01-30
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
## 1.4.72022-09-16
- 可以使用 uni-scss 控制主题色
- 优化 支持使用 uni-scss 控制主题色
## 1.4.62022-09-08
- fix: 表头年月切换导致改变当前日期为选择月1号且未触发change事件
- 修复 表头年月切换导致改变当前日期为选择月1号且未触发change事件的Bug
## 1.4.52022-02-25
- 修复 条件编译 nvue 不支持的 css 样式
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.42022-02-25
- 修复 条件编译 nvue 不支持的 css 样式
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.32021-09-22
- 修复 startDate、 endDate 属性失效的 bug
- 修复 startDate、 endDate 属性失效的Bug
## 1.4.22021-08-24
- 新增 支持国际化
## 1.4.12021-08-05
- 修复 弹出层被 tabbar 遮盖 bug
- 修复 弹出层被 tabbar 遮盖的Bug
## 1.4.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.3.162021-05-12
- 新增 组件示例地址
## 1.3.152021-02-04
- 调整为uni_modules目录规范
- 调整为uni_modules目录规范

View File

@@ -351,10 +351,8 @@ var calendar = {
s = '\u521d\u5341'; break
case 20:
s = '\u4e8c\u5341'; break
break
case 30:
s = '\u4e09\u5341'; break
break
default :
s = this.nStr2[Math.floor(d / 10)]
s += this.nStr1[d % 10]

View File

@@ -51,11 +51,10 @@
</template>
<script>
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const { t } = initVueI18n(i18nMessages)
export default {
emits:['change'],
props: {

View File

@@ -20,7 +20,7 @@
<view class="uni-calendar__header-btn-box" @click.stop="next">
<view class="uni-calendar__header-btn uni-calendar--right"></view>
</view>
<text class="uni-calendar__backtoday" @click="backtoday">{{todayText}}</text>
<text class="uni-calendar__backtoday" @click="backToday">{{todayText}}</text>
</view>
<view class="uni-calendar__box">
@@ -62,12 +62,12 @@
<script>
import Calendar from './util.js';
import calendarItem from './uni-calendar-item.vue'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
import CalendarItem from './uni-calendar-item.vue'
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const { t } = initVueI18n(i18nMessages)
/**
* Calendar 日历
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
@@ -90,7 +90,7 @@
*/
export default {
components: {
calendarItem
CalendarItem
},
emits:['close','confirm','change','monthSwitch'],
props: {
@@ -199,26 +199,26 @@
}
},
created() {
// 获取日历方法实例
this.cale = new Calendar({
// date: new Date(),
selected: this.selected,
startDate: this.startDate,
endDate: this.endDate,
range: this.range,
})
// 选中某一天
// this.cale.setDate(this.date)
this.init(this.date)
// this.setDay
},
methods: {
// 取消穿透
clean() {},
bindDateChange(e) {
const value = e.detail.value + '-1'
console.log(this.cale.getDate(value));
this.setDate(value)
const { year,month } = this.cale.getDate(value)
this.$emit('monthSwitch', {
year,
month
})
},
/**
* 初始化日期显示
@@ -323,11 +323,17 @@
/**
* 回到今天
*/
backtoday() {
console.log(this.cale.getDate(new Date()).fullDate);
let date = this.cale.getDate(new Date()).fullDate
// this.cale.setDate(date)
this.init(date)
backToday() {
const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
const date = this.cale.getDate(new Date())
const todayYearMonth = `${date.year}-${date.month}`
this.init(date.fullDate)
if(nowYearMonth !== todayYearMonth) {
this.monthSwitch()
}
this.change()
},
/**
@@ -446,7 +452,6 @@
.uni-calendar--fixed-width {
width: 50px;
// padding: 0 15px;
}
.uni-calendar__backtoday {

View File

@@ -76,10 +76,20 @@ class Calendar {
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31) {
if (dd.getDate() === 31 && AddDayCount>0) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
const preMonth = dd.getMonth()
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
const nextMonth = dd.getMonth()
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
}
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
}
}
break
case 'year':
@@ -286,7 +296,7 @@ class Calendar {
if (!this.range) return
if (before && after) {
this.multipleStatus.before = ''
this.multipleStatus.before = fullDate
this.multipleStatus.after = ''
this.multipleStatus.data = []
} else {

View File

@@ -1,7 +1,7 @@
{
"id": "uni-calendar",
"displayName": "uni-calendar 日历",
"version": "1.4.7",
"version": "1.4.12",
"description": "日历组件",
"keywords": [
"uni-ui",
@@ -44,7 +44,8 @@
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
@@ -82,4 +83,4 @@
}
}
}
}
}

View File

@@ -77,7 +77,7 @@ export default {
### Calendar Props
| 属性名 | 类型 | 默认值| 说明 |
| | |
| - | - | - | - |
| date | String |- | 自定义当前时间,默认为今天 |
| lunar | Boolean | false | 显示农历 |
| startDate | String |- | 日期选择范围-开始日期 |
@@ -91,7 +91,7 @@ export default {
### Calendar Events
| 事件名 | 说明 |返回值|
| | | |
| - | - | - |
| open | 弹出日历组件,`insert :false` 时生效|- |
@@ -100,4 +100,4 @@ export default {
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)

View File

@@ -1,3 +1,15 @@
## 1.4.82025-09-16
- 修复 modelValue 修改会两次触发 change 事件的 Bug
## 1.4.72025-09-11
- 修复 modelValue 修改不会触发更新的 Bug
## 1.4.62025-09-02
- 修复 modelValue 修改不会触发 change 事件的 Bug
## 1.4.52025-09-02
- 修复 非手风琴模式 不能设置 modeValue 为 [] 的 Bug (question/205130)
## 1.4.42024-03-20
- 修复 titleBorder类型修正
## 1.4.32022-01-25
- 修复 初始化的时候 open 属性失效的bug
## 1.4.22022-01-21

View File

@@ -40,7 +40,7 @@
* @property {String} name 唯一标志符
* @property {Boolean} open = [true|false] 是否展开组件
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
* @property {Boolean} border = [true|false] 是否显示分隔线
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
* @property {Boolean} disabled = [true|false] 是否展开面板
* @property {Boolean} showAnimation = [true|false] 开启动画
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头

View File

@@ -51,8 +51,11 @@
}
},
watch: {
dataValue(val) {
this.setOpen(val)
dataValue: {
handler(newVal) {
this.setOpen(newVal)
},
deep: true
}
},
created() {
@@ -66,9 +69,9 @@
},
methods: {
setOpen(val) {
let str = typeof val === 'string'
let arr = Array.isArray(val)
this.childrens.forEach((vm, index) => {
const str = typeof val === 'string'
const arr = Array.isArray(val)
this.childrens.forEach((vm) => {
if (str) {
if (val === vm.nameSync) {
if (!this.accordion) {
@@ -79,15 +82,12 @@
}
}
if (arr) {
val.forEach(v => {
if (v === vm.nameSync) {
if (this.accordion) {
console.warn('accordion 属性为 true ,v-model 类型应该为 string')
return
}
vm.isOpen = true
}
})
const isOpen = val.findIndex(v => v === vm.nameSync) !== -1
if (this.accordion && isOpen) {
console.warn('accordion 属性为 true ,v-model 类型应该为 string')
return
}
vm.isOpen = isOpen
}
})
this.emit(val)

View File

@@ -1,7 +1,7 @@
{
"id": "uni-collapse",
"displayName": "uni-collapse 折叠面板",
"version": "1.4.3",
"version": "1.4.8",
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
"keywords": [
"uni-ui",
@@ -11,16 +11,14 @@
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
"HBuilderX": "",
"uni-app": "^4.07",
"uni-app-x": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -37,53 +35,72 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "√",
"lark": "-"
},
"quickapp": {
"huawei": "√",
"union": "√"
}
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
}
}
}

View File

@@ -1,3 +1,5 @@
## 1.0.22024-09-21
- 新增 clearAble属性
## 1.0.12021-11-23
- 优化 label、label-width 属性
## 1.0.02021-11-19

View File

@@ -4,10 +4,11 @@
<text>{{label}}</text>
</view>
<view class="uni-combox__input-box">
<input class="uni-combox__input" type="text" :placeholder="placeholder"
placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
@blur="onBlur" />
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
<input class="uni-combox__input" type="text" :placeholder="placeholder" placeholder-class="uni-combox__input-plac"
v-model="inputVal" @input="onInput" @focus="onFocus" @blur="onBlur" />
<uni-icons v-if="!inputVal || !clearAble" :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
</uni-icons>
<uni-icons v-if="inputVal && clearAble" type="clear" size="24" color="#999" @click="clean">
</uni-icons>
</view>
<view class="uni-combox__selector" v-if="showSelector">
@@ -16,8 +17,8 @@
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
<text>{{emptyTips}}</text>
</view>
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
@click="onSelectorClick(index)">
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
@click="onSelectorClick(index)">
<text>{{item}}</text>
</view>
</scroll-view>
@@ -41,6 +42,10 @@
name: 'uniCombox',
emits: ['input', 'update:modelValue'],
props: {
clearAble: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: true
@@ -143,12 +148,16 @@
this.$emit('input', this.inputVal)
this.$emit('update:modelValue', this.inputVal)
})
},
clean() {
this.inputVal = ''
this.onInput()
}
}
}
</script>
<style lang="scss" >
<style lang="scss" scoped>
.uni-combox {
font-size: 14px;
border: 1px solid #DCDFE6;

View File

@@ -1,7 +1,7 @@
{
"id": "uni-combox",
"displayName": "uni-combox 组合框",
"version": "1.0.1",
"version": "1.0.2",
"description": "可以选择也可以输入的表单项 ",
"keywords": [
"uni-ui",
@@ -17,11 +17,7 @@
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
@@ -38,7 +34,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
@@ -49,7 +46,8 @@
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
@@ -87,4 +85,4 @@
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
## 0.0.32022-11-11
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
## 0.0.22021-04-16
- 修改插件package信息
## 0.0.12021-03-15
- 初始化项目

View File

@@ -0,0 +1,81 @@
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.3",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
"配置中心"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"directories": {
"example": "../../../scripts/dist"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
# 为什么使用uni-config-center
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ ├─index.js
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b // plugin-b对应的目录
├─index.js
└─config.json // plugin-b对应的配置文件
```
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
uni-config-center就是用了统一管理这些配置文件的使用uni-config-center后的目录结构如下
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ └─index.js
├─plugin-b // plugin-b对应的目录
│ └─index.js
└─uni-config-center
├─index.js // config-center入口文件
├─plugin-a
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b
└─config.json // plugin-b对应的配置文件
```
使用uni-config-center后的优势
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
- 支持对config.json设置schema插件使用者在HBuilderX内编写config.json文件时会有更好的提示后续HBuilderX会提供支持
# 用法
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖请参考[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
```js
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id', // 插件id
defaultConfig: { // 默认配置
tokenExpiresIn: 7200,
tokenExpiresThreshold: 600,
},
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
// defaudltConfig 默认配置
// userConfig 用户配置
return Object.assign(defaultConfig, userConfig)
}
})
// 以如下配置为例
// {
// "tokenExpiresIn": 7200,
// "passwordErrorLimit": 6,
// "bindTokenToDevice": false,
// "passwordErrorRetryTime": 3600,
// "app-plus": {
// "tokenExpiresIn": 2592000
// },
// "service": {
// "sms": {
// "codeExpiresIn": 300
// }
// }
// }
// 获取配置
uniIdConfig.config() // 获取全部配置注意uni-config-center内不存在对应插件目录时会返回空对象
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置返回7200
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置返回300
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置如果不存在则取传入的默认值返回600
// 获取文件绝对路径
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
// 引用文件require
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined文件内有其他错误导致require失败时会抛出错误。
// 判断是否包含某文件
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件true: 文件存在false: 文件不存在
```

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