6.30 版本提交

This commit is contained in:
尚政杰
2025-06-30 19:02:44 +08:00
commit c4267a0e27
338 changed files with 27942 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
## 0.1.132024-08-22
1. 修理文档错误
## 0.1.122024-06-07
1. 修复渐变色不生效的问题
2. 更新示例
3. 更新文档
## 0.1.102023-10-25
1. [feat] 增加 slot, 便于扩展组件功能
2. [fix] 修复父容器指定宽度时可能导致组件布局异常的bug
3. [fix] 其它fix
## 0.1.82023-09-20
1. 修复示例文字错误
## 0.1.72023-09-20
1. 添加 debug 模式,方便布局
2. 组件更名为 zui-progress-circle原名zui-meter-basic明确组件功能
## 0.1.52023-09-15
1. 增加仪表指针
2. 优化组件性能
## 0.1.22023-09-11
1. 更新小程序兼容性说明
## 0.1.12023-09-10
1. 更新说明文档,完善属性参数配置说明
## 0.1.02023-09-10
1. 仪表盘基础功能实现

View File

@@ -0,0 +1,465 @@
<template>
<view ref="eleMeter" :class="['zui-progress-circle', debug ? 'debug' : '']" :style="style">
<view class="zui-progress-circle-wrapper">
<image v-if="pointer" class="zui-progress-circle-pointer" mode="aspectFit" :src="pointer" :style="pointerStyle" />
<image class="zui-progress-circle-ring" mode="aspectFit" :src="svgDataUrl" />
</view>
<view class="zui-progress-circle-slot">
<slot />
</view>
<view v-if="debug" class="debug-frame">
<view class="cross-v"></view>
<view class="cross-h"></view>
<view class="half-size"></view>
</view>
</view>
</template>
<script>
const DIR_CCW = "counterclockwise";
const DIR_CW = "clockwise";
const generateId = () => {
const base = 999999 * Math.random();
return Math.round(base) + 100000;
};
export default {
name: "zui-progress-circle",
components: {},
props: {
size: {
type: Number,
default: 180,
},
/**
* 当前位置
*
* [0, 1]
*/
position: {
type: Number,
default: 0,
},
/**
* 环形起止位置
*/
range: {
type: [Array],
default: () => [0, 360],
},
/**
* 方向
*/
direction: {
type: String,
default: DIR_CW,
validator(val) {
return [DIR_CCW, DIR_CW].includes(val)
}
},
/**
* 环形宽度
*/
ringWidth: {
type: Number,
default: 8,
},
/**
* 端点效果
*
* round | butt | square
*/
linecap: {
type: String,
default: "round",
},
/**
* 纹理贴图组件支持配置前景和背景2个贴图
*
* 贴图可以是一个颜色,一个渐变填充,一个 base64 编码的图片。3种贴图可以搭配使用
*
*/
texture: {
type: [String, Array],
default: () => ["#1BB507", "#E2D8D8"],
},
pointer: String,
pointerOffset: Number,
/**
* 修复遮盖问题
*/
fixOverlay: Boolean,
debug: Boolean,
},
data() {
return {};
},
computed: {
preset() {
const preset = {}
preset.start = this.range[0];
preset.end =
this.range[0] > this.range[1]
? this.range[1] + 360
: this.range[1];
preset.ringRadius = (this.size - this.ringWidth) / 2;
preset.ringCenter = this.size / 2;
preset.ringPerimeter = 2 * Math.PI * preset.ringRadius;
preset.ringLength =
((preset.end - preset.start) * Math.PI * preset.ringRadius) / 180;
preset.ringStart = (preset.start * Math.PI * preset.ringRadius) / 180;
preset.ringEnd = (preset.end * Math.PI * preset.ringRadius) / 180;
if (/^(ccw|counterclockwise)$/i.test(this.direction))
preset.direction = DIR_CCW;
else preset.direction = DIR_CW;
return preset
},
textureFG() {
const textureSize = this.size;
if (typeof this.texture === "string") {
return this.parseTexture(this.texture, textureSize);
} else if (
Object.prototype.toString.call(this.texture) === "[object Array]"
) {
if (typeof this.texture[0] === "number") {
return this.parseTexture(this.texture, textureSize);
} else {
return this.parseTexture(this.texture[0], textureSize);
}
} else {
// use default texture
return this.parseTexture("#1BB507", textureSize);
}
},
textureBG() {
const textureSize = this.size;
if (typeof this.texture === "string") {
return this.parseTexture(undefined, textureSize);
} else if (
Object.prototype.toString.call(this.texture) === "[object Array]"
) {
if (typeof this.texture[0] === "number") {
return this.parseTexture(undefined, textureSize);
} else {
return this.parseTexture(this.texture[1], textureSize);
}
} else {
// use default texture
return this.parseTexture("#E2D8D8", textureSize);
}
},
hasBackground() {
return !!this.textureBG;
},
svgDataUrl() {
let svg = this.createSVG();
svg = `data:image/svg+xml,${encodeURIComponent(svg.replace(/ +/g, " "))}`;
return svg;
},
style() {
const style = {
width: `${this.size}px`,
height: `${this.size}px`,
"--zui-progress-circle-ring-size": `${this.size}px`,
"--zui-progress-circle-ring-width": `${this.ringWidth}px`,
}
return Object.keys(style)
.map((key) => `${key}:${style[key]}`)
.join(";");
},
pointerStyle() {
const style = {}
const { start, end, ringRadius } = this.preset
let rotate = ((end - start) * this.position + start)
if (this.linecap === 'round' || this.linecap === 'butt') {
rotate += (this.ringWidth / 3 * 180) / (Math.PI * ringRadius)
}
const offset = this.pointerOffset || 0
style['--zui-progress-circle-pointer-rotate'] = `translate(-${offset}px, -50%) rotate(${rotate}deg)`
style['--zui-progress-circle-pointer-center'] = `${offset}px 50%`
return Object.keys(style)
.map((key) => `${key}:${style[key]}`)
.join(";");
},
},
methods: {
parseTexture(texture, textureSize) {
if (!texture) return undefined;
if (/^#[0-9a-f]+/i.test(texture)) {
return {
type: "color",
value: texture,
};
}
const defId = generateId();
if (/Gradient>/i.test(texture)) {
if (/id="[^"]+"/.test(texture)) {
// Replace id
texture = texture.replace(/id="[^"]+"/, `id="def_${defId}"`);
} else {
// Create id
texture = texture.replace(
/<(\w+Gradient) /,
`<$1 id="def_${defId}" `
);
}
return {
type: "gradient",
value: `url(#def_${defId})`,
def: texture,
};
}
if (Object.prototype.toString.call(texture) === "[object Array]") {
texture = this.createGradient(defId, texture.slice(1), texture[0]);
return {
type: "gradient",
value: `url(#def_${defId})`,
def: texture,
};
}
// Create image pattern
if (/<pattern /.test(texture)) {
if (/id="[^"]+"/.test(texture)) {
// Replace id
texture = texture.replace(/id="[^"]+"/, `id="def_${defId}"`);
} else {
// Create id
texture = texture.replace(/<pattern /, `<$1 id="def_${defId}" `);
}
} else {
// Url or base64 code
texture = this.createPattern(`def_${defId}`, texture, textureSize);
}
return {
type: "pattern",
value: `url(#def_${defId})`,
def: texture,
};
},
/**
* 创建渐变填充
*
* @param {color[]} stops 渐变颜色
* @param {number} angle 渐变填充角度
*/
createGradient(id, stops, angle) {
const step = 100 / (stops.length - 1);
const stopNodes = new Array(stops.length).fill(null).map((_, idx) => {
return `<stop offset="${step * idx * 100}%" stop-color="${
stops[idx]
}" />`;
});
return `<linearGradient id="def_${id}" x1="0%" y1="0%" x2="100%" y2="64.9%" gradientTransform="rotate(${angle})">
${stopNodes.join("")}
</linearGradient>`;
},
/**
* 创建文理填充
*
* @param {string} img base64 image. URI not surpported.
*/
createPattern(id, img, size) {
size = size || "100";
return `<pattern id="${id}" patternUnits="userSpaceOnUse" width="100%" height="100%">
<image xlink:href="${img}" x="0" y="0" width="100%" height="100%"/>
</pattern>`;
},
/**
* 创建圆环
*
* @param {string} fill 纹理ID
* @param {number[]} dasharray 弧形数据
*/
createCircle(fill, dasharray, type) {
const { ringCenter, ringRadius, fixOverlay } =
this.preset;
const circle = {
cx: ringCenter,
cy: ringCenter,
r: ringRadius,
// 前景环稍大点, 用于遮盖底部环纹理
"stroke-width": this.ringWidth,
stroke: fill && fill.value,
"stroke-linecap": this.linecap,
"stroke-dasharray": dasharray.join(","),
};
if (fixOverlay) {
/**
* 装背景环尺寸调小,以解决前景无法完全遮盖的问题
*/
const fw = type === "fg" ? this.ringWidth : this.ringWidth - 1;
circle["stroke-width"] = fw > 8 ? fw : 8;
}
const props = Object.keys(circle)
.map((key) => (circle[key] ? `${key}="${circle[key]}"` : ""))
.join(" ");
return `<circle fill="none" stroke-dashoffset="1" ${props}></circle>`;
},
generateDashArray(pos) {
const {
direction,
ringStart,
ringPerimeter,
ringLength,
} = this.preset;
let ringStartPos = direction === DIR_CCW ? ringStart + (1 - pos) * ringLength : ringStart;
let dash1 = 0;
let dash2 = 0;
let dash3 = 0;
dash2 = 1 + ringStartPos;
dash3 = pos * ringLength;
const npos = ringStartPos + pos * ringLength;
if (npos > ringPerimeter) {
dash1 = npos - ringPerimeter;
dash2 = ringStartPos - dash1;
} else {
dash1 = 0;
}
return [dash1, dash2, dash3, ringPerimeter];
},
/**
* 创建 SVG 图形
*/
createSVG() {
const cirleBG = this.hasBackground
? this.createCircle(this.textureBG, this.generateDashArray(1))
: "";
const cirleFG = this.createCircle(
this.textureFG,
this.generateDashArray(this.position),
"fg"
);
const defs = [this.textureFG.def || "", (this.textureBG && this.textureBG.def) || ""];
const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${this.size}" height="${this.size}">
<defs>
${defs.join("\n")}
</defs>
<g>
${cirleBG}
${cirleFG}
</g>
</svg>`;
return svg;
},
},
};
</script>
<style lang="scss" scoped>
.zui-progress-circle {
--zui-progress-circle-debug-color: #f00;
position: relative;
}
.zui-progress-circle-wrapper {
width: 100%;
height: 100%;
}
.zui-progress-circle-ring {
width: var(--zui-progress-circle-ring-size);
height: var(--zui-progress-circle-ring-size);
}
.zui-progress-circle-slot {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: var(--zui-progress-circle-ring-width);
box-sizing: border-box;
}
.zui-progress-circle-pointer {
position: absolute;
z-index: 10;
top: 50%;
left: 50%;
transform: var(--zui-progress-circle-pointer-rotate);
transform-origin: var(--zui-progress-circle-pointer-center);
width: 50%;
height: 50%;
transition: transform 0.1s linear;
}
// Debug item style
.debug-frame ,
.cross-v,
.cross-h,
.half-size {
position: absolute;
}
.debug-frame {
position: absolute;
z-index: 99;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid var(--zui-progress-circle-debug-color);
border-radius: 50%;
}
.cross-h, .cross-v,
.half-size {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--zui-progress-circle-debug-color);
mix-blend-mode: difference;
}
.cross-v {
width: 1px;
height: 100%;
}
.cross-h {
width: 100%;
height: 1px;
}
.half-size {
width: 50%;
height: 50%;
border: 1px solid var(--zui-progress-circle-debug-color);
background-color: transparent;
border-radius: 50%;
}
// Debug style END
</style>

View File

@@ -0,0 +1,84 @@
{
"id": "zui-progress-circle",
"displayName": "环形进度条、弧形进度条。可设置弧和进度方向。非 canvas 实现,简单体积小",
"version": "0.1.13",
"description": "一款高度自定义的环形(弧形)进度条组件,可自由设置弧形和起始位置,可设置进度条动画方向,可设置图片纹理增强进度条表现能力。",
"keywords": [
"进度条",
"环形",
"圆形"
],
"repository": "",
"engines": {
"HBuilderX": "^3.4.15"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "n",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "u",
"字节跳动": "u",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "y",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,156 @@
# &lt;zui-progress-circle /&gt;
一款汽车仪表盘,环形温度指示器,环形进度条组件。支持自由弧度设置,支持图片纹理设置。
## 🍏 在线演示
**[💻 点我在浏览器里预览 https://uni.imgozi.cn/zui-progress-circle/](https://uni.imgozi.cn/zui-progress-circle/?utm_source=uni-plugin-market&utm_medium=readme&utm_campaign=zui-progress-circle&utm_id=uni-plugin)**
PS: 启动浏览器预览需要打开手机模器
**[📱 扫码体验](https://uni.imgozi.cn/zui-progress-circle/?utm_source=uni-plugin-market&utm_medium=readme&utm_campaign=zui-progress-circle&utm_id=uni-plugin)**
<img src="https://uni.imgozi.cn/zui-progress-circle/static/preview-qr.png" width="128" />
## 🍐 快速上手:
```html
<zui-progress-circle :position="0.5" />
```
## 🍊 参数
#### 🍉 size: `number`
组件尺寸
#### 🍉 ringWidth: `number`
环形宽度
#### 🍉 range: `[number, number]`
弧形起始角度范围,默认:[0, 360]。
值范围:[0, 360]
- 当起点角度小于终点角度时,按顺时针方向画圆弧;
- 当起点角度大于终点角度时,按逆时针方向画弧形;
#### 🍉 position: `number`
当前位置默认0。
值范围:[0, 1]
#### 🍉 direction: `'cw' | 'ccw' | 'clockwise' | 'counterclockwise'`
进度绘制方向,默认:'cw'
- cw, clockwise 顺时针方向
- ccw, counterclockwise 逆时针方向
#### 🍉 linecap: `'round' | 'butt' | 'flat'`
弧形端点形状,默认:'round'
#### 🍉 texture: `string | [number, ...string[]] | [ string | [number, ...string[]], string | [number, ...string[]] ]`
弧形纹理,默认:[ '<span style="background: #1BB507;color:#333">#1BB507</span>', '<span style="background: #E2D8D8;color:#333">#E2D8D8</span>' ]
- 只配置前景:`'#1BB507'`
- 同时配置前景与背景:`[ '#1BB507', '#E2D8D8' ]`
弧形的纹理支持以下几种形式:
1. CSS 颜色值
2. 一个包含线性渐变的颜色数组,颜色均匀分布
`[number, ...sring[]]`
第一个参数为渐变角度,第二个及以后的参数为颜色序列
3. 一段描述渐变的 SVG 代码,可完全自定义渐变。
4. base64 格式图片。❗️❗️❗️实验性功能,可能引起性能问题,请谨慎使用❗️❗️❗️
当只指定一种纹理时,表示只设置前景纹理,背景为透明状态。
当指定两种纹理时,第一个纹理配置为前景纹理,第二个纹理配置为背景纹理
#### 🍉 pointer: `string`
指针图片。图片尺寸要求宽度**等于**仪表盘尺寸的一半,高度**不超过**仪表盘尺寸的一半。
#### 🍉 pointerOffset: `number`
指针偏移。用于调整指针中心点位置,默认位于指针图片的左侧垂直中心点位置。
#### 🍉 fixOverlay: `boolean`
是否启用修正。
该参数仅在同时设置前景和背景和有效,用于解决前景无法完全覆盖背景的情况。
使用此修正的副作用是背景圆弧的宽度会比前景宽度小2像素。
#### 🍉 debug: `boolean`
默认: false
开启 debug 模式debug 模式会显示组件边界线,方便进行 UI 布局与 debug
<span class="banner">
<span class="surport">
<a class="btn btn-support " data-toggle="modal" data-target="#support_modal" style="border: 1px solid #ec4d4d;letter-spacing: 1px;">
🍓🍇🍉 喜欢就打赏一下 🍒🍑🥭
</a>
</span>
</span>
## 🍎 兼容性说明
| 兼容性 | 小程序 | 版本 | 说明 |
| :---: | :--- | :--- | ---- |
| | 快应用 | 0.1.0 | |
| ✅ | 微信小程序 | 0.1.2 | |
| ✅ | 支付宝小程序 | 0.1.2 | |
| | 百度小程序 | 0.1.0 | |
| | 字节小程序 | 0.1.0 | |
| ✅ | QQ小程序 | 0.1.2 | |
| ✅ | 钉钉小程序 | 0.1.2 | |
| | 快手小程序 | 0.1.0 | |
| ✅ | 飞书小程序 | 0.1.2 | 飞书小程序不支持动态将图片编码为 base64使用图片纹理时需要注意 |
| | 京东小程序 | 0.1.0 | |
## 🍓 支持
如果组件对您有帮助,请不吝打赏。肥宅快乐水🥤是创作动力!🥤🥤🥤
<span class="banner">
<span class="surport">
<a class="btn btn-support " data-toggle="modal" data-target="#support_modal" style="border: 1px solid #ec4d4d;letter-spacing: 1px;">
🍓🍇🍉 喜欢就打赏一下 🍒🍑🥭
</a>
</span>
</span>