优化生产css

This commit is contained in:
廖德云 2025-03-12 21:51:38 +08:00
commit 7bcaf09421
577 changed files with 76473 additions and 657 deletions

View File

@ -1,4 +1,3 @@
# 开发环境
# 请求接口地址
VITE_REQUEST_BASE_URL = https://36.112.48.190
#VITE_REQUEST_BASE_URL = http://10.75.166.6:8080

View File

@ -13,4 +13,20 @@ export function initDictOption(dictCode) { // 获取部门所有人员信息
method: 'get',
data: dictCode
})
}
export function writeCommonJsonDisplay(dictCode) { // 通用json数据写入API
return https({
url: ' /sys/dict/getDictItems',
method: 'get',
data: dictCode
})
}
export function readCommonJsonDisplay(params) { // 通用json数据写入API
return https({
url: '/cxcPersonalTailor/cxcPersonalTailor/getByRules',
method: 'get',
data: params
})
}

View File

@ -18,8 +18,6 @@ export function queryJinriTrqShengchansj(params) { // 获取当前日期、今
})
}
export function queryYearShengchansj(params) { // 获取今年以来天然气的生产数据
return https({
url: '/scdt.CxcScdtChart/cxcScdtChart/getYearStatis',
@ -34,4 +32,22 @@ export function queryJinriYuanyouShengchansj(params) { // 获取今日原油 污
method: 'get',
data: params
})
}
// 实时数据API
export function queryJldZcList(params) { // 获取计量点站场列表
return https({
url: '/sys/sysDepart/queryDepartsByzdjl',
method: 'get',
data: params
})
}
export function queryJldDataByZc(params) { // 获取站场计量点实时数据
return https({
url: 'http://10.75.166.6:9999/Gyk/sssj/GetJlByZc',
method: 'get',
data: params
})
}

View File

@ -1,122 +1,123 @@
{
"name": "数智产销",
"appid": "__UNI__9F097F0",
"description": "",
"versionName": "1.1.11",
"versionCode": 20250303,
"transformPx": false,
/* 5+App */
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"compatible": {
"ignoreVersion": true
},
/* */
"modules": {
"Geolocation": {},
"Fingerprint": {},
"Camera": {},
"Barcode": {}
},
/* */
"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": {
"dSYMs": false
},
/* SDK */
"sdkConfigs": {
"ad": {},
"geolocation": {
"system": {
"__platform__": ["android"]
}
}
},
"icons": {
"android": {
"hdpi": "unpackage/res/icons/72x72.png",
"xhdpi": "unpackage/res/icons/96x96.png",
"xxhdpi": "unpackage/res/icons/144x144.png",
"xxxhdpi": "unpackage/res/icons/192x192.png"
},
"ios": {
"appstore": "unpackage/res/icons/1024x1024.png",
"ipad": {
"app": "unpackage/res/icons/76x76.png",
"app@2x": "unpackage/res/icons/152x152.png",
"notification": "unpackage/res/icons/20x20.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"proapp@2x": "unpackage/res/icons/167x167.png",
"settings": "unpackage/res/icons/29x29.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"spotlight": "unpackage/res/icons/40x40.png",
"spotlight@2x": "unpackage/res/icons/80x80.png"
},
"iphone": {
"app@2x": "unpackage/res/icons/120x120.png",
"app@3x": "unpackage/res/icons/180x180.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"notification@3x": "unpackage/res/icons/60x60.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"settings@3x": "unpackage/res/icons/87x87.png",
"spotlight@2x": "unpackage/res/icons/80x80.png",
"spotlight@3x": "unpackage/res/icons/120x120.png"
}
}
}
}
},
/* */
"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"
"name" : "数智产销",
"appid" : "__UNI__9F097F0",
"description" : "",
"versionName" : "1.1.13",
"versionCode" : 20250321,
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"compatible" : {
"ignoreVersion" : true
},
/* */
"modules" : {
"Geolocation" : {},
"Fingerprint" : {},
"Camera" : {},
"Barcode" : {}
},
/* */
"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" : {
"dSYMs" : false
},
/* SDK */
"sdkConfigs" : {
"ad" : {},
"geolocation" : {
"system" : {
"__platform__" : [ "android" ]
}
}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
}
}
},
/* */
"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"
}
/* */
/* */

1323
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,18 @@
{
"dependencies": {
"@dcloudio/uni-ui": "^1.5.6",
"base-64": "^1.0.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0"
}
"name": "your-project",
"version": "1.0.0",
"scripts": {
"build": "vite build --config vite.config.js --format esm"
},
"dependencies": {
"@dcloudio/uni-ui": "^1.5.6",
"base-64": "^1.0.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"pinia": "^3.0.1",
"js-base64": "^3.6.1"
},
"devDependencies": {
"vite": "^6.2.2"
}
}

View File

@ -102,6 +102,13 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/document/onlinePreviewH5",
"style": {
"navigationBarTitleText": "在线预览",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/zhongheguanli/meeting/index",
"style": {
@ -185,6 +192,63 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/shengchan/shishishuju/index",
"style": {
// "navigationStyle": "custom"
"navigationBarTitleText": "实时数据",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/shengchan/shishishuju/trqSssj",
"style": {
// "navigationStyle": "custom"
"navigationBarTitleText": "天然气实时数据",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/shengchan/shishishuju/gycsSssj",
"style": {
// "navigationStyle": "custom"
"navigationBarTitleText": "工艺参数实时数据",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/shengchan/shishishuju/ysjSssj",
"style": {
// "navigationStyle": "custom"
"navigationBarTitleText": "压缩机实时数据",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/shengchan/shishishuju/aqbjSssj",
"style": {
// "navigationStyle": "custom"
"navigationBarTitleText": "安全报警实时数据",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/views/shengchan/shishishuju/nyxhSssj",
"style": {
// "navigationStyle": "custom"
"navigationBarTitleText": "能耗实时数据",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/userlist/index",
"style": {

View File

@ -14,6 +14,14 @@
</text>
<view class="f-col">
<view v-if="ifH5">
<!-- 在线预览 by -->
<view class="" style="padding: 5rpx 0;" @click="onlinePreview(`/pages/document/onlinePreviewH5?data=${JSON.stringify(item)}`)"
v-for="item,i in detail?.pdf?.split(',')">
{{item}}
</view>
</view>
<view v-esle>
<!-- 在线预览 by -->
<view class="" style="padding: 5rpx 0;" @click="onlinePreview(`/pages/document/onlinePreview?data=${JSON.stringify(item)}`)"
v-for="item,i in detail?.pdf?.split(',')">
@ -21,13 +29,13 @@
</view>
</view>
<view v-else>
<!-- <view v-else>
<view class="" style="padding: 5rpx 0;" @click="opendocument(item)"
v-for="item,i in detail?.pdf?.split(',')">
{{item}}
</view>
</view>
</view> -->
</view>
</view>

View File

@ -1,6 +1,6 @@
<template>
<view :class="['content',{'gray':store.isgray==1}]">
<iframe id="bdIframe" :src="fileUrl" ref="bdIframe" style="border: none;" class="iframe" />
<view :class="['content',{'gray':store.isgray==1}]" >
<iframe id="bdIframe" :src="fileUrl" ref="bdIframe" style="border: none;" class="iframe"/>
</view>
</template>
@ -12,7 +12,8 @@
onLoad
} from '@dcloudio/uni-app';
import Base64 from '@/utils/code.js';
const baseUrl = import.meta.env.VITE_REQUEST_BASE_URL + '/jeecg-boot/sys/common/static/'
const requestUrl = import.meta.env.VITE_REQUEST_BASE_URL;
const baseUrl = 'https://10.75.166.6/jeecg-boot/sys/common/static/';
const store = useStore();
var fileUrl = "";
@ -21,7 +22,9 @@
let base64 = new Base64();
var url= JSON.parse(options.data)
url = baseUrl + url;
fileUrl = 'https://10.75.166.6/preview/onlinePreview' + '?url=' + encodeURIComponent(base64.encode(url))
console.log(url)
fileUrl =requestUrl+ '/preview/onlinePreview' + '?url=' + encodeURIComponent(base64.encode(url))
console.log(fileUrl)
})
</script>

View File

@ -0,0 +1,46 @@
<template>
<view :class="['content',{'gray':store.isgray==1}]">
<iframe id="bdIframe" :src="fileUrl" ref="bdIframe" style="border: none;" class="iframe" />
</view>
</template>
<script setup>
import {
useStore
} from '@/store';
import {
onLoad
} from '@dcloudio/uni-app';
const requestUrl = import.meta.env.VITE_REQUEST_BASE_URL;
const baseUrl = 'https://10.75.166.6/jeecg-boot/sys/common/static/';
const store = useStore();
import {Base64} from 'js-base64'
var fileUrl = "";
onLoad((options) => {
var url= JSON.parse(options.data)
url = baseUrl + url;
console.log(url)
console.log(Base64.encode(url))
fileUrl =requestUrl+ '/preview/onlinePreview' + '?url=' + encodeURIComponent(Base64.encode(url))
console.log(fileUrl)
})
</script>
<style>
.container {
position: relative;
width: 100%;
height: 100vh;
}
#bdIframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>

View File

@ -23,42 +23,22 @@
<view class="title">
请假类型
</view>
<tree-select :dataSource="dataSource" v-model="type" dataValue="name" />
<tree-select :dataSource="dataSource" v-model="type" dataValue="name" @change="queryHisDate" />
</view>
<picker mode="date" fields="day" @change="chooseStart" :value="beginTime" :start="startDate" :end="endTime">
<view class="f-row aic jcb box">
<view class="title">
开始时间
</view>
<view class="f-row aic">
<view :class="[{'choose':!beginTime},{'choosed':beginTime}]">
{{beginTime?beginTime:'请选择'}}
</view>
<uni-icons type="bottom" color="#333333"></uni-icons>
</view>
<view class="f-row aic jcb input_box">
<view class="title">
请假时间
</view>
</picker>
<picker mode="date" fields="day" @change="chooseEnd" :value="endTime" :start="beginTime">
<view class="f-row aic jcb box">
<view class="title">
截止时间
</view>
<view class="f-row aic">
<view :class="[{'choose':!endTime},{'choosed':endTime}]">
{{endTime?endTime:'请选择'}}
</view>
<uni-icons type="bottom" color="#333333"></uni-icons>
</view>
</view>
</picker>
<picker @change="bindType" :value="typeIndex" :range="typeArr" range-key="realname">
<cxc-szcx-dateRangeSelect mode="range" v-model="rangeDate" :startDate="startDate" :isSearch="false"></cxc-szcx-dateRangeSelect>
</view>
<picker @change="bindType" :value="leaderIndex" :range="leaderArr" range-key="realname">
<view class="f-row aic jcb box">
<view class="title">
{{examineleader}}
</view>
<view class="f-row aic">
<view :class="[{'choose':typeIndex==null},{'choosed':typeIndex!=null}]">
{{typeIndex!=null?typeArr[typeIndex].realname:'请选择'}}
<view :class="[{'choose':leaderIndex==null},{'choosed':leaderIndex!=null}]">
{{leaderIndex!=null?leaderArr[leaderIndex].realname:'请选择'}}
</view>
<uni-icons type="bottom" color="#333333"></uni-icons>
</view>
@ -140,22 +120,13 @@
/**请假类型*/
const type = ref('')
const dataSource = ref([])
/**开始时间*/
const beginTime = ref('')
const chooseStart = (e) => {
beginTime.value = e.detail.value
}
/**请假开始时间 绑定start*/
/**请假时间*/
const rangeDate = ref([null, null])
const startDate = ref('')
/**结束时间*/
const endTime = ref('')
const chooseEnd = (e) => {
endTime.value = e.detail.value
}
/**审批领导*/
const typeArr = ref([])
const typeIndex = ref(null)
/**判断显示审批 / 分管 领导*/
const leaderArr = ref([])
const leaderIndex = ref(null)
/**判断显示审批 / 分管领导*/
const examineleader = ref(true)
/**职位层级*/
const zwcj = ref('')
@ -178,6 +149,9 @@
radius: '2px'
}
}
/**返回的最新一条请假结束时间*/
const resDate = ref('')
onLoad(() => {
loadData()
})
@ -203,11 +177,8 @@
const qjAdd = () => {
if (!phone.value.trim()) return proxy.$toast('请输入联系方式')
if (!type.value) return proxy.$toast('请选择请假类型')
if (!beginTime.value) return proxy.$toast('请选择开始时间')
if (!endTime.value) return proxy.$toast('请选择结束时间')
if (typeIndex.value == null) { //
return proxy.$toast('请选择' + examineleader.value)
}
if (!rangeDate.value || rangeDate.value.every(item => item === null)) return proxy.$toast('请选择请假时间')
if (leaderIndex.value == null) return proxy.$toast('请选择' + examineleader.value)
if (!departure.value.trim()) return proxy.$toast('请输入出发地')
if (!destination.value.trim()) return proxy.$toast('请输入目的地')
if (!reason.value.trim()) return proxy.$toast('请输入请假事由')
@ -217,9 +188,9 @@
username: store.userinfo.username,
phone: phone.value,
type: type.value,
begintime: beginTime.value,
endtime: endTime.value,
examineleader: typeArr.value[typeIndex.value] ? typeArr.value[typeIndex.value].username : '',
begintime: rangeDate.value[0],
endtime: rangeDate.value[1],
examineleader: leaderArr.value[leaderIndex.value].username,
departure: departure.value,
destination: destination.value,
reason: reason.value,
@ -265,11 +236,11 @@
queryZwmcAndExaApi(store.userinfo.username).then((res) => { //
if (res.success) {
typeArr.value = res.result.list
leaderArr.value = res.result.list
zwcj.value = res.result.zwmc
if (zwcj.value == '单位专家' || zwcj.value == '基层正职' || zwcj.value == '高级主管') {
examineleader.value = '分管领导';
}else{
} else {
examineleader.value = '审批领导';
}
} else {
@ -277,39 +248,43 @@
}
})
queryHisDateApi(store.userinfo.username).then((res) => { //
if (res) {
getTomorrowDate(res);
startDate.value = beginTime.value; // startDate
} else {
getTomorrowDate();
startDate.value = '1900-01-01';
}
})
queryHisDateApi(store.userinfo.username).then((res) => {
resDate.value = res ? new Date(res) : null; // res Date
queryHisDate();
});
}
const queryHisDate = () => {
const today = new Date(); //
if (resDate.value) {
// res
if (type.value != '干部离返濮报备') {
startDate.value = getNextDay(resDate.value); // startDate res
}else{
startDate.value = null;
}
rangeDate.value = [getNextDay(resDate.value < today ? today : resDate.value), getNextDay(getNextDay(
resDate.value < today ? today : resDate.value))]
} else {
// res
rangeDate.value = [getNextDay(today), getNextDay(today)]
}
}
//
const getNextDay = (date) => {
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1); //
const year = nextDay.getFullYear();
const month = (nextDay.getMonth() + 1).toString().padStart(2, '0');
const day = nextDay.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
};
const bindType = (e) => {
typeIndex.value = e.detail.value
leaderIndex.value = e.detail.value
}
const getTomorrowDate = (e) => {
let tomorrow;
if (e) {
// Date
const dateParts = e.split('-').map(Number);
tomorrow = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
} else {
// 使
tomorrow = new Date();
}
//
tomorrow.setDate(tomorrow.getDate() + 1);
//
const year = tomorrow.getFullYear();
const month = (tomorrow.getMonth() + 1).toString().padStart(2, '0');
const day = tomorrow.getDate().toString().padStart(2, '0');
beginTime.value = `${year}-${month}-${day}`;
}
</script>
<style>

View File

@ -5,6 +5,9 @@
<uni-card :is-shadow="false">
<button type="primary" @click="toTaizhang">人员台账</button>
</uni-card>
<uni-card :is-shadow="false">
<button v-if="roleDetail" type="primary" @click="toSzcs">通过素质测试</button>
</uni-card>
</uni-section>
<uni-section title="统计信息" type="line">
<uni-card :is-shadow="false">
@ -16,10 +19,37 @@
</uni-card>
</uni-section>
</uni-card>
<uni-popup ref="popup" type="bottom" background-color="#fff">
<cxc-szcx-jsonDataViewer :username="username" :persionalKey="persionalKey"
:type="type"></cxc-szcx-jsonDataViewer>
</uni-popup>
</view>
</template>
<script setup>
import {
ref
} from 'vue';
import {
useStore
} from '@/store';
const store = useStore();
const roleDetail = ref(store.userinfo.realname === '廖德云' || store.userinfo.realname === '马敬朝');
//
const username = ref('');
const persionalKey = ref('');
const type = ref('');
const popup = ref(null);
function toSzcs() {
username.value = 'liaody'
persionalKey.value = 'szcsRy'
type.value = '5'
popup.value.open();
}
function toTaizhang() {
uni.navigateTo({
url: '/pages/views/renliziyuan/renyuanxinxi/taizhang'

View File

@ -15,7 +15,7 @@
@change="onFieldChange"></uni-data-select>
</uni-col>
<uni-col style="margin-left:5px" :span="6" v-if="selectedField==='zjmc'"><button type="primary"
size="mini" @click="showPopup=!showPopup">筛选</button></uni-col>
size="mini" @click="openZhengshu">筛选</button></uni-col>
</uni-row>
</view>
@ -408,6 +408,11 @@
});
}, 300);
};
function openZhengshu() {
showPopup.value.open()
}
//
//
function findRyByOrgCode(treeData, targetOrgCode) {
@ -595,7 +600,7 @@
// data
rootNodes.forEach((root) => computeData(root));
console.log(rootNodes)
return rootNodes;
// console.log('rootNodes', rootNodes);
@ -719,9 +724,9 @@
try {
// A01A01A01 orgType by
var tempOrgType = '1';
if(selectedOrgCode.value.includes('A01A01A01')){
if (selectedOrgCode.value.includes('A01A01A01')) {
tempOrgType = '1';
}else{
} else {
tempOrgType = orgType
}
if (selectedField.value === 'zjmc') {
@ -769,7 +774,7 @@
} else {
var tempArr = [];
tempArr.push(temp[0])
chartData.value =tempArr;
chartData.value = tempArr;
}
updateChart(chartData.value);
}
@ -800,7 +805,7 @@
selectedOrgCode.value = e;
console.log(e)
console.log(data);
orgType.value = data.value.orgType; // by
orgType.value = data.value.orgType; // by
selectedOrgCodeLabel.value = data.value.title;
fetchStatisticsData();
};

View File

@ -5,57 +5,189 @@
<view style="width: 100%; display: grid; place-items: center">
<uni-title :title="dateDate + ':生产经营情况'" type="h1" color="blue" />
</view>
<view style="margin: 0 10px;">
<uni-segmented-control style="margin-top: 10px;margin-bottom: 10px" :current="current" :values="items"
@clickItem="onClickItem" styleType="button" activeColor="#0055ff"></uni-segmented-control>
</view>
<view class="content">
<view v-show="current === 0">
<view style="padding: 0 10px">
<view class="progress-bartime">
<!-- 动态设置宽度和颜色 -->
<view class="progressTime" :style="{ width: `${timePercent}%`, 'background-color': '#0055ff' }">
</view>
<!-- 显示带符号的百分比 -->
<text class="progress-text">全年时间进度:{{ timePercent }}%</text>
</view>
</view>
<scroll-view scroll-y :style="{ height: scrollViewHeight + 'px' }">
<trq-data></trq-data>
<yy-data></yy-data>
</scroll-view>
</view>
<view v-show="current === 1">
<scroll-view scroll-y :style="{ height: scrollViewHeight + 'px' }">
<sssjForm></sssjForm>
</scroll-view>
</view>
<view v-show="current === 2">
<scroll-view scroll-y :style="{ height: scrollViewHeight + 'px' }">
选项卡2的内容
</scroll-view>
</view>
</view>
<trq-data></trq-data>
<yy-data></yy-data>
</view>
</template>
<script setup>
import { formatDate, getDateAfterDays } from '@/utils/dateTime.js';
import {
formatDate,
getDateAfterDays
} from '@/utils/dateTime.js';
import {
getYearProgress
} from '@/utils/dateTime.js';
import { ref, onMounted, computed, nextTick, watchEffect } from 'vue';
import {
ref,
onMounted,
computed,
nextTick,
watchEffect,
onUnmounted
} from 'vue';
const items = ref(['日报数据', '实时数据', '经营数据'])
const current = ref(0)
const res = wx.getSystemInfoSync();
const statusHeight = res.statusBarHeight; //
const cusnavbarheight = statusHeight + 44 + 'px';
const res = wx.getSystemInfoSync();
const statusHeight = res.statusBarHeight; //
const cusnavbarheight = statusHeight + 44 + 'px';
const scrollViewHeight = ref(0); //
const dateDate = ref('');
import trqData from './ribaoshuju/trqRbsj.vue';
import yyData from './ribaoshuju/yyRbsj.vue';
const timePercent = ref(0);
const dateDate = ref('');
const strDate = () => {
const now = new Date();
if (now.getHours() < 11) {
return formatDate(getDateAfterDays(now, -1)); //11
} else {
return formatDate(now);
import trqData from './ribaoshuju/trqRbsj.vue';
import yyData from './ribaoshuju/yyRbsj.vue';
import sssjForm from './shishishuju/index.vue';
function onClickItem(e) {
if (current.value != e.currentIndex) {
current.value = e.currentIndex;
}
}
};
onMounted(() => {
dateDate.value = strDate();
});
const strDate = () => {
const now = new Date();
if (now.getHours() < 11) {
return formatDate(getDateAfterDays(now, -1)); //11
} else {
return formatDate(now);
}
};
onMounted(() => {
dateDate.value = strDate();
timePercent.value = getYearProgress();
calculateScrollViewHeight();
//
window.addEventListener('resize', calculateScrollViewHeight);
});
onUnmounted(() => {
//
window.removeEventListener('resize', calculateScrollViewHeight);
});
const calculateScrollViewHeight = () => {
//
const screenHeight = uni.getSystemInfoSync().windowHeight;
//
const query = uni.createSelectorQuery();
//
query
.select('.nav')
.boundingClientRect();
query
.select('.placeholder')
.boundingClientRect();
query
.select('.uni-title')
.boundingClientRect();
query
.select('.uni-segmented-control')
.boundingClientRect();
query
.select('.progress-bartime')
.boundingClientRect();
//
query.exec((res) => {
let totalHeight = 0;
res.forEach((element) => {
if (element) {
totalHeight += element.height;
}
});
// scroll-view
scrollViewHeight.value = screenHeight - totalHeight - 80;
});
};
</script>
<style lang="scss" scoped>
.nav {
width: calc(100% - 60rpx);
padding: 0 30rpx;
height: v-bind(cusnavbarheight);
.nav {
width: calc(100% - 60rpx);
padding: 0 30rpx;
height: v-bind(cusnavbarheight);
font-size: 24rpx;
color: #ffffff;
position: fixed;
top: 0;
left: 0;
z-index: 99;
background-image: url('../../static/my/navbg.png');
background-repeat: no-repeat;
background-size: 750rpx 458rpx;
}
font-size: 24rpx;
color: #ffffff;
position: fixed;
top: 0;
left: 0;
z-index: 99;
background-image: url('../../static/my/navbg.png');
background-repeat: no-repeat;
background-size: 750rpx 458rpx;
}
.placeholder {
height: v-bind(cusnavbarheight);
}
</style>
.placeholder {
height: v-bind(cusnavbarheight);
}
.progress-bartime {
position: relative;
height: 25px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 20rpx;
}
.progress {
height: 100%;
transition: all 0.3s;
}
.progressTime {
height: 100%;
transition: all 0.3s;
padding: 0 20px;
}
.progress-text {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: red;
/* 保持红色 */
font-size: 16px;
font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
/* 提升可读性 */
}
</style>

View File

@ -1,19 +1,10 @@
<template>
<view :class="{ gray: store.isgray == 1 }">
<view style="padding: 0 10px">
<view class="progress-bartime">
<!-- 动态设置宽度和颜色 -->
<view class="progressTime" :style="{ width: `${timePercent}%`, 'background-color': '#00aaff' }"></view>
<!-- 显示带符号的百分比 -->
<text class="progress-text">全年时间进度:{{ timePercent }}%</text>
</view>
</view>
<view class="content">
<!-- 标题行 -->
<view class="header-row">
<view class="title">天然气产量</view>
<view class="more" @click="selectMore">更多 </view>
<view class="more" @click="selectMore">更多>></view>
</view>
</view>
<view class="container">
@ -32,7 +23,9 @@
<!-- 动态设置宽度和颜色 -->
<view class="progress" :style="{ width: `${Math.abs(item.yearPerCent)}%`, 'background-color': item.yearPerCent < 0 ? '#ff4444' : '#007aff' }"></view>
<!-- 显示带符号的百分比 -->
<text v-if="!(item.yearPlan === '' || item.yearPlan === '0')" class="progress-text">{{ item.yearPerCent }}%</text>
<text v-if="!(item.yearPlan === '' || item.yearPlan === '0')" class="progress-text" :style="{ color: item.yearPerCent < 0 ? '#007aff' : '#ff4444' }">
{{ item.yearPerCent }}%
</text>
</view>
</view>
</view>
@ -41,14 +34,22 @@
<uni-popup ref="popupSelect" type="top" background-color="#fff">
<view style="margin-top: 50px">
<view class="titlepopup">选择显示更多生产数据</view>
<uni-data-checkbox
style="font-size: 14px"
@change="onselectionchange"
:localdata="shishiArr"
v-model="shishiArrDisplay"
multiple
:map="{ value: 'gas', text: 'gas' }"
></uni-data-checkbox>
<view class="popupBtn">
<button size="mini" @click="selectDefault" style="padding: 8px 16px">默认</button>
<button size="mini" @click="selectAll" style="padding: 8px 16px">全选</button>
<button size="mini" @click="selectNo" style="padding: 8px 16px">全不选</button>
</view>
<view class="popupcheckbox">
<uni-data-checkbox
style="font-size: 14px"
@change="onselectionchange"
:localdata="shishiArr"
v-model="shishiArrDisplay"
multiple
:map="{ value: 'gas', text: 'gas' }"
></uni-data-checkbox>
</view>
</view>
</uni-popup>
<!-- 数据弹窗 -->
@ -98,25 +99,28 @@ import { queryJinriShengchansj, queryYearShengchansj, queryJinriTrqShengchansj }
import { formatDate, getDateAfterDays } from '@/utils/dateTime.js';
import { beforeJump } from '@/utils/index.js';
import { useStore } from '@/store';
import { getYearProgress } from '@/utils/dateTime.js';
const store = useStore();
const shishiArr = ref([]);
const shishiArrSelect = ref([]);
const shishiArrDisplay = ref(['气井气', '站线综合输差']);
const shishiArrDisplay = ref(['气井气', '商品量', '站线综合输差']);
const dataJinriUnit = ref([]);
const selectedGas = ref('');
const filteredData = ref([]);
const popup = ref(null);
const popupSelect = ref(null);
const strDate = ref('');
const timePercent = ref(0);
const dataJinri = ref([]);
const dataJinriSum = ref([]);
const dataJinriSumUnit = ref([]);
const qjqNdjs = ref(7500);
const splNdjs = ref(7220);
const zhqNdjs = ref(300);
const zhscNdjs = ref(50);
//
// const handleCardClick = (gas) => {
// selectedGas.value = gas;
@ -135,9 +139,33 @@ const dataJinriSumUnit = ref([]);
// popup.value.open();
// };
function selectMore() {
popupSelect.value.open();
}
function selectAll() {
shishiArrDisplay.value = [];
// console.log(9, shishiArr.value)
shishiArr.value.forEach((item) => {
// console.log(10, item)
shishiArrDisplay.value.push(item.gas);
});
onselectionchange();
popup.value.close();
}
function selectNo() {
shishiArrDisplay.value = [];
onselectionchange();
popup.value.close();
}
function selectDefault() {
shishiArrDisplay.value = ['气井气', '商品量', '站线综合输差'];
onselectionchange();
popup.value.close();
}
const onselectionchange = () => {
shishiArrSelect.value = [];
shishiArrDisplay.value.forEach((item) => {
@ -157,7 +185,7 @@ const closePopup = () => {
};
onMounted(() => {
getJinriTrqShengchansj();
timePercent.value = getYearProgress();
getJinriShengchansj();
});
@ -202,7 +230,15 @@ function calcZhsc(tempArray) {
yearVolume: 0,
yearPlan: '100',
yearPerCent: 0
}; //
};
const trqSpl = {
gas: '商品量',
dailyVolume: 0,
yearVolume: 0,
yearPlan: '7220',
yearPerCent: 0
};
//
tempArray.forEach((item) => {
if (item.gas === '站输差' || item.gas === '线输差') {
compositeZx.dailyVolume += parseFloat(item.dailyVolume) || 0;
@ -216,16 +252,26 @@ function calcZhsc(tempArray) {
totalChuqi.dailyVolume += parseFloat(item.dailyVolume) || 0;
totalChuqi.yearVolume += parseFloat(item.yearVolume) || 0;
}
if (item.gas === '气井气' || item.gas === '站输差' || item.gas === '线输差') {
trqSpl.dailyVolume += parseFloat(item.dailyVolume) || 0;
trqSpl.yearVolume += parseFloat(item.yearVolume) || 0;
}
if (item.gas === '自耗气') {
trqSpl.dailyVolume -= parseFloat(item.dailyVolume) || 0;
trqSpl.yearVolume -= parseFloat(item.yearVolume) || 0;
}
});
compositeZx.yearPerCent = calcPercent(compositeZx.yearPlan, compositeZx.yearVolume);
trqSpl.yearPerCent = calcPercent(trqSpl.yearPlan, trqSpl.yearVolume);
compositeJc.dailyVolume = (-totalJinqi.dailyVolume + totalChuqi.dailyVolume).toFixed(4);
compositeJc.yearVolume = (-totalJinqi.yearVolume + totalChuqi.yearVolume).toFixed(4);
compositeJc.yearPerCent = calcPercent(compositeJc.yearPlan, compositeJc.yearVolume);
tempArray.push(compositeZx);
// tempArray.push(compositeJc);
tempArray.push(trqSpl);
return tempArray;
@ -421,6 +467,8 @@ function sumByUnit(records) {
align-items: center;
padding: 10 10rpx;
border-bottom: 1rpx solid #eee;
margin-left: 10px;
margin-right: 10px;
}
.title {
@ -428,13 +476,46 @@ function sumByUnit(records) {
font-weight: bold;
color: #333;
}
.titlepopup {
font-size: 30rpx;
font-size: 35rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-top: 20px;
margin-top: 20px;
padding: 20px;
margin-top: 10px;
}
/* 给包裹按钮的容器设置样式 */
.popupBtn {
display: grid;
/* 创建三个等宽的列 */
grid-template-columns: repeat(3, 1fr);
/* 设置列与列之间的间距 */
gap: 2px;
margin-bottom: 20px;
}
/* 添加按钮按下效果 */
.popupBtn button:active {
opacity: 0.8;
transform: scale(0.98);
transition: all 0.1s ease;
}
/* 给按钮设置通用样式 */
.popupBtn button {
border: none;
padding: 0px 10px;
background-color: #007bff;
color: white;
border-radius: 5px;
cursor: pointer;
}
/* 鼠标悬停在按钮上时的样式 */
.popupBtn button:hover {
background-color: #0056b3;
}
.more {
@ -458,6 +539,23 @@ function sumByUnit(records) {
.more:hover {
color: #007aff;
}
.popupcheckbox {
/* 使用 Flexbox 布局 */
display: flex;
flex-wrap: wrap;
/* 允许换行 */
gap: 2px;
/* 设置复选框之间的间距 */
margin-left: 20px;
margin-bottom: 10px;
}
.popupcheckbox .uni-data-checkbox__item {
flex-basis: calc((100% / var(4)) - 10px);
box-sizing: border-box;
}
.container {
display: flex;
flex-wrap: wrap;
@ -612,25 +710,11 @@ function sumByUnit(records) {
border-radius: 10px;
overflow: hidden;
}
.progress-bartime {
position: relative;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 30rpx;
}
.progress {
height: 100%;
transition: all 0.3s;
}
.progressTime {
height: 100%;
transition: all 0.3s;
padding: 0 20px;
}
.progress-text {
position: absolute;
left: 50%;
@ -643,4 +727,16 @@ function sumByUnit(records) {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
/* 提升可读性 */
}
.progress-textFs {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: red;
/* 保持红色 */
font-size: 12px;
font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
/* 提升可读性 */
}
</style>

View File

@ -29,15 +29,13 @@
<view class="progress-bar">
<!-- 动态设置宽度和颜色 -->
<view
class="progress"
:style="{
<view class="progress" :style="{
width: `${Math.abs(item.yearPerCent)}%`,
'background-color': item.yearPerCent < 0 ? '#ff4444' : '#007aff'
}"
></view>
}"></view>
<!-- 显示带符号的百分比 -->
<text v-if="!(item.yearPlan === '' || item.yearPlan === '0')" class="progress-text">{{ item.yearPerCent }}%</text>
<text v-if="!(item.yearPlan === '' || item.yearPlan === '0')"
class="progress-text">{{ item.yearPerCent }}%</text>
</view>
</view>
</view>
@ -63,7 +61,8 @@
</view>
<!-- 表格内容 -->
<view class="tr" v-for="(item, index) in dataJinri" :key="index" :class="{ even: index % 2 === 0 }">
<view class="tr" v-for="(item, index) in dataJinri" :key="index"
:class="{ even: index % 2 === 0 }">
<view class="td1">{{ index }}</view>
<view class="td">{{ item.dw }}</view>
<view class="td">
@ -86,327 +85,353 @@
</template>
<script setup>
import { ref, onMounted, computed, nextTick, watchEffect } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { queryJinriYuanyouShengchansj } from '@/api/shengchan.js';
import { formatDate, getDateAfterDays } from '@/utils/dateTime.js';
import { beforeJump } from '@/utils/index.js';
import { useStore } from '@/store';
import {
ref,
onMounted,
computed,
nextTick,
watchEffect
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
queryJinriYuanyouShengchansj
} from '@/api/shengchan.js';
import {
formatDate,
getDateAfterDays
} from '@/utils/dateTime.js';
import {
beforeJump
} from '@/utils/index.js';
import {
useStore
} from '@/store';
const store = useStore();
import dataCom from '@/bpm/dataCom.vue';
const store = useStore();
import dataCom from '@/bpm/dataCom.vue';
const shishiArr = ref([
{
const shishiArr = ref([{
gas: '原油产量',
rcwy: '',
yl: '',
nl: '',
yearPlan: '1500',
yearPerCent: ''
}
]);
}]);
const dataJinri = ref([]);
const dataJinriSum = ref([]);
const popup = ref(null);
//
const handleCardClick = () => {
popup.value.open();
};
const dataJinri = ref([]);
const dataJinriSum = ref([]);
const popup = ref(null);
//
const handleCardClick = () => {
popup.value.open();
};
//
const closePopup = () => {
popup.value.close();
};
onMounted(() => {
getJinriYuanyouShengchansj();
// getYearShengchansj();
});
const strDate = ref('');
//
const formatNumber = (num) => {
if (typeof num !== 'number') return '-';
return num.toFixed(4).replace(/\.?0+$/, '');
};
function goHistory(val) {
uni.navigateTo({
url: '/pages/views/shengchan/ribaoshuju/rbsjLsxq?data=' + JSON.stringify(val) + '&type=yy'
//
const closePopup = () => {
popup.value.close();
};
onMounted(() => {
getJinriYuanyouShengchansj();
// getYearShengchansj();
});
}
const getJinriYuanyouShengchansj = () => {
const now = new Date();
if (now.getHours() < 11) {
strDate.value = formatDate(getDateAfterDays(now, -1)).toString(); //11
} else {
strDate.value = formatDate(now).toString();
}
let queryParms = {};
dataJinri.value = [];
dataJinriSum.value = [];
queryParms.scrq = strDate.value;
queryParms.pageSize = 100;
// // console.log(queryParms);
queryJinriYuanyouShengchansj(queryParms).then((res) => {
if (res.success) {
// // console.log(res);
dataJinri.value = res.result.records;
dataJinriSum.value = sumByOil(dataJinri.value); //gas unit rq cq totalGas
// // console.log(dataJinriSum.value);
nextTick();
shishiArr.value.forEach((item) => {
dataJinriSum.value.forEach((itemjinri) => {
item.rcwy = itemjinri.rcwy.toFixed(4);
item.nl = itemjinri.nl.toFixed(4);
item.yl = itemjinri.yl.toFixed(4);
item.yearPerCent = calcPercent(item.yearPlan, item.nl);
});
});
// getYearShengchansj(); //
}
});
};
function calcPercent(yearJihua, yearShiji) {
// 0
// 100
let plan = parseFloat(yearJihua === '' ? 0 : yearJihua);
let shiji = parseFloat(yearShiji);
let percent = 0;
//
if (plan > 0) {
percent = (shiji / plan) * 100;
percent = Math.min(percent, 100); // 100%
}
return parseFloat(percent.toFixed(2)); //
}
function sumByOil(records) {
// console.log(records);
const summaryMap = {};
try {
records.forEach((record) => {
const gas = record.gas;
if (!summaryMap[gas]) {
// gas
summaryMap[gas] = {
rcwy: 0,
yl: 0,
nl: 0
};
}
// gas
summaryMap[gas].rcwy += record.rcwy || 0;
summaryMap[gas].yl += record.yl || 0;
summaryMap[gas].nl += record.nl || 0;
const strDate = ref('');
//
const formatNumber = (num) => {
if (typeof num !== 'number') return '-';
return num.toFixed(4).replace(/\.?0+$/, '');
};
function goHistory(val) {
uni.navigateTo({
url: '/pages/views/shengchan/ribaoshuju/rbsjLsxq?data=' + JSON.stringify(val) + '&type=yy'
});
return Object.values(summaryMap);
} catch (error) {
//TODO handle the exception
// console.log(error);
}
}
const getJinriYuanyouShengchansj = () => {
const now = new Date();
if (now.getHours() < 11) {
strDate.value = formatDate(getDateAfterDays(now, -1)).toString(); //11
} else {
strDate.value = formatDate(now).toString();
}
let queryParms = {};
dataJinri.value = [];
dataJinriSum.value = [];
queryParms.scrq = strDate.value;
queryParms.pageSize = 100;
// // console.log(queryParms);
queryJinriYuanyouShengchansj(queryParms).then((res) => {
if (res.success) {
// // console.log(res);
dataJinri.value = res.result.records;
dataJinriSum.value = sumByOil(dataJinri.value); //gas unit rq cq totalGas
// // console.log(dataJinriSum.value);
nextTick();
shishiArr.value.forEach((item) => {
dataJinriSum.value.forEach((itemjinri) => {
item.rcwy = itemjinri.rcwy.toFixed(4);
item.nl = itemjinri.nl.toFixed(4);
item.yl = itemjinri.yl.toFixed(4);
item.yearPerCent = calcPercent(item.yearPlan, item.nl);
});
});
// getYearShengchansj(); //
}
});
};
function calcPercent(yearJihua, yearShiji) {
// 0
// 100
let plan = parseFloat(yearJihua === '' ? 0 : yearJihua);
let shiji = parseFloat(yearShiji);
let percent = 0;
//
if (plan > 0) {
percent = (shiji / plan) * 100;
percent = Math.min(percent, 100); // 100%
}
return parseFloat(percent.toFixed(2)); //
}
function sumByOil(records) {
// console.log(records);
const summaryMap = {};
try {
records.forEach((record) => {
const gas = record.gas;
if (!summaryMap[gas]) {
// gas
summaryMap[gas] = {
rcwy: 0,
yl: 0,
nl: 0
};
}
// gas
summaryMap[gas].rcwy += record.rcwy || 0;
summaryMap[gas].yl += record.yl || 0;
summaryMap[gas].nl += record.nl || 0;
});
return Object.values(summaryMap);
} catch (error) {
//TODO handle the exception
// console.log(error);
}
}
</script>
<style lang="scss" scoped>
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 0;
border-bottom: 1rpx solid #eee;
}
.title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.more {
font-size: 26rpx;
color: #666;
cursor: pointer;
}
.more::after {
content: '';
width: 8rpx;
height: 8rpx;
border-top: 2rpx solid #666;
border-right: 2rpx solid #666;
transform: rotate(45deg);
margin-left: 8rpx;
vertical-align: middle;
}
/* 鼠标悬停效果 */
.more:hover {
color: #007aff;
}
.container {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
gap: 20rpx;
}
.popup-content {
padding: 30rpx;
max-height: 70vh;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #eee;
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.table-container {
width: 100%;
height: 30vh;
overflow: hidden;
}
.table {
min-width: 100%;
border: 2rpx solid #e8e8e8;
.tr {
display: flex;
border-bottom: 2rpx solid #e8e8e8;
&.header {
background-color: #fafafa;
font-weight: 600;
}
&.even {
background-color: #f8f8f8;
}
}
.th,
.td {
flex: 1;
min-width: 80rpx;
padding: 10rpx;
font-size: 20rpx;
font-weight: 500;
color: #333;
text-align: center;
white-space: nowrap;
}
.th1,
.td1 {
flex: 1;
max-width: 40rpx;
padding: 10rpx;
font-size: 22rpx;
font-weight: bold;
color: #333;
text-align: center;
white-space: nowrap;
height: 30rpx;
vertical-align: middle;
}
.th {
background-color: #f0f0f0;
}
.td.negative {
color: #ff4444;
font-weight: 500;
}
}
.empty {
padding: 40rpx;
text-align: center;
color: #888;
font-size: 16rpx;
}
.card-item {
flex: 1 1 200rpx; // 300rpx selectedGas formatNumber
min-width: 200rpx;
max-width: calc(50% - 10rpx); //
@media (min-width: 768px) {
max-width: calc(33.33% - 14rpx); // 3
}
}
.card {
background: #ececec;
border-radius: 16rpx;
padding: 15rpx;
box-shadow: 0 4rpx 12rpx rgba(197, 197, 197, 0.1);
.title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
}
.content {
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
padding: 15rpx 0;
border-bottom: 1rpx solid #eee;
margin-left: 10px;
margin-right: 10px;
&:last-child {
margin-bottom: 0;
}
.title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.more {
font-size: 26rpx;
color: #666;
cursor: pointer;
}
.more::after {
content: '';
width: 8rpx;
height: 8rpx;
border-top: 2rpx solid #666;
border-right: 2rpx solid #666;
transform: rotate(45deg);
margin-left: 8rpx;
vertical-align: middle;
}
/* 鼠标悬停效果 */
.more:hover {
color: #007aff;
}
.container {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
gap: 20rpx;
}
.popup-content {
padding: 30rpx;
max-height: 70vh;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #eee;
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.table-container {
width: 100%;
height: 30vh;
overflow: hidden;
}
.table {
min-width: 100%;
border: 2rpx solid #e8e8e8;
.tr {
display: flex;
border-bottom: 2rpx solid #e8e8e8;
&.header {
background-color: #fafafa;
font-weight: 600;
}
&.even {
background-color: #f8f8f8;
}
}
.label {
font-size: 24rpx;
color: #666;
.th,
.td {
flex: 1;
min-width: 80rpx;
padding: 10rpx;
font-size: 20rpx;
font-weight: 500;
color: #333;
text-align: center;
white-space: nowrap;
}
.value {
font-size: 28rpx;
color: #0000ff;
.th1,
.td1 {
flex: 1;
max-width: 40rpx;
padding: 10rpx;
font-size: 22rpx;
font-weight: bold;
color: #333;
text-align: center;
white-space: nowrap;
height: 30rpx;
vertical-align: middle;
}
.th {
background-color: #f0f0f0;
}
.td.negative {
color: #ff4444;
font-weight: 500;
}
}
}
.progress-item {
margin-bottom: 20px;
}
.progress-bar {
position: relative;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
}
.empty {
padding: 40rpx;
text-align: center;
color: #888;
font-size: 16rpx;
}
.progress {
height: 100%;
transition: all 0.3s;
}
.card-item {
flex: 1 1 200rpx; // 300rpx selectedGas formatNumber
min-width: 200rpx;
max-width: calc(50% - 10rpx); //
.progress-text {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: red; /* 保持红色 */
font-size: 12px;
font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); /* 提升可读性 */
}
</style>
@media (min-width: 768px) {
max-width: calc(33.33% - 14rpx); // 3
}
}
.card {
background: #ececec;
border-radius: 16rpx;
padding: 15rpx;
box-shadow: 0 4rpx 12rpx rgba(197, 197, 197, 0.1);
.title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
}
.content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 24rpx;
color: #666;
}
.value {
font-size: 28rpx;
color: #0000ff;
font-weight: 500;
}
}
}
.progress-item {
margin-bottom: 20px;
}
.progress-bar {
position: relative;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
}
.progress {
height: 100%;
transition: all 0.3s;
}
.progress-text {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: red;
/* 保持红色 */
font-size: 12px;
font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
/* 提升可读性 */
}
</style>

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@ -0,0 +1,44 @@
<template>
<view :class="{ gray: store.isgray == 1 }">
<trqSssjVue></trqSssjVue>
</view>
</template>
<script setup>
import {
ref,
onMounted,
computed,
nextTick,
watchEffect
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
queryJinriShengchansj,
queryYearShengchansj,
queryJinriTrqShengchansj
} from '@/api/shengchan.js';
import {
formatDate,
getDateAfterDays
} from '@/utils/dateTime.js';
import {
beforeJump
} from '@/utils/index.js';
import {
useStore
} from '@/store';
import trqSssjVue from './trqSssj.vue';
const store = useStore();
</script>
<style scoped>
</style>

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@ -0,0 +1,315 @@
<template>
<view :class="{ gray: store.isgray == 1 }">
<view>
<!-- 标题行 -->
<view class="header-row">
<view class="title">天然气实时数据</view>
<view style="min-width: 200px;"><cxc-szcx-stationJl-select v-model="stationID" returnCodeOrID="id"
@change="onChange"></cxc-szcx-stationJl-select></view>
</view>
</view>
<button size="mini" @click="getData">连接WebSocket</button>
<view class="container">
<view v-for="(item, index) in jlData" :key="index" class="card">
<view class="field-item">
<text class="titlejl">{{ stationName }}--{{ item.jldname }}</text>
<view class="status-circle"
:style="{ backgroundColor: item.yxzt==='运行' ? '#4CAF50' : '#F44336' }">
{{item.yxzt}}
</view>
</view>
<view class="field-list">
<!-- 压力 -->
<view class="field-item">
<text class="field-label">压力(MPa)</text>
<text class="field-value">{{ formatNumber(item.yl) || '-' }}</text>
</view>
<!-- 差压 -->
<view class="field-item">
<text class="field-label">差压(kPa)</text>
<text class="field-value">{{ formatNumber(item.yc) || '-' }}</text>
</view>
<!-- 温度 -->
<view class="field-item">
<text class="field-label">温度()</text>
<text class="field-value">{{ formatNumber(item.wd) || '-' }}</text>
</view>
<!-- 瞬时流量 -->
<view class="field-item">
<text class="field-label">瞬时流量(/d)</text>
<text class="field-value">{{ formatNumber(item.ssll) || '-' }}</text>
</view>
<!-- 今日流量 -->
<view class="field-item">
<text class="field-label">今日流量()</text>
<text class="field-value">{{ formatNumber(item.jrl) || '-' }}</text>
</view>
<!-- 昨日流量 -->
<view class="field-item">
<text class="field-label">昨日流量()</text>
<text class="field-value">{{ formatNumber(item.zrl) || '-' }}</text>
</view>
<!-- 昨日时间 -->
<view class="field-item">
<text class="field-label">昨日时间(min)</text>
<text class="field-value">{{ formatNumber(item.zrsj) || '-' }}</text>
</view>
<!-- 今日时间 -->
<view class="field-item">
<text class="field-label">今日时间(min)</text>
<text class="field-value">{{ formatNumber(item.jrsj) || '-' }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
computed,
nextTick,
watchEffect
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
queryJldZcList,
queryJldDataByZc
} from '@/api/shengchan.js';
import {
formatDate,
getDateAfterDays
} from '@/utils/dateTime.js';
import {
beforeJump
} from '@/utils/index.js';
import {
useStore
} from '@/store';
const store = useStore();
const stationList = ref([])
//
const popupSelect = ref(null);
const stationID = ref("")
const stationName = ref("")
const jlData = ref([])
const sssjUrl = ref('wss://36.112.48.190/Gyk/websocket/')
const jlByzc = ref('https://36.112.48.190/Gyk/sssj/GetJlByZc')
//websocket线
// websocket
function connectSocketInit() {
console.log(11, )
let userID = '1412198011559055361'
// this.socketTasksocket
uni.connectSocket({
// ,使ws://127.0.0.1:9099
// url: 'wss://'+this.$api.sellerWebsocket+'/'+this.userinfo.id,
url: sssjUrl.value + userID,
success(data) {
console.log(data);
console.log('websocket连接成功');
}
});
// ,
uni.onSocketOpen(function(res) {
console.log('WebSocket连接已打开');
});
uni.onSocketMessage(function(res) {
console.log('收到服务器内容:' + res.data);
// start
const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
innerAudioContext.src = 'https://wzs1.oss-cn-beijing.aliyuncs.com/music.mp3';
innerAudioContext.onPlay(() => {
console.log('开始播放');
});
innerAudioContext.onError(res => {
console.log(res.errMsg);
console.log(res.errCode);
});
// end
});
// socket
uni.onSocketClose(function(res) {
console.log('WebSocket 已关闭!');
});
}
function getData() {
connectSocketInit()
}
function onChange(e, data) {
console.log(2, e, data.value);
stationID.value = e
stationName.value = data.value.title
uni.request({
url: jlByzc.value + '?zhanc=' + stationID.value + '&jldLx=0',
method: 'GET',
success: (res) => {
console.log(3, res, stationName.value)
jlData.value = JSON.parse(res.data.result).JlData;
console.log(4, jlData.value)
}
})
}
const websock = ref(null);
const timer2 = ref(null);
//
const websocketheart = () => {
timer2.value = setInterval(() => {
if (websock.value && websock.value.readyState === 1) {
//
connectSocketInit()
}
}, 1000);
};
onMounted(() => {
queryJldZcList({
pId: '1267633406031953921'
}).then((res) => {
if (res.success) {
stationList.value = res.result
console.log(1, stationList.value)
}
})
websocketheart()
})
//
const formatNumber = (num) => {
let temp = 0;
try {
temp = parseFloat(num);
} catch (error) {
//TODO handle the exception
}
return temp.toFixed(4).replace(/\.?0+$/, '');
};
</script>
<style scoped>
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10 10rpx;
border-bottom: 1rpx solid #eee;
margin-left: 10px;
margin-right: 10px;
}
.title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.titlejl {
font-size: 20rpx;
vertical-align: middle;
font-weight: bold;
color: #0055ff;
margin-bottom: 15px;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
padding: 16px;
}
.card {
background: #fff;
border-radius: 8px;
padding: 5px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.field-list {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
/* 允许子元素换行 */
gap: 3px;
}
.field-item {
display: flex;
height: 30px;
justify-content: space-between;
align-items: center;
padding: 5px 5px;
background: #f8f9fa;
border-radius: 4px;
flex-basis: calc(50%-10px);
/* 每个元素占据约一半宽度,减去间隙 */
box-sizing: border-box;
/* 包含内边距和边框 */
}
/* 当屏幕宽度较小时,每个元素占据整行 */
@media (max-width: 200px) {
.field-item {
flex-basis: 100%;
}
}
.field-label {
color: #666;
font-size: 8px;
flex: 1;
margin-right: 2px;
width: 80px;
font-weight: 500;
}
.field-value {
color: #1890ff;
font-weight: 500;
font-size: 12px;
text-align: right;
width: 60px;
overflow: hidden;
text-overflow: ellipsis;
}
.status-circle {
width: 70rpx;
height: 30rpx;
font-size: 12px;
vertical-align: middle;
}
</style>

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

947
static/data/nlszcs.json Normal file
View File

@ -0,0 +1,947 @@
[{
"序号": "1",
"单位职务": "安全环保室主管",
"姓名": "方超",
"性别": "男",
"劳动合同号": "239603",
"2024.02考试成绩": "",
"2024.08考试成绩": "90.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "2",
"单位职务": "安全环保督查中心(工程监督中心)助理员",
"姓名": "孔玉",
"性别": "男",
"劳动合同号": "239616",
"2024.02考试成绩": "",
"2024.08考试成绩": "87.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "3",
"单位职务": "人力资源服务中心副主任",
"姓名": "陈康朝",
"性别": "男",
"劳动合同号": "103333",
"2024.02考试成绩": "",
"2024.08考试成绩": "88.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "4",
"单位职务": "人力资源服务中心主管师",
"姓名": "韩斌",
"性别": "男",
"劳动合同号": "018733",
"2024.02考试成绩": "",
"2024.08考试成绩": "84.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "5",
"单位职务": "治安保卫中心主办",
"姓名": "齐显琛",
"性别": "男",
"劳动合同号": "100496",
"2024.02考试成绩": "",
"2024.08考试成绩": "87.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "6",
"单位职务": "柳屯输气管理区主管师",
"姓名": "殷丽丽",
"性别": "女",
"劳动合同号": "008925",
"2024.02考试成绩": "",
"2024.08考试成绩": "87.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "7",
"单位职务": "东濮采气管理区副经理",
"姓名": "王全超",
"性别": "男",
"劳动合同号": "009115",
"2024.02考试成绩": "",
"2024.08考试成绩": "82.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "8",
"单位职务": "东濮采气管理项目部副经理",
"姓名": "彭江苏",
"性别": "男",
"劳动合同号": "018645",
"2024.02考试成绩": "",
"2024.08考试成绩": "86.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "9",
"单位职务": "天然气计量化验中心助理师",
"姓名": "张海波",
"性别": "男",
"劳动合同号": "237229",
"2024.02考试成绩": "",
"2024.08考试成绩": "90.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "10",
"单位职务": "开发研究所助理师",
"姓名": "王瑞浩",
"性别": "男",
"劳动合同号": "241327",
"2024.02考试成绩": "",
"2024.08考试成绩": "86.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "11",
"单位职务": "天津LNG项目部助理师",
"姓名": "史航",
"性别": "男",
"劳动合同号": "100474",
"2024.02考试成绩": "",
"2024.08考试成绩": "88.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "12",
"单位职务": "通南巴项目主管师",
"姓名": "黄卓",
"性别": "男",
"劳动合同号": "039385",
"2024.02考试成绩": "",
"2024.08考试成绩": "87.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "13",
"单位职务": "输气管理项目部助理师",
"姓名": "孙宇明",
"性别": "男",
"劳动合同号": "102625",
"2024.02考试成绩": "",
"2024.08考试成绩": "85.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "14",
"单位职务": "输气管理项目部助理师",
"姓名": "张艺皓",
"性别": "男",
"劳动合同号": "240971",
"2024.02考试成绩": "",
"2024.08考试成绩": "83.0",
"2025.02考试成绩": "",
"截止有效期": "2025年8月"
},
{
"序号": "15",
"单位职务": "生产调度室主管",
"姓名": "向龙",
"性别": "男",
"劳动合同号": "103812",
"2024.02考试成绩": "98.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "16",
"单位职务": "计划财务室助理员",
"姓名": "吴曾科",
"性别": "男",
"劳动合同号": "236709",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "86.0",
"截止有效期": "2026年2月"
},
{
"序号": "17",
"单位职务": "经营管理室主办",
"姓名": "穆丽",
"性别": "女",
"劳动合同号": "001334",
"2024.02考试成绩": "87.0",
"2024.08考试成绩": "82.0",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "18",
"单位职务": "综合办公室助理员",
"姓名": "杨非非",
"性别": "男",
"劳动合同号": "236635",
"2024.02考试成绩": "93.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "19",
"单位职务": "群众工作办公室助理员",
"姓名": "余杰",
"性别": "男",
"劳动合同号": "236739",
"2024.02考试成绩": "95.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "20",
"单位职务": "群众工作办公室助理员",
"姓名": "马婷",
"性别": "女",
"劳动合同号": "239720",
"2024.02考试成绩": "94.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "21",
"单位职务": "市场开发室主管",
"姓名": "靳丽娟",
"性别": "女",
"劳动合同号": "022648",
"2024.02考试成绩": "95.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "22",
"单位职务": "安全环保督查中心(工程监督中心)主办",
"姓名": "杨家田",
"性别": "男",
"劳动合同号": "239723",
"2024.02考试成绩": "91.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "23",
"单位职务": "安全环保督查中心(工程监督中心)主办",
"姓名": "罗继勇",
"性别": "男",
"劳动合同号": "239766",
"2024.02考试成绩": "92.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "24",
"单位职务": "天然气计量化验中心副主任",
"姓名": "张杰",
"性别": "男",
"劳动合同号": "240802",
"2024.02考试成绩": "93.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "25",
"单位职务": "天然气计量化验中心主管师",
"姓名": "姜文峰",
"性别": "男",
"劳动合同号": "237664",
"2024.02考试成绩": "94.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "26",
"单位职务": "治安保卫中心助理员",
"姓名": "李巍",
"性别": "男",
"劳动合同号": "098529",
"2024.02考试成绩": "97.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "27",
"单位职务": "开发研究所助理师",
"姓名": "王志远",
"性别": "男",
"劳动合同号": "241278",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "80.0",
"截止有效期": "2026年2月"
},
{
"序号": "28",
"单位职务": "东濮采气管理区副经理",
"姓名": "王鹏",
"性别": "男",
"劳动合同号": "237338",
"2024.02考试成绩": "98.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "29",
"单位职务": "东濮采气管理区主管师",
"姓名": "于蔚兰",
"性别": "女",
"劳动合同号": "101355",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "83.0",
"截止有效期": "2026年2月"
},
{
"序号": "30",
"单位职务": "东濮采气管理区主管师",
"姓名": "何战波",
"性别": "男",
"劳动合同号": "239771",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "87.0",
"截止有效期": "2026年2月"
},
{
"序号": "31",
"单位职务": "东濮采气管理区助理师",
"姓名": "裴昱赫",
"性别": "男",
"劳动合同号": "239561",
"2024.02考试成绩": "92.0",
"2024.08考试成绩": "83.0",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "32",
"单位职务": "东濮采气管理区助理师",
"姓名": "李锡原",
"性别": "男",
"劳动合同号": "241174",
"2024.02考试成绩": "94.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "33",
"单位职务": "文留输气管理区副经理",
"姓名": "王京健",
"性别": "男",
"劳动合同号": "239543",
"2024.02考试成绩": "100.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "34",
"单位职务": "文留输气管理区副经理",
"姓名": "胡海",
"性别": "男",
"劳动合同号": "095325",
"2024.02考试成绩": "98.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "35",
"单位职务": "文留输气管理区主管",
"姓名": "刘先振",
"性别": "男",
"劳动合同号": "013995",
"2024.02考试成绩": "96.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "36",
"单位职务": "文留输气管理区主管师",
"姓名": "张琦",
"性别": "男",
"劳动合同号": "239530",
"2024.02考试成绩": "97.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "37",
"单位职务": "柳屯输气管理区主管师",
"姓名": "韩宣春",
"性别": "男",
"劳动合同号": "022645",
"2024.02考试成绩": "98.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "38",
"单位职务": "巡线大队主管师",
"姓名": "王斌",
"性别": "男",
"劳动合同号": "025333",
"2024.02考试成绩": "100.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "39",
"单位职务": "压缩机维保项目部副经理",
"姓名": "郭阳",
"性别": "男",
"劳动合同号": "013992",
"2024.02考试成绩": "94.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "40",
"单位职务": "压缩机维保项目部副经理",
"姓名": "张恒立",
"性别": "男",
"劳动合同号": "240655",
"2024.02考试成绩": "93.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "41",
"单位职务": "压缩机维保项目部助理师",
"姓名": "张博",
"性别": "男",
"劳动合同号": "241196",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "89.0",
"截止有效期": "2026年2月"
},
{
"序号": "42",
"单位职务": "普光项目部副经理",
"姓名": "王宝华",
"性别": "男",
"劳动合同号": "017889",
"2024.02考试成绩": "97.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "43",
"单位职务": "普光项目部党支部副书记",
"姓名": "杨申",
"性别": "男",
"劳动合同号": "239677",
"2024.02考试成绩": "100.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "44",
"单位职务": "普光项目部主管师",
"姓名": "周峰",
"性别": "男",
"劳动合同号": "095931",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "89.0",
"截止有效期": "2026年2月"
},
{
"序号": "45",
"单位职务": "普光项目部助理师",
"姓名": "安克法",
"性别": "男",
"劳动合同号": "239718",
"2024.02考试成绩": "92.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "46",
"单位职务": "输气管理项目部副主任师",
"姓名": "蔡小虎",
"性别": "男",
"劳动合同号": "043004",
"2024.02考试成绩": "96.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "47",
"单位职务": "人力资源服务中心主管师",
"姓名": "熊文",
"性别": "男",
"劳动合同号": "041870",
"2024.02考试成绩": "98.0",
"2024.08考试成绩": "",
"2025.02考试成绩": "",
"截止有效期": "2026年2月"
},
{
"序号": "48",
"单位职务": "生产调度室主管",
"姓名": "孙东阳",
"性别": "男",
"劳动合同号": "013998",
"2024.02考试成绩": "",
"2024.08考试成绩": "94.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "49",
"单位职务": "综合办公室主办",
"姓名": "王谦",
"性别": "男",
"劳动合同号": "236681",
"2024.02考试成绩": "",
"2024.08考试成绩": "92.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "50",
"单位职务": "综合办公室助理师",
"姓名": "王云尧",
"性别": "男",
"劳动合同号": "240974",
"2024.02考试成绩": "",
"2024.08考试成绩": "95.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "51",
"单位职务": "安全环保室助理员",
"姓名": "高晨峰",
"性别": "男",
"劳动合同号": "240477",
"2024.02考试成绩": "",
"2024.08考试成绩": "94.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "52",
"单位职务": "组织人事室助理员",
"姓名": "吕晓路",
"性别": "男",
"劳动合同号": "237039",
"2024.02考试成绩": "",
"2024.08考试成绩": "92.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "53",
"单位职务": "市场开发室主办",
"姓名": "郑慧研",
"性别": "男",
"劳动合同号": "101099",
"2024.02考试成绩": "",
"2024.08考试成绩": "100.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "54",
"单位职务": "安全环保督查中心(工程监督中心)主办",
"姓名": "张龙超",
"性别": "男",
"劳动合同号": "236628",
"2024.02考试成绩": "",
"2024.08考试成绩": "94.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "55",
"单位职务": "安全环保督查中心(工程监督中心)助理师",
"姓名": "杨高峰",
"性别": "男",
"劳动合同号": "240973",
"2024.02考试成绩": "",
"2024.08考试成绩": "91.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "56",
"单位职务": "生产服务中心党支部副书记",
"姓名": "柳彬",
"性别": "男",
"劳动合同号": "098107",
"2024.02考试成绩": "",
"2024.08考试成绩": "95.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "57",
"单位职务": "治安保卫中心副主任",
"姓名": "杨舜",
"性别": "男",
"劳动合同号": "100521",
"2024.02考试成绩": "",
"2024.08考试成绩": "93.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "58",
"单位职务": "天然气计量化验中心主管",
"姓名": "程志伟",
"性别": "男",
"劳动合同号": "101359",
"2024.02考试成绩": "",
"2024.08考试成绩": "91.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "59",
"单位职务": "开发研究所副所长",
"姓名": "曹正安",
"性别": "男",
"劳动合同号": "039383",
"2024.02考试成绩": "",
"2024.08考试成绩": "93.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "60",
"单位职务": "开发研究所主管师",
"姓名": "龙一慧",
"性别": "女",
"劳动合同号": "240318",
"2024.02考试成绩": "",
"2024.08考试成绩": "93.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "61",
"单位职务": "开发研究所主管师",
"姓名": "李香芹",
"性别": "女",
"劳动合同号": "040224",
"2024.02考试成绩": "",
"2024.08考试成绩": "92.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "62",
"单位职务": "文留输气管理区副经理",
"姓名": "张海波",
"性别": "男",
"劳动合同号": "042449",
"2024.02考试成绩": "",
"2024.08考试成绩": "98.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "63",
"单位职务": "柳屯输气管理区党支部副书记、副经理",
"姓名": "张超",
"性别": "男",
"劳动合同号": "103814",
"2024.02考试成绩": "",
"2024.08考试成绩": "94.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "64",
"单位职务": "通南巴项目助理师",
"姓名": "梁鹏辉",
"性别": "男",
"劳动合同号": "240534",
"2024.02考试成绩": "",
"2024.08考试成绩": "98.0",
"2025.02考试成绩": "",
"截止有效期": "2026年8月"
},
{
"序号": "65",
"单位职务": "市场开发室主管",
"姓名": "徐胜愿",
"性别": "男",
"劳动合同号": "043083",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "96.0",
"截止有效期": "2027年2月"
},
{
"序号": "66",
"单位职务": "安全环保室主办",
"姓名": "鲍灵云",
"性别": "女",
"劳动合同号": "098095",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "95.0",
"截止有效期": "2027年2月"
},
{
"序号": "67",
"单位职务": "组织人事室主办",
"姓名": "高俊华",
"性别": "女",
"劳动合同号": "097139",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "99.0",
"截止有效期": "2027年2月"
},
{
"序号": "68",
"单位职务": "人力资源服务中心助理师",
"姓名": "周正葳",
"性别": "男",
"劳动合同号": "240970",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "99.0",
"截止有效期": "2027年2月"
},
{
"序号": "69",
"单位职务": "人力资源服务中心业务员",
"姓名": "武文斌",
"性别": "男",
"劳动合同号": "236789",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "94.0",
"截止有效期": "2027年2月"
},
{
"序号": "70",
"单位职务": "纪检监督室助理员",
"姓名": "屈倩竹",
"性别": "女",
"劳动合同号": "240735",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "93.0",
"截止有效期": "2027年2月"
},
{
"序号": "71",
"单位职务": "天然气计量化验中心副主任",
"姓名": "郭照祥",
"性别": "男",
"劳动合同号": "237637",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "92.0",
"截止有效期": "2027年2月"
},
{
"序号": "72",
"单位职务": "天然气计量化验中心副主任师",
"姓名": "段善友",
"性别": "男",
"劳动合同号": "096248",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "92.0",
"截止有效期": "2027年2月"
},
{
"序号": "73",
"单位职务": "东濮采气管理区主管",
"姓名": "张凯",
"性别": "男",
"劳动合同号": "018913",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "98.0",
"截止有效期": "2027年2月"
},
{
"序号": "74",
"单位职务": "东濮采气管理区主办",
"姓名": "胡小光",
"性别": "男",
"劳动合同号": "097043",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "95.0",
"截止有效期": "2027年2月"
},
{
"序号": "75",
"单位职务": "东濮采气管理项目部主管",
"姓名": "贾福东",
"性别": "男",
"劳动合同号": "026356",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "93.0",
"截止有效期": "2027年2月"
},
{
"序号": "76",
"单位职务": "柳屯输气管理区助理师",
"姓名": "刘子敬",
"性别": "男",
"劳动合同号": "240638",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "95.0",
"截止有效期": "2027年2月"
},
{
"序号": "77",
"单位职务": "柳屯输气管理区助理师",
"姓名": "马天娇",
"性别": "女",
"劳动合同号": "241155",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "93.0",
"截止有效期": "2027年2月"
},
{
"序号": "78",
"单位职务": "压缩机维保项目部助理师",
"姓名": "王帅",
"性别": "男",
"劳动合同号": "103322",
"2024.02考试成绩": "83.0",
"2024.08考试成绩": "93.0",
"2025.02考试成绩": "",
"截止有效期": "2027年2月"
},
{
"序号": "79",
"单位职务": "山东管道项目部业务员",
"姓名": "侯明路",
"性别": "男",
"劳动合同号": "238781",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "91.0",
"截止有效期": "2027年2月"
},
{
"序号": "80",
"单位职务": "普光项目部副经理",
"姓名": "姚文涛",
"性别": "男",
"劳动合同号": "073696",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "99.0",
"截止有效期": "2027年2月"
},
{
"序号": "81",
"单位职务": "普光项目部主管师",
"姓名": "彭端勇",
"性别": "男",
"劳动合同号": "101106",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "98.0",
"截止有效期": "2027年2月"
},
{
"序号": "82",
"单位职务": "普光项目部助理师",
"姓名": "陈饷",
"性别": "男",
"劳动合同号": "023239",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "96.0",
"截止有效期": "2027年2月"
},
{
"序号": "83",
"单位职务": "通南巴项目部主管",
"姓名": "李鹏辉",
"性别": "男",
"劳动合同号": "101104",
"2024.02考试成绩": "87.0",
"2024.08考试成绩": "96.0",
"2025.02考试成绩": "",
"截止有效期": "2027年2月"
},
{
"序号": "84",
"单位职务": "通南巴项目部助理师",
"姓名": "付小波",
"性别": "男",
"劳动合同号": "013974",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "93.0",
"截止有效期": "2027年2月"
},
{
"序号": "85",
"单位职务": "天津LNG项目部技术员",
"姓名": "江嘉俊",
"性别": "男",
"劳动合同号": "103317",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "99.0",
"截止有效期": "2027年2月"
},
{
"序号": "86",
"单位职务": "天津LNG项目部业务员",
"姓名": "周晓亮",
"性别": "男",
"劳动合同号": "026622",
"2024.02考试成绩": "",
"2024.08考试成绩": "",
"2025.02考试成绩": "93.0",
"截止有效期": "2027年2月"
}
]

View File

@ -0,0 +1,132 @@
<template>
<view class="compact-datetime-picker">
<!-- 输入框区域 -->
<view class="input-container">
<view class="compact-input" @click="openPicker">
{{ dateRange[0] || '开始日期' }}
</view>
<text v-if="mode==='range'" class="separator"></text>
<view v-if="mode==='range'" class="compact-input" @click="openPicker">
{{ dateRange[1] || '结束日期' }}
</view>
</view>
<wu-calendar ref="calendar" :mode="mode" :date="dateRange" @confirm="calendarConfirm"
slideSwitchMode="horizontal" type="month" :fold="false" :insert="false" :rangeSameDay="true" :lunar="true"
:monthShowCurrentMonth="false" :range-end-repick="true" :item-height="60" :rangeEndRepick="true"
:startDate="startDate" :endDate="endDate" :isSearch="isSearch"></wu-calendar>
</view>
</template>
<script>
import {
formatDate,
getDateAfterDays,
getDateAfterMonths
} from '@/utils/dateTime.js';
export default {
props: {
modelValue: {
type: Array,
default: () => [null, null]
},
//
mode: {
type: String,
default: 'single'
},
//
startDate: {
type: String,
default: ''
},
//
endDate: {
type: String,
default: ''
},
isSearch: {
type: Boolean,
default: true
}
},
data() {
return {
dateRange: null
};
},
watch: {
modelValue: {
immediate: true,
handler(newVal) {
let dateType = Object.prototype.toString.call(newVal);
if (this.mode == 'single' && dateType != '[object String]') {
return console.error(`类型错误mode=${this.mode}date=String`);
} else if (this.mode != 'single' && dateType != '[object Array]') {
return console.error(`类型错误mode=${this.mode}date=Array`);
}
if (this.mode == 'single') {
this.dateRange = ''
this.dateRange = newVal;
}
//
if (this.mode == 'multiple') {
this.dateRange = []
this.dateRange = newVal;
} else if (this.mode == 'range') {
if (newVal[1] == 'NaN-NaN-NaN') newVal[1] = newVal[0]
this.dateRange = []
this.dateRange.push(formatDate(new Date(newVal[0])));
this.dateRange.push(formatDate(new Date(newVal[1])));
}
}
}
},
methods: {
openPicker() {
this.$refs.calendar.open();
},
calendarConfirm(e) {
this.dateRange = [];
try {
this.dateRange.push(formatDate(new Date(e.range.before)));
this.dateRange.push(formatDate(new Date(e.range.after)));
} catch (error) {
return;
//TODO handle the exception
}
this.$emit('update:modelValue', this.dateRange);
}
}
};
</script>
<style scoped>
.input-container {
display: flex;
align-items: center;
gap: 5px;
}
/* 紧凑型输入框 */
.compact-input {
flex: 1;
padding: 6px 8px;
font-size: 16px;
height: 20px;
line-height: 20px;
border: 1px solid #dcdfe6;
border-radius: 4px;
text-align: center;
width: 90px;
}
.compact-input.active {
border-color: #409eff;
background-color: #f5f7ff;
}
</style>

View File

@ -0,0 +1,83 @@
{
"id": "cxc-szcx-dateRangeSelect",
"displayName": "cxc-szcx-dateRangeSelect",
"version": "1.0.0",
"description": "cxc-szcx-dateRangeSelect",
"keywords": [
"cxc-szcx-dateRangeSelect"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"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": "u",
"aliyun": "u",
"alipay": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,168 @@
### 紧凑型日期时间选择器组件说明
#### 一、组件功能
该组件是基于 UniApp 开发的紧凑型日期选择器,支持以下功能:
1. **三种选择模式**
- 单选single
- 范围选择range
- 多选multiple
2. **弹出式日历选择**
- 支持月份滑动切换
- 显示农历日期
- 支持同天范围选择
3. **数据双向绑定**
- 通过 `modelValue` 实现父子组件数据同步
4. **自定义样式**
- 紧凑型输入框设计
- 支持自定义主题颜色
- 响应式布局适配
5. **支持自定义日期禁用范围**
- 限制用户选择的日期范围。只能选择在 startDate 和 endDate 之间的日期,超出该范围的日期将被禁用,无法选择。
- startDate设置可选日期的起始日期用户不能选择早于该日期的日期。
- endDate设置可选日期的结束日期用户不能选择晚于该日期的日期。
#### 二、组件Props
| 参数名 | 类型 | 默认值 | 说明 |
|--------------|------------|----------|-------------------------------- |
| `modelValue` | Array/Date | `[null]` | 双向绑定的值(根据模式变化) |
| `mode` | String | `single` | 选择模式single/range/multiple |
| `startDate` | String | ` ` | 设置可选日期的起始日期 |
| `endDate` | String | ` ` | 设置可选日期的结束日期 |
| `isSearch` | Boolean | `true` | 设置快速选择日期样式 |
#### 三、组件数据
| 名称 | 类型 | 说明 |
|------------|--------|--------------------------|
| `dateRange` | Array | 当前选中的日期范围(内部状态) |
#### 四、组件方法
| 方法名 | 参数 | 说明 |
|----------------|------------|--------------------------|
| `openPicker` | 无 | 打开日历选择器 |
| `calendarConfirm` | `e` | 日历确认回调(处理选中数据) |
#### 五、事件说明
| 事件名 | 说明 | 返回值 |
|--------------|--------------------------|----------------------|
| `update:modelValue` | 数据更新时触发 | 当前选中的日期数组 |
#### 六、样式说明
```vue
<style scoped>
.input-container {
display: flex;
align-items: center;
gap: 10px;
}
.compact-input {
flex: 1;
padding: 6px 8px;
font-size: 16px;
height: 20px;
line-height: 20px;
border: 1px solid #dcdfe6;
border-radius: 4px;
text-align: center;
width: 120px;
}
.compact-input.active {
border-color: #409eff;
background-color: #f5f7ff;
}
</style>
```
**关键样式点**
1. 输入框宽度固定为 120px
2. 紧凑型设计height: 20px
3. 活动状态样式增强
4. 水平排列布局
#### 七、使用示例
```vue
<template>
<view>
<!-- 单选模式 -->
<compact-datetime-picker
v-model="singleDate"
mode="single"
/>
<!-- 范围选择模式 -->
<compact-datetime-picker
v-model="rangeDate"
mode="range"
/>
<!-- 多选模式 -->
<compact-datetime-picker
v-model="multipleDate"
mode="multiple"
/>
</view>
</template>
<script>
export default {
data() {
return {
singleDate: null,
rangeDate: [null, null],
multipleDate: []
};
}
};
</script>
```
#### 八、注意事项
1. **日期格式要求**
- 单选模式:`YYYY-MM-DD` 字符串格式
- 范围模式:`[startDate, endDate]` 数组格式
- 多选模式:`[date1, date2, ...]` 数组格式
2. **依赖组件**
- 需安装 `wu-calendar` 组件(第三方库)
3. **异常处理**
```javascript
calendarConfirm(e) {
this.dateRange = [];
try {
this.dateRange.push(formatDate(new Date(e.range.before)));
this.dateRange.push(formatDate(new Date(e.range.after)));
} catch (error) {
return; // 异常处理
}
this.$emit('update:modelValue', this.dateRange);
}
```
4. **性能优化**
- 建议使用 `:range-end-repick="true"` 开启范围重新选择
- 通过 `:item-height="45"` 控制日历行高
#### 九、扩展功能建议
1. 添加时间选择功能
2. 增加国际化支持
3. 添加动画过渡效果
4. 支持预设常用日期范围
如果需要进一步定制化,可以修改以下部分:
1. 日历组件配置(`wu-calendar` 参数)
2. 日期格式化函数(`formatDate`
3. 输入框样式和交互逻辑
4. 异常处理机制

View File

@ -0,0 +1,71 @@
<template>
<view>
<uni-data-checkbox v-model="selectedValuesArray" :localdata="dictItems" :multiple="true" data-key="value"
data-value="label"></uni-data-checkbox>
</view>
</template>
<script setup>
import {
ref,
onMounted,
watch
} from 'vue';
import {
getDictItemsApi
} from '@/api/api.js';
// props emits
const props = defineProps({
dictCode: {
type: String,
required: true
},
modelValue: {
type: String,
default: ''
}
});
const emits = defineEmits(['update:modelValue', 'change']);
//
const dictItems = ref([]);
const selectedValuesArray = ref(props.modelValue ? props.modelValue.split(',') : []);
//
const loadDictItems = async () => {
try {
const response = await getDictItemsApi(props.dictCode);
dictItems.value = response.result; // { data: [...] }
} catch (error) {
console.error('加载字典数据失败:', error);
}
};
//
watch(selectedValuesArray, (newValue) => {
console.log('selectedValuesArray 变化:', newValue);
const selectedValuesString = newValue.join(',');
emits('update:modelValue', selectedValuesString);
emits('change', selectedValuesString);
}, {
deep: true
});
// props.modelValue
watch(() => props.modelValue, (newValue) => {
console.log('props.modelValue 变化:', newValue);
selectedValuesArray.value = newValue ? newValue.split(',') : [];
}, {
immediate: true
});
onMounted(() => {
loadDictItems();
});
</script>
<style scoped>
/* 可以添加组件的样式 */
</style>

View File

@ -0,0 +1,83 @@
{
"id": "cxc-szcx-dictSelect",
"displayName": "cxc-szcx-dictSelect",
"version": "1.0.0",
"description": "cxc-szcx-dictSelect",
"keywords": [
"cxc-szcx-dictSelect"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"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": "u",
"aliyun": "u",
"alipay": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,90 @@
# cxc-szcx-dictSelect
### 组件说明:`uni-data-checkbox` 多选字典项选择组件
#### 一、组件概述
该组件基于 Vue 3 和 `uni-data-checkbox` 实现了一个多选的字典项选择器。它通过传入字典编码(`dictCode`)从后端接口获取字典项数据,并将其展示为多选框列表。组件支持双向数据绑定,可将选中的值同步给父组件,同时在选中值发生变化时触发 `change` 事件。
#### 二、组件使用的技术栈
- **框架**Vue 3使用组合式 API
- **UI 组件**`uni-data-checkbox`(用于展示多选框列表)
- **请求库**:假设使用了自定义的 `getDictItemsApi` 函数进行后端数据请求
#### 三、组件输入Props
| 属性名 | 类型 | 是否必填 | 默认值 | 说明 |
| ---- | ---- | ---- | ---- | ---- |
| `dictCode` | String | 是 | 无 | 用于从后端获取字典项数据的字典编码 |
| `modelValue` | String | 否 | '' | 双向绑定的选中值,多个值以逗号分隔 |
#### 四、组件输出Emits
| 事件名 | 说明 | 参数 |
| ---- | ---- | ---- |
| `update:modelValue` | 当选中值发生变化时,用于更新父组件的 `modelValue` | 选中值的字符串,多个值以逗号分隔 |
| `change` | 当选中值发生变化时触发 | 选中值的字符串,多个值以逗号分隔 |
#### 五、组件内部数据
| 变量名 | 类型 | 说明 |
| ---- | ---- | ---- |
| `dictItems` | Ref<Array> | 存储从后端获取的字典项数据 |
| `selectedValuesArray` | Ref<Array> | 存储当前选中的值,以数组形式存储 |
#### 六、组件方法
##### 1. `loadDictItems`
- **功能**:异步从后端接口获取字典项数据,并将其赋值给 `dictItems`
- **实现逻辑**
- 调用 `getDictItemsApi` 函数,传入 `props.dictCode` 进行数据请求。
- 若请求成功,将响应数据的 `result` 字段赋值给 `dictItems.value`
- 若请求失败,在控制台输出错误信息。
##### 2. 监听 `selectedValuesArray` 的变化
- **功能**:当 `selectedValuesArray` 发生变化时,将选中的值转换为字符串,并触发 `update:modelValue``change` 事件通知父组件。
- **实现逻辑**
- 使用 `watch` 函数监听 `selectedValuesArray` 的变化。
- 当变化发生时,将新的选中值数组使用 `join(',')` 方法转换为字符串。
- 触发 `update:modelValue``change` 事件,将转换后的字符串作为参数传递。
##### 3. 监听 `props.modelValue` 的变化
- **功能**:当父组件传递的 `modelValue` 发生变化时,更新 `selectedValuesArray`
- **实现逻辑**
- 使用 `watch` 函数监听 `props.modelValue` 的变化。
- 当变化发生时,将新的 `modelValue` 使用 `split(',')` 方法转换为数组,并赋值给 `selectedValuesArray.value`
- `immediate: true` 表示在组件初始化时就会执行一次监听回调,确保初始值能正确同步。
#### 七、组件生命周期钩子
##### `onMounted`
- **功能**:在组件挂载后调用 `loadDictItems` 函数,从后端获取字典项数据。
#### 八、使用示例
```vue
<template>
<div>
<MyDictCheckbox
:dictCode="myDictCode"
v-model="selectedValues"
@change="handleChange"
/>
<p>当前选中的值: {{ selectedValues }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import MyDictCheckbox from './MyDictCheckbox.vue';
const myDictCode = 'exampleDictCode';
const selectedValues = ref('');
const handleChange = (newValue) => {
console.log('选中值发生变化:', newValue);
};
</script>
```
#### 九、注意事项
- 确保 `getDictItemsApi` 函数能正确获取后端数据,且响应数据的格式符合 `{ result: [...] }`
- 若 `modelValue` 初始值为空字符串,`selectedValuesArray` 会初始化为空数组。
- 由于使用了 `watch``deep: true` 选项,在 `selectedValuesArray` 内部元素发生变化时也会触发监听回调,可能会影响性能,需注意。

View File

@ -0,0 +1,274 @@
<template>
<view>
<!-- ECharts图表 -->
<view class="chart-container">
<l-echart ref="chart" @finished="initChart" />
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import { formatDate, getDateAfterDays, getDateAfterMonths } from '@/utils/dateTime.js';
const props = defineProps({
dataList: {
type: Array,
default: () => [],
required: true
},
xField: {
type: String,
default: 'rqDate'
},
yField: {
type: String,
default: 'rq'
},
legendField: {
type: String,
default: 'unit'
},
referenceValue: {
type: Number,
default: 0
}
});
const chart = ref(null);
const chartOption = ref({});
const chartTitle = ref('');
//
const processSeriesData = () => {
const legendItems = [...new Set(props.dataList.map((item) => item[props.legendField]?.trim() || '未命名'))];
return legendItems.map((legend) => {
const items = props.dataList
.filter((item) => item[props.legendField] === legend)
.sort((a, b) => new Date(a[props.xField]) - new Date(b[props.xField]))
.map((item) => ({
name: item[props.xField],
value: [
formatDate(item[props.xField]), // X
parseFloat(item[props.yField]) || 0 // Y
]
}));
return {
name: legend,
type: 'line',
showSymbol: true,
smooth: true,
data: items
};
});
};
//
watch(
() => props.dataList,
() => {
chartTitle.value = '历史趋势';
updateChart();
},
{ deep: true }
);
//
onMounted(() => {});
onUnmounted(() => {
if (chart.value) {
chart.value.dispose();
chart.value = null;
}
});
// formatDate
const updateChart = () => {
if (!chart.value) return;
chartOption.value = generateOptions();
chart.value.setOption(chartOption.value, true);
};
//
const generateOptions = () => ({
title: {
text: chartTitle.value,
padding: [0, 0, 0, 30]
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.96)',
borderColor: '#eee',
borderWidth: 1,
textStyle: {
color: '#333',
fontSize: 14
},
axisPointer: {
type: 'line',
lineStyle: {
color: '#FF6B6B',
width: 1,
type: 'dashed'
}
},
confine: true // tooltip
},
backgroundColor: 'rgba(255, 255, 255, 0.8)',
textStyle: {
color: '#666',
fontSize: 12
},
xAxis: {
type: 'time',
axisLine: {
lineStyle: {
color: '#ddd'
}
},
axisLabel: {
color: '#666',
formatter: '{MM}-{dd}',
rotate: 30
},
splitLine: {
show: true,
lineStyle: {
color: '#f5f5f5'
}
}
},
yAxis: {
type: 'value',
axisLine: {
show: true,
lineStyle: {
color: '#ddd'
}
},
splitLine: {
lineStyle: {
color: '#f5f5f5'
}
},
axisLabel: {
color: '#666',
formatter: (value) => `${value.toFixed(2)}`
}
},
series: [
...processSeriesData().map((series) => ({
...series,
lineStyle: {
width: 2,
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 6,
shadowOffsetY: 3
},
itemStyle: {
color: '#00007f', //
borderColor: '#fff',
borderWidth: 1
}
})),
{
type: 'line',
markLine: {
silent: true,
lineStyle: {
type: 'dashed',
color: '#FF4757',
width: 2
},
data: [{ yAxis: props.referenceValue }],
label: {
show: true,
position: 'end',
formatter: `参考值: ${props.referenceValue}`,
fontSize: 12
}
},
data: [] // 线
}
],
legend: {
type: 'scroll',
bottom: 5,
textStyle: {
color: '#666',
fontSize: 12
},
pageIconColor: '#FF6B6B',
pageTextStyle: {
color: '#999'
}
},
grid: {
top: 30,
right: 30,
bottom: 40,
left: 20,
containLabel: true
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
minValueSpan: 3600 * 24 * 1000 * 7 // 7
}
]
});
//
const initChart = () => {
setTimeout(async () => {
if (!chart.value) return;
const myChart = await chart.value.init(echarts);
myChart.setOption(chartOption.value);
}, 300);
};
</script>
<style scoped>
/* 容器样式 */
.chart-container {
width: 100%;
height: 30vh;
min-height: 200px;
padding: 20rpx;
background: linear-gradient(145deg, #f8f9fa 0%, #ffffff 100%);
border-radius: 16rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
position: relative;
overflow: hidden;
}
/* 图表加载占位 */
.chart-container::before {
/* content: '数据加载中...'; */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #666;
font-size: 28rpx;
z-index: 1;
}
/* 图表主体样式 */
.chart-wrapper {
width: 100%;
height: 100%;
transition: opacity 0.3s ease;
}
/* 移动端优化 */
@media screen and (max-width: 768px) {
.chart-container {
height: 30vh;
min-height: 200px;
padding: 10rpx;
border-radius: 12rpx;
}
}
</style>

View File

@ -0,0 +1,83 @@
{
"id": "cxc-szcx-lineChart",
"displayName": "cxc-szcx-lineChart",
"version": "1.0.0",
"description": "cxc-szcx-lineChart",
"keywords": [
"cxc-szcx-lineChart"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"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": "u",
"aliyun": "u",
"alipay": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,179 @@
# cxc-szcx-lineChart
# # `line-chart.vue` 组件说明书
## 一、组件概述
`line-chart.vue` 是一个基于 ECharts 实现的折线图组件,用于在 UniApp 项目中展示数据的折线图。该组件接收一系列数据和配置参数,支持动态更新数据,并展示参考线。
## 二、组件依赖
- **Vue 3**:使用 Vue 3 的组合式 API 进行开发。
- **ECharts**:用于绘制折线图。
- **`lime-echart`**UniApp 插件,提供 ECharts 的渲染支持。
## 三、组件使用方法
### 1. 引入组件
在需要使用该组件的页面中引入 `line-chart.vue` 组件。
```vue
<template>
<view>
<line-chart
:dataList="dataList"
:xField="xField"
:yField="yField"
:legendField="legendField"
:referenceValue="referenceValue"
/>
</view>
</template>
<script setup>
import LineChart from '@/components/line-chart.vue';
const dataList = [
{ date: '2023-01-01', value: 10, category: 'A' },
// 更多数据...
];
const xField = 'date';
const yField = 'value';
const legendField = 'category';
const referenceValue = 15;
</script>
```
### 2. 组件属性
| 属性名 | 类型 | 默认值 | 描述 |
| ---- | ---- | ---- | ---- |
| `dataList` | `Object` | `[]` | 包含图表数据的数组,每个元素是一个对象,包含 `xField`、`yField` 和 `legendField` 对应的数据。 |
| `xField` | `String` | `''` | 数据中用于表示 x 轴的字段名。 |
| `yField` | `String` | `''` | 数据中用于表示 y 轴的字段名。 |
| `legendField` | `String` | `''` | 数据中用于表示图例的字段名。 |
| `referenceValue` | `Number` | `10` | 图表中参考线的数值。 |
## 四、组件内部实现
### 1. 模板部分
```vue
<template>
<view class="chart-container">
<l-echart ref="chart" @init="initChart"></l-echart>
</view>
</template>
```
- 使用 `l-echart` 组件渲染图表,通过 `ref` 绑定到 `chart`,并在初始化完成时调用 `initChart` 方法。
### 2. 脚本部分
#### 2.1 引入必要的模块
```javascript
import { ref, watch } from 'vue';
```
引入 Vue 3 的 `ref``watch` 函数。
#### 2.2 定义组件属性
```javascript
const props = defineProps({
dataList: {
type: Object,
default: () => []
},
// 其他属性...
});
```
定义组件接收的属性。
#### 2.3 初始化图表
```javascript
const chart = ref(null);
let echarts = null;
const initChart = async (canvas) => {
await initEcharts(canvas);
updateChart();
};
const initEcharts = async (canvas) => {
echarts = await import('echarts');
const { init } = await import('@/uni_modules/lime-echart/static/echarts.min');
echarts = init(canvas, echarts);
};
```
- `chart` 用于引用 `l-echart` 组件。
- `initChart` 方法在图表初始化时调用,先初始化 ECharts 实例,再更新图表。
- `initEcharts` 方法异步加载 ECharts 并初始化实例。
#### 2.4 处理数据
```javascript
const processData = () => {
const legendData = [...new Set(props.dataList.map((item) => item[props.legendField]))];
return legendData.map((legendItem) => {
const seriesData = props.dataList
.filter((item) => item[props.legendField] === legendItem)
.sort((a, b) => new Date(a[props.xField]) - new Date(b[props.xField]))
.map((item) => ({
name: item[props.xField],
value: [item[props.xField], item[props.yField]]
}));
return {
name: legendItem,
type: 'line',
showSymbol: true,
data: seriesData
};
});
};
```
处理传入的数据,根据 `legendField` 分组,对每组数据按 `xField` 排序,并转换为 ECharts 所需的格式。
#### 2.5 获取图表配置
```javascript
const getOption = () => ({
tooltip: {
trigger: 'axis',
formatter: (params) => {
return `${params[0].axisValue}<br/>` + params.map((item) => `${item.marker} ${item.seriesName}: ${item.value[1]}`).join('<br/>');
}
},
// 其他配置...
});
```
返回 ECharts 的配置对象,包括 tooltip、x 轴、y 轴、系列数据、图例和网格等配置。
#### 2.6 更新图表
```javascript
const updateChart = () => {
if (!chart.value) return;
const option = getOption();
chart.value.setOption(option);
};
```
如果 `chart` 实例存在,获取最新的图表配置并更新图表。
#### 2.7 监听数据变化
```javascript
watch(
() => props.dataList,
() => {
updateChart();
},
{ deep: true }
);
```
`props.dataList` 发生变化时,调用 `updateChart` 方法更新图表。
### 3. 样式部分
```css
.chart-container {
width: 100%;
height: 30vh;
}
```
设置图表容器的宽度为 100%,高度为 30vh。
## 五、注意事项
- 确保项目中已经正确安装并配置了 `lime-echart` 插件。
- 传入的 `dataList` 数据格式要符合要求,包含 `xField`、`yField` 和 `legendField` 对应的数据。
- 由于使用了异步加载 ECharts可能会有一定的延迟需要确保在合适的时机初始化图表。

View File

@ -0,0 +1,33 @@
## 0.1.32023-08-19
- fix: 修复使用remove导致样式错乱
## 0.1.22023-08-09
- fix: 修复nvue没有获取节点的问题
- fix: 修复因延时导致卡在中途
- fix: 修复change事件有时失效的问题
## 0.1.12023-07-03
- chore: 更新文档
## 0.1.02023-07-03
- fix: 外面的事件冒泡导致点击调动内部移动方法错乱
## 0.0.92023-05-30
- fix: 修复因手机事件为`onLongpress`导致,在手机上无法长按
- fix: 无法因css导致滚动
## 0.0.82023-04-23
- feat: 更新文档
## 0.0.72023-04-23
- feat: 由于删除是一个危险的动作,故把方法暴露出来,而不在内部处理。如果之前有使用删除的,需要注意
- feat: 原来的`add`变更为`push`,增加`unshift`
## 0.0.62023-04-12
- fix: 修复`handle`不生效问题
- feat: 增加 `to`方法
## 0.0.52023-04-11
- chore: `grid` 插槽增加 `nindex`、`oindex`
## 0.0.42023-04-04
- chore: 去掉 script-setup 语法糖
- chore: 文档增加 vue2 使用方法
## 0.0.32023-03-30
- feat: 重要说明 更新 list 只会再次初始化
- feat: 更新文档
## 0.0.22023-03-29
- 修改文档
## 0.0.12023-03-29
- 初次提交

View File

@ -0,0 +1,93 @@
$drag-handle-size: var(--l-drag-handle-size, 50rpx);
$drag-delete-size: var(--l-drag-delete-size, 32rpx);
.l-drag {
// min-height: 100rpx;
overflow: hidden;
margin: 24rpx 30rpx 0 30rpx;
// padding: 30rpx 0;
/* #ifdef APP-NVUE */
// flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
// width: 100%;
/* #endif */
}
.l-drag__inner {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
min-height: 100rpx;
}
.l-drag__view {
// touch-action: none;
// user-select: none;
// -webkit-user-select: auto;
z-index: 2;
transition: opacity 300ms ease;
.mask {
position: absolute;
inset: 0;
background-color: transparent;
z-index: 9;
}
/* #ifndef APP-NVUE */
> view {
&:last-child {
width: 100%;
height: 100%;
}
}
box-sizing: border-box;
/* #endif */
}
.l-drag-enter {
opacity: 0;
}
.l-drag__ghost {
/* #ifndef APP-NVUE */
> view {
&:last-child {
width: 100%;
height: 100%;
}
}
box-sizing: border-box;
/* #endif */
}
.l-is-active {
z-index: 3;
}
.l-is-hidden {
opacity: 0;
}
.l-drag__delete {
position: absolute;
z-index: 10;
width: $drag-delete-size;
height: $drag-delete-size;
}
.l-drag__handle {
position: absolute;
z-index: 10;
width: $drag-handle-size;
height: $drag-handle-size;
}
/* #ifndef APP-NVUE */
.l-drag__delete::before,.l-drag__handle::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 10;
}
/* #endif */

View File

@ -0,0 +1,532 @@
<template>
<view class="l-drag l-class" :style="[areaStyles]" ref="dragRef" @touchstart="setDisabled">
<movable-area class="l-drag__inner" v-if="isReset" :style="[innerStyles]">
<slot></slot>
<movable-view class="l-drag__ghost" v-if="isDrag && props.ghost" :animation="true" :style="[viewStyles]" direction="all" :x="ghostEl.x" :y="ghostEl.y" key="l-drag-clone">
<slot name="ghost"></slot>
</movable-view>
<movable-view v-if="props.before" class="l-drag__before" disabled :animation="false" :style="[viewStyles]" :x="beforeEl.x" :y="beforeEl.y">
<slot name="before"></slot>
</movable-view>
<movable-view
v-for="(item, oindex) in cloneList" :key="item.id"
direction="all"
:data-oindex="oindex"
:style="[viewStyles]"
class="l-drag__view"
:class="[{'l-is-active': oindex == active, 'l-is-hidden': !item.show}, item.class]"
:x="item.x"
:y="item.y"
:friction="friction"
:damping="damping"
:animation="animation"
:disabled="isDisabled || props.disabled"
@touchstart="touchStart"
@change="touchMove"
@touchend="touchEnd"
@touchcancel="touchEnd"
@longpress="setDisabled"
>
<!-- <view v-if="props.remove" class="l-drag__remove" :style="removeStyle" data-remove="true">
<slot name="remove" :oindex="oindex" data-remove="true" />
</view> -->
<!-- <view v-if="props.handle" class="l-drag__handle" :style="handleStyle" data-handle="true">
<slot name="handle" :oindex="oindex" :active="!isDisabled && !isDisabled && oindex == active" />
</view> -->
<slot name="grid" :oindex="oindex" :index="item.index" :oldindex="item.oldindex" :content="item.content" :active="!isDisabled && !isDisabled && oindex == active" />
<view class="mask" v-if="!(isDisabled || props.disabled) && props.longpress"></view>
</movable-view>
<movable-view v-if="props.after" class="l-drag__after" disabled :animation="true" direction="all" :style="[viewStyles]" :x="afterEl.x" :y="afterEl.y">
<slot name="after"></slot>
</movable-view>
</movable-area>
</view>
</template>
<script lang="ts">
// @ts-nocheck
import { computed, onMounted, ref, getCurrentInstance, watch, nextTick, reactive , triggerRef, onUnmounted, defineComponent} from "./vue";
import DragProps from './props';
import type {GridRect, Grid, Position} from './type'
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
export default defineComponent({
name: 'l-drag',
externalClasses: ['l-class'],
options: {
addGlobalClass: true,
virtualHost: true,
},
props: DragProps,
emits: ['change'],
setup(props, {emit, expose}) {
const res = wx.getSystemInfoSync();
const statusHeight = res.statusBarHeight; //
const cusnavbarheight = (statusHeight + 44) + "px";
// #ifdef APP-NVUE
const dragRef = ref(null)
// #endif
const app = getCurrentInstance()
const isDrag = ref(false)
const isInit = ref(false)
const isReset = ref(true)
const colmunId = ref(-1)
/** 选中项原始下标 */
const active = ref(-1)
const maxIndex = ref(-1)
const animation = ref(true)
const isDisabled = ref(props.handle || props.longpress ? true: false)
const dragEl = reactive({
content: null,
/** 当前视图下标*/
index: 0,
/** 旧视图下标 */
oldindex: -1,
/** 上次原始下标 */
lastindex: -1
})
const ghostEl = reactive({
content: null,
x: 0,
y: 0
})
const beforeEl = reactive({
x: 0,
y: 0
})
const afterEl = reactive({
x: 0,
y: 0
})
let gridRects = [] //ref<GridRect[]>([])
const areaWidth = ref(0)
const cloneList = ref<Grid[]>([])
//
const leaveRow = ref(0)
const extra = computed(() => (props.before ? 1 :0) + (props.after ? 1 : 0))
const rows = computed(() => Math.ceil( ((isInit.value ? cloneList.value.length : props.list.length) + extra.value) / props.column ))
const gridHeight = computed(() => props.aspectRatio ? girdWidth.value / props.aspectRatio : (/rpx$/.test(`${props.gridHeight}`) ? uni.upx2px(parseInt(`${props.gridHeight}`)) : parseInt(`${props.gridHeight}`)))
const girdWidth = computed(() => areaWidth.value / props.column)
const viewStyles = computed(() => ({width: girdWidth.value + 'px',height: gridHeight.value + 'px'}))
const areaStyles = computed(() => ({height: (rows.value + leaveRow.value ) * gridHeight.value + 'px'}))
const innerStyles = computed(() => ({
// #ifdef APP-NVUE
width: areaWidth.value + 'px',
// #endif
height: (rows.value + props.extraRow + leaveRow.value) * gridHeight.value + 'px'}))
const sleep = (cb: Function, time = 1000/60) => setTimeout(cb, time)
const createGrid = (content: any, position?:Position|null): Grid => {
colmunId.value++
maxIndex.value++
const index = maxIndex.value
const colmun = gridRects[index]
let x = 0
let y = 0
if(colmun) {
if(props.after) {
let nxet = gridRects[index + 1]
if(!nxet) {
nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
gridRects.push(nxet)
}
setReset(() => setAfter(nxet))
} else {
setReset()
}
x = colmun.x
y = colmun.y
} else {
const nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
gridRects.push(nxet)
setReset()
x = nxet.x
y = nxet.y
}
if(position) {
x = position.x
y = position.y
}
return {id: `l-drag-item-${colmunId.value}`, index, oldindex: index, content, x, y, class: '', show: true}
}
const setReset = (cb?: any) => {
// const newRow = (cloneList.value.length + extra.value) % (props.column)
if(isInit.value) {
cb&&sleep(cb)
}
}
const setAfter = ({x, y} = {x: 0, y: 0}) => {
if(props.after) {
afterEl.x = x
afterEl.y = y
}
}
const setDisabled = (e: any, flag?: boolean= false) => {
// e?.preventDefault()
const type = `${e.type}`.toLowerCase()
const {handle = props.touchHandle} = e.target.dataset
if(props.handle && !handle) {
isDisabled.value = true
} else if(props.handle && handle && !props.longpress) {
isDisabled.value = flag
} else if(props.handle && handle && props.longpress && type.includes('longpress')) {
isDisabled.value = false
} else if(props.longpress && type.includes('longpress') && !props.handle) {
isDisabled.value = false
}
if(type.includes('touchend') && props.longpress) {
isDisabled.value = true
}
}
const createGridRect = (i: number, last?: GridRect): GridRect => {
let { row } = last || gridRects[gridRects.length - 1] || { row: 0 }
const col = i % (props.column)
const grid = (row: number, x: number, y: number):GridRect => {
return {row, x, y, x1: x + girdWidth.value, y1: y + gridHeight.value}
}
if(col == 0 && i != 0) {row++}
return grid(row, col * girdWidth.value, row * gridHeight.value)
}
const createGridRects = () => {
let rects: GridRect[] = []
const length = rows.value * props.column + extra.value
gridRects = []
for (var i = 0; i < length; i++) {
const item = createGridRect(i, rects[rects.length - 1])
rects.push(item)
}
if(props.before) {
const {x, y} = rects.shift()
beforeEl.x = x
beforeEl.y = y
}
setAfter(rects[props.list.length])
gridRects = rects as GridRect[]
}
const updateList = (v: any[]) => {
cloneList.value = v.map((content) => createGrid(content))
}
const touchStart = (e: any) => {
if(e.target.dataset.remove) return
//
const {oindex} = e.currentTarget?.dataset || e.target?.dataset || {}
if(typeof oindex !== 'number') return
const target = cloneList.value[oindex]
isDrag.value = true
//
active.value = oindex
//
dragEl.index = dragEl.oldindex = target.index
ghostEl.x = target.x||0
ghostEl.y = target.y||0
dragEl.content = ghostEl.content = target.content
}
const touchEnd = (e: any) => {
setTimeout(() => {
if(e.target.dataset.remove || active.value==-1) return
setDisabled(e, true)
isDrag.value = false
const isEmit = dragEl.index !== dragEl.oldindex && dragEl.oldindex > -1 // active.value !== dragEl.index
dragEl.lastindex = active.value
dragEl.oldindex = active.value = -1
const last = cloneList.value[dragEl.lastindex]
const position = gridRects[dragEl.index]
nextTick(() => {
last.x = position.x + 0.001
last.y = position.y + 0.001
sleep(() => {
last.x = position.x
last.y = position.y
isEmit && emitting()
})
})
},80)
}
const emitting = () => {
const clone = [...cloneList.value].sort((a, b) => a.index - b.index)//.map(item => ref(item.content))
emit('change', clone)
}
const touchMove = (e: any) => {
if(!isDrag.value) return
// #ifndef APP-NVUE
let {oindex} = e.currentTarget.dataset
// #endif
// #ifdef APP-NVUE
oindex = e.currentTarget.dataset['-oindex']
// #endif
if(oindex != active.value) return
const {x, y} = e.detail
const centerX = x + girdWidth.value / 2
const centerY = y + gridHeight.value / 2
for (let i = 0; i < cloneList.value.length; i++) {
const item = gridRects[i]
if(centerX > item.x && centerX < item.x1 && centerY > item.y && centerY < item.y1) {
ghostEl.x = item.x
ghostEl.y = item.y
if(dragEl.index != i) {
_move(active.value, i)
}
break;
}
}
}
const getDragEl = (oindex: number) => {
if(isDrag.value) {return dragEl}
return cloneList.value[oindex]
}
/**
* 把原始数据中排序为index的项 移动到 toIndex
* @param oindex 原始数据的下标
* @param toIndex 视图中的下标
* @param position 指定坐标
*/
const _move = (oindex: number, toIndex: number, position?: Position|null, emit: boolean = true) => {
const length = cloneList.value.length - 1
if(toIndex > length || toIndex < 0) return
// oIdnex
const dragEl = getDragEl(oindex)
let speed = 0
let start = dragEl.index
// indexindex
if(start < toIndex) {speed = 1}
if(start > toIndex) {speed = -1}
if(!speed) return
//
let distance = start - toIndex
//
while(distance) {
distance += speed
//
const target = isDrag.value ? (dragEl.index += speed) : (start += speed)
let targetOindex = cloneList.value.findIndex(item => item.index == target && item.content != dragEl.content)
if (targetOindex == oindex) return
if (targetOindex < 0) {targetOindex = cloneList.value.length - 1}
let targetEl = cloneList.value[targetOindex]
if(!targetEl) return;
// index
const lastIndex = target - speed
const activeEl = cloneList.value[oindex]
const rect = gridRects[lastIndex]
targetEl.x = rect.x
targetEl.y = rect.y
targetEl.oldindex = targetEl.index
targetEl.index = lastIndex
activeEl.oldindex = activeEl.index //oIndex
activeEl.index = toIndex
//
if(!distance && !isDrag.value) {
const rect = gridRects[toIndex]
const {x, y} = position||rect
activeEl.x = dragEl.x = x
activeEl.y = dragEl.y = y
// triggerRef(cloneList)
if(emit) {
emitting()
}
}
}
}
/**
* 为区分是主动调用还是内部方法
*/
const move = (oindex: number, toIndex: number) => {
active.value = -1
isDrag.value = false
_move(oindex, toIndex)
}
//
const REMOVE_TIME = 400
let removeTimer = null
const remove = (oindex: number) => {
active.value = -1
isDrag.value = false
clearTimeout(removeTimer)
const item = cloneList.value[oindex]
if(props.disabled || !item) return
item.show = false
const after = cloneList.value.length - 1
_move(oindex, after, item, false)
setAfter(gridRects[after])
maxIndex.value--
const _remove = (_index = oindex) => {
//
// animation.value = false
const row = Math.ceil((cloneList.value.length - 1 + extra.value) / props.column)
if( row < rows.value) {
leaveRow.value = (rows.value - row)
}
cloneList.value.splice(_index, 1)[0]
emitting()
removeTimer = setTimeout(() => {
leaveRow.value = 0
}, REMOVE_TIME)
}
_remove()
}
const push = (...args: any) => {
if(props.disabled) return
if(Array.isArray(args)) {
Promise.all(args.map(async item => await add(item, true))).then(emitting)
}
}
const add = (content: any, after: boolean) => {
return new Promise((resolve) => {
const item = createGrid(content, after ? null : {x: -100, y:0})
item.class = 'l-drag-enter'
cloneList.value.push(item)
const length = cloneList.value.length - 1
nextTick(() => {
sleep(() => {
item.class = 'l-drag-leave'
_move(length, (after ? length : 0), null, false)
triggerRef(cloneList)
resolve(true)
})
})
})
}
const unshift = (...args: any) => {
if(props.disabled) return
if(Array.isArray(args)) {
Promise.all(args.map(async (item) => await add(item))).then(emitting)
}
}
//
const shift = () => {
if(!cloneList.value.length) return
remove(cloneList.value.findIndex(item => item.index == 0) || 0)
}
const pop = () => {
const length = cloneList.value.length-1
if(length < 0 ) return
remove(cloneList.value.findIndex(item => item.index == length) || length)
}
// const splice = (start, count, ...context) => {
// //
// }
const clear = () => {
isInit.value = isDrag.value = false
maxIndex.value = colmunId.value = active.value = -1
cloneList.value = []
gridRects = []
}
const init = () => {
clear()
createGridRects()
nextTick(() => {
updateList(props.list)
isInit.value = true
})
}
let count = 0
const getRect = () => {
count++
// #ifndef APP-NVUE
uni.createSelectorQuery().in(app.proxy).select('.l-drag').boundingClientRect((res: UniNamespace.NodeInfo) => {
if(res) {
areaWidth.value = res.width || 0
//
init()
}
}).exec()
// #endif
// #ifdef APP-NVUE
sleep(() => {
nextTick(() => {
dom.getComponentRect(dragRef.value, (res) => {
if(!res.size.width && count < 5) {
getRect()
} else {
areaWidth.value = res.size.width || 0
init()
}
})
})
})
// #endif
}
onMounted(getRect)
onUnmounted(clear)
watch(() => props.list, init)
// #ifdef VUE3
expose({
remove,
// add,
move,
push,
unshift,
shift,
pop
})
// #endif
return {
// #ifdef APP-NVUE
dragRef,
// #endif
cloneList,
areaStyles,
innerStyles,
viewStyles,
setDisabled,
isDisabled,
isReset,
isDrag,
active,
animation,
afterEl,
ghostEl,
beforeEl,
touchStart,
touchMove,
touchEnd,
remove,
// add,
move,
push,
unshift,
// shift,
// pop,
props
// isDelete: props.delete,
// ...toRefs(props)
}
}
})
</script>
<style lang="scss">
.l-drag{
margin-top: v-bind(cusnavbarheight);
}
@import './index';
</style>

View File

@ -0,0 +1,47 @@
// @ts-nocheck
export default {
list: {
type: Array,
default: []
},
column: {
type: Number,
default: 2
},
/**宽高比 填写这项, gridHeight 失效*/
aspectRatio: Number,
gridHeight: {
type: [Number, String],
default: '120rpx'
},
// removeStyle: String,
// handleStyle: String,
damping: {
type: Number,
default: 40
},
friction: {
type: Number,
default: 2
},
/**
* movable-area
*/
extraRow: {
type: Number,
default: 0
},
/**
* movable-area vif , BUG uni官方好像修复了
*/
// reset: Boolean,
// sort: Boolean,
// remove: Boolean,
ghost: Boolean,
handle: Boolean,
touchHandle: Boolean,
before: Boolean,
after: Boolean,
disabled: Boolean,
longpress: Boolean,
}

View File

@ -0,0 +1,21 @@
export interface Position {
x: number
y: number
}
export interface GridRect extends Position{
row : number
// x : number
// y : number
x1 : number
y1 : number
}
export interface Grid extends Position{
id : string
index : number
oldindex : number
content : any
// x : number
// y : number
class : string
show: boolean
}

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// export * from '@/uni_modules/lime-vue'
// #ifdef VUE3
export * from 'vue';
// #endif
// #ifndef VUE3
export * from '@vue/composition-api';
// #endif

View File

@ -0,0 +1,268 @@
<template>
<demo-block type="ultra" title="拖拽">
<demo-block title="基础">
<l-drag :list="list">
<template #grid="{active, index}">
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{index}}</text>
</view>
</template>
</l-drag>
</demo-block>
<demo-block title="多列 长按">
<!-- 列后 删除 幽灵 长按 -->
<l-drag ref="dragRef2" :list="list" @change="change2" :aspectRatio="1" :column="4" after ghost longpress>
<template #grid="{oindex, content, active}">
<view class="grid">
<view class="remove" @click="onRemove2(oindex)"></view>
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{content}}</text>
</view>
</view>
</template>
<template #ghost>
<view class="grid">
<!-- 幽灵样式 -->
<view class="inner ghost"></view>
</view>
</template>
<!-- 示例未设置 before -->
<template #before>
<view class="grid">
<view class="inner extra" @click="onAdd2">
增加
</view>
</view>
</template>
<template #after>
<view class="grid">
<view class="inner extra" @click="onAdd2">
增加
</view>
</view>
</template>
</l-drag>
<button @click="refresh2">更新列表</button>
<button @click="onUnshift2">向前增加</button>
<button @click="onPush2">向后增加</button>
<button @click="onShift2">shift</button>
<button @click="onPop2">pop</button>
</demo-block>
<demo-block title="单列 按手柄">
<!-- 幽灵 手柄 -->
<view class="inputs">
<text>把原始下标为</text>
<input type="text" v-model="move3.index">
<text>的项移动到</text>
<input type="text" v-model="move3.nindex">
</view>
<view style="height: 600rpx; overflow: auto;">
<l-drag ref="dragRef3" :list="list3" @change="change3" grid-height="200rpx" :column="1" :touchHandle="touchHandle3" ghost handle>
<template #grid="{active, content, index, oindex}">
<view class="grid">
<view class="mover" @touchstart="touchHandle3 = true" @touchend="touchHandle3 = false" style="left: 100rpx; top: 50%; transform: translateY(-50%); position: absolute; z-index: 1;"></view>
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{content}}</text>
<view @click.stop.prevent="to(oindex, index - 1)">上移</view>
<view @click.stop.prevent="to(oindex, index + 1)">下移</view>
</view>
</view>
</template>
<template #ghost>
<view class="grid">
<!-- 幽灵样式 -->
<view class="inner ghost"></view>
</view>
</template>
</l-drag>
</view>
</demo-block>
</demo-block>
</template>
<script>
import {ref, watch, defineComponent, reactive} from '../l-drag/vue'
export default defineComponent({
setup() {
// 12list
const list = ref(new Array(7).fill(0).map((v,i) => i));
// 2
const dragRef2 = ref(null)
const newList2 = ref([])
const change2 = v => {
console.log('示例2数据发生变化', v)
newList2.value = [...v]
}
const onRemove2 = (index) => {
if(dragRef2.value && index >= 0) {
dragRef2.value.remove(index)
}
}
const onAdd2 = () => {
dragRef2.value.push(Math.round(Math.random() * 1000))
}
const onShift2 = () => {
dragRef2.value.shift()
}
const onPop2 = () => {
dragRef2.value.pop()
}
const onUnshift2 = () => {
dragRef2.value.unshift(Math.round(Math.random() * 1000), Math.round(Math.random() * 1000))
}
const onPush2 = () => {
dragRef2.value.push(Math.round(Math.random() * 1000), Math.round(Math.random() * 1000))
}
const refresh2 = () => {
list.value = new Array(10).fill(0).map((v,i) => i);
}
// 3
const list3 = new Array(5).fill(0).map((v,i) => i);
const dragRef3 = ref(null)
const newList3 = ref([])
const touchHandle3 = ref(false)
const change3 = v => {
newList3.value = v
}
const move3 = reactive({
index: 0,
nindex: 0
})
watch(() => move3.nindex, (v, o) => {
dragRef3.value.move(move3.index*1, move3.nindex*1)
})
const to = (oindex, index) => {
dragRef3.value.move(oindex, index)
}
return {
list,
// 2
dragRef2,
change2,
onRemove2,
onAdd2,
refresh2,
onUnshift2,
onPush2,
onShift2,
onPop2,
// 3
list3,
dragRef3,
change3,
move3,
touchHandle3,
to
}
}
})
</script>
<style lang="scss">
.grid {
height: 100%;
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
padding: 16rpx;
box-sizing: border-box;
position: relative;
}
.remove {
width: 32rpx;
height: 32rpx;
background-color: red;
border-radius: 50%;
font-size: 16rpx;
color: white;
display: flex;
text-align: center;
justify-content: center;
z-index: 10;
position: absolute;
right: 0;
top: 0;
}
.inner {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
border: 1rpx solid #eee;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
font-size: 50rpx;
font-weight: bold;
color: blue;
transition: all 300ms ease;
position: relative;
}
.extra {
color: #ddd
}
.mover {
position: relative;
width: 50rpx;
margin-top: 10rpx;
height: 30rpx;
border: 2px solid #ddd;
border-left: none;
border-right: none;
/* background-color: #ddd; */
}
.mover::before {
content: '';
position: absolute;
width: 50rpx;
top: 15rpx;
border-top: 2px solid #ddd;
}
.active {
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.1);
background-color: blue;
color: white;
border: 1rpx solid blue;
transform: scale(1.1);
}
/* #ifdef APP-NVUE */
.text {
font-size: 50rpx;
font-weight: bold;
}
.text-active {
font-size: 50rpx;
font-weight: bold;
color: white;
}
/* #endif */
.ghost {
background-color: rgba(0, 0, 255, 0.1);
}
.inputs {
display: flex;
align-items: center;
input {
background-color: #fff;
width: 80rpx;
border: 1rpx solid #ddd;
padding: 5rpx 10rpx;
margin: 0 8rpx;
}
}
</style>

View File

@ -0,0 +1,87 @@
{
"id": "lime-drag",
"displayName": "拖拽排序-拖动排序-LimeUI",
"version": "0.1.3",
"description": "uniapp vue3 拖拽排序插件,用于图片或列表的拖动排序,可设置列数、增加删除等功能, vue2只要配置@vue/composition-api",
"keywords": [
"拖拽",
"拖拽排序",
"排序",
"拖动",
"拖动排序"
],
"repository": "",
"engines": {
"HBuilderX": "^3.7.12"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"lime-shared"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "n",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,170 @@
# lime-drag 拖拽排序
- 当前为初版 可能会有BUG
- 基于uniapp vue3
- Q群 1169785031
### 安装
- 在市场导入插件即可在任意页面使用,无须再`import`
### 使用
- 提供简单的使用示例更多请查看下方的demo
```html
<l-drag :list="list" @change="change">
<!-- // 每一项的插槽 grid 的 content 您传入的数据 -->
<template #grid="{active, content}">
<!-- // grid.active 是否为当前拖拽项目 根据自己需要写样式 -->
<view class="inner" :class="{active: active}">
<text class="text" :class="{'text-active': active}">{{grid.content}}</text>
</view>
</template>
</l-drag>
```
```js
const list = new Array(7).fill(0).map((v,i) => i);
// 拖拽后新的数据
const newList = ref([])
const change = v => newList.value = v
```
#### 增删
- 不要给list赋值这样只会重新初始化
- 增加数据 调用暴露的`push`
- 删除某条数据调用暴露的`remove`方法,需要传入`oindex`
```html
<l-drag :list="list" @change="change" ref="dragRef" after remove>
<!-- 每一项插槽 grid 的 content 是您传入的数据 -->
<template #grid="{active, index, oldindex, oindex}">
<!-- active 是否为当前拖拽项目 根据自己需要写样式 -->
<!-- index 排序后列表下标 -->
<!-- oldindex 排序后列表旧下标 -->
<!-- oindex 列表原始下标,输入的数据排位不会因为排序而改变 -->
<view class="remove" @click="onRemove(oindex)"></view>
<view class="inner" :class="{active}">
<text class="text" :class="{'text-active': active}">{{content}}</text>
</view>
</template>
<template #after>
<view class="grid">
<view class="inner extra" @click="onAdd">
增加
</view>
</view>
</template>
</l-drag>
```
```js
const dragRef = ref(null)
const list = new Array(7).fill(0).map((v,i) => i);
const onAdd = () => {
dragRef.value.push(Math.round(Math.random() * 1000))
}
const onRemove = (oindex) => {
if(dragRef.value && oindex >= 0) {
// 记得oindex为数组的原始index
dragRef.value.remove(oindex)
}
}
```
#### 插槽
```html
<l-drag :list="list">
<!-- 每一项的插槽 -->
<template #grid="{active, index, oldindex, oindex, content}"></template>
<!-- 当前拖拽项幽灵插槽 设置`ghost`后使用 主要为实现拖拽时 有个影子跟着 -->
<template #ghots></template>
<!-- 前后方插槽为固定在列表前方和后方,不能拖动 -->
<!-- 列表前方的插槽 设置`before`后使用 -->
<template #before></template>
<!-- 列表后方的插槽 设置`after`后使用 -->
<template #after></template>
</l-drag>
```
### 查看示例
- 导入后直接使用这个标签查看演示效果
```html
<!-- // 代码位于 uni_modules/lime-drag/compoents/lime-drag -->
<lime-drag />
```
### 插件标签
- 默认 l-drag 为 component
- 默认 lime-drag 为 demo
### 关于vue2的使用方式
- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置
- 关键代码是: 在main.js中 在vue2部分加上这一段即可,官方是把它单独成了一个文件.
```js
// vue2
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
- 另外插件也用到了TSvue2可能会遇过官方的TS版本过低的问题,找到HX目录下的`compile-typescript`目录
```cmd
// \HBuilderX\plugins\compile-typescript
yarn add typescript -D
- or -
npm install typescript -D
```
- 小程序需要在`manifest.json`启用`slotMultipleInstance`
```json
"mp-weixin" : {
"slotMultipleInstance" : true
}
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --------------------------| ------------------------------------------------------------ | ---------------- | ------------ |
| list | 列表数组,不可变化,变化后会重新初始化 | <em>array</em> | `[]` |
| column | 列数 | <em>number</em> | `2` |
| gridHeight | 行高,宫格高度 | <em>string</em> | `120rpx` |
| damping | 阻尼系数用于控制x或y改变时的动画和过界回弹的动画值越大移动越快 | <em>string</em> | `-` |
| friction | 摩擦系数用于控制惯性滑动的动画值越大摩擦力越大滑动越快停止必须大于0否则会被设置成默认值 | <em>number</em> | `2` |
| extraRow | 额外行数 | <em>number</em> | `0` |
| ghost | 开启幽灵插槽 | <em>boolean</em> | `false` |
| before | 开启列前插槽 | <em>boolean</em> | `false` |
| after | 开启列后插槽 | <em>boolean</em> | `false` |
| disabled | 是否禁用 | <em>boolean</em> | `false` |
| longpress | 是否长按 | <em>boolean</em> | `false` |
### Events
| 参数 | 说明 | 参数 |
| --------------------------| ------------------------------------------------------------ | ---------------- |
| change | 返回新数据 | list |
### Expose
| 参数 | 说明 | 参数 |
| --------------------------| ------------------------------------------------------------ | ---------------- |
| remove | 删除, 传入`oindex`,即数据列表原始的index | |
| push | 向后增加,可以是数组或单数据 | |
| unshift | 向前增加,可以是数组或单数据 | |
| move | 移动, 传入(`oindex`, `toindex`),将数据列表原始的index项移到视图中的目标位置 | |
### TODO
将来实现的功能
- splice
## 打赏
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

View File

@ -0,0 +1,205 @@
## 0.9.82024-12-20
- fix: 修复 APP 无法放大问题
## 0.9.72024-12-02
- feat: uniapp 增加`landscape`,当`landscape`为`true`时旋转90deg达到横屏效果。
- feat: 支持uniapp x 微信小程序
## 0.9.62024-07-23
- fix: 修复 uni is not defined
## 0.9.52024-07-19
- chore: 鸿蒙`measureText`为异步,异步字体不正常,使用模拟方式。
## 0.9.42024-07-18
- chore: 更新文档
## 0.9.32024-07-16
- feat: 鸿蒙 canvas 事件缺失,待官方修复,如何在鸿蒙使用请看文档`常见问题 vue3`
## 0.9.22024-07-12
- chore: 删除多余文件
## 0.9.12024-07-12
- fix: 修复 安卓5不显示图表问题
## 0.9.02024-06-13
- chore: 合并nvue和uvue
## 0.8.92024-05-19
- chore: 更新文档
## 0.8.82024-05-13
- chore: 更新文档和uvue示例
## 0.8.72024-04-26
- fix: uniapp x需要HBX 4.13以上
## 0.8.62024-04-10
- feat: 支持 uniapp x ios
## 0.8.52024-04-03
- fix: 修复 nvue `reset`传值不生效问题
- feat: 支持 uniapp x web
## 0.8.42024-01-27
- chore: 更新文档
## 0.8.32024-01-21
- chore: 更新文档
## 0.8.22024-01-21
- feat: 支持 `uvue`
## 0.8.12023-08-24
- fix: app 的`touch`事件为`object` 导致无法显示 `tooltip`
## 0.8.02023-08-22
- fix: 离屏 报错问题
- fix: 微信小程序PC无法使用事件
- chore: 更新文档
## 0.7.92023-07-29
- chore: 更新文档
## 0.7.82023-07-29
- fix: 离屏 报错问题
## 0.7.72023-07-27
- chore: 更新文档
- chore: lime-echart 里的示例使用自定tooltips
- feat: 对支持离屏的使用离屏创建(微信、字节、支付宝)
## 0.7.62023-06-30
- fix: vue3 报`width`的错
## 0.7.52023-05-25
- chore: 更新文档 和 demo, 使用`lime-echart`这个标签即可查看示例
## 0.7.42023-05-22
- chore: 增加关于钉钉小程序上传时提示安全问题的说明及修改建议
## 0.7.32023-05-16
- chore: 更新 vue3 非微信小程序平台可能缺少`wx`的说明
## 0.7.22023-05-16
- chore: 更新 vue3 非微信小程序平台的可以缺少`wx`的说明
## 0.7.12023-04-26
- chore: 更新demo使用`lime-echart`这个标签即可查看示例
- chore微信小程序的`tooltip`文字有阴影,怀疑是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
## 0.7.02023-04-24
- fix: 修复`setAttribute is not a function`
## 0.6.92023-04-15
- chore: 更新文档vue3请使用echarts esm的包
## 0.6.82023-03-22
- feat: mac pc无法使用canvas 2d
## 0.6.72023-03-17
- feat: 更新文档
## 0.6.62023-03-17
- feat: 微信小程序PC已经支持canvas 2d故去掉判断PC
## 0.6.52022-11-03
- fix: 某些手机touches为对象导致无法交互。
## 0.6.42022-10-28
- fix: 优化点击事件的触发条件
## 0.6.32022-10-26
- fix: 修复 dataZoom 拖动问题
## 0.6.22022-10-23
- fix: 修复 飞书小程序 尺寸问题
## 0.6.12022-10-19
- fix: 修复 PC mousewheel 事件 鼠标位置不准确的BUG不兼容火狐
- feat: showLoading 增加传参
## 0.6.02022-09-16
- feat: 增加PC的mousewheel事件
## 0.5.42022-09-16
- fix: 修复 nvue 动态数据不显示问题
## 0.5.32022-09-16
- feat: 增加enableHover属性 在PC端时当鼠标进入显示tooltip不必按下。
- chore: 更新文档
## 0.5.22022-09-16
- feat: 增加enableHover属性 在PC端时当鼠标进入显示tooltip不必按下。
## 0.5.12022-09-16
- fix: 修复nvue报错
## 0.5.02022-09-15
- feat: init(echarts, theme?:string, opts?:{}, callback: function(chart))
## 0.4.82022-09-11
- feat: 增加 @finished
## 0.4.72022-08-24
- chore: 去掉 stylus
## 0.4.62022-08-24
- feat: 增加 beforeDelay
## 0.4.52022-08-12
- chore: 更新文档
## 0.4.42022-08-12
- fix: 修复 resize 无参数时报错
## 0.4.32022-08-07
# 评论有说本插件对新手不友好,让我做不好就不要发出来。 还有的说跟官网一样,发出来做什么,给我整无语了。
# 所以在此提醒一下准备要下载的你,如果你从未使用过 echarts 请不要下载 或 谨慎下载。
# 如果你确认要下载麻烦看完文档。还有请注意插件是让echarts在uniapp能运行API 配置请自行去官网查阅!
# 如果你不会echarts 但又需要图表,市场上有个很优秀的图表插件 uchart 你可以去使用这款插件uchart的作者人很好也热情。
# 每个人都有自己的本职工作,如果你能力强可以自行兼容,如果使用了他人的插件也麻烦尊重他人的成果和劳动时间。谢谢。
# 为了心情愉悦,本人已经使用插件屏蔽差评。
- chore: 更新文档
## 0.4.22022-07-20
- feat: 增加 resize
## 0.4.12022-06-07
- fix: 修复 canvasToTempFilePath 不生效问题
## 0.4.02022-06-04
- chore 为了词云 增加一个canvas 标签
- 词云下载地址[echart-wordcloud](https://ext.dcloud.net.cn/plugin?id=8430)
## 0.3.92022-06-02
- chore: 更新文档
- tips: lines 不支持 `trailLength`
## 0.3.82022-05-31
- fix: 修复 因mouse事件冲突tooltip跳动问题
## 0.3.72022-05-26
- chore: 更新文档
- chore: 设置默认宽高300px
- fix: 修复 vue3 微信小程序 拖影BUG
- chore: 支持PC
## 0.3.52022-04-28
- chore: 更新使用方式
- 🔔 必须使用hbuilderx 3.4.8-alpha以上
## 0.3.42021-08-03
- chore: 增加 setOption的参数值
## 0.3.32021-07-22
- fix: 修复 径向渐变报错的问题
## 0.3.22021-07-09
- chore: 统一命名规范,无须主动引入组件
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
## 0.3.12021-06-21
- fix: 修复 app-nvue ios is-enable 无效的问题
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
## 0.3.02021-06-14
- fix: 修复 头条系小程序 2d 报 JSON.stringify 的问题
- 目前 头条系小程序 2d 无法在开发工具上预览划动图表页面无法滚动axisLabel 字体颜色无法更改建议使用非2d。
## 0.2.92021-06-06
- fix: 修复 头条系小程序 2d 放大的BUG
- 头条系小程序 2d 无法在开发工具上预览,也存在划动图表页面无法滚动的问题。
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.82021-05-19
- fix: 修复 微信小程序 PC 显示过大的问题
## 0.2.72021-05-19
- fix: 修复 微信小程序 PC 不显示问题
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.62021-05-14
- feat: 支持 `image`
- feat: props 增加 `ec.clear`,更新时是否先删除图表样式
- feat: props 增加 `isDisableScroll` ,触摸图表时是否禁止页面滚动
- feat: props 增加 `webviewStyles` webview 的样式, 仅nvue有效
## 0.2.52021-05-13
- docs: 插件用到了css 预编译器 [stylus](https://ext.dcloud.net.cn/plugin?name=compile-stylus) 请安装它
## 0.2.42021-05-12
- fix: 修复 百度平台 多个图表ctx 和 渐变色 bug
- ## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.32021-05-10
- feat: 增加 `canvasToTempFilePath` 方法,用于生成图片
```js
this.$refs.chart.canvasToTempFilePath({success: (res) => {
console.log('tempFilePath:', res.tempFilePath)
}})
```
## 0.2.22021-05-10
- feat: 增加 `dispose` 方法,用于销毁实例
- feat: 增加 `isClickable` 是否派发点击
- feat: 实验性的支持 `nvue` 使用要慎重考虑
- ## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.12021-05-06
- fix修复 微信小程序 json 报错
- chore: `reset` 更改为 `setChart`
- feat: 增加 `isEnable` 开启初始化 启用这个后 无须再使用`init`方法
```html
<l-echart ref="chart" is-enable />
```
```js
// 显示加载
this.$refs.chart.showLoading()
// 使用实例回调
this.$refs.chart.setChart(chart => ...code)
// 直接设置图表配置
this.$refs.chart.setOption(data)
```
## 0.2.02021-05-05
- fix修复 头条 百度 偏移的问题
- docs: 更新文档
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.1.02021-05-02
- chore: 第一次上传,基本全端兼容,使用方法与官网一致。
- 已知BUG非2d 无法使用背景色,已反馈官方
- 已知BUG头条 百度 有许些偏移
- 后期计划兼容nvue

View File

@ -0,0 +1,395 @@
import {getDeviceInfo} from './utils';
const cacheChart = {}
const fontSizeReg = /([\d\.]+)px/;
class EventEmit {
constructor() {
this.__events = {};
}
on(type, listener) {
if (!type || !listener) {
return;
}
const events = this.__events[type] || [];
events.push(listener);
this.__events[type] = events;
}
emit(type, e) {
if (type.constructor === Object) {
e = type;
type = e && e.type;
}
if (!type) {
return;
}
const events = this.__events[type];
if (!events || !events.length) {
return;
}
events.forEach((listener) => {
listener.call(this, e);
});
}
off(type, listener) {
const __events = this.__events;
const events = __events[type];
if (!events || !events.length) {
return;
}
if (!listener) {
delete __events[type];
return;
}
for (let i = 0, len = events.length; i < len; i++) {
if (events[i] === listener) {
events.splice(i, 1);
i--;
}
}
}
}
class Image {
constructor() {
this.currentSrc = null
this.naturalHeight = 0
this.naturalWidth = 0
this.width = 0
this.height = 0
this.tagName = 'IMG'
}
set src(src) {
this.currentSrc = src
uni.getImageInfo({
src,
success: (res) => {
this.naturalWidth = this.width = res.width
this.naturalHeight = this.height = res.height
this.onload()
},
fail: () => {
this.onerror()
}
})
}
get src() {
return this.currentSrc
}
}
class OffscreenCanvas {
constructor(ctx, com, canvasId) {
this.tagName = 'canvas'
this.com = com
this.canvasId = canvasId
this.ctx = ctx
}
set width(w) {
this.com.offscreenWidth = w
}
set height(h) {
this.com.offscreenHeight = h
}
get width() {
return this.com.offscreenWidth || 0
}
get height() {
return this.com.offscreenHeight || 0
}
getContext(type) {
return this.ctx
}
getImageData() {
return new Promise((resolve, reject) => {
this.com.$nextTick(() => {
uni.canvasGetImageData({
x:0,
y:0,
width: this.com.offscreenWidth,
height: this.com.offscreenHeight,
canvasId: this.canvasId,
success: (res) => {
resolve(res)
},
fail: (err) => {
reject(err)
},
}, this.com)
})
})
}
}
export class Canvas {
constructor(ctx, com, isNew, canvasNode={}) {
cacheChart[com.canvasId] = {ctx}
this.canvasId = com.canvasId;
this.chart = null;
this.isNew = isNew
this.tagName = 'canvas'
this.canvasNode = canvasNode;
this.com = com;
if (!isNew) {
this._initStyle(ctx)
}
this._initEvent();
this._ee = new EventEmit()
}
getContext(type) {
if (type === '2d') {
return this.ctx;
}
}
setAttribute(key, value) {
if(key === 'aria-label') {
this.com['ariaLabel'] = value
}
}
setChart(chart) {
this.chart = chart;
}
createOffscreenCanvas(param){
if(!this.children) {
this.com.isOffscreenCanvas = true
this.com.offscreenWidth = param.width||300
this.com.offscreenHeight = param.height||300
const com = this.com
const canvasId = this.com.offscreenCanvasId
const context = uni.createCanvasContext(canvasId, this.com)
this._initStyle(context)
this.children = new OffscreenCanvas(context, com, canvasId)
}
return this.children
}
appendChild(child) {
console.log('child', child)
}
dispatchEvent(type, e) {
if(typeof type == 'object') {
this._ee.emit(type.type, type);
} else {
this._ee.emit(type, e);
}
return true
}
attachEvent() {
}
detachEvent() {
}
addEventListener(type, listener) {
this._ee.on(type, listener)
}
removeEventListener(type, listener) {
this._ee.off(type, listener)
}
_initCanvas(zrender, ctx) {
// zrender.util.getContext = function() {
// return ctx;
// };
// zrender.util.$override('measureText', function(text, font) {
// ctx.font = font || '12px sans-serif';
// return ctx.measureText(text, font);
// });
}
_initStyle(ctx, child) {
const styles = [
'fillStyle',
'strokeStyle',
'fontSize',
'globalAlpha',
'opacity',
'textAlign',
'textBaseline',
'shadow',
'lineWidth',
'lineCap',
'lineJoin',
'lineDash',
'miterLimit',
// #ifdef H5
'font',
// #endif
];
const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
styles.forEach(style => {
Object.defineProperty(ctx, style, {
set: value => {
// #ifdef H5
if (style === 'font' && fontSizeReg.test(value)) {
const match = fontSizeReg.exec(value);
ctx.setFontSize(match[1]);
return;
}
// #endif
if (style === 'opacity') {
ctx.setGlobalAlpha(value)
return;
}
if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
// #ifdef H5 || APP-PLUS || MP-BAIDU
if(typeof value == 'object') {
if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
}
return
}
// #endif
// #ifdef MP-TOUTIAO
if(colorReg.test(value)) {
value = value.replace(colorReg, '#$1$1$2$2$3$3')
}
// #endif
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
}
}
});
});
if(!this.isNew && !child) {
ctx.uniDrawImage = ctx.drawImage
ctx.drawImage = (...a) => {
a[0] = a[0].src
ctx.uniDrawImage(...a)
}
}
if(!ctx.createRadialGradient) {
ctx.createRadialGradient = function() {
return ctx.createCircularGradient(...[...arguments].slice(-3))
};
}
// 字节不支持
if (!ctx.strokeText) {
ctx.strokeText = (...a) => {
ctx.fillText(...a)
}
}
// 钉钉不支持 , 鸿蒙是异步
if (!ctx.measureText || getDeviceInfo().osName == 'harmonyos') {
ctx._measureText = ctx.measureText
const strLen = (str) => {
let len = 0;
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
len++;
} else {
len += 2;
}
}
return len;
}
ctx.measureText = (text, font) => {
let fontSize = ctx?.state?.fontSize || 12;
if (font) {
fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
}
fontSize /= 2;
let isBold = fontSize >= 16;
const widthFactor = isBold ? 1.3 : 1;
// ctx._measureText(text, (res) => {})
return {
width: strLen(text) * fontSize * widthFactor
};
}
}
}
_initEvent(e) {
this.event = {};
const eventNames = [{
wxName: 'touchStart',
ecName: 'mousedown'
}, {
wxName: 'touchMove',
ecName: 'mousemove'
}, {
wxName: 'touchEnd',
ecName: 'mouseup'
}, {
wxName: 'touchEnd',
ecName: 'click'
}];
eventNames.forEach(name => {
this.event[name.wxName] = e => {
const touch = e.touches[0];
this.chart.getZr().handler.dispatch(name.ecName, {
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
zrY: name.wxName === 'tap' ? touch.clientY : touch.y
});
};
});
}
set width(w) {
this.canvasNode.width = w
}
set height(h) {
this.canvasNode.height = h
}
get width() {
return this.canvasNode.width || 0
}
get height() {
return this.canvasNode.height || 0
}
get ctx() {
return cacheChart[this.canvasId]['ctx'] || null
}
set chart(chart) {
cacheChart[this.canvasId]['chart'] = chart
}
get chart() {
return cacheChart[this.canvasId]['chart'] || null
}
}
export function dispatch(name, {x,y, wheelDelta}) {
this.dispatch(name, {
zrX: x,
zrY: y,
zrDelta: wheelDelta,
preventDefault: () => {},
stopPropagation: () =>{}
});
}
export function setCanvasCreator(echarts, {canvas, node}) {
// echarts.setCanvasCreator(() => canvas);
if(echarts && !echarts.registerPreprocessor) {
return console.warn('echarts 版本不对或未传入echartsvue3请使用esm格式')
}
echarts.registerPreprocessor(option => {
if (option && option.series) {
if (option.series.length > 0) {
option.series.forEach(series => {
series.progressive = 0;
});
} else if (typeof option.series === 'object') {
option.series.progressive = 0;
}
}
});
function loadImage(src, onload, onerror) {
let img = null
if(node && node.createImage) {
img = node.createImage()
img.onload = onload.bind(img);
img.onerror = onerror.bind(img);
img.src = src;
return img
} else {
img = new Image()
img.onload = onload.bind(img)
img.onerror = onerror.bind(img);
img.src = src
return img
}
}
if(echarts.setPlatformAPI) {
echarts.setPlatformAPI({
loadImage: canvas.setChart ? loadImage : null,
createCanvas(){
const key = 'createOffscreenCanvas'
return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
}
})
}
}

View File

@ -0,0 +1,310 @@
<template>
<!-- #ifdef APP -->
<web-view class="lime-echart" ref="chartRef" @load="loaded" :style="[customStyle]"
:webview-styles="[webviewStyles]" src="/uni_modules/lime-echart/static/uvue.html?v=10112">
</web-view>
<!-- #endif -->
<!-- #ifdef H5 -->
<div class="lime-echart" ref="chartRef"></div>
<!-- #endif -->
<!-- #ifndef H5 || APP-->
<canvas class="lime-echart" :id="canvasid" :canvas-id="canvasid"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend">
</canvas>
<!-- #endif -->
</template>
<script lang="uts" setup>
// @ts-nocheck
import { getCurrentInstance, nextTick } from "vue";
import { Echarts } from './uvue';
// #ifdef WEB
import { dispatch } from './canvas';
// #endif
// #ifndef APP || WEB
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect} from './utils';
// #endif
type EchartsResolve = (value : Echarts) => void
defineOptions({
name: 'l-echart'
})
const emits = defineEmits(['finished'])
const props = defineProps({
// #ifdef APP
webviewStyles: {
type: Object
},
customStyle: {
type: Object
},
// #endif
// #ifndef APP
webviewStyles: {
type: Object
},
customStyle: {
type: [String, Object]
},
// #endif
isDisableScroll: {
type: Boolean,
default: false
},
isClickable: {
type: Boolean,
default: true
},
enableHover: {
type: Boolean,
default: false
},
beforeDelay: {
type: Number,
default: 30
}
})
const instance = getCurrentInstance()!;
const canvasid = `lime-echart-${instance.uid}`
const finished = ref(false)
const map = [] as EchartsResolve[]
const callbackMap = [] as EchartsResolve[]
// let context = null as UniWebViewElement | null
let chart = null as Echarts | null
let chartRef = ref<UniWebViewElement | null>(null)
const trigger = () => {
// #ifdef APP
if (finished.value) {
if (chart == null) {
chart = new Echarts(chartRef.value!)
}
while (map.length > 0) {
const resolve = map.pop() as EchartsResolve
resolve(chart!)
}
}
// #endif
// #ifndef APP
while (map.length > 0) {
if(chart != null){
const resolve = map.pop() as EchartsResolve
resolve(chart!)
}
}
// #endif
if(chart != null){
while(callbackMap.length > 0){
const callback = callbackMap.pop() as EchartsResolve
callback(chart!)
}
}
}
// #ifdef APP
const loaded = (event : UniWebViewLoadEvent) => {
event.stopPropagation()
event.preventDefault()
finished.value = true
trigger()
emits('finished')
}
// #endif
const _next = () : boolean => {
if (chart == null) {
console.warn(`组件还未初始化,请先使用 init`)
return true
}
return false
}
const setOption = (option : UTSJSONObject) => {
if (_next()) return
chart!.setOption(option);
}
const showLoading = () => {
if (_next()) return
chart!.showLoading();
}
const hideLoading = () => {
if (_next()) return
chart!.hideLoading();
}
const clear = () => {
if (_next()) return
chart!.clear();
}
const dispose = () => {
if (_next()) return
chart!.dispose();
}
const resize = (size : UTSJSONObject) => {
if (_next()) return
chart!.resize(size);
}
const canvasToTempFilePath = (opt : UTSJSONObject) => {
if (_next()) return
chart!.canvasToTempFilePath(opt);
}
// #ifdef APP
function init(callback : ((chart : Echarts) => void) | null) : Promise<Echarts> {
if(callback!=null){
callbackMap.push(callback)
}
return new Promise<Echarts>((resolve) => {
map.push(resolve)
trigger()
})
}
// #endif
// #ifndef APP
// #ifndef WEB
let use2dCanvas = canIUseCanvas2d()
const getContext = async () =>{
return getRect(`#${canvasid}`, {context: instance.proxy!, type: use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
if(res) {
let dpr = uni.getWindowInfo().pixelRatio
let {width, height, node} = res
let canvas;
if(node) {
const ctx = node.getContext('2d');
canvas = new Canvas(ctx, instance.proxy, true, node);
} else {
const ctx = uni.createCanvasContext(canvasid, instance.proxy);
canvas = new Canvas(ctx, instance.proxy, false);
}
return { canvas, width, height, devicePixelRatio: dpr, node };
} else {
return {}
}
})
}
// #endif
const getTouch = (e) => {
const touches = e.touches[0]
// #ifdef WEB
const rect = chart!.getZr().dom.getBoundingClientRect();
const touch = {
x: touches.clientX - rect.left,
y: touches.clientY - rect.top
}
// #endif
// #ifndef WEB
const touch = {
x: touches.x,
y: touches.y
}
// #endif
return touch
}
const touchstart = (e) => {
if(chart == null) return
const handler = chart.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'click', touch)
}
const touchmove = (e) => {
if(chart == null) return
const handler = chart.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousemove', touch)
// const rect = chart.getZr().dom.getBoundingClientRect()
// handler.dispatch('mousemove', {
// zrX: e.touches[0].clientX - rect.left,
// zrY: e.touches[0].clientY - rect.top
// })
}
const touchend = (e) => {
if(chart == null) return
const handler = chart.getZr().handler;
const touch = {
x: 999999999,
y: 999999999
}
dispatch.call(handler, 'mousemove', touch)
dispatch.call(handler, 'touchend', touch)
}
async function init(echarts: any, ...args: any[]): Promise<Echarts>{
if(echarts == null){
console.error('请确保已经引入了 ECharts 库');
return Promise.reject('请确保已经引入了 ECharts 库');
}
let theme:string|null=null
let opts={}
let callback:Function|null=null;
args.forEach(item =>{
if(typeof item === 'function') {
callback = item
} else if(['string'].includes(typeof item)){
theme = item
} else if(typeof item === 'object'){
opts = item
}
})
// #ifdef WEB
chart = echarts.init(chartRef.value, theme, opts)
window.addEventListener('touchstart', touchstart)
window.addEventListener('touchmove', touchmove)
window.addEventListener('touchend', touchend)
// #endif
// #ifndef WEB
let config = await getContext();
setCanvasCreator(echarts, config)
chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
// #endif
console.log('chart', chart)
if(callback!=null && typeof callback == 'function'){
callbackMap.push(callback)
}
return new Promise<Echarts>((resolve) => {
map.push(resolve)
trigger()
})
}
onMounted(()=>{
nextTick(()=>{
finished.value = true
trigger()
emits('finished')
})
})
onUnmounted(()=>{
// #ifdef WEB
window.removeEventListener('touchstart', touchstart)
window.removeEventListener('touchmove', touchmove)
window.removeEventListener('touchend', touchend)
// #endif
})
// #endif
defineExpose({
init,
setOption,
showLoading,
hideLoading,
clear,
dispose,
resize,
canvasToTempFilePath
})
</script>
<style lang="scss">
.lime-echart {
flex: 1;
width: 100%;
}
</style>

View File

@ -0,0 +1,530 @@
<template>
<view class="lime-echart" :style="[customStyle]" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
<!-- #ifndef APP-NVUE -->
<canvas
class="lime-echart__canvas"
v-if="use2dCanvas"
type="2d"
:id="canvasId"
:style="canvasStyle"
:disable-scroll="isDisableScroll"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
/>
<canvas
class="lime-echart__canvas"
v-else
:width="nodeWidth"
:height="nodeHeight"
:style="canvasStyle"
:canvas-id="canvasId"
:id="canvasId"
:disable-scroll="isDisableScroll"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
/>
<view class="lime-echart__mask"
v-if="isPC"
@mousedown="touchStart"
@mousemove="touchMove"
@mouseup="touchEnd"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd">
</view>
<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<web-view
class="lime-echart__canvas"
:id="canvasId"
:style="canvasStyle"
:webview-styles="webviewStyles"
ref="webview"
src="/uni_modules/lime-echart/static/uvue.html?v=1"
@pagefinish="finished = true"
@onPostMessage="onMessage"
></web-view>
<!-- #endif -->
</view>
</template>
<script>
// @ts-nocheck
// #ifndef APP-NVUE
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo} from './utils';
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils';
import {Echarts} from './nvue'
// #endif
const charts = {}
const echartsObj = {}
/**
* LimeChart 图表
* @description 全端兼容的eCharts
* @tutorial https://ext.dcloud.net.cn/plugin?id=4899
* @property {String} customStyle 自定义样式
* @property {String} type 指定 canvas 类型
* @value 2d 使用canvas 2d部分小程序支持
* @value '' 使用原生canvas会有层级问题
* @value bottom right 不缩放图片只显示图片的右下边区域
* @property {Boolean} isDisableScroll
* @property {number} beforeDelay = [30] 延迟初始化 (毫秒)
* @property {Boolean} enableHover PC端使用鼠标悬浮
* @event {Function} finished 加载完成触发
*/
export default {
name: 'lime-echart',
props: {
// #ifdef MP-WEIXIN || MP-TOUTIAO
type: {
type: String,
default: '2d'
},
// #endif
// #ifdef APP-NVUE
webviewStyles: Object,
// hybrid: Boolean,
// #endif
customStyle: String,
isDisableScroll: Boolean,
isClickable: {
type: Boolean,
default: true
},
enableHover: Boolean,
beforeDelay: {
type: Number,
default: 30
},
landscape: Boolean
},
data() {
return {
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
use2dCanvas: true,
// #endif
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
use2dCanvas: false,
// #endif
ariaLabel: '图表',
width: null,
height: null,
nodeWidth: null,
nodeHeight: null,
// canvasNode: null,
config: {},
inited: false,
finished: false,
file: '',
platform: '',
isPC: false,
isDown: false,
isOffscreenCanvas: false,
offscreenWidth: 0,
offscreenHeight: 0,
};
},
computed: {
rootStyle() {
if(this.landscape) {
return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
}
},
canvasId() {
return `lime-echart${this._ && this._.uid || this._uid}`
},
offscreenCanvasId() {
return `${this.canvasId}_offscreen`
},
offscreenStyle() {
return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
},
canvasStyle() {
return this.rootStyle + (this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '')
}
},
// #ifndef VUE3
beforeDestroy() {
this.clear()
this.dispose()
// #ifdef H5
if(this.isPC) {
document.removeEventListener('mousewheel', this.mousewheel)
}
// #endif
},
// #endif
// #ifdef VUE3
beforeUnmount() {
this.clear()
this.dispose()
// #ifdef H5
if(this.isPC) {
document.removeEventListener('mousewheel', this.mousewheel)
}
// #endif
},
// #endif
created() {
// #ifdef H5
if(!('ontouchstart' in window)) {
this.isPC = true
document.addEventListener('mousewheel', this.mousewheel)
}
// #endif
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
// const { platform } = uni.getSystemInfoSync();
const { platform } = getDeviceInfo();
this.isPC = /windows/i.test(platform)
// #endif
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
},
mounted() {
this.$nextTick(() => {
this.$emit('finished')
})
},
methods: {
// #ifdef APP-NVUE
onMessage(e) {
const detail = e?.detail?.data[0] || null;
const data = detail?.data
const key = detail?.event
const options = data?.options
const event = data?.event
const file = detail?.file
if (key == 'log' && data) {
console.log(data)
}
if(event) {
this.chart.dispatchAction(event.replace(/"/g,''), options)
}
if(file) {
thie.file = file
}
},
// #endif
setChart(callback) {
if(!this.chart) {
console.warn(`组件还未初始化,请先使用 init`)
return
}
if(typeof callback === 'function' && this.chart) {
callback(this.chart);
}
// #ifdef APP-NVUE
if(typeof callback === 'function') {
this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
}
// #endif
},
setOption() {
if (!this.chart || !this.chart.setOption) {
console.warn(`组件还未初始化,请先使用 init`)
return
}
this.chart.setOption(...arguments);
},
showLoading() {
if(this.chart) {
this.chart.showLoading(...arguments)
}
},
hideLoading() {
if(this.chart) {
this.chart.hideLoading()
}
},
clear() {
if(this.chart) {
this.chart.clear()
}
},
dispose() {
if(this.chart) {
this.chart.dispose()
}
},
resize(size) {
if(size && size.width && size.height) {
this.height = size.height
this.width = size.width
if(this.chart) {this.chart.resize(size)}
} else {
this.$nextTick(() => {
uni.createSelectorQuery()
.in(this)
.select(`.lime-echart`)
.boundingClientRect()
.exec(res => {
if (res) {
let { width, height } = res[0];
this.width = width = width || 300;
this.height = height = height || 300;
this.chart.resize({width, height})
}
});
})
}
},
canvasToTempFilePath(args = {}) {
// #ifndef APP-NVUE
const { use2dCanvas, canvasId } = this;
return new Promise((resolve, reject) => {
const copyArgs = Object.assign({
canvasId,
success: resolve,
fail: reject
}, args);
if (use2dCanvas) {
delete copyArgs.canvasId;
copyArgs.canvas = this.canvasNode;
}
uni.canvasToTempFilePath(copyArgs, this);
});
// #endif
// #ifdef APP-NVUE
this.file = ''
this.$refs.webview.evalJs(`canvasToTempFilePath()`);
return new Promise((resolve, reject) => {
this.$watch('file', async (file) => {
if(file) {
const tempFilePath = await base64ToPath(file)
resolve(args.success({tempFilePath}))
} else {
reject(args.fail({error: ``}))
}
})
})
// #endif
},
async init(echarts, ...args) {
// #ifndef APP-NVUE
if(args && args.length == 0 && !echarts) {
console.error('缺少参数init(echarts, theme?:string, opts?: object, callback?: function)')
return
}
// #endif
let theme=null,opts={},callback;
Array.from(arguments).forEach(item => {
if(typeof item === 'function') {
callback = item
}
if(['string'].includes(typeof item)) {
theme = item
}
if(typeof item === 'object') {
opts = item
}
})
if(this.beforeDelay) {
await sleep(this.beforeDelay)
}
let config = await this.getContext();
// #ifndef APP-NVUE
setCanvasCreator(echarts, config)
try {
this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
if(typeof callback === 'function') {
callback(this.chart)
} else {
return this.chart
}
} catch(e) {
console.error(e.messges)
return null
}
// #endif
// #ifdef APP-NVUE
this.chart = new Echarts(this.$refs.webview)
this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
if(callback) {
callback(this.chart)
} else {
return this.chart
}
// #endif
},
getContext() {
// #ifdef APP-NVUE
if(this.finished) {
return Promise.resolve(this.finished)
}
return new Promise(resolve => {
this.$watch('finished', (val) => {
if(val) {
resolve(this.finished)
}
})
})
// #endif
// #ifndef APP-NVUE
return getRect(`#${this.canvasId}`, {context: this, type: this.use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
if(res) {
let dpr = devicePixelRatio
let {width, height, node} = res
let canvas;
this.width = width = width || 300;
this.height = height = height || 300;
if(node) {
const ctx = node.getContext('2d');
canvas = new Canvas(ctx, this, true, node);
this.canvasNode = node
} else {
// #ifdef MP-TOUTIAO
dpr = !this.isPC ? devicePixelRatio : 1// 1.25
// #endif
// #ifndef MP-ALIPAY || MP-TOUTIAO
dpr = this.isPC ? devicePixelRatio : 1
// #endif
// #ifdef MP-ALIPAY || MP-LARK
dpr = devicePixelRatio
// #endif
// #ifdef WEB
dpr = 1
// #endif
this.rect = res
this.nodeWidth = width * dpr;
this.nodeHeight = height * dpr;
const ctx = uni.createCanvasContext(this.canvasId, this);
canvas = new Canvas(ctx, this, false);
}
return { canvas, width, height, devicePixelRatio: dpr, node };
} else {
return {}
}
})
// #endif
},
// #ifndef APP-NVUE
getRelative(e, touches) {
let { clientX, clientY } = e
if(!(clientX && clientY) && touches && touches[0]) {
clientX = touches[0].clientX
clientY = touches[0].clientY
}
return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
},
getTouch(e, touches) {
const {x} = touches && touches[0] || {}
const touch = x ? touches[0] : this.getRelative(e, touches);
if(this.landscape) {
[touch.x, touch.y] = [touch.y, this.height - touch.x]
}
return touch;
},
touchStart(e) {
this.isDown = true
const next = () => {
const touches = convertTouchesToArray(e.touches)
if(this.chart) {
const touch = this.getTouch(e, touches)
this.startX = touch.x
this.startY = touch.y
this.startT = new Date()
const handler = this.chart.getZr().handler;
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'start');
clearTimeout(this.endTimer);
}
}
if(this.isPC) {
getRect(`#${this.canvasId}`, {context: this}).then(res => {
this.rect = res
next()
})
return
}
next()
},
touchMove(e) {
if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
const touches = convertTouchesToArray(e.touches)
if (this.chart && this.isDown) {
const handler = this.chart.getZr().handler;
dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
handler.processGesture(wrapTouch(e), 'change');
}
},
touchEnd(e) {
this.isDown = false
if (this.chart) {
const touches = convertTouchesToArray(e.changedTouches)
const {x} = touches && touches[0] || {}
const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
if(this.landscape) {
[touch.x, touch.y] = [touch.y, this.height - touch.x]
}
const handler = this.chart.getZr().handler;
const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
dispatch.call(handler, 'mouseup', touch)
handler.processGesture(wrapTouch(e), 'end');
if(isClick) {
dispatch.call(handler, 'click', touch)
} else {
this.endTimer = setTimeout(() => {
dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
},50)
}
}
},
// #endif
// #ifdef H5
mousewheel(e){
if(this.chart) {
// dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
}
}
// #endif
}
};
</script>
<style>
.lime-echart {
position: relative;
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.lime-echart__canvas {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
/* #ifndef APP-NVUE */
.lime-echart__mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
}
/* #endif */
</style>

View File

@ -0,0 +1,51 @@
export class Echarts {
eventMap = new Map()
constructor(webview) {
this.webview = webview
this.options = null
}
setOption() {
this.options = arguments
this.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
}
getOption() {
return this.options
}
showLoading() {
this.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
}
hideLoading() {
this.webview.evalJs(`hideLoading()`);
}
clear() {
this.webview.evalJs(`clear()`);
}
dispose() {
this.webview.evalJs(`dispose()`);
}
resize(size) {
if(size) {
this.webview.evalJs(`resize(${JSON.stringify(size)})`);
} else {
this.webview.evalJs(`resize()`);
}
}
on(type, ...args) {
const query = args[0]
const useQuery = query && typeof query != 'function'
const param = useQuery ? [type, query] : [type]
const key = `${type}${useQuery ? JSON.stringify(query): '' }`
const callback = useQuery ? args[1]: args[0]
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.webview.evalJs(`on(${JSON.stringify(param)})`);
console.warn('nvue 暂不支持事件')
}
dispatchAction(type, options){
const handler = this.eventMap.get(type)
if(handler){
handler(options)
}
}
}

View File

@ -0,0 +1,190 @@
// @ts-nocheck
/**
* 获取设备基础信息
*
* @see [uni.getDeviceInfo](https://uniapp.dcloud.net.cn/api/system/getDeviceInfo.html)
*/
export function getDeviceInfo() {
if (uni.getDeviceInfo && uni.canIUse('getDeviceInfo')) {
return uni.getDeviceInfo();
} else {
return uni.getSystemInfoSync();
}
}
/**
* 获取窗口信息
*
* @see [uni.getWindowInfo](https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html)
*/
export function getWindowInfo() {
if (uni.getWindowInfo && uni.canIUse('getWindowInfo')) {
return uni.getWindowInfo();
} else {
return uni.getSystemInfoSync();
}
}
/**
* 获取APP基础信息
*
* @see [uni.getAppBaseInfo](https://uniapp.dcloud.net.cn/api/system/getAppBaseInfo.html)
*/
export function getAppBaseInfo() {
if (uni.getAppBaseInfo && uni.canIUse('getAppBaseInfo')) {
return uni.getAppBaseInfo();
} else {
return uni.getSystemInfoSync();
}
}
// #ifndef APP-NVUE
// 计算版本
export function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i], 10)
const num2 = parseInt(v2[i], 10)
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
// const systemInfo = uni.getSystemInfoSync();
function gte(version) {
// 截止 2023-03-22 mac pc小程序不支持 canvas 2d
// let {
// SDKVersion,
// platform
// } = systemInfo;
const { platform } = getDeviceInfo();
let { SDKVersion } = getAppBaseInfo();
// #ifdef MP-ALIPAY
SDKVersion = my.SDKVersion
// #endif
// #ifdef MP-WEIXIN
return platform !== 'mac' && compareVersion(SDKVersion, version) >= 0;
// #endif
return compareVersion(SDKVersion, version) >= 0;
}
export function canIUseCanvas2d() {
// #ifdef MP-WEIXIN
return gte('2.9.0');
// #endif
// #ifdef MP-ALIPAY
return gte('2.7.0');
// #endif
// #ifdef MP-TOUTIAO
return gte('1.78.0');
// #endif
return false
}
export function convertTouchesToArray(touches) {
// 如果 touches 是一个数组,则直接返回它
if (Array.isArray(touches)) {
return touches;
}
// 如果touches是一个对象则转换为数组
if (typeof touches === 'object' && touches !== null) {
return Object.values(touches);
}
// 对于其他类型,直接返回它
return touches;
}
export function wrapTouch(event) {
event.touches = convertTouchesToArray(event.touches)
for (let i = 0; i < event.touches.length; ++i) {
const touch = event.touches[i];
touch.offsetX = touch.x;
touch.offsetY = touch.y;
}
return event;
}
// export const devicePixelRatio = uni.getSystemInfoSync().pixelRatio
export const devicePixelRatio = getWindowInfo().pixelRatio;
// #endif
// #ifdef APP-NVUE
export function base64ToPath(base64) {
return new Promise((resolve, reject) => {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
if (!format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
const time = new Date().getTime();
const filePath = `_doc/uniapp_temp/${time}.${format}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
console.error(`${JSON.stringify(error)}`)
reject(error)
})
}, (error) => {
bitmap.clear()
console.error(`${JSON.stringify(error)}`)
reject(error)
})
})
}
// #endif
export function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, time)
})
}
export function getRect(selector, options = {}) {
const typeDefault = 'boundingClientRect'
const {
context,
type = typeDefault
} = options
return new Promise((resolve, reject) => {
const dom = uni.createSelectorQuery().in(context).select(selector);
const result = (rect) => {
if (rect) {
resolve(rect)
} else {
reject()
}
}
if (type == typeDefault) {
dom[type](result).exec()
} else {
dom[type]({
node: true,
size: true,
rect: true
}, result).exec()
}
});
};

View File

@ -0,0 +1,133 @@
// @ts-nocheck
// #ifdef APP
type EchartsEventHandler = (event: UTSJSONObject)=>void
// type EchartsTempResolve = (obj : UTSJSONObject) => void
// type EchartsTempOptions = UTSJSONObject
export class Echarts {
options: UTSJSONObject = {} as UTSJSONObject
context: UniWebViewElement
eventMap: Map<string, EchartsEventHandler> = new Map()
private temp: UTSJSONObject[] = []
constructor(context: UniWebViewElement){
this.context = context
this.init()
}
init(){
this.context.evalJS(`init(null, null, ${JSON.stringify({})})`)
this.context.addEventListener('message', (e : UniWebViewMessageEvent) => {
// event.stopPropagation()
// event.preventDefault()
const detail = e.detail.data[0]
const file = detail.getString('file')
const data = detail.get('data')
const key = detail.getString('event')
const options = typeof data == 'object' ? (data as UTSJSONObject).getJSON('options'): null
const event = typeof data == 'object' ? (data as UTSJSONObject).getString('event'): null
if (key == 'log' && data != null) {
console.log(data)
}
if (event != null && options != null) {
this.dispatchAction(event.replace(/"/g,''), options)
}
if(file != null){
while (this.temp.length > 0) {
const opt = this.temp.pop()
const success = opt?.get('success')
if(typeof success == 'function'){
success as (res: UTSJSONObject) => void
success({tempFilePath: file})
}
}
}
})
}
setOption(option: UTSJSONObject){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option])})`)
}
setOption(option: UTSJSONObject, notMerge: boolean = false, lazyUpdate: boolean = false){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge, lazyUpdate])})`)
}
setOption(option: UTSJSONObject, notMerge: UTSJSONObject){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge])})`)
}
getOption(): UTSJSONObject {
return this.options
}
showLoading(){
this.context.evalJS(`showLoading(${JSON.stringify([] as any[])})`);
}
showLoading(type: string, opts: UTSJSONObject){
this.context.evalJS(`showLoading(${JSON.stringify([type, opts])})`);
}
hideLoading(){
this.context.evalJS(`hideLoading()`);
}
clear(){
this.context.evalJS(`clear()`);
}
dispose(){
this.context.evalJS(`dispose()`);
}
resize(size:UTSJSONObject){
setTimeout(()=>{
this.context.evalJS(`resize(${JSON.stringify(size)})`);
},0)
}
resize(){
setTimeout(()=>{
this.context.evalJS(`resize()`);
},10)
}
on(type:string, query: any, callback: EchartsEventHandler) {
const key = `${type}${JSON.stringify(query)}`
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.context.evalJS(`on(${JSON.stringify([type, query])})`);
console.warn('uvue 暂不支持事件')
}
on(type:string, callback: EchartsEventHandler) {
const key = `${type}`
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.context.evalJS(`on(${JSON.stringify([type])})`);
console.warn('uvue 暂不支持事件')
}
dispatchAction(type:string, options: UTSJSONObject){
const handler = this.eventMap.get(type)
if(handler!=null){
handler(options)
}
}
canvasToTempFilePath(opt: UTSJSONObject){
// this.context.evalJS(`on(${JSON.stringify(opt)})`);
this.context.evalJS(`canvasToTempFilePath(${JSON.stringify(opt)})`);
this.temp.push(opt)
}
}
// #endif
// #ifndef APP
export class Echarts {
constructor() {}
setOption(option: UTSJSONObject): void
isDisposed(): boolean;
clear(): void;
resize(size:UTSJSONObject): void;
resize(): void;
canvasToTempFilePath(opt : UTSJSONObject): void;
dispose(): void;
showLoading(cfg?: UTSJSONObject): void;
showLoading(name?: string, cfg?: UTSJSONObject): void;
hideLoading(): void;
getZr(): any
}
// #endif

View File

@ -0,0 +1,159 @@
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script>
export default {
data() {
return {
showTip: false,
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
}
},
mounted() {
console.log('lime echarts nvue')
},
methods: {
init() {
const chartRef = this.$refs['chartRef']
chartRef.init(chart => {
chart.setOption(this.option);
setTimeout(()=>{
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
chart.setOption(option);
},1000)
})
},
save() {
// this.$refs.chart.canvasToTempFilePath({
// success(res) {
// console.log('res::::', res)
// }
// })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,160 @@
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script lang="uts" setup>
// @ts-nocheck
// #ifndef APP
import * as echarts from 'echarts/dist/echarts.esm.js'
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(null)
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
// formatter: async (params: any) => {
// console.log('params', params)
// return 1
// },
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
const init = async () =>{
if(chartRef.value== null) return
// #ifdef APP
const chart = await chartRef.value!.init(null)
// #endif
// #ifndef APP
const chart = await chartRef.value!.init(echarts, null)
// #endif
chart.setOption(option)
chart.on('mouseover', function (params) {
console.log('params', params);
});
// setTimeout(()=> {
// const option1 = {
// tooltip: {
// trigger: 'axis',
// // shadowBlur: 0,
// textStyle: {
// textShadowBlur: 0
// },
// renderMode: 'richText',
// },
// legend: {
// data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
// },
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true
// },
// xAxis: {
// type: 'category',
// boundaryGap: false,
// data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// name: '邮件营销',
// type: 'line',
// stack: '总量',
// data: [820, 132, 101, 134, 90, 230, 210]
// },
// {
// name: '联盟广告',
// type: 'line',
// stack: '总量',
// data: [220, 182, 191, 234, 290, 330, 310]
// },
// {
// name: '视频广告',
// type: 'line',
// stack: '总量',
// data: [950, 232, 201, 154, 190, 330, 410]
// },
// {
// name: '直接访问',
// type: 'line',
// stack: '总量',
// data: [320, 332, 301, 334, 390, 330, 320]
// },
// {
// name: '搜索引擎',
// type: 'line',
// stack: '总量',
// data: [820, 932, 901, 934, 1290, 1330, 1320]
// }
// ]
// }
// chart.setOption(option1)
// },1000)
}
</script>
<style>
</style>

View File

@ -0,0 +1,227 @@
<template>
<view>
<view style="height: 750rpx; position: relative">
<l-echart ref="chart" @finished="init"></l-echart>
<view
class="customTooltips"
:style="{ left: position[0] + 'px', top: position[1] + 'px' }"
v-if="params.length && position.length && showTip"
>
<view>这是个自定的tooltips</view>
<view>{{ params[0]['axisValue'] }}</view>
<view v-for="item in params">
<view>
<text>{{ item.seriesName }}</text>
<text>{{ item.value }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// nvue
// #ifdef VUE2
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
// #endif
// #ifdef VUE3
// #ifdef MP
// vue3 使vite umd使使require
const echarts = require('../../static/echarts.min')
// #endif
// #ifndef MP
// vue3 使vite umdnpm
import * as echarts from 'echarts/dist/echarts.esm'
// #endif
// #endif
export default {
data() {
return {
showTip: false,
position: [],
params: [],
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
position: (point, params, dom, rect, size) => {
// tooltips
const box = [170, 170]
//
const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20
const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20
const x = point[0] + offsetX
const y = point[1] + offsetY
this.position = [x, y]
this.params = params
},
formatter: (params, ticket, callback) => {},
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
},
}
},
methods: {
init() {
// init(echarts, theme?:string, opts?:{}, chart => {})
// echarts nvuenvue
// theme 'dark'
// opts = { //
// locale?: string // `5.0.0`
// }
// chart => {} callback
// setTimeout(()=>{
// this.$refs.chart.init(echarts, chart => {
// chart.setOption(this.option);
// });
// },300)
this.$refs.chart.init(echarts, (chart) => {
chart.setOption(this.option)
// tooltip
chart.on('showTip', (params) => {
this.showTip = true
console.log('showTip::')
})
chart.on('hideTip', (params) => {
setTimeout(() => {
this.showTip = false
}, 300)
})
setTimeout(() => {
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [1120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 632, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [820, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
}
chart.setOption(option)
}, 1000)
})
},
save() {
this.$refs.chart.canvasToTempFilePath({
success(res) {
console.log('res::::', res)
},
})
},
},
}
</script>
<style>
.customTooltips {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 20rpx;
}
</style>

View File

@ -0,0 +1,91 @@
{
"id": "lime-echart",
"displayName": "echarts",
"version": "0.9.8",
"description": "echarts 全端兼容一款使echarts图表能跑在uniapp各端中的插件, 支持uniapp/uniappx(web,ios,安卓)",
"keywords": [
"echarts",
"canvas",
"图表",
"可视化"
],
"repository": "https://gitee.com/liangei/lime-echart",
"engines": {
"HBuilderX": "^3.6.4"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y",
"app-uvue": "y",
"app-harmony": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
},
"dependencies": {
"echarts": "^5.4.1",
"zrender": "^5.4.3"
}
}

View File

@ -0,0 +1,407 @@
# echarts 图表 <span style="font-size:16px;">👑👑👑👑👑 <span style="background:#ff9d00;padding:2px 4px;color:#fff;font-size:10px;border-radius: 3px;">全端</span></span>
> 一个基于 JavaScript 的开源可视化图表库 [查看更多](https://limeui.qcoon.cn/#/echart) <br>
> 基于 echarts 做了兼容处理,更多示例请访问 [uni示例](https://limeui.qcoon.cn/#/echart-example) | [官方示例](https://echarts.apache.org/examples/zh/index.html) <br>
## 平台兼容
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
| --- | ---------- | ------------ | ---------- | ---------- | --------- | ---- |
| √ | √ | √ | √ | √ | √ | √ |
## 安装
- 第一步:在市场导入 [百度图表](https://ext.dcloud.net.cn/plugin?id=4899)
- 第二步:选择插件依赖:<br>
1、可以选插件内的`echarts`包或自定义包,自定义包[下载地址](https://echarts.apache.org/zh/builder.html)<br>
2、或者使用`npm`安装`echarts`
**注意**
* 🔔 echarts 5.3.0及以上
* 🔔 如果是 `cli` 项目请下载插件到`src`目录下的`uni_modules`,没有这个目录就创建一个
## 代码演示
### Vue2
- 引入依赖,可以是插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html),也可以是`npm`包
```html
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef" @finished="init"></l-echart></view>
```
```js
// 插件内的 三选一
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
// 自定义的 三选一 下载后放入项目的路径
import * as echarts from 'xxx/echarts.min'
// npm包 三选一 需要在控制台 输入命令npm install echarts
import * as echarts from 'echarts'
```
```js
export default {
data() {
return {
option: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
confine: true
},
legend: {
data: ['热度', '正面', '负面']
},
grid: {
left: 20,
right: 20,
bottom: 15,
top: 40,
containLabel: true
},
xAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
series: [
{
name: '热度',
type: 'bar',
label: {
normal: {
show: true,
position: 'inside'
}
},
data: [300, 270, 340, 344, 300, 320, 310],
},
{
name: '正面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true
}
},
data: [120, 102, 141, 174, 190, 250, 220]
},
{
name: '负面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true,
position: 'left'
}
},
data: [-20, -32, -21, -34, -90, -130, -110]
}
]
},
};
},
// 组件能被调用必须是组件的节点已经被渲染到页面上
methods: {
async init() {
// chart 图表实例不能存在data里
const chart = await this.$refs.chartRef.init(echarts);
chart.setOption(this.option)
}
}
}
```
### Vue3
- 小程序可以使用`require`引入插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html)
- `require`仅支持相对路径,不支持路径别名
- 非小程序使用 `npm`
```html
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef"></l-echart></view>
```
```js
// 小程序 二选一
// 插件内的 二选一
const echarts = require('../../uni_modules/lime-echart/static/echarts.min');
// 自定义的 二选一 下载后放入项目的路径
const echarts = require('xxx/xxx/echarts');
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 非小程序
// 需要在控制台 输入命令npm install echarts
import * as echarts from 'echarts'
```
```js
const chartRef = ref(null)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
confine: true
},
legend: {
data: ['热度', '正面', '负面']
},
grid: {
left: 20,
right: 20,
bottom: 15,
top: 40,
containLabel: true
},
xAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
series: [
{
name: '热度',
type: 'bar',
label: {
normal: {
show: true,
position: 'inside'
}
},
data: [300, 270, 340, 344, 300, 320, 310],
},
{
name: '正面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true
}
},
data: [120, 102, 141, 174, 190, 250, 220]
},
{
name: '负面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true,
position: 'left'
}
},
data: [-20, -32, -21, -34, -90, -130, -110]
}
]
};
onMounted( ()=>{
// 组件能被调用必须是组件的节点已经被渲染到页面上
setTimeout(async()=>{
if(!chartRef.value) return
const myChart = await chartRef.value.init(echarts)
myChart.setOption(option)
},300)
})
```
### Uvue
- Uvue和Nvue不需要引入`echarts`,因为它们的实现方式是`webview`
- uniapp x需要HBX 4.13以上
```html
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
```
```js
// @ts-nocheck
// #ifdef H5
import * as echarts from 'echarts/dist/echarts.esm.js'
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(null);
const init = async () => {
if(chartRef.value== null) return
// #ifdef APP
const chart = await chartRef.value!.init(null)
// #endif
// #ifdef H5
const chart = await chartRef.value!.init(echarts, null)
// #endif
chart.setOption(option)
}
```
## 数据更新
- 1、使用 `ref` 可获取`setOption`设置更新
- 2、也可以拿到图表实例`chart`设置`myChart.setOption(data)`
```js
// ref
this.$refs.chart.setOption(data)
// 图表实例
myChart.setOption(data)
```
## 图表大小
- 在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。
```js
// 默认获取容器尺寸
this.$refs.chart.resize()
// 指定尺寸
this.$refs.chart.resize({width: 375, height: 375})
```
## 自定义Tooltips
- uvue\nvue 不支持
由于除H5之外都不存在dom但又有tooltips个性化的需求代码就不贴了看示例吧
```
代码位于/uni_modules/lime-echart/component/lime-echart
```
## 插件标签
- 默认 l-echart 为 component
- 默认 lime-echart 为 demo
```html
// 在任意地方使用可查看domo, 代码位于/uni_modules/lime-echart/component/lime-echart
<lime-echart></lime-echart>
```
## 常见问题
- 钉钉小程序 由于没有`measureText`,模拟的`measureText`又无法得到当前字体的`fontWeight`,故可能存在估计不精细的问题
- 微信小程序 `2d` 只支持 真机调试2.0
- 微信开发工具会出现 `canvas` 不跟随页面的情况,真机不影响
- 微信开发工具会出现 `canvas` 层级过高的问题,真机一般不受影响,可以先测只有两个元素的页面看是否会有层级问题。
- toolbox 不支持 `saveImage`
- echarts 5.3.0 的 lines 不支持 trailLength故需设置为 `0`
- dataZoom H5不要设置 `showDetail`
- 如果微信小程序的`tooltip`文字有阴影,可能是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
- 如果钉钉小程序上传时报安全问题`Uint8Clamped`,可以向钉钉反馈是安全代码扫描把Uint8Clamped数组错误识别了也可以在 echarts 文件修改`Uint8Clamped`
```js
// 找到这段代码把代码中`Uint8Clamped`改成`Uint8_Clamped`,再把下划线去掉,不过直接去掉`Uint8Clamped`也是可行的
// ["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e+"Array]"]
// 改成如下
["Int8","Uint8","Uint8_Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e.replace('_','')+"Array]"]
```
### vue3
如果您是使用 **vite + vue3** 非微信小程序可能会遇到`echarts`文件缺少`wx`判断导致无法使用或缺少`tooltip`<br>
方式一:可以在`echarts.min.js`文件开头增加以下内容参考插件内的echart.min.js的做法
```js
let global = null
let wx = uni
```
方式二:在`vite.config.js`的`define`设置环境
```js
// 或者在`vite.config.js`的`define`设置环境
import { defineConfig } from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
const define = {}
if(!["mp-weixin", "h5", "web"].includes(process.env.UNI_PLATFORM)) {
define['global'] = null
define['wx'] = 'uni'
}
export default defineConfig({
plugins: [uni()],
define
});
```
## Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --------------- | -------- | ------- | ------------ | ----- |
| custom-style | 自定义样式 | `string` | - | - |
| type | 指定 canvas 类型 | `string` | `2d` | |
| is-disable-scroll | 触摸图表时是否禁止页面滚动 | `boolean` | `false` | |
| beforeDelay | 延迟初始化 (毫秒) | `number` | `30` | |
| enableHover | PC端使用鼠标悬浮 | `boolean` | `false` | |
| landscape | 是否旋转90deg,模拟横屏效果 | `boolean` | `false` | |
## 事件
| 参数 | 说明 |
| --------------- | --------------- |
| init(echarts, chart => {}) | 初始化调用函数,第一个参数是传入`echarts`,第二个参数是回调函数,回调函数的参数是 `chart` 实例 |
| setChart(chart => {}) | 已经初始化后,请使用这个方法,是个回调函数,参数是 `chart` 实例 |
| setOption(data) | [图表配置项](https://echarts.apache.org/zh/option.html#title),用于更新 ,传递是数据 `option` |
| clear() | 清空当前实例,会移除实例中所有的组件和图表。 |
| dispose() | 销毁实例 |
| showLoading() | 显示加载 |
| hideLoading() | 隐藏加载 |
| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(opt) | 用于生成图片,与官方使用方法一致,但不需要传`canvasId` |
## 打赏
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style type="text/css">
html,
body,
.canvas {
padding: 0;
margin: 0;
overflow-y: hidden;
background-color: transparent;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="canvas" id="limeChart"></div>
<script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
<script type="text/javascript" src="./echarts.min.js"></script>
<script type="text/javascript" src="./ecStat.min.js"></script>
<!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
<script>
let chart = null;
let cache = [];
console.log = function() {
emit('log', {
log: arguments,
})
}
function emit(event, data) {
postMessage({
event,
data
})
cache = []
}
function postMessage(data) {
uni.webView.postMessage({
data
})
// window.__uniapp_x_.postMessage(JSON.stringify(data))
};
function stringify(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
return;
}
cache.push(value);
}
return value;
}
function parse(name, callback, options) {
const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
if (optionNameReg.test(callback)) {
const optionNames = callback.match(optionNameReg)
if (optionNames[1]) {
const _this = optionNames[1].split('.')[0]
window[_this] = {}
window[_this][optionNames[2]] = options
return optionNames[2]
} else {
return null
}
}
return null
}
function init(callback, options, opts, theme) {
if (!chart) {
chart = echarts.init(document.getElementById('limeChart'), theme, opts)
if (options) {
chart.setOption(options)
}
}
}
function on(data) {
if (chart && data.length > 0) {
const [type, query] = data
const key = `${type}${JSON.stringify(query||'')}`
if (query) {
chart.on(type, query, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
} else {
chart.on(type, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
}
}
}
function setChart(callback, options) {
if (!callback) return
if (chart && callback && options) {
var r = null
const name = parse('r', callback, options)
if (name) this[name] = options
eval(`r = ${callback};`)
if (r) {
r(chart)
}
}
}
function setOption(data) {
if (chart) chart.setOption(data[0], data[1])
}
function showLoading(data) {
if (chart) chart.showLoading(data[0], data[1])
}
function hideLoading() {
if (chart) chart.hideLoading()
}
function clear() {
if (chart) chart.clear()
}
function dispose() {
if (chart) chart.dispose()
}
function resize(size) {
if (chart) chart.resize(size)
}
function canvasToTempFilePath(opt) {
if (chart) {
delete opt.success
const src = chart.getDataURL(opt)
postMessage({
// event: 'file',
file: src
})
}
}
document.addEventListener('touchmove', () => {
})
</script>
</body>
</html>

View File

@ -0,0 +1,42 @@
// @ts-nocheck
import {isNumeric} from '../isNumeric'
import {isDef} from '../isDef'
/**
* px
* @param value
* @returns null null
*/
// #ifndef UNI-APP-X && APP
export function addUnit(value?: string | number): string | null {
if (!isDef(value)) {
return null;
}
value = String(value); // 将值转换为字符串
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
return isNumeric(value) ? `${value}px` : value;
}
// #endif
// #ifdef UNI-APP-X && APP
function addUnit(value: string): string
function addUnit(value: number): string
function addUnit(value: any|null): string|null {
if (!isDef(value)) {
return null;
}
value = `${value}` //value.toString(); // 将值转换为字符串
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
return isNumeric(value) ? `${value}px` : value;
}
export {addUnit}
// #endif
// console.log(addUnit(100)); // 输出: "100px"
// console.log(addUnit("200")); // 输出: "200px"
// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位)
// console.log(addUnit()); // 输出: undefined值为 undefined
// console.log(addUnit(null)); // 输出: undefined值为 null

View File

@ -0,0 +1,82 @@
export function cubicBezier(p1x : number, p1y : number, p2x : number, p2y : number):(x: number)=> number {
const ZERO_LIMIT = 1e-6;
// Calculate the polynomial coefficients,
// implicit first and last control points are (0,0) and (1,1).
const ax = 3 * p1x - 3 * p2x + 1;
const bx = 3 * p2x - 6 * p1x;
const cx = 3 * p1x;
const ay = 3 * p1y - 3 * p2y + 1;
const by = 3 * p2y - 6 * p1y;
const cy = 3 * p1y;
function sampleCurveDerivativeX(t : number) : number {
// `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
return (3 * ax * t + 2 * bx) * t + cx;
}
function sampleCurveX(t : number) : number {
return ((ax * t + bx) * t + cx) * t;
}
function sampleCurveY(t : number) : number {
return ((ay * t + by) * t + cy) * t;
}
// Given an x value, find a parametric value it came from.
function solveCurveX(x : number) : number {
let t2 = x;
let derivative : number;
let x2 : number;
// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
// first try a few iterations of Newton's method -- normally very fast.
// http://en.wikipedia.org/wikiNewton's_method
for (let i = 0; i < 8; i++) {
// f(t) - x = 0
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
derivative = sampleCurveDerivativeX(t2);
// == 0, failure
/* istanbul ignore if */
if (Math.abs(derivative) < ZERO_LIMIT) {
break;
}
t2 -= x2 / derivative;
}
// Fall back to the bisection method for reliability.
// bisection
// http://en.wikipedia.org/wiki/Bisection_method
let t1 = 1;
/* istanbul ignore next */
let t0 = 0;
/* istanbul ignore next */
t2 = x;
/* istanbul ignore next */
while (t1 > t0) {
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
if (x2 > 0) {
t1 = t2;
} else {
t0 = t2;
}
t2 = (t1 + t0) / 2;
}
// Failure
return t2;
}
return function (x : number) : number {
return sampleCurveY(solveCurveX(x));
}
// return solve;
}

View File

@ -0,0 +1,3 @@
import {cubicBezier} from './bezier';
export let ease = cubicBezier(0.25, 0.1, 0.25, 1);
export let linear = cubicBezier(0,0,1,1);

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
export * from './uvue.uts'
// #endif
// #ifndef UNI-APP-X && APP
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,100 @@
// @ts-nocheck
import type { ComponentPublicInstance } from 'vue'
import { ease, linear } from './ease';
import { Timeline, Animation } from './';
export type UseTransitionOptions = {
duration ?: number
immediate ?: boolean
context ?: ComponentPublicInstance
}
// #ifndef UNI-APP-X && APP
import { ref, watch, type Ref } from '@/uni_modules/lime-shared/vue'
export function useTransition(percent : Ref<number>|(() => number), options : UseTransitionOptions) : Ref<number> {
const current = ref(0)
const { immediate, duration = 300 } = options
let tl:Timeline|null = null;
let timer = -1
const isFunction = typeof percent === 'function'
watch(isFunction ? percent : () => percent.value, (v) => {
if(tl == null){
tl = new Timeline()
}
tl.start();
tl.add(
new Animation(
current.value,
v,
duration,
0,
ease,
nowValue => {
current.value = nowValue
clearTimeout(timer)
if(current.value == v){
timer = setTimeout(()=>{
tl?.pause();
tl = null
}, duration)
}
}
)
);
}, { immediate })
return current
}
// #endif
// #ifdef UNI-APP-X && APP
type UseTransitionReturnType = Ref<number>
export function useTransition(source : any, options : UseTransitionOptions) : UseTransitionReturnType {
const outputRef : Ref<number> = ref(0)
const immediate = options.immediate ?? false
const duration = options.duration ?? 300
const context = options.context //as ComponentPublicInstance | null
let tl:Timeline|null = null;
let timer = -1
const watchFunc = (v : number) => {
if(tl == null){
tl = new Timeline()
}
tl!.start();
tl!.add(
new Animation(
outputRef.value,
v,
duration,
0,
ease,
nowValue => {
outputRef.value = nowValue
clearTimeout(timer)
if(outputRef.value == v){
timer = setTimeout(()=>{
tl?.pause();
tl = null
}, duration)
}
}
),
null
);
}
if (context != null && typeof source == 'string') {
context.$watch(source, watchFunc, { immediate } as WatchOptions)
} else if(typeof source == 'function'){
watch(source, watchFunc, { immediate })
} else if(source instanceof Ref<number>){
watch(source as Ref<number>, watchFunc, { immediate })
}
const stop = ()=>{
}
return outputRef //as UseTransitionReturnType
}
// #endif

View File

@ -0,0 +1,119 @@
import { raf, cancelRaf} from '../raf'
// @ts-nocheck
export class Timeline {
state : string
animations : Set<Animation> = new Set<Animation>()
delAnimations : Animation[] = []
startTimes : Map<Animation, number> = new Map<Animation, number>()
pauseTime : number = 0
pauseStart : number = Date.now()
tickHandler : number = 0
tickHandlers : number[] = []
tick : (() => void) | null = null
constructor() {
this.state = 'Initiated';
}
start() {
if (!(this.state == 'Initiated')) return;
this.state = 'Started';
let startTime = Date.now();
this.pauseTime = 0;
this.tick = () => {
let now = Date.now();
this.animations.forEach((animation : Animation) => {
let t:number;
const ani = this.startTimes.get(animation)
if (ani == null) return
if (ani < startTime) {
t = now - startTime - animation.delay - this.pauseTime;
} else {
t = now - ani - animation.delay - this.pauseTime;
}
if (t > animation.duration) {
this.delAnimations.push(animation)
// 不能在 foreach 里面 对 集合进行删除操作
// this.animations.delete(animation);
t = animation.duration;
}
if (t > 0) animation.run(t);
})
// 不能在 foreach 里面 对 集合进行删除操作
while (this.delAnimations.length > 0) {
const animation = this.delAnimations.pop();
if (animation == null) return
this.animations.delete(animation);
}
// cancelAnimationFrame(this.tickHandler);
if (this.state != 'Started') return
this.tickHandler = raf(()=>{
this.tick!()
})
this.tickHandlers.push(this.tickHandler)
}
if(this.tick != null) {
this.tick!()
}
}
pause() {
if (!(this.state === 'Started')) return;
this.state = 'Paused';
this.pauseStart = Date.now();
cancelRaf(this.tickHandler);
// cancelRaf(this.tickHandler);
}
resume() {
if (!(this.state === 'Paused')) return;
this.state = 'Started';
this.pauseTime += Date.now() - this.pauseStart;
this.tick!();
}
reset() {
this.pause();
this.state = 'Initiated';
this.pauseTime = 0;
this.pauseStart = 0;
this.animations.clear()
this.delAnimations.clear()
this.startTimes.clear()
this.tickHandler = 0;
}
add(animation : Animation, startTime ?: number | null) {
if (startTime == null) startTime = Date.now();
this.animations.add(animation);
this.startTimes.set(animation, startTime);
}
}
export class Animation {
startValue : number
endValue : number
duration : number
timingFunction : (t : number) => number
delay : number
template : (t : number) => void
constructor(
startValue : number,
endValue : number,
duration : number,
delay : number,
timingFunction : (t : number) => number,
template : (v : number) => void) {
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
run(time : number) {
let range = this.endValue - this.startValue;
let progress = time / this.duration
if(progress != 1) progress = this.timingFunction(progress)
this.template(this.startValue + range * progress)
}
}

View File

@ -0,0 +1,123 @@
// @ts-nocheck
const TICK = Symbol('tick');
const TICK_HANDLER = Symbol('tick-handler');
const ANIMATIONS = Symbol('animations');
const START_TIMES = Symbol('start-times');
const PAUSE_START = Symbol('pause-start');
const PAUSE_TIME = Symbol('pause-time');
const _raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(cb: Function) {return setTimeout(cb, 1000/60)}
const _caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame: function(id: any) {clearTimeout(id)}
// const TICK = 'tick';
// const TICK_HANDLER = 'tick-handler';
// const ANIMATIONS = 'animations';
// const START_TIMES = 'start-times';
// const PAUSE_START = 'pause-start';
// const PAUSE_TIME = 'pause-time';
// const _raf = function(callback):number|null {return setTimeout(callback, 1000/60)}
// const _caf = function(id: number):void {clearTimeout(id)}
export class Timeline {
state: string
constructor() {
this.state = 'Initiated';
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
}
start() {
if (!(this.state === 'Initiated')) return;
this.state = 'Started';
let startTime = Date.now();
this[PAUSE_TIME] = 0;
this[TICK] = () => {
let now = Date.now();
this[ANIMATIONS].forEach((animation) => {
let t: number;
if (this[START_TIMES].get(animation) < startTime) {
t = now - startTime - animation.delay - this[PAUSE_TIME];
} else {
t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
}
if (t > animation.duration) {
this[ANIMATIONS].delete(animation);
t = animation.duration;
}
if (t > 0) animation.run(t);
})
// for (let animation of this[ANIMATIONS]) {
// let t: number;
// console.log('animation', animation)
// if (this[START_TIMES].get(animation) < startTime) {
// t = now - startTime - animation.delay - this[PAUSE_TIME];
// } else {
// t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
// }
// if (t > animation.duration) {
// this[ANIMATIONS].delete(animation);
// t = animation.duration;
// }
// if (t > 0) animation.run(t);
// }
this[TICK_HANDLER] = _raf(this[TICK]);
};
this[TICK]();
}
pause() {
if (!(this.state === 'Started')) return;
this.state = 'Paused';
this[PAUSE_START] = Date.now();
_caf(this[TICK_HANDLER]);
}
resume() {
if (!(this.state === 'Paused')) return;
this.state = 'Started';
this[PAUSE_TIME] += Date.now() - this[PAUSE_START];
this[TICK]();
}
reset() {
this.pause();
this.state = 'Initiated';
this[PAUSE_TIME] = 0;
this[PAUSE_START] = 0;
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
this[TICK_HANDLER] = null;
}
add(animation: any, startTime?: number) {
if (arguments.length < 2) startTime = Date.now();
this[ANIMATIONS].add(animation);
this[START_TIMES].set(animation, startTime);
}
}
export class Animation {
startValue: number
endValue: number
duration: number
timingFunction: (t: number) => number
delay: number
template: (t: number) => void
constructor(startValue: number, endValue: number, duration: number, delay: number, timingFunction: (t: number) => number, template: (v: number) => void) {
timingFunction = timingFunction || (v => v);
template = template || (v => v);
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
run(time: number) {
let range = this.endValue - this.startValue;
let progress = time / this.duration
if(progress != 1) progress = this.timingFunction(progress)
this.template(this.startValue + range * progress)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
// @ts-nocheck
import _areaList from './city-china.json';
export const areaList = _areaList
// #ifndef UNI-APP-X
type UTSJSONObject = Record<string, string>
// #endif
// #ifdef UNI-APP-X
type Object = UTSJSONObject
// #endif
type AreaList = {
province_list : Map<string, string>;
city_list : Map<string, string>;
county_list : Map<string, string>;
}
// type CascaderOption = {
// text : string;
// value : string;
// children ?: CascaderOption[];
// };
const makeOption = (
label : string,
value : string,
children ?: UTSJSONObject[],
) : UTSJSONObject => ({
label,
value,
children,
});
export function useCascaderAreaData() : UTSJSONObject[] {
const city = areaList['city_list'] as UTSJSONObject
const county = areaList['county_list'] as UTSJSONObject
const province = areaList['province_list'] as UTSJSONObject
const provinceMap = new Map<string, UTSJSONObject>();
Object.keys(province).forEach((code) => {
provinceMap.set(code.slice(0, 2), makeOption(`${province[code]}`, code, []));
});
const cityMap = new Map<string, UTSJSONObject>();
Object.keys(city).forEach((code) => {
const option = makeOption(`${city[code]}`, code, []);
cityMap.set(code.slice(0, 4), option);
const _province = provinceMap.get(code.slice(0, 2));
if (_province != null) {
(_province['children'] as UTSJSONObject[]).push(option)
}
});
Object.keys(county).forEach((code) => {
const _city = cityMap.get(code.slice(0, 4));
if (_city != null) {
(_city['children'] as UTSJSONObject[]).push(makeOption(`${county[code]}`, code, null));
}
});
// #ifndef APP-ANDROID || APP-IOS
return Array.from(provinceMap.values());
// #endif
// #ifdef APP-ANDROID || APP-IOS
const obj : UTSJSONObject[] = []
provinceMap.forEach((value, code) => {
obj.push(value)
})
return obj
// #endif
}

View File

@ -0,0 +1,8 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
export * from './uvue.uts'
// #endif

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// import {platform} from '../platform'
/**
* buffer转路径
* @param {Object} buffer
*/
// @ts-nocheck
export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> {
console.error('[arrayBufferToFile] 当前环境不支持')
}

View File

@ -0,0 +1,63 @@
// @ts-nocheck
import {platform} from '../platform'
/**
* buffer转路径
* @param {Object} buffer
*/
// @ts-nocheck
export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> {
return new Promise((resolve, reject) => {
// #ifdef MP
const fs = uni.getFileSystemManager()
//自定义文件名
if (!name && !format) {
reject(new Error('ERROR_NAME_PARSE'))
}
const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
let pre = platform()
const filePath = `${pre.env.USER_DATA_PATH}/${fileName}`
fs.writeFile({
filePath,
data: buffer,
success() {
resolve(filePath)
},
fail(err) {
console.error(err)
reject(err)
}
})
// #endif
// #ifdef H5
const file = new File([buffer], name, {
type: format,
});
resolve(file)
// #endif
// #ifdef APP-PLUS
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
const base64 = uni.arrayBufferToBase64(buffer)
bitmap.loadBase64Data(base64, () => {
if (!name && !format) {
reject(new Error('ERROR_NAME_PARSE'))
}
const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
const filePath = `_doc/uniapp_temp/${fileNmae}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
// #endif
})
}

View File

@ -0,0 +1,13 @@
// @ts-nocheck
// 未完成
export function base64ToArrayBuffer(base64 : string) {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
if (!format) {
new Error('ERROR_BASE64SRC_PARSE')
}
if(uni.base64ToArrayBuffer) {
return uni.base64ToArrayBuffer(bodyData)
} else {
}
}

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
export * from './uvue.uts'
// #endif

View File

@ -0,0 +1,22 @@
// @ts-nocheck
import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils'
/**
* base64转路径
* @param {Object} base64
*/
export function base64ToPath(base64: string, filename: string | null = null):Promise<string> {
return new Promise((resolve,reject) => {
processFile({
type: 'toDataURL',
path: base64,
filename,
success(res: string){
resolve(res)
},
fail(err){
reject(err)
}
} as ProcessFileOptions)
})
}

View File

@ -0,0 +1,75 @@
// @ts-nocheck
import {platform} from '../platform'
/**
* base64转路径
* @param {Object} base64
*/
export function base64ToPath(base64: string, filename?: string):Promise<string> {
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
return new Promise((resolve, reject) => {
// #ifdef MP
const fs = uni.getFileSystemManager()
//自定义文件名
if (!filename && !format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
// const time = new Date().getTime();
const name = filename || `${new Date().getTime()}.${format}`;
let pre = platform()
const filePath = `${pre.env.USER_DATA_PATH}/${name}`
fs.writeFile({
filePath,
data: base64.split(',')[1],
encoding: 'base64',
success() {
resolve(filePath)
},
fail(err) {
console.error(err)
reject(err)
}
})
// #endif
// #ifdef H5
// mime类型
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
//base64 解码
let byteString = atob(base64.split(',')[1]);
//创建缓冲数组
let arrayBuffer = new ArrayBuffer(byteString.length);
//创建视图
let intArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
intArray[i] = byteString.charCodeAt(i);
}
resolve(URL.createObjectURL(new Blob([intArray], {
type: mimeString
})))
// #endif
// #ifdef APP-PLUS
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
if (!filename && !format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
// const time = new Date().getTime();
const name = filename || `${new Date().getTime()}.${format}`;
const filePath = `_doc/uniapp_temp/${name}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
// #endif
})
}

View File

@ -0,0 +1,21 @@
/**
* camelCase PascalCase
* @param str
* @param isPascalCase PascalCase false
* @returns
*/
export function camelCase(str: string, isPascalCase: boolean = false): string {
// 将字符串分割成单词数组
let words: string[] = str.split(/[\s_-]+/);
// 将数组中的每个单词首字母大写(除了第一个单词)
let camelCased: string[] = words.map((word, index):string => {
if (index == 0 && !isPascalCase) {
return word.toLowerCase(); // 第一个单词全小写
}
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
});
// 将数组中的单词拼接成一个字符串
return camelCased.join('');
};

View File

@ -0,0 +1,67 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// #ifdef MP-ALIPAY
interface My {
SDKVersion: string
}
declare var my: My
// #endif
function compareVersion(v1:string, v2:string) {
let a1 = v1.split('.');
let a2 = v2.split('.');
const len = Math.max(a1.length, a2.length);
while (a1.length < len) {
a1.push('0');
}
while (a2.length < len) {
a2.push('0');
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(a1[i], 10);
const num2 = parseInt(a2[i], 10);
if (num1 > num2) {
return 1;
}
if (num1 < num2) {
return -1;
}
}
return 0;
}
function gte(version: string) {
let {SDKVersion} = uni.getSystemInfoSync();
// #ifdef MP-ALIPAY
SDKVersion = my.SDKVersion
// #endif
return compareVersion(SDKVersion, version) >= 0;
}
// #endif
/** 环境是否支持canvas 2d */
export function canIUseCanvas2d(): boolean {
// #ifdef MP-WEIXIN
return gte('2.9.0');
// #endif
// #ifdef MP-ALIPAY
return gte('2.7.0');
// #endif
// #ifdef MP-TOUTIAO
return gte('1.78.0');
// #endif
// #ifdef UNI-APP-X && WEB || UNI-APP-X && APP
return true;
// #endif
// #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
return false
// #endif
}

View File

@ -0,0 +1,111 @@
// @ts-nocheck
import { isString } from "../isString";
import { isNumber } from "../isNumber";
/**
*
* @param {number | string} amount -
* @returns {string}
*/
function capitalizedAmount(amount : number) : string
function capitalizedAmount(amount : string) : string
function capitalizedAmount(amount : any | null) : string {
try {
let _amountStr :string;
let _amountNum :number = 0;
// 如果输入是字符串,先将其转换为数字,并去除逗号
if (typeof amount == 'string') {
_amountNum = parseFloat((amount as string).replace(/,/g, ''));
}
if(isNumber(amount)) {
_amountNum = amount as number
}
// 判断输入是否为有效的金额 || isNaN(amount)
if (amount == null) throw new Error('不是有效的金额!');
let result = '';
// 处理负数情况
if (_amountNum < 0) {
result = '欠';
_amountNum = Math.abs(_amountNum);
}
// 金额不能超过千亿以上
if (_amountNum >= 10e11) throw new Error('计算金额过大!');
// 保留两位小数并转换为字符串
_amountStr = _amountNum.toFixed(2);
// 定义数字、单位和小数单位的映射
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const units = ['', '拾', '佰', '仟'];
const bigUnits = ['', '万', '亿'];
const decimalUnits = ['角', '分'];
// 分离整数部分和小数部分
const amountArray = _amountStr.split('.');
let integerPart = amountArray[0]; // string| number[]
const decimalPart = amountArray[1];
// 处理整数部分
if (integerPart != '0') {
let _integerPart = integerPart.split('').map((item):number => parseInt(item));
// 将整数部分按四位一级进行分组
const levels = _integerPart.reverse().reduce((prev:string[][], item, index):string[][] => {
// const level = prev?.[0]?.length < 4 ? prev[0] : [];
const level = prev.length > 0 && prev[0].length < 4 ? prev[0]: []
const value = item == 0 ? digits[item] : digits[item] + units[index % 4];
level.unshift(value);
if (level.length == 1) {
prev.unshift(level);
} else {
prev[0] = level;
}
return prev;
}, [] as string[][]);
// 将分组后的整数部分转换为中文大写形式
result += levels.reduce((prev, item, index):string => {
let _level = bigUnits[levels.length - index - 1];
let _item = item.join('').replace(/(零)\1+/g, '$1');
if (_item == '零') {
_level = '';
_item = '';
} else if (_item.endsWith('零')) {
_item = _item.slice(0, _item.length - 1);
}
return prev + _item + _level;
}, '');
} else {
result += '零';
}
// 添加元
result += '元';
// 处理小数部分
if (decimalPart != '00') {
if (result == '零元') result = '';
for (let i = 0; i < decimalPart.length; i++) {
const digit = parseInt(decimalPart.charAt(i));
if (digit != 0) {
result += digits[digit] + decimalUnits[i];
}
}
} else {
result += '整';
}
return result;
} catch (error : Error) {
return error.message;
}
};

View File

@ -0,0 +1,59 @@
## 0.2.72025-01-17
- fix: 针对canvas 平台判断优化
## 0.2.62025-01-09
- feat: 增加`areaData`中国省市区数据
## 0.2.52025-01-07
- fix: animation在app上类型问题
## 0.2.42025-01-04
- feat: getRect类型问题
## 0.2.32025-01-01
- chore: unitConvert使用uni.rpx2px
## 0.2.22024-12-11
- chore: 动画使用`requestAnimationFrame`
## 0.2.12024-11-20
- feat: 增加`characterLimit`
## 0.2.02024-11-14
- fix: vue2的类型问题
## 0.1.92024-11-14
- feat: 增加`shuffle`
## 0.1.82024-10-08
- fix: vue2 条件编译 // #ifdef APP-IOS || APP-ANDROID 会生效
## 0.1.72024-09-23
- fix: raf 类型跟随版本变更
## 0.1.62024-07-24
- fix: vue2 app ts需要明确的后缀所有补全
- chore: 减少依赖
## 0.1.52024-07-21
- feat: 删除 Hooks
- feat: 兼容uniappx
## 0.1.42023-09-05
- feat: 增加 Hooks `useIntersectionObserver`
- feat: 增加 `floatAdd`
- feat: 因为本人插件兼容 vue2 需要使用 `composition-api`故增加vue文件代码插件的条件编译
## 0.1.32023-08-13
- feat: 增加 `camelCase`
## 0.1.22023-07-17
- feat: 增加 `getClassStr`
## 0.1.12023-07-06
- feat: 增加 `isNumeric` 区别于 `isNumber`
## 0.1.02023-06-30
- fix: `clamp`忘记导出了
## 0.0.92023-06-27
- feat: 增加`arrayBufferToFile`
## 0.0.82023-06-19
- feat: 增加`createAnimation`、`clamp`
## 0.0.72023-06-08
- chore: 更新注释
## 0.0.62023-06-08
- chore: 增加`createImage`为`lime-watermark`和`lime-qrcode`提供依赖
## 0.0.52023-06-03
- chore: 更新注释
## 0.0.42023-05-22
- feat: 增加`range`,`exif`,`selectComponent`
## 0.0.32023-05-08
- feat: 增加`fillZero`,`debounce`,`throttle`,`random`
## 0.0.22023-05-05
- chore: 更新文档
## 0.0.12023-05-05
- 无

View File

@ -0,0 +1,63 @@
// @ts-nocheck
/**
*
* @param char maxcharacter条件下
* @param max
* @returns maxCharacter/maxLength maxCharacter/maxLength时返回截取之后的字符串和长度
*/
export type CharacterLengthResult = {
length : number;
characters : string;
}
// #ifdef APP-ANDROID
type ChartType = any
// #endif
// #ifndef APP-ANDROID
type ChartType = string | number
// #endif
export function characterLimit(type : string, char : ChartType, max : number) : CharacterLengthResult {
const str = `${char}`;
if (str.length == 0) {
return {
length: 0,
characters: '',
} as CharacterLengthResult
}
if (type == 'maxcharacter') {
let len = 0;
for (let i = 0; i < str.length; i += 1) {
let currentStringLength : number// = 0;
const code = str.charCodeAt(i)!
if (code > 127 || code == 94) {
currentStringLength = 2;
} else {
currentStringLength = 1;
}
if (len + currentStringLength > max) {
return {
length: len,
characters: str.slice(0, i),
} as CharacterLengthResult
}
len += currentStringLength;
}
return {
length: len,
characters: str,
} as CharacterLengthResult
} else if (type == 'maxlength') {
const length = str.length > max ? max : str.length;
return {
length: length,
characters: str.slice(0, length),
} as CharacterLengthResult
}
return {
length: str.length,
characters: str,
} as CharacterLengthResult
};

View File

@ -0,0 +1,16 @@
// @ts-nocheck
/**
*
* @param val
* @param min
* @param max
* @returns
*/
export function clamp(val: number, min: number, max: number): number {
return Math.max(min, Math.min(max, val));
}
// console.log(clamp(5 ,0, 10)); // 输出: 5在范围内不做更改
// console.log(clamp(-5 ,0, 10)); // 输出: 0小于最小值被限制为最小值
// console.log(clamp(15 ,0, 10)); // 输出: 10大于最大值被限制为最大值

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
export * from './uvue.ts'
// #endif
// #ifndef UNI-APP-X && APP
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,17 @@
// @ts-nocheck
/**
*
* @param obj
* @returns
*/
export function cloneDeep<T>(obj: any): T {
// 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
// if(['number', 'string'].includes(typeof obj) || Array.isArray(obj)){
// return obj as T
// }
if(typeof obj == 'object'){
return JSON.parse(JSON.stringify(obj as T)) as T;
}
return obj as T
}

View File

@ -0,0 +1,103 @@
// @ts-nocheck
/**
*
* @param obj
* @returns
*/
export function cloneDeep<T>(obj: any): T {
// 如果传入的对象为空,返回空
if (obj === null) {
return null as unknown as T;
}
// 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象
if (obj instanceof Set) {
return new Set([...obj]) as unknown as T;
}
// 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象
if (obj instanceof Map) {
return new Map([...obj]) as unknown as T;
}
// 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值
if (obj instanceof WeakMap) {
let weakMap = new WeakMap();
weakMap = obj;
return weakMap as unknown as T;
}
// 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值
if (obj instanceof WeakSet) {
let weakSet = new WeakSet();
weakSet = obj;
return weakSet as unknown as T;
}
// 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj) as unknown as T;
}
// 如果传入的对象是 undefined 类型,则返回 undefined
if (typeof obj === 'undefined') {
return undefined as unknown as T;
}
// 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆
if (Array.isArray(obj)) {
return obj.map(cloneDeep) as unknown as T;
}
// 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象
if (obj instanceof Date) {
return new Date(obj.getTime()) as unknown as T;
}
// 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆
if (typeof obj === 'object') {
const newObj: any = {};
for (const [key, value] of Object.entries(obj)) {
newObj[key] = cloneDeep(value);
}
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (const key of symbolKeys) {
newObj[key] = cloneDeep(obj[key]);
}
return newObj;
}
// 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
return obj;
}
// 示例使用
// // 克隆一个对象
// const obj = { name: 'John', age: 30 };
// const clonedObj = cloneDeep(obj);
// console.log(clonedObj); // 输出: { name: 'John', age: 30 }
// console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的)
// // 克隆一个数组
// const arr = [1, 2, 3];
// const clonedArr = cloneDeep(arr);
// console.log(clonedArr); // 输出: [1, 2, 3]
// console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的)
// // 克隆一个包含嵌套对象的对象
// const person = {
// name: 'Alice',
// age: 25,
// address: {
// city: 'New York',
// country: 'USA',
// },
// };
// const clonedPerson = cloneDeep(person);
// console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } }
// console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的)
// console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的)

View File

@ -0,0 +1,22 @@
// @ts-nocheck
/**
*
* @param arr
* @param target
* @returns
*/
export function closest(arr: number[], target: number):number {
return arr.reduce((pre: number, cur: number):number =>
Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur
);
}
// 示例
// // 定义一个数字数组
// const numbers = [1, 3, 5, 7, 9];
// // 在数组中找到最接近目标数字 6 的元素
// const closestNumber = closest(numbers, 6);
// console.log(closestNumber); // 输出结果: 5

View File

@ -0,0 +1,156 @@
<template>
<view id="shared" style="height: 500px; width: 300px; background-color: aqua;">
</view>
</template>
<script lang="ts">
import { getRect, getAllRect } from '@/uni_modules/lime-shared/getRect'
import { camelCase } from '@/uni_modules/lime-shared/camelCase'
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
import { clamp } from '@/uni_modules/lime-shared/clamp'
import { cloneDeep } from '@/uni_modules/lime-shared/cloneDeep'
import { closest } from '@/uni_modules/lime-shared/closest'
import { debounce } from '@/uni_modules/lime-shared/debounce'
import { fillZero } from '@/uni_modules/lime-shared/fillZero'
import { floatAdd } from '@/uni_modules/lime-shared/floatAdd'
import { floatMul } from '@/uni_modules/lime-shared/floatMul'
import { floatDiv } from '@/uni_modules/lime-shared/floatDiv'
import { floatSub } from '@/uni_modules/lime-shared/floatSub'
import { getClassStr } from '@/uni_modules/lime-shared/getClassStr'
import { getCurrentPage } from '@/uni_modules/lime-shared/getCurrentPage'
import { getStyleStr } from '@/uni_modules/lime-shared/getStyleStr'
import { hasOwn } from '@/uni_modules/lime-shared/hasOwn'
import { isBase64 } from '@/uni_modules/lime-shared/isBase64'
import { isBrowser } from '@/uni_modules/lime-shared/isBrowser'
import { isDef } from '@/uni_modules/lime-shared/isDef'
import { isEmpty } from '@/uni_modules/lime-shared/isEmpty'
import { isFunction } from '@/uni_modules/lime-shared/isFunction'
import { isNumber } from '@/uni_modules/lime-shared/isNumber'
import { isNumeric } from '@/uni_modules/lime-shared/isNumeric'
import { isObject } from '@/uni_modules/lime-shared/isObject'
import { isPromise } from '@/uni_modules/lime-shared/isPromise'
import { isString } from '@/uni_modules/lime-shared/isString'
import { kebabCase } from '@/uni_modules/lime-shared/kebabCase'
import { raf, doubleRaf } from '@/uni_modules/lime-shared/raf'
import { random } from '@/uni_modules/lime-shared/random'
import { range } from '@/uni_modules/lime-shared/range'
import { sleep } from '@/uni_modules/lime-shared/sleep'
import { throttle } from '@/uni_modules/lime-shared/throttle'
import { toArray } from '@/uni_modules/lime-shared/toArray'
import { toBoolean } from '@/uni_modules/lime-shared/toBoolean'
import { toNumber } from '@/uni_modules/lime-shared/toNumber'
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
import { getCurrentInstance } from '@/uni_modules/lime-shared/vue'
import { capitalizedAmount } from '@/uni_modules/lime-shared/capitalizedAmount'
// #ifdef VUE2
type UTSJSONObject = any
// #endif
const context = getCurrentInstance()
// getRect('#shared', context!).then(res =>{
// console.log('res', res.bottom)
// })
// getAllRect('#shared', context).then(res =>{
// console.log('res', res)
// })
// console.log('camelCase::', camelCase("hello world"));
// console.log('camelCase::', camelCase("my_name_is_john", true));
// console.log('canIUseCanvas2d::', canIUseCanvas2d());
// console.log('clamp::', clamp(5 ,0, 10));
// console.log('cloneDeep::', cloneDeep<UTSJSONObject>({a:5}));
// console.log('closest::', closest([1, 3, 5, 7, 9], 6));
// const saveData = (data: any) => {
// //
// console.log(`Saving data: ${data}`);
// }
// const debouncedSaveData = debounce(saveData, 500);
// debouncedSaveData('Data 1');
// debouncedSaveData('Data 2');
// console.log('fillZero', fillZero(1))
// console.log('floatAdd', floatAdd(0.1, 0.2), floatAdd(1.05, 0.05), floatAdd(0.1, 0.7), floatAdd(0.0001, 0.0002), floatAdd(123.456 , 789.012))
// console.log('floatMul', floatMul(0.1, 0.02), floatMul(1.0255, 100))
// console.log('floatDiv', floatDiv(10.44, 100), floatDiv(1.0255, 100), floatDiv(5.419909340994699, 0.2))
// console.log('floatSub', floatSub(0.4, 0.1), floatSub(1.0255, 100))
const now = () : number => System.nanoTime() / 1_000_000.0
console.log('capitalizedAmount', capitalizedAmount(0.4))
console.log('capitalizedAmount', capitalizedAmount(100))
console.log('capitalizedAmount', capitalizedAmount(100000000))
console.log('capitalizedAmount', capitalizedAmount('2023.04'))
console.log('capitalizedAmount', capitalizedAmount(-1024))
console.log('now', now(), Date.now())
// console.log('getClassStr', getClassStr({hover: true}))
// console.log('getStyleStr', getStyleStr({ color: 'red', fontSize: '16px', backgroundColor: '', border: null }))
// console.log('hasOwn', hasOwn({a: true}, 'key'))
// console.log('isBase64::', isBase64("SGVsbG8sIFdvcmxkIQ=="));
// console.log('isBrowser::', isBrowser);
// console.log('isDef::', isDef('6'));
// console.log('isEmpty::', isEmpty({a: true}));
// const b = () =>{}
// console.log('isFunction::', isFunction(b));
// console.log('isNumber::', isNumber('6'));
// console.log('isNumeric::', isNumeric('6'));
// console.log('isObject::', isObject({}));
// const promise = ():Promise<boolean> => {
// return new Promise((resolve) => {
// resolve(true)
// })
// }
// const a = promise()
// console.log('isPromise::', isPromise(a));
// console.log('isString::', isString('null'));
// console.log('kebabCase::', kebabCase('my love'));
// console.log('raf::', raf(()=>{
// console.log('raf:::1')
// }));
// console.log('doubleRaf::', doubleRaf(()=>{
// console.log('doubleRaf:::1')
// }));
// console.log('random', random(0, 10))
// console.log('random', random(0, 1, 2))
// console.log('range', range(0, 10, 2))
// console.log('sleep', sleep(300).then(res => {
// console.log('log')
// }))
// const handleScroll = (a: string) => {
// console.log("Scroll event handled!", a);
// }
// // // 使 handleScroll 500
// const throttledScroll = throttle(handleScroll, 500);
// throttledScroll('5');
// const page = getCurrentPage()
// console.log('getCurrentPage::', page)
// console.log('toArray', toArray<number>(5))
// console.log('toBoolean', toBoolean(5))
// console.log('toNumber', toNumber('5'))
// console.log('unitConvert', unitConvert('5'))
// uni.getImageInfo({
// src: '/static/logo.png',
// success(res) {
// console.log('res', res)
// }
// })
export default {
}
</script>
<style>
</style>

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef UNI-APP-X
export * from './type.ts'
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X
export * from './uvue.ts'
// #endif

View File

@ -0,0 +1,25 @@
export type CreateAnimationOptions = {
/**
* ms
*/
duration ?: number;
/**
*
* - linear: 动画从头到尾的速度是相同的
* - ease: 动画以低速开始
* - ease-in:
* - ease-in-out: 动画以低速开始和结束
* - ease-out: 动画以低速结束
* - step-start: 动画第一帧就跳至结束状态直到结束
* - step-end: 动画一直保持开始状态
*/
timingFunction ?: string //'linear' | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | 'step-start' | 'step-end';
/**
* ms
*/
delay ?: number;
/**
* transform-origin
*/
transformOrigin ?: string;
}

View File

@ -0,0 +1,5 @@
// @ts-nocheck
// export * from '@/uni_modules/lime-animateIt'
export function createAnimation() {
console.error('当前环境不支持,请使用lime-animateIt')
}

View File

@ -0,0 +1,148 @@
// @ts-nocheck
// nvue 需要在节点上设置ref或在export里传入
// const animation = createAnimation({
// ref: this.$refs['xxx'],
// duration: 0,
// timingFunction: 'linear'
// })
// animation.opacity(1).translate(x, y).step({duration})
// animation.export(ref)
// 抹平nvue 与 uni.createAnimation的使用差距
// 但是nvue动画太慢
import { CreateAnimationOptions } from './type'
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
type AnimationTypes = 'matrix' | 'matrix3d' | 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY' | 'scaleZ' | 'skew' | 'skewX' | 'skewY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'translateZ'
| 'opacity' | 'backgroundColor' | 'width' | 'height' | 'left' | 'right' | 'top' | 'bottom'
interface Styles {
[key : string] : any
}
interface StepConfig {
duration?: number
timingFunction?: string
delay?: number
needLayout?: boolean
transformOrigin?: string
}
interface StepAnimate {
styles?: Styles
config?: StepConfig
}
interface StepAnimates {
[key: number]: StepAnimate
}
// export interface CreateAnimationOptions extends UniApp.CreateAnimationOptions {
// ref?: string
// }
type Callback = (time: number) => void
const animateTypes1 : AnimationTypes[] = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 : AnimationTypes[] = ['opacity', 'backgroundColor']
const animateTypes3 : AnimationTypes[] = ['width', 'height', 'left', 'right', 'top', 'bottom']
class LimeAnimation {
ref : any
context : any
options : UniApp.CreateAnimationOptions
// stack : any[] = []
next : number = 0
currentStepAnimates : StepAnimates = {}
duration : number = 0
constructor(options : CreateAnimationOptions) {
const {ref} = options
this.ref = ref
this.options = options
}
addAnimate(type : AnimationTypes, args: (string | number)[]) {
let aniObj = this.currentStepAnimates[this.next]
let stepAnimate:StepAnimate = {}
if (!aniObj) {
stepAnimate = {styles: {}, config: {}}
} else {
stepAnimate = aniObj
}
if (animateTypes1.includes(type)) {
if (!stepAnimate.styles.transform) {
stepAnimate.styles.transform = ''
}
let unit = ''
if (type === 'rotate') {
unit = 'deg'
}
stepAnimate.styles.transform += `${type}(${args.map((v: number) => v + unit).join(',')}) `
} else {
stepAnimate.styles[type] = `${args.join(',')}`
}
this.currentStepAnimates[this.next] = stepAnimate
}
animateRun(styles: Styles = {}, config:StepConfig = {}, ref: any) {
const el = ref || this.ref
if (!el) return
return new Promise((resolve) => {
const time = +new Date()
nvueAnimation.transition(el, {
styles,
...config
}, () => {
resolve(+new Date() - time)
})
})
}
nextAnimate(animates: StepAnimates, step: number = 0, ref: any, cb: Callback) {
let obj = animates[step]
if (obj) {
let { styles, config } = obj
// this.duration += config.duration
this.animateRun(styles, config, ref).then((time: number) => {
step += 1
this.duration += time
this.nextAnimate(animates, step, ref, cb)
})
} else {
this.currentStepAnimates = {}
cb && cb(this.duration)
}
}
step(config:StepConfig = {}) {
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
return this
}
export(ref: any, cb?: Callback) {
ref = ref || this.ref
if(!ref) return
this.duration = 0
this.next = 0
this.nextAnimate(this.currentStepAnimates, 0, ref, cb)
return null
}
}
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
LimeAnimation.prototype[type] = function(...args: (string | number)[]) {
this.addAnimate(type, args)
return this
}
})
// #endif
export function createAnimation(options : CreateAnimationOptions) {
// #ifndef APP-NVUE
return uni.createAnimation({ ...options })
// #endif
// #ifdef APP-NVUE
return new LimeAnimation(options)
// #endif
}

View File

@ -0,0 +1,73 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
import type { ComponentInternalInstance } from '@/uni_modules/lime-shared/vue'
import { getRect } from '@/uni_modules/lime-shared/getRect'
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
export const isCanvas2d = canIUseCanvas2d()
// #endif
export function createCanvas(canvasId : string, component : ComponentInternalInstance) {
// #ifdef UNI-APP-X
uni.createCanvasContextAsync({
canvasId,
component,
success(context : CanvasContext) {
},
fail(error : UniError) {
}
})
// #endif
// #ifndef UNI-APP-X
const isCanvas2d = canIUseCanvas2d()
getRect('#' + canvasId, context, isCanvas2d).then(res => {
if (res.node) {
res.node.width = res.width
res.node.height = res.height
return res.node
} else {
const ctx = uni.createCanvasContext(canvasId, context)
if (!ctx._drawImage) {
ctx._drawImage = ctx.drawImage
ctx.drawImage = function (...args) {
const { path } = args.shift()
ctx._drawImage(path, ...args)
}
}
if (!ctx.getImageData) {
ctx.getImageData = function () {
return new Promise((resolve, reject) => {
uni.canvasGetImageData({
canvasId,
x: parseInt(arguments[0]),
y: parseInt(arguments[1]),
width: parseInt(arguments[2]),
height: parseInt(arguments[3]),
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
}, context)
})
}
return {
getContext(type: string) {
if(type == '2d') {
return ctx
}
},
width: res.width,
height: res.height,
createImage
}
}
}
})
// #endif
}

View File

@ -0,0 +1,71 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
import {isBrowser} from '../isBrowser'
class Image {
currentSrc: string | null = null
naturalHeight: number = 0
naturalWidth: number = 0
width: number = 0
height: number = 0
tagName: string = 'IMG'
path: string = ''
crossOrigin: string = ''
referrerPolicy: string = ''
onload: () => void = () => {}
onerror: () => void = () => {}
complete: boolean = false
constructor() {}
set src(src: string) {
console.log('src', src)
if(!src) {
return this.onerror()
}
src = src.replace(/^@\//,'/')
this.currentSrc = src
uni.getImageInfo({
src,
success: (res) => {
const localReg = /^\.|^\/(?=[^\/])/;
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
res.path = localReg.test(src) ? `/${res.path}` : res.path;
// #endif
this.complete = true
this.path = res.path
this.naturalWidth = this.width = res.width
this.naturalHeight = this.height = res.height
this.onload()
},
fail: () => {
this.onerror()
}
})
}
get src() {
return this.currentSrc
}
}
interface UniImage extends WechatMiniprogram.Image {
complete?: boolean
naturalHeight?: number
naturalWidth?: number
}
/** 创建用于 canvas 的 img */
export function createImage(canvas?: any): HTMLImageElement | UniImage {
if(canvas && canvas.createImage) {
return (canvas as WechatMiniprogram.Canvas).createImage()
} else if(this && this['tagName'] == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){
return new Image()
} else if(isBrowser) {
return new window.Image()
}
return new Image()
}
// #endif
// #ifdef UNI-APP-X && APP
export function createImage():Image{
// console.error('当前环境不支持')
return new Image()
}
// #endif

View File

@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
export * from './uvue.ts'
// #endif
// #ifndef UNI-APP-X && APP
export * from './vue.ts'
// #endif

View File

@ -0,0 +1,36 @@
// @ts-nocheck
/**
*
* @param fn
* @param wait
* @returns
*/
export function debounce<A extends any>(fn : (args: A)=> void, wait = 300): (args: A)=> void {
let timer = -1
return (args: A) => {
if (timer >-1) {clearTimeout(timer)};
timer = setTimeout(()=>{
fn(args)
}, wait)
}
};
// 示例
// 定义一个函数
// function saveData(data: string) {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
// const debouncedSaveData = debounce(saveData, 500);
// // 连续调用防抖函数
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"

View File

@ -0,0 +1,40 @@
// @ts-nocheck
type Timeout = ReturnType<typeof setTimeout> | null;
/**
*
* @param fn
* @param wait
* @returns
*/
export function debounce<A extends any, R>(
fn : (...args : A) => R,
wait : number = 300) : (...args : A) => void {
let timer : Timeout = null;
return function (...args : A) {
if (timer) clearTimeout(timer); // 如果上一个 setTimeout 存在,则清除它
// 设置一个新的 setTimeout在指定的等待时间后调用防抖函数
timer = setTimeout(() => {
fn.apply(this, args); // 使用提供的参数调用原始函数
}, wait);
};
};
// 示例
// 定义一个函数
// function saveData(data: string) {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
// const debouncedSaveData = debounce(saveData, 500);
// // 连续调用防抖函数
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"

View File

@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
export * from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
export * from './uvue.ts'
// #endif

View File

@ -0,0 +1,7 @@
class EXIF {
constructor(){
console.error('当前环境不支持')
}
}
export const exif = new EXIF()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
// @ts-nocheck
/**
*
* @param number
* @param length 2
* @returns
*/
export function fillZero(number: number, length: number = 2): string {
// 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度
return `${number}`.padStart(length, '0');
}

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