This commit is contained in:
Ankkaya 2024-05-22 15:42:13 +08:00
commit 70ad660268
363 changed files with 47465 additions and 0 deletions

20
.env Normal file
View File

@ -0,0 +1,20 @@
# 版本号
MALL_VERSION = v1.0.0
# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development
MALL_BASE_URL = http://api-dashboard.yudao.iocoder.cn
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development
MALL_DEV_BASE_URL = http://mall-backend-dev.jiandyb.cn:7001
# 后端接口前缀(一般不建议调整)
MALL_API_PATH = /app-api
# 开发环境运行端口
MALL_DEV_PORT = 3000
# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
MALL_STATIC_URL = https://file.sheepjs.com
# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
MALL_MPLIVE_ON = 0

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
unpackage/*
node_modules/*
.idea/*
deploy.sh
.hbuilderx/
.vscode/
**/.DS_Store
yarn.lock
package-lock.json
*.keystore
pnpm-lock.yaml

22
App.vue Normal file
View File

@ -0,0 +1,22 @@
<script setup>
import { onLaunch, onShow, onError } from '@dcloudio/uni-app'
import { peachInit } from './peach'
onLaunch(() => {
// 使
peachInit()
})
onError((err) => {
console.log('AppOnError:', err)
})
onShow((options) => {
console.log('AppOnShow:', options)
})
</script>
<style lang="scss">
/*每个页面公共css */
@import '@/peach/scss/index.scss';
</style>

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

12
main.js Normal file
View File

@ -0,0 +1,12 @@
import App from './App'
import { setupPinia } from './peach/store'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
setupPinia(app)
return {
app,
}
}

72
manifest.json Normal file
View File

@ -0,0 +1,72 @@
{
"name": "mall-app-t",
"appid": "__UNI__B201544",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App */
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
/* */
"modules": {},
/* */
"distribute": {
/* android */
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios": {},
/* SDK */
"sdkConfigs": {}
}
},
/* */
"quickapp": {},
/* */
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3"
}

12
package.json Normal file
View File

@ -0,0 +1,12 @@
{
"dependencies": {
"dayjs": "^1.11.11",
"lodash": "^4.17.21",
"luch-request": "^3.1.1",
"pinia": "^2.1.7",
"pinia-plugin-persist-uni": "^1.3.1"
},
"devDependencies": {
"vconsole": "^3.15.1"
}
}

48
pages.json Normal file
View File

@ -0,0 +1,48 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^p-(.*)": "@/peach/components/p-$1/p-$1.vue",
"^pb-(.*)": "@/peach/ui/pb-$1/pb-$1.vue"
}
},
"pages": [
//pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"aliasPath": "/",
"style": {
"navigationBarTitleText": "uni-app"
},
"meta": {
"auth": false
}
},
{
"path": "pages/index/category",
"style": {
"navigationBarTitleText": "uni-app"
},
"meta": {
"auth": false
}
},
{
"path": "pages/index/icons",
"style": {
"navigationBarTitleText": "uni-app"
},
"meta": {
"auth": false
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"navigationStyle": "custom"
},
"uniIdRouter": {}
}

20
pages/index/category.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<pb-layout
title="分类"
navbar="normal"
tabbar="/pages/index/category"
:bgStyle="bgStyle"
opacityBgUi="bg-white"
color="black"
>
<p-empty icon="/static/soldout-empty.png" text="暂无内容" />
</pb-layout>
</template>
<script setup>
const bgStyle = {
backgroundImage: '',
backgroundColor: '',
description: '',
}
</script>

13
pages/index/icons.vue Normal file
View File

@ -0,0 +1,13 @@
<template>
<pb-layout title="图标" navbar="normal" tabbar="/pages/index/icons" opacityBgUi="bg-white" color="black">
<view>
<text v-for="item in icons" :class="[item, 'icon-x']"></text>
</view>
</pb-layout>
</template>
<script setup>
import { ref } from 'vue'
console.log(COLORICONS)
const icons = ref(COLORICONS)
</script>

20
pages/index/index.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<pb-layout
title="首页"
navbar="normal"
tabbar="/pages/index/index"
:bgStyle="bgStyle"
opacityBgUi="bg-white"
color="black"
>
</pb-layout>
</template>
<script setup>
const bgStyle = {
backgroundImage:
'http://101.43.181.163:9001/mall-backend/24dd085ca57fbfaa27c3e16788237b1d7a95c854c01b5e3e219aad6709bbb748.jpg',
backgroundColor: '',
description: '',
}
</script>

48
peach/api/member/auth.js Normal file
View File

@ -0,0 +1,48 @@
import request from '@/peach/request'
const AuthUtil = {
login: (data) => {
return request({
url: '/member/auth/login',
method: 'POST',
data,
custom: {
showSuccess: true,
loadingMsg: '登陆中',
successMsg: '登陆成功',
},
})
},
// 发送手机验证码
sendSmsCode: (mobile, scene) => {
return request({
url: '/member/auth/send-sms-code',
method: 'POST',
data: {
mobile,
scene,
},
custom: {
loadingMsg: '发送中',
showSuccess: true,
successMsg: '发送成功',
},
})
},
// 刷新令牌
refreshToken: (refreshToken) => {
return request({
url: '/member/auth/refresh-token',
method: 'POST',
params: {
refreshToken,
},
custom: {
loading: false, // 不用加载中
showError: false, // 不展示错误提示
},
})
},
}
export default AuthUtil

View File

@ -0,0 +1,87 @@
<template>
<view class="ss-flex-col ss-col-center ss-row-center empty-box" :style="[{ paddingTop: paddingTop + 'rpx' }]">
<view class=""><image class="empty-icon" :src="icon" mode="widthFix"></image></view>
<view class="empty-text ss-m-t-28 ss-m-b-40">
<text v-if="text !== ''">{{ text }}</text>
</view>
<button class="ss-reset-button empty-btn" v-if="showAction" @tap="clickAction">
{{ actionText }}
</button>
</view>
</template>
<script setup>
import peach from '@/peach'
const props = defineProps({
//
icon: {
type: String,
default: '',
},
//
text: {
type: String,
default: '',
},
// button
showAction: {
type: Boolean,
default: false,
},
// button
actionText: {
type: String,
default: '',
},
//
actionUrl: {
type: String,
default: '',
},
//
paddingTop: {
type: String,
default: '260',
},
//
buttonColor: {
type: String,
default: 'var(--ui-BG-Main)',
},
})
const emits = defineEmits(['clickAction'])
function clickAction() {
if (props.actionUrl !== '') {
peach.$router.go(props.actionUrl)
}
emits('clickAction')
}
</script>
<style lang="scss" scoped>
.empty-box {
width: 100%;
}
.empty-icon {
width: 240rpx;
}
.empty-text {
font-size: 26rpx;
font-weight: 500;
color: #999999;
}
.empty-btn {
width: 320rpx;
height: 70rpx;
border: 2rpx solid v-bind('buttonColor');
border-radius: 35rpx;
font-weight: 500;
color: v-bind('buttonColor');
font-size: 28rpx;
}
</style>

19
peach/config/index.js Normal file
View File

@ -0,0 +1,19 @@
// 开发环境配置
export let baseUrl
export let version
if (process.env.NODE_ENV === 'development') {
baseUrl = import.meta.env.MALL_DEV_BASE_URL
} else {
baseUrl = import.meta.env.MALL_BASE_URL
}
version = import.meta.env.MALL_VERSION
console.log(`[🍑商城 ${version}] http://ankkaya.top`)
export const apiPath = import.meta.env.MALL_API_PATH
export const staticUrl = import.meta.env.MALL_STATIC_URL
export default {
baseUrl,
apiPath,
staticUrl,
}

165
peach/helper/digit.js Normal file
View File

@ -0,0 +1,165 @@
let _boundaryCheckingState = true // 是否进行越界检查的全局开关
/**
* 把错误的数据转正
* @private
* @example strip(0.09999999999999998)=0.1
*/
function strip(num, precision = 15) {
return +parseFloat(Number(num).toPrecision(precision))
}
/**
* Return digits length of a number
* @private
* @param {*number} num Input number
*/
function digitLength(num) {
// Get digit length of e
const eSplit = num.toString().split(/[eE]/)
const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0)
return len > 0 ? len : 0
}
/**
* 把小数转成整数,如果是小数则放大成整数
* @private
* @param {*number} num 输入数
*/
function float2Fixed(num) {
if (num.toString().indexOf('e') === -1) {
return Number(num.toString().replace('.', ''))
}
const dLen = digitLength(num)
return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num)
}
/**
* 检测数字是否越界如果越界给出提示
* @private
* @param {*number} num 输入数
*/
function checkBoundary(num) {
if (_boundaryCheckingState) {
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
console.warn(`${num} 超出了精度限制,结果可能不正确`)
}
}
}
/**
* 把递归操作扁平迭代化
* @param {number[]} arr 要操作的数字数组
* @param {function} operation 迭代操作
* @private
*/
function iteratorOperation(arr, operation) {
const [num1, num2, ...others] = arr
let res = operation(num1, num2)
others.forEach((num) => {
res = operation(res, num)
})
return res
}
/**
* 高精度乘法
* @export
*/
export function times(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, times)
}
const [num1, num2] = nums
const num1Changed = float2Fixed(num1)
const num2Changed = float2Fixed(num2)
const baseNum = digitLength(num1) + digitLength(num2)
const leftValue = num1Changed * num2Changed
checkBoundary(leftValue)
return leftValue / Math.pow(10, baseNum)
}
/**
* 高精度加法
* @export
*/
export function plus(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, plus)
}
const [num1, num2] = nums
// 取最大的小数位
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)))
// 把小数都转为整数然后再计算
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum
}
/**
* 高精度减法
* @export
*/
export function minus(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, minus)
}
const [num1, num2] = nums
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)))
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum
}
/**
* 高精度除法
* @export
*/
export function divide(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, divide)
}
const [num1, num2] = nums
const num1Changed = float2Fixed(num1)
const num2Changed = float2Fixed(num2)
checkBoundary(num1Changed)
checkBoundary(num2Changed)
// 重要这里必须用strip进行修正
return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))))
}
/**
* 四舍五入
* @export
*/
export function round(num, ratio) {
const base = Math.pow(10, ratio)
let result = divide(Math.round(Math.abs(times(num, base))), base)
if (num < 0 && result !== 0) {
result = times(result, -1)
}
// 位数不足则补0
return result
}
/**
* 是否进行边界检查默认开启
* @param flag 标记开关true 为开启false 为关闭默认为 true
* @export
*/
export function enableBoundaryChecking(flag = true) {
_boundaryCheckingState = flag
}
export default {
times,
plus,
minus,
divide,
round,
enableBoundaryChecking,
}

707
peach/helper/index.js Normal file
View File

@ -0,0 +1,707 @@
import test from './test.js'
import { round } from './digit.js'
/**
* @description 如果value小于min取min如果value大于max取max
* @param {number} min
* @param {number} max
* @param {number} value
*/
function range(min = 0, max = 0, value = 0) {
return Math.max(min, Math.min(max, Number(value)))
}
/**
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx"取出其数值部分如果是"xxxrpx"还需要用过uni.upx2px进行转换
* @param {number|string} value 用户传递值的px值
* @param {boolean} unit
* @returns {number|string}
*/
export function getPx(value, unit = false) {
if (test.number(value)) {
return unit ? `${value}px` : Number(value)
}
// 如果带有rpx先取出其数值部分再转为px值
if (/(rpx|upx)$/.test(value)) {
return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
}
return unit ? `${parseInt(value)}px` : parseInt(value)
}
/**
* @description 进行延时以达到可以简写代码的目的
* @param {number} value 堵塞时间 单位ms 毫秒
* @returns {Promise} 返回promise
*/
export function sleep(value = 30) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, value)
})
}
/**
* @description 运行期判断平台
* @returns {string} 返回所在平台(小写)
* @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
*/
export function os() {
return uni.getSystemInfoSync().platform.toLowerCase()
}
/**
* @description 获取系统信息同步接口
* @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
*/
export function sys() {
return uni.getSystemInfoSync()
}
/**
* @description 取一个区间数
* @param {Number} min 最小值
* @param {Number} max 最大值
*/
function random(min, max) {
if (min >= 0 && max > 0 && max >= min) {
const gab = max - min + 1
return Math.floor(Math.random() * gab + min)
}
return 0
}
/**
* @param {Number} len uuid的长度
* @param {Boolean} firstU 将返回的首字母置为"u"
* @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
*/
export function guid(len = 32, firstU = true, radix = null) {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
const uuid = []
radix = radix || chars.length
if (len) {
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]
} else {
let r
// rfc4122标准要求返回的uuid中,某些位为固定的字符
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
uuid[14] = '4'
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16)
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r]
}
}
}
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
if (firstU) {
uuid.shift()
return `u${uuid.join('')}`
}
return uuid.join('')
}
/**
* @description 获取父组件的参数因为支付宝小程序不支持provide/inject的写法
this.$parent在非H5中可以准确获取到父组件但是在H5中需要多次this.$parent.$parent.xxx
这里默认值等于undefined有它的含义因为最顶层元素(组件)的$parent就是undefined意味着不传name
(默认为undefined)就是查找最顶层的$parent
* @param {string|undefined} name 父组件的参数名
*/
export function $parent(name = undefined) {
let parent = this.$parent
// 通过while历遍这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options && parent.$options.name !== name) {
// 如果组件的name不相等继续上一级寻找
parent = parent.$parent
} else {
return parent
}
}
return false
}
/**
* @description 样式转换
* 对象转字符串或者字符串转对象
* @param {object | string} customStyle 需要转换的目标
* @param {String} target 转换的目的object-转为对象string-转为字符串
* @returns {object|string}
*/
export function addStyle(customStyle, target = 'object') {
// 字符串转字符串,对象转对象情形,直接返回
if (
test.empty(customStyle) ||
(typeof customStyle === 'object' && target === 'object') ||
(target === 'string' && typeof customStyle === 'string')
) {
return customStyle
}
// 字符串转对象
if (target === 'object') {
// 去除字符串样式中的两端空格(中间的空格不能去掉比如padding: 20px 0如果去掉了就错了),空格是无用的
customStyle = trim(customStyle)
// 根据";"将字符串转为数组形式
const styleArray = customStyle.split(';')
const style = {}
// 历遍数组,拼接成对象
for (let i = 0; i < styleArray.length; i++) {
// 'font-size:20px;color:red;',如此最后字符串有";"的话会导致styleArray最后一个元素为空字符串这里需要过滤
if (styleArray[i]) {
const item = styleArray[i].split(':')
style[trim(item[0])] = trim(item[1])
}
}
return style
}
// 这里为对象转字符串形式
let string = ''
for (const i in customStyle) {
// 驼峰转为中划线的形式否则css内联样式无法识别驼峰样式属性名
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
string += `${key}:${customStyle[i]};`
}
// 去除两端空格
return trim(string)
}
/**
* @description 添加单位如果有rpxupx%px等单位结尾或者值为auto直接返回否则加上px单位结尾
* @param {string|number} value 需要添加单位的值
* @param {string} unit 添加的单位名 比如px
*/
export function addUnit(value = 'auto', unit = 'px') {
value = String(value)
return test.number(value) ? `${value}${unit}` : value
}
/**
* @description 深度克隆
* @param {object} obj 需要深度克隆的对象
* @returns {*} 克隆后的对象或者原值不是对象
*/
function deepClone(obj) {
// 对常见的“非”值,直接返回原来值
if ([null, undefined, NaN, false].includes(obj)) return obj
if (typeof obj !== 'object' && typeof obj !== 'function') {
// 原始类型直接返回
return obj
}
const o = test.array(obj) ? [] : {}
for (const i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
}
}
return o
}
/**
* @description JS对象深度合并
* @param {object} target 需要拷贝的对象
* @param {object} source 拷贝的来源对象
* @returns {object|boolean} 深度合并后的对象或者false入参有不是对象
*/
export function deepMerge(target = {}, source = {}) {
target = deepClone(target)
if (typeof target !== 'object' || typeof source !== 'object') return false
for (const prop in source) {
if (!source.hasOwnProperty(prop)) continue
if (prop in target) {
if (typeof target[prop] !== 'object') {
target[prop] = source[prop]
} else if (typeof source[prop] !== 'object') {
target[prop] = source[prop]
} else if (target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop])
} else {
target[prop] = deepMerge(target[prop], source[prop])
}
} else {
target[prop] = source[prop]
}
}
return target
}
/**
* @description error提示
* @param {*} err 错误内容
*/
function error(err) {
// 开发环境才提示,生产环境不会提示
if (process.env.NODE_ENV === 'development') {
console.error(`SheepJS:${err}`)
}
}
/**
* @description 打乱数组
* @param {array} array 需要打乱的数组
* @returns {array} 打乱后的数组
*/
function randomArray(array = []) {
// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
return array.sort(() => Math.random() - 0.5)
}
// padStart 的 polyfill因为某些机型或情况还无法支持es7的padStart比如电脑版的微信小程序
// 所以这里做一个兼容polyfill的兼容处理
if (!String.prototype.padStart) {
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
String.prototype.padStart = function (maxLength, fillString = ' ') {
if (Object.prototype.toString.call(fillString) !== '[object String]') {
throw new TypeError('fillString must be String')
}
const str = this
// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
if (str.length >= maxLength) return String(str)
const fillLength = maxLength - str.length
let times = Math.ceil(fillLength / fillString.length)
while ((times >>= 1)) {
fillString += fillString
if (times === 1) {
fillString += fillString
}
}
return fillString.slice(0, fillLength) + str
}
}
/**
* @description 格式化时间
* @param {String|Number} dateTime 需要格式化的时间戳
* @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
* @returns {string} 返回格式化后的字符串
*/
function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
let date
// 若传入时间为假值,则取当前时间
if (!dateTime) {
date = new Date()
}
// 若为unix秒时间戳则转为毫秒时间戳逻辑有点奇怪但不敢改以保证历史兼容
else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
date = new Date(dateTime * 1000)
}
// 若用户传入字符串格式时间戳new Date无法解析需做兼容
else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
date = new Date(Number(dateTime))
}
// 其他都认为符合 RFC 2822 规范
else {
// 处理平台性差异在Safari/Webkit中new Date仅支持/作为分割符的字符串时间
date = new Date(typeof dateTime === 'string' ? dateTime.replace(/-/g, '/') : dateTime)
}
const timeSource = {
y: date.getFullYear().toString(), // 年
m: (date.getMonth() + 1).toString().padStart(2, '0'), // 月
d: date.getDate().toString().padStart(2, '0'), // 日
h: date.getHours().toString().padStart(2, '0'), // 时
M: date.getMinutes().toString().padStart(2, '0'), // 分
s: date.getSeconds().toString().padStart(2, '0'), // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
}
for (const key in timeSource) {
const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
if (ret) {
// 年可能只需展示两位
const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
}
}
return formatStr
}
/**
* @description 时间戳转为多久之前
* @param {String|Number} timestamp 时间戳
* @param {String|Boolean} format
* 格式化规则如果为时间格式字符串超出一定时间范围返回固定的时间格式
* 如果为布尔值false无论什么时间都返回多久以前的格式
* @returns {string} 转化后的内容
*/
function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
if (timestamp == null) timestamp = Number(new Date())
timestamp = parseInt(timestamp)
// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
if (timestamp.toString().length == 10) timestamp *= 1000
let timer = new Date().getTime() - timestamp
timer = parseInt(timer / 1000)
// 如果小于5分钟,则返回"刚刚",其他以此类推
let tips = ''
switch (true) {
case timer < 300:
tips = '刚刚'
break
case timer >= 300 && timer < 3600:
tips = `${parseInt(timer / 60)}分钟前`
break
case timer >= 3600 && timer < 86400:
tips = `${parseInt(timer / 3600)}小时前`
break
case timer >= 86400 && timer < 2592000:
tips = `${parseInt(timer / 86400)}天前`
break
default:
// 如果format为false则无论什么时间戳都显示xx之前
if (format === false) {
if (timer >= 2592000 && timer < 365 * 86400) {
tips = `${parseInt(timer / (86400 * 30))}个月前`
} else {
tips = `${parseInt(timer / (86400 * 365))}年前`
}
} else {
tips = timeFormat(timestamp, format)
}
}
return tips
}
/**
* @description 去除空格
* @param String str 需要去除空格的字符串
* @param String pos both(左右)|left|right|all 默认both
*/
function trim(str, pos = 'both') {
str = String(str)
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, '')
}
if (pos == 'left') {
return str.replace(/^\s*/, '')
}
if (pos == 'right') {
return str.replace(/(\s*$)/g, '')
}
if (pos == 'all') {
return str.replace(/\s+/g, '')
}
return str
}
/**
* @description 对象转url参数
* @param {object} data,对象
* @param {Boolean} isPrefix,是否自动加上"?"
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
*/
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
const prefix = isPrefix ? '?' : ''
const _result = []
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
for (const key in data) {
const value = data[key]
// 去掉为空的参数
if (['', undefined, null].indexOf(value) >= 0) {
continue
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case 'indices':
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(`${key}[${i}]=${value[i]}`)
}
break
case 'brackets':
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`)
})
break
case 'repeat':
// 结果: ids=1&ids=2&ids=3
value.forEach((_value) => {
_result.push(`${key}=${_value}`)
})
break
case 'comma':
// 结果: ids=1,2,3
let commaStr = ''
value.forEach((_value) => {
commaStr += (commaStr ? ',' : '') + _value
})
_result.push(`${key}=${commaStr}`)
break
default:
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`)
})
}
} else {
_result.push(`${key}=${value}`)
}
}
return _result.length ? prefix + _result.join('&') : ''
}
/**
* 显示消息提示框
* @param {String} title 提示的内容长度与 icon 取值有关
* @param {Number} duration 提示的延迟时间单位毫秒默认2000
*/
function toast(title, duration = 2000) {
uni.showToast({
title: String(title),
icon: 'none',
duration,
})
}
/**
* @description 根据主题type值,获取对应的图标
* @param {String} type 主题名称,primary|info|error|warning|success
* @param {boolean} fill 是否使用fill填充实体的图标
*/
function type2icon(type = 'success', fill = false) {
// 如果非预置值,默认为success
if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
let iconName = ''
// 目前(2019-12-12),info和primary使用同一个图标
switch (type) {
case 'primary':
iconName = 'info-circle'
break
case 'info':
iconName = 'info-circle'
break
case 'error':
iconName = 'close-circle'
break
case 'warning':
iconName = 'error-circle'
break
case 'success':
iconName = 'checkmark-circle'
break
default:
iconName = 'checkmark-circle'
}
// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
if (fill) iconName += '-fill'
return iconName
}
/**
* @description 数字格式化
* @param {number|string} number 要格式化的数字
* @param {number} decimals 保留几位小数
* @param {string} decimalPoint 小数点符号
* @param {string} thousandsSeparator 千分位符号
* @returns {string} 格式化后的数字
*/
function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
number = `${number}`.replace(/[^0-9+-Ee.]/g, '')
const n = !isFinite(+number) ? 0 : +number
const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
const sep = typeof thousandsSeparator === 'undefined' ? ',' : thousandsSeparator
const dec = typeof decimalPoint === 'undefined' ? '.' : decimalPoint
let s = ''
s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
const re = /(-?\d+)(\d{3})/
while (re.test(s[0])) {
s[0] = s[0].replace(re, `$1${sep}$2`)
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || ''
s[1] += new Array(prec - s[1].length + 1).join('0')
}
return s.join(dec)
}
/**
* @description 获取duration值
* 如果带有ms或者s直接返回如果大于一定值认为是ms单位小于一定值认为是s单位
* 比如以30位阈值那么300大于30可以理解为用户想要的是300ms而不是想花300s去执行一个动画
* @param {String|number} value 比如: "1s"|"100ms"|1|100
* @param {boolean} unit 提示: 如果是false 默认返回number
* @return {string|number}
*/
function getDuration(value, unit = true) {
const valueNum = parseInt(value)
if (unit) {
if (/s$/.test(value)) return value
return value > 30 ? `${value}ms` : `${value}s`
}
if (/ms$/.test(value)) return valueNum
if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
return valueNum
}
/**
* @description 日期的月或日补零操作
* @param {String} value 需要补零的值
*/
function padZero(value) {
return `00${value}`.slice(-2)
}
/**
* @description 获取某个对象下的属性用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
* @param {object} obj 对象
* @param {string} key 需要获取的属性字段
* @returns {*}
*/
function getProperty(obj, key) {
if (!obj) {
return
}
if (typeof key !== 'string' || key === '') {
return ''
}
if (key.indexOf('.') !== -1) {
const keys = key.split('.')
let firstObj = obj[keys[0]] || {}
for (let i = 1; i < keys.length; i++) {
if (firstObj) {
firstObj = firstObj[keys[i]]
}
}
return firstObj
}
return obj[key]
}
/**
* @description 设置对象的属性值如果'a.b.c'的形式进行设置
* @param {object} obj 对象
* @param {string} key 需要设置的属性
* @param {string} value 设置的值
*/
function setProperty(obj, key, value) {
if (!obj) {
return
}
// 递归赋值
const inFn = function (_obj, keys, v) {
// 最后一个属性key
if (keys.length === 1) {
_obj[keys[0]] = v
return
}
// 0~length-1个key
while (keys.length > 1) {
const k = keys[0]
if (!_obj[k] || typeof _obj[k] !== 'object') {
_obj[k] = {}
}
const key = keys.shift()
// 自调用判断是否存在属性,不存在则自动创建对象
inFn(_obj[k], keys, v)
}
}
if (typeof key !== 'string' || key === '') {
} else if (key.indexOf('.') !== -1) {
// 支持多层级赋值操作
const keys = key.split('.')
inFn(obj, keys, value)
} else {
obj[key] = value
}
}
/**
* @description 获取当前页面路径
*/
function page() {
const pages = getCurrentPages()
// 某些特殊情况下(比如页面进行redirectTo时的一些时机)pages可能为空数组
return `/${pages[pages.length - 1]?.route ?? ''}`
}
/**
* @description 获取当前路由栈实例数组
*/
function pages() {
const pages = getCurrentPages()
return pages
}
/**
* 获取H5-真实根地址 兼容hash+history模式
*/
export function getRootUrl() {
let url = ''
// #ifdef H5
url = location.origin + '/'
if (location.hash !== '') {
url += '#/'
}
// #endif
return url
}
/**
* copyText 多端复制文本
*/
export function copyText(text) {
// #ifndef H5
uni.setClipboardData({
data: text,
success: function () {
toast('复制成功!')
},
fail: function () {
toast('复制失败!')
},
})
// #endif
// #ifdef H5
var createInput = document.createElement('textarea')
createInput.value = text
document.body.appendChild(createInput)
createInput.select()
document.execCommand('Copy')
createInput.className = 'createInput'
createInput.style.display = 'none'
toast('复制成功')
// #endif
}
export default {
range,
getPx,
sleep,
os,
sys,
random,
guid,
$parent,
addStyle,
addUnit,
deepClone,
deepMerge,
error,
randomArray,
timeFormat,
timeFrom,
trim,
queryParams,
toast,
type2icon,
priceFormat,
getDuration,
padZero,
getProperty,
setProperty,
page,
pages,
test,
getRootUrl,
copyText,
}

293
peach/helper/test.js Normal file
View File

@ -0,0 +1,293 @@
/**
* 验证银行卡号
*/
function bankCode(value) {
return /^[1-9]\d{15,19}$/.test(value)
}
/**
* 验证电子邮箱格式
*/
function email(value) {
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
}
/**
* 验证手机格式
*/
function mobile(value) {
return /^1[23456789]\d{9}$/.test(value)
}
/**
* 验证URL格式
*/
function url(value) {
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/.test(
value
)
}
/**
* 验证日期格式
*/
function date(value) {
if (!value) return false
// 判断是否数值或者字符串数值(意味着为时间戳)转为数值否则new Date无法识别字符串时间戳
if (number(value)) value = +value
return !/Invalid|NaN/.test(new Date(value).toString())
}
/**
* 验证ISO类型的日期格式
*/
function dateISO(value) {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}
/**
* 验证十进制数字
*/
function number(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* 验证字符串
*/
function string(value) {
return typeof value === 'string'
}
/**
* 验证整数
*/
function digits(value) {
return /^\d+$/.test(value)
}
/**
* 验证身份证号码
*/
function idCard(value) {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)
}
/**
* 是否车牌号
*/
function carNo(value) {
// 新能源车牌
const xreg =
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
// 旧车牌
const creg =
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
if (value.length === 7) {
return creg.test(value)
}
if (value.length === 8) {
return xreg.test(value)
}
return false
}
/**
* 金额,只允许2位小数
*/
function amount(value) {
// 金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
}
/**
* 中文
*/
function chinese(value) {
const reg = /^[\u4e00-\u9fa5]+$/gi
return reg.test(value)
}
/**
* 只能输入字母
*/
function letter(value) {
return /^[a-zA-Z]*$/.test(value)
}
/**
* 只能是字母或者数字
*/
function enOrNum(value) {
// 英文或者数字
const reg = /^[0-9a-zA-Z]*$/g
return reg.test(value)
}
/**
* 验证是否包含某个值
*/
function contains(value, param) {
return value.indexOf(param) >= 0
}
/**
* 验证一个值范围[min, max]
*/
function range(value, param) {
return value >= param[0] && value <= param[1]
}
/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value, param) {
return value.length >= param[0] && value.length <= param[1]
}
/**
* 是否固定电话
*/
function landline(value) {
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
return reg.test(value)
}
/**
* 判断是否为空
*/
function empty(value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
}
/**
* 是否json字符串
*/
function jsonString(value) {
if (typeof value === 'string') {
try {
const obj = JSON.parse(value)
if (typeof obj === 'object' && obj) {
return true
}
return false
} catch (e) {
return false
}
}
return false
}
/**
* 是否数组
*/
function array(value) {
if (typeof Array.isArray === 'function') {
return Array.isArray(value)
}
return Object.prototype.toString.call(value) === '[object Array]'
}
/**
* 是否对象
*/
function object(value) {
return Object.prototype.toString.call(value) === '[object Object]'
}
/**
* 是否短信验证码
*/
function code(value, len = 6) {
return new RegExp(`^\\d{${len}}$`).test(value)
}
/**
* 是否函数方法
* @param {Object} value
*/
function func(value) {
return typeof value === 'function'
}
/**
* 是否promise对象
* @param {Object} value
*/
function promise(value) {
return object(value) && func(value.then) && func(value.catch)
}
/**
* @param {Object} value
*/
function image(value) {
const newValue = value.split('?')[0]
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
return IMAGE_REGEXP.test(newValue)
}
/**
* 是否视频格式
* @param {Object} value
*/
function video(value) {
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
return VIDEO_REGEXP.test(value)
}
/**
* 是否为正则对象
* @param {Object}
* @return {Boolean}
*/
function regExp(o) {
return o && Object.prototype.toString.call(o) === '[object RegExp]'
}
export default {
email,
mobile,
url,
date,
dateISO,
number,
digits,
idCard,
carNo,
amount,
chinese,
letter,
enOrNum,
contains,
range,
rangeLength,
empty,
isEmpty: empty,
isNumber: number,
jsonString,
landline,
object,
array,
code,
bankCode,
}

31
peach/helper/throttle.js Normal file
View File

@ -0,0 +1,31 @@
let timer
let flag
/**
* 节流原理在一定时间内只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
} else {
}
} else if (!flag) {
flag = true
// 如果是非立即执行则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}
export default throttle

171
peach/hooks/useModal.js Normal file
View File

@ -0,0 +1,171 @@
import { ref } from 'vue'
import dayjs from 'dayjs'
import $store from '@/peach/store'
import $helper from '@/peach/helper'
import test from '@/peach/helper/test.js'
import AuthUtil from '@/peach/api/member/auth'
/**
* @author Ankkaya
* @description 打开授权弹窗
* @param {string} type - 弹窗类型
* @returns {Type}
*/
export function showAuthModal(type = 'smsLogin') {
const modal = $store('modal')
if (modal.auth !== '') {
closeAuthModal()
setTimeout(() => {
modal.auth = type
}, 100)
} else {
modal.auth = type
}
}
/**
* @author Ankkaya
* @description 关闭授权弹窗
* @param {Type} -
* @returns {Type}
*/
export function closeAuthModal() {
$store('modal').auth = ''
}
/**
* @author Ankkaya
* @description 打开分享弹窗
* @param {Type} -
* @returns {Type}
*/
export function showShareModal() {
$store('modal').share = true
}
/**
* @author Ankkaya
* @description 关闭分享弹窗
* @param {Type} -
* @returns {Type}
*/
export function closeShareModal() {
$store('modal').share = false
}
/**
* @author Ankkaya
* @description 打开快捷菜单
* @param {Type} -
* @returns {Type}
*/
export function showMenuTools() {
$store('modal').menu = true
}
/**
* @author Ankkaya
* @description 关闭快捷菜单
* @param {Type} -
* @returns {Type}
*/
// 关闭快捷菜单
export function closeMenuTools() {
$store('modal').menu = false
}
/**
* @author Ankkaya
* @description 发送验证码
* @param {string} event - 事件类型
* @param {string} mobile - 手机号码
* @returns {Type}
*/
export function getSmsCode(event, mobile) {
const modalStore = $store('modal')
const lastSendTimer = modalStore.lastTimer[event]
if (typeof lastSendTimer === 'undefined') {
$helper.toast('短信发送事件错误')
return
}
const duration = dayjs().unix() - lastSendTimer
const canSend = duration >= 60
if (!canSend) {
$helper.toast('请稍后再试')
return
}
// 只有 mobile 非空时才校验。因为部分场景(修改密码),不需要输入手机
if (mobile && !test.mobile(mobile)) {
$helper.toast('手机号码格式不正确')
return
}
// 发送验证码 + 更新上次发送验证码时间
let scene = -1
switch (event) {
case 'resetPassword':
scene = 4
break
case 'changePassword':
scene = 3
break
case 'changeMobile':
scene = 2
break
case 'smsLogin':
scene = 1
break
}
AuthUtil.sendSmsCode(mobile, scene).then((res) => {
if (res.code === 0) {
modalStore.lastTimer[event] = dayjs().unix()
}
})
}
/**
* @author Ankkaya
* @description 获取验证码倒计时
* @param {string} event - 事件类型
* @returns {Type}
*/
export function getSmsTimer(event) {
const modalStore = $store('modal')
const lastSendTimer = modalStore.lastTimer[event]
if (typeof lastSendTimer === 'undefined') {
$helper.toast('短信发送事件错误')
return
}
const duration = ref(dayjs().unix() - lastSendTimer - 60)
const canSend = duration.value >= 0
if (canSend) {
return '获取验证码'
}
if (!canSend) {
setTimeout(() => {
duration.value++
}, 1000)
return -duration.value.toString() + ' 秒'
}
}
/**
* @author Ankkaya
* @description 广告弹框历史
* @param {Type} -
* @returns {Type}
*/
export function saveAdvHistory(adv) {
const modal = $store('modal')
modal.$patch((state) => {
if (!state.advHistory.includes(adv.imgUrl)) {
state.advHistory.push(adv.imgUrl)
}
})
}

41
peach/index.js Normal file
View File

@ -0,0 +1,41 @@
import $store from '@/peach/store'
import $platform from '@/peach/platform'
import $url from '@/peach/url'
import $router from '@/peach/router'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import duration from 'dayjs/plugin/duration'
import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn')
dayjs.extend(relativeTime)
dayjs.extend(duration)
const peach = {
$store,
$platform,
$url,
$router,
}
export async function peachInit() {
// 应用初始化
await $store('app').init()
// 平台初始化加载(各平台 provider 提供不同加载流程)
// debug
if (process.env.NODE_ENV === 'development') {
debug()
}
}
function debug() {
// #ifdef H5
import('vconsole').then((vconsole) => {
new vconsole.default()
})
// #endif
}
export default peach

73
peach/platform/index.js Normal file
View File

@ -0,0 +1,73 @@
import share from './share'
const device = uni.getSystemInfoSync()
/**
* @author Ankkaya
* @description 标题栏高度
* @param {Type} -
* @returns {Type}
*/
function getNavBar() {
return device.statusBarHeight + 44
}
const navBar = getNavBar()
/**
* @author Ankkaya
* @description 胶囊高度
* @param {Type} -
* @returns {Type}
*/
function getCapsule() {
// #ifdef MP
let capsule = uni.getMenuButtonBoundingClientRect()
if (!capsule) {
capsule = {
bottom: 56,
height: 32,
left: 278,
right: 365,
top: 24,
width: 87,
}
}
return capsule
// #endif
// #ifndef MP
return {
bottom: 56,
height: 32,
left: 278,
right: 365,
top: 24,
width: 87,
}
// #endif
}
const capsule = getCapsule()
/**
* @author Ankkaya
* @description 判断网络状态
* @param {Type} -
* @returns {Type}
*/
async function checkNetwork() {
const networkStatus = await uni.getNetworkType()
if (networkStatus.networkType == 'none') {
return Promise.resolve(false)
}
return Promise.resolve(true)
}
const _platform = {
device,
share,
navBar,
capsule,
checkNetwork,
}
export default _platform

53
peach/platform/share.js Normal file
View File

@ -0,0 +1,53 @@
import $store from '@/peach/store'
// 设置分享信息参数
const getShareInfo = (
scene = {
title: '', // 自定义分享标题
desc: '', // 自定义描述
image: '', // 自定义分享图片
params: {}, // 自定义分享参数
},
poster = {
// 自定义海报数据
type: 'user',
}
) => {
let shareInfo = {
title: '', // 分享标题
desc: '', // 描述
image: '', // 分享图片
path: '', // 分享页面+参数
link: '', // 分享Url+参数
query: '', // 分享参数
poster, // 海报所需数据
}
const app = $store('app')
const shareConfig = app.platform.share
// 自动拼接分享用户参数
// const query = buildSpmQuery(scene.params)
// shareInfo.query = query
// 配置分享链接地址
// shareInfo.link = buildSpmLink(query, shareConfig.linkAddress)
// 配置转发参数
if (shareConfig.methods.includes('forward')) {
if (shareConfig.forwardInfo.title === '' || shareConfig.forwardInfo.image === '') {
console.log('请在平台设置中配置转发信息')
}
// 设置自定义分享信息
shareInfo.title = scene.title || shareConfig.forwardInfo.title
shareInfo.image = $url.cdn(scene.image || shareConfig.forwardInfo.image)
shareInfo.desc = scene.desc || shareConfig.forwardInfo.subtitle
shareInfo.path = buildSpmPath(query)
}
return shareInfo
}
export default {
getShareInfo,
}

297
peach/request/index.js Normal file
View File

@ -0,0 +1,297 @@
/**
* 网络请求封装
* @description api 模块管理loading 配置请求拦截错误处理
*/
import Request from 'luch-request'
import { baseUrl, apiPath } from '@/peach/config'
import $store from '@/peach/store'
import { showAuthModal } from '@/peach/hooks/useModal'
import AuthUtil from '@/peach/api/member/auth'
const options = {
// 显示操作成功消息 默认不显示
showSuccess: false,
// 成功提醒 默认使用后端返回值
successMsg: '',
// 显示失败消息 默认显示
showError: true,
// 失败提醒 默认使用后端返回信息
errorMsg: '',
// 显示请求时loading模态框 默认显示
showLoading: true,
// loading提醒文字
loadingMsg: '加载中',
// 需要授权才能请求 默认放开
auth: false,
}
// Loading全局实例
let LoadingInstance = {
target: null,
count: 0,
}
/**
* @author Ankkaya
* @description 关闭 loading
* @param {Type} -
* @returns {Type}
*/
function closeLoading() {
if (LoadingInstance.count > 0) LoadingInstance.count--
if (LoadingInstance.count === 0) uni.hideLoading()
}
// 请求实例
const http = new Request({
baseUrl: baseUrl + apiPath,
timeout: 8000,
method: 'GET',
header: {
Accept: 'text/json',
'Content-Type': 'application/json;charset=UTF-8',
},
custom: options,
})
/**
* @author Ankkaya
* @description 请求拦截
* @param {Type} -
* @returns {Type}
*/
http.interceptors.request.use(
(config) => {
// 自定义处理【auth 授权】:必须登录的接口,否则提示登录
if (config.custom.auth && !$store('user').isLogin) {
// 处理登录
showAuthModal()
return Promise.reject()
}
// 自定义处理【loading 加载中】:如果需要显示 loading则显示 loading
if (config.custom.showLoading) {
LoadingInstance.count++
LoadingInstance.count === 1 &&
uni.showLoading({
title: config.custom.loadingMsg,
mask: true,
fail: () => {
uni.hideLoading()
},
})
}
// 增加 token 令牌、terminal 终端、tenant 租户的请求头
const token = getAccessToken()
if (token) {
config.header['Authorization'] = token
}
config.header['Accept'] = '*/*'
return config
},
(error) => {
return Promise.reject(error)
}
)
/**
* @author Ankkaya
* @description 响应拦截
* @param {Type} -
* @returns {Type}
*/
http.interceptors.response.use(
(response) => {
console.log('response', response)
// 约定:如果是 /auth/ 下的 URL 地址,并且返回了 accessToken 说明是登录相关的接口,则自动设置登陆令牌
if (response.config.url.indexOf('/member/auth/') >= 0 && response.data?.data?.accessToken) {
$store('user').setToken(response.data.data.accessToken, response.data.data.refreshToken)
}
// 自定处理【loading 加载中】:如果需要显示 loading则关闭 loading
response.config.custom.showLoading && closeLoading()
// 自定义处理【error 错误提示】:如果需要显示错误提示,则显示错误提示
if (response.data.code !== 0) {
// 特殊:如果 401 错误码,则跳转到登录页 or 刷新令牌
if (response.data.code === 401) {
return refreshToken(response.config)
}
// 错误提示
if (response.config.custom.showError) {
uni.showToast({
title: response.data.msg || '服务器开小差啦,请稍后再试~',
icon: 'none',
mask: true,
})
return Promise.reject(false)
}
}
// 自定义处理【showSuccess 成功提示】:如果需要显示成功提示,则显示成功提示
if (
response.config.custom.showSuccess &&
response.config.custom.successMsg !== '' &&
response.data.code === 0
) {
uni.showToast({
title: response.config.custom.successMsg,
icon: 'none',
})
}
// 返回结果:包括 code + data + msg
return Promise.resolve(response.data)
},
(error) => {
console.log('error', error)
const userStore = $store('user')
const isLogin = userStore.isLogin
let errorMessage = '网络请求出错'
if (error !== undefined) {
switch (error.statusCode) {
case 400:
errorMessage = '请求错误'
break
case 401:
errorMessage = isLogin ? '您的登陆已过期' : '请先登录'
// 正常情况下,后端不会返回 401 错误,所以这里不处理 handleAuthorized
break
case 403:
errorMessage = '拒绝访问'
break
case 404:
errorMessage = '请求出错'
break
case 408:
errorMessage = '请求超时'
break
case 429:
errorMessage = '请求频繁, 请稍后再访问'
break
case 500:
errorMessage = '服务器开小差啦,请稍后再试~'
break
case 501:
errorMessage = '服务未实现'
break
case 502:
errorMessage = '网络错误'
break
case 503:
errorMessage = '服务不可用'
break
case 504:
errorMessage = '网络超时'
break
case 505:
errorMessage = 'HTTP 版本不受支持'
break
}
if (error.errMsg.includes('timeout')) errorMessage = '请求超时'
// #ifdef H5
if (error.errMsg.includes('Network'))
errorMessage = window.navigator.onLine ? '服务器异常' : '请检查您的网络连接'
// #endif
}
if (error && error.config) {
if (error.config.custom.showError === true) {
uni.showToast({
title: error.data?.msg || errorMessage,
icon: 'none',
mask: true,
})
}
error.config.custom.showLoading && closeLoading()
}
return Promise.reject(false)
}
)
let requestList = [] // 请求队列
let isRefreshToken = false // 是否正在刷新中
const refreshToken = async (config) => {
// 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error)
if (config.url.indexOf('/member/auth/refresh-token') >= 0) {
return Promise.reject('error')
}
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
if (!isRefreshToken) {
isRefreshToken = true
// 1. 如果获取不到刷新令牌,则只能执行登出操作
const refreshToken = getRefreshToken()
if (!refreshToken) {
return handleAuthorized()
}
// 2. 进行刷新访问令牌
try {
const refreshTokenResult = await AuthUtil.refreshToken(refreshToken)
if (refreshTokenResult.code !== 0) {
// 如果刷新不成功,直接抛出 e 触发 2.2 的逻辑
// noinspection ExceptionCaughtLocallyJS
throw new Error('刷新令牌失败')
}
// 2.1 刷新成功,则回放队列的请求 + 当前请求
config.header.Authorization = 'Bearer ' + getAccessToken()
requestList.forEach((cb) => {
cb()
})
requestList = []
return request(config)
} catch (e) {
// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
// 2.2 刷新失败,只回放队列的请求
requestList.forEach((cb) => {
cb()
})
// 提示是否要登出。即不回放当前请求!不然会形成递归
return handleAuthorized()
} finally {
requestList = []
isRefreshToken = false
}
} else {
// 添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
requestList.push(() => {
config.header.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
resolve(request(config))
})
})
}
}
/** 处理 401 未登陆的错误 */
const handleAuthorized = () => {
const userStore = $store('user')
userStore.logout(true)
showAuthModal()
// 登录超时
return Promise.reject({
code: 401,
msg: userStore.isLogin ? '您的登陆已过期' : '请先登录',
})
}
/** 获得访问令牌 */
const getAccessToken = () => {
return uni.getStorageSync('token')
}
/** 获得刷新令牌 */
const getRefreshToken = () => {
return uni.getStorageSync('refresh-token')
}
const request = (config) => {
return http.middleware(config)
}
export default request

185
peach/router/index.js Normal file
View File

@ -0,0 +1,185 @@
import $store from '@/peach/store'
import { showAuthModal, showShareModal } from '@/peach/hooks/useModal'
import { isNumber, isString, isEmpty, startsWith, isObject, isNil, clone } from 'lodash'
import throttle from '@/peach/helper/throttle'
const _go = (
path,
params = {},
options = {
redirect: false,
}
) => {
let page = '' // 跳转页面
let query = '' // 页面参数
let url = '' // 跳转页面完整路径
if (isString(path)) {
// 判断跳转类型是 path 还是http
if (startsWith(path, 'http')) {
// #ifdef H5
window.location = path
return
// #endif
// #ifndef H5
page = `/pages/public/webview`
query = `url=${encodeURIComponent(path)}`
// #endif
} else if (startsWith(path, 'action:')) {
handleAction(path)
return
} else {
;[page, query] = path.split('?')
}
if (!isEmpty(params)) {
let query2 = paramsToQuery(params)
if (isEmpty(query)) {
query = query2
} else {
query += '&' + query2
}
}
}
if (isObject(path)) {
page = path.url
if (!isNil(path.params)) {
query = paramsToQuery(path.params)
}
}
const nextRoute = ROUTES_MAP[page]
// 未找到指定跳转页面
// mark: 跳转404页
if (!nextRoute) {
console.log(`%c跳转路径参数错误<${page || 'EMPTY'}>`, 'color:red;background:yellow')
return
}
// 页面登录拦截
if (nextRoute.meta?.auth && !$store('user').isLogin) {
showAuthModal()
return
}
url = page
if (!isEmpty(query)) {
url += `?${query}`
}
// 跳转底部导航
if (TABBAR.includes(page)) {
uni.switchTab({
url,
})
return
}
// 使用redirect跳转
if (options.redirect) {
uni.redirectTo({
url,
})
return
}
uni.navigateTo({
url,
})
}
// 限流 防止重复点击跳转
function go(...args) {
throttle(() => {
_go(...args)
})
}
function paramsToQuery(params) {
if (isEmpty(params)) {
return ''
}
// return new URLSearchParams(Object.entries(params)).toString();
let query = []
for (let key in params) {
query.push(key + '=' + params[key])
}
return query.join('&')
}
function back() {
// #ifdef H5
history.back()
// #endif
// #ifndef H5
uni.navigateBack()
// #endif
}
function redirect(path, params = {}) {
go(path, params, {
redirect: true,
})
}
// 检测是否有浏览器历史
function hasHistory() {
// #ifndef H5
const pages = getCurrentPages()
if (pages.length > 1) {
return true
}
return false
// #endif
// #ifdef H5
return !!history.back
// #endif
}
function getCurrentRoute(field = '') {
let currentPage = getCurrentPage()
// #ifdef MP
currentPage.$page['route'] = currentPage.route
currentPage.$page['options'] = currentPage.options
// #endif
if (field !== '') {
return currentPage.$page[field]
} else {
return currentPage.$page
}
}
function getCurrentPage() {
let pages = getCurrentPages()
return pages[pages.length - 1]
}
function handleAction(path) {
const action = path.split(':')
switch (action[1]) {
case 'showShareModal':
showShareModal()
break
}
}
function error(errCode, errMsg = '') {
redirect('/pages/public/error', {
errCode,
errMsg,
})
}
export default {
go,
back,
hasHistory,
redirect,
getCurrentPage,
getCurrentRoute,
error,
}

View File

@ -0,0 +1,79 @@
const singleComment = Symbol('singleComment');
const multiComment = Symbol('multiComment');
const stripWithoutWhitespace = () => '';
const stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, ' ');
const isEscaped = (jsonString, quotePosition) => {
let index = quotePosition - 1;
let backslashCount = 0;
while (jsonString[index] === '\\') {
index -= 1;
backslashCount += 1;
}
return Boolean(backslashCount % 2);
};
export default function stripJsonComments(jsonString, { whitespace = true } = {}) {
if (typeof jsonString !== 'string') {
throw new TypeError(
`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``,
);
}
const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
let isInsideString = false;
let isInsideComment = false;
let offset = 0;
let result = '';
for (let index = 0; index < jsonString.length; index++) {
const currentCharacter = jsonString[index];
const nextCharacter = jsonString[index + 1];
if (!isInsideComment && currentCharacter === '"') {
const escaped = isEscaped(jsonString, index);
if (!escaped) {
isInsideString = !isInsideString;
}
}
if (isInsideString) {
continue;
}
if (!isInsideComment && currentCharacter + nextCharacter === '//') {
result += jsonString.slice(offset, index);
offset = index;
isInsideComment = singleComment;
index++;
} else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
index++;
isInsideComment = false;
result += strip(jsonString, offset, index);
offset = index;
continue;
} else if (isInsideComment === singleComment && currentCharacter === '\n') {
isInsideComment = false;
result += strip(jsonString, offset, index);
offset = index;
} else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
result += jsonString.slice(offset, index);
offset = index;
isInsideComment = multiComment;
index++;
continue;
} else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
index++;
isInsideComment = false;
result += strip(jsonString, offset, index + 1);
offset = index + 1;
continue;
}
}
return result + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
}

View File

@ -0,0 +1,103 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
const fs = require('fs');
import stripJsonComments from './strip-json-comments';
import { isArray, isEmpty } from 'lodash';
class TransformPages {
constructor({ includes, pagesJsonDir }) {
this.includes = includes;
this.uniPagesJSON = JSON.parse(stripJsonComments(fs.readFileSync(pagesJsonDir, 'utf-8')));
this.routes = this.getPagesRoutes().concat(this.getSubPackagesRoutes());
this.tabbar = this.getTabbarRoutes();
this.routesMap = this.transformPathToKey(this.routes);
}
/**
* 通过读取pages.json文件 生成直接可用的routes
*/
getPagesRoutes(pages = this.uniPagesJSON.pages, rootPath = null) {
let routes = [];
for (let i = 0; i < pages.length; i++) {
const item = pages[i];
let route = {};
for (let j = 0; j < this.includes.length; j++) {
const key = this.includes[j];
let value = item[key];
if (key === 'path') {
value = rootPath ? `/${rootPath}/${value}` : `/${value}`;
}
if (key === 'aliasPath' && i == 0 && rootPath == null) {
route[key] = route[key] || '/';
} else if (value !== undefined) {
route[key] = value;
}
}
routes.push(route);
}
return routes;
}
/**
* 解析小程序分包路径
*/
getSubPackagesRoutes() {
if (!(this.uniPagesJSON && this.uniPagesJSON.subPackages)) {
return [];
}
const subPackages = this.uniPagesJSON.subPackages;
let routes = [];
for (let i = 0; i < subPackages.length; i++) {
const subPages = subPackages[i].pages;
const root = subPackages[i].root;
const subRoutes = this.getPagesRoutes(subPages, root);
routes = routes.concat(subRoutes);
}
return routes;
}
getTabbarRoutes() {
if (!(this.uniPagesJSON && this.uniPagesJSON.tabBar && this.uniPagesJSON.tabBar.list)) {
return [];
}
const tabbar = this.uniPagesJSON.tabBar.list;
let tabbarMap = [];
tabbar.forEach((bar) => {
tabbarMap.push('/' + bar.pagePath);
});
return tabbarMap;
}
transformPathToKey(list) {
if (!isArray(list) || isEmpty(list)) {
return [];
}
let map = {};
list.forEach((i) => {
map[i.path] = i;
});
return map;
}
}
function uniReadPagesV3Plugin({ pagesJsonDir, includes }) {
let defaultIncludes = ['path', 'aliasPath', 'name'];
includes = [...defaultIncludes, ...includes];
let pages = new TransformPages({
pagesJsonDir,
includes,
});
return {
name: 'uni-read-pages-v3',
config(config) {
return {
define: {
ROUTES: pages.routes,
ROUTES_MAP: pages.routesMap,
TABBAR: pages.tabbar,
},
};
},
};
}
exports.default = uniReadPagesV3Plugin;

3
peach/scss/_main.scss Normal file
View File

@ -0,0 +1,3 @@
body {
color: var(--text-a);
}

61
peach/scss/_mixins.scss Normal file
View File

@ -0,0 +1,61 @@
@mixin bg-square {
background: {
color: #fff;
image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%),
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%);
size: 40rpx 40rpx;
position: 0 0, 20rpx 20rpx;
}
}
@mixin flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: $direction;
}
@mixin flex-bar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
}
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin arrow {
content: '';
height: 0;
width: 0;
position: absolute;
}
@mixin arrow-top {
@include arrow;
// border-color: transparent transparent $ui-BG;
border-style: none solid solid;
border-width: 0 20rpx 20rpx;
}
@mixin arrow-right {
@include arrow;
// border-color: transparent $ui-BG transparent;
border-style: solid solid solid none;
border-width: 20rpx 20rpx 20rpx 0;
}
@mixin position-center {
position: absolute !important;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
@mixin blur {
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
color: var(--ui-TC);
}

286
peach/scss/_tools.scss Normal file
View File

@ -0,0 +1,286 @@
/* ==================
常用工具
==================== */
.ss-bg-opactity-block {
background-color: rgba(#000, 0.2);
color: #fff;
}
/* ==================
flex布局
==================== */
.ss-flex {
display: flex;
flex-direction: row;
align-items: center;
}
.ss-flex-1 {
flex: 1;
}
.ss-flex-col {
display: flex;
flex-direction: column;
}
.ss-flex-wrap {
flex-wrap: wrap;
}
.ss-flex-nowrap {
flex-wrap: nowrap;
}
.ss-col-center {
align-items: center;
}
.ss-col-top {
align-items: flex-start;
}
.ss-col-bottom {
align-items: flex-end;
}
.ss-col-stretch {
align-items: stretch;
}
.ss-row-center {
justify-content: center;
}
.ss-row-left {
justify-content: flex-start;
}
.ss-row-right {
justify-content: flex-end;
}
.ss-row-between {
justify-content: space-between;
}
.ss-row-around {
justify-content: space-around;
}
.ss-self-start {
align-self: flex-start;
}
.ss-self-end {
align-self: flex-end;
}
.ss-self-center {
align-self: center;
}
.ss-h-100 {
height: 100%;
}
.ss-w-100 {
width: 100%;
}
/* ==================
margin padding: 内外边距
==================== */
@for $i from 0 through 100 {
// 只要双数和能被5除尽的数
@if $i % 2==0 or $i % 5==0 {
// 得出u-margin-30或者u-m-30
.ss-margin-#{$i},
.ss-m-#{$i} {
margin: $i + rpx;
}
.ss-m-x-#{$i} {
margin-left: $i + rpx;
margin-right: $i + rpx;
}
.ss-m-y-#{$i} {
margin-top: $i + rpx;
margin-bottom: $i + rpx;
}
// 得出u-padding-30或者u-p-30
.ss-padding-#{$i},
.ss-p-#{$i} {
padding: $i + rpx;
}
.ss-p-x-#{$i} {
padding-left: $i + rpx;
padding-right: $i + rpx;
}
.ss-p-y-#{$i} {
padding-top: $i + rpx;
padding-bottom: $i + rpx;
}
@each $short, $long in l left, t top, r right, b bottom {
// 缩写版结果如 u-m-l-30
// 定义外边距
.ss-m-#{$short}-#{$i} {
margin-#{$long}: $i + rpx;
}
// 定义内边距
.ss-p-#{$short}-#{$i} {
padding-#{$long}: $i + rpx;
}
// 完整版结果如u-margin-left-30
// 定义外边距
.ss-margin-#{$long}-#{$i} {
margin-#{$long}: $i + rpx;
}
// 定义内边距
.ss-padding-#{$long}-#{$i} {
padding-#{$long}: $i + rpx;
}
}
}
}
/* ==================
radius
==================== */
@for $i from 0 through 100 {
// 只要双数和能被5除尽的数
@if $i % 2==0 or $i % 5==0 {
.ss-radius-#{$i},
.ss-r-#{$i} {
border-radius: $i + rpx;
}
.ss-r-t-#{$i} {
border-top-left-radius: $i + rpx;
border-top-right-radius: $i + rpx;
}
.ss-r-b-#{$i} {
border-bottom-left-radius: $i + rpx;
border-bottom-right-radius: $i + rpx;
}
@each $short, $long in tl 'top-left', tr 'top-right', bl 'bottom-right', br 'bottom-right' {
// 定义外边距
.ss-r-#{$short}-#{$i} {
border-#{$long}-radius: $i + rpx;
}
// 定义内边距
.ss-radius-#{$long}-#{$i} {
border-#{$long}-radius: $i + rpx;
}
}
}
}
/* ==================
溢出省略号
@param {Number} 行数
==================== */
@mixin ellipsis($rowCount: 1) {
// @if $rowCount <=1 {
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
// } @else {
// min-width: 0;
// overflow: hidden;
// text-overflow: ellipsis;
// display: -webkit-box;
// -webkit-line-clamp: $rowCount;
// -webkit-box-orient: vertical;
// }
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $rowCount;
-webkit-box-orient: vertical;
}
@for $i from 1 through 6 {
.ss-line-#{$i} {
@include ellipsis($i);
}
}
/* ==================
hover
==================== */
.ss-hover-class {
background-color: $gray-c;
opacity: 0.6;
}
.ss-hover-btn {
transform: translate(1px, 1px);
}
/* ==================
底部安全区域
==================== */
.ss-safe-bottom {
padding-bottom: 0;
padding-bottom: calc(constant(safe-area-inset-bottom) / 5 * 3);
padding-bottom: calc(env(safe-area-inset-bottom) / 5 * 3);
}
/* ==================
字体大小
==================== */
@for $i from 20 through 50 {
.ss-font-#{$i} {
font-size: $i + rpx;
}
}
/* ==================
按钮
==================== */
.ss-reset-button {
padding: 0;
margin: 0;
font-size: inherit;
background-color: transparent;
color: inherit;
position: relative;
border: 0rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
box-sizing: border-box;
text-align: center;
text-decoration: none;
white-space: nowrap;
vertical-align: baseline;
transform: translate(0, 0);
}
.ss-reset-button.button-hover {
transform: translate(1px, 1px);
background: none;
}
.ss-reset-button::after {
border: none;
}

162
peach/scss/_var.scss Normal file
View File

@ -0,0 +1,162 @@
@import './mixins';
//颜色 渐变背景60%
$yellow: #ffc300; //ss-黄
$orange: #ff6000; //ss-橘
$red: #ff3000; //ss-红
$pink: #e03997;
$mauve: #b745cb;
$purple: #652abf; //rgba(101, 42, 191, 1); // ss-紫
$blue: #0081ff;
$cyan: #37c0fe;
$green: #2aae67; //ss-绿
$olive: #8dc63f;
$grey: #8799a3;
$brown: #a5673f;
$black: #484848; //ss-黑
$golden: #e9b461; //ss-金
$colors: ();
$colors: map-merge(
(
'yellow': $yellow,
'orange': $orange,
'red': $red,
'pink': $pink,
'mauve': $mauve,
'purple': $purple,
'violet': $purple,
'blue': $blue,
'cyan': $cyan,
'green': $green,
'olive': $olive,
'grey': $grey,
'brown': $brown,
'black': $black,
'golden': $golden,
),
$colors
);
//灰度
$bg-page: #f6f6f6;
$white: #ffffff;
$gray-f: #f8f9fa;
$gray-e: #eeeeee;
$gray-d: #dddddd;
$gray-c: #cccccc;
$gray-b: #bbbbbb;
$gray-a: #aaaaaa;
$dark-9: #999999;
$dark-8: #888888;
$dark-7: #777777;
$dark-6: #666666;
$dark-5: #555555;
$dark-4: #484848; //ss-黑
$dark-3: #333333;
$dark-2: #222222;
$dark-1: #111111;
$black: #000000;
$grays: ();
$grays: map-merge(
(
'white': $white,
'gray-f': $gray-f,
'gray-e': $gray-e,
'gray-d': $gray-d,
'gray-c': $gray-c,
'gray-b': $gray-b,
'gray-a': $gray-a,
'gray': $gray-a,
),
$grays
);
$darks: ();
$darks: map-merge(
(
'dark-9': $dark-9,
'dark-8': $dark-8,
'dark-7': $dark-7,
'dark-6': $dark-6,
'dark-5': $dark-5,
'dark-4': $dark-4,
'dark-3': $dark-3,
'dark-2': $dark-2,
'dark-1': $dark-1,
'black': $black,
),
$darks
);
// 边框
$border-width: 1rpx !default; // 边框大小
$border-color: $gray-d !default; // 边框颜色
// 圆角
$radius: 10rpx !default; // 默认圆角大小
$radius-lg: 40rpx !default; // 大圆角
$radius-sm: 6rpx !default; // 小圆角
$round-pill: 1000rpx !default; // 半圆
// 动画过渡
$transition-base: all 0.2s ease-in-out !default; // 默认过渡
$transition-base-out: all 0.04s ease-in-out !default; // 进场过渡
$transition-fade: opacity 0.15s linear !default; // 透明过渡
$transition-collapse: height 0.35s ease !default; // 收缩过渡
// 间距
$spacer: 20rpx !default;
$spacers: () !default;
$spacers: map-merge(
(
0: 0,
1: $spacer * 0.25,
2: $spacer * 0.5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3,
6: $spacer * 5,
),
$spacers
);
// 字形
$font-weight-lighter: lighter !default;
$font-weight-light: 300 !default;
$font-weight-normal: 400 !default;
$font-weight-bold: 700 !default;
$font-weight-bolder: 900 !default;
$fontsize: () !default;
$fontsize: map-merge(
(
xs: 20,
sm: 24,
df: 28,
lg: 32,
xl: 36,
xxl: 44,
sl: 80,
xsl: 120,
),
$fontsize
);
// 段落
$line-height-base: 1.5 !default;
$line-height-lg: 2 !default;
$line-height-sm: 1.25 !default;
// 图标
$iconsize: () !default;
$iconsize: map-merge(
(
xs: 0.5,
sm: 0.75,
df: 1,
lg: 1.25,
xl: 1.5,
xxl: 2,
sl: 6,
xsl: 10,
),
$iconsize
);

Binary file not shown.

File diff suppressed because one or more lines are too long

181
peach/scss/icon/_icon.scss Normal file
View File

@ -0,0 +1,181 @@
@font-face {
font-family: 'colorui'; /* Project id 2620914 */
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAA08AAsAAAAAIIAAAAzuAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACIUgqqHKM2ATYCJAOBJAtUAAQgBYR5B4MwGwIcs6JmclIAsv9LAT3W/EiCkXnK3Xny3Onomo8T7OwIi5b6OurgI7NQyMA0DecgDbMUXzZtybquth6v1ed4jzlbhWe8oZQGrbWZlVt/3xesuXQIRTwSkka/imlMNEIkda7mMvhA4790wiQPssgK0F3uNkCb2eyTq63TFabC13bqKnT/f9r2XRijHkYPBoxYQdioY200RxgzwE6Gn4NsYyX+qsD9VRvVQwAcs1sVVQBPpMFHVTiQ0SY0khn2Z8ycgyURv1jvJyjWdAPEHyCPBYW6EyErvLhvBgDU54DA7uCctjbtpwoHYQbOEsApsYk9Q2BggezBcZ+bo/+fmistkypBinEV7nXZqewmBwsHezD9/D5TnvCgwKyQciUAWeHIo6/ynRppC6ZrdCN/KHpRo/+MrZoHW6HrYDSjXzAweu/6SUCAN2ef1Ty/fTAGK8mlMeSwViRETblKImm0Hp1UQTJZa0G9Yhj8okHsxS/As/ft+UiSgYCgQ7AutDE508bX574s8v4P3VE/lPo82JkFQAMkYMowlxgUF7Bv2AGa8oiXfRpOYI4LXCTyWoFlG+zFKuqMab5PL8+X18tXf7FkeEnXczQarXBs0Sv6CQppD6S+92ewm2hT9tS1yjKWvNppv6d++Ar+Gf/X8z/lsbZxdLK1s1dSVdNxMFTWUeEFUVLR1dVTUCBBUloGZuYmVhILU0syUWhcBzJvvPJEcETWREBkQwRFjkRIptI4RLZETWRH1EX2RE+kBDEQaUFsRNrEjumAHsAcQE9ghqDPwNqjr2BGoO9gKqAfYKqgn2BqoF9g6qB/YBqg/xBpQhoRGZPGRLqkCZEeaRKj0QyYImgWTAY0ByYLmgeTAy2AyYMWgYGWgIG2wMxA22DmoDMwE9A1mBXoFkwCeoTIgv8GM/VoH+0swVlc5ok/igfKvyxXpsudskcG8ixSB8jocEkfJ6IThI+wkJDGpULiYQNCXq7Uqn0FpjTnzsr4aIpxIHfzRQpAVD23bETXqCa6tPK6zRhFqR+qD9RLM5Cjyzm3ZWSqD+e2dKdUpVStVqCcVVE4Kev1eHn5F5a7yUBScDhVqvfzsazL0czrlY407QMbxpfhPkHiWXd5Tgk771nuZqNCNp7T2HSoUDaYtDH1EEshIf2TcnB+gjWn++SFQt11rOufxDS/zcDpRUgt9wi7/3YS8vwnEddvEnT+OPpYYJjERIpR8cUD2Di5NDOC2FLH5/TJSLf4rpNHRzVwx2WWMzyRACnwdytZloIwScBjJbB0iE/ybmMSFHLKbupqsZvm2Jo8OtOPn5UgbpdKMEXLck6mEgz7zHC/lmW4fzUvh66me6Z9xvgBdcE+afyYIzMzWgiag8AdQ0IDoDpdaiSrlTUyp4h3GBgNxql/KnkE2VFmUsdEIGO3v7Pjm5GpNGKz45sX/021DR1Dig2386gn2PMeRtYS1JyPTq9ngOAToofLeXQ2uh6XsnhwHAmmyz9hlt3G1QBgO5xKYrkuJhDfzV+s8MKaPbsbvs9P0ef4Ib0SEG0A7y4DfAALepMfo8edgliTpS13fj94y+MnLUuH95qxPX/y4iVapVK+E9wyzSqmgZUAHsBKBg+ztxFDu11io9k6AXssDwBTw0Lq3GlhR8GuC0kFsuTJTinNTH8YzXDc+AzgdXYKmml5jZVLUHPqdjWWRkxiEwnQsdyvP/yzouBvFPmvQH9egEYtXLNe85Aew3+NUBlEyiJYZSZ9NfS3cMr4G7rhCJgml8yFcKlybogqj/VKxI3FkN8Znr5o8FZqpqbSuvOYpRnyJwt818FeEcfjp1LHbhsO7gAPfLt/+NIusKB3XzZc3XuQl9tzpbAJFehj3yOgf2S4t/XsDcfpY3bdAHW1GgGgR+2RxRV+p2DLZZVPBbjHONiPTjeQ5Gso+/LR2Wp2dhiqks+h7PtnQJmVFXRbi2N56tEZtx16JBumdNOeueP7W97VxpF9J0XS3NaDfHS2jQOpY6OBT9c/eh4jOl0xPPDU5ao3qG6qqjq3plKlrlFW17+h4oGjUqFWq3LVuarKgw1OE+VE87OhPE+9Kd1Ahs0BF+78MkxA44encRqfPowHj7ZCxQqOszhD4aDIkMCQTo8TC0xN7VffMHwX29/i/dF3OckiAtFOLbj7+64wnK6mllkgcP2QDiTajrvYmcUmbEsd1HXZtJVGXpWcXCXX/OyeJ1dVJWvkP2f0rY2kJl9GicWULN+MlJ1T4nyZmb1EKJ13fpMJH++JNWW19UjGwyUr9F0RlV6VvVKp7vJJM8+ZN+Z8NVLvOhZoroi+I2J4P+g/Di/GesJ2e5d/oufW1KvBif5du2yrUSTP2ZfDi3G9Csnih52StufbeuKMWS962doJGkXfi65X1hqdpyOuORnZ9cyiY0GNAjsHr2yQAEkOufEpROAEOjVMRIJABsGN6DtspT4lZQJ3be+VrMCJPSfpmCw0EDFka80QdCZsrK2sGb6QPxHpM088MOO0sat8ARBgqb1qeVVSUpVc/bNSdl6VpJb/zBevuH3Wl56z3Uz0Ukkkql4SlUriiy1uk83GAKbOMdnOqVJeZPgEPsno9cwQ1Un92sBX5i/qcnkSrV87OdFMz1hmHJVZy3zl2RF8fcNWA5tyw+sHsicRgdhlPECezfNccBelqdHRwnjtwoebyYLWjS4k/ahVlv334/VDlJPThW5PIExEwvbQp0/LEIGo8H/mdjTEvgjEOglNROL20P9yytCH9BDlTSW2sc5/MKcciQNMAWKkLB/0t1Y1PACR49Aco9z+mT9+eFo2fViG2bOKBYIk7sICiU9vxtPzOE4uaMIkb/emlgQk5H24X+ANMBxnw8lPUibmP1kaK4ZSLboDbIbrbHF+9tfPGtLKdEhHLoik55+9qjwh3p4yZS70bHlA6y1A+sFR1pSxz/5DQ5mmPVK8ElnT/FO71B/bHGBT3lQg17Nckr9qSGyoTxyeUmpuSJxicYbJxX6/WG+A2I82xsc7VQuqndiGlIByYlPl6C+QN7sFIkuXmE1RrGNtlGvypNsvXZpnFkL6z9z8PEm9fW0sEP3JlqmAOE7Z8ZgsWgBHNzrK8Bu2qlA0uFJ5RGcHAVYteO1XwceCX18jPX3QajWyPAlFSfJkZqVcUELJ8jS+RBLhm53V2Zk1jAQeDkh2jlVT3BpkGOmFZWEJjvvHXT67j1bdFQERyBUx27cTYq6FwjJnLXxPm8ZVKiqx8++3VY3z20d2Vb3AaydhdHQMBFi4C+NCHyISCzhrE7/Fis1v675sShv1TDZ5Lo02pf3pJXw9o/3SS9Kr9frTz9f5en/p5kti3vEqHLjkaUr2bKAD35zw2gEWSvOWpdNily6rwLo8URdQb1Fkh2QGBWWGZJtCsjJsZnB2sCkrGHCWZYrpZGYGZYUc4fb0HOWC8PXDl19av37D+pdeKigAN1FbN7AW7O2/+86asr7qHFUqIey25YQwTxe0143GawGM6Oru3XOYY2fPHBUxdpBCvz8CUYMZQRkzDCbAQfL4ibc5wMrfKJBnWi4lHxy7YUITzAntTjDAUw/TslY0hL1iP7SACb2KU05jZJKfwkSwXJAFOoulAQBg6dwTl4zTTjITdYo4+lAV+SPs2V4BYDTnQ6AzbZLUuhW+/T9WY5ZtGSCr4kXkcZ9yS1A5xe3EamQMA/CTWHldRm/AHf1YeOpFhwc+6FssOIY1QCrFveu+y4GTC1i+mUubpDDlv8+nhlXRTEWpo3wsQ6Jgrff322zCCv4v8jDHZenBNAxOvUKcqfrfciEwnpf9uxzZ4EHj3jTKBMDb8wH/d85p+B2WJZOhQC6hPAiCMSRIznCwCp8RtGBVKDhrgjfp5upB05yNYQdgwsdFoIZPQaj4EaSG72AV/idobf9CoZFhgrcX2LrBYCTzd04IJIMWwUokCsvQutxRmCOx5ncQzXJJjya/9AmoTigRE3+crb6AEqiei9TzaMocipCwEOca7w7yHEVFmIJiP2autkejUPYQvsLC6FhHgMSAJgRWYtSEgkohZ50kyh9sJP92d0BkJidRkZ+M+gSA6keIvRMmfGNK8AUpaT5Nal2bi0yxrYaE05mggnBuJAf5AxcqOUrf6ivMFyuUVbaN7JmFVOK3ayn2GMFK9Gt94uMZQElFzYKGlo6egZGJFVZZo2iG5XhBlGRF1XTDtGx2h9Pl9nh9tYYxqCzApe1YOzF9D13CWakbMRYwsOOe1gGdsKtyrKFLId7t6fsR97D7YZR6MInOzYVMOCmjtgdqlN4MKhtX8H7GAgkaGnJgaLkNPGxLravauoHqoc3rOEkZYuMqV2s/B5cTzqEhNHUzR6n1lzUUVqub0MWN7E0ANWqQpGInkCprhkgt34Z2JREu2pqw8jQuymbAZ5U7KNzRrbQ7XS/M99AwAA==')
format('woff2');
/* #ifdef MP-ALIPAY */
src: url('//at.alicdn.com/t/font_2620914_57y9q5zpbel.woff?t=1624238023908') format('woff'),
url('//at.alicdn.com/t/font_2620914_57y9q5zpbel.ttf?t=1624238023908') format('truetype');
/* #endif */
}
[class*='_icon-'] {
font-family: 'colorui' !important;
display: inline-block;
}
@font-face {
font-family: 'ui-num';
src: url('data:application/x-font-ttf;base64,AAEAAAAKAIAAAwAgT1MvMla+dCkAAACsAAAAYGNtYXAQUxhKAAABDAAAAVJnbHlmS86JUQAAAmAAAAUUaGVhZA7I1xIAAAd0AAAANmhoZWEFqgF3AAAHrAAAACRobXR4BycBzgAAB9AAAAAibG9jYQZmB5wAAAf0AAAAHG1heHAAEQBDAAAIEAAAACBuYW1lGVKlzAAACDAAAAGtcG9zdADDAJYAAAngAAAAPAAEAewBkAAFAAACmQLMAAAAjwKZAswAAAHrADMBCQAAAgAGAwAAAAAAAAAAAAEQAAAAAAAAAAAAAABQZkVkAMAALAA5Ayz/LABcAywA1AAAAAEAAAAAAxgAAAAAACAAAQAAAAMAAAADAAAAHAABAAAAAABMAAMAAQAAABwABAAwAAAACAAIAAIAAAAsAC4AOf//AAAALAAuADD////V/9T/0wABAAAAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAgADBAUGBwgJCgsMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAiAAABMgKqAAMABwAANxEhESczESMiARDuzMwAAqr9ViICZgAAAAEAUP9hAMcAdwADAAAXNSMRx3c9tP7qAAEAUAAAAM0AfQADAAA3NSMVzX0AfX0AAAIAPv/6AeMC3wASACQAACUDJicmJwYHBgcRFhcWFzY3NjcHFAcGByYnJjURNDc2NxYXFhUB7wwCPDxZWTs7AwM7O1lZPDwOdB0bMzIbHBwbMjMbHdABPmM3NgEBNjdj/r1jNzYBATY3aAI2ICABASAgNgE9Nx8gAQEgHzcAAAAAAQB1AAABbALZAAYAACURIwcVNxEBbGmOjgAC2Xt0ff2ZAAAAAQBBAAAB6ALfAB4AACU1IRM2NzY1JicmJwYHBgczNjc2FxYXFhUUBwYHARUB6P7X5SIREQE5OV9fOjkCaAIfHywzGxwJCRX+6ABdARgoJCIvYDY2AQE3N189GhsBAR4dMxoYFhn+q10AAAAAAQAr//gB6QLgADUAACUmJyYnNjc2NSYnJicGBwYHMzY3NjMyFxYXFAcGByMVMxYXFhUGBwYjIicmJyMWFxY3Mjc2NwH1DRocLysYGAI5O15ZOzwGaQQcHTAuHh8BGxw4ERE+Hh4BISE0LyIhBWgGQD9aXkA/DtI+KioVFCcmOl03NwEBNDNeMRscHRw4Mh0eAVsBHyA4Oh8gGxk7azEyATU1bwABACQAAAH+AtkADgAAJTUjNSMVIwEjARUhFTM1Af5OZbUBAHH+/wEnZW5hqqoCCv32YW5uAAAAAAEAQf/5AewC2QA3AAAlJicmJyYnJiMiBwYHNSE1IREzNjc2NxYXFgcWBwYHBgcGIyInJicjFhcWFxYXFhc2NzY3Njc2NwH2Cg0MKBcgISsoHx8TASv+d18IGhosPRgWAQEHBhcOExMYMRkaBmgCDAwdFygoNDYmJRknDAwK+i4yMioXDAwLCxTBXf5yGxMSAQErKkIlIiIXDwcHGxkxJiQjHhgQDwEBDxEYKDAvQQAAAgA5//oB6ALZABcAKAAAJSYnJiciBwYHEyMDBgcGFRYXFhc2NzY3BwYHBgcmJyYnNjc2MxYXFhcB9A42NlERERAPnW+mGQ4QAjs7YGE6Og5rCh4eMzIdHgEBHh0yNR0eCd1cOTgBAgMGATn+ri8sLCxmOjkBATs8awJAISIBASIhOzshIgEjIzIAAAABAEEAAAHzAtkACAAAATUhFTM1MwMzAfP+TmTe9XECfF3Qc/2EAAAAAwAw//oB8gLfACAAMQBCAAAlJicmJzY3NjcmJyYnBgcGBxYXFhcGBwYHFhcWFzY3NjcnBgcGByYnJic2NzY3FhcWFwMGBwYHJicmJzY3NjcWFxYXAf4NHh4oJRkZAQI7PFxbOzwCARoZJCceHgECQD5gYT9ADmwLIiA1NCEhAQEhITQ1ICILDAoeHTEwHR0BAR0dMDEdHgrTOyoqFxUnJzpcNjYBATY2XDonJxUXKipAZTc3AQE3N2oCOSIiAQEiIjQ0IiMBASMiLwFKPh4eAQEeHjEyHh8BAR8eJQAAAAACADkAAAHoAt8AFwAoAAABJicmJwYHBgcWFxYXMjc2NwMzEzY3NjcHBgcGIyYnJjU2NzY3FhcWFwH0Djo7YWA6OwICNjZRERERDpxvphkODwxrCh4eMzQdHQEeHTIzHh4KAhJaOTkBATs8ZmE5OAEDAgb+xwFSLywsOQNHISIBIyM3OyIhAQEhIi8AAAEAAAABAADHiynwXw889QALBAAAAAAA1sTJ5wAAAADWxMntACL/YQH+AuAAAAAIAAIAAAAAAAAAAQAAAyz/LABcAiIAIgAkAf4AAQAAAAAAAAAAAAAAAAAAAAQBdgAiARcAUAEdAFACIgA+AHUAQQArACQAQQA5AEEAMAA5AAAAAAAUACAALABsAH4AtAEGASIBegHAAdQCRAKKAAEAAAANAEMAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAJYAAQAAAAAAAQAKAAAAAQAAAAAAAgAGAAoAAQAAAAAAAwAbABAAAQAAAAAABAAKACsAAQAAAAAABQAeADUAAQAAAAAABgAKAFMAAwABBAkAAQAUAF0AAwABBAkAAgAMAHEAAwABBAkAAwA2AH0AAwABBAkABAAUALMAAwABBAkABQA8AMcAAwABBAkABgAUAQNmb250ZWRpdG9yTWVkaXVtRm9udEVkaXRvciAxLjAgOiBmb250ZWRpdG9yZm9udGVkaXRvclZlcnNpb24gMS4wOyBGb250RWRpdG9yICh2MS4wKWZvbnRlZGl0b3IAZgBvAG4AdABlAGQAaQB0AG8AcgBNAGUAZABpAHUAbQBGAG8AbgB0AEUAZABpAHQAbwByACAAMQAuADAAIAA6ACAAZgBvAG4AdABlAGQAaQB0AG8AcgBmAG8AbgB0AGUAZABpAHQAbwByAFYAZQByAHMAaQBvAG4AIAAxAC4AMAA7ACAARgBvAG4AdABFAGQAaQB0AG8AcgAgACgAdgAxAC4AMAApAGYAbwBuAHQAZQBkAGkAdABvAHIAAAAAAgAAAAAAAAAyAAAAAAAAAAAAAAAAAAAAAAAAAAAADQANAAAADwARABMAFAAVABYAFwAYABkAGgAbABw=')
format('woff2');
font-weight: normal;
font-style: normal;
}
._icon-checkbox:before {
content: '\e713';
}
._icon-box:before {
content: '\e714';
}
._icon-checkbox-o:before {
content: '\e715';
}
._icon-round:before {
content: '\e716';
}
._icon-home-o:before {
content: '\e70a';
}
._icon-home:before {
content: '\e70d';
}
._icon-edit:before {
content: '\e649';
}
._icon-close:before {
content: '\e6ed';
}
._icon-check-round:before {
content: '\e6f1';
}
._icon-check-round-o:before {
content: '\e6f2';
}
._icon-close-round:before {
content: '\e6f3';
}
._icon-close-round-o:before {
content: '\e6f4';
}
._icon-waiting:before {
content: '\e6f8';
}
._icon-waiting-o:before {
content: '\e6f9';
}
._icon-warn:before {
content: '\e662';
}
._icon-warn-o:before {
content: '\e675';
}
._icon-more:before {
content: '\e688';
}
._icon-delete:before {
content: '\e707';
}
._icon-delete-o:before {
content: '\e709';
}
._icon-add-round:before {
content: '\e717';
}
._icon-add-round-o:before {
content: '\e718';
}
._icon-add:before {
content: '\e6e4';
}
._icon-info:before {
content: '\e6ef';
}
._icon-info-o:before {
content: '\e705';
}
._icon-move:before {
content: '\e768';
}
._icon-title:before {
content: '\e82f';
}
._icon-titles:before {
content: '\e745';
}
._icon-loading:before {
content: '\e746';
}
._icon-copy-o:before {
content: '\e7bc';
}
._icon-copy:before {
content: '\e85c';
}
._icon-loader:before {
content: '\e76d';
}
._icon-search:before {
content: '\e782';
}
._icon-back:before {
content: '\e600';
}
._icon-forward:before {
content: '\e601';
}
._icon-arrow:before {
content: '\e608';
}
._icon-drop-down:before {
content: '\e61c';
}
._icon-drop-up:before {
content: '\e61d';
}
._icon-check:before {
content: '\e69f';
}
._icon-move-round:before {
content: '\e602';
}
._icon-move-round-o:before {
content: '\e603';
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,43 @@
@import './icon'; //核心图标库
@import './coloricon'; //扩展图标库
@import './sheepicon';
.icon-spin {
animation: icon-spin 2s infinite linear;
}
.icon-pulse {
animation: icon-spin 1s infinite steps(8);
}
@keyframes icon-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.icon-90 {
transform: rotate(90deg);
}
.icon-180 {
transform: rotate(180deg);
}
.icon-270 {
transform: rotate(270deg);
}
.icon-x {
transform: scale(-1, 1);
}
.icon-y {
transform: scale(1, -1);
}
.icon-fw {
width: calc(18em / 14);
text-align: center;
}
@each $class, $value in $iconsize {
.icon-#{$class} {
transform: scale(#{$value});
}
}

28
peach/scss/index.scss Normal file
View File

@ -0,0 +1,28 @@
@import './tools';
@import './ui';
@font-face {
font-family: OPPOSANS;
src: url('~@/peach/scss/font/OPPOSANS-M-subfont.ttf');
}
.font-OPPOSANS {
font-family: OPPOSANS;
}
page {
-webkit-overflow-scrolling: touch; // 解决 ios 滑动不流畅
height: 100%;
width: 100%;
word-break: break-all;
white-space: normal;
background-color: $bg-page;
color: $dark-3;
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
display: none;
}

View File

@ -0,0 +1,204 @@
/* ==================
背景
==================== */
/* -- 基础色 -- */
@each $color, $value in map-merge($colors, $darks) {
.bg-#{$color} {
background-color: $value !important;
@if $color == 'yellow' {
color: #333333 !important;
} @else {
color: #ffffff !important;
}
}
}
/* -- 浅色 -- */
@each $color, $value in $colors {
.bg-#{$color}-light {
background-image: linear-gradient(45deg, white, mix(white, $value, 85%)) !important;
color: $value !important;
}
.bg-#{$color}-thin {
background-color: rgba($value, var(--ui-BG-opacity)) !important;
color: $value !important;
}
}
/* -- 渐变色 -- */
@each $color, $value in $colors {
@each $colorsub, $valuesub in $colors {
@if $color != $colorsub {
.bg-#{$color}-#{$colorsub} {
// background-color: $value !important;
background-image: linear-gradient(130deg, $value, $valuesub) !important;
color: #ffffff !important;
}
}
}
}
.bg-yellow-gradient {
background-image: linear-gradient(45deg, #f5fe00, #ff6600) !important;
color: $dark-3 !important;
}
.bg-orange-gradient {
background-image: linear-gradient(90deg, #ff6000, #fe832a) !important;
color: $white !important;
}
.bg-red-gradient {
background-image: linear-gradient(45deg, #f33a41, #ed0586) !important;
color: $white !important;
}
.bg-pink-gradient {
background-image: linear-gradient(45deg, #fea894, #ff1047) !important;
color: $white !important;
}
.bg-mauve-gradient {
background-image: linear-gradient(45deg, #c01f95, #7115cc) !important;
color: $white !important;
}
.bg-purple-gradient {
background-image: linear-gradient(45deg, #9829ea, #5908fb) !important;
color: $white !important;
}
.bg-blue-gradient {
background-image: linear-gradient(45deg, #00b8f9, #0166eb) !important;
color: $white !important;
}
.bg-cyan-gradient {
background-image: linear-gradient(45deg, #06edfe, #48b2fe) !important;
color: $white !important;
}
.bg-green-gradient {
background-image: linear-gradient(45deg, #3ab54a, #8cc63f) !important;
color: $white !important;
}
.bg-olive-gradient {
background-image: linear-gradient(45deg, #90e630, #39d266) !important;
color: $white !important;
}
.bg-grey-gradient {
background-image: linear-gradient(45deg, #9aadb9, #354855) !important;
color: $white !important;
}
.bg-brown-gradient {
background-image: linear-gradient(45deg, #ca6f2e, #cb1413) !important;
color: $white !important;
}
@each $color, $value in $grays {
.bg-#{$color} {
background-color: $value !important;
color: #333333 !important;
}
}
.bg-square {
@include bg-square;
}
.bg-none {
background: transparent !important;
color: inherit !important;
}
[class*='bg-mask'] {
position: relative;
//background: transparent !important;
color: #ffffff !important;
> view,
> text {
position: relative;
z-index: 1;
color: #ffffff;
}
&::before {
content: '';
border-radius: inherit;
width: 100%;
height: 100%;
@include position-center;
background-color: rgba(0, 0, 0, 0.4);
z-index: 0;
}
@at-root .bg-mask-80::before {
background: rgba(0, 0, 0, 0.8) !important;
}
@at-root .bg-mask-50::before {
background: rgba(0, 0, 0, 0.5) !important;
}
@at-root .bg-mask-20::before {
background: rgba(0, 0, 0, 0.2) !important;
}
@at-root .bg-mask-top::before {
background-color: rgba(0, 0, 0, 0);
background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.618), rgba(0, 0, 0, 0.01));
}
@at-root .bg-white-top {
background-color: rgba(0, 0, 0, 0);
background-image: linear-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.3));
}
@at-root .bg-mask-bottom::before {
background-color: rgba(0, 0, 0, 0);
background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.618), rgba(0, 0, 0, 1));
}
}
.bg-img {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
[class*='bg-blur'] {
position: relative;
> view,
> text {
position: relative;
z-index: 1;
}
&::before {
content: '';
width: 100%;
height: 100%;
@include position-center;
border-radius: inherit;
transform-origin: 0 0;
pointer-events: none;
box-sizing: border-box;
}
}
@supports (-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px)) {
.bg-blur::before {
@include blur;
background-color: var(--ui-Blur-1);
}
.bg-blur-1::before {
@include blur;
background-color: var(--ui-Blur-2);
}
.bg-blur-2::before {
@include blur;
background-color: var(--ui-Blur-3);
}
}
@supports not (backdrop-filter: blur(5px)) {
.bg-blur {
color: var(--ui-TC);
&::before {
background-color: var(--ui-BG);
}
}
.bg-blur-1 {
color: var(--ui-TC);
&::before {
background-color: var(--ui-BG-1);
}
}
.bg-blur-2 {
color: var(--ui-TC);
&::before {
background-color: var(--ui-BG-2);
}
}
}

View File

@ -0,0 +1,66 @@
/* ==================
弹性布局
==================== */
.flex {
display: flex !important;
&-sub {
flex: 1 !important;
}
&-twice {
flex: 2 !important;
}
&-treble {
flex: 3 !important;
}
&-column {
flex-direction: column !important;
}
&-row {
flex-direction: row !important;
}
&-column-reverse {
flex-direction: column-reverse !important;
}
&-row-reverse {
flex-direction: row-reverse !important;
}
&-wrap {
flex-wrap: wrap !important;
}
&-center {
@include flex-center;
}
&-bar {
@include flex-bar;
}
}
.basis {
@each $class, $value in (xs: 20%, sm: 40%, df: 50%, lg: 60%, xl: 80%) {
&-#{$class} {
flex-basis: $value !important;
}
}
}
.align {
@each $class, $value in (start: flex-start, end: flex-end, center: center, stretch: stretch, baseline: baseline) {
&-#{$class} {
align-items: $value !important;
}
}
}
.self {
@each $class, $value in (start: flex-start, end: flex-end, center: center, stretch: stretch, baseline: baseline) {
&-#{$class} {
align-self: $value !important;
}
}
}
.justify {
@each $class,
$value in (start: flex-start, end: flex-end, center: center, between: space-between, around: space-around)
{
&-#{$class} {
justify-content: $value !important;
}
}
}

103
peach/scss/style/_grid.scss Normal file
View File

@ -0,0 +1,103 @@
/* ==================
栅栏
==================== */
@use 'sass:math';
@mixin make_col($screen) {
@for $i from 1 through 12 {
.ui-col-#{$screen}-#{$i} {
width: calc(100% / 12 * #{$i});
}
.ui-cols-#{$screen}-#{$i} .ui-item {
width: calc(100% / #{$i});
}
}
}
.ui-container {
box-sizing: border-box;
margin-left: auto;
margin-right: auto;
padding-left: 30rpx;
padding-right: 30rpx;
width: 100%;
max-width: 1440px;
&-fluid {
max-width: 100%;
padding-left: 0;
padding-right: 0;
}
}
.ui-grid {
display: flex;
flex-wrap: wrap;
&.multi-column {
display: block;
column-count: 2;
column-width: 0px;
column-gap: 0px;
> .ui-item {
break-inside: avoid;
padding: 0.001em;
}
}
&.grid-square {
overflow: hidden;
> .ui-item {
margin-right: 20rpx;
margin-bottom: 20rpx;
position: relative;
overflow: hidden;
}
@for $i from 1 through 12 {
&.ui-cols-#{$i} > .ui-item {
padding-bottom: calc((100% - #{20rpx * ($i - 1)}) / #{$i});
height: 0;
width: calc((100% - #{20rpx * ($i - 1)}) / #{$i});
}
}
@for $i from 1 through 12 {
&.ui-cols-#{$i} > .ui-item:nth-child(#{$i}n) {
margin-right: 0;
}
}
}
}
@for $i from 1 through 12 {
.ui-cols-#{$i} .ui-item {
width: calc(100% / #{$i});
}
}
@for $i from 1 through 12 {
.ui-col-#{$i} {
width: calc(100% / 12 * #{$i});
}
}
// 小屏
@media screen and (min-width: 0px) {
@include make_col('xs');
}
// 小屏
@media screen and (min-width: 320px) {
@include make_col('sm');
}
// 中屏
@media screen and (min-width: 768px) {
@include make_col('md');
}
// 普通屏
@media screen and (min-width: 1025px) {
@include make_col('lg');
}
// 大屏
@media screen and (min-width: 1440px) {
@include make_col('xl');
}
// 超大屏
@media screen and (min-width: 1920px) {
@include make_col('xxl');
}

106
peach/scss/style/_text.scss Normal file
View File

@ -0,0 +1,106 @@
@use 'sass:math';
.font-0 {
font-size: 24rpx;
--textSize: -4rpx;
}
.font-1 {
font-size: 28rpx;
--textSize: 0rpx;
}
.font-2 {
font-size: 32rpx;
--textSize: 4rpx;
}
.font-3 {
font-size: 36rpx;
--textSize: 8rpx;
}
.font-4 {
font-size: 40rpx;
--textSize: 12rpx;
}
/**
* ??? var(--textSize) 能取到值吗
*/
.text {
@each $class, $value in $fontsize {
&-#{$class},
&-#{math.div($value ,2)} {
font-size: calc(#{$value}rpx + var(--textSize)) !important;
}
}
&-cut {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@at-root [class*='text-linecut'] {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
}
@for $i from 2 through 10 {
&-linecut-#{$i} {
-webkit-line-clamp: #{$i};
}
}
&-justify {
text-align: justify;
}
&-justify-line {
text-align: justify;
line-height: 0.5em;
margin-top: 0.5em;
&::after {
content: '.';
display: inline-block;
width: 100%;
}
}
&-Abc {
text-transform: Capitalize !important;
}
&-ABC {
text-transform: Uppercase !important;
}
&-abc {
text-transform: Lowercase !important;
}
&-del,
&-line {
text-decoration: line-through !important;
}
&-bottomline {
text-decoration: underline !important;
}
&-italic {
font-style: italic !important;
}
&-style-none {
text-decoration: none !important;
}
&-break {
word-break: break-word !important;
overflow-wrap: break-word !important;
}
&-reset {
color: inherit !important;
}
&-price::before {
content: '¥';
font-size: 80%;
margin-right: 4rpx;
}
&-hide {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
}

View File

@ -0,0 +1,39 @@
// 核心主题样式文件
@mixin theme-dark {
// 背景色
--ui-BG: #393939;
--ui-BG-1: #333333;
--ui-BG-2: #2c2c2c;
--ui-BG-3: #292929;
--ui-BG-4: #222222;
// 文本色
--ui-TC: #ffffff;
--ui-TC-1: #d4d4d4;
--ui-TC-2: #919191;
--ui-TC-3: #6a6a6a;
--ui-TC-4: #474747;
// 模糊
--ui-Blur: rgba(38, 38, 38, 0.98);
--ui-Blur-1: rgba(38, 38, 38, 0.75);
--ui-Blur-2: rgba(38, 38, 38, 0.25);
--ui-Blur-3: rgba(38, 38, 38, 0.05);
// 边框
--ui-Border: rgba(119, 119, 119, 0.25);
--ui-Outline: rgba(255, 255, 255, 0.1);
--ui-Line: rgba(119, 119, 119, 0.25);
// 透明与阴影
--ui-Shadow: 0 0.5em 1em rgba(0, 0, 0, 0.45);
--ui-Shadow-sm: 0 0.125em 0.25em rgba(0, 0, 0, 0.475);
--ui-Shadow-lg: 0 1em 3em rgba(0, 0, 0, 0.475);
--ui-Shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.475);
--ui-Shadow-opacity: 0.55;
--ui-Shadow-opacity-sm: 0.175;
--ui-Shadow-opacity-lg: 0.75;
--ui-BG-opacity: 0.1;
}

View File

@ -0,0 +1,39 @@
// 核心主题样式文件
@mixin theme-light {
// 背景色
--ui-BG: #ffffff;
--ui-BG-1: #f6f6f6;
--ui-BG-2: #f1f1f1;
--ui-BG-3: #e8e8e8;
--ui-BG-4: #e0e0e0;
// 文本色
--ui-TC: #303030;
--ui-TC-1: #525252;
--ui-TC-2: #777777;
--ui-TC-3: #9e9e9e;
--ui-TC-4: #c6c6c6;
// 模糊
--ui-Blur: rgba(255, 255, 255, 0.98);
--ui-Blur-1: rgba(255, 255, 255, 0.75);
--ui-Blur-2: rgba(255, 255, 255, 0.25);
--ui-Blur-3: rgba(255, 255, 255, 0.05);
// 边框
--ui-Border: rgba(119, 119, 119, 0.25);
--ui-Outline: rgba(0, 0, 0, 0.1);
--ui-Line: rgba(119, 119, 119, 0.25);
// 透明与阴影
--ui-Shadow: 0 0.5em 1em rgba(0, 0, 0, 0.15);
--ui-Shadow-sm: 0 0.125em 0.25em rgba(0, 0, 0, 0.075);
--ui-Shadow-lg: 0 1em 3em rgba(0, 0, 0, 0.175);
--ui-Shadow-inset: inset 0 0.1em 0.2em rgba(0, 0, 0, 0.075);
--ui-Shadow-opacity: 0.45;
--ui-Shadow-opacity-sm: 0.075;
--ui-Shadow-opacity-lg: 0.65;
--ui-BG-opacity: 0.1;
}

View File

@ -0,0 +1,37 @@
@import './light';
@import './dark';
.theme-light {
@include theme-light;
}
.theme-dark {
@include theme-dark;
}
.theme-auto {
@include theme-light;
}
@media (prefers-color-scheme: dark) {
.theme-auto {
@include theme-dark;
}
}
@each $color, $value in $colors {
.main-#{$color} {
--ui-BG-Main: #{$value};
--ui-BG-Main-tag: #{rgba($value, 0.05)};
--ui-BG-Main-gradient: #{rgba($value, 0.6)};
--ui-BG-Main-light: #{rgba($value, 0.2)};
--ui-BG-Main-opacity-1: #{rgba($value, 0.1)};
--ui-BG-Main-opacity-4: #{rgba($value, 0.4)};
--ui-Main-box-shadow: 0 0.2em 0.5em #{rgba($value, var(--ui-Shadow-opacity))};
--ui-BG-Main-1: #{mix(rgba(255, 255, 255, 0.7), desaturate($value, 20%), 10%)};
--ui-BG-Main-2: #{mix(rgba(255, 255, 255, 0.6), desaturate($value, 40%), 20%)};
--ui-BG-Main-3: #{mix(rgba(119, 119, 119, 0.2), desaturate($value, 40%), 40%)};
--ui-BG-Main-4: #{mix(rgba(119, 119, 119, 0.1), desaturate($value, 40%), 60%)};
--ui-BG-Main-TC: #ffffff !important;
}
}

9
peach/scss/ui.scss Normal file
View File

@ -0,0 +1,9 @@
@import './theme/_style';
@import './main';
@import './style/background';
@import './style/flex.scss';
@import './style/grid.scss';
@import './style/text.scss';
@import './icon/style'; //图标
@import './style/text';

181
peach/store/app.js Normal file
View File

@ -0,0 +1,181 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import $platform from '@/peach/platform'
import $router from '@/peach/router'
import user from './user'
import sys from './sys'
const useAppStore = defineStore(
'app',
() => {
/**
* @description 应用信息
* @param string name 应用名称
* @param string logo 应用logo
* @param string version 应用版本
* @param string copyright 版权信息
* @param string copyrightTime 版权时间
* @param string cdnurl 静态资源域名
* @param string filesystem 文件系统
*/
const info = ref({
name: '',
logo: '',
version: '',
copyright: '',
copytime: '',
cdnurl: '',
filesystem: '',
})
/**
* @description 平台信息
* @param Array share.methods 分享方式
* @param Object share.forwardInfo 转发信息
* @param Object share.posterInfo 海报信息
* @param string share.linkAddress 分享链接地址
* @param number bindMobile 绑定手机号提醒 0: 提醒 1: 不提醒
*/
const platform = ref({
share: {
methods: [],
forwardInfo: {},
posterInfo: {},
linkAddress: '',
},
bindMobile: 0,
})
const chat = ref({})
/**
* @description 模板信息
* @param Object basic 基础模板
* @param Object tabbar 底部导航模板
*/
const template = ref({
basic: {
tabbar: {
items: [
{
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-002.png',
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-001.png',
text: '首页',
url: '/pages/index/index',
},
{
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-002.png',
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-001.png',
text: '分类',
url: '/pages/index/category?id=3',
},
{
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-002.png',
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-001.png',
text: '购物车',
url: '/pages/index/icons',
},
{
activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-002.png',
iconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-001.png',
text: '我的',
url: '/pages/index/user',
},
],
style: {
activeColor: '#fc4141',
bgColor: '#fff',
bgType: 'color',
color: '#282828',
},
theme: 'red',
},
},
})
// 全局分享信息
const shareInfo = ref({})
// 小程序发货信息管理 0: 没有 1
const hasWechatTradeManaged = ref(0)
/**
* @author Ankkaya
* @description 小程序初始化
* @param {Type} -
* @returns {Type}
*/
async function init() {
// 检查网络
const networkStatus = await $platform.checkNetwork()
if (!networkStatus) {
$router.error('NetworkError')
}
if (true) {
this.info = {
name: '🍑商城',
logo: 'https://static.iocoder.cn/ruoyi-vue-pro-logo.png',
version: '1.0.0',
copyright: '全部开源,个人与企业可 100% 免费使用',
copytime: 'Copyright© 2018-2024',
cdnurl: 'https://file.sheepjs.com', // 云存储域名
filesystem: 'qcloud', // 云存储平台
}
this.platform = {
share: {
methods: ['poster', 'link'],
linkAddress: 'https://shopro.sheepjs.com/#/',
posterInfo: {
user_bg: '/static/img/shop/config/user-poster-bg.png',
goods_bg: '/static/img/shop/config/goods-poster-bg.png',
groupon_bg: '/static/img/shop/config/groupon-poster-bg.png',
},
},
bind_mobile: 0,
}
this.chat = {
chat_domain: 'https://api.shopro.sheepjs.com/chat',
room_id: 'admin',
}
this.has_wechat_trade_managed = 0
// 加载主题
const sysStore = sys()
sysStore.setTheme()
// 模拟用户登录
const userStore = user()
if (userStore.isLogin) {
userStore.loginAfter()
}
return Promise.resolve(true)
} else {
$router.error('InitError', res.msg || '加载失败')
}
}
return {
info,
platform,
chat,
template,
shareInfo,
hasWechatTradeManaged,
init,
}
},
{
persist: {
enabled: true,
strategies: [
{
key: 'app-store',
},
],
},
}
)
export default useAppStore

19
peach/store/index.js Normal file
View File

@ -0,0 +1,19 @@
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist-uni'
// 自动注入所有pinia模块
const files = import.meta.globEager('./*.js')
const modules = {}
Object.keys(files).forEach((key) => {
modules[key.replace(/(.*\/)*([^.]+).*/gi, '$2')] = files[key].default
})
export const setupPinia = (app) => {
const pinia = createPinia()
pinia.use(piniaPersist)
app.use(pinia)
}
export default (name) => {
return modules[name]()
}

40
peach/store/modal.js Normal file
View File

@ -0,0 +1,40 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
const useModalStore = defineStore(
'modal',
() => {
// 授权弹窗 accountLoginsmsLoginresetPasswordchangeMobilechangePasswordchangeUsername
const auth = ref('')
const share = ref(false)
const menu = ref(false)
const advHistory = ref([])
const lastTimer = ref({
smsLogin: 0,
changeMobile: 0,
resetPassword: 0,
changePassword: 0,
})
return {
auth,
share,
menu,
advHistory,
lastTimer,
}
},
{
persist: {
enabled: true,
strategies: [
{
key: 'modal-store',
paths: ['lastTimer', 'advHistory'],
},
],
},
}
)
export default useModalStore

36
peach/store/sys.js Normal file
View File

@ -0,0 +1,36 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
const useSysStore = defineStore(
'sys',
() => {
const theme = ref('')
const mode = ref('light')
const modeAuto = ref(false)
const fontSize = ref(1)
function setTheme(stheme = '') {
if (theme === '') {
theme.value = 'orange'
} else {
theme.value = stheme
}
}
return {
theme,
mode,
modeAuto,
fontSize,
setTheme,
}
},
{
persist: {
enabled: true,
strategies: [{ key: 'sys-store' }],
},
}
)
export default useSysStore

30
peach/store/user.js Normal file
View File

@ -0,0 +1,30 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { isEmpty, cloneDeep, clone } from 'lodash'
const useUserStore = defineStore('user', () => {
const userInfo = ref()
const isLogin = ref(!!uni.getStorageSync('token'))
function setToken(accessToken, refreshToken) {
if (token === '') {
isLogin.value = false
uni.removeStorageSync('token')
uni.removeStorageSync('refresh-token')
} else {
isLogin.value = true
uni.setStorageSync('token', token)
uni.setStorageSync('refresh-token', refreshToken)
// 成功后处理
}
return isLogin.value
}
return {
userInfo,
isLogin,
setToken,
}
})
export default useUserStore

View File

@ -0,0 +1,209 @@
<template>
<view class="ui-fixed">
<view
class="ui-fixed-box"
:id="`fixed-${uuid}`"
:class="[{ fixed: state.fixed }]"
:style="[
{
left: sticky ? 'auto' : '0px',
top: state.fixed && !bottom ? (noNav ? val : val + sys_navBar) + 'px' : 'auto',
bottom: insetHeight,
zIndex: index + sheep.$zIndex.navbar,
},
!alway ? { opacity: state.opacityVal } : '',
]"
>
<view class="ui-fixed-content" @tap="toTop" :style="[{ zIndex: index + sheep.$zIndex.navbar }]">
<slot></slot>
<view
v-if="safeAreaInsets.bottom && bottom && isInset"
class="inset-bottom"
:style="[{ height: safeAreaInsets.bottom + 'px' }]"
></view>
</view>
<view class="ui-fixed-bottom" :class="[bg]" v-if="bottom"></view>
<view
class="ui-fixed-bg"
:class="[ui, bg]"
:style="[
{ zIndex: index + sheep.$zIndex.navbar - 1 },
bgStyles,
opacity ? { opacity: state.opacityVal } : '',
]"
></view>
</view>
<view
class="skeleton"
:style="[{ height: state.content.height + 'px', width: width + 'px' }]"
v-if="sticky ? state.fixed : placeholder && state.fixed"
></view>
</view>
</template>
<script setup>
import { onPageScroll } from '@dcloudio/uni-app'
import { getCurrentInstance, unref, onMounted, reactive, nextTick, computed } from 'vue'
import peach from '@/peach'
const { safeAreaInsets } = sheep.$platform.device
const vm = getCurrentInstance()
const uuid = peach.$helper.guid()
const sys_navBar = peach.$platform.navBar
const state = reactive({
content: {},
fixed: true,
scrollTop: 0,
opacityVal: 0,
})
const insetHeight = computed(() => {
if (state.fixed && props.bottom) {
if (props.isInset) {
return props.val + 'px'
} else {
return props.val + safeAreaInsets.bottom + 'px'
}
} else {
return 'auto'
}
})
const props = defineProps({
noNav: {
type: Boolean,
default: false,
},
bottom: {
type: Boolean,
default: false,
},
bg: {
type: String,
default: '',
},
bgStyles: {
type: Object,
default() {},
},
val: {
type: Number,
default: 0,
},
width: {
type: [String, Number],
default: 0,
},
alway: {
type: Boolean,
default: true,
},
opacity: {
type: Boolean,
default: false,
},
index: {
type: [Number, String],
default: 0,
},
placeholder: {
type: [Boolean],
default: false,
},
sticky: {
type: [Boolean],
default: false,
},
noFixed: {
type: Boolean,
default: false,
},
ui: {
type: String,
default: '',
},
clickTo: {
type: Boolean,
default: false,
},
//
isInset: {
type: Boolean,
default: true,
},
})
state.fixed = !unref(props.sticky)
onPageScroll((e) => {
let top = e.scrollTop
state.scrollTop = top
state.opacityVal = top > peach.$platform.navbar ? 1 : top * 0.01
})
onMounted(() => {
nextTick(() => {
computedQuery()
})
})
const computedQuery = () => {
uni.createSelectorQuery()
.in(vm)
.select(`#fixed-${uuid}`)
.boundingClientRect((data) => {
if (data != null) {
state.content = data
if (unref(props.sticky)) {
setFixed(state.scrollTop)
}
}
})
.exec()
}
const setFixed = (value) => {
if (unref(props.bottom)) {
state.fixed =
value >=
state.content.bottom - sheep.$platform.device.windowHeight + state.content.height + unref(props.val)
} else {
state.fixed =
value >=
state.content.top - (unref(props.noNav) ? unref(props.val) : unref(props.val) + sheep.$platform.navbar)
}
}
const toTop = () => {
if (props.hasToTop) {
uni.pageScrollTo({
scrollTop: state.content.top,
duration: 100,
})
}
}
</script>
<style lang="scss">
.ui-fixed {
.ui-fixed-box {
position: relative;
width: 100%;
&.fixed {
position: fixed;
}
.ui-fixed-content {
position: relative;
}
.ui-fixed-bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
z-index: 1;
pointer-events: none;
}
}
}
.inset-bottom {
background: #fff;
}
</style>

View File

@ -0,0 +1,363 @@
<template>
<pb-fixed
:noFixed="props.noFixed"
:alway="props.alway"
:bgStyles="props.bgStyles"
:val="0"
:index="props.zIndex"
noNav
:bg="props.bg"
:ui="props.ui"
:opacity="props.opacity"
:placeholder="props.placeholder"
>
<pb-status-bar />
<view class="ui-navbar-box">
<view
class="ui-bar ss-p-x-20"
:class="state.isDark ? 'text-white' : 'text-black'"
:style="[{ height: sys_navBar - sys_statusBar + 'px' }]"
>
<view class="icon-box ss-flex">
<view class="icon-button icon-button-left ss-flex ss-row-center" @tap="onClickLeft">
<text class="sicon-back" v-if="hasHistory" />
<text class="sicon-home" v-else />
</view>
<view class="line"></view>
<view class="icon-button icon-button-right ss-flex ss-row-center" @tap="onClickRight">
<text class="sicon-more" />
</view>
</view>
<slot name="center">
<view class="center navbar-title">{{ title }}</view>
</slot>
<!-- #ifdef MP -->
<view :style="[state.capsuleStyle]"></view>
<!-- #endif -->
</view>
</view>
</pb-fixed>
</template>
<script setup>
/**
* 标题栏 - 基础组件navbar
*
* @param {Number} zIndex = 100 - 层级
* @param {Boolean} back = true - 是否返回上一页
* @param {String} backtext = '' - 返回文本
* @param {String} bg = 'bg-white' - 公共Class
* @param {String} status = '' - 状态栏颜色
* @param {Boolean} alway = true - 是否常驻
* @param {Boolean} opacity = false - 是否开启透明渐变
* @param {Boolean} noFixed = false - 是否浮动
* @param {String} ui = '' - 公共Class
* @param {Boolean} capsule = false - 是否开启胶囊返回
* @param {Boolean} stopBack = false - 是否禁用返回
* @param {Boolean} placeholder = true - 是否开启占位
* @param {Object} bgStyles = {} - 背景样式
*
*/
import { computed, reactive, onBeforeMount, ref } from 'vue'
import peach from '@/peach'
import { onPageScroll } from '@dcloudio/uni-app'
import { showMenuTools, closeMenuTools } from '@/peach/hooks/useModal'
//
const state = reactive({
statusCur: '',
capsuleStyle: {},
capsuleBack: {},
isDark: true,
})
const sys_statusBar = peach.$platform.device.statusBarHeight
const sys_navBar = peach.$platform.navbar
const props = defineProps({
zIndex: {
type: Number,
default: 100,
},
title: {
//
type: String,
default: '',
},
bg: {
type: String,
default: 'bg-white',
},
//
alway: {
type: Boolean,
default: true,
},
opacity: {
//
type: Boolean,
default: true,
},
noFixed: {
//
type: Boolean,
default: true,
},
ui: {
type: String,
default: '',
},
capsule: {
//
type: Boolean,
default: false,
},
stopBack: {
type: Boolean,
default: false,
},
placeholder: {
type: [Boolean],
default: false,
},
bgStyles: {
type: Object,
default() {},
},
})
const emits = defineEmits(['navback', 'clickLeft'])
const hasHistory = peach.$router.hasHistory()
onBeforeMount(() => {
init()
})
onPageScroll((e) => {
let top = e.scrollTop
state.isDark = top < peach.$platform.navbar
})
function onClickLeft() {
if (hasHistory) {
peach.$router.back()
} else {
peach.$router.go('/pages/index/index')
}
emits('clickLeft')
}
function onClickRight() {
showMenuTools()
}
//
const init = () => {
// #ifdef MP-ALIPAY
my.hideAllFavoriteMenu()
// #endif
state.capsuleStyle = {
width: peach.$platform.capsule.width + 'px',
height: peach.$platform.capsule.height + 'px',
}
state.capsuleBack = state.capsuleStyle
}
</script>
<style lang="scss" scoped>
.icon-box {
box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
border-radius: 30rpx;
width: 134rpx;
height: 56rpx;
margin-left: 8rpx;
border: 1px solid rgba(#fff, 0.4);
.line {
width: 2rpx;
height: 24rpx;
background: #e5e5e7;
}
.sicon-back {
font-size: 32rpx;
}
.sicon-home {
font-size: 32rpx;
}
.sicon-more {
font-size: 32rpx;
}
.icon-button {
width: 67rpx;
height: 56rpx;
&-left:hover {
background: rgba(0, 0, 0, 0.16);
border-radius: 30rpx 0px 0px 30rpx;
}
&-right:hover {
background: rgba(0, 0, 0, 0.16);
border-radius: 0px 30rpx 30rpx 0px;
}
}
}
.navbar-title {
font-size: 36rpx;
}
.tools-icon {
font-size: 40rpx;
}
.ui-navbar-box {
background-color: transparent;
width: 100%;
.ui-bar {
position: relative;
z-index: 2;
white-space: nowrap;
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
.left {
@include flex-bar;
.back {
@include flex-bar;
.back-icon {
@include flex-center;
width: 56rpx;
height: 56rpx;
margin: 0 10rpx;
font-size: 46rpx !important;
&.opacityIcon {
position: relative;
border-radius: 50%;
background-color: rgba(127, 127, 127, 0.5);
&::after {
content: '';
display: block;
position: absolute;
height: 200%;
width: 200%;
left: 0;
top: 0;
border-radius: inherit;
transform: scale(0.5);
transform-origin: 0 0;
opacity: 0.1;
border: 1px solid currentColor;
pointer-events: none;
}
&::before {
transform: scale(0.9);
}
}
}
/* #ifdef MP-ALIPAY */
._icon-back {
opacity: 0;
}
/* #endif */
}
.capsule {
@include flex-bar;
border-radius: 100px;
position: relative;
&.dark {
background-color: rgba(255, 255, 255, 0.5);
}
&.light {
background-color: rgba(0, 0, 0, 0.15);
}
&::after {
content: '';
display: block;
position: absolute;
height: 60%;
width: 1px;
left: 50%;
top: 20%;
background-color: currentColor;
opacity: 0.1;
pointer-events: none;
}
&::before {
content: '';
display: block;
position: absolute;
height: 200%;
width: 200%;
left: 0;
top: 0;
border-radius: inherit;
transform: scale(0.5);
transform-origin: 0 0;
opacity: 0.1;
border: 1px solid currentColor;
pointer-events: none;
}
.capsule-back,
.capsule-home {
@include flex-center;
flex: 1;
}
&.isFristPage {
.capsule-back,
&::after {
display: none;
}
}
}
}
.right {
@include flex-bar;
.right-content {
@include flex;
flex-direction: row-reverse;
}
}
.center {
@include flex-center;
text-overflow: ellipsis;
// text-align: center;
position: absolute;
left: 50%;
transform: translateX(-50%);
.image {
display: block;
height: 36px;
max-width: calc(100vw - 200px);
}
}
}
.ui-bar-bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
z-index: 1;
pointer-events: none;
}
}
</style>

View File

@ -0,0 +1,220 @@
<template>
<view class="page-app" :class="['theme-' + sys.mode, 'main-' + sys.theme, 'font-' + sys.fontSize]">
<view class="page-main" :style="[bgMain]">
<!-- 顶部导航栏-情况1默认通用顶部导航栏 -->
<pb-navbar
v-if="navbar === 'normal'"
:title="title"
statusBar
:color="color"
:tools="tools"
:opacityBgUi="opacityBgUi"
@search="(e) => emits('search', e)"
:defaultSearch="defaultSearch"
/>
<view class="page-body" :style="[bgBody]">
<!-- 顶部导航栏-情况2沉浸式头部 -->
<pb-inner-navbar v-if="navbar === 'inner'" :title="title" />
<view v-if="navbar === 'inner'" :style="[{ paddingTop: peach.$platform.navbar + 'px' }]"></view>
<!-- 页面内容插槽 -->
<slot />
<!-- 底部导航 -->
<pb-tabbar v-if="tabbar !== ''" :path="tabbar" />
</view>
</view>
<view class="page-modal">
<!-- 全局授权弹窗 -->
<!-- <p-auth-modal /> -->
<!-- 全局分享弹窗 -->
<!-- <p-share-modal :shareInfo="shareInfo" /> -->
<!-- 全局快捷入口 -->
<!-- <p-menu-tools /> -->
</view>
</view>
</template>
<script setup>
/**
* 模板组件 - 提供页面公共组件属性方法
* @param {String} title 标题
* @param {String} navbar 导航栏模式(normal-默认通用顶部导航栏 inner-沉浸式头部)
* @param {String} opacityBgUi 导航栏背景色 bg-color 详细查看 scss/style/_background.scss 文件
* @param {String} color 标题颜色
* @param {String} tools 取值 title标题或者 search搜索框
* @param {Object} bgStyle 背景样式
* @param {String} bgStyle.backgroundColor 背景色
* @param {String} bgStyle.backgroundImage 背景图
* @param {String} tabbar 底部导航页面路径
*/
import { computed, reactive, ref } from 'vue'
import peach from '@/peach'
import { isEmpty } from 'lodash'
import { onShow } from '@dcloudio/uni-app'
// #ifdef MP-WEIXIN
import { onShareAppMessage } from '@dcloudio/uni-app'
// #endif
const props = defineProps({
title: {
type: String,
default: '',
},
navbar: {
type: String,
default: 'normal',
},
opacityBgUi: {
type: String,
default: 'bg-white',
},
color: {
type: String,
default: '',
},
tools: {
type: String,
default: 'title',
},
bgStyle: {
type: Object,
default: () => ({
src: '',
color: 'var(--ui-BG-1)',
}),
},
tabbar: {
type: [String, Boolean],
default: '',
},
onShareAppMessage: {
type: [Boolean, Object],
default: true,
},
leftWidth: {
type: [Number, String],
default: 100,
},
rightWidth: {
type: [Number, String],
default: 100,
},
defaultSearch: {
type: String,
default: '',
},
//
showLeftButton: {
type: Boolean,
default: false,
},
})
const emits = defineEmits(['search'])
const sysStore = peach.$store('sys')
const userStore = peach.$store('user')
const appStore = peach.$store('app')
// const modalStore = peach.$store('modal')
const sys = computed(() => sysStore)
// ( )
const navbarMode = computed(() => {
if (props.navbar === 'normal') {
return 'normal'
}
return 'inner'
})
// 1
const bgMain = computed(() => {
if (navbarMode.value === 'inner') {
return {
background: `${props.bgStyle.backgroundColor} url(${peach.$url.cdn(
props.bgStyle.backgroundImage
)}) no-repeat top center / 100% auto`,
}
}
return {}
})
// 2
const bgBody = computed(() => {
if (navbarMode.value === 'normal') {
return {
background: `${props.bgStyle.backgroundColor} url(${peach.$url.cdn(
props.bgStyle.backgroundImage
)}) no-repeat top center / 100% auto`,
}
}
return {}
})
//
const shareInfo = computed(() => {
if (props.onShareAppMessage === true) {
return peach.$platform.share.getShareInfo()
} else {
if (!isEmpty(props.onShareAppMessage)) {
// peach.$platform.share.updateShareInfo(props.onShareAppMessage)
return props.onShareAppMessage
}
}
return {}
})
// #ifdef MP-WEIXIN
//
onShareAppMessage(() => {
return {
title: shareInfo.value.title,
path: shareInfo.value.path,
imageUrl: shareInfo.value.image,
}
})
// #endif
onShow(() => {
if (!isEmpty(shareInfo.value)) {
// peach.$platform.share.updateShareInfo(shareInfo.value)
}
})
</script>
<style lang="scss" scoped>
.page-app {
position: relative;
color: var(--ui-TC);
background-color: var(--ui-BG-1) !important;
z-index: 2;
display: flex;
width: 100%;
height: 100vh;
.page-main {
position: absolute;
z-index: 1;
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
.page-body {
width: 100%;
position: relative;
z-index: 1;
flex: 1;
}
.page-img {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
}
}
</style>

View File

@ -0,0 +1,429 @@
<!-- 自定义导航栏 -->
<template>
<view class="uni-navbar" :class="{ 'uni-dark': dark }">
<view
:class="{
'uni-navbar--fixed': fixed,
'uni-navbar--shadow': shadow,
'uni-navbar--border': border,
}"
class="uni-navbar__content"
>
<view class="fixed-bg" :class="[opacity ? '' : opacityBgUi]"></view>
<pb-status-bar v-if="statusBar" />
<view
:style="{
color: themeColor,
height: navbarHeight,
background: backgroundColor,
}"
class="uni-navbar__header"
>
<view class="uni-navbar__header-btns uni-navbar__header-btns-left" :style="{ width: leftIconWidth }">
<slot name="left">
<view class="uni-navbar__content_view" v-if="leftIcon.length > 0">
<view class="icon-box ss-flex">
<view class="icon-button icon-button-left ss-flex ss-row-center" @tap="onClickLeft">
<text class="sicon-back" v-if="hasHistory" />
<text class="sicon-home" v-else />
</view>
<view class="line"></view>
<view class="icon-button icon-button-right ss-flex ss-row-center" @tap="showMenuTools">
<text class="sicon-more" />
</view>
</view>
</view>
<view
:class="{ 'uni-navbar-btn-icon-left': !leftIcon.length > 0 }"
class="uni-navbar-btn-text"
v-if="
titleAlign === 'left' &&
title.length &&
peach.$platform.name !== 'WechatOfficialAccount'
"
>
<text :style="{ color: themeColor, fontSize: '18px' }">{{ title }}</text>
</view>
</slot>
</view>
<view v-if="tools === 'search'" class="ss-flex-1">
<slot name="center">
<uni-search-bar
class="ss-flex-1 search-box"
:radius="20"
placeholder="请输入关键词"
cancelButton="none"
v-model="searchModel"
@confirm="onSearch"
/>
</slot>
</view>
<view v-else class="uni-navbar__header-container" @tap="onClickTitle">
<slot name="center">
<view
v-if="tools === 'title' && titleAlign === 'center' && title.length"
class="uni-navbar__header-container-inner"
>
<text :style="{ color: themeColor, fontSize: '36rpx' }" class="ss-line-1">{{ title }}</text>
</view>
</slot>
</view>
</view>
</view>
<view class="uni-navbar__placeholder" v-if="placeholder">
<pb-status-bar v-if="statusBar" />
<view class="uni-navbar__placeholder-view" :style="{ height: navbarHeight }" />
</view>
</view>
</template>
<script setup>
import peach from '@/peach'
import { onLoad } from '@dcloudio/uni-app'
import { showMenuTools } from '@/peach/hooks/useModal'
import { computed } from 'vue'
/**
* NavBar 自定义导航栏
* @description 导航栏组件主要用于头部导航
* @property {Boolean} dark 开启黑暗模式
* @property {String} title 标题文字
* @property {String} rightText 右侧按钮文本
* @property {String} leftIcon 左侧按钮图标
* @property {String} rightIcon 右侧按钮图标
* @property {String} color 图标和文字颜色
* @property {String} backgroundColor 导航栏背景颜色
* @property {Boolean} fixed = [true|false] 是否固定顶部
* @property {Boolean} statusBar = [true|false] 是否包含状态栏
* @property {Boolean} shadow = [true|false] 导航栏下是否有阴影
* @event {Function} clickLeft 左侧按钮点击时触发
* @event {Function} clickRight 右侧按钮点击时触发
* @event {Function} clickTitle 中间标题点击时触发
*/
const getVal = (val) => (typeof val === 'number' ? val + 'px' : val)
const emits = defineEmits(['clickLeft', 'clickRight', 'clickTitle', 'search'])
const props = defineProps({
dark: {
type: Boolean,
default: false,
},
modelValue: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
// left | center
titleAlign: {
type: String,
default: 'center',
},
rightText: {
type: String,
default: '',
},
leftIcon: {
type: String,
default: 'left',
},
rightIcon: {
type: String,
default: '',
},
fixed: {
type: [Boolean, String],
default: true,
},
placeholder: {
type: [Boolean, String],
default: true,
},
color: {
type: String,
default: '',
},
backgroundColor: {
type: String,
default: '',
},
opacity: {
type: [Boolean, String],
default: false,
},
opacityBgUi: {
type: String,
default: 'bg-white',
},
statusBar: {
type: [Boolean, String],
default: false,
},
shadow: {
type: [Boolean, String],
default: false,
},
border: {
type: [Boolean, String],
default: false,
},
height: {
type: [Number, String],
default: 44,
},
leftWidth: {
type: [Number, String],
default: 80,
},
rightWidth: {
type: [Number, String],
default: 0,
},
tools: {
type: String,
default: 'title',
},
defaultSearch: {
type: String,
default: '',
},
})
const searchModel = computed(() => {
return props.defaultSearch
})
const themeColor = computed(() => {
if (props.dark) {
return props.color ? props.color : '#fff'
}
return props.color || '#333'
})
const navbarHeight = computed(() => {
return getVal(props.height)
})
const leftIconWidth = computed(() => {
return getVal(props.leftWidth)
})
function onSearch(e) {
emits('search', e.value)
}
onLoad(() => {
if (uni.report && props.title !== '') {
uni.report('title', props.title)
}
})
const hasHistory = peach.$router.hasHistory()
function onClickLeft() {
if (hasHistory) {
peach.$router.back()
} else {
peach.$router.go('/pages/index/index')
}
emits('clickLeft')
}
function onClickTitle() {
emits('clickTitle')
}
</script>
<style lang="scss" scoped>
.bg-main {
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)) !important;
color: #fff !important;
}
.icon-box {
background: #ffffff;
box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
border-radius: 30rpx;
width: 134rpx;
height: 56rpx;
margin-left: 8rpx;
.line {
width: 2rpx;
height: 24rpx;
background: #e5e5e7;
}
.sicon-back {
font-size: 32rpx;
color: #000;
}
.sicon-home {
font-size: 32rpx;
color: #000;
}
.sicon-more {
font-size: 32rpx;
color: #000;
}
.icon-button {
width: 67rpx;
height: 56rpx;
&-left:hover {
background: rgba(0, 0, 0, 0.16);
border-radius: 30rpx 0px 0px 30rpx;
}
&-right:hover {
background: rgba(0, 0, 0, 0.16);
border-radius: 0px 30rpx 30rpx 0px;
}
}
}
$nav-height: 44px;
.fixed-bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
z-index: 1;
pointer-events: none;
}
.uni-nav-bar-text {
/* #ifdef APP-PLUS */
font-size: 34rpx;
/* #endif */
/* #ifndef APP-PLUS */
font-size: 14px;
/* #endif */
}
.uni-nav-bar-right-text {
font-size: 12px;
}
.uni-navbar__content {
position: relative;
background-color: transparent;
}
.uni-navbar__content_view {
// box-sizing: border-box;
}
.uni-navbar-btn-text {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
align-items: center;
line-height: 18px;
}
.uni-navbar__header {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding: 0 10px;
flex-direction: row;
justify-content: space-between;
height: $nav-height;
font-size: 12px;
position: relative;
z-index: 2;
}
.uni-navbar__header-btns {
/* #ifndef APP-NVUE */
overflow: hidden;
display: flex;
/* #endif */
flex-wrap: nowrap;
flex-direction: row;
min-width: 40rpx;
justify-content: center;
align-items: center;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-navbar__header-btns-left {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
width: 120rpx;
justify-content: flex-start;
align-items: center;
}
.uni-navbar__header-btns-right {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.uni-navbar__header-container {
position: absolute;
left: 50%;
transform: translateX(-50%) translateY(-50%);
top: 50%;
}
.uni-navbar__header-container-inner {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 12px;
overflow: hidden;
}
.uni-navbar__placeholder-view {
height: $nav-height;
}
.uni-navbar--fixed {
position: fixed;
z-index: 998;
/* #ifdef H5 */
left: var(--window-left);
right: var(--window-right);
/* #endif */
/* #ifndef H5 */
left: 0;
right: 0;
/* #endif */
}
.uni-navbar--shadow {
box-shadow: 0 1px 6px #ccc;
}
.uni-navbar--border {
border-bottom-width: 1rpx;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.uni-ellipsis-1 {
overflow: hidden;
/* #ifndef APP-NVUE */
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
text-overflow: ellipsis;
/* #endif */
}
//
.uni-dark {
}
</style>

View File

@ -0,0 +1,15 @@
<!-- 自定义状态栏 -->
<template>
<view :style="{ height: statusBarHeight }" class="uni-status-bar"><slot /></view>
</template>
<script setup>
import peach from '@/peach'
const statusBarHeight = peach.$platform.device.statusBarHeight + 'px'
</script>
<style lang="scss">
.uni-status-bar {
height: var(--status-bar-height);
}
</style>

View File

@ -0,0 +1,234 @@
<!-- 自定义底部导航项 -->
<template>
<view class="u-tabbar-item" :style="[addStyle(customStyle)]">
<view v-if="isCenter" class="tabbar-center-item">
<image class="center-image" :src="centerImage" mode="aspectFill"></image>
</view>
<template v-else>
<view class="u-tabbar-item__icon">
<image
v-if="icon"
:name="icon"
:color="isActive ? parentData.activeColor : parentData.color"
:size="20"
></image>
<block v-else>
<slot v-if="isActive" name="active-icon" />
<slot v-else name="inactive-icon" />
</block>
<!-- <u-badge
absolute
:offset="[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']"
:customStyle="badgeStyle"
:isDot="dot"
:value="badge || (dot ? 1 : null)"
:show="dot || badge > 0"
></u-badge> -->
</view>
<slot name="text">
<text
class="u-tabbar-item__text"
:style="{
color: isActive ? parentData.activeColor : parentData.color,
}"
>
{{ text }}
</text>
</slot>
</template>
</view>
</template>
<script>
/**
* TabbarItem 底部导航栏子组件
* @description 此组件提供了自定义tabbar的能力
* @property {String | Number} name item标签的名称作为与u-tabbar的value参数匹配的标识符
* @property {String} icon uView内置图标或者绝对路径的图片
* @property {String | Number} badge 右上角的角标提示信息
* @property {Boolean} dot 是否显示圆点将会覆盖badge参数默认 false
* @property {String} text 描述文本
* @property {Object | String} badgeStyle 控制徽标的位置对象或者字符串形式可以设置top和right属性默认 'top: 6px;right:2px;'
* @property {Object} customStyle 定义需要用到的外部样式
*
*/
import { deepMerge, addStyle, sleep, $parent } from '@/peach/helper'
export default {
name: 'pb-tabbar-item',
props: {
customStyle: {
type: [Object, String],
default: () => ({}),
},
customClass: {
type: String,
default: '',
},
//
url: {
type: String,
default: '',
},
//
linkType: {
type: String,
default: 'navigateTo',
},
// itemu-tabbarvalue
name: {
type: [String, Number, null],
default: '',
},
// uView
icon: {
icon: String,
default: '',
},
//
badge: {
type: [String, Number, null],
default: '',
},
// badge
dot: {
type: Boolean,
default: false,
},
//
text: {
type: String,
default: '',
},
// topright
badgeStyle: {
type: [Object, String],
default: '',
},
isCenter: {
type: Boolean,
default: false,
},
centerImage: {
type: String,
default: '',
},
},
data() {
return {
isActive: false, //
addStyle,
parentData: {
value: null,
activeColor: '',
color: '',
},
parent: {},
}
},
created() {
this.init()
},
methods: {
getParentData(parentName = '') {
// createdparent
if (!this.parent) this.parent = {}
// (u-radiou-radio-groupthis)
// this(u-radiothis)parentData
// this.parent.xxx
// u-radio-groupdata
this.parent = $parent.call(this, parentName)
if (this.parent.children) {
// childrenchildren
this.parent.children.indexOf(this) === -1 && this.parent.children.push(this)
}
if (this.parent && this.parentData) {
// parentDataparentparentData
Object.keys(this.parentData).map((key) => {
this.parentData[key] = this.parent[key]
})
}
},
init() {
// provide/inject使created
this.updateParentData()
if (!this.parent) {
console.log('u-tabbar-item必须搭配u-tabbar组件使用')
}
// u-tabbarchildren
const index = this.parent.children.indexOf(this)
// name(nameindex)value
this.isActive = (this.name.split('?')[0] || index) === this.parentData.value
},
updateParentData() {
// mixin
this.getParentData('pb-s-tabbar')
},
// u-tabbar
updateFromParent() {
//
this.init()
},
clickHandler() {
this.$nextTick(() => {
const index = this.parent.children.indexOf(this)
const name = this.name || index
// itemitemchange
if (name !== this.parent.value) {
this.parent.$emit('change', name)
}
this.$emit('click', name)
})
},
},
}
</script>
<style lang="scss" scoped>
.tabbar-center-item {
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rebeccapurple;
transform: scale(1.3) translateY(-6px);
position: absolute;
z-index: 2;
.center-image {
width: 25px;
height: 25px;
}
}
.u-tabbar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
position: relative;
z-index: 1;
&__icon {
display: flex;
position: relative;
width: 150rpx;
justify-content: center;
}
&__text {
margin-top: 2px;
font-size: 12px;
color: var(--textSize);
}
}
/* #ifdef MP */
// 使shadow DOM宿flex: 1
:host {
flex: 1;
}
/* #endif */
</style>

View File

@ -0,0 +1,227 @@
<!-- 底部导航栏 -->
<template>
<view class="u-tabbar">
<view
class="u-tabbar__content"
ref="u-tabbar__content"
@touchmove.stop.prevent=""
:class="[border && 'u-border-top', fixed && 'u-tabbar--fixed', { 'mid-tabbar': midTabBar }]"
:style="[tabbarStyle]"
>
<view class="u-tabbar__content__item-wrapper">
<slot></slot>
</view>
<view v-if="safeAreaInsetBottom" :style="[{ height: safeBottomHeight + 'px' }]"></view>
</view>
<view
class="u-tabbar__placeholder"
v-if="placeholder"
:style="{
height: placeholderHeight + 'px',
}"
></view>
</view>
</template>
<script>
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* Tabbar 底部导航栏
* @description 此组件提供了自定义tabbar的能力
* @property {String | Number} value 当前匹配项的name
* @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离默认 true
* @property {Boolean} border 是否显示上方边框默认 true
* @property {String | Number} zIndex 元素层级z-index默认 1
* @property {String} activeColor 选中标签的颜色默认 '#1989fa'
* @property {String} inactiveColor 未选中标签的颜色默认 '#7d7e80'
* @property {Boolean} fixed 是否固定在底部默认 true
* @property {Boolean} placeholder fixed定位固定在底部时是否生成一个等高元素防止塌陷默认 true
* @property {Object} customStyle 定义需要用到的外部样式
*
*/
import { deepMerge, addStyle, sleep } from '@/peach/helper'
import peach from '@/peach'
export default {
name: 'pb-s-tabbar',
props: {
customStyle: {
type: [Object, String],
default: () => ({}),
},
customClass: {
type: String,
default: '',
},
//
url: {
type: String,
default: '',
},
//
linkType: {
type: String,
default: 'navigateTo',
},
// name
value: {
type: [String, Number, null],
default: '',
},
// iPhoneX
safeAreaInsetBottom: {
type: Boolean,
default: true,
},
//
border: {
type: Boolean,
default: true,
},
// z-index
zIndex: {
type: [String, Number],
default: 10,
},
//
activeColor: {
type: String,
default: '#1989fa',
},
//
inactiveColor: {
type: String,
default: '#7d7e80',
},
//
fixed: {
type: Boolean,
default: true,
},
// fixed
placeholder: {
type: Boolean,
default: true,
},
midTabBar: {
type: Boolean,
default: false,
},
},
data() {
return {
placeholderHeight: 0,
safeBottomHeight: peach.$platform.device.safeAreaInsets.bottom,
}
},
computed: {
tabbarStyle() {
const style = {
zIndex: this.zIndex,
}
// customStyle
return deepMerge(style, addStyle(this.customStyle))
},
// computed
updateChild() {
return [this.value, this.activeColor, this.inactiveColor]
},
updatePlaceholder() {
return [this.fixed, this.placeholder]
},
},
watch: {
updateChild() {
// updateChildren
this.updateChildren()
},
updatePlaceholder() {
// fixedplaceholder
this.setPlaceholderHeight()
},
},
created() {
this.children = []
},
mounted() {
this.setPlaceholderHeight()
},
methods: {
updateChildren() {
// updateFromParent
this.children.length && this.children.map((child) => child.updateFromParent())
},
getRect(selector, all) {
return new Promise((resolve) => {
uni.createSelectorQuery()
.in(this)
[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect) => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
})
.exec()
})
},
//
async setPlaceholderHeight() {
if (!this.fixed || !this.placeholder) return
//
await sleep(20)
// #ifndef APP-NVUE
this.getRect('.u-tabbar__content').then(({ height = 50 }) => {
// IOS safearea bottom
this.placeholderHeight = height
})
// #endif
// #ifdef APP-NVUE
dom.getComponentRect(this.$refs['u-tabbar__content'], (res) => {
const { size } = res
this.placeholderHeight = size.height
})
// #endif
},
},
}
</script>
<style lang="scss" scoped>
.u-tabbar {
display: flex;
flex: 1;
justify-content: center;
&__content {
display: flex;
flex-direction: column;
background-color: #fff;
box-shadow: 0px -2px 4px 0px rgba(51, 51, 51, 0.06);
&__item-wrapper {
height: 50px;
display: flex;
justify-content: space-around;
align-items: center;
}
}
.mid-tabbar {
border-radius: 30rpx 30rpx 0 0;
}
&--fixed {
position: fixed;
bottom: -1px;
left: 0;
right: 0;
}
}
</style>

View File

@ -0,0 +1,87 @@
<template>
<view class="u-page__item" v-if="tabbar?.items?.length > 0">
<pb-s-tabbar
:value="path"
:fixed="true"
:placeholder="true"
:safeAreaInsetBottom="true"
:inactiveColor="tabbar.style.color"
:activeColor="tabbar.style.activeColor"
:midTabBar="tabbar.mode === 2"
:customStyle="tabbarStyle"
>
<pb-tabbar-item
v-for="(item, index) in tabbar.items"
:key="item.text"
:text="item.text"
:name="item.url"
:isCenter="getTabbarCenter(index)"
:centerImage="peach.$url.cdn(item.iconUrl)"
@tap="peach.$router.go(item.url)"
>
<template v-slot:active-icon>
<image class="u-page__item__slot-icon" :src="peach.$url.cdn(item.activeIconUrl)"></image>
</template>
<template v-slot:inactive-icon>
<image class="u-page__item__slot-icon" :src="peach.$url.cdn(item.iconUrl)"></image>
</template>
</pb-tabbar-item>
</pb-s-tabbar>
</view>
</template>
<script setup>
import { computed, unref } from 'vue'
import PbSTabbar from './pb-s-tabbar.vue'
import peach from '@/peach'
const tabbar = computed(() => {
return peach.$store('app').template.basic?.tabbar
})
const tabbarStyle = computed(() => {
const backgroundStyle = tabbar.value.style
if (backgroundStyle.bgType === 'color') {
return { background: backgroundStyle.bgColor }
}
if (backgroundStyle.bgType === 'img')
return {
background: `url(${peach.$url.cdn(backgroundStyle.bgImg)}) no-repeat top center / 100% auto`,
}
})
const getTabbarCenter = (index) => {
if (unref(tabbar).mode !== 2) return false
return unref(tabbar).items % 2 > 0 ? Math.ceil(unref(tabbar).items.length / 2) === index + 1 : false
}
const props = defineProps({
path: String,
default: '',
})
</script>
<style lang="scss">
.u-page {
padding: 0;
&__item {
&__title {
color: var(--textSize);
background-color: #fff;
padding: 15px;
font-size: 15px;
&__slot-title {
color: var(--textSize);
font-size: 14px;
}
}
&__slot-icon {
width: 25px;
height: 25px;
}
}
}
</style>

199
peach/url/index.js Normal file
View File

@ -0,0 +1,199 @@
import $store from '@/peach/store'
import { staticUrl } from '@/peach/config'
const cdn = (url = '', cdnurl = '') => {
if (!url) return ''
if (url.indexOf('http') === 0) {
return url
}
if (cdnurl === '') {
cdnurl = $store('app').info.cdnurl
}
return cdnurl + url
}
export default {
// 添加cdn域名前缀
cdn,
// 对象存储自动剪裁缩略图
thumb: (url = '', params) => {
url = cdn(url)
return append_thumbnail_params(url, params)
},
// 静态资源地址
static: (url = '', staticurl = '') => {
if (staticurl === '') {
staticurl = staticUrl
}
if (staticurl !== 'local') {
url = cdn(url, staticurl)
}
return url
},
// css背景图片地址
css: (url = '', staticurl = '') => {
if (staticurl === '') {
staticurl = staticUrl
}
if (staticurl !== 'local') {
url = cdn(url, staticurl)
}
// #ifdef APP-PLUS
if (staticurl === 'local') {
url = plus.io.convertLocalFileSystemURL(url)
}
// #endif
return `url(${url})`
},
}
/**
* 追加对象存储自动裁剪/压缩参数
*
* @return string
*/
function append_thumbnail_params(url, params) {
const filesystem = $store('app').info.filesystem
if (filesystem === 'public') {
return url
}
let width = params.width || '200' // 宽度
let height = params.height || '200' // 高度
let mode = params.mode || 'lfit' // 缩放模式
let quality = params.quality || 90 // 压缩质量
let gravity = params.gravity || 'center' // 剪裁质量
let suffix = ''
let crop_str = ''
let quality_str = ''
let size = width + 'x' + height
switch (filesystem) {
case 'aliyun':
// 裁剪
if (!gravity && gravity != 'center') {
// 指定了裁剪区域
mode = 'mfit'
crop_str = '/crop,g_' + gravityFormatMap('aliyun', gravity) + ',w_' + width + ',h_' + height
}
// 质量压缩
if (quality > 0 && quality < 100) {
quality_str = '/quality,q_' + quality
}
// 缩放参数
suffix = 'x-oss-process=image/resize,m_' + mode + ',w_' + width + ',h_' + height
// 拼接裁剪和质量压缩
suffix += crop_str + quality_str
break
case 'qcloud':
let mode_str = 'thumbnail'
if (mode == 'fill' || (!gravity && gravity != 'center')) {
// 指定了裁剪区域
mode_str = 'crop'
mode = 'fill'
crop_str = '/gravity/' + gravityFormatMap('qcloud', gravity)
}
// 质量压缩
if (quality > 0 && quality < 100) {
quality_str = '/rquality/' + quality
}
switch (mode) {
case 'lfit':
size = '' + size + '>'
break
case 'mfit':
size = '!' + size + 'r'
case 'fill':
break
case 'pad':
size = size + '/pad/1'
break
case 'fixed':
size = size + '!'
break
}
suffix = 'imageMogr2/' + mode_str + '/' + size + crop_str + quality_str
break
case 'qiniu':
if (mode == 'fill' || (!gravity && gravity != 'center')) {
// 指定了裁剪区域,全部转为 mfit
mode = 'mfit'
crop_str = '/gravity/' + gravityFormatMap('qiniu', gravity) + '/crop/' + size
}
// 质量压缩
if (quality > 0 && quality < 100) {
quality_str = '/quality/' + quality
}
switch (mode) {
case 'lfit':
case 'pad': // 七牛不支持在缩放之后,尺寸不足时,填充背景色,所以这里和 lfit 模式一样
size = size + '>'
break
case 'mfit':
size = '!' + size + 'r'
break
case 'fill':
// 会被转为 mfit
break
case 'fixed':
size = size + '!'
break
}
suffix = 'imageMogr2/thumbnail/' + size + crop_str + quality_str
break
}
return url + '?' + suffix
}
/**
* 裁剪区域格式转换
*
* @param string $type aliyun|qcloud|qiniu
* @param string $gravity 统一的裁剪区域字符
*
* @return string
*/
function gravityFormatMap(type, gravity) {
let gravityFormat = {
aliyun: {
north_west: 'nw', // 左上
north: 'north', // 中上
north_east: 'ne', // 右上
west: 'west', // 左中
center: 'center', // 中部
east: 'east', // 右中
south_west: 'sw', // 左下
south: 'south', // 中下
south_east: 'se', // 右下
},
qcloud: {
northwest: 'nw', // 左上
north: 'north', // 中上
northeast: 'ne', // 右上
west: 'west', // 左中
center: 'center', // 中部
east: 'east', // 右中
southwest: 'sw', // 左下
south: 'south', // 中下
southeast: 'se', // 右下
},
qiniu: {
NorthWest: 'nw', // 左上
North: 'north', // 中上
NorthEast: 'ne', // 右上
West: 'west', // 左中
Center: 'center', // 中部
East: 'east', // 右中
SouthWest: 'sw', // 左下
South: 'south', // 中下
SouthEast: 'se', // 右下
},
}
return gravityFormat[type][gravity]
}

View File

@ -0,0 +1,34 @@
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
const fs = require('fs')
function getAllIconsClass(scssFilePath) {
let result = []
for (const path of scssFilePath) {
console.log(path)
const fileContent = fs.readFileSync(path, 'utf-8')
const regex = /(cicon|_icon|sicon).*:before/g
let matches = fileContent.match(regex)
matches = matches ? matches.map((match) => match.replace(/\:before/, '')) : []
result.push(...matches)
}
return result
}
function uniGetAllIconsPlugin({ scssFilePath }) {
return {
name: 'uni-get-all-icons',
config(config) {
return {
define: {
COLORICONS: getAllIconsClass(scssFilePath),
},
}
},
}
}
exports.default = uniGetAllIconsPlugin

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
static/soldout-empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

10
uni.promisify.adaptor.js Normal file
View File

@ -0,0 +1,10 @@
uni.addInterceptor({
returnValue (res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
});
},
});

78
uni.scss Normal file
View File

@ -0,0 +1,78 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
@import '@/peach/scss/_var.scss';
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color: #333; //基本色
$uni-text-color-inverse: #fff; //反色
$uni-text-color-grey: #999; //辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #ffffff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; //点击状态颜色
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm: 12px;
$uni-font-size-base: 14px;
$uni-font-size-lg: 16;
/* 图片尺寸 */
$uni-img-size-sm: 20px;
$uni-img-size-base: 26px;
$uni-img-size-lg: 40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2c405a; // 文章标题颜色
$uni-font-size-title: 20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle: 26px;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 15px;

View File

@ -0,0 +1,33 @@
## 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
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
## 1.1.72021-11-08
- 优化 升级ui
- 修改 size 属性默认值调整为 small
- 修改 type 属性,默认值调整为 errorinfo 替换 default
## 1.1.62021-09-22
- 修复 在字节小程序上样式不生效的 bug
## 1.1.52021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.42021-07-29
- 修复 去掉 nvue 不支持css 的 align-self 属性nvue 下不暂支持 absolute 属性
## 1.1.32021-06-24
- 优化 示例项目
## 1.1.12021-05-12
- 新增 组件示例地址
## 1.1.02021-05-12
- 新增 uni-badge 的 absolute 属性,支持定位
- 新增 uni-badge 的 offset 属性,支持定位偏移
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
- 优化 uni-badge 属性 custom-style 支持以对象形式自定义样式
## 1.0.72021-05-07
- 修复 uni-badge 在 App 端数字小于10时不是圆形的bug
- 修复 uni-badge 在父元素不是 flex 布局时宽度缩小的bug
- 新增 uni-badge 属性 custom-style 支持自定义样式
## 1.0.62021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,268 @@
<template>
<view class="uni-badge--x">
<slot />
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
class="uni-badge" @click="onClick()">{{displayValue}}</text>
</view>
</template>
<script>
/**
* Badge 数字角标
* @description 数字角标一般和其它控件列表9宫格等配合使用用于进行数量提示默认为实心灰色背景
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
* @property {String} text 角标内容
* @property {String} size = [normal|small] 角标内容
* @property {String} type = [info|primary|success|warning|error] 颜色类型
* @value info 灰色
* @value primary 蓝色
* @value success 绿色
* @value warning 黄色
* @value error 红色
* @property {String} inverted = [true|false] 是否无需背景颜色
* @property {Number} maxNum 展示封顶的数字值超过 99 显示 99+
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
* @value rightTop 右上
* @value rightBottom 右下
* @value leftTop 左上
* @value leftBottom 左下
* @property {Array[number]} offset 距定位角中心点的偏移量只有存在 absolute 属性时有效例如[-10, -10] 表示向外偏移 10px[10, 10] 表示向 absolute 指定的内偏移 10px
* @property {String} isDot = [true|false] 是否显示为一个小点
* @event {Function} click 点击 Badge 触发事件
* @example <uni-badge text="1"></uni-badge>
*/
export default {
name: 'UniBadge',
emits: ['click'],
props: {
type: {
type: String,
default: 'error'
},
inverted: {
type: Boolean,
default: false
},
isDot: {
type: Boolean,
default: false
},
maxNum: {
type: Number,
default: 99
},
absolute: {
type: String,
default: ''
},
offset: {
type: Array,
default () {
return [0, 0]
}
},
text: {
type: [String, Number],
default: ''
},
size: {
type: String,
default: 'small'
},
customStyle: {
type: Object,
default () {
return {}
}
}
},
data() {
return {};
},
computed: {
width() {
return String(this.text).length * 8 + 12
},
classNames() {
const {
inverted,
type,
size,
absolute
} = this
return [
inverted ? 'uni-badge--' + type + '-inverted' : '',
'uni-badge--' + type,
'uni-badge--' + size,
absolute ? 'uni-badge--absolute' : ''
].join(' ')
},
positionStyle() {
if (!this.absolute) return {}
let w = this.width / 2,
h = 10
if (this.isDot) {
w = 5
h = 5
}
const x = `${- w + this.offset[0]}px`
const y = `${- h + this.offset[1]}px`
const whiteList = {
rightTop: {
right: x,
top: y
},
rightBottom: {
right: x,
bottom: y
},
leftBottom: {
left: x,
bottom: y
},
leftTop: {
left: x,
top: y
}
}
const match = whiteList[this.absolute]
return match ? match : whiteList['rightTop']
},
dotStyle() {
if (!this.isDot) return {}
return {
width: '10px',
minWidth: '0',
height: '10px',
padding: '0',
borderRadius: '10px'
}
},
displayValue() {
const {
isDot,
text,
maxNum
} = this
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
}
},
methods: {
onClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" >
$uni-primary: #2979ff !default;
$uni-success: #4cd964 !default;
$uni-warning: #f0ad4e !default;
$uni-error: #dd524d !default;
$uni-info: #909399 !default;
$bage-size: 12px;
$bage-small: scale(0.8);
.uni-badge--x {
/* #ifdef APP-NVUE */
// align-self: flex-start;
/* #endif */
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
position: relative;
}
.uni-badge--absolute {
position: absolute;
}
.uni-badge--small {
transform: $bage-small;
transform-origin: center center;
}
.uni-badge {
/* #ifndef APP-NVUE */
display: flex;
overflow: hidden;
box-sizing: border-box;
font-feature-settings: "tnum";
min-width: 20px;
/* #endif */
justify-content: center;
flex-direction: row;
height: 20px;
padding: 0 4px;
line-height: 18px;
color: #fff;
border-radius: 100px;
background-color: $uni-info;
background-color: transparent;
border: 1px solid #fff;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-size: $bage-size;
/* #ifdef H5 */
z-index: 999;
cursor: pointer;
/* #endif */
&--info {
color: #fff;
background-color: $uni-info;
}
&--primary {
background-color: $uni-primary;
}
&--success {
background-color: $uni-success;
}
&--warning {
background-color: $uni-warning;
}
&--error {
background-color: $uni-error;
}
&--inverted {
padding: 0 5px 0 0;
color: $uni-info;
}
&--info-inverted {
color: $uni-info;
background-color: transparent;
}
&--primary-inverted {
color: $uni-primary;
background-color: transparent;
}
&--success-inverted {
color: $uni-success;
background-color: transparent;
}
&--warning-inverted {
color: $uni-warning;
background-color: transparent;
}
&--error-inverted {
color: $uni-error;
background-color: transparent;
}
}
</style>

View File

@ -0,0 +1,85 @@
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.2.2",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",
"badge",
"uni-ui",
"uniui",
"数字角标",
"徽章"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,10 @@
## Badge 数字角标
> **组件名uni-badge**
> 代码块: `uBadge`
数字角标一般和其它控件列表、9宫格等配合使用用于进行数量提示默认为实心灰色背景
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,28 @@
## 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 控制主题色
## 1.4.62022-09-08
- 修复 表头年月切换导致改变当前日期为选择月1号且未触发change事件的Bug
## 1.4.52022-02-25
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.42022-02-25
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.32021-09-22
- 修复 startDate、 endDate 属性失效的Bug
## 1.4.22021-08-24
- 新增 支持国际化
## 1.4.12021-08-05
- 修复 弹出层被 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目录规范

View File

@ -0,0 +1,546 @@
/**
* @1900-2100区间内的公历农历互转
* @charset UTF-8
* @github https://github.com/jjonline/calendar.js
* @Author Jea杨(JJonline@JJonline.Cn)
* @Time 2014-7-21
* @Time 2016-8-13 Fixed 2033hexAttribution Annals
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
* @Version 1.0.3
* @公历转农历calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
* @农历转公历calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
*/
/* eslint-disable */
var calendar = {
/**
* 农历1900-2100的润大小信息表
* @Array Of Property
* @return Hex
*/
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
/** Add By JJonline@JJonline.Cn**/
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
0x0d520], // 2100
/**
* 公历每个月份的天数普通表
* @Array Of Property
* @return Number
*/
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
/**
* 天干地支之天干速查表
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
* @return Cn string
*/
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
/**
* 天干地支之地支速查表
* @Array Of Property
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
* @return Cn string
*/
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
/**
* 天干地支之地支速查表<=>生肖
* @Array Of Property
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
* @return Cn string
*/
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
/**
* 24节气速查表
* @Array Of Property
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
* @return Cn string
*/
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
/**
* 1900-2100各年的24节气日期速查表
* @Array Of Property
* @return 0x string For splice
*/
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
/**
* 数字转中文速查表
* @Array Of Property
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
* @return Cn string
*/
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
/**
* 日期转农历称呼速查表
* @Array Of Property
* @trans ['初','十','廿','卅']
* @return Cn string
*/
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
/**
* 月份转农历称呼速查表
* @Array Of Property
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
* @return Cn string
*/
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
/**
* 返回农历y年一整年的总天数
* @param lunar Year
* @return Number
* @eg:var count = calendar.lYearDays(1987) ;//count=387
*/
lYearDays: function (y) {
var i; var sum = 348
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
return (sum + this.leapDays(y))
},
/**
* 返回农历y年闰月是哪个月若y年没有闰月 则返回0
* @param lunar Year
* @return Number (0-12)
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
*/
leapMonth: function (y) { // 闰字编码 \u95f0
return (this.lunarInfo[y - 1900] & 0xf)
},
/**
* 返回农历y年闰月的天数 若该年没有闰月则返回0
* @param lunar Year
* @return Number (02930)
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
*/
leapDays: function (y) {
if (this.leapMonth(y)) {
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
}
return (0)
},
/**
* 返回农历y年m月非闰月的总天数计算m为闰月时的天数请使用leapDays方法
* @param lunar Year
* @return Number (-12930)
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
*/
monthDays: function (y, m) {
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12参数错误返回-1
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
},
/**
* 返回公历(!)y年m月的天数
* @param solar Year
* @return Number (-128293031)
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
*/
solarDays: function (y, m) {
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var ms = m - 1
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
} else {
return (this.solarMonth[ms])
}
},
/**
* 农历年份转换为干支纪年
* @param lYear 农历年的年份数
* @return Cn string
*/
toGanZhiYear: function (lYear) {
var ganKey = (lYear - 3) % 10
var zhiKey = (lYear - 3) % 12
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
},
/**
* 公历月日判断所属星座
* @param cMonth [description]
* @param cDay [description]
* @return Cn string
*/
toAstro: function (cMonth, cDay) {
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
},
/**
* 传入offset偏移量返回干支
* @param offset 相对甲子的偏移量
* @return Cn string
*/
toGanZhi: function (offset) {
return this.Gan[offset % 10] + this.Zhi[offset % 12]
},
/**
* 传入公历(!)y年获得该年第n个节气的公历日期
* @param y公历年(1900-2100)n二十四节气中的第几个节气(1~24)从n=1(小寒)算起
* @return day Number
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
*/
getTerm: function (y, n) {
if (y < 1900 || y > 2100) { return -1 }
if (n < 1 || n > 24) { return -1 }
var _table = this.sTermInfo[y - 1900]
var _info = [
parseInt('0x' + _table.substr(0, 5)).toString(),
parseInt('0x' + _table.substr(5, 5)).toString(),
parseInt('0x' + _table.substr(10, 5)).toString(),
parseInt('0x' + _table.substr(15, 5)).toString(),
parseInt('0x' + _table.substr(20, 5)).toString(),
parseInt('0x' + _table.substr(25, 5)).toString()
]
var _calday = [
_info[0].substr(0, 1),
_info[0].substr(1, 2),
_info[0].substr(3, 1),
_info[0].substr(4, 2),
_info[1].substr(0, 1),
_info[1].substr(1, 2),
_info[1].substr(3, 1),
_info[1].substr(4, 2),
_info[2].substr(0, 1),
_info[2].substr(1, 2),
_info[2].substr(3, 1),
_info[2].substr(4, 2),
_info[3].substr(0, 1),
_info[3].substr(1, 2),
_info[3].substr(3, 1),
_info[3].substr(4, 2),
_info[4].substr(0, 1),
_info[4].substr(1, 2),
_info[4].substr(3, 1),
_info[4].substr(4, 2),
_info[5].substr(0, 1),
_info[5].substr(1, 2),
_info[5].substr(3, 1),
_info[5].substr(4, 2)
]
return parseInt(_calday[n - 1])
},
/**
* 传入农历数字月份返回汉语通俗表示法
* @param lunar month
* @return Cn string
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
*/
toChinaMonth: function (m) { // 月 => \u6708
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var s = this.nStr3[m - 1]
s += '\u6708'// 加上月字
return s
},
/**
* 传入农历日期数字返回汉字表示法
* @param lunar day
* @return Cn string
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
*/
toChinaDay: function (d) { // 日 => \u65e5
var s
switch (d) {
case 10:
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]
}
return (s)
},
/**
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是立春
* @param y year
* @return Cn string
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
*/
getAnimal: function (y) {
return this.Animals[(y - 4) % 12]
},
/**
* 传入阳历年月日获得详细的公历农历object信息 <=>JSON
* @param y solar year
* @param m solar month
* @param d solar day
* @return JSON object
* @eg:console.log(calendar.solar2lunar(1987,11,01));
*/
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
// 年份限定、上限
if (y < 1900 || y > 2100) {
return -1// undefined转换为数字变为NaN
}
// 公历传参最下限
if (y == 1900 && m == 1 && d < 31) {
return -1
}
// 未传参 获得当天
if (!y) {
var objDate = new Date()
} else {
var objDate = new Date(y, parseInt(m) - 1, d)
}
var i; var leap = 0; var temp = 0
// 修正ymd参数
var y = objDate.getFullYear()
var m = objDate.getMonth() + 1
var d = objDate.getDate()
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
for (i = 1900; i < 2101 && offset > 0; i++) {
temp = this.lYearDays(i)
offset -= temp
}
if (offset < 0) {
offset += temp; i--
}
// 是否今天
var isTodayObj = new Date()
var isToday = false
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
isToday = true
}
// 星期几
var nWeek = objDate.getDay()
var cWeek = this.nStr1[nWeek]
// 数字表示周几顺应天朝周一开始的惯例
if (nWeek == 0) {
nWeek = 7
}
// 农历年
var year = i
var leap = this.leapMonth(i) // 闰哪个月
var isLeap = false
// 效验闰月
for (i = 1; i < 13 && offset > 0; i++) {
// 闰月
if (leap > 0 && i == (leap + 1) && isLeap == false) {
--i
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
} else {
temp = this.monthDays(year, i)// 计算农历普通月天数
}
// 解除闰月
if (isLeap == true && i == (leap + 1)) { isLeap = false }
offset -= temp
}
// 闰月导致数组下标重叠取反
if (offset == 0 && leap > 0 && i == leap + 1) {
if (isLeap) {
isLeap = false
} else {
isLeap = true; --i
}
}
if (offset < 0) {
offset += temp; --i
}
// 农历月
var month = i
// 农历日
var day = offset + 1
// 天干地支处理
var sm = m - 1
var gzY = this.toGanZhiYear(year)
// 当月的两个节气
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
// 依据12节气修正干支月
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
if (d >= firstNode) {
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
}
// 传入的日期的节气与否
var isTerm = false
var Term = null
if (firstNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 2]
}
if (secondNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 1]
}
// 日柱 当月一日与 1900/1/1 相差天数
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
var gzD = this.toGanZhi(dayCyclical + d - 1)
// 该日期所属的星座
var astro = this.toAstro(m, d)
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
},
/**
* 传入农历年月日以及传入的月份是否闰月获得详细的公历农历object信息 <=>JSON
* @param y lunar year
* @param m lunar month
* @param d lunar day
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
* @return JSON object
* @eg:console.log(calendar.lunar2solar(1987,9,10));
*/
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
var isLeapMonth = !!isLeapMonth
var leapOffset = 0
var leapMonth = this.leapMonth(y)
var leapDay = this.leapDays(y)
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
var day = this.monthDays(y, m)
var _day = day
// bugFix 2016-9-25
// if month is leap, _day use leapDays method
if (isLeapMonth) {
_day = this.leapDays(y, m)
}
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
// 计算农历的时间差
var offset = 0
for (var i = 1900; i < y; i++) {
offset += this.lYearDays(i)
}
var leap = 0; var isAdd = false
for (var i = 1; i < m; i++) {
leap = this.leapMonth(y)
if (!isAdd) { // 处理闰月
if (leap <= i && leap > 0) {
offset += this.leapDays(y); isAdd = true
}
}
offset += this.monthDays(y, i)
}
// 转换闰月农历 需补充该年闰月的前一个月的时差
if (isLeapMonth) { offset += day }
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
var cY = calObj.getUTCFullYear()
var cM = calObj.getUTCMonth() + 1
var cD = calObj.getUTCDate()
return this.solar2lunar(cY, cM, cD)
}
}
export default calendar

View File

@ -0,0 +1,12 @@
{
"uni-calender.ok": "ok",
"uni-calender.cancel": "cancel",
"uni-calender.today": "today",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN"
}

View File

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@ -0,0 +1,12 @@
{
"uni-calender.ok": "确定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

View File

@ -0,0 +1,12 @@
{
"uni-calender.ok": "確定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

View File

@ -0,0 +1,187 @@
<template>
<view class="uni-calendar-item__weeks-box" :class="{
'uni-calendar-item--disable':weeks.disable,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
}"
@click="choiceDate(weeks)">
<view class="uni-calendar-item__weeks-box-item">
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
<text class="uni-calendar-item__weeks-box-text" :class="{
'uni-calendar-item--isDay-text': weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.date}}</text>
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
'uni-calendar-item--isDay-text':weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
}">{{todayText}}</text>
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
'uni-calendar-item--isDay-text':weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
'uni-calendar-item--extra':weeks.extraInfo.info,
'uni-calendar-item--isDay-text':weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.extraInfo.info}}</text>
</view>
</view>
</template>
<script>
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const { t } = initVueI18n(i18nMessages)
export default {
emits:['change'],
props: {
weeks: {
type: Object,
default () {
return {}
}
},
calendar: {
type: Object,
default: () => {
return {}
}
},
selected: {
type: Array,
default: () => {
return []
}
},
lunar: {
type: Boolean,
default: false
}
},
computed: {
todayText() {
return t("uni-calender.today")
},
},
methods: {
choiceDate(weeks) {
this.$emit('change', weeks)
}
}
}
</script>
<style lang="scss" scoped>
$uni-font-size-base:14px;
$uni-text-color:#333;
$uni-font-size-sm:12px;
$uni-color-error: #e43d33;
$uni-opacity-disabled: 0.3;
$uni-text-color-disable:#c0c0c0;
$uni-primary: #2979ff !default;
.uni-calendar-item__weeks-box {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
}
.uni-calendar-item__weeks-box-text {
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.uni-calendar-item__weeks-lunar-text {
font-size: $uni-font-size-sm;
color: $uni-text-color;
}
.uni-calendar-item__weeks-box-item {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
}
.uni-calendar-item__weeks-box-circle {
position: absolute;
top: 5px;
right: 5px;
width: 8px;
height: 8px;
border-radius: 8px;
background-color: $uni-color-error;
}
.uni-calendar-item--disable {
background-color: rgba(249, 249, 249, $uni-opacity-disabled);
color: $uni-text-color-disable;
}
.uni-calendar-item--isDay-text {
color: $uni-primary;
}
.uni-calendar-item--isDay {
background-color: $uni-primary;
opacity: 0.8;
color: #fff;
}
.uni-calendar-item--extra {
color: $uni-color-error;
opacity: 0.8;
}
.uni-calendar-item--checked {
background-color: $uni-primary;
color: #fff;
opacity: 0.8;
}
.uni-calendar-item--multiple {
background-color: $uni-primary;
color: #fff;
opacity: 0.8;
}
.uni-calendar-item--before-checked {
background-color: #ff5a5f;
color: #fff;
}
.uni-calendar-item--after-checked {
background-color: #ff5a5f;
color: #fff;
}
</style>

View File

@ -0,0 +1,567 @@
<template>
<view class="uni-calendar">
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
<view class="uni-calendar__header-btn-box" @click="close">
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
</view>
<view class="uni-calendar__header-btn-box" @click="confirm">
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
</view>
</view>
<view class="uni-calendar__header">
<view class="uni-calendar__header-btn-box" @click.stop="pre">
<view class="uni-calendar__header-btn uni-calendar--left"></view>
</view>
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
<text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
</picker>
<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>
</view>
<view class="uni-calendar__box">
<view v-if="showMonth" class="uni-calendar__box-bg">
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
</view>
<view class="uni-calendar__weeks">
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
</view>
</view>
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import Calendar from './util.js';
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 日历组件可以查看日期选择任意范围内的日期打点操作常用场景如酒店日期预订火车机票选择购买日期上下班打卡等
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
* @property {String} date 自定义当前时间默认为今天
* @property {Boolean} lunar 显示农历
* @property {String} startDate 日期选择范围-开始日期
* @property {String} endDate 日期选择范围-结束日期
* @property {Boolean} range 范围选择
* @property {Boolean} insert = [true|false] 插入模式,默认为false
* @value true 弹窗模式
* @value false 插入模式
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
* @property {Array} selected 打点期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
* @property {Boolean} showMonth 是否选择月份为背景
* @event {Function} change 日期改变`insert :ture` 时生效
* @event {Function} confirm 确认选择`insert :false` 时生效
* @event {Function} monthSwitch 切换月份时触发
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
*/
export default {
components: {
CalendarItem
},
emits:['close','confirm','change','monthSwitch'],
props: {
date: {
type: String,
default: ''
},
selected: {
type: Array,
default () {
return []
}
},
lunar: {
type: Boolean,
default: false
},
startDate: {
type: String,
default: ''
},
endDate: {
type: String,
default: ''
},
range: {
type: Boolean,
default: false
},
insert: {
type: Boolean,
default: true
},
showMonth: {
type: Boolean,
default: true
},
clearDate: {
type: Boolean,
default: true
}
},
data() {
return {
show: false,
weeks: [],
calendar: {},
nowDate: '',
aniMaskShow: false
}
},
computed:{
/**
* for i18n
*/
okText() {
return t("uni-calender.ok")
},
cancelText() {
return t("uni-calender.cancel")
},
todayText() {
return t("uni-calender.today")
},
monText() {
return t("uni-calender.MON")
},
TUEText() {
return t("uni-calender.TUE")
},
WEDText() {
return t("uni-calender.WED")
},
THUText() {
return t("uni-calender.THU")
},
FRIText() {
return t("uni-calender.FRI")
},
SATText() {
return t("uni-calender.SAT")
},
SUNText() {
return t("uni-calender.SUN")
},
},
watch: {
date(newVal) {
// this.cale.setDate(newVal)
this.init(newVal)
},
startDate(val){
this.cale.resetSatrtDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
endDate(val){
this.cale.resetEndDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
selected(newVal) {
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
this.weeks = this.cale.weeks
}
},
created() {
this.cale = new Calendar({
selected: this.selected,
startDate: this.startDate,
endDate: this.endDate,
range: this.range,
})
this.init(this.date)
},
methods: {
// 穿
clean() {},
bindDateChange(e) {
const value = e.detail.value + '-1'
this.setDate(value)
const { year,month } = this.cale.getDate(value)
this.$emit('monthSwitch', {
year,
month
})
},
/**
* 初始化日期显示
* @param {Object} date
*/
init(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.calendar = this.cale.getInfo(date)
},
/**
* 打开日历弹窗
*/
open() {
//
if (this.clearDate && !this.insert) {
this.cale.cleanMultipleStatus()
// this.cale.setDate(this.date)
this.init(this.date)
}
this.show = true
this.$nextTick(() => {
setTimeout(() => {
this.aniMaskShow = true
}, 50)
})
},
/**
* 关闭日历弹窗
*/
close() {
this.aniMaskShow = false
this.$nextTick(() => {
setTimeout(() => {
this.show = false
this.$emit('close')
}, 300)
})
},
/**
* 确认按钮
*/
confirm() {
this.setEmit('confirm')
this.close()
},
/**
* 变化触发
*/
change() {
if (!this.insert) return
this.setEmit('change')
},
/**
* 选择月份触发
*/
monthSwitch() {
let {
year,
month
} = this.nowDate
this.$emit('monthSwitch', {
year,
month: Number(month)
})
},
/**
* 派发事件
* @param {Object} name
*/
setEmit(name) {
let {
year,
month,
date,
fullDate,
lunar,
extraInfo
} = this.calendar
this.$emit(name, {
range: this.cale.multipleStatus,
year,
month,
date,
fulldate: fullDate,
lunar,
extraInfo: extraInfo || {}
})
},
/**
* 选择天触发
* @param {Object} weeks
*/
choiceDate(weeks) {
if (weeks.disable) return
this.calendar = weeks
//
this.cale.setMultiple(this.calendar.fullDate)
this.weeks = this.cale.weeks
this.change()
},
/**
* 回到今天
*/
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()
},
/**
* 上个月
*/
pre() {
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
this.setDate(preDate)
this.monthSwitch()
},
/**
* 下个月
*/
next() {
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
this.setDate(nextDate)
this.monthSwitch()
},
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.cale.getInfo(date)
}
}
}
</script>
<style lang="scss" scoped>
$uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
$uni-border-color: #EDEDED;
$uni-text-color: #333;
$uni-bg-color-hover:#f1f1f1;
$uni-font-size-base:14px;
$uni-text-color-placeholder: #808080;
$uni-color-subtitle: #555555;
$uni-text-color-grey:#999;
.uni-calendar {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.uni-calendar__mask {
position: fixed;
bottom: 0;
top: 0;
left: 0;
right: 0;
background-color: $uni-bg-color-mask;
transition-property: opacity;
transition-duration: 0.3s;
opacity: 0;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--mask-show {
opacity: 1
}
.uni-calendar--fixed {
position: fixed;
/* #ifdef APP-NVUE */
bottom: 0;
/* #endif */
left: 0;
right: 0;
transition-property: transform;
transition-duration: 0.3s;
transform: translateY(460px);
/* #ifndef APP-NVUE */
bottom: calc(var(--window-bottom));
z-index: 99;
/* #endif */
}
.uni-calendar--ani-show {
transform: translateY(0);
}
.uni-calendar__content {
background-color: #fff;
}
.uni-calendar__header {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
height: 50px;
border-bottom-color: $uni-border-color;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.uni-calendar--fixed-top {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 1px;
}
.uni-calendar--fixed-width {
width: 50px;
}
.uni-calendar__backtoday {
position: absolute;
right: 0;
top: 25rpx;
padding: 0 5px;
padding-left: 10px;
height: 25px;
line-height: 25px;
font-size: 12px;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
color: $uni-text-color;
background-color: $uni-bg-color-hover;
}
.uni-calendar__header-text {
text-align: center;
width: 100px;
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.uni-calendar__header-btn-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
}
.uni-calendar__header-btn {
width: 10px;
height: 10px;
border-left-color: $uni-text-color-placeholder;
border-left-style: solid;
border-left-width: 2px;
border-top-color: $uni-color-subtitle;
border-top-style: solid;
border-top-width: 2px;
}
.uni-calendar--left {
transform: rotate(-45deg);
}
.uni-calendar--right {
transform: rotate(135deg);
}
.uni-calendar__weeks {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-calendar__weeks-item {
flex: 1;
}
.uni-calendar__weeks-day {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
height: 45px;
border-bottom-color: #F5F5F5;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.uni-calendar__weeks-day-text {
font-size: 14px;
}
.uni-calendar__box {
position: relative;
}
.uni-calendar__box-bg {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.uni-calendar__box-bg-text {
font-size: 200px;
font-weight: bold;
color: $uni-text-color-grey;
opacity: 0.1;
text-align: center;
/* #ifndef APP-NVUE */
line-height: 1;
/* #endif */
}
</style>

View File

@ -0,0 +1,360 @@
import CALENDAR from './calendar.js'
class Calendar {
constructor({
date,
selected,
startDate,
endDate,
range
} = {}) {
// 当前日期
this.date = this.getDate(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 范围开始
this.startDate = startDate
// 范围结束
this.endDate = endDate
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
// this._getWeek(this.date.fullDate)
}
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.selectDate = this.getDate(date)
this._getWeek(this.selectDate.fullDate)
}
/**
* 清理多选状态
*/
cleanMultipleStatus() {
this.multipleStatus = {
before: '',
after: '',
data: []
}
}
/**
* 重置开始日期
*/
resetSatrtDate(startDate) {
// 范围开始
this.startDate = startDate
}
/**
* 重置结束日期
*/
resetEndDate(endDate) {
// 范围结束
this.endDate = endDate
}
/**
* 获取任意时间
*/
getDate(date, AddDayCount = 0, str = 'day') {
if (!date) {
date = new Date()
}
if (typeof date !== 'object') {
date = date.replace(/-/g, '/')
}
const dd = new Date(date)
switch (str) {
case 'day':
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31 && AddDayCount>0) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
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':
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
break
}
const y = dd.getFullYear()
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期不足10补0
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号不足10补0
return {
fullDate: y + '-' + m + '-' + d,
year: y,
month: m,
date: d,
day: dd.getDay()
}
}
/**
* 获取上月剩余天数
*/
_getLastMonthDays(firstDay, full) {
let dateArr = []
for (let i = firstDay; i > 0; i--) {
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
dateArr.push({
date: beforeDate,
month: full.month - 1,
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
disable: true
})
}
return dateArr
}
/**
* 获取本月天数
*/
_currentMonthDys(dateData, full) {
let dateArr = []
let fullDate = this.date.fullDate
for (let i = 1; i <= dateData; i++) {
let nowDate = full.year + '-' + (full.month < 10 ?
full.month : full.month) + '-' + (i < 10 ?
'0' + i : i)
// 是否今天
let isDay = fullDate === nowDate
// 获取打点信息
let info = this.selected && this.selected.find((item) => {
if (this.dateEqual(nowDate, item.date)) {
return item
}
})
// 日期禁用
let disableBefore = true
let disableAfter = true
if (this.startDate) {
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
disableBefore = this.dateCompare(this.startDate, nowDate)
}
if (this.endDate) {
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
disableAfter = this.dateCompare(nowDate, this.endDate)
}
let multiples = this.multipleStatus.data
let checked = false
let multiplesStatus = -1
if (this.range) {
if (multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, nowDate)
})
}
if (multiplesStatus !== -1) {
checked = true
}
}
let data = {
fullDate: nowDate,
year: full.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
month: full.month,
lunar: this.getlunar(full.year, full.month, i),
disable: !(disableBefore && disableAfter),
isDay
}
if (info) {
data.extraInfo = info
}
dateArr.push(data)
}
return dateArr
}
/**
* 获取下月天数
*/
_getNextMonthDays(surplus, full) {
let dateArr = []
for (let i = 1; i < surplus + 1; i++) {
dateArr.push({
date: i,
month: Number(full.month) + 1,
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
disable: true
})
}
return dateArr
}
/**
* 获取当前日期详情
* @param {Object} date
*/
getInfo(date) {
if (!date) {
date = new Date()
}
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
return dateInfo
}
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
// 计算截止时间
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
}
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
// 计算截止时间
before = new Date(before.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
after = new Date(after.replace('-', '/').replace('-', '/'))
if (before.getTime() - after.getTime() === 0) {
return true
} else {
return false
}
}
/**
* 获取日期范围内所有日期
* @param {Object} begin
* @param {Object} end
*/
geDateAll(begin, end) {
var arr = []
var ab = begin.split('-')
var ae = end.split('-')
var db = new Date()
db.setFullYear(ab[0], ab[1] - 1, ab[2])
var de = new Date()
de.setFullYear(ae[0], ae[1] - 1, ae[2])
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
}
return arr
}
/**
* 计算阴历日期显示
*/
getlunar(year, month, date) {
return CALENDAR.solar2lunar(year, month, date)
}
/**
* 设置打点
*/
setSelectInfo(data, value) {
this.selected = value
this._getWeek(data)
}
/**
* 获取多选状态
*/
setMultiple(fullDate) {
let {
before,
after
} = this.multipleStatus
if (!this.range) return
if (before && after) {
this.multipleStatus.before = ''
this.multipleStatus.after = ''
this.multipleStatus.data = []
} else {
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
}
this._getWeek(fullDate)
}
/**
* 获取每周数据
* @param {Object} dateData
*/
_getWeek(dateData) {
const {
year,
month
} = this.getDate(dateData)
let firstDay = new Date(year, month - 1, 1).getDay()
let currentDay = new Date(year, month, 0).getDate()
let dates = {
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
nextMonthDays: [], // 下个月开始几天
weeks: []
}
let canlender = []
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
let weeks = {}
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
for (let i = 0; i < canlender.length; i++) {
if (i % 7 === 0) {
weeks[parseInt(i / 7)] = new Array(7)
}
weeks[parseInt(i / 7)][i % 7] = canlender[i]
}
this.canlender = canlender
this.weeks = weeks
}
//静态方法
// static init(date) {
// if (!this.instance) {
// this.instance = new Calendar(date);
// }
// return this.instance;
// }
}
export default Calendar

View File

@ -0,0 +1,85 @@
{
"id": "uni-calendar",
"displayName": "uni-calendar 日历",
"version": "1.4.11",
"description": "日历组件",
"keywords": [
"uni-ui",
"uniui",
"日历",
"",
"打卡",
"日历选择"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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"
}
}
}
}
}

View File

@ -0,0 +1,103 @@
## Calendar 日历
> **组件名uni-calendar**
> 代码块: `uCalendar`
日历组件
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
> - 仅支持自定义组件模式
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 基本用法
在 ``template`` 中使用组件
```html
<view>
<uni-calendar
:insert="true"
:lunar="true"
:start-date="'2019-3-2'"
:end-date="'2019-5-20'"
@change="change"
/>
</view>
```
### 通过方法打开日历
需要设置 `insert``false`
```html
<view>
<uni-calendar
ref="calendar"
:insert="false"
@confirm="confirm"
/>
<button @click="open">打开日历</button>
</view>
```
```javascript
export default {
data() {
return {};
},
methods: {
open(){
this.$refs.calendar.open();
},
confirm(e) {
console.log(e);
}
}
};
```
## API
### Calendar Props
| 属性名 | 类型 | 默认值| 说明 |
| - | - | - | - |
| date | String |- | 自定义当前时间,默认为今天 |
| lunar | Boolean | false | 显示农历 |
| startDate | String |- | 日期选择范围-开始日期 |
| endDate | String |- | 日期选择范围-结束日期 |
| range | Boolean | false | 范围选择 |
| insert | Boolean | false | 插入模式,可选值ture插入模式false弹窗模式默认为插入模式 |
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|showMonth | Boolean | true | 是否显示月份为背景 |
### Calendar Events
| 事件名 | 说明 |返回值|
| - | - | - |
| open | 弹出日历组件,`insert :false` 时生效|- |
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)

View File

@ -0,0 +1,26 @@
## 1.3.12021-12-20
- 修复 在vue页面下略缩图显示不正常的bug
## 1.3.02021-11-19
- 重构插槽的用法 header 替换为 title
- 新增 actions 插槽
- 新增 cover 封面图属性和插槽
- 新增 padding 内容默认内边距离
- 新增 margin 卡片默认外边距离
- 新增 spacing 卡片默认内边距
- 新增 shadow 卡片阴影属性
- 取消 mode 属性,可使用组合插槽代替
- 取消 note 属性 使用actions插槽代替
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
## 1.2.12021-07-30
- 优化 vue3下事件警告的问题
## 1.2.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.82021-07-01
- 优化 图文卡片无图片加载时,提供占位图标
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
- 修复 thumbnail 不存在仍然占位的 bug
## 1.1.72021-05-12
- 新增 组件示例地址
## 1.1.62021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,270 @@
<template>
<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
<!-- 封面 -->
<slot name="cover">
<view v-if="cover" class="uni-card__cover">
<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
</view>
</slot>
<slot name="title">
<view v-if="title || extra" class="uni-card__header">
<!-- 卡片标题 -->
<view class="uni-card__header-box" @click="onClick('title')">
<view v-if="thumbnail" class="uni-card__header-avatar">
<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
</view>
<view class="uni-card__header-content">
<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
<text v-if="title&&subTitle"
class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
</view>
</view>
<view class="uni-card__header-extra" @click="onClick('extra')">
<text class="uni-card__header-extra-text">{{ extra }}</text>
</view>
</view>
</slot>
<!-- 卡片内容 -->
<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
<slot></slot>
</view>
<view class="uni-card__actions" @click="onClick('actions')">
<slot name="actions"></slot>
</view>
</view>
</template>
<script>
/**
* Card 卡片
* @description 卡片视图组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
* @property {String} title 标题文字
* @property {String} subTitle 副标题
* @property {Number} padding 内容内边距
* @property {Number} margin 卡片外边距
* @property {Number} spacing 卡片内边距
* @property {String} extra 标题额外信息
* @property {String} cover 封面图本地路径需要引入
* @property {String} thumbnail 标题左侧缩略图
* @property {Boolean} is-full = [true | false] 卡片内容是否通栏 true 时将去除padding值
* @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
* @property {String} shadow 卡片阴影
* @property {Boolean} border 卡片边框
* @event {Function} click 点击 Card 触发事件
*/
export default {
name: 'UniCard',
emits: ['click'],
props: {
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
},
padding: {
type: String,
default: '10px'
},
margin: {
type: String,
default: '15px'
},
spacing: {
type: String,
default: '0 10px'
},
extra: {
type: String,
default: ''
},
cover: {
type: String,
default: ''
},
thumbnail: {
type: String,
default: ''
},
isFull: {
//
type: Boolean,
default: false
},
isShadow: {
//
type: Boolean,
default: true
},
shadow: {
type: String,
default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
},
border: {
type: Boolean,
default: true
}
},
methods: {
onClick(type) {
this.$emit('click', type)
}
}
}
</script>
<style lang="scss">
$uni-border-3: #EBEEF5 !default;
$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
$uni-main-color: #3a3a3a !default;
$uni-base-color: #6a6a6a !default;
$uni-secondary-color: #909399 !default;
$uni-spacing-sm: 8px !default;
$uni-border-color:$uni-border-3;
$uni-shadow: $uni-shadow-base;
$uni-card-title: 15px;
$uni-cart-title-color:$uni-main-color;
$uni-card-subtitle: 12px;
$uni-cart-subtitle-color:$uni-secondary-color;
$uni-card-spacing: 10px;
$uni-card-content-color: $uni-base-color;
.uni-card {
margin: $uni-card-spacing;
padding: 0 $uni-spacing-sm;
border-radius: 4px;
overflow: hidden;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
background-color: #fff;
flex: 1;
.uni-card__cover {
position: relative;
margin-top: $uni-card-spacing;
flex-direction: row;
overflow: hidden;
border-radius: 4px;
.uni-card__cover-image {
flex: 1;
// width: 100%;
/* #ifndef APP-PLUS */
vertical-align: middle;
/* #endif */
}
}
.uni-card__header {
display: flex;
border-bottom: 1px $uni-border-color solid;
flex-direction: row;
align-items: center;
padding: $uni-card-spacing;
overflow: hidden;
.uni-card__header-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
overflow: hidden;
}
.uni-card__header-avatar {
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 5px;
margin-right: $uni-card-spacing;
.uni-card__header-avatar-image {
flex: 1;
width: 40px;
height: 40px;
}
}
.uni-card__header-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
flex: 1;
// height: 40px;
overflow: hidden;
.uni-card__header-content-title {
font-size: $uni-card-title;
color: $uni-cart-title-color;
// line-height: 22px;
}
.uni-card__header-content-subtitle {
font-size: $uni-card-subtitle;
margin-top: 5px;
color: $uni-cart-subtitle-color;
}
}
.uni-card__header-extra {
line-height: 12px;
.uni-card__header-extra-text {
font-size: 12px;
color: $uni-cart-subtitle-color;
}
}
}
.uni-card__content {
padding: $uni-card-spacing;
font-size: 14px;
color: $uni-card-content-color;
line-height: 22px;
}
.uni-card__actions {
font-size: 12px;
}
}
.uni-card--border {
border: 1px solid $uni-border-color;
}
.uni-card--shadow {
position: relative;
/* #ifndef APP-NVUE */
box-shadow: $uni-shadow;
/* #endif */
}
.uni-card--full {
margin: 0;
border-left-width: 0;
border-left-width: 0;
border-radius: 0;
}
/* #ifndef APP-NVUE */
.uni-card--full:after {
border-radius: 0;
}
/* #endif */
.uni-ellipsis {
/* #ifndef APP-NVUE */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
}
</style>

View File

@ -0,0 +1,90 @@
{
"id": "uni-card",
"displayName": "uni-card 卡片",
"version": "1.3.1",
"description": "Card 组件,提供常见的卡片样式。",
"keywords": [
"uni-ui",
"uniui",
"card",
"",
"卡片"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-icons",
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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"
}
}
}
}
}

View File

@ -0,0 +1,12 @@
## Card 卡片
> **组件名uni-card**
> 代码块: `uCard`
卡片视图组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,38 @@
## 1.4.42024-03-20
- 修复 titleBorder类型修正
## 1.4.32022-01-25
- 修复 初始化的时候 open 属性失效的bug
## 1.4.22022-01-21
- 修复 微信小程序resize后组件收起的bug
## 1.4.12021-11-22
- 修复 vue3中个别scss变量无法找到的问题
## 1.4.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
## 1.3.32021-08-17
- 优化 show-arrow 属性默认为true
## 1.3.22021-08-17
- 新增 show-arrow 属性,控制是否显示右侧箭头
## 1.3.12021-07-30
- 优化 vue3下小程序事件警告的问题
## 1.3.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.2.22021-07-21
- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
## 1.2.12021-07-21
- 优化 组件示例
## 1.2.02021-07-21
- 新增 组件折叠动画
- 新增 value\v-model 属性 ,动态修改面板折叠状态
- 新增 title 插槽 ,可定义面板标题
- 新增 border 属性 ,显示隐藏面板内容分隔线
- 新增 title-border 属性 ,显示隐藏面板标题分隔线
- 修复 resize 方法失效的Bug
- 修复 change 事件返回参数不正确的Bug
- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
## 1.1.72021-05-12
- 新增 组件示例地址
## 1.1.62021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 1.1.52021-02-05
- 调整为uni_modules目录规范

View File

@ -0,0 +1,402 @@
<template>
<view class="uni-collapse-item">
<!-- onClick(!isOpen) -->
<view @click="onClick(!isOpen)" class="uni-collapse-item__title"
:class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}">
<view class="uni-collapse-item__title-wrap">
<slot name="title">
<view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}">
<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
<text class="uni-collapse-item__title-text">{{ title }}</text>
</view>
</slot>
</view>
<view v-if="showArrow"
:class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }"
class="uni-collapse-item__title-arrow">
<uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" />
</view>
</view>
<view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}"
:style="{height: (isOpen?height:0) +'px'}">
<view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content"
:class="{open:isheight,'uni-collapse-item--border':border&&isOpen}">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
/**
* CollapseItem 折叠面板子组件
* @description 折叠面板子组件
* @property {String} title 标题文字
* @property {String} thumb 标题左侧缩略图
* @property {String} name 唯一标志符
* @property {Boolean} open = [true|false] 是否展开组件
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
* @property {Boolean} disabled = [true|false] 是否展开面板
* @property {Boolean} showAnimation = [true|false] 开启动画
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
*/
export default {
name: 'uniCollapseItem',
props: {
//
title: {
type: String,
default: ''
},
name: {
type: [Number, String],
default: ''
},
//
disabled: {
type: Boolean,
default: false
},
// #ifdef APP-PLUS
// ,app
showAnimation: {
type: Boolean,
default: false
},
// #endif
// #ifndef APP-PLUS
//
showAnimation: {
type: Boolean,
default: true
},
// #endif
//
open: {
type: Boolean,
default: false
},
//
thumb: {
type: String,
default: ''
},
// 线
titleBorder: {
type: String,
default: 'auto'
},
border: {
type: Boolean,
default: true
},
showArrow: {
type: Boolean,
default: true
}
},
data() {
// TODO IDbug
const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
return {
isOpen: false,
isheight: null,
height: 0,
elId,
nameSync: 0
}
},
watch: {
open(val) {
this.isOpen = val
this.onClick(val, 'init')
}
},
updated(e) {
this.$nextTick(() => {
this.init(true)
})
},
created() {
this.collapse = this.getCollapse()
this.oldHeight = 0
this.onClick(this.open, 'init')
},
// #ifndef VUE3
// TODO vue2
destroyed() {
if (this.__isUnmounted) return
this.uninstall()
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.__isUnmounted = true
this.uninstall()
},
// #endif
mounted() {
if (!this.collapse) return
if (this.name !== '') {
this.nameSync = this.name
} else {
this.nameSync = this.collapse.childrens.length + ''
}
if (this.collapse.names.indexOf(this.nameSync) === -1) {
this.collapse.names.push(this.nameSync)
} else {
console.warn(`name 值 ${this.nameSync} 重复`);
}
if (this.collapse.childrens.indexOf(this) === -1) {
this.collapse.childrens.push(this)
}
this.init()
},
methods: {
init(type) {
// #ifndef APP-NVUE
this.getCollapseHeight(type)
// #endif
// #ifdef APP-NVUE
this.getNvueHwight(type)
// #endif
},
uninstall() {
if (this.collapse) {
this.collapse.childrens.forEach((item, index) => {
if (item === this) {
this.collapse.childrens.splice(index, 1)
}
})
this.collapse.names.forEach((item, index) => {
if (item === this.nameSync) {
this.collapse.names.splice(index, 1)
}
})
}
},
onClick(isOpen, type) {
if (this.disabled) return
this.isOpen = isOpen
if (this.isOpen && this.collapse) {
this.collapse.setAccordion(this)
}
if (type !== 'init') {
this.collapse.onChange(isOpen, this)
}
},
getCollapseHeight(type, index = 0) {
const views = uni.createSelectorQuery().in(this)
views
.select(`#${this.elId}`)
.fields({
size: true
}, data => {
// TODO
if (index >= 10) return
if (!data) {
index++
this.getCollapseHeight(false, index)
return
}
// #ifdef APP-NVUE
this.height = data.height + 1
// #endif
// #ifndef APP-NVUE
this.height = data.height
// #endif
this.isheight = true
if (type) return
this.onClick(this.isOpen, 'init')
})
.exec()
},
getNvueHwight(type) {
const result = dom.getComponentRect(this.$refs['collapse--hook'], option => {
if (option && option.result && option.size) {
// #ifdef APP-NVUE
this.height = option.size.height + 1
// #endif
// #ifndef APP-NVUE
this.height = option.size.height
// #endif
this.isheight = true
if (type) return
this.onClick(this.open, 'init')
}
})
},
/**
* 获取父元素实例
*/
getCollapse(name = 'uniCollapse') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
}
}
}
</script>
<style lang="scss">
.uni-collapse-item {
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
&__title {
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
box-sizing: border-box;
/* #endif */
flex-direction: row;
align-items: center;
transition: border-bottom-color .3s;
// transition-property: border-bottom-color;
// transition-duration: 5s;
&-wrap {
width: 100%;
flex: 1;
}
&-box {
padding: 0 15px;
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
box-sizing: border-box;
/* #endif */
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 48px;
line-height: 48px;
background-color: #fff;
color: #303133;
font-size: 13px;
font-weight: 500;
/* #ifdef H5 */
cursor: pointer;
outline: none;
/* #endif */
&.is-disabled {
.uni-collapse-item__title-text {
color: #999;
}
}
}
&.uni-collapse-item-border {
border-bottom: 1px solid #ebeef5;
}
&.is-open {
border-bottom-color: transparent;
}
&-img {
height: 22px;
width: 22px;
margin-right: 10px;
}
&-text {
flex: 1;
font-size: 14px;
/* #ifndef APP-NVUE */
white-space: nowrap;
color: inherit;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
overflow: hidden;
text-overflow: ellipsis;
}
&-arrow {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin-right: 10px;
transform: rotate(0deg);
&-active {
transform: rotate(-180deg);
}
}
}
&__wrap {
/* #ifndef APP-NVUE */
will-change: height;
box-sizing: border-box;
/* #endif */
background-color: #fff;
overflow: hidden;
position: relative;
height: 0;
&.is--transition {
// transition: all 0.3s;
transition-property: height, border-bottom-width;
transition-duration: 0.3s;
/* #ifndef APP-NVUE */
will-change: height;
/* #endif */
}
&-content {
position: absolute;
font-size: 13px;
color: #303133;
// transition: height 0.3s;
border-bottom-color: transparent;
border-bottom-style: solid;
border-bottom-width: 0;
&.uni-collapse-item--border {
border-bottom-width: 1px;
border-bottom-color: red;
border-bottom-color: #ebeef5;
}
&.open {
position: relative;
}
}
}
&--animation {
transition-property: transform;
transition-duration: 0.3s;
transition-timing-function: ease;
}
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<view class="uni-collapse">
<slot />
</view>
</template>
<script>
/**
* Collapse 折叠面板
* @description 展示可以折叠 / 展开的内容区域
* @tutorial https://ext.dcloud.net.cn/plugin?id=23
* @property {String|Array} value 当前激活面板改变时触发(如果是手风琴模式参数类型为string否则为array)
* @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
* @event {Function} change 切换面板时触发如果是手风琴模式返回类型为string否则为array
*/
export default {
name: 'uniCollapse',
emits:['change','activeItem','input','update:modelValue'],
props: {
value: {
type: [String, Array],
default: ''
},
modelValue: {
type: [String, Array],
default: ''
},
accordion: {
//
type: [Boolean, String],
default: false
},
},
data() {
return {}
},
computed: {
// TODO vue2 vue3
dataValue() {
let value = (typeof this.value === 'string' && this.value === '') ||
(Array.isArray(this.value) && this.value.length === 0)
let modelValue = (typeof this.modelValue === 'string' && this.modelValue === '') ||
(Array.isArray(this.modelValue) && this.modelValue.length === 0)
if (value) {
return this.modelValue
}
if (modelValue) {
return this.value
}
return this.value
}
},
watch: {
dataValue(val) {
this.setOpen(val)
}
},
created() {
this.childrens = []
this.names = []
},
mounted() {
this.$nextTick(()=>{
this.setOpen(this.dataValue)
})
},
methods: {
setOpen(val) {
let str = typeof val === 'string'
let arr = Array.isArray(val)
this.childrens.forEach((vm, index) => {
if (str) {
if (val === vm.nameSync) {
if (!this.accordion) {
console.warn('accordion 属性为 false ,v-model 类型应该为 array')
return
}
vm.isOpen = true
}
}
if (arr) {
val.forEach(v => {
if (v === vm.nameSync) {
if (this.accordion) {
console.warn('accordion 属性为 true ,v-model 类型应该为 string')
return
}
vm.isOpen = true
}
})
}
})
this.emit(val)
},
setAccordion(self) {
if (!this.accordion) return
this.childrens.forEach((vm, index) => {
if (self !== vm) {
vm.isOpen = false
}
})
},
resize() {
this.childrens.forEach((vm, index) => {
// #ifndef APP-NVUE
vm.getCollapseHeight()
// #endif
// #ifdef APP-NVUE
vm.getNvueHwight()
// #endif
})
},
onChange(isOpen, self) {
let activeItem = []
if (this.accordion) {
activeItem = isOpen ? self.nameSync : ''
} else {
this.childrens.forEach((vm, index) => {
if (vm.isOpen) {
activeItem.push(vm.nameSync)
}
})
}
this.$emit('change', activeItem)
this.emit(activeItem)
},
emit(val){
this.$emit('input', val)
this.$emit('update:modelValue', val)
}
}
}
</script>
<style lang="scss" >
.uni-collapse {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
flex-direction: column;
background-color: #fff;
}
</style>

View File

@ -0,0 +1,86 @@
{
"id": "uni-collapse",
"displayName": "uni-collapse 折叠面板",
"version": "1.4.4",
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
"keywords": [
"uni-ui",
"折叠",
"折叠面板",
"手风琴"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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"
}
}
}
}
}

View File

@ -0,0 +1,12 @@
## Collapse 折叠面板
> **组件名uni-collapse**
> 代码块: `uCollapse`
> 关联组件:`uni-collapse-item`、`uni-icons`。
折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,15 @@
## 1.0.12021-11-23
- 优化 label、label-width 属性
## 1.0.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
## 0.1.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.0.62021-05-12
- 新增 组件示例地址
## 0.0.52021-04-21
- 优化 添加依赖 uni-icons, 导入后自动下载依赖
## 0.0.42021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 0.0.32021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,275 @@
<template>
<view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
<view v-if="label" class="uni-combox__label" :style="labelStyle">
<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">
</uni-icons>
</view>
<view class="uni-combox__selector" v-if="showSelector">
<view class="uni-popper__arrow"></view>
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
<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)">
<text>{{item}}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
/**
* Combox 组合输入框
* @description 组合输入框一般用于既可以输入也可以选择的场景
* @tutorial https://ext.dcloud.net.cn/plugin?id=1261
* @property {String} label 左侧文字
* @property {String} labelWidth 左侧内容宽度
* @property {String} placeholder 输入框占位符
* @property {Array} candidates 候选项列表
* @property {String} emptyTips 筛选结果为空时显示的文字
* @property {String} value 组合框的值
*/
export default {
name: 'uniCombox',
emits: ['input', 'update:modelValue'],
props: {
border: {
type: Boolean,
default: true
},
label: {
type: String,
default: ''
},
labelWidth: {
type: String,
default: 'auto'
},
placeholder: {
type: String,
default: ''
},
candidates: {
type: Array,
default () {
return []
}
},
emptyTips: {
type: String,
default: '无匹配项'
},
// #ifndef VUE3
value: {
type: [String, Number],
default: ''
},
// #endif
// #ifdef VUE3
modelValue: {
type: [String, Number],
default: ''
},
// #endif
},
data() {
return {
showSelector: false,
inputVal: ''
}
},
computed: {
labelStyle() {
if (this.labelWidth === 'auto') {
return ""
}
return `width: ${this.labelWidth}`
},
filterCandidates() {
return this.candidates.filter((item) => {
return item.toString().indexOf(this.inputVal) > -1
})
},
filterCandidatesLength() {
return this.filterCandidates.length
}
},
watch: {
// #ifndef VUE3
value: {
handler(newVal) {
this.inputVal = newVal
},
immediate: true
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal) {
this.inputVal = newVal
},
immediate: true
},
// #endif
},
methods: {
toggleSelector() {
this.showSelector = !this.showSelector
},
onFocus() {
this.showSelector = true
},
onBlur() {
setTimeout(() => {
this.showSelector = false
}, 153)
},
onSelectorClick(index) {
this.inputVal = this.filterCandidates[index]
this.showSelector = false
this.$emit('input', this.inputVal)
this.$emit('update:modelValue', this.inputVal)
},
onInput() {
setTimeout(() => {
this.$emit('input', this.inputVal)
this.$emit('update:modelValue', this.inputVal)
})
}
}
}
</script>
<style lang="scss" scoped>
.uni-combox {
font-size: 14px;
border: 1px solid #DCDFE6;
border-radius: 4px;
padding: 6px 10px;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
// height: 40px;
flex-direction: row;
align-items: center;
// border-bottom: solid 1px #DDDDDD;
}
.uni-combox__label {
font-size: 16px;
line-height: 22px;
padding-right: 10px;
color: #999999;
}
.uni-combox__input-box {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
}
.uni-combox__input {
flex: 1;
font-size: 14px;
height: 22px;
line-height: 22px;
}
.uni-combox__input-plac {
font-size: 14px;
color: #999;
}
.uni-combox__selector {
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
position: absolute;
top: calc(100% + 12px);
left: 0;
width: 100%;
background-color: #FFFFFF;
border: 1px solid #EBEEF5;
border-radius: 6px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 2;
padding: 4px 0;
}
.uni-combox__selector-scroll {
/* #ifndef APP-NVUE */
max-height: 200px;
box-sizing: border-box;
/* #endif */
}
.uni-combox__selector-empty,
.uni-combox__selector-item {
/* #ifndef APP-NVUE */
display: flex;
cursor: pointer;
/* #endif */
line-height: 36px;
font-size: 14px;
text-align: center;
// border-bottom: solid 1px #DDDDDD;
padding: 0px 10px;
}
.uni-combox__selector-item:hover {
background-color: #f9f9f9;
}
.uni-combox__selector-empty:last-child,
.uni-combox__selector-item:last-child {
/* #ifndef APP-NVUE */
border-bottom: none;
/* #endif */
}
// picker
.uni-popper__arrow,
.uni-popper__arrow::after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
.uni-popper__arrow {
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
top: -6px;
left: 10%;
margin-right: 3px;
border-top-width: 0;
border-bottom-color: #EBEEF5;
}
.uni-popper__arrow::after {
content: " ";
top: 1px;
margin-left: -6px;
border-top-width: 0;
border-bottom-color: #fff;
}
.uni-combox__no-border {
border: none;
}
</style>

View File

@ -0,0 +1,90 @@
{
"id": "uni-combox",
"displayName": "uni-combox 组合框",
"version": "1.0.1",
"description": "可以选择也可以输入的表单项 ",
"keywords": [
"uni-ui",
"uniui",
"combox",
"组合框",
"select"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"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",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,11 @@
## Combox 组合框
> **组件名uni-combox**
> 代码块: `uCombox`
组合框组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,26 @@
## 1.2.32024-02-20
- 新增 支持控制小时分钟的显隐showHour showMinute
## 1.2.22022-01-19
- 修复 在微信小程序中样式不生效的bug
## 1.2.12022-01-18
- 新增 update 方法 ,在动态更新时间后,刷新组件
## 1.2.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
## 1.1.32021-10-18
- 重构
- 新增 font-size 支持自定义字体大小
## 1.1.22021-08-24
- 新增 支持国际化
## 1.1.12021-07-30
- 优化 vue3下小程序事件警告的问题
## 1.1.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.52021-06-18
- 修复 uni-countdown 重复赋值跳两秒的 bug
## 1.0.42021-05-12
- 新增 组件示例地址
## 1.0.32021-05-08
- 修复 uni-countdown 不能控制倒计时的 bug
## 1.0.22021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,6 @@
{
"uni-countdown.day": "day",
"uni-countdown.h": "h",
"uni-countdown.m": "m",
"uni-countdown.s": "s"
}

View File

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@ -0,0 +1,6 @@
{
"uni-countdown.day": "天",
"uni-countdown.h": "时",
"uni-countdown.m": "分",
"uni-countdown.s": "秒"
}

View File

@ -0,0 +1,6 @@
{
"uni-countdown.day": "天",
"uni-countdown.h": "時",
"uni-countdown.m": "分",
"uni-countdown.s": "秒"
}

View File

@ -0,0 +1,281 @@
<template>
<view class="uni-countdown">
<text v-if="showDay" :style="[timeStyle]" class="uni-countdown__number">{{ d }}</text>
<text v-if="showDay" :style="[splitorStyle]" class="uni-countdown__splitor">{{dayText}}</text>
<text v-if="showHour" :style="[timeStyle]" class="uni-countdown__number">{{ h }}</text>
<text v-if="showHour" :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : hourText }}</text>
<text v-if="showMinute" :style="[timeStyle]" class="uni-countdown__number">{{ i }}</text>
<text v-if="showMinute" :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : minuteText }}</text>
<text :style="[timeStyle]" class="uni-countdown__number">{{ s }}</text>
<text v-if="!showColon" :style="[splitorStyle]" class="uni-countdown__splitor">{{secondText}}</text>
</view>
</template>
<script>
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const {
t
} = initVueI18n(messages)
/**
* Countdown 倒计时
* @description 倒计时组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=25
* @property {String} backgroundColor 背景色
* @property {String} color 文字颜色
* @property {Number} day 天数
* @property {Number} hour 小时
* @property {Number} minute 分钟
* @property {Number} second
* @property {Number} timestamp 时间戳
* @property {Boolean} showDay = [true|false] 是否显示天数
* @property {Boolean} showHour = [true|false] 是否显示小时
* @property {Boolean} showMinute = [true|false] 是否显示分钟
* @property {Boolean} show-colon = [true|false] 是否以冒号为分隔符
* @property {String} splitorColor 分割符号颜色
* @event {Function} timeup 倒计时时间到触发事件
* @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown>
*/
export default {
name: 'UniCountdown',
emits: ['timeup'],
props: {
showDay: {
type: Boolean,
default: true
},
showHour: {
type: Boolean,
default: true
},
showMinute: {
type: Boolean,
default: true
},
showColon: {
type: Boolean,
default: true
},
start: {
type: Boolean,
default: true
},
backgroundColor: {
type: String,
default: ''
},
color: {
type: String,
default: '#333'
},
fontSize: {
type: Number,
default: 14
},
splitorColor: {
type: String,
default: '#333'
},
day: {
type: Number,
default: 0
},
hour: {
type: Number,
default: 0
},
minute: {
type: Number,
default: 0
},
second: {
type: Number,
default: 0
},
timestamp: {
type: Number,
default: 0
}
},
data() {
return {
timer: null,
syncFlag: false,
d: '00',
h: '00',
i: '00',
s: '00',
leftTime: 0,
seconds: 0
}
},
computed: {
dayText() {
return t("uni-countdown.day")
},
hourText(val) {
return t("uni-countdown.h")
},
minuteText(val) {
return t("uni-countdown.m")
},
secondText(val) {
return t("uni-countdown.s")
},
timeStyle() {
const {
color,
backgroundColor,
fontSize
} = this
return {
color,
backgroundColor,
fontSize: `${fontSize}px`,
width: `${fontSize * 22 / 14}px`, // 14px
lineHeight: `${fontSize * 20 / 14}px`,
borderRadius: `${fontSize * 3 / 14}px`,
}
},
splitorStyle() {
const { splitorColor, fontSize, backgroundColor } = this
return {
color: splitorColor,
fontSize: `${fontSize * 12 / 14}px`,
margin: backgroundColor ? `${fontSize * 4 / 14}px` : ''
}
}
},
watch: {
day(val) {
this.changeFlag()
},
hour(val) {
this.changeFlag()
},
minute(val) {
this.changeFlag()
},
second(val) {
this.changeFlag()
},
start: {
immediate: true,
handler(newVal, oldVal) {
if (newVal) {
this.startData();
} else {
if (!oldVal) return
clearInterval(this.timer)
}
}
}
},
created: function(e) {
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
this.countDown()
},
// #ifndef VUE3
destroyed() {
clearInterval(this.timer)
},
// #endif
// #ifdef VUE3
unmounted() {
clearInterval(this.timer)
},
// #endif
methods: {
toSeconds(timestamp, day, hours, minutes, seconds) {
if (timestamp) {
return timestamp - parseInt(new Date().getTime() / 1000, 10)
}
return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
},
timeUp() {
clearInterval(this.timer)
this.$emit('timeup')
},
countDown() {
let seconds = this.seconds
let [day, hour, minute, second] = [0, 0, 0, 0]
if (seconds > 0) {
day = Math.floor(seconds / (60 * 60 * 24))
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
} else {
this.timeUp()
}
if (day < 10) {
day = '0' + day
}
if (hour < 10) {
hour = '0' + hour
}
if (minute < 10) {
minute = '0' + minute
}
if (second < 10) {
second = '0' + second
}
this.d = day
this.h = hour
this.i = minute
this.s = second
},
startData() {
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
if (this.seconds <= 0) {
this.seconds = this.toSeconds(0, 0, 0, 0, 0)
this.countDown()
return
}
clearInterval(this.timer)
this.countDown()
this.timer = setInterval(() => {
this.seconds--
if (this.seconds < 0) {
this.timeUp()
return
}
this.countDown()
}, 1000)
},
update(){
this.startData();
},
changeFlag() {
if (!this.syncFlag) {
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
this.startData();
this.syncFlag = true;
}
}
}
}
</script>
<style lang="scss" scoped>
$font-size: 14px;
.uni-countdown {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
&__splitor {
margin: 0 2px;
font-size: $font-size;
color: #333;
}
&__number {
border-radius: 3px;
text-align: center;
font-size: $font-size;
}
}
</style>

View File

@ -0,0 +1,83 @@
{
"id": "uni-countdown",
"displayName": "uni-countdown 倒计时",
"version": "1.2.3",
"description": "CountDown 倒计时组件",
"keywords": [
"uni-ui",
"uniui",
"countdown",
"倒计时"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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"
}
}
}
}
}

View File

@ -0,0 +1,10 @@
## CountDown 倒计时
> **组件名uni-countdown**
> 代码块: `uCountDown`
倒计时组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-countdown)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,49 @@
## 1.0.52024-03-20
- 修复 单选模式下选中样式不生效的bug
## 1.0.42024-01-27
- 修复 修复错别字chagne为change
## 1.0.32022-09-16
- 可以使用 uni-scss 控制主题色
## 1.0.22022-06-30
- 优化 在 uni-forms 中的依赖注入方式
## 1.0.12022-02-07
- 修复 multiple 为 true 时v-model 的值为 null 报错的 bug
## 1.0.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
## 0.2.52021-08-23
- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
## 0.2.42021-08-17
- 修复 单选 list 模式下 icon 为 left 时,选中图标不显示的问题
## 0.2.32021-08-11
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.2.22021-07-30
- 优化 在uni-forms组件与label不对齐的问题
## 0.2.12021-07-27
- 修复 单选默认值为0不能选中的Bug
## 0.2.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.112021-07-06
- 优化 删除无用日志
## 0.1.102021-07-05
- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
## 0.1.92021-07-05
- 修复 nvue 黑框样式问题
## 0.1.82021-06-28
- 修复 selectedTextColor 属性不生效的Bug
## 0.1.72021-06-02
- 新增 map 属性可以方便映射text/value属性
## 0.1.62021-05-26
- 修复 不关联服务空间的情况下组件报错的Bug
## 0.1.52021-05-12
- 新增 组件示例地址
## 0.1.42021-04-09
- 修复 nvue 下无法选中的问题
## 0.1.32021-03-22
- 新增 disabled属性
## 0.1.22021-02-24
- 优化 默认颜色显示
## 0.1.12021-02-24
- 新增 支持nvue
## 0.1.02021-02-18
- “暂无数据”显示居中

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