mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-28 05:00:41 +08:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1407b58024 | |||
| de142c4729 | |||
| 0b4090f9e9 | |||
| 3ccd4f7ccf | |||
| 6f01501fd1 | |||
| e426abf336 | |||
| a767397e23 | |||
| 17f5b4b917 | |||
| 94ff37f77f | |||
| 73bc88bad8 | |||
| 72dc278a2b | |||
| 7ba35ffd5d | |||
| 3643bdf6f6 | |||
| 21fee2dc9a | |||
| 90f6516f45 | |||
| a15621d55e | |||
| a717beb0e7 | |||
| b9ad94ddb5 | |||
| 9ab013562d | |||
| a8bd121deb | |||
| 476a1ec48e | |||
| 31ade9908a | |||
| 5606e8c59f | |||
| 61d3b22fdb | |||
| c05aa5253c | |||
| 0b599dcced | |||
| 623e3fa59a | |||
| 1075f588fa | |||
| c0d8cb2c3b | |||
| 9c6a054b12 | |||
| 6a05f664ba | |||
| 9c4d2242ae | |||
| eac027c116 | |||
| 3a08008192 | |||
| 7daf93d51f | |||
| 409be28deb | |||
| 97e358e41f | |||
| c17e668afe | |||
| 4611932ad8 | |||
| 452d021816 | |||
| d71022c224 | |||
| d521a641de | |||
| 4246d8cdcb | |||
| 6989fd8ddc | |||
| 742b51a23e | |||
| bc541a3ef6 | |||
| 67fc682164 | |||
| b43dd7e5a7 | |||
| e29ccf65c5 | |||
| 91553fc053 | |||
| 22f57b7860 | |||
| 2a25727c0b | |||
| eb69ae4af6 | |||
| 2fd83a7814 | |||
| 8963ba1921 | |||
| 051c72b584 |
+1
-5
@@ -2,8 +2,4 @@ node_modules/
|
|||||||
unpackage/
|
unpackage/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.idea
|
.idea
|
||||||
|
.trae
|
||||||
config/halo.config.js
|
|
||||||
config/love.config.js
|
|
||||||
config/token.config.js
|
|
||||||
config/plugins.config.js
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import HaloTokenConfig from '@/config/token.config.js';
|
import HaloTokenConfig from '@/config/uhalo.config.js';
|
||||||
|
|
||||||
// app升级检测(搭配:https://ext.dcloud.net.cn/plugin?id=4470 升级中心)
|
// app升级检测(搭配:https://ext.dcloud.net.cn/plugin?id=4470 升级中心)
|
||||||
import CheckAppUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
|
import CheckAppUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励,同时为了项目能够持续发展,可以根据您的喜好支持一下本项目哦,非常感谢您的支持,作者也会更有动力持续维护和更新新的功能哦~
|
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励,同时为了项目能够持续发展,可以根据您的喜好支持一下本项目哦,非常感谢您的支持,作者也会更有动力持续维护和更新新的功能哦~
|
||||||
|
|
||||||
| 支付宝 | 微信 | QQ |
|
| 支付宝 | 微信 | QQ |
|
||||||
|:----------------------------------------------:|:---------------------------------------------:|:---------------------------------------------:|
|
|:---------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
## 一、项目介绍
|
## 一、项目介绍
|
||||||
|
|
||||||
@@ -34,29 +34,20 @@
|
|||||||
- 支持特色功能,恋爱日记
|
- 支持特色功能,恋爱日记
|
||||||
- 理论支持编译为 小程序(推荐)、APP
|
- 理论支持编译为 小程序(推荐)、APP
|
||||||
|
|
||||||
|
|
||||||
### 在线预览(小莫唐尼)
|
|
||||||
|
|
||||||
微信内容扫描下方二维码,即可在线预览小程序。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### 部分截图
|
### 部分截图
|
||||||
|
|
||||||
|首页|分类|图库|
|
|首页|分类|我的|
|
||||||
|:--:|:--:|:--:|
|
|:--:|:--:|:--:|
|
||||||
||||
|
||||
|
||||||
|
|
||||||
|瞬间|我的|
|
|
||||||
|:--:|:--:|
|
|
||||||
|||
|
|
||||||
|
|
||||||
|
|
||||||
### 恋爱日记
|
### 恋爱日记
|
||||||
|
|
||||||
- 在 uni-halo 中,我们为您准备了一个恋爱日记的模块,您可以在其中记录您的恋爱故事,分享给您的朋友们,请扫示例小程序,在线体验恋爱日记的功能。
|
- 在 uni-halo 中,我们为您准备了一个恋爱日记的模块,您可以在其中记录您的恋爱故事,分享给您的朋友们,请扫示例小程序,在线体验恋爱日记的功能。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
@@ -95,8 +86,6 @@
|
|||||||
- QQ 交流群:632969367
|
- QQ 交流群:632969367
|
||||||
- 微信交流群:由于微信交流群时效性太短,所以有需要的话可以先加入QQ群,再邀请进微信群
|
- 微信交流群:由于微信交流群时效性太短,所以有需要的话可以先加入QQ群,再邀请进微信群
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
## 🎉 加入我们
|
## 🎉 加入我们
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import v2Config from './v2/all.config.js'
|
|||||||
|
|
||||||
const ApiManager = {
|
const ApiManager = {
|
||||||
v2,
|
v2,
|
||||||
v2Config,
|
v2Config
|
||||||
};
|
};
|
||||||
|
|
||||||
const install = (Vue) => {
|
const install = (Vue) => {
|
||||||
|
|||||||
+63
-112
@@ -1,47 +1,32 @@
|
|||||||
/**
|
import {
|
||||||
* 所有的接口
|
getPersonalToken
|
||||||
*/
|
} from '@/utils/token.js'
|
||||||
import HaloTokenConfig from '@/config/token.config.js'
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
import HttpHandler from '@/common/http/request.js'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getAppConfigs
|
getAppConfigs
|
||||||
} from '@/config/index.js'
|
} from '@/config/index.js'
|
||||||
|
import { getNologinEmail, getOpenid } from "@/utils/auth";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
|
||||||
* 获取文章列表
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getPostList: (params) => {
|
getPostList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts`, params)
|
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据名称获取文章
|
|
||||||
* @param {String} name 分类名称
|
|
||||||
*/
|
|
||||||
getPostByName: (name) => {
|
getPostByName: (name) => {
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts/${name}`, {}, {
|
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts/${name}`, {}, {
|
||||||
header: {
|
header: {
|
||||||
'Wechat-Session-Id': uni.getStorageSync('openid'),
|
'Wechat-Session-Id': getOpenid(),
|
||||||
|
'nologin-email': getNologinEmail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索文章
|
|
||||||
* @param {Object} params 数据
|
|
||||||
*/
|
|
||||||
getPostListByKeyword: (params) => {
|
getPostListByKeyword: (params) => {
|
||||||
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/indices/post`, params)
|
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/indices/-/search`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询分类列表
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getCategoryList: (params) => {
|
getCategoryList: (params) => {
|
||||||
const param = qs.stringify(params, {
|
const param = qs.stringify(params, {
|
||||||
allowDots: true,
|
allowDots: true,
|
||||||
@@ -52,126 +37,72 @@ export default {
|
|||||||
})
|
})
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories?${param}`, {})
|
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories?${param}`, {})
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 查询分类下的文章
|
|
||||||
* @param {String} name 分类名称
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getCategoryPostList: (name, params) => {
|
getCategoryPostList: (name, params) => {
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories/${name}/posts`, params)
|
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories/${name}/posts`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取评论列表接口(列表数据)
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostCommentList: (params) => {
|
getPostCommentList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments`, params)
|
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取回复列表
|
|
||||||
* @param {String} commentName 名称
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostCommentReplyList: (commentName, params) => {
|
getPostCommentReplyList: (commentName, params) => {
|
||||||
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, params)
|
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 提交评论
|
|
||||||
addPostComment: (data) => {
|
addPostComment: (data) => {
|
||||||
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments`, data)
|
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments`, data)
|
||||||
},
|
},
|
||||||
// 提交回复
|
|
||||||
addPostCommentReply: (commentName, data) => {
|
addPostCommentReply: (commentName, data) => {
|
||||||
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, data)
|
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, data)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取标签列表
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getTagList: (params) => {
|
getTagList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags`, params)
|
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据标签获取文章列表
|
|
||||||
* @param {String} tagName 参数
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostByTagName: (tagName, params) => {
|
getPostByTagName: (tagName, params) => {
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags/${tagName}/posts`, params)
|
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags/${tagName}/posts`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取瞬间列表
|
|
||||||
*/
|
|
||||||
getMomentList: (params) => {
|
getMomentList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/moment.halo.run/v1alpha1/moments`, params, {
|
return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments`, params, {
|
||||||
custom: {
|
custom: {
|
||||||
systemToken: HaloTokenConfig.systemToken
|
personalToken: getPersonalToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getMomentByName: (name) => {
|
||||||
|
return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments/${name}`, {}, {
|
||||||
|
custom: {
|
||||||
|
personalToken: getPersonalToken()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询站点统计信息
|
|
||||||
*/
|
|
||||||
getBlogStatistics: () => {
|
getBlogStatistics: () => {
|
||||||
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/stats/-`, {})
|
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/stats/-`, {})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取相册分组
|
|
||||||
*/
|
|
||||||
getPhotoGroupList: (params) => {
|
getPhotoGroupList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/photogroups`, params, {
|
return HttpHandler.Get(`/apis/api.photo.halo.run/v1alpha1/photogroups`, params)
|
||||||
custom: {
|
|
||||||
systemToken: HaloTokenConfig.systemToken
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据分组获取相册
|
|
||||||
*/
|
|
||||||
getPhotoListByGroupName: (params) => {
|
getPhotoListByGroupName: (params) => {
|
||||||
return HttpHandler.Get(`/apis/console.api.photo.halo.run/v1alpha1/photos`, params, {
|
return HttpHandler.Get(`/apis/api.photo.halo.run/v1alpha1/photos`, params)
|
||||||
custom: {
|
|
||||||
systemToken: HaloTokenConfig.systemToken
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取友链分组
|
|
||||||
*/
|
|
||||||
getFriendLinkGroupList: (params) => {
|
getFriendLinkGroupList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/linkgroups`, params, {
|
return HttpHandler.Get(`/apis/api.link.halo.run/v1alpha1/linkgroups`, params)
|
||||||
custom: {
|
|
||||||
systemToken: HaloTokenConfig.systemToken
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取友链
|
|
||||||
*/
|
|
||||||
getFriendLinkList: (params) => {
|
getFriendLinkList: (params) => {
|
||||||
return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/PluginLinks/links`, params)
|
return HttpHandler.Get(`/apis/api.link.halo.run/v1alpha1/links`, params)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 限制阅读校验
|
|
||||||
* @param restrictType
|
|
||||||
* @param code
|
|
||||||
* @param keyId
|
|
||||||
* @returns {HttpPromise<any>}
|
|
||||||
*/
|
|
||||||
requestRestrictReadCheck: (restrictType, code, keyId) => {
|
requestRestrictReadCheck: (restrictType, code, keyId) => {
|
||||||
const params = {
|
const params = {
|
||||||
code: code,
|
code: code,
|
||||||
@@ -182,53 +113,73 @@ export default {
|
|||||||
return HttpHandler.Post(`/apis/tools.muyin.site/v1alpha1/restrict-read/check`, params, {
|
return HttpHandler.Post(`/apis/tools.muyin.site/v1alpha1/restrict-read/check`, params, {
|
||||||
header: {
|
header: {
|
||||||
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
|
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
|
||||||
'Wechat-Session-Id': uni.getStorageSync('openid'),
|
'Wechat-Session-Id': getOpenid(),
|
||||||
|
'nologin-email': getNologinEmail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文章验证码
|
|
||||||
*/
|
|
||||||
createVerificationCode: () => {
|
createVerificationCode: () => {
|
||||||
return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/restrict-read/create`, null, {
|
return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/restrict-read/create`, null, {
|
||||||
header: {
|
header: {
|
||||||
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
|
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
|
||||||
|
'Wechat-Session-Id': getOpenid(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 提交友情链接
|
|
||||||
*/
|
|
||||||
submitLink(form) {
|
submitLink(form) {
|
||||||
return HttpHandler.Post(`/apis/linksSubmit.muyin.site/v1alpha1/submit`, form, {
|
return HttpHandler.Post(`/apis/linkssubmit.muyin.site/v1alpha1/submit`, form, {
|
||||||
header: {
|
header: {
|
||||||
'Authorization': getAppConfigs().pluginConfig.linksSubmitPlugin?.Authorization,
|
'Authorization': getAppConfigs().pluginConfig.linksSubmitPlugin?.Authorization,
|
||||||
'Wechat-Session-Id': uni.getStorageSync('openid'),
|
'Wechat-Session-Id': getOpenid(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 获取二维码信息
|
|
||||||
*/
|
|
||||||
getQRCodeInfo: (key) => {
|
getQRCodeInfo: (key) => {
|
||||||
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeInfo/${key}`,
|
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeInfo/${key}`,
|
||||||
null, {})
|
null, {})
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 获取二维码图片
|
|
||||||
*/
|
|
||||||
getQRCodeImg: (postId) => {
|
getQRCodeImg: (postId) => {
|
||||||
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeImg/${postId}`,
|
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeImg/${postId}`,
|
||||||
null, {})
|
null, {})
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 点赞
|
|
||||||
* @param {*} data ={group, plural, name}
|
|
||||||
*/
|
|
||||||
submitUpvote(data) {
|
submitUpvote(data) {
|
||||||
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/upvote`, data, {})
|
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/upvote`, data, {})
|
||||||
}
|
},
|
||||||
|
|
||||||
|
getVoteList: (params) => {
|
||||||
|
return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes`, params)
|
||||||
|
},
|
||||||
|
|
||||||
|
getVoteDetail: (name) => {
|
||||||
|
return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/detail`, {})
|
||||||
|
},
|
||||||
|
|
||||||
|
getVoteUserList: (name) => {
|
||||||
|
return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/user-list`, {})
|
||||||
|
},
|
||||||
|
|
||||||
|
submitVote: (name, data, canAnonymously = true) => {
|
||||||
|
return HttpHandler.Post(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/submit`, data, {
|
||||||
|
custom: {
|
||||||
|
personalToken: canAnonymously ? undefined : getPersonalToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
checkPluginAvailable: (name) => {
|
||||||
|
return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, {})
|
||||||
|
},
|
||||||
|
|
||||||
|
getDoubanDetail: (url) => {
|
||||||
|
return HttpHandler.Get(`/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail`, { url })
|
||||||
|
},
|
||||||
|
|
||||||
|
postTrackersCounter: (data) => {
|
||||||
|
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/counter`, data)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Halo 数据看板插件
|
||||||
|
* @link https://www.halo.run/store/apps/app-rtnbbgfk
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
getPersonalToken
|
||||||
|
} from '@/utils/token.js'
|
||||||
|
import HttpHandler from '@/common/http/request.js'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAppConfigs
|
||||||
|
} from '@/config/index.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 获取图表统计数据
|
||||||
|
* @description - 标签、分类、文章发布趋势、评论活跃用户、获取热门文章top10
|
||||||
|
*/
|
||||||
|
getChartData: () => {
|
||||||
|
return HttpHandler.Get(`/apis/api.data.statistics.xhhao.com/v1alpha1/chart/data`,{})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取Github配置信息
|
||||||
|
*/
|
||||||
|
getGithubConfig:()=>{
|
||||||
|
return HttpHandler.Get('/apis/api.data.statistics.xhhao.com/v1alpha1/github/config',{})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取 Uptime Kuma 状态页面数据
|
||||||
|
*/
|
||||||
|
getUptimeStatus:()=>{
|
||||||
|
return HttpHandler.Get('/apis/api.data.statistics.xhhao.com/v1alpha1/github/config',{})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
* 修改时间:
|
* 修改时间:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import HaloTokenConfig from '@/config/token.config.js'
|
import HaloTokenConfig from '@/config/uhalo.config.js'
|
||||||
import {
|
import {
|
||||||
setInterceptors
|
setInterceptors
|
||||||
} from "./interceptors.js";
|
} from "./interceptors.js";
|
||||||
@@ -21,7 +21,7 @@ const http = new Request()
|
|||||||
http.setConfig((config) => {
|
http.setConfig((config) => {
|
||||||
|
|
||||||
// 如果是在外部浏览器调试或者编译为h5,请注释该行代码
|
// 如果是在外部浏览器调试或者编译为h5,请注释该行代码
|
||||||
config.baseURL = HaloTokenConfig.BASE_API;
|
config.baseURL = HaloTokenConfig.BASE_API.replace(/\/+$/, "");
|
||||||
|
|
||||||
config.header = {
|
config.header = {
|
||||||
...config.header,
|
...config.header,
|
||||||
|
|||||||
+14
-30
@@ -9,13 +9,7 @@
|
|||||||
* 修改人员:
|
* 修改人员:
|
||||||
* 修改时间:
|
* 修改时间:
|
||||||
*/
|
*/
|
||||||
import {
|
import {delCache, setCache} from "@/utils/storage";
|
||||||
getAdminAccessToken
|
|
||||||
} from "@/utils/auth.js";
|
|
||||||
import {
|
|
||||||
delCache,
|
|
||||||
setCache
|
|
||||||
} from "@/utils/storage";
|
|
||||||
|
|
||||||
const getCategoryNameByUrl = (url) => {
|
const getCategoryNameByUrl = (url) => {
|
||||||
const reg = '(?<=/api/content/categories/).+(?=/posts)'
|
const reg = '(?<=/api/content/categories/).+(?=/posts)'
|
||||||
@@ -37,9 +31,7 @@ const showCategoryInputPasswordModal = (response, category) => {
|
|||||||
// TODO 这里如果没有输入密码点击确认应该阻止窗口关闭,但是没找到方法
|
// TODO 这里如果没有输入密码点击确认应该阻止窗口关闭,但是没找到方法
|
||||||
if (!res.content) {
|
if (!res.content) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '提示:请输入密码',
|
title: '提示:请输入密码', icon: 'none', success: () => {
|
||||||
icon: 'none',
|
|
||||||
success: () => {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showCategoryInputPasswordModal(response, category);
|
showCategoryInputPasswordModal(response, category);
|
||||||
}, 800)
|
}, 800)
|
||||||
@@ -56,14 +48,14 @@ const showCategoryInputPasswordModal = (response, category) => {
|
|||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: '/pages/tabbar/category/category'
|
url: '/pages/tabbar/category/category'
|
||||||
});
|
});
|
||||||
} else if (res.cancel) {}
|
} else if (res.cancel) {
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setInterceptors = (http) => {
|
export const setInterceptors = (http) => {
|
||||||
http.interceptors.request.use(
|
http.interceptors.request.use((config) => {
|
||||||
(config) => {
|
|
||||||
console.log("config", config)
|
console.log("config", config)
|
||||||
|
|
||||||
// 可使用async await 做异步操作
|
// 可使用async await 做异步操作
|
||||||
@@ -71,19 +63,16 @@ export const setInterceptors = (http) => {
|
|||||||
...config.header
|
...config.header
|
||||||
// ... 可以直接加参数
|
// ... 可以直接加参数
|
||||||
};
|
};
|
||||||
if (config.custom.systemToken) {
|
if (config.custom.personalToken) {
|
||||||
config.header['Authorization'] = `Bearer ${config.custom.systemToken}`
|
config.header['Authorization'] = `Bearer ${config.custom.personalToken}`
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
}, (config) => {
|
||||||
(config) => {
|
|
||||||
// 可使用async await 做异步操作
|
// 可使用async await 做异步操作
|
||||||
return Promise.reject(config);
|
return Promise.reject(config);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
http.interceptors.response.use(
|
http.interceptors.response.use((response) => {
|
||||||
(response) => {
|
|
||||||
/* 对响应成功做点什么 可使用async await 做异步操作*/
|
/* 对响应成功做点什么 可使用async await 做异步操作*/
|
||||||
// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
|
// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
|
||||||
// return Promise.reject(response) // return Promise.reject 可使promise状态进入catch
|
// return Promise.reject(response) // return Promise.reject 可使promise状态进入catch
|
||||||
@@ -96,13 +85,11 @@ export const setInterceptors = (http) => {
|
|||||||
} else {
|
} else {
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
}
|
}
|
||||||
},
|
}, (response) => {
|
||||||
(response) => {
|
|
||||||
/* 对响应错误做点什么 (statusCode !== 200)*/
|
/* 对响应错误做点什么 (statusCode !== 200)*/
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
status: 500,
|
status: 500, message: 'API接口服务异常!'
|
||||||
message: 'API接口服务异常!'
|
|
||||||
})
|
})
|
||||||
} else if (response.data.status == 401) {
|
} else if (response.data.status == 401) {
|
||||||
uni.$tm.toast(response.data.message);
|
uni.$tm.toast(response.data.message);
|
||||||
@@ -111,9 +98,7 @@ export const setInterceptors = (http) => {
|
|||||||
const category = getCategoryNameByUrl(response.config.url)
|
const category = getCategoryNameByUrl(response.config.url)
|
||||||
delCache('APP_CATEGORY_PWD_' + category);
|
delCache('APP_CATEGORY_PWD_' + category);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '提示:密码不正确',
|
title: '提示:密码不正确', icon: 'none', success: () => {
|
||||||
icon: 'none',
|
|
||||||
success: () => {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showCategoryInputPasswordModal(response, category);
|
showCategoryInputPasswordModal(response, category);
|
||||||
}, 800)
|
}, 800)
|
||||||
@@ -152,6 +137,5 @@ export const setInterceptors = (http) => {
|
|||||||
return Promise.reject(response.data);
|
return Promise.reject(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 可以以页面为单位来写,
|
|
||||||
* 比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
|
|
||||||
* */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
app: {
|
|
||||||
name: "alley studio",
|
|
||||||
author: "Evan Mo",
|
|
||||||
},
|
|
||||||
tabbar: {
|
|
||||||
home: "Home",
|
|
||||||
moments: "Moments",
|
|
||||||
publish: "Publish",
|
|
||||||
mall: "Mall",
|
|
||||||
mine: "Mine",
|
|
||||||
},
|
|
||||||
// 提示文本
|
|
||||||
tips: {
|
|
||||||
switchLang: "switch Lang",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 可以以页面为单位来写,
|
|
||||||
* 比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
|
|
||||||
* */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
app: {
|
|
||||||
name: "巷子工坊",
|
|
||||||
author: "小莫唐尼",
|
|
||||||
},
|
|
||||||
tabbar: {
|
|
||||||
home: "首页",
|
|
||||||
moments: "动态",
|
|
||||||
publish: "发布",
|
|
||||||
mall: "商店",
|
|
||||||
mine: "我的",
|
|
||||||
},
|
|
||||||
// 提示文本
|
|
||||||
tips: {
|
|
||||||
switchLang: "切换语言",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {getAppConfigs} from '@/config/index.js'
|
import {getAppConfigs} from '@/config/index.js'
|
||||||
import HaloTokenConfig from '@/config/token.config.js'
|
import HaloTokenConfig from '@/config/uhalo.config.js'
|
||||||
import utils from '@/utils/index.js'
|
import utils from '@/utils/index.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 功能:插件检查
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
NeedPluginIds,
|
||||||
|
NeedPlugins,
|
||||||
|
checkNeedPluginAvailable
|
||||||
|
} from "@/utils/plugin.js"
|
||||||
|
|
||||||
|
const HaloPluginAvailableMixin = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
NeedPluginIds,
|
||||||
|
NeedPlugins,
|
||||||
|
uniHaloPluginAvailableError: "",
|
||||||
|
uniHaloPluginAvailable: true,
|
||||||
|
uniHaloPluginPageClass: "",
|
||||||
|
uniHaloPluginId: "", // 当前需要的插件
|
||||||
|
uniHaloPluginInfo: "" // 当前插件信息
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
uniHaloPluginAvailable: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
if (val) {
|
||||||
|
this.uniHaloPluginPageClass = ""
|
||||||
|
} else {
|
||||||
|
this.uniHaloPluginPageClass = "box-border items-center justify-center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 设置插件ID */
|
||||||
|
setPluginId(pluginId) {
|
||||||
|
this.uniHaloPluginId = pluginId
|
||||||
|
this.uniHaloPluginInfo = NeedPlugins.get(pluginId)
|
||||||
|
},
|
||||||
|
/** 检查插件状态 */
|
||||||
|
async checkPluginAvailable(pluginId) {
|
||||||
|
pluginId = pluginId ?? this.uniHaloPluginId
|
||||||
|
if (!pluginId) return false;
|
||||||
|
const available = await checkNeedPluginAvailable(pluginId)
|
||||||
|
this.uniHaloPluginAvailable = available
|
||||||
|
return available
|
||||||
|
},
|
||||||
|
/** 设置错误信息 */
|
||||||
|
setPluginError(text) {
|
||||||
|
this.uniHaloPluginAvailableError = text
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HaloPluginAvailableMixin;
|
||||||
@@ -172,3 +172,178 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 文本省略样式 结束 */
|
/* 文本省略样式 结束 */
|
||||||
|
|
||||||
|
.box-border{
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义尺寸变量(单位:rpx)
|
||||||
|
$spacing-sizes: (
|
||||||
|
0: 0,
|
||||||
|
2: 4rpx,
|
||||||
|
4: 8rpx,
|
||||||
|
8: 16rpx,
|
||||||
|
12: 24rpx,
|
||||||
|
24: 48rpx,
|
||||||
|
48: 96rpx
|
||||||
|
);
|
||||||
|
|
||||||
|
// 内边距类
|
||||||
|
@each $name, $size in $spacing-sizes {
|
||||||
|
// 全方向内边距:p-{size}
|
||||||
|
.uh-p-#{$name} {
|
||||||
|
padding: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水平方向内边距:px-{size}
|
||||||
|
.uh-px-#{$name} {
|
||||||
|
padding-left: $size !important;
|
||||||
|
padding-right: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直方向内边距:py-{size}
|
||||||
|
.uh-py-#{$name} {
|
||||||
|
padding-top: $size !important;
|
||||||
|
padding-bottom: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上内边距:pt-{size}
|
||||||
|
.uh-pt-#{$name} {
|
||||||
|
padding-top: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右内边距:pr-{size}
|
||||||
|
.uh-pr-#{$name} {
|
||||||
|
padding-right: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下内边距:pb-{size}
|
||||||
|
.uh-pb-#{$name} {
|
||||||
|
padding-bottom: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左内边距:pl-{size}
|
||||||
|
.uh-pl-#{$name} {
|
||||||
|
padding-left: $size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//外边距工具类
|
||||||
|
@each $name, $size in $spacing-sizes {
|
||||||
|
// 全方向外边距:m-{size}
|
||||||
|
.uh-m-#{$name} {
|
||||||
|
margin: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水平方向外边距:mx-{size}
|
||||||
|
.uh-mx-#{$name} {
|
||||||
|
margin-left: $size !important;
|
||||||
|
margin-right: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直方向外边距:my-{size}
|
||||||
|
.uh-my-#{$name} {
|
||||||
|
margin-top: $size !important;
|
||||||
|
margin-bottom: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上外边距:mt-{size}
|
||||||
|
.uh-mt-#{$name} {
|
||||||
|
margin-top: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右外边距:mr-{size}
|
||||||
|
.uh-mr-#{$name} {
|
||||||
|
margin-right: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下外边距:mb-{size}
|
||||||
|
.uh-mb-#{$name} {
|
||||||
|
margin-bottom: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左外边距:ml-{size}
|
||||||
|
.uh-ml-#{$name} {
|
||||||
|
margin-left: $size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gap 类
|
||||||
|
@each $name, $size in $spacing-sizes {
|
||||||
|
// 全方向gap:gap-{size}
|
||||||
|
.uh-gap-#{$name} {
|
||||||
|
gap: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水平方向gap:gap-x-{size}
|
||||||
|
.uh-gap-x-#{$name} {
|
||||||
|
column-gap: $size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直方向gap:gap-y-{size}
|
||||||
|
.uh-gap-y-#{$name} {
|
||||||
|
row-gap: $size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-shrink-0 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-start {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-break {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 24rpx;
|
margin: 0 24rpx;
|
||||||
padding: 24rpx;
|
padding:22rpx 24rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
background-color: #ffff;
|
background-color: #ffff;
|
||||||
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
<template>
|
||||||
|
<view class="card" :class="[loading]">
|
||||||
|
<view v-if="loading !== 'success'" class="card-error" @click="fnGetData()">
|
||||||
|
{{ loadingText }}
|
||||||
|
</view>
|
||||||
|
<template v-else>
|
||||||
|
<view class="tag">豆瓣</view>
|
||||||
|
<view class="flex w-full">
|
||||||
|
<view v-if="posterEmpty" class="poster round-2">无封面</view>
|
||||||
|
<image v-else class="poster round-2" :src="poster" mode="aspectFill" @error="onPosterError"></image>
|
||||||
|
<view class="box">
|
||||||
|
<view class="title text-overflow">{{ detail.spec.name }}</view>
|
||||||
|
<view class="flex" style="align-items: center; margin-top: 12rpx">
|
||||||
|
<text class="text-size-s">评分:</text>
|
||||||
|
<tm-rate v-model="detail.spec.score / 2" color="orange" size="24" :margin="2" :num="5"></tm-rate>
|
||||||
|
<text class="text-size-s" style="margin-left: 4rpx">{{ detail.spec.score }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content text-overflow-2">{{ detail.spec.cardSubtitle }}</view>
|
||||||
|
<view class="flex flex-wrap" style="margin-left: -10rpx">
|
||||||
|
<tm-tags color="orange" :shadow="0" size="s" model="fill">{{ types[detail.spec.type] }}</tm-tags>
|
||||||
|
<tm-tags v-for="(gen, genIndex) in detail.spec.genres" :key="genIndex" color="light-blue" :shadow="0" size="s" model="fill">{{ gen }}</tm-tags>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 扩展内容 -->
|
||||||
|
<view class="btn-group">
|
||||||
|
<tm-button theme="bg-gradient-orange-accent" icon="icon-copy" :shadow="0" :dense="true" size="m" @click="copy('post')">原文地址</tm-button>
|
||||||
|
<tm-button theme="bg-gradient-light-blue-accent" icon="icon-copy" :shadow="0" :dense="true" size="m" @click="copy('douban')">豆瓣地址</tm-button>
|
||||||
|
<tm-button theme="bg-gradient-light-blue-accent" icon="icon-copy" :shadow="0" :dense="true" size="m" @click="copy('info')">资源信息</tm-button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
import tmRate from '@/tm-vuetify/components/tm-rate/tm-rate.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleDouban',
|
||||||
|
components: {
|
||||||
|
tmButton,
|
||||||
|
tmTags,
|
||||||
|
tmRate
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
article: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: 'loading',
|
||||||
|
loadingText: '加载中,请稍等...',
|
||||||
|
detail: null,
|
||||||
|
types: {
|
||||||
|
movie: '电影',
|
||||||
|
book: '图书',
|
||||||
|
music: '音乐',
|
||||||
|
game: '游戏',
|
||||||
|
drama: '舞台剧'
|
||||||
|
},
|
||||||
|
poster: '',
|
||||||
|
posterEmpty: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onPosterError() {
|
||||||
|
if (!this.article.spec.cover) {
|
||||||
|
this.poster = '';
|
||||||
|
this.posterEmpty = true;
|
||||||
|
} else {
|
||||||
|
this.poster = this.$utils.checkImageUrl(this.article.spec.cover);
|
||||||
|
this.posterEmpty = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fnGetData() {
|
||||||
|
this.loadingText = '加载中,请稍等...';
|
||||||
|
this.loading = 'loading';
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getDoubanDetail(this.url)
|
||||||
|
.then((res) => {
|
||||||
|
this.detail = res;
|
||||||
|
this.poster = res.spec.poster;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = 'success';
|
||||||
|
}, 200);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.loading = 'error';
|
||||||
|
this.loadingText = '豆瓣内容加载失败,点击重试';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showToast(content) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: content,
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
copy(type) {
|
||||||
|
if (type === 'post') {
|
||||||
|
const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? '');
|
||||||
|
this.$utils.copyText(articleUrl, '文章原文地址复制成功');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === 'douban') {
|
||||||
|
this.$utils.copyText(this.detail?.spec.link, '豆瓣资源地址复制成功');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === 'info') {
|
||||||
|
const content = `名称:${this.detail?.spec.name}丨其他:${this.detail?.spec.cardSubtitle}丨标签:${this.detail?.spec.genres.join('/')}丨时间:${
|
||||||
|
this.detail?.spec.pubdate
|
||||||
|
}丨评分:${this.detail?.spec.score}分丨链接:${this.detail?.spec.link}`;
|
||||||
|
this.$utils.copyText(content, '资源信息复制成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-50 {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #ffff;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
padding: 0;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: #e88080;
|
||||||
|
color: #e88080;
|
||||||
|
background-color: rgba(232, 128, 128, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
padding: 0;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: rgba(3, 174, 252, 1);
|
||||||
|
color: rgba(3, 174, 252, 1);
|
||||||
|
background-color: rgba(3, 174, 252, 0.075);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-error {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 50rpx 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.poster {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 180rpx;
|
||||||
|
height: 220rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: #eee;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
flex-grow: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 26rpx;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 2rpx 12rpx;
|
||||||
|
background-color: #f5c618;
|
||||||
|
border-radius: 0 6rpx 0 12rpx;
|
||||||
|
}
|
||||||
|
.btn-group {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 22rpx;
|
||||||
|
gap: 0 22rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="article-min-card" :class="[globalAppSettings.layout.cardType]" @click="fnClickEvent('card')">
|
<view class="article-min-card" @click="fnClickEvent('card')">
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<cache-image class="thumbnail" radius="6rpx" :url="$utils.checkThumbnailUrl(article.thumbnail)" :fileMd5="$utils.checkThumbnailUrl(article.thumbnail)" mode="aspectFill"></cache-image>
|
<cache-image class="thumbnail" radius="6rpx" :url="$utils.checkThumbnailUrl(article.spec.cover)" :fileMd5="article.spec.cover"
|
||||||
|
mode="aspectFill"></cache-image>
|
||||||
</view>
|
</view>
|
||||||
<view class="right">
|
<view class="right">
|
||||||
<view class="title text-overflow">{{ article.title }}</view>
|
<view class="title text-overflow">{{ article.spec.title }}</view>
|
||||||
<view class="content text-overflow">{{ article.summary }}</view>
|
<view class="content text-overflow">{{ article.status.excerpt }}</view>
|
||||||
<view class="foot">
|
<view class="foot">
|
||||||
<view class="create-time">
|
<view class="create-time">
|
||||||
<!-- <text class="icon iconfont icon-clock"></text> -->
|
|
||||||
<text class="time-label">发布时间:</text>
|
<text class="time-label">发布时间:</text>
|
||||||
{{ { d: article.createTime, f: 'yyyy-MM-dd' } | formatTime }}
|
{{ {d: article.spec.publishTime, f: 'yyyy-MM-dd'} | formatTime }}
|
||||||
</view>
|
</view>
|
||||||
<view class="visits">
|
<view class="visits">
|
||||||
<!-- <text class="icon iconfont icon-eye"></text> -->
|
|
||||||
浏览
|
浏览
|
||||||
<text class="number">{{ article.visits }}</text>
|
<text class="number">{{ article.stats.visit }}</text>
|
||||||
次
|
次
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -29,7 +28,8 @@
|
|||||||
props: {
|
props: {
|
||||||
article: {
|
article: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -52,7 +52,8 @@
|
|||||||
padding: 16rpx;
|
padding: 16rpx;
|
||||||
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
||||||
|
|
||||||
&.lr_image_text {}
|
&.lr_image_text {
|
||||||
|
}
|
||||||
|
|
||||||
&.lr_text_image {
|
&.lr_text_image {
|
||||||
.left {
|
.left {
|
||||||
@@ -217,4 +218,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,569 @@
|
|||||||
|
<template>
|
||||||
|
<view class="vote-card" :class="[loading]">
|
||||||
|
<view v-if="loading !=='success'" class="vote-error" @click="fnGetData()">
|
||||||
|
{{loadingText}}
|
||||||
|
</view>
|
||||||
|
<template v-else>
|
||||||
|
<view class="vote-card-head flex flex-col">
|
||||||
|
<view class="flex justify-between">
|
||||||
|
<view class="flex">
|
||||||
|
<tm-tags color="orange" style="min-width:40rpx;" :shadow="0" rounded size="s"
|
||||||
|
model="fill">{{ index + 1 }}</tm-tags>
|
||||||
|
<tm-tags color="light-blue" :shadow="0" rounded size="s"
|
||||||
|
model="fill">{{ vote.spec._uh_type }}</tm-tags>
|
||||||
|
<tm-tags :color="vote.spec._uh_state.color" size="s" rounded :shadow="0"
|
||||||
|
model="fill">{{vote.spec._uh_state.state}}</tm-tags>
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink">
|
||||||
|
<tm-button theme="light-blue" :shadow="0" dense size="s"
|
||||||
|
@click="handleToVoteDetail(vote)">查看投票详情</tm-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="title">
|
||||||
|
{{ vote.spec.title }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="vote-card-body">
|
||||||
|
|
||||||
|
<view v-if="vote.spec.remark" class="remark text-size-s">
|
||||||
|
{{vote.spec.remark}}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 单选 -->
|
||||||
|
<view v-if="vote.spec.type==='single'" class="single">
|
||||||
|
<view class="w-full flex flex-col uh-gap-8">
|
||||||
|
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
||||||
|
'--percent': option._uh_percent + '%'
|
||||||
|
}">
|
||||||
|
<view class="is-voted-item-content flex w-full flex-between uh-gap-4">
|
||||||
|
<view class="text-align-left flex-1 text-break">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink ">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="vote-select-option flex-1 w-full text-break"
|
||||||
|
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
||||||
|
@click="handleSelectSingleOption(option)">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 多选 -->
|
||||||
|
<view v-else-if="vote.spec.type==='multiple'" class="multiple">
|
||||||
|
<view class="w-full flex flex-col uh-gap-8">
|
||||||
|
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
||||||
|
'--percent': option._uh_percent + '%'
|
||||||
|
}">
|
||||||
|
<view class="is-voted-item-content flex w-full flex-between uh-gap-4">
|
||||||
|
<view class="text-align-left flex-1 text-break">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink ">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="vote-select-option flex-1 w-full text-break"
|
||||||
|
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
||||||
|
@click="handleSelectCheckboxOption(option)">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- PK -->
|
||||||
|
<view v-else-if="vote.spec.type==='pk'" class="pk">
|
||||||
|
<view class="pk-container">
|
||||||
|
<view class="radio-item" v-for="(option,optionIndex) in vote.spec.options"
|
||||||
|
:key="optionIndex" :class="[optionIndex==0?'radio-left':'radio-right']"
|
||||||
|
:style="{width:option._uh_percent + '%'}">
|
||||||
|
<view class="option-item"
|
||||||
|
:class="[optionIndex==0?'option-item-left':'option-item-right']">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="option-foot w-full flex flex-between uh-mt-12">
|
||||||
|
<view class="w-full flex flex-col uh-gap-8">
|
||||||
|
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
||||||
|
'--percent': option._uh_percent + '%'
|
||||||
|
}">
|
||||||
|
<view class="is-voted-item-content flex w-full flex-between uh-gap-4">
|
||||||
|
<view class="text-align-left flex-1 text-break">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink ">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="vote-select-option flex-1 w-full text-break"
|
||||||
|
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
||||||
|
@click="handleSelectSingleOption(option)">
|
||||||
|
选项{{ optionIndex+1}}:{{option.title }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<view class="vote-card-foot flex flex-between">
|
||||||
|
<view class="left flex">
|
||||||
|
<tm-tags v-if="vote.spec.timeLimit==='permanent'" color="grey-darken-2" rounded size="s"
|
||||||
|
model="text">结束:永久有效 </tm-tags>
|
||||||
|
<tm-tags v-else color="grey-darken-2" rounded size="s" model="text">
|
||||||
|
<template
|
||||||
|
v-if="vote.spec._state=='未开始'">开始:{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
||||||
|
</template>
|
||||||
|
<template v-else>结束:{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
||||||
|
</template>
|
||||||
|
</tm-tags>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="right flex flex-end">
|
||||||
|
<tm-tags color="grey-darken-2" rounded size="s" model="text">{{ vote.stats.voteCount }}
|
||||||
|
人已参与</tm-tags>
|
||||||
|
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="submitForm.voteData.length!==0" class="box-border uh-mt-12 w-full uh-px-2">
|
||||||
|
<tm-button v-if="fnCalcIsVoted()" theme="white" text :block="true" class="w-full">您已参与投票</tm-button>
|
||||||
|
<tm-button v-else-if="vote.spec._uh_state.state==='未开始'" theme="orange" text class="w-full" :height="72"
|
||||||
|
:block="true" @click="handleSubmitTip('投票未开始')">投票未开始</tm-button>
|
||||||
|
<tm-button v-else-if="vote.spec._uh_state.state==='已结束'" theme="red" text class="w-full" :height="72"
|
||||||
|
:block="true" @click="handleSubmitTip('投票已结束')">投票已结束</tm-button>
|
||||||
|
<tm-button v-else-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" :height="72"
|
||||||
|
text :block="true" @click="handleSubmit()">不支持匿名投票</tm-button>
|
||||||
|
<tm-button v-else-if="submitForm.voteData.length===0" theme="white" text class="w-full" :height="72"
|
||||||
|
:block="true" @click="handleSubmitTip('请选择选项')">提交投票(请选择选项)</tm-button>
|
||||||
|
<tm-button v-else theme="light-blue" class="w-full" :height="72" :block="true" :loading="submitLoading"
|
||||||
|
:disabled="submitLoading" @click="handleSubmit()">提交投票</tm-button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
import {
|
||||||
|
VOTE_TYPES,
|
||||||
|
calcVoteState,
|
||||||
|
calcVotePercent,
|
||||||
|
voteCacheUtil
|
||||||
|
} from '@/utils/vote.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ArticleVote",
|
||||||
|
components: {
|
||||||
|
tmButton,
|
||||||
|
tmTags
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
voteId: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
article: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: "loading",
|
||||||
|
loadingText: "加载中,请稍等...",
|
||||||
|
detail: null,
|
||||||
|
vote: null,
|
||||||
|
submitForm: {
|
||||||
|
voteData: []
|
||||||
|
},
|
||||||
|
submitLoading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fnGetData() {
|
||||||
|
this.loadingText = "加载中,请稍等...";
|
||||||
|
this.loading = "loading";
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getVoteDetail(this.voteId)
|
||||||
|
.then(res => {
|
||||||
|
this.pageTitle = "投票详情" + `(${VOTE_TYPES[res.vote.spec.type]})`
|
||||||
|
|
||||||
|
const tempVoteRes = res;
|
||||||
|
|
||||||
|
tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
|
||||||
|
tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
|
||||||
|
tempVoteRes.vote.spec._uh_state = calcVoteState(tempVoteRes.vote)
|
||||||
|
tempVoteRes.vote.spec._uh_type = VOTE_TYPES[tempVoteRes.vote.spec.type]
|
||||||
|
|
||||||
|
tempVoteRes.vote.spec.options.map((option, index) => {
|
||||||
|
option.value = option.id
|
||||||
|
option.label = option.title
|
||||||
|
option.isVoted = this.fnCalcIsVoted()
|
||||||
|
option.checked = this.fnCalcIsChecked(option)
|
||||||
|
option._uh_percent = calcVotePercent(tempVoteRes.vote, option);
|
||||||
|
option.dataStr = JSON.stringify(option)
|
||||||
|
|
||||||
|
return option
|
||||||
|
})
|
||||||
|
|
||||||
|
this.vote = tempVoteRes.vote
|
||||||
|
this.detail = tempVoteRes;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = 'success';
|
||||||
|
}, 200);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.loading = 'error';
|
||||||
|
this.loadingText = "投票内容加载失败,点击重试"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
fnCalcIsVoted() {
|
||||||
|
return voteCacheUtil.has(this.voteId)
|
||||||
|
},
|
||||||
|
fnCalcIsChecked(option) {
|
||||||
|
const data = voteCacheUtil.get(this.voteId)
|
||||||
|
if (!data) return false;
|
||||||
|
const checked = data.selected.includes(option.id)
|
||||||
|
return checked
|
||||||
|
},
|
||||||
|
formatJsonStr(jsonStr) {
|
||||||
|
return jsonStr ? JSON.parse(jsonStr) : {}
|
||||||
|
},
|
||||||
|
handleSubmitTip(text) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: text
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
if (!this.vote.spec.canAnonymously) {
|
||||||
|
uni.showModal({
|
||||||
|
icon: "none",
|
||||||
|
title: "提示",
|
||||||
|
content: "该投票不支持匿名,请到博主的 网站端 进行投票!",
|
||||||
|
cancelColor: "#666666",
|
||||||
|
cancelText: "关闭",
|
||||||
|
confirmText: "复制地址",
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
console.log("this.article", this.article)
|
||||||
|
const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? "")
|
||||||
|
this.$utils.copyText(articleUrl, "原文地址复制成功")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showLoading({
|
||||||
|
title: "正在保存..."
|
||||||
|
})
|
||||||
|
this.submitLoading = true
|
||||||
|
this.$httpApi.v2
|
||||||
|
.submitVote(this.voteId, this.submitForm, this.vote.spec.canAnonymously)
|
||||||
|
.then(res => {
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: "提交成功"
|
||||||
|
})
|
||||||
|
|
||||||
|
voteCacheUtil.set(this.voteId, {
|
||||||
|
selected: [...this.submitForm.voteData],
|
||||||
|
data: this.vote
|
||||||
|
})
|
||||||
|
|
||||||
|
this.fnGetData()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: "提交失败,请重试"
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.submitLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSelectSingleOption(option) {
|
||||||
|
if (this.vote.spec._uh_state.state == '未开始') {
|
||||||
|
this.showToast(`投票未开始`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.vote.spec.hasEnded) return
|
||||||
|
if (this.vote.spec.disabled) return
|
||||||
|
this.vote.spec.options.map(item => {
|
||||||
|
if (option.id == item.id) {
|
||||||
|
item.checked = true
|
||||||
|
} else {
|
||||||
|
item.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSelectCheckboxOption(option) {
|
||||||
|
if (this.vote.spec._uh_state.state == '未开始') {
|
||||||
|
this.showToast(`投票未开始`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.vote.spec.hasEnded) return
|
||||||
|
if (this.vote.spec.disabled) return
|
||||||
|
|
||||||
|
const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id)
|
||||||
|
|
||||||
|
if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) {
|
||||||
|
this.showToast(`最多选择 ${this.vote.spec.maxVotes} 项`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vote.spec.options.map(item => {
|
||||||
|
if (option.id == item.id) {
|
||||||
|
item.checked = !item.checked
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
|
||||||
|
},
|
||||||
|
handleToVoteDetail(vote) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pagesA/vote-detail/vote-detail?name=${vote.metadata.name}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showToast(content) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: content,
|
||||||
|
mask: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-50 {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #ffff;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
padding: 0;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: #e88080;
|
||||||
|
color: #e88080;
|
||||||
|
background-color: rgba(232, 128, 128, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
padding: 0;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: rgba(3, 174, 252, 1);
|
||||||
|
color: rgba(3, 174, 252, 1);
|
||||||
|
background-color: rgba(3, 174, 252, 0.075);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.vote-error {
|
||||||
|
padding: 50rpx 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-head {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 12rpx 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-body {
|
||||||
|
|
||||||
|
.remark {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-foot {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 6px;
|
||||||
|
margin-top: 12px;
|
||||||
|
border-top: 2rpx solid #eee;
|
||||||
|
|
||||||
|
.left {}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-voted-item {
|
||||||
|
min-height: 72rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(229, 229, 229, 0.75);
|
||||||
|
font-size: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: var(--percent);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(208, 208, 208, 1);
|
||||||
|
z-index: 0;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: rgba(3, 169, 244, 0.35);
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: rgba(3, 169, 244, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-voted-item-content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 72rpx;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-select-option {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single {
|
||||||
|
::v-deep {}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiple {
|
||||||
|
::v-deep {}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
|
||||||
|
.pk-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 30% !important;
|
||||||
|
max-width: 70% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-left {}
|
||||||
|
|
||||||
|
.radio-right {}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item-left {
|
||||||
|
background: linear-gradient(90deg, #3B82F6, #60A5FA);
|
||||||
|
color: white;
|
||||||
|
clip-path: polygon(0 0, calc(100% - 40rpx) 0, 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item-right {
|
||||||
|
background: linear-gradient(90deg, #F87171, #EF4444);
|
||||||
|
color: white;
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 100%, 40rpx 100%);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-foot {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
backdrop-filter: blur(3rpx);
|
backdrop-filter: blur(3rpx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="right-hot-ttf">
|
<view class="right-hot-ttf">
|
||||||
<text class="text hot-text">{{ title }}</text>
|
<text class="text hot-text text-overflow-2">{{ title }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<view class="Top-Title">
|
<view class="Top-Title">
|
||||||
<text class="text title-text">{{ item.title }}</text>
|
<text class="text title-text text-overflow-2">{{ item.title }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 用户信息 -->
|
<!-- 用户信息 -->
|
||||||
<view v-if="false" class="Bottom-UserInfo">
|
<view v-if="false" class="Bottom-UserInfo">
|
||||||
|
|||||||
@@ -0,0 +1,479 @@
|
|||||||
|
<template>
|
||||||
|
<view class="contribution-heatmap pa-24">
|
||||||
|
<view class="header">
|
||||||
|
<view class="title">
|
||||||
|
<view v-if="false" style="color: #7e7e7f">数据范围:</view>
|
||||||
|
<view>{{ dataRangeYears }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="controls">
|
||||||
|
<tm-stepper v-model="currentYear" :width="220" :height="48" :min="0" :max="2099" :shadow="0" :round="2" color="light-blue" @change="changeYear"></tm-stepper>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="heatmap-container">
|
||||||
|
<view class="weeks">
|
||||||
|
<view class="week-label" v-for="week in weeks" :key="week">{{ week }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="heatmap-content">
|
||||||
|
<view class="months" :style="[calcContentStyle]">
|
||||||
|
<view class="month-label" v-for="month in monthLabels" :key="month.index">
|
||||||
|
{{ month.name }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="days-container" :style="[calcContentStyle]">
|
||||||
|
<view
|
||||||
|
v-for="(day, index) in displayDays"
|
||||||
|
:key="index"
|
||||||
|
class="day-cell"
|
||||||
|
:style="{ backgroundColor: getDayColor(day) }"
|
||||||
|
@click="handleDayClick(day, index)"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="footer">
|
||||||
|
<view class="releases-count">
|
||||||
|
<text>累计 {{ calcAllYearCount }} 篇</text>
|
||||||
|
<text>丨</text>
|
||||||
|
<text>本年 {{ calcCurrentYearCount }} 篇</text>
|
||||||
|
</view>
|
||||||
|
<view class="legend">
|
||||||
|
<text class="legend-text">少</text>
|
||||||
|
<view v-for="(color, index) in intensityColors" :key="index" class="day-cell legend-day-cell" :style="{ backgroundColor: color }"></view>
|
||||||
|
<text class="legend-text">多</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmStepper from '@/tm-vuetify/components/tm-stepper/tm-stepper.vue';
|
||||||
|
export default {
|
||||||
|
name: 'Heatmap',
|
||||||
|
components: {
|
||||||
|
tmStepper
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
year: {
|
||||||
|
type: Number,
|
||||||
|
default: () => new Date().getFullYear()
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
weeks: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||||
|
intensityColors: [
|
||||||
|
'#ebedf0', // 无贡献
|
||||||
|
'#dbeafe', // 1-2次
|
||||||
|
'#93c5fd', // 3-4次
|
||||||
|
'#3b82f6', // 5-6次
|
||||||
|
'#1e40af' // 7次以上
|
||||||
|
],
|
||||||
|
displayDays: [],
|
||||||
|
showTooltip: null,
|
||||||
|
currentYear: '1900',
|
||||||
|
currentYearData: [],
|
||||||
|
yearList:[]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dataRangeYears() {
|
||||||
|
const arr = this.chartData;
|
||||||
|
const dateField = 'name';
|
||||||
|
|
||||||
|
if (!arr || !Array.isArray(arr) || arr.length === 0) {
|
||||||
|
return { minDate: null, maxDate: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有有效日期
|
||||||
|
const validDates = arr
|
||||||
|
.map((item) => {
|
||||||
|
if (item && item[dateField]) {
|
||||||
|
const date = new Date(item[dateField]);
|
||||||
|
return isNaN(date.getTime()) ? null : date;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((date) => date !== null);
|
||||||
|
|
||||||
|
if (validDates.length === 0) {
|
||||||
|
return { minDate: null, maxDate: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到最小和最大日期
|
||||||
|
const minDate = new Date(Math.min(...validDates.map((date) => date.getTime())));
|
||||||
|
const maxDate = new Date(Math.max(...validDates.map((date) => date.getTime())));
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
minDate: this.formatDate(minDate),
|
||||||
|
maxDate: this.formatDate(maxDate)
|
||||||
|
};
|
||||||
|
|
||||||
|
return `${result.minDate} 至 ${result.maxDate}`;
|
||||||
|
},
|
||||||
|
// 计算内容宽度
|
||||||
|
calcContentStyle() {
|
||||||
|
const rowCount = Math.ceil(this.displayDays.length / 7);
|
||||||
|
const singleWidth = 36;
|
||||||
|
return {
|
||||||
|
width: rowCount * singleWidth + 'rpx'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
//累计的发文次数
|
||||||
|
calcAllYearCount() {
|
||||||
|
return this.chartData.reduce((acc, cur) => {
|
||||||
|
return acc + cur.total;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
// 统计当前年累计的发文次数
|
||||||
|
calcCurrentYearCount() {
|
||||||
|
return this.currentYearData.reduce((acc, cur) => {
|
||||||
|
return acc + cur.total;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
monthLabels() {
|
||||||
|
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
|
||||||
|
const labels = [];
|
||||||
|
|
||||||
|
if (this.displayDays.length === 0) return labels;
|
||||||
|
|
||||||
|
// 计算每个格子的总宽度(包括间距)
|
||||||
|
const cellTotalWidth = 15 + 1.5 * 2; // width + margin * 2
|
||||||
|
|
||||||
|
let currentMonth = -1;
|
||||||
|
let monthStartWeek = 0;
|
||||||
|
|
||||||
|
// 遍历所有周
|
||||||
|
const totalWeeks = Math.ceil(this.displayDays.length / 7);
|
||||||
|
|
||||||
|
for (let week = 0; week < totalWeeks; week++) {
|
||||||
|
// 找到这一周的第一个有效日期
|
||||||
|
let weekMonth = -1;
|
||||||
|
for (let day = 0; day < 7; day++) {
|
||||||
|
const dayIndex = week * 7 + day;
|
||||||
|
if (dayIndex < this.displayDays.length && this.displayDays[dayIndex].date) {
|
||||||
|
weekMonth = this.displayDays[dayIndex].date.getMonth();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到了有效月份
|
||||||
|
if (weekMonth !== -1) {
|
||||||
|
// 如果是新的月份
|
||||||
|
if (weekMonth !== currentMonth) {
|
||||||
|
// 如果不是第一个月份,先计算前一个月份的宽度
|
||||||
|
if (currentMonth !== -1) {
|
||||||
|
const weeksInMonth = week - monthStartWeek;
|
||||||
|
const width = weeksInMonth * cellTotalWidth;
|
||||||
|
labels.push({
|
||||||
|
name: months[currentMonth],
|
||||||
|
width: width,
|
||||||
|
marginLeft: monthStartWeek === 0 ? 0 : 2,
|
||||||
|
index: currentMonth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMonth = weekMonth;
|
||||||
|
monthStartWeek = week;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理最后一个月份
|
||||||
|
if (currentMonth !== -1) {
|
||||||
|
const weeksInMonth = totalWeeks - monthStartWeek;
|
||||||
|
const width = weeksInMonth * cellTotalWidth;
|
||||||
|
labels.push({
|
||||||
|
name: months[currentMonth],
|
||||||
|
width: width,
|
||||||
|
marginLeft: monthStartWeek === 0 ? 0 : 2,
|
||||||
|
index: currentMonth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
year: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newYear) {
|
||||||
|
this.generateDisplayData(newYear);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
deep: true,
|
||||||
|
handler(newData) {
|
||||||
|
this.mergeContributionData(newData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initYearList()
|
||||||
|
this.filterAndSetCurrentYearData(this.year);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initYearList(){
|
||||||
|
for (var index = 1900; index < 2099; index++) {
|
||||||
|
this.yearList.push(index)
|
||||||
|
}
|
||||||
|
this.yearList = this.yearList.reverse();
|
||||||
|
},
|
||||||
|
filterAndSetCurrentYearData(year) {
|
||||||
|
this.currentYear = year;
|
||||||
|
this.currentYearData = this.chartData.filter((x) => x.name.includes(this.currentYear));
|
||||||
|
this.generateDisplayData(year);
|
||||||
|
},
|
||||||
|
generateDisplayData(year) {
|
||||||
|
const startDate = new Date(year, 0, 1);
|
||||||
|
const endDate = new Date(year, 11, 31);
|
||||||
|
const days = [];
|
||||||
|
|
||||||
|
// 计算第一周的偏移量(确保周一开始)
|
||||||
|
let firstDay = startDate.getDay();
|
||||||
|
firstDay = firstDay === 0 ? 6 : firstDay - 1; // 将周日(0)转换为6,周一(1)转换为0
|
||||||
|
|
||||||
|
// 添加空白填充,使第一周从周一开始
|
||||||
|
for (let i = 0; i < firstDay; i++) {
|
||||||
|
days.push({ date: null, count: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成一年的日期
|
||||||
|
let currentDate = new Date(startDate);
|
||||||
|
while (currentDate <= endDate) {
|
||||||
|
// 默认无贡献
|
||||||
|
days.push({
|
||||||
|
date: new Date(currentDate),
|
||||||
|
count: 0
|
||||||
|
});
|
||||||
|
currentDate.setDate(currentDate.getDate() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.displayDays = days;
|
||||||
|
this.mergeContributionData(this.currentYearData);
|
||||||
|
},
|
||||||
|
mergeContributionData(contributionData) {
|
||||||
|
if (!contributionData || contributionData.length === 0) return;
|
||||||
|
|
||||||
|
// 将传入的数据合并到显示数据中
|
||||||
|
const dataMap = new Map();
|
||||||
|
contributionData.forEach((item) => {
|
||||||
|
const dateStr = this.formatDate(item.date);
|
||||||
|
dataMap.set(dateStr, item.total);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.displayDays.forEach((day) => {
|
||||||
|
if (day.date) {
|
||||||
|
const dateStr = this.formatDate(day.date);
|
||||||
|
if (dataMap.has(dateStr)) {
|
||||||
|
day.count = dataMap.get(dateStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatDate(date) {
|
||||||
|
date = new Date(date);
|
||||||
|
// 将日期格式化为 YYYY-MM-DD
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
},
|
||||||
|
formatDisplayDate(date) {
|
||||||
|
// 将日期格式化为中文显示格式
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
return `${year}年${month}月${day}日`;
|
||||||
|
},
|
||||||
|
getDayColor(day) {
|
||||||
|
if (day.count === 0) return this.intensityColors[0];
|
||||||
|
if (day.count <= 2) return this.intensityColors[1];
|
||||||
|
if (day.count <= 4) return this.intensityColors[2];
|
||||||
|
if (day.count <= 6) return this.intensityColors[3];
|
||||||
|
return this.intensityColors[4];
|
||||||
|
},
|
||||||
|
getTooltipText(day) {
|
||||||
|
if (!day.date) return '无数据';
|
||||||
|
|
||||||
|
const dateStr = this.formatDisplayDate(day.date);
|
||||||
|
if (day.count === 0) {
|
||||||
|
return `${dateStr}: 发布 0 篇文章`;
|
||||||
|
} else {
|
||||||
|
return `${dateStr}: 发布 ${day.count} 篇文章`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleDayClick(day, index) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: this.getTooltipText(day)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeYear(value) {
|
||||||
|
this.filterAndSetCurrentYearData(value);
|
||||||
|
this.$emit('year-change', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.contribution-heatmap {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.12), 0 2rpx 4rpx rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #000;
|
||||||
|
border: 2rpx solid #ebedf0;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 8rpx 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #0969da;
|
||||||
|
font-size: 28rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-btn:hover {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-display {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 120rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border: 2rpx solid #ebedf0;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 8rpx 3rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-text {
|
||||||
|
margin: 0 10rpx;
|
||||||
|
}
|
||||||
|
.legend-day-cell {
|
||||||
|
width: 24rpx!important;
|
||||||
|
height: 24rpx!important;
|
||||||
|
}
|
||||||
|
.heatmap-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weeks {
|
||||||
|
margin-top: -2rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
padding-top: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #7e7e7f;
|
||||||
|
height: 30rpx;
|
||||||
|
line-height: 30rpx;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.months {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-label {
|
||||||
|
flex: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #7e7e7f;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heatmap-content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.days-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: 252rpx; /* 7行 * 30rpx + 7行 * 6rpx间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
margin: 3rpx;
|
||||||
|
background-color: #ebedf0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #586069;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.releases-count{
|
||||||
|
display: flex;
|
||||||
|
gap: 0 6rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2rpx solid #ebedf0;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 8rpx 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<tm-poup v-model="isShow" width="80vw" height="800rpx" position="center" :round="6" @change="fnOnChange">
|
||||||
|
<view class="fulled pa-4">
|
||||||
|
<view class="mt-24 fulled text-weight-b text-size-sm text-align-center text-overflow-2 pb-24">{{ title }}</view>
|
||||||
|
<view class="fulled mt-2">
|
||||||
|
<scroll-view class="fulled" scroll-y :style="{
|
||||||
|
height: url? '600rpx': '680rpx'
|
||||||
|
}">
|
||||||
|
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
||||||
|
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
||||||
|
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||||
|
:content="content" :markdown="true" :showLineNumber="true" :showLanguageName="true"
|
||||||
|
:copyByLongPress="true"/>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
<view v-if="url" class="fulled mt-12 flex flex-center">
|
||||||
|
<tm-button theme="bg-gradient-light-blue-accent" size="m" @click="fnToWebview()">
|
||||||
|
点击跳转内容链接
|
||||||
|
</tm-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</tm-poup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||||
|
|
||||||
|
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notify-dialog',
|
||||||
|
components: {
|
||||||
|
tmPoup,
|
||||||
|
tmButton,
|
||||||
|
mpHtml
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isShow: false,
|
||||||
|
markdownConfig: MarkdownConfig,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.isShow = this.show;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fnToWebview() {
|
||||||
|
if (this.$utils.checkIsUrl(this.url)) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pagesC/website/website?data=' +
|
||||||
|
JSON.stringify({
|
||||||
|
title: this.title,
|
||||||
|
url: encodeURIComponent(this.url)
|
||||||
|
}),
|
||||||
|
success: () => {
|
||||||
|
this.fnOnChange(false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fnOnChange(e) {
|
||||||
|
this.isShow = false;
|
||||||
|
this.$emit('on-change', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="pluginInfo" class="plugin-unavailable flex flex-col flex-center"
|
||||||
|
:class="{border:useBorder,'decoration':useDecoration}" :style="[calcCustomStyle]">
|
||||||
|
<!-- 图标 -->
|
||||||
|
<image class="plugin-logo" :src="pluginInfo.logo" mode="scaleToFill"></image>
|
||||||
|
<!-- 名称 -->
|
||||||
|
<view class="plugin-name">
|
||||||
|
{{ pluginInfo.name }}
|
||||||
|
</view>
|
||||||
|
<view class="plugin-error">
|
||||||
|
未安装/启用插件
|
||||||
|
</view>
|
||||||
|
<!-- 描述 -->
|
||||||
|
<view class="plugin-desc">
|
||||||
|
{{ pluginInfo.desc }}
|
||||||
|
</view>
|
||||||
|
<view v-if="errorText" class="plugin-tip">
|
||||||
|
{{errorText}}
|
||||||
|
</view>
|
||||||
|
<!-- 插件地址 -->
|
||||||
|
<view class="plugin-url">
|
||||||
|
插件地址:{{ pluginInfo.url }}
|
||||||
|
</view>
|
||||||
|
<!-- 反馈按钮/复制地址 -->
|
||||||
|
<view class="plugin-btns w-full flex-center">
|
||||||
|
<!-- #ifndef MP-WEIXIN -->
|
||||||
|
<tm-button theme="light-blue" :block="true" class="w-full" :height="70" @click="copy">复制地址</tm-button>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<tm-button theme="light-blue" plan text :height="70" @click="copy">复制地址</tm-button>
|
||||||
|
<tm-button theme="light-blue" open-type="contact" :height="70">提交反馈</tm-button>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="plugin-copyright">
|
||||||
|
提示:请确保 Halo 博客已安装相关插件
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
NeedPluginIds,
|
||||||
|
NeedPlugins,
|
||||||
|
CheckNeedPluginAvailable
|
||||||
|
} from "@/utils/plugin.js"
|
||||||
|
import utils from '@/utils/index.js'
|
||||||
|
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "plugin-unavailable",
|
||||||
|
components: {
|
||||||
|
tmButton
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
pluginId: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
useDecoration: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
useBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
customStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
NeedPluginIds,
|
||||||
|
NeedPlugins,
|
||||||
|
pluginInfo: null,
|
||||||
|
defaultStyle: {
|
||||||
|
width: "80vw",
|
||||||
|
borderRadius: "24rpx"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
calcCustomStyle() {
|
||||||
|
const style = this.customStyle ?? {}
|
||||||
|
return {
|
||||||
|
...this.defaultStyle,
|
||||||
|
...style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
pluginId: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal, oldVal) {
|
||||||
|
this.pluginInfo = NeedPlugins.get(newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
copy() {
|
||||||
|
utils.copyText(this.pluginInfo.url, "插件地址已复制")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.plugin-unavailable {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: auto;
|
||||||
|
padding: 100rpx 36rpx;
|
||||||
|
gap: 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
|
||||||
|
&.border {
|
||||||
|
// border: 2rpx solid #E2E8F0;
|
||||||
|
border: 2rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.decoration {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
box-shadow: 0 0 12rpx rgba(226, 232, 240, 0.35);
|
||||||
|
backdrop-filter: blur(6rpx);
|
||||||
|
border-top: 12rpx solid rgba(3, 169, 244, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-logo {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-name {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-error {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
border-radius: 36rpx;
|
||||||
|
background-color: rgba(255, 61, 49, 0.075);
|
||||||
|
color: rgba(255, 61, 49, 1);
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-tip {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 2rpx dashed #f2c97d;
|
||||||
|
color: #f0a020;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-desc {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 60vw;
|
||||||
|
font-size: 24rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-url {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #F1F5F9;
|
||||||
|
color: #666;
|
||||||
|
font-size: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-btns {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-copyright {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
transform: scale(0.9) translateY(20px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,735 +0,0 @@
|
|||||||
export default{
|
|
||||||
data(){
|
|
||||||
return{
|
|
||||||
system_info:{}, //system info
|
|
||||||
canvas_width:0, //canvas width px
|
|
||||||
canvas_height:0, //canvas height px
|
|
||||||
ctx:null, //canvas object
|
|
||||||
canvas_id:null, //canvas id
|
|
||||||
hidden:false,//Whether to hide canvas
|
|
||||||
scale:1,//canvas scale
|
|
||||||
r_canvas_scale:1,
|
|
||||||
if_ctx:true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods:{
|
|
||||||
/**
|
|
||||||
* save r-canvas.vue object
|
|
||||||
* @param {Object} that
|
|
||||||
*/
|
|
||||||
// saveThis(that){
|
|
||||||
// rCanvasThis = that
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Draw round rect text
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {Number} config.w 宽度
|
|
||||||
* @param {Number} config.h 高度
|
|
||||||
* @param {Number} config.radius 圆角弧度
|
|
||||||
* @param {String} config.fill_color 矩形颜色
|
|
||||||
*/
|
|
||||||
fillRoundRect(config) {
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let x = this.compatibilitySize(parseFloat(config.x)*this.scale)
|
|
||||||
let y = this.compatibilitySize(parseFloat(config.y)*this.scale)
|
|
||||||
let w = this.compatibilitySize(parseFloat(config.w)*this.scale)
|
|
||||||
let h = this.compatibilitySize(parseFloat(config.h)*this.scale)
|
|
||||||
let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale
|
|
||||||
|
|
||||||
let fill_color = config.fill_color || "black"
|
|
||||||
// The diameter of the circle must be less than the width and height of the rectangle
|
|
||||||
if (2 * radius > w || 2 * radius > h) {
|
|
||||||
reject("The diameter of the circle must be less than the width and height of the rectangle")
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.ctx.save();
|
|
||||||
this.ctx.translate(x, y);
|
|
||||||
//
|
|
||||||
this.drawRoundRectPath({
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
radius: radius
|
|
||||||
});
|
|
||||||
this.ctx.fillStyle = fill_color
|
|
||||||
this.ctx.fill();
|
|
||||||
this.ctx.restore();
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draws the sides of a rounded rectangle
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Number} config.w 宽度
|
|
||||||
* @param {Number} config.h 高度
|
|
||||||
* @param {Number} config.radius 圆角弧度
|
|
||||||
*/
|
|
||||||
drawRoundRectPath(config) {
|
|
||||||
this.ctx.beginPath(0);
|
|
||||||
this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);
|
|
||||||
this.ctx.lineTo(config.radius, config.h);
|
|
||||||
this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);
|
|
||||||
this.ctx.lineTo(0, config.radius);
|
|
||||||
this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);
|
|
||||||
this.ctx.lineTo(config.w - config.radius, 0);
|
|
||||||
this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);
|
|
||||||
this.ctx.lineTo(config.w, config.h - config.radius);
|
|
||||||
this.ctx.closePath();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw special Text,line wrapping is not supported
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {String} config.text 文字
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {String} config.font_color 文字颜色
|
|
||||||
* @param {String} config.font_family 文字字体
|
|
||||||
* @param {Number} config.font_size 文字大小(px)
|
|
||||||
*/
|
|
||||||
drawSpecialText(params){
|
|
||||||
let general = params.general
|
|
||||||
let list = params.list
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!general){
|
|
||||||
reject("general cannot be empty:101")
|
|
||||||
return;
|
|
||||||
}else if(list && list.length>0){
|
|
||||||
for(let i in list){
|
|
||||||
if(i != 0){
|
|
||||||
let font_size = list[i-1].font_size?parseFloat(list[i-1].font_size):20
|
|
||||||
this.ctx.setFontSize(font_size)
|
|
||||||
general.x = parseFloat(general.x) + this.ctx.measureText(list[i-1].text).width
|
|
||||||
}
|
|
||||||
list[i].x = general.x
|
|
||||||
list[i].y = general.y + (list[i].margin_top?parseFloat(list[i].margin_top):0)
|
|
||||||
await this.drawText(list[i])
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
}else{
|
|
||||||
reject("The length of config arr is less than 0")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* array delete empty
|
|
||||||
* @param {Object} arr
|
|
||||||
*/
|
|
||||||
arrDeleteEmpty(arr){
|
|
||||||
let newArr = []
|
|
||||||
for(let i in arr){
|
|
||||||
if(arr[i]){
|
|
||||||
newArr.push(arr[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newArr
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw Text,support line
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {String} config.text 文字
|
|
||||||
* @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)
|
|
||||||
* @param {Number} config.line_height 文字上下行间距
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {String} config.font_color 文字颜色
|
|
||||||
* @param {String} config.font_family 文字字体 默认值:Arial
|
|
||||||
* @param {String} config.text_align 文字对齐方式(left/center/right)
|
|
||||||
* @param {Number} config.font_size 文字大小(px)
|
|
||||||
* @param {Boolean} config.line_through_height 中划线大小
|
|
||||||
* @param {Boolean} config.line_through_color 中划线颜色
|
|
||||||
* @param {String} config.font_style 规定文字样式
|
|
||||||
* @param {String} config.font_variant 规定字体变体
|
|
||||||
* @param {String} config.font_weight 规定字体粗细
|
|
||||||
* @param {String} config.line_through_cap 线末端类型
|
|
||||||
* @param {String} config.line_clamp 最大行数
|
|
||||||
* @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...
|
|
||||||
* @param {String} config.is_line_break 是否开启换行符换行
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
drawText(config,configuration = {}){
|
|
||||||
|
|
||||||
configuration['line_num'] = configuration.line_num?configuration.line_num:0
|
|
||||||
configuration['text_width'] = configuration.text_width?configuration.text_width:0
|
|
||||||
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
|
|
||||||
if(config.text){
|
|
||||||
|
|
||||||
let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y
|
|
||||||
let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)
|
|
||||||
let font_color = config.font_color || "#000"
|
|
||||||
let font_family = config.font_family || "Arial"
|
|
||||||
let line_height = config.line_height || config.font_size || 20
|
|
||||||
let text_align = config.text_align || "left"
|
|
||||||
let font_weight = config.font_weight || "normal"
|
|
||||||
let font_variant = config.font_variant || "normal"
|
|
||||||
let font_style = config.font_style || "normal"
|
|
||||||
let line_clamp_hint = config.line_clamp_hint || '...'
|
|
||||||
let lineBreakJoinText = ""
|
|
||||||
let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0
|
|
||||||
// checkout is line break
|
|
||||||
if(config.is_line_break){
|
|
||||||
let splitTextArr = config.text.split(/[\n]/g)
|
|
||||||
if(splitTextArr && splitTextArr.length > 0){
|
|
||||||
let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)
|
|
||||||
if(newSplitTextArr && newSplitTextArr.length > 0){
|
|
||||||
lineBreakJoinText = newSplitTextArr.slice(1).join("\n")
|
|
||||||
config.text = newSplitTextArr[0]
|
|
||||||
}else{
|
|
||||||
reject("Text cannot be empty:103")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
reject("Text cannot be empty:102")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ctx.setFillStyle(font_color) // color
|
|
||||||
this.ctx.textAlign = text_align;
|
|
||||||
this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`
|
|
||||||
if(configuration.text_width >= this.ctx.measureText(config.text).width){
|
|
||||||
draw_width = configuration.text_width
|
|
||||||
}else if(max_width > 0){
|
|
||||||
draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
|
|
||||||
}else{
|
|
||||||
draw_width = this.ctx.measureText(config.text).width
|
|
||||||
}
|
|
||||||
configuration.text_width = draw_width / this.scale
|
|
||||||
if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){
|
|
||||||
let current_text = ""
|
|
||||||
let text_arr = config.text.split("")
|
|
||||||
for(let i in text_arr){
|
|
||||||
if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){
|
|
||||||
// Hyphenation that is greater than the drawable width continues to draw
|
|
||||||
if(config.line_clamp && parseInt(config.line_clamp) == 1){
|
|
||||||
// Subtracting the current_text tail width from the line_clamp_hint width
|
|
||||||
let current_text_arr = current_text.split('')
|
|
||||||
let json_current_text = ''
|
|
||||||
while(true){
|
|
||||||
current_text_arr = current_text_arr.slice(1)
|
|
||||||
json_current_text = current_text_arr.join('')
|
|
||||||
if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){
|
|
||||||
current_text = current_text.replace(json_current_text,'')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configuration.line_num += 1
|
|
||||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
|
||||||
this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
|
||||||
}else{
|
|
||||||
configuration.line_num += 1
|
|
||||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
|
||||||
this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
|
||||||
config.text = text_arr.slice(i).join("")
|
|
||||||
config.y = config.y + line_height
|
|
||||||
if(config.line_clamp){
|
|
||||||
config.line_clamp = parseInt(config.line_clamp) - 1
|
|
||||||
}
|
|
||||||
await this.drawText(config,configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}else{
|
|
||||||
current_text = current_text+text_arr[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(config.line_through_height){
|
|
||||||
let x = parseFloat(config.x)*this.scale
|
|
||||||
let w
|
|
||||||
let y = parseFloat(config.y)*this.scale - (font_size / 2.6)
|
|
||||||
if(text_align == "left"){
|
|
||||||
w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale
|
|
||||||
}else if(text_align == "right"){
|
|
||||||
w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1
|
|
||||||
}else if(text_align == "center"){
|
|
||||||
x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2
|
|
||||||
w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2
|
|
||||||
}
|
|
||||||
this.drawLineTo({
|
|
||||||
x:x,
|
|
||||||
y:y,
|
|
||||||
w:w,
|
|
||||||
h:y,
|
|
||||||
line_width:config.line_through_height,
|
|
||||||
line_color:config.line_through_color,
|
|
||||||
line_cap:config.line_through_cap
|
|
||||||
})
|
|
||||||
}
|
|
||||||
configuration.line_num += 1
|
|
||||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
|
||||||
this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
|
||||||
if(config.line_clamp){
|
|
||||||
config.line_clamp = parseInt(config.line_clamp) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(lineBreakJoinText){
|
|
||||||
await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)
|
|
||||||
}
|
|
||||||
draw_height = config.font_size * configuration.line_num
|
|
||||||
draw_width = configuration.text_width
|
|
||||||
resolve({draw_width,draw_height,draw_x,draw_y})
|
|
||||||
}else{
|
|
||||||
reject("Text cannot be empty:101")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw Line
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Object} config.x x坐标
|
|
||||||
* @param {Object} config.y y坐标
|
|
||||||
* @param {Object} config.w 线的宽度
|
|
||||||
* @param {Object} config.h 线的高度
|
|
||||||
* @param {Object} config.line_width 线的宽度
|
|
||||||
* @param {Object} config.line_color 线条颜色
|
|
||||||
*/
|
|
||||||
drawLineTo(config){
|
|
||||||
let x = this.compatibilitySize(config.x)
|
|
||||||
let y = this.compatibilitySize(config.y)
|
|
||||||
let w = this.compatibilitySize(config.w)
|
|
||||||
let h = this.compatibilitySize(config.h)
|
|
||||||
let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale
|
|
||||||
let line_color = config.line_color || "black"
|
|
||||||
let line_cap = config.line_cap || "butt"
|
|
||||||
this.ctx.beginPath()
|
|
||||||
this.ctx.lineCap = line_cap
|
|
||||||
this.ctx.lineWidth = line_width
|
|
||||||
this.ctx.strokeStyle = line_color
|
|
||||||
this.ctx.moveTo(x,y)
|
|
||||||
this.ctx.lineTo(w,h)
|
|
||||||
this.ctx.stroke()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Compatibility px
|
|
||||||
* @param {Object} size
|
|
||||||
*/
|
|
||||||
compatibilitySize(size) {
|
|
||||||
let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth
|
|
||||||
canvasSize = parseFloat(canvasSize * 2)
|
|
||||||
return canvasSize
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Restore compatibility px
|
|
||||||
* @param {Object} size
|
|
||||||
*/
|
|
||||||
resetCompatibilitySize(size) {
|
|
||||||
let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750
|
|
||||||
return canvasSize
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Init canvas
|
|
||||||
*/
|
|
||||||
init(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!config.canvas_id){
|
|
||||||
reject("Canvas ID cannot be empty, please refer to the usage example")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hidden = config.hidden
|
|
||||||
this.canvas_id = config.canvas_id
|
|
||||||
let system_info = await uni.getSystemInfoSync()
|
|
||||||
this.system_info = system_info
|
|
||||||
this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1
|
|
||||||
this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale
|
|
||||||
this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,
|
|
||||||
this.r_canvas_scale = 1/this.scale
|
|
||||||
this.ctx = uni.createCanvasContext(this.canvas_id,this)
|
|
||||||
this.setCanvasConfig({
|
|
||||||
global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,
|
|
||||||
backgroundColor:config.background_color?config.background_color:"#fff"
|
|
||||||
})
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* clear canvas all path
|
|
||||||
*/
|
|
||||||
clearCanvas(){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!this.ctx){
|
|
||||||
reject("canvas is not initialized:101")
|
|
||||||
return
|
|
||||||
}else{
|
|
||||||
this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)
|
|
||||||
await this.draw()
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Set canvas config
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
setCanvasConfig(config){
|
|
||||||
this.ctx.globalAlpha = config.global_alpha
|
|
||||||
this.ctx.fillStyle = config.backgroundColor
|
|
||||||
this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* set canvas width
|
|
||||||
* @param {Object} width
|
|
||||||
*/
|
|
||||||
setCanvasWidth(width){
|
|
||||||
if(!width){
|
|
||||||
uni.showToast({
|
|
||||||
title:'setCanvasWidth:width error',
|
|
||||||
icon:'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.canvas_width = this.compatibilitySize(parseFloat(width)) * this.scale
|
|
||||||
this.ctx.width = this.canvas_width
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* set canvas height
|
|
||||||
* @param {Object} height
|
|
||||||
*/
|
|
||||||
setCanvasHeight(height){
|
|
||||||
if(!height){
|
|
||||||
uni.showToast({
|
|
||||||
title:'setCanvasWidth:height error',
|
|
||||||
icon:'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.canvas_height = this.compatibilitySize(parseFloat(height)) * this.scale
|
|
||||||
this.ctx.height = this.canvas_height
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw to filepath
|
|
||||||
*/
|
|
||||||
draw(callback){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let stop = setTimeout(()=>{
|
|
||||||
this.ctx.draw(false,setTimeout(()=>{
|
|
||||||
uni.canvasToTempFilePath({
|
|
||||||
canvasId: this.canvas_id,
|
|
||||||
quality: 1,
|
|
||||||
success: (res)=>{
|
|
||||||
console.log('res',res)
|
|
||||||
resolve(res)
|
|
||||||
callback && callback(res)
|
|
||||||
},
|
|
||||||
fail:(err)=>{
|
|
||||||
reject(JSON.stringify(err)|| "Failed to generate poster:101")
|
|
||||||
}
|
|
||||||
},this)
|
|
||||||
},300))
|
|
||||||
clearTimeout(stop)
|
|
||||||
},300)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* draw rect
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {Number} config.w 图形宽度(px)
|
|
||||||
* @param {Number} config.h 图形高度(px)
|
|
||||||
* @param {Number} config.color 图形颜色
|
|
||||||
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
|
|
||||||
* @param {Number} config.border_width 边框大小
|
|
||||||
* @param {Number} config.border_color 边框颜色
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
drawRect(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!config.border_width || config.border_width <=0){
|
|
||||||
config.border_width = 0
|
|
||||||
}else{
|
|
||||||
config.border_width = parseFloat(config.border_width)
|
|
||||||
}
|
|
||||||
if(parseFloat(config.border_width) > 0){
|
|
||||||
let sub_config = JSON.parse(JSON.stringify(config))
|
|
||||||
sub_config.border_width = 0
|
|
||||||
sub_config.w = config.w + config.border_width
|
|
||||||
sub_config.h = config.h + config.border_width
|
|
||||||
sub_config.color = config.border_color || 'black'
|
|
||||||
if(sub_config.border_radius){
|
|
||||||
sub_config.border_radius = parseFloat(sub_config.border_radius) + parseFloat(config.border_width) / 2
|
|
||||||
}
|
|
||||||
await this.drawRect(sub_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
let color = config.color || 'white'
|
|
||||||
config.x = (parseFloat(config.x) + config.border_width / 2)
|
|
||||||
config.y = (parseFloat(config.y) + config.border_width / 2)
|
|
||||||
config['color'] = color
|
|
||||||
this.ctx.fillStyle = color;
|
|
||||||
if(config.is_radius || config.border_radius){
|
|
||||||
this.setNativeBorderRadius(config)
|
|
||||||
this.ctx.fill()
|
|
||||||
}else{
|
|
||||||
console.log('config.border_width',config.border_width)
|
|
||||||
this.ctx.fillRect(this.compatibilitySize(config.x*this.scale),this.compatibilitySize(config.y*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw image
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {String} config.url 图片链接
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {Number} config.w 图片宽度(px)
|
|
||||||
* @param {Number} config.h 图片高度(px)
|
|
||||||
* @param {Number} config.border_width 边大小
|
|
||||||
* @param {Number} config.border_color 边颜色
|
|
||||||
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
|
|
||||||
* @param {Number} config.border_radius 圆角弧度
|
|
||||||
*/
|
|
||||||
drawImage(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(config.url){
|
|
||||||
let type = 0 // 1、network image 2、native image 3、base64 image
|
|
||||||
let image_url
|
|
||||||
let reg = /^https?/ig;
|
|
||||||
if(reg.test(config.url)){
|
|
||||||
type = 1
|
|
||||||
}else{
|
|
||||||
if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1 || config.url.indexOf("data:image/gif;base64") != -1){
|
|
||||||
type = 3
|
|
||||||
}else{
|
|
||||||
type = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(type == 1){
|
|
||||||
// network image
|
|
||||||
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
|
|
||||||
image_url = res
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(err)
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
}else if(type == 2){
|
|
||||||
// native image
|
|
||||||
const imageInfoResult = await uni.getImageInfo({
|
|
||||||
src: config.url
|
|
||||||
});
|
|
||||||
try{
|
|
||||||
if(imageInfoResult.length <= 1){
|
|
||||||
reject(imageInfoResult[0].errMsg + ':404')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
reject(e+':500')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let base64 = await this.urlToBase64({url:imageInfoResult[1].path})
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
await this.base64ToNative({url:base64}).then(res=>{
|
|
||||||
image_url = res
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(JSON.stringify(err)+":501")
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
image_url = base64
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
}else if(type == 3){
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
await this.base64ToNative({url:config.url}).then(res=>{
|
|
||||||
image_url = res
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(JSON.stringify(err)+":500")
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
image_url = config.url
|
|
||||||
// #endif
|
|
||||||
}else{
|
|
||||||
reject("Other Type Errors:101")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if(config.border_width){
|
|
||||||
let border_radius = 0
|
|
||||||
if(config.border_radius){
|
|
||||||
let multiple = config.w / config.border_radius
|
|
||||||
border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple
|
|
||||||
}
|
|
||||||
// drawRect
|
|
||||||
await this.drawRect({
|
|
||||||
x:parseFloat(config.x) - parseFloat(config.border_width)/2,
|
|
||||||
y:parseFloat(config.y) - parseFloat(config.border_width)/2,
|
|
||||||
w:parseFloat(config.w) + parseFloat(config.border_width),
|
|
||||||
h:parseFloat(config.h) + parseFloat(config.border_width),
|
|
||||||
color:config.border_color,
|
|
||||||
border_radius:border_radius,
|
|
||||||
border_width:config.border_width,
|
|
||||||
is_radius:config.is_radius
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(config.border_radius){
|
|
||||||
config.color = config.color?config.color:'rgba(0,0,0,0)'
|
|
||||||
|
|
||||||
// 圆角有白边,+0.5的误差
|
|
||||||
config.w = config.w + 0.3
|
|
||||||
config.h = config.h + 0.3
|
|
||||||
|
|
||||||
this.setNativeBorderRadius(config)
|
|
||||||
}else if(config.is_radius){
|
|
||||||
//已废弃 is_radius
|
|
||||||
this.ctx.setStrokeStyle("rgba(0,0,0,0)")
|
|
||||||
this.ctx.save()
|
|
||||||
this.ctx.beginPath()
|
|
||||||
this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)
|
|
||||||
this.ctx.stroke();
|
|
||||||
this.ctx.clip()
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
|
|
||||||
this.ctx.restore() //Restore previously saved drawing context
|
|
||||||
resolve()
|
|
||||||
}else{
|
|
||||||
let err_msg = "Links cannot be empty:101"
|
|
||||||
reject(err_msg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* base64 to native available path
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
base64ToNative(config){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let fileName = new Date().getTime()
|
|
||||||
var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`
|
|
||||||
wx.getFileSystemManager().writeFile({
|
|
||||||
filePath: filePath,
|
|
||||||
data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),
|
|
||||||
encoding: 'base64',
|
|
||||||
success: function() {
|
|
||||||
resolve(filePath)
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* native url to base64
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
urlToBase64(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if (typeof window != 'undefined') {
|
|
||||||
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
|
|
||||||
resolve(res)
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}else if (typeof plus != 'undefined') {
|
|
||||||
plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{
|
|
||||||
obj.file((file)=>{
|
|
||||||
let fileReader = new plus.io.FileReader()
|
|
||||||
fileReader.onload = (res)=>{
|
|
||||||
resolve(res.target.result)
|
|
||||||
}
|
|
||||||
fileReader.onerror = (err)=>{
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
fileReader.readAsDataURL(file)
|
|
||||||
}, (err)=>{
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
},(err)=>{
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}else if(typeof wx != 'undefined'){
|
|
||||||
wx.getFileSystemManager().readFile({
|
|
||||||
filePath: config.url,
|
|
||||||
encoding: 'base64',
|
|
||||||
success: function(res) {
|
|
||||||
resolve('data:image/png;base64,' + res.data)
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setNativeBorderRadius(config){
|
|
||||||
let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)
|
|
||||||
if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;
|
|
||||||
if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));
|
|
||||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.closePath();
|
|
||||||
this.ctx.strokeStyle = config.color || config.border_color || 'rgba(0,0,0,0)'; // 设置绘制边框的颜色
|
|
||||||
this.ctx.stroke();
|
|
||||||
this.ctx.save()
|
|
||||||
this.ctx.clip();
|
|
||||||
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Download network file
|
|
||||||
* @param {Object} url : download url
|
|
||||||
*/
|
|
||||||
downLoadNetworkFile(url){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
uni.downloadFile({
|
|
||||||
url,
|
|
||||||
success:(res)=>{
|
|
||||||
if(res.statusCode == 200){
|
|
||||||
resolve(res.tempFilePath)
|
|
||||||
}else{
|
|
||||||
reject("Download Image Fail:102")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail:(err)=>{
|
|
||||||
reject("Download Image Fail:101")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Save image to natice
|
|
||||||
* @param {Object} filePath : native imageUrl
|
|
||||||
*/
|
|
||||||
saveImage(filePath){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
if(!filePath){
|
|
||||||
reject("FilePath cannot be null:101")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
var createA = document.createElement("a");
|
|
||||||
createA.download = filePath;
|
|
||||||
createA.href = filePath;
|
|
||||||
document.body.appendChild(createA);
|
|
||||||
createA.click();
|
|
||||||
createA.remove();
|
|
||||||
resolve()
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5
|
|
||||||
uni.saveImageToPhotosAlbum({
|
|
||||||
filePath: filePath,
|
|
||||||
success:(res)=>{
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
fail:(err)=>{
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view>
|
|
||||||
<view class="r-canvas-component" :style="{width:canvas_width/scale+'px',height:canvas_height/scale+'px'}" :class="{'hidden':hidden}">
|
|
||||||
<canvas class="r-canvas" v-if="canvas_id" :canvas-id="canvas_id" :id="canvas_id" :style="{width:canvas_width+'px',height:canvas_height+'px','transform': `scale(${r_canvas_scale})`}"></canvas>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import rCanvasJS from "./r-canvas.js"
|
|
||||||
export default {
|
|
||||||
mixins:[rCanvasJS]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.r-canvas{
|
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
.r-canvas-component{
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.r-canvas-component.hidden{
|
|
||||||
position: fixed;
|
|
||||||
top:-5000upx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,784 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view v-show="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
|
|
||||||
<view class="t-mask" :class="{active:active}" @click.stop="close"></view>
|
|
||||||
<view class="t-box" :class="{active:active}">
|
|
||||||
<view class="t-header">
|
|
||||||
<view class="t-header-button" @click="close">取消</view>
|
|
||||||
<view class="t-header-button" @click="confirm">确认</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')'}">
|
|
||||||
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)" @touchend="touchend($event, 0)">
|
|
||||||
<view class="t-color-mask"></view>
|
|
||||||
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-control__box">
|
|
||||||
<view class="t-control__color">
|
|
||||||
<view class="t-control__color-content" :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
|
|
||||||
</view>
|
|
||||||
<view class="t-control-box__item">
|
|
||||||
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
|
|
||||||
<view class="t-hue">
|
|
||||||
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
|
|
||||||
<view class="t-transparency">
|
|
||||||
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__box">
|
|
||||||
<view v-if="mode" class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{hex}}</view>
|
|
||||||
<view class="t-result__box-text">HEX</view>
|
|
||||||
</view>
|
|
||||||
<template v-else>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.r}}</view>
|
|
||||||
<view class="t-result__box-text">R</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.g}}</view>
|
|
||||||
<view class="t-result__box-text">G</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.b}}</view>
|
|
||||||
<view class="t-result__box-text">B</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.a}}</view>
|
|
||||||
<view class="t-result__box-text">A</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<view class="t-result__item t-select" @click="select">
|
|
||||||
<view class="t-result__box-input">
|
|
||||||
<view>切换</view>
|
|
||||||
<view>模式</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-alternative">
|
|
||||||
<view class="t-alternative__item" v-for="(item,index) in colorList" :key="index">
|
|
||||||
<view class="t-alternative__item-content" :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
|
|
||||||
@click="selectColor(item)">
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
color: {
|
|
||||||
type: Object,
|
|
||||||
default () {
|
|
||||||
return {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
spareColor: {
|
|
||||||
type: Array,
|
|
||||||
default () {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
show: false,
|
|
||||||
active: false,
|
|
||||||
// rgba 颜色
|
|
||||||
rgba: {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 1
|
|
||||||
},
|
|
||||||
// hsb 颜色
|
|
||||||
hsb: {
|
|
||||||
h: 0,
|
|
||||||
s: 0,
|
|
||||||
b: 0
|
|
||||||
},
|
|
||||||
site: [{
|
|
||||||
top: 0,
|
|
||||||
left: 0
|
|
||||||
}, {
|
|
||||||
left: 0
|
|
||||||
}, {
|
|
||||||
left: 0
|
|
||||||
}],
|
|
||||||
index: 0,
|
|
||||||
bgcolor: {
|
|
||||||
r: 255,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 1
|
|
||||||
},
|
|
||||||
hex: '#000000',
|
|
||||||
mode: true,
|
|
||||||
colorList: [{
|
|
||||||
r: 244,
|
|
||||||
g: 67,
|
|
||||||
b: 54,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 233,
|
|
||||||
g: 30,
|
|
||||||
b: 99,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 156,
|
|
||||||
g: 39,
|
|
||||||
b: 176,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 103,
|
|
||||||
g: 58,
|
|
||||||
b: 183,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 63,
|
|
||||||
g: 81,
|
|
||||||
b: 181,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 33,
|
|
||||||
g: 150,
|
|
||||||
b: 243,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 3,
|
|
||||||
g: 169,
|
|
||||||
b: 244,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 188,
|
|
||||||
b: 212,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 150,
|
|
||||||
b: 136,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 76,
|
|
||||||
g: 175,
|
|
||||||
b: 80,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 139,
|
|
||||||
g: 195,
|
|
||||||
b: 74,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 205,
|
|
||||||
g: 220,
|
|
||||||
b: 57,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 235,
|
|
||||||
b: 59,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 193,
|
|
||||||
b: 7,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 152,
|
|
||||||
b: 0,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 87,
|
|
||||||
b: 34,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 121,
|
|
||||||
g: 85,
|
|
||||||
b: 72,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 158,
|
|
||||||
g: 158,
|
|
||||||
b: 158,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 0.5
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 0
|
|
||||||
}, ]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.rgba = this.color;
|
|
||||||
if (this.spareColor.length !== 0) {
|
|
||||||
this.colorList = this.spareColor;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
// hsb 颜色
|
|
||||||
this.hsb = this.rgbToHex(this.rgba);
|
|
||||||
// this.setColor();
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
},
|
|
||||||
moveHandle() {},
|
|
||||||
open() {
|
|
||||||
this.show = true;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.init();
|
|
||||||
setTimeout(() => {
|
|
||||||
this.active = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getSelectorQuery();
|
|
||||||
}, 350)
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.active = false;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.show = false;
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
confirm() {
|
|
||||||
this.close();
|
|
||||||
this.$emit('confirm', {
|
|
||||||
rgba: this.rgba,
|
|
||||||
hex: this.hex
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 选择模式
|
|
||||||
select() {
|
|
||||||
this.mode = !this.mode
|
|
||||||
},
|
|
||||||
// 常用颜色选择
|
|
||||||
selectColor(item) {
|
|
||||||
this.setColorBySelect(item)
|
|
||||||
},
|
|
||||||
touchstart(e, index) {
|
|
||||||
const {
|
|
||||||
pageX,
|
|
||||||
pageY
|
|
||||||
} = e.touches[0];
|
|
||||||
this.pageX = pageX;
|
|
||||||
this.pageY = pageY;
|
|
||||||
this.setPosition(pageX, pageY, index);
|
|
||||||
},
|
|
||||||
touchmove(e, index) {
|
|
||||||
const {
|
|
||||||
pageX,
|
|
||||||
pageY
|
|
||||||
} = e.touches[0];
|
|
||||||
this.moveX = pageX;
|
|
||||||
this.moveY = pageY;
|
|
||||||
this.setPosition(pageX, pageY, index);
|
|
||||||
},
|
|
||||||
touchend(e, index) {},
|
|
||||||
/**
|
|
||||||
* 设置位置
|
|
||||||
*/
|
|
||||||
setPosition(x, y, index) {
|
|
||||||
this.index = index;
|
|
||||||
const {
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
} = this.position[index];
|
|
||||||
// 设置最大最小值
|
|
||||||
|
|
||||||
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
|
|
||||||
if (index === 0) {
|
|
||||||
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
|
|
||||||
// 设置颜色
|
|
||||||
this.hsb.s = parseInt((100 * this.site[index].left) / width);
|
|
||||||
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
|
|
||||||
this.setColor();
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
} else {
|
|
||||||
this.setControl(index, this.site[index].left);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 设置 rgb 颜色
|
|
||||||
*/
|
|
||||||
setColor() {
|
|
||||||
const rgb = this.HSBToRGB(this.hsb);
|
|
||||||
this.rgba.r = rgb.r;
|
|
||||||
this.rgba.g = rgb.g;
|
|
||||||
this.rgba.b = rgb.b;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 设置二进制颜色
|
|
||||||
* @param {Object} rgb
|
|
||||||
*/
|
|
||||||
setValue(rgb) {
|
|
||||||
this.hex = '#' + this.rgbToHex(rgb);
|
|
||||||
},
|
|
||||||
setControl(index, x) {
|
|
||||||
const {
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
} = this.position[index];
|
|
||||||
|
|
||||||
if (index === 1) {
|
|
||||||
this.hsb.h = parseInt((360 * x) / width);
|
|
||||||
this.bgcolor = this.HSBToRGB({
|
|
||||||
h: this.hsb.h,
|
|
||||||
s: 100,
|
|
||||||
b: 100
|
|
||||||
});
|
|
||||||
this.setColor()
|
|
||||||
} else {
|
|
||||||
this.rgba.a = (x / width).toFixed(1);
|
|
||||||
}
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* rgb 转 二进制 hex
|
|
||||||
* @param {Object} rgb
|
|
||||||
*/
|
|
||||||
rgbToHex(rgb) {
|
|
||||||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
|
||||||
hex.map(function(str, i) {
|
|
||||||
if (str.length == 1) {
|
|
||||||
hex[i] = '0' + str;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return hex.join('');
|
|
||||||
},
|
|
||||||
setColorBySelect(getrgb) {
|
|
||||||
const {
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
a
|
|
||||||
} = getrgb;
|
|
||||||
let rgb = {}
|
|
||||||
rgb = {
|
|
||||||
r: r ? parseInt(r) : 0,
|
|
||||||
g: g ? parseInt(g) : 0,
|
|
||||||
b: b ? parseInt(b) : 0,
|
|
||||||
a: a ? a : 0,
|
|
||||||
};
|
|
||||||
this.rgba = rgb;
|
|
||||||
this.hsb = this.rgbToHsb(rgb);
|
|
||||||
this.changeViewByHsb();
|
|
||||||
},
|
|
||||||
changeViewByHsb() {
|
|
||||||
const [a, b, c] = this.position;
|
|
||||||
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
|
|
||||||
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
|
|
||||||
this.setColor(this.hsb.h);
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
this.bgcolor = this.HSBToRGB({
|
|
||||||
h: this.hsb.h,
|
|
||||||
s: 100,
|
|
||||||
b: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
this.site[1].left = this.hsb.h / 360 * b.width;
|
|
||||||
this.site[2].left = this.rgba.a * c.width;
|
|
||||||
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* hsb 转 rgb
|
|
||||||
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
|
|
||||||
*/
|
|
||||||
HSBToRGB(hsb) {
|
|
||||||
let rgb = {};
|
|
||||||
let h = Math.round(hsb.h);
|
|
||||||
let s = Math.round((hsb.s * 255) / 100);
|
|
||||||
let v = Math.round((hsb.b * 255) / 100);
|
|
||||||
if (s == 0) {
|
|
||||||
rgb.r = rgb.g = rgb.b = v;
|
|
||||||
} else {
|
|
||||||
let t1 = v;
|
|
||||||
let t2 = ((255 - s) * v) / 255;
|
|
||||||
let t3 = ((t1 - t2) * (h % 60)) / 60;
|
|
||||||
if (h == 360) h = 0;
|
|
||||||
if (h < 60) {
|
|
||||||
rgb.r = t1;
|
|
||||||
rgb.b = t2;
|
|
||||||
rgb.g = t2 + t3;
|
|
||||||
} else if (h < 120) {
|
|
||||||
rgb.g = t1;
|
|
||||||
rgb.b = t2;
|
|
||||||
rgb.r = t1 - t3;
|
|
||||||
} else if (h < 180) {
|
|
||||||
rgb.g = t1;
|
|
||||||
rgb.r = t2;
|
|
||||||
rgb.b = t2 + t3;
|
|
||||||
} else if (h < 240) {
|
|
||||||
rgb.b = t1;
|
|
||||||
rgb.r = t2;
|
|
||||||
rgb.g = t1 - t3;
|
|
||||||
} else if (h < 300) {
|
|
||||||
rgb.b = t1;
|
|
||||||
rgb.g = t2;
|
|
||||||
rgb.r = t2 + t3;
|
|
||||||
} else if (h < 360) {
|
|
||||||
rgb.r = t1;
|
|
||||||
rgb.g = t2;
|
|
||||||
rgb.b = t1 - t3;
|
|
||||||
} else {
|
|
||||||
rgb.r = 0;
|
|
||||||
rgb.g = 0;
|
|
||||||
rgb.b = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
r: Math.round(rgb.r),
|
|
||||||
g: Math.round(rgb.g),
|
|
||||||
b: Math.round(rgb.b)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rgbToHsb(rgb) {
|
|
||||||
let hsb = {
|
|
||||||
h: 0,
|
|
||||||
s: 0,
|
|
||||||
b: 0
|
|
||||||
};
|
|
||||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
|
||||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
|
||||||
let delta = max - min;
|
|
||||||
hsb.b = max;
|
|
||||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
|
||||||
if (hsb.s != 0) {
|
|
||||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
|
||||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
|
||||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
|
||||||
} else hsb.h = -1;
|
|
||||||
hsb.h *= 60;
|
|
||||||
if (hsb.h < 0) hsb.h = 0;
|
|
||||||
hsb.s *= 100 / 255;
|
|
||||||
hsb.b *= 100 / 255;
|
|
||||||
return hsb;
|
|
||||||
},
|
|
||||||
getSelectorQuery() {
|
|
||||||
const views = uni.createSelectorQuery().in(this);
|
|
||||||
views
|
|
||||||
.selectAll('.boxs')
|
|
||||||
.boundingClientRect(data => {
|
|
||||||
if (!data || data.length === 0) {
|
|
||||||
setTimeout(() => this.getSelectorQuery(), 20)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.position = data;
|
|
||||||
// this.site[0].top = data[0].height;
|
|
||||||
// this.site[0].left = 0;
|
|
||||||
// this.site[1].left = data[1].width;
|
|
||||||
// this.site[2].left = data[2].width;
|
|
||||||
this.setColorBySelect(this.rgba);
|
|
||||||
})
|
|
||||||
.exec();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
spareColor(newVal) {
|
|
||||||
this.colorList = newVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.t-wrapper {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-box {
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 30upx 0;
|
|
||||||
padding-top: 0;
|
|
||||||
background: #fff;
|
|
||||||
transition: all 0.3s;
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-box.active {
|
|
||||||
transform: translateY(0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
height: 100upx;
|
|
||||||
border-bottom: 1px #eee solid;
|
|
||||||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-header-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 150upx;
|
|
||||||
height: 100upx;
|
|
||||||
font-size: 30upx;
|
|
||||||
color: #666;
|
|
||||||
padding-left: 20upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-header-button:last-child {
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-right: 20upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-mask {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
z-index: -1;
|
|
||||||
transition: all 0.3s;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-mask.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-color__box {
|
|
||||||
position: relative;
|
|
||||||
height: 400upx;
|
|
||||||
background: rgb(255, 0, 0);
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0 20upx;
|
|
||||||
margin-top: 20upx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-color-mask {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 400upx;
|
|
||||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-pointer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
left: -8px;
|
|
||||||
z-index: 2;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
border: 1px #fff solid;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-show-color {
|
|
||||||
width: 100upx;
|
|
||||||
height: 50upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control__box {
|
|
||||||
margin-top: 50upx;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
padding-left: 20upx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control__color {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 100upx;
|
|
||||||
height: 100upx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
|
||||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
|
||||||
background-size: 36upx 36upx;
|
|
||||||
background-position: 0 0, 18upx 18upx;
|
|
||||||
border: 1px #eee solid;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control__color-content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control-box__item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 30upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-controller {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 16px;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
|
||||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
|
||||||
background-size: 32upx 32upx;
|
|
||||||
background-position: 0 0, 16upx 16upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-hue {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-transparency {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-circle {
|
|
||||||
position: absolute;
|
|
||||||
/* right: -10px; */
|
|
||||||
top: -2px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__box {
|
|
||||||
margin-top: 20upx;
|
|
||||||
padding: 10upx;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10upx;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__box-input {
|
|
||||||
padding: 10upx 0;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 28upx;
|
|
||||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__box-text {
|
|
||||||
margin-top: 10upx;
|
|
||||||
font-size: 28upx;
|
|
||||||
line-height: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-select {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 150upx;
|
|
||||||
padding: 0 30upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-select .t-result__box-input {
|
|
||||||
border-radius: 10upx;
|
|
||||||
border: none;
|
|
||||||
color: #999;
|
|
||||||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-select .t-result__box-input:active {
|
|
||||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
/* justify-content: space-between; */
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 10upx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative__item {
|
|
||||||
margin-left: 12upx;
|
|
||||||
margin-top: 10upx;
|
|
||||||
width: 50upx;
|
|
||||||
height: 50upx;
|
|
||||||
border-radius: 10upx;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
|
||||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
|
||||||
background-size: 36upx 36upx;
|
|
||||||
background-position: 0 0, 18upx 18upx;
|
|
||||||
border: 1px #eee solid;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative__item-content {
|
|
||||||
width: 50upx;
|
|
||||||
height: 50upx;
|
|
||||||
background: rgba(255, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative__item:active {
|
|
||||||
transition: all 0.3s;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<template>
|
||||||
|
<view class="vote-card" @click="$emit('on-click',vote)">
|
||||||
|
<view class="vote-card-head flex">
|
||||||
|
<view class="left flex flex-center w-full">
|
||||||
|
<view class="flex-shrink">
|
||||||
|
<tm-tags color="light-blue" :shadow="0" rounded size="s" model="fill">{{ vote.spec._uh_type}}</tm-tags>
|
||||||
|
</view>
|
||||||
|
<view class="title text-overflow">
|
||||||
|
{{ vote.spec.title }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="vote-card-body w-full">
|
||||||
|
<view v-if="vote.spec.remark" class="remark text-overflow-2 text-size-s">
|
||||||
|
{{vote.spec.remark}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="vote-card-foot flex flex-between">
|
||||||
|
<view class="left flex">
|
||||||
|
<tm-tags :color="vote.spec._uh_state.color" size="s" rounded :shadow="0"
|
||||||
|
model="text">{{vote.spec._uh_state.state}}</tm-tags>
|
||||||
|
|
||||||
|
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags>
|
||||||
|
|
||||||
|
<tm-tags v-if="vote.spec.timeLimit==='permanent'" color="grey-darken-2" rounded size="s"
|
||||||
|
model="text">结束时间:永久有效 </tm-tags>
|
||||||
|
<tm-tags v-else color="grey-darken-2" rounded size="s" model="text">
|
||||||
|
<template
|
||||||
|
v-if="vote.spec._uh_state.state=='未开始'">开始时间:{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
||||||
|
</template>
|
||||||
|
<template v-else>结束时间:{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
||||||
|
</template>
|
||||||
|
</tm-tags>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="false" class="right flex flex-end">
|
||||||
|
<tm-tags v-if="false" color="grey-darken-2" rounded size="s" model="text">{{ vote.stats.voteCount }}
|
||||||
|
人已参与</tm-tags>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmGroupradio from '@/tm-vuetify/components/tm-groupradio/tm-groupradio.vue';
|
||||||
|
import tmRadio from '@/tm-vuetify/components/tm-radio/tm-radio.vue';
|
||||||
|
import tmGroupcheckbox from '@/tm-vuetify/components/tm-groupcheckbox/tm-groupcheckbox.vue';
|
||||||
|
import tmCheckbox from '@/tm-vuetify/components/tm-checkbox/tm-checkbox.vue';
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "VoteCard",
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
tmGroupradio,
|
||||||
|
tmRadio,
|
||||||
|
tmGroupcheckbox,
|
||||||
|
tmCheckbox,
|
||||||
|
tmButton,
|
||||||
|
tmTags
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
vote: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
showOptions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-50 {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #ffff;
|
||||||
|
box-shadow: 0rpx 2rpx 12rpx rgba(0, 0, 0, 0.035);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
// border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-head {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
.title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-body {
|
||||||
|
|
||||||
|
.remark {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 0;
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-foot {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 6px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-top: 2rpx solid #F7F7F7;
|
||||||
|
|
||||||
|
.left {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.single {
|
||||||
|
::v-deep {
|
||||||
|
.tm-groupradio {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-checkbox {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-button-label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiple {
|
||||||
|
::v-deep {
|
||||||
|
.tm-groupcheckbox {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-checkbox {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-button-label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.tm-groupradio {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-checkbox {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 30% !important;
|
||||||
|
max-width: 70% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-left {}
|
||||||
|
|
||||||
|
.radio-right {}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item-left {
|
||||||
|
background: linear-gradient(90deg, #3B82F6, #60A5FA);
|
||||||
|
color: white;
|
||||||
|
clip-path: polygon(0 0, calc(100% - 40rpx) 0, 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item-right {
|
||||||
|
background: linear-gradient(90deg, #F87171, #EF4444);
|
||||||
|
color: white;
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 100%, 40rpx 100%);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-foot {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+42
-2
@@ -4,12 +4,52 @@ export const _AppMockJsonKey = 'APP_GLOBAL_MOCK_JSON';
|
|||||||
|
|
||||||
// 默认的应用设置
|
// 默认的应用设置
|
||||||
export const DefaultAppConfigs = {
|
export const DefaultAppConfigs = {
|
||||||
|
basicConfig: {
|
||||||
|
tokenConfig: {
|
||||||
|
personalToken: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
loveConfig: {},
|
loveConfig: {},
|
||||||
imagesConfig: {},
|
imagesConfig: {},
|
||||||
authorConfig: {},
|
authorConfig: {},
|
||||||
appConfig: {},
|
appConfig: {},
|
||||||
pluginConfig: {},
|
pluginConfig: {
|
||||||
pageConfig: {}
|
votePlugin: {},
|
||||||
|
toolsPlugin: {},
|
||||||
|
linksPlugin:{},
|
||||||
|
linksSubmitPlugin: {},
|
||||||
|
doubanPlugin: {
|
||||||
|
position: 'bottom'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pageConfig: {
|
||||||
|
homeConfig: {
|
||||||
|
pageTitle: "首页",
|
||||||
|
useCategory: true,
|
||||||
|
bannerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
showTitle: true,
|
||||||
|
showIndicator: true,
|
||||||
|
height: "400rpx",
|
||||||
|
dotPosition: "right",
|
||||||
|
type: "post",
|
||||||
|
list: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
categoryConfig: {
|
||||||
|
type: "list"
|
||||||
|
},
|
||||||
|
momentConfig: {
|
||||||
|
useTagRandomColor: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auditConfig: {
|
||||||
|
auditModeEnabled: false,
|
||||||
|
auditModeData: {
|
||||||
|
jsonUrl: "",
|
||||||
|
jsonData: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import {getAppConfigs} from "@/config/index";
|
|
||||||
|
|
||||||
/** 配置后台管理员token */
|
|
||||||
const HaloTokenConfig = Object.freeze({
|
|
||||||
|
|
||||||
/** 基础请求域名:你的Halo博客基础域名 */
|
|
||||||
BASE_API: "",
|
|
||||||
// BASE_API: "https://blog.xiaoxiaomo.cn",
|
|
||||||
|
|
||||||
|
|
||||||
/** 管理员token */
|
|
||||||
systemToken: getAppConfigs()?.basicConfig?.tokenConfig?.personalToken,
|
|
||||||
/** 匿名用户token */
|
|
||||||
anonymousToken: ``
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
export default HaloTokenConfig;
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/** 应用基础配置 */
|
||||||
|
const UHaloBaseConfig = Object.freeze({
|
||||||
|
|
||||||
|
/** 基础请求域名:你的Halo博客基础域名,注意,域名后面不能带有斜杠【/】 */
|
||||||
|
// BASE_API: "",
|
||||||
|
// 示例:
|
||||||
|
BASE_API: "http://localhost:8090",
|
||||||
|
|
||||||
|
// todo:可能有其他配置
|
||||||
|
})
|
||||||
|
|
||||||
|
export default UHaloBaseConfig;
|
||||||
@@ -4,6 +4,7 @@ import Vue from "vue";
|
|||||||
// 挂载全局工具类
|
// 挂载全局工具类
|
||||||
import utils from "./utils/index.js";
|
import utils from "./utils/index.js";
|
||||||
Vue.prototype.$utils = utils;
|
Vue.prototype.$utils = utils;
|
||||||
|
uni.$utils = utils;
|
||||||
|
|
||||||
// 全局统一样式的对话框
|
// 全局统一样式的对话框
|
||||||
import Fy from '@/js_sdk/fy-showModal/index.js'
|
import Fy from '@/js_sdk/fy-showModal/index.js'
|
||||||
@@ -25,28 +26,6 @@ for (let fKey in AppFilters) {
|
|||||||
import tmVuetify from "./tm-vuetify";
|
import tmVuetify from "./tm-vuetify";
|
||||||
Vue.use(tmVuetify);
|
Vue.use(tmVuetify);
|
||||||
|
|
||||||
// i18n部分的配置
|
|
||||||
// 引入语言包,注意路径
|
|
||||||
import Chinese from "@/common/locales/zh.js";
|
|
||||||
import English from "@/common/locales/en.js";
|
|
||||||
|
|
||||||
// 引入并使用vue-i18n
|
|
||||||
import VueI18n from "vue-i18n";
|
|
||||||
Vue.use(VueI18n);
|
|
||||||
|
|
||||||
// 构造i18n对象
|
|
||||||
const i18n = new VueI18n({
|
|
||||||
// 默认语言,这里的local属性,对应message中的zh、en属性
|
|
||||||
locale: uni.getStorageSync("Default_Lang") || "zh",
|
|
||||||
// 引入语言文件
|
|
||||||
messages: {
|
|
||||||
// 这里的属性名是任意的,您也可以把zh设置为cn等,只是后续切换语言时
|
|
||||||
// 要标识这里的语言属性,如:this.$i18n.locale = zh|en|cn|xxx
|
|
||||||
zh: Chinese,
|
|
||||||
en: English,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 引入请求库
|
// 引入请求库
|
||||||
import {
|
import {
|
||||||
http
|
http
|
||||||
@@ -57,16 +36,12 @@ Vue.prototype.$http = http;
|
|||||||
import ApiManager from '@/api/index.js'
|
import ApiManager from '@/api/index.js'
|
||||||
Vue.use(ApiManager);
|
Vue.use(ApiManager);
|
||||||
|
|
||||||
import HaloTokenConfig from '@/config/token.config.js'
|
import HaloTokenConfig from '@/config/uhalo.config.js'
|
||||||
Vue.prototype.$baseApiUrl = HaloTokenConfig.BASE_API
|
Vue.prototype.$baseApiUrl = HaloTokenConfig.BASE_API
|
||||||
|
|
||||||
// 由于微信小程序的运行机制问题,需声明如下一行,H5和APP非必填
|
|
||||||
Vue.prototype._i18n = i18n;
|
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
App.mpType = "app";
|
App.mpType = "app";
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
i18n,
|
|
||||||
...App,
|
...App,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+30
-15
@@ -2,8 +2,8 @@
|
|||||||
"name" : "uni-halo",
|
"name" : "uni-halo",
|
||||||
"appid" : "__UNI__5748B6E",
|
"appid" : "__UNI__5748B6E",
|
||||||
"description" : "uni-halo博客:基于halo开源博客系统API开发的多端博客。",
|
"description" : "uni-halo博客:基于halo开源博客系统API开发的多端博客。",
|
||||||
"versionName" : "1.0.0",
|
"versionName" : "2.0.10",
|
||||||
"versionCode" : 100,
|
"versionCode" : 210,
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
/* 5+App特有相关 */
|
/* 5+App特有相关 */
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/* 快应用特有相关 */
|
/* 快应用特有相关 */
|
||||||
@@ -126,29 +126,29 @@
|
|||||||
},
|
},
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-baidu" : {
|
"mp-baidu" : {
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-toutiao" : {
|
"mp-toutiao" : {
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true,
|
"enable" : false,
|
||||||
"version" : "2"
|
"version" : "2"
|
||||||
},
|
},
|
||||||
"vueVersion" : "2",
|
"vueVersion" : "2",
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
},
|
},
|
||||||
"router" : {
|
"router" : {
|
||||||
"base" : "/uni-halo/",
|
"base" : "/uni-halo/",
|
||||||
@@ -190,32 +190,47 @@
|
|||||||
"es6" : true
|
"es6" : true
|
||||||
},
|
},
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-jd" : {
|
"mp-jd" : {
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-kuaishou" : {
|
"mp-kuaishou" : {
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-lark" : {
|
"mp-lark" : {
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quickapp-webview-huawei" : {
|
"quickapp-webview-huawei" : {
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quickapp-webview-union" : {
|
"quickapp-webview-union" : {
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : true
|
"enable" : false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app-harmony" : {
|
||||||
|
"uniStatistics" : {
|
||||||
|
"enable" : false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mp-harmony" : {
|
||||||
|
"uniStatistics" : {
|
||||||
|
"enable" : false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mp-xhs" : {
|
||||||
|
"uniStatistics" : {
|
||||||
|
"enable" : false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni-halo",
|
"name": "uni-halo",
|
||||||
"version": "1.0.0",
|
"version": "2.0.10",
|
||||||
"description": "<p align=\"center\">\r <img alt=\"logo\" src=\"https://b.925i.cn/uni_halo/uni_halo_logo.png\" width=\"120\" height=\"120\" style=\"margin-bottom: 10px;\">\r </p>\r <h3 align=\"center\" style=\"margin: 30px 0 30px;font-weight: bold;font-size:40px;\">uni-halo</h3>\r <h3 align=\"center\">uni-halo 基于Halo一款现代化的开源博客/CMS系统API开发的可多端编译应用</h3>",
|
"description": "<p align=\"center\">\r <img alt=\"logo\" src=\"https://b.925i.cn/uni_halo/uni_halo_logo.png\" width=\"120\" height=\"120\" style=\"margin-bottom: 10px;\">\r </p>\r <h3 align=\"center\" style=\"margin: 30px 0 30px;font-weight: bold;font-size:40px;\">uni-halo</h3>\r <h3 align=\"center\">uni-halo 基于Halo一款现代化的开源博客/CMS系统API开发的可多端编译应用</h3>",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -17,9 +17,6 @@
|
|||||||
"url": "https://gitee.com/ialley-workshop-open/uni-halo/issues"
|
"url": "https://gitee.com/ialley-workshop-open/uni-halo/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://uni-halo.925i.cn",
|
"homepage": "https://uni-halo.925i.cn",
|
||||||
"devDependencies": {
|
|
||||||
"vue-i18n": "^9.1.10"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"qs": "^6.12.1"
|
"qs": "^6.12.1"
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-12
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [{
|
||||||
{
|
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "uni-halo",
|
"navigationBarTitleText": "uni-halo",
|
||||||
@@ -92,11 +91,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
//此配置为小程序的分包加载机制。在5+App里始终为整包。
|
//此配置为小程序的分包加载机制。在5+App里始终为整包。
|
||||||
"subPackages": [
|
"subPackages": [{
|
||||||
{
|
|
||||||
"root": "pagesA",
|
"root": "pagesA",
|
||||||
"pages": [
|
"pages": [{
|
||||||
{
|
|
||||||
"path": "start/start",
|
"path": "start/start",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "uni-halo",
|
"navigationBarTitleText": "uni-halo",
|
||||||
@@ -266,20 +263,70 @@
|
|||||||
"navigationBarTitleText": "友链提交",
|
"navigationBarTitleText": "友链提交",
|
||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "moment-detail/moment-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "瞬间详情",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"app-plus": {
|
||||||
|
"pullToRefresh": {
|
||||||
|
"color": "#03a9f4",
|
||||||
|
"style": "circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "votes/votes",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "投票列表",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"app-plus": {
|
||||||
|
"pullToRefresh": {
|
||||||
|
"color": "#03a9f4",
|
||||||
|
"style": "circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "vote-detail/vote-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "投票详情",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"app-plus": {
|
||||||
|
"pullToRefresh": {
|
||||||
|
"color": "#03a9f4",
|
||||||
|
"style": "circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "data-visual/data-visual",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "数据看板",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"app-plus": {
|
||||||
|
"pullToRefresh": {
|
||||||
|
"color": "#03a9f4",
|
||||||
|
"style": "circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"root": "pagesC",
|
"root": "pagesC",
|
||||||
"pages": [
|
"pages": [{
|
||||||
{
|
|
||||||
"path": "website/website",
|
"path": "website/website",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
@@ -293,8 +340,7 @@
|
|||||||
"borderStyle": "white",
|
"borderStyle": "white",
|
||||||
"color": "#303133",
|
"color": "#303133",
|
||||||
"selectedColor": "#03a9f4",
|
"selectedColor": "#03a9f4",
|
||||||
"list": [
|
"list": [{
|
||||||
{
|
|
||||||
"iconPath": "static/tabbar/select_home.png",
|
"iconPath": "static/tabbar/select_home.png",
|
||||||
"selectedIconPath": "static/tabbar/select_home_active.png",
|
"selectedIconPath": "static/tabbar/select_home_active.png",
|
||||||
"pagePath": "pages/tabbar/home/home",
|
"pagePath": "pages/tabbar/home/home",
|
||||||
|
|||||||
+61
-10
@@ -1,19 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="app-page"></view>
|
<view class="app-page flex flex-center">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId" :error-text="uniHaloPluginAvailableError" :use-border="false" :use-decoration="false" />
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const homePagePath = '/pages/tabbar/home/home'
|
import pluginAvailableMixin from '@/common/mixins/pluginAvailable.js';
|
||||||
const startPagePath = '/pagesA/start/start'
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue';
|
||||||
|
|
||||||
|
const homePagePath = '/pages/tabbar/home/home';
|
||||||
|
const startPagePath = '/pagesA/start/start';
|
||||||
const articleDetailPath = '/pagesA/article-detail/article-detail';
|
const articleDetailPath = '/pagesA/article-detail/article-detail';
|
||||||
|
|
||||||
|
const _DEV_ = false;
|
||||||
|
const _DEV_TO_TYPE_ = 'page';
|
||||||
|
const _DEV_TO_PATH_ = '/pagesA/data-visual/data-visual';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [pluginAvailableMixin],
|
||||||
|
components: {
|
||||||
|
PluginUnavailable
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
configs() {
|
configs() {
|
||||||
return this.$tm.vx.getters().getConfigs;
|
return this.$tm.vx.getters().getConfigs;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad: function (options) {
|
async onLoad(options) {
|
||||||
uni.$tm.vx.actions('config/fetchConfigs').then(async (res) => {
|
// 本地开发,快速跳转页面,发布请设置 _DEV_ = false
|
||||||
|
if (_DEV_ && _DEV_TO_PATH_) {
|
||||||
|
if (_DEV_TO_TYPE_ == 'tabbar') {
|
||||||
|
uni.switchTab({
|
||||||
|
url: _DEV_TO_PATH_
|
||||||
|
});
|
||||||
|
} else if (_DEV_TO_TYPE_ == 'page') {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: _DEV_TO_PATH_
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查插件
|
||||||
|
this.setPluginId(this.NeedPluginIds.PluginUniHalo);
|
||||||
|
this.setPluginError('阿偶,检测到当前插件没有安装或者启用,无法启动 uni-halo 哦,请联系管理员');
|
||||||
|
if (!(await this.checkPluginAvailable())) return;
|
||||||
|
|
||||||
|
// 获取配置
|
||||||
|
uni.$tm.vx
|
||||||
|
.actions('config/fetchConfigs')
|
||||||
|
.then(async (res) => {
|
||||||
if (options.scene) {
|
if (options.scene) {
|
||||||
if ('' !== options.scene) {
|
if ('' !== options.scene) {
|
||||||
const postId = await this.getPostIdByQRCode(options.scene);
|
const postId = await this.getPostIdByQRCode(options.scene);
|
||||||
@@ -31,17 +67,25 @@ export default {
|
|||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// 获取mockjson
|
// 获取mockjson
|
||||||
if (res.basicConfig.auditModeEnabled) {
|
if (res.auditConfig.auditModeEnabled) {
|
||||||
await uni.$tm.vx.actions('config/fetchMockJson')
|
if (res.auditConfig.auditModeData.jsonUrl) {
|
||||||
|
await uni.$tm.vx.actions('config/fetchMockJson');
|
||||||
|
} else {
|
||||||
|
const mockJson = uni.$utils.checkJsonAndParse(res.auditConfig.auditModeData.jsonData);
|
||||||
|
if (mockJson.ok) {
|
||||||
|
uni.$tm.vx.commit('config/setMockJson', mockJson.jsonData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进入检查
|
// 进入检查
|
||||||
this.fnCheckShowStarted();
|
this.fnCheckShowStarted();
|
||||||
}).catch((err) => {
|
})
|
||||||
|
.catch((err) => {
|
||||||
uni.switchTab({
|
uni.switchTab({
|
||||||
url: homePagePath
|
url: homePagePath
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fnCheckShowStarted() {
|
fnCheckShowStarted() {
|
||||||
@@ -54,7 +98,7 @@ export default {
|
|||||||
|
|
||||||
// 是否每次都显示启动页
|
// 是否每次都显示启动页
|
||||||
if (this.configs.appConfig.startConfig.alwaysShow) {
|
if (this.configs.appConfig.startConfig.alwaysShow) {
|
||||||
uni.removeStorageSync('APP_HAS_STARTED')
|
uni.removeStorageSync('APP_HAS_STARTED');
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: startPagePath
|
url: startPagePath
|
||||||
});
|
});
|
||||||
@@ -84,3 +128,10 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-page {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -84,9 +84,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {checkHasAdminLogin} from '@/utils/auth.js';
|
import {
|
||||||
|
checkHasAdminLogin
|
||||||
|
} from '@/utils/auth.js';
|
||||||
import CheckAppUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
|
import CheckAppUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
|
||||||
import {CheckWxUpdate} from '@/utils/update.js';
|
import {
|
||||||
|
CheckWxUpdate
|
||||||
|
} from '@/utils/update.js';
|
||||||
|
import {
|
||||||
|
NeedPluginIds,
|
||||||
|
NeedPlugins,
|
||||||
|
checkNeedPluginAvailable
|
||||||
|
} from "@/utils/plugin.js"
|
||||||
|
|
||||||
import tmGrouplist from '@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue';
|
import tmGrouplist from '@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue';
|
||||||
import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
|
import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
|
||||||
@@ -152,7 +161,16 @@ export default {
|
|||||||
},
|
},
|
||||||
copyrightConfig() {
|
copyrightConfig() {
|
||||||
return this.haloConfigs.basicConfig.copyrightConfig;
|
return this.haloConfigs.basicConfig.copyrightConfig;
|
||||||
}
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
|
calcVotePluginEnabled() {
|
||||||
|
return !!this.haloConfigs?.pluginConfig?.votePlugin?.enabled;
|
||||||
|
},
|
||||||
|
calcLinksPluginEnabled() {
|
||||||
|
return !!this.haloConfigs?.pluginConfig?.linksPlugin?.enabled;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
haloConfigs: {
|
haloConfigs: {
|
||||||
@@ -171,19 +189,28 @@ export default {
|
|||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fnGetNavList() {
|
async fnGetNavList() {
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
let _isWx = false;
|
let _isWx = false;
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
_isWx = true;
|
_isWx = true;
|
||||||
// #endif
|
// #endif
|
||||||
this.navList = [
|
this.navList = [{
|
||||||
{
|
key: 'data-visual',
|
||||||
|
title: '数据看板',
|
||||||
|
leftIcon: 'icon-chart-bar',
|
||||||
|
leftIconColor: 'light-blue',
|
||||||
|
rightText: '站点数据可视化',
|
||||||
|
path: '/pagesA/data-visual/data-visual',
|
||||||
|
isAdmin: false,
|
||||||
|
type: 'page',
|
||||||
|
show: await checkNeedPluginAvailable(NeedPluginIds.PluginDataStatistics)
|
||||||
|
},{
|
||||||
key: 'archives',
|
key: 'archives',
|
||||||
title: this.haloConfigs.basicConfig.auditModeEnabled ? '内容归档' : '文章归档',
|
title: this.calcAuditModeEnabled ? '内容归档' : '文章归档',
|
||||||
leftIcon: 'halocoloricon-classify',
|
leftIcon: 'halocoloricon-classify',
|
||||||
leftIconColor: 'red',
|
leftIconColor: 'red',
|
||||||
rightText: this.haloConfigs.basicConfig.auditModeEnabled ? '已归档的内容' : '已归档的文章',
|
rightText: this.calcAuditModeEnabled ? '全部已归档内容' : '全部已归档文章',
|
||||||
path: '/pagesA/archives/archives',
|
path: '/pagesA/archives/archives',
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
type: 'page',
|
type: 'page',
|
||||||
@@ -198,6 +225,16 @@ export default {
|
|||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
type: 'page',
|
type: 'page',
|
||||||
show: this.haloConfigs.loveConfig.loveEnabled
|
show: this.haloConfigs.loveConfig.loveEnabled
|
||||||
|
}, {
|
||||||
|
key: 'vote',
|
||||||
|
title: '投票中心',
|
||||||
|
leftIcon: 'icon-box',
|
||||||
|
leftIconColor: 'red',
|
||||||
|
rightText: '查看和进行投票',
|
||||||
|
path: '/pagesA/votes/votes',
|
||||||
|
isAdmin: false,
|
||||||
|
type: 'page',
|
||||||
|
show: !this.calcAuditModeEnabled && !!this.calcVotePluginEnabled,
|
||||||
}, {
|
}, {
|
||||||
key: 'disclaimers',
|
key: 'disclaimers',
|
||||||
title: '友情链接',
|
title: '友情链接',
|
||||||
@@ -207,7 +244,7 @@ export default {
|
|||||||
path: '/pagesA/friend-links/friend-links',
|
path: '/pagesA/friend-links/friend-links',
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
type: 'page',
|
type: 'page',
|
||||||
show: true
|
show: !!this.calcLinksPluginEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'disclaimers',
|
key: 'disclaimers',
|
||||||
@@ -338,8 +375,7 @@ export default {
|
|||||||
url: '/pagesB/login/login'
|
url: '/pagesB/login/login'
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {});
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,8 +396,7 @@ export default {
|
|||||||
this.navList.find(x => x.key == 'cache').rightText =
|
this.navList.find(x => x.key == 'cache').rightText =
|
||||||
uni.getStorageInfoSync().currentSize + 'KB';
|
uni.getStorageInfoSync().currentSize + 'KB';
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {});
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
// #ifdef APP-PLUS
|
// #ifdef APP-PLUS
|
||||||
@@ -396,7 +431,7 @@ export default {
|
|||||||
url: '/pagesC/website/website?data=' +
|
url: '/pagesC/website/website?data=' +
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
title: item.text || this.$haloConfig.title,
|
title: item.text || this.$haloConfig.title,
|
||||||
url: item.path
|
url: encodeURIComponent(item.path)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,37 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="app-page">
|
<view class="app-page" :style="{
|
||||||
|
padding:calcShowType==='list-post'? 0 : '24rpx 0'
|
||||||
|
}">
|
||||||
<view v-if="loading !== 'success'" class="loading-wrap">
|
<view v-if="loading !== 'success'" class="loading-wrap">
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
</view>
|
</view>
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view v-else class="app-page-content">
|
<view v-else class="app-page-content" :class="[calcShowType==='list-post'?'list-post':'']">
|
||||||
<view v-if="dataList.length === 0" class="content-empty flex flex-center" style="height: 70vh;">
|
<view v-if="dataList.length === 0" class="content-empty flex flex-center" style="height: 70vh;">
|
||||||
<!-- 空布局 -->
|
<!-- 空布局 -->
|
||||||
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
|
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
|
||||||
</view>
|
</view>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
|
<block v-if="calcAuditModeEnabled || calcShowType==='list'">
|
||||||
<tm-translate v-for="(item, index) in dataList" :key="index"
|
<tm-translate v-for="(item, index) in dataList" :key="index"
|
||||||
style="box-sizing: border-box;width: 50%;padding: 0 8rpx;" animation-name="fadeUp"
|
style="box-sizing: border-box;width: 50%;padding: 0 8rpx;" animation-name="fadeUp"
|
||||||
:wait="calcAniWait(index)">
|
:wait="calcAniWait(index)">
|
||||||
<view class="catgory-card" :style="{backgroundImage:`url(${item.spec.cover})`}">
|
<view class="catgory-card" :style="{backgroundImage:`url(${item.spec.cover})`}">
|
||||||
<view class="content" @click="handleToCategory(item)">
|
<view class="content" @click="handleToCategory(item)">
|
||||||
<view style="font-size: 32rpx;color: #ffffff;">{{ item.spec.displayName }}</view>
|
<view style="font-size: 32rpx;color: #ffffff;">{{ item.spec.displayName }}</view>
|
||||||
<view v-if="!haloConfigs.basicConfig.auditModeEnabled" style="font-size: 24rpx;color: #ffffff;margin-top: 6rpx;">
|
<view v-if="!calcAuditModeEnabled"
|
||||||
|
style="font-size: 24rpx;color: #ffffff;margin-top: 6rpx;">
|
||||||
共 {{ item.postCount }} 篇文章
|
共 {{ item.postCount }} 篇文章
|
||||||
</view>
|
</view>
|
||||||
<view v-else style="font-size: 24rpx;color: #ffffff;margin-top: 6rpx;">
|
|
||||||
共 {{ item.postCount }} 篇内容
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</tm-translate>
|
</tm-translate>
|
||||||
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue"
|
|
||||||
icon="icon-angle-up"></tm-flotbutton>
|
|
||||||
<view class="load-text">{{ loadMoreText }}</view>
|
<view class="load-text">{{ loadMoreText }}</view>
|
||||||
</block>
|
</block>
|
||||||
|
<view v-else-if="calcShowType==='list-post'" class="fulled flex" style="min-height:100vh">
|
||||||
|
<view class="bg-white" :style="{height: '100%'}">
|
||||||
|
<tm-sliderNav :list="categoryList" bg-color="white" color="light-blue" rang-key="displayName"
|
||||||
|
@change="fnOnCategoryChange"></tm-sliderNav>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="right-content pt-12 pb-12" :scroll-y="true" :scroll-top="scrollTop"
|
||||||
|
:scroll-with-animation="true" :refresher-enabled="true" :refresher-triggered="triggered"
|
||||||
|
:refresher-threshold="60" refresher-background="#fafafa"
|
||||||
|
@refresherrefresh="fnGetPostByCategory(true)" @scrolltolower="fnGetPostByCategory(false)"
|
||||||
|
@scroll="fnOnScroll" @touchmove.stop @touchstart="fnOnTouchStart" @touchend="fnOnTouchEnd"
|
||||||
|
@touchcancel="fnOnTouchEnd">
|
||||||
|
<view v-if="postList.length === 0" class="article-empty flex flex-center">
|
||||||
|
<tm-empty :size="120" icon="icon-shiliangzhinengduixiang-" label="该分类下暂无文章~"></tm-empty>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<block v-for="(post, index) in postList" :key="post.spec.publishTime">
|
||||||
|
<tm-translate animation-name="fadeUp" :wait="calcAniWait(index)">
|
||||||
|
<article-min-card :article="post" @on-click="fnToArticleDetail"></article-min-card>
|
||||||
|
</tm-translate>
|
||||||
|
</block>
|
||||||
|
<view class="load-text">{{ loadMoreText }}</view>
|
||||||
|
</block>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="flot-buttons">
|
||||||
|
<tm-button @click="fnScrollTop" size="m" :fab="true" theme="light-blue"
|
||||||
|
icon="icon-angle-up"></tm-button>
|
||||||
|
<tm-button v-if="!calcAuditModeEnabled" @click="fnChangeShowType" size="m" :fab="true" theme="light-blue"
|
||||||
|
:icon="calcShowType==='list'?'icon-align-left':'icon-all'"></tm-button>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
@@ -39,20 +69,27 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||||
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
||||||
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
|
||||||
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||||
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||||
|
import tmFlowLayout from '@/tm-vuetify/components/tm-flowLayout/tm-flowLayout.vue';
|
||||||
|
import tmSliderNav from '@/tm-vuetify/components/tm-sliderNav/tm-sliderNav.vue';
|
||||||
|
import ArticleMinCard from '@/components/article-min-card/article-min-card.vue';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
tmSkeleton,
|
tmSkeleton,
|
||||||
tmFlotbutton,
|
tmButton,
|
||||||
tmTranslate,
|
tmTranslate,
|
||||||
tmEmpty,
|
tmEmpty,
|
||||||
mpHtml
|
mpHtml,
|
||||||
|
tmFlowLayout,
|
||||||
|
tmSliderNav,
|
||||||
|
ArticleMinCard
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -60,33 +97,65 @@ export default {
|
|||||||
loading: 'loading',
|
loading: 'loading',
|
||||||
queryParams: {
|
queryParams: {
|
||||||
size: 20,
|
size: 20,
|
||||||
page: 1
|
page: 1,
|
||||||
|
fieldSelector: ['spec.hideFromList=false']
|
||||||
},
|
},
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
dataList: [],
|
dataList: [],
|
||||||
|
categoryList: [],
|
||||||
isLoadMore: false,
|
isLoadMore: false,
|
||||||
loadMoreText: '加载中...'
|
loadMoreText: '加载中...',
|
||||||
|
currentCategoryConfig: {
|
||||||
|
type: "list"
|
||||||
|
},
|
||||||
|
currentCategoryName: "",
|
||||||
|
postQueryParams: {
|
||||||
|
size: 10,
|
||||||
|
page: 0,
|
||||||
|
},
|
||||||
|
postList: [],
|
||||||
|
triggered: false,
|
||||||
|
scrollTop: 0,
|
||||||
|
tempScrollTop: 0,
|
||||||
|
scrollTimeout: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
haloConfigs() {
|
haloConfigs() {
|
||||||
return this.$tm.vx.getters().getConfigs;
|
return this.$tm.vx.getters().getConfigs;
|
||||||
},
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
mockJson() {
|
mockJson() {
|
||||||
return this.$tm.vx.getters().getMockJson;
|
return this.$tm.vx.getters().getMockJson;
|
||||||
|
},
|
||||||
|
categoryConfig() {
|
||||||
|
return this.haloConfigs.pageConfig.categoryConfig
|
||||||
|
},
|
||||||
|
calcShowType() {
|
||||||
|
return this.currentCategoryConfig.type
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
watch: {
|
||||||
this.fnGetData();
|
categoryConfig: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
console.log("执行了", newVal)
|
||||||
|
if (!newVal) return;
|
||||||
|
this.currentCategoryConfig = newVal
|
||||||
|
this.handleInitPage()
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onPullDownRefresh() {
|
onPullDownRefresh() {
|
||||||
this.isLoadMore = false;
|
this.isLoadMore = false;
|
||||||
this.queryParams.page = 0;
|
this.queryParams.page = 0;
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
|
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
title: '没有更多数据了'
|
title: '没有更多数据了'
|
||||||
@@ -94,7 +163,12 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.hasNext) {
|
if (this.hasNext) {
|
||||||
|
if (this.calcShowType === 'list') {
|
||||||
|
|
||||||
this.queryParams.page += 1;
|
this.queryParams.page += 1;
|
||||||
|
} else {
|
||||||
|
this.postQueryParams.page += 1;
|
||||||
|
}
|
||||||
this.isLoadMore = true;
|
this.isLoadMore = true;
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
} else {
|
} else {
|
||||||
@@ -105,8 +179,37 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
fnChangeShowType() {
|
||||||
|
if (this.calcShowType === 'list-post') {
|
||||||
|
this.currentCategoryConfig.type = 'list'
|
||||||
|
} else {
|
||||||
|
this.currentCategoryConfig.type = 'list-post'
|
||||||
|
}
|
||||||
|
this.handleInitPage();
|
||||||
|
},
|
||||||
|
handleResetInit() {
|
||||||
|
this.postList = []
|
||||||
|
this.dataList = []
|
||||||
|
this.categoryList = []
|
||||||
|
this.queryParams.page = 0;
|
||||||
|
this.postQueryParams.page = 0;
|
||||||
|
this.hasNext = false
|
||||||
|
this.isLoadMore = false
|
||||||
|
this.loadMoreText = '加载中...'
|
||||||
|
this.currentCategoryName = ""
|
||||||
|
this.triggered = false
|
||||||
|
this.fnResetSetAniWaitIndex()
|
||||||
|
},
|
||||||
|
handleInitPage() {
|
||||||
|
this.handleResetInit()
|
||||||
|
if (this.calcShowType === 'list-post') {
|
||||||
|
this.queryParams.size = 99999
|
||||||
|
}
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
fnGetData() {
|
fnGetData() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
|
this.categoryConfig.type = "list"
|
||||||
this.dataList = this.mockJson.category.list.map((item) => {
|
this.dataList = this.mockJson.category.list.map((item) => {
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -137,15 +240,14 @@ export default {
|
|||||||
this.$httpApi.v2
|
this.$httpApi.v2
|
||||||
.getCategoryList(this.queryParams)
|
.getCategoryList(this.queryParams)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
console.log('请求结果:');
|
if (this.calcShowType === 'list') {
|
||||||
console.log(res);
|
|
||||||
|
|
||||||
this.loading = 'success';
|
this.loading = 'success';
|
||||||
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
|
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
|
||||||
// 处理数据
|
// 处理数据
|
||||||
this.hasNext = res.hasNext;
|
this.hasNext = res.hasNext;
|
||||||
|
|
||||||
const tempItems = res.items.map(item => {
|
const tempItems = res.items.map(item => {
|
||||||
|
item.postCount = item.postCount ?? 0
|
||||||
item.spec.cover = this.$utils.checkThumbnailUrl(item.spec.cover, true)
|
item.spec.cover = this.$utils.checkThumbnailUrl(item.spec.cover, true)
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
@@ -155,6 +257,23 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.dataList = tempItems;
|
this.dataList = tempItems;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.dataList = res.items
|
||||||
|
this.categoryList = res.items.map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
displayName: item.spec.displayName,
|
||||||
|
name: item.metadata.name,
|
||||||
|
postCount: item.postCount ?? 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.triggered = false;
|
||||||
|
this.loading = 'success';
|
||||||
|
if (this.dataList.length !== 0) {
|
||||||
|
this.currentCategoryName = this.dataList[0].metadata.name;
|
||||||
|
this.fnGetPostByCategory()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -175,12 +294,90 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleToCategory(data) {
|
handleToCategory(data) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pagesA/category-detail/category-detail?name=${data.metadata.name}&title=${data.spec.displayName}`
|
url: `/pagesA/category-detail/category-detail?name=${data.metadata.name}&title=${data.spec.displayName}`
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
fnGetPostByCategory(isPulldownRefresh = true, triggered = true) {
|
||||||
|
if (!isPulldownRefresh) {
|
||||||
|
if (this.hasNext) {
|
||||||
|
this.postQueryParams.page += 1;
|
||||||
|
} else {
|
||||||
|
return uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '没有更多数据了'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.postQueryParams.page = 0;
|
||||||
|
if (triggered) {
|
||||||
|
this.triggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getCategoryPostList(this.currentCategoryName, this.postQueryParams)
|
||||||
|
.then(res => {
|
||||||
|
this.hasNext = res.hasNext;
|
||||||
|
|
||||||
|
if (!isPulldownRefresh) {
|
||||||
|
this.postList = this.postList.concat(res.items);
|
||||||
|
} else {
|
||||||
|
this.postList = res.items;
|
||||||
|
}
|
||||||
|
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.loadMoreText = '加载失败!';
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.triggered = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fnOnCategoryChange(e) {
|
||||||
|
this.fnResetSetAniWaitIndex();
|
||||||
|
this.currentCategoryName = this.dataList[e].metadata.name;
|
||||||
|
this.fnToTopScroll();
|
||||||
|
this.postList = [];
|
||||||
|
this.fnGetPostByCategory();
|
||||||
|
},
|
||||||
|
|
||||||
|
fnOnScroll(e) {
|
||||||
|
this.tempScrollTop = e.detail.scrollTop;
|
||||||
|
},
|
||||||
|
fnToTopScroll() {
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
this.scrollTop = 0;
|
||||||
|
this.tempScrollTop = 0;
|
||||||
|
},
|
||||||
|
fnOnTouchStart() {
|
||||||
|
clearTimeout(this.scrollTimeout);
|
||||||
|
},
|
||||||
|
fnOnTouchEnd() {
|
||||||
|
this.scrollTimeout = setTimeout(() => {
|
||||||
|
this.scrollTop = this.tempScrollTop;
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
//跳转文章详情
|
||||||
|
fnToArticleDetail(post) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pagesA/article-detail/article-detail?name=' + post.metadata.name,
|
||||||
|
animationType: 'slide-in-right'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fnScrollTop() {
|
||||||
|
if (this.calcShowType === 'list') {
|
||||||
|
this.fnToTopPage()
|
||||||
|
} else {
|
||||||
|
this.fnToTopScroll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -212,7 +409,13 @@ export default {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0 12rpx;
|
padding: 0 12rpx;
|
||||||
gap: 20rpx 0;
|
gap: 20rpx 0;
|
||||||
|
|
||||||
|
&.list-post {
|
||||||
|
padding: 0;
|
||||||
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.catgory-card {
|
.catgory-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -236,6 +439,7 @@ export default {
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: rgba(0, 0, 0, 0.15);
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
backdrop-filter: blur(3rpx);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,4 +459,14 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flot-buttons {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 100rpx;
|
||||||
|
right: 32rpx;
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
gap: 6rpx;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,21 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="app-page">
|
<view class="app-page" :class="[uniHaloPluginPageClass]">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
|
||||||
|
:error-text="uniHaloPluginAvailableError" />
|
||||||
|
<template v-else>
|
||||||
<!-- 顶部切换 -->
|
<!-- 顶部切换 -->
|
||||||
<view class="e-fixed" v-if="category.list.length > 0">
|
<view class="e-fixed" v-if="category.list.length > 0">
|
||||||
<tm-tabs color="light-blue" v-model="category.activeIndex" range-key="displayName" :list="category.list"
|
<tm-tabs color="light-blue" :shadow="0" v-model="category.activeIndex" range-key="displayName"
|
||||||
align="left" @change="fnOnCategoryChange"></tm-tabs>
|
:list="category.list" align="left" @change="fnOnCategoryChange($event, false)"></tm-tabs>
|
||||||
</view>
|
</view>
|
||||||
<!-- 占位区域 -->
|
<!-- 占位区域 -->
|
||||||
<view v-if="category.list.length > 0" style="width: 100vw;height: 90rpx;"></view>
|
<view v-if="category.list.length > 0" style="width: 100vw;height: 90rpx;"></view>
|
||||||
<!-- 加载区域 -->
|
<!-- 加载区域 -->
|
||||||
<view v-if="loading !== 'success'" class="loading-wrap">
|
<view v-if="loading == 'loading'" class="loading-wrap">
|
||||||
<tm-skeleton model="card"></tm-skeleton>
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
<tm-skeleton model="card"></tm-skeleton>
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
<tm-skeleton model="card"></tm-skeleton>
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
<tm-skeleton model="card"></tm-skeleton>
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-else-if="loading == 'error'" class="flex flex-col flex-center" style="width:100%;height:60vh;">
|
||||||
|
<tm-empty icon="icon-wind-cry" label="阿偶,获取数据失败了~">
|
||||||
|
<tm-button theme="light-blue" size="m" :shadow="0" @click="fnGetData(true)">刷新试试</tm-button>
|
||||||
|
</tm-empty>
|
||||||
|
</view>
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view class="content" v-else>
|
<view v-else class="content">
|
||||||
|
<k-touch-listen class="touch-listen-content" @touchLeft="touchLeft" @touchRight="touchRight">
|
||||||
<view v-if="dataList.length === 0" class="content-empty">
|
<view v-if="dataList.length === 0" class="content-empty">
|
||||||
<!-- 空布局 -->
|
<!-- 空布局 -->
|
||||||
<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有分享图片~"></tm-empty>
|
<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有分享图片~"></tm-empty>
|
||||||
@@ -23,7 +32,8 @@
|
|||||||
<block v-else>
|
<block v-else>
|
||||||
<block v-if="galleryConfig.useWaterfall">
|
<block v-if="galleryConfig.useWaterfall">
|
||||||
<!--瀑布流-->
|
<!--瀑布流-->
|
||||||
<tm-flowLayout-custom ref="wafll" style="width: 100%;" @click="fnOnFlowClick"></tm-flowLayout-custom>
|
<tm-flowLayout-custom ref="wafll" style="width: 100%;"
|
||||||
|
@click="fnOnFlowClick"></tm-flowLayout-custom>
|
||||||
</block>
|
</block>
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<block v-else>
|
<block v-else>
|
||||||
@@ -37,11 +47,18 @@
|
|||||||
</tm-translate>
|
</tm-translate>
|
||||||
</block>
|
</block>
|
||||||
|
|
||||||
<tm-flotbutton @click="fnToTopPage" color="light-blue" size="m" icon="icon-angle-up"></tm-flotbutton>
|
|
||||||
<view class="load-text">{{ loadMoreText }}</view>
|
<view class="load-text">{{ loadMoreText }}</view>
|
||||||
</block>
|
</block>
|
||||||
|
</k-touch-listen>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-if="!calcAuditModeEnabled" class="flot-buttons">
|
||||||
|
<tm-button v-if="loading == 'error'" @click="fnGetCategory" size="m" :fab="true" theme="light-blue"
|
||||||
|
icon="icon-sync-alt"></tm-button>
|
||||||
|
<tm-button @click="fnToTopPage" size="m" :fab="true" theme="light-blue"
|
||||||
|
icon="icon-angle-up"></tm-button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -55,11 +72,15 @@ import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
|
|||||||
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
|
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
|
||||||
import tmFlowLayoutCustom from '@/tm-vuetify/components/tm-flowLayout-custom/tm-flowLayout-custom.vue';
|
import tmFlowLayoutCustom from '@/tm-vuetify/components/tm-flowLayout-custom/tm-flowLayout-custom.vue';
|
||||||
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
|
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
|
||||||
|
import pluginAvailable from "@/common/mixins/pluginAvailable.js"
|
||||||
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue'
|
||||||
export default {
|
export default {
|
||||||
options: {
|
options: {
|
||||||
multipleSlots: true
|
multipleSlots: true
|
||||||
},
|
},
|
||||||
|
mixins: [pluginAvailable],
|
||||||
components: {
|
components: {
|
||||||
tmSkeleton,
|
tmSkeleton,
|
||||||
tmTranslate,
|
tmTranslate,
|
||||||
@@ -69,7 +90,9 @@ export default {
|
|||||||
tmIcons,
|
tmIcons,
|
||||||
tmImages,
|
tmImages,
|
||||||
tmFlowLayoutCustom,
|
tmFlowLayoutCustom,
|
||||||
tmTabs
|
tmTabs,
|
||||||
|
tmButton,
|
||||||
|
PluginUnavailable
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -88,7 +111,8 @@ export default {
|
|||||||
isLoadMore: false,
|
isLoadMore: false,
|
||||||
loadMoreText: '',
|
loadMoreText: '',
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
dataList: []
|
dataList: [],
|
||||||
|
lock: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -100,11 +124,14 @@ export default {
|
|||||||
},
|
},
|
||||||
mockJson() {
|
mockJson() {
|
||||||
return this.$tm.vx.getters().getMockJson;
|
return this.$tm.vx.getters().getMockJson;
|
||||||
}
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
galleryConfig: {
|
galleryConfig: {
|
||||||
handler(newValue, oldValue) {
|
async handler(newValue, oldValue) {
|
||||||
if (!newValue) return;
|
if (!newValue) return;
|
||||||
this.fnSetPageTitle(newValue.pageTitle);
|
this.fnSetPageTitle(newValue.pageTitle);
|
||||||
this.fnGetCategory();
|
this.fnGetCategory();
|
||||||
@@ -113,20 +140,33 @@ export default {
|
|||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async onLoad() {
|
||||||
|
// 检查插件
|
||||||
|
this.setPluginId(this.NeedPluginIds.PluginPhotos)
|
||||||
|
this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用图库功能哦,请联系管理员")
|
||||||
|
await this.checkPluginAvailable()
|
||||||
|
},
|
||||||
onPullDownRefresh() {
|
onPullDownRefresh() {
|
||||||
|
if (!this.uniHaloPluginAvailable) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return
|
||||||
|
}
|
||||||
this.dataList = []
|
this.dataList = []
|
||||||
this.isLoadMore = false;
|
this.isLoadMore = false;
|
||||||
this.queryParams.page = 1;
|
this.queryParams.page = 1;
|
||||||
this.fnGetData(true);
|
this.fnGetData(true);
|
||||||
},
|
},
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (!this.uniHaloPluginAvailable) return;
|
||||||
|
if (this.calcAuditModeEnabled) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
title: '没有更多数据了'
|
title: '没有更多数据了'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasNext) {
|
if (this.hasNext) {
|
||||||
this.queryParams.page += 1;
|
this.queryParams.page += 1;
|
||||||
this.isLoadMore = true;
|
this.isLoadMore = true;
|
||||||
@@ -139,7 +179,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fnOnCategoryChange(index) {
|
fnGetDataByCategory(index) {
|
||||||
this.fnResetSetAniWaitIndex();
|
this.fnResetSetAniWaitIndex();
|
||||||
this.queryParams.group = this.category.list[index].name;
|
this.queryParams.group = this.category.list[index].name;
|
||||||
this.queryParams.page = 1;
|
this.queryParams.page = 1;
|
||||||
@@ -147,8 +187,18 @@ export default {
|
|||||||
this.dataList = [];
|
this.dataList = [];
|
||||||
this.fnGetData(true);
|
this.fnGetData(true);
|
||||||
},
|
},
|
||||||
|
fnOnCategoryChange(index) {
|
||||||
|
if (this.lock) {
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "上一个请求进行中...",
|
||||||
|
// icon: "none"
|
||||||
|
// })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fnGetDataByCategory(index)
|
||||||
|
},
|
||||||
fnGetCategory() {
|
fnGetCategory() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
this.fnGetData(true);
|
this.fnGetData(true);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -156,20 +206,31 @@ export default {
|
|||||||
page: 1,
|
page: 1,
|
||||||
size: 0
|
size: 0
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
this.category.list = res.items.map(item => {
|
this.category.list = res.map(item => {
|
||||||
return {
|
return {
|
||||||
name: item.metadata.name,
|
name: item.metadata.name,
|
||||||
displayName: item.spec.displayName
|
displayName: item.spec.displayName,
|
||||||
|
priority: item.spec.priority
|
||||||
}
|
}
|
||||||
|
}).sort((a, b) => a.priority - b.priority)
|
||||||
|
this.category.list.unshift({
|
||||||
|
name: undefined,
|
||||||
|
displayName: '全部',
|
||||||
|
priority: 0
|
||||||
});
|
});
|
||||||
if (this.category.list.length !== 0) {
|
if (this.category.list.length !== 0) {
|
||||||
this.queryParams.group = this.category.list[0].name;
|
this.queryParams.group = this.category.list[0].name;
|
||||||
this.fnGetData(true);
|
this.fnGetData(true);
|
||||||
}
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
this.loading = 'error'
|
||||||
|
this.category.list = []
|
||||||
|
this.category.activeIndex = 0
|
||||||
|
this.category.activeValue = ""
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fnGetData(isClearWaterfall = false) {
|
fnGetData(isClearWaterfall = false) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
this.dataList = this.mockJson.gallery.list.map(item => {
|
this.dataList = this.mockJson.gallery.list.map(item => {
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -196,6 +257,7 @@ export default {
|
|||||||
this.loadMoreText = '呜呜,没有更多数据啦~';
|
this.loadMoreText = '呜呜,没有更多数据啦~';
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.stopPullDownRefresh();
|
uni.stopPullDownRefresh();
|
||||||
|
this.lock = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,10 +301,13 @@ export default {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.stopPullDownRefresh();
|
uni.stopPullDownRefresh();
|
||||||
|
this.lock = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fnOnFlowClick({item}) {
|
fnOnFlowClick({
|
||||||
|
item
|
||||||
|
}) {
|
||||||
this.fnPreview(item)
|
this.fnPreview(item)
|
||||||
},
|
},
|
||||||
// 预览
|
// 预览
|
||||||
@@ -253,6 +318,24 @@ export default {
|
|||||||
indicator: 'number',
|
indicator: 'number',
|
||||||
loop: true
|
loop: true
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
touchLeft() {
|
||||||
|
if (this.loading != "success") return;
|
||||||
|
this.category.activeIndex += 1
|
||||||
|
if (this.category.activeIndex >= this.category.list.length) {
|
||||||
|
this.category.activeIndex = 0
|
||||||
|
}
|
||||||
|
this.lock = true
|
||||||
|
this.fnGetDataByCategory(this.category.activeIndex)
|
||||||
|
},
|
||||||
|
touchRight() {
|
||||||
|
if (this.loading != "success") return;
|
||||||
|
this.category.activeIndex -= 1
|
||||||
|
if (this.category.activeIndex < 0) {
|
||||||
|
this.category.activeIndex = 0
|
||||||
|
}
|
||||||
|
this.lock = true
|
||||||
|
this.fnGetDataByCategory(this.category.activeIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -269,11 +352,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 24rpx 24rpx 0;
|
|
||||||
gap: 12rpx 0;
|
|
||||||
|
|
||||||
.content-empty {
|
.content-empty {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -284,6 +366,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.touch-listen-content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 24rpx 24rpx 0;
|
||||||
|
gap: 12rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-wrap {
|
.loading-wrap {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
@@ -293,4 +384,14 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flot-buttons {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 100rpx;
|
||||||
|
right: 32rpx;
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
gap: 6rpx;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
+248
-48
@@ -21,22 +21,40 @@
|
|||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
</view>
|
</view>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<view class="bg-white pb-24">
|
<view v-if="bannerConfig.enabled" class="bg-white pb-24">
|
||||||
<view class="banner bg-white ml-24 mr-24 mt-12 round-3" v-if="bannerList.length !== 0">
|
<view class="banner bg-white ml-24 mr-24 mt-12 round-3" v-if="bannerList.length !== 0">
|
||||||
<e-swiper
|
<e-swiper :height="bannerConfig.height" :dotPosition="bannerConfig.dotPosition" :autoplay="true"
|
||||||
height="400rpx"
|
:useDot="bannerConfig.showIndicator" :showTitle="bannerConfig.showTitle"
|
||||||
dotPosition="right"
|
:type="bannerConfig.type" :list="bannerList" @on-click="fnOnBannerClick" />
|
||||||
:autoplay="true"
|
|
||||||
:useDot="false"
|
|
||||||
:list="bannerList"
|
|
||||||
@on-click="fnOnBannerClick"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 金刚区 :v-if="navList.filter(x=>x.show).length>=4" -->
|
||||||
|
<view v-if="calcIsShowQuickNavigationEnabled && !!navList.filter(x=>x.show).length" class="nav-box mt-16 mb-24 pl-24 pr-24 uh-py-12">
|
||||||
|
<view class="page-item_title text-weight-b ">快捷导航</view>
|
||||||
|
<view class="nav-list flex uh-mt-12">
|
||||||
|
<template v-for="(item,index) in navList">
|
||||||
|
<view v-if="item.show" class="nav-item" :key="index" @click="fnClickNav(item)">
|
||||||
|
<!-- :class="[item.bgClass]" -->
|
||||||
|
<view class="nav-item-icon" :style="{
|
||||||
|
'--bgColor':item.bgColor,
|
||||||
|
boxShadow: '0rpx 0rpx 6rpx ' + item.shadow,
|
||||||
|
backgroundColor: item.bgColor
|
||||||
|
}">
|
||||||
|
<tm-icons :size="48" color="white" prefx="halocoloricon" :name="item.icon"></tm-icons>
|
||||||
|
</view>
|
||||||
|
<view class="nav-item-text">
|
||||||
|
{{item.title}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 精品分类 -->
|
<!-- 精品分类 -->
|
||||||
<block v-if="calcIsShowCategory">
|
<block v-if="calcIsShowCategory">
|
||||||
<view class="flex flex-between mt-16 mb-24 pl-24 pr-24">
|
<view class="flex flex-between mt-16 mb-24 pl-24 pr-24">
|
||||||
<view class="page-item_title text-weight-b ">精品分类</view>
|
<view class="page-item_title text-weight-b ">精选分类</view>
|
||||||
<view class="show-more flex flex-center bg-white round-3" @click="fnToCategoryPage">
|
<view class="show-more flex flex-center bg-white round-3" @click="fnToCategoryPage">
|
||||||
<text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
|
<text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
|
||||||
</view>
|
</view>
|
||||||
@@ -82,10 +100,15 @@
|
|||||||
</tm-translate>
|
</tm-translate>
|
||||||
</view>
|
</view>
|
||||||
<view class="load-text mt-12">{{ loadMoreText }}</view>
|
<view class="load-text mt-12">{{ loadMoreText }}</view>
|
||||||
<tm-flotbutton v-if="articleList.length > 10" color="light-blue" @click="fnToTopPage" size="m"
|
<tm-flotbutton v-if="articleList.length > 10" :width="90" color="light-blue" @click="fnToTopPage"
|
||||||
icon="icon-angle-up"></tm-flotbutton>
|
size="s" icon="icon-angle-up"></tm-flotbutton>
|
||||||
</block>
|
</block>
|
||||||
</block>
|
</block>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 弹窗 -->
|
||||||
|
<NotifyDialog v-if="notify.show" :show="notify.show" :title="notify.data.title" :content="notify.data.content"
|
||||||
|
:url="notify.data.url" @on-change="fnOnNotifyChange"></NotifyDialog>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -96,8 +119,10 @@ import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
|||||||
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||||
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
|
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
|
||||||
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmGrid from '@/tm-vuetify/components/tm-grid/tm-grid.vue';
|
||||||
|
|
||||||
import eSwiper from '@/components/e-swiper/e-swiper.vue';
|
import eSwiper from '@/components/e-swiper/e-swiper.vue';
|
||||||
|
import NotifyDialog from "@/components/notify-dialog/notify-dialog.vue";
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -108,7 +133,9 @@ export default {
|
|||||||
tmFlotbutton,
|
tmFlotbutton,
|
||||||
tmIcons,
|
tmIcons,
|
||||||
tmEmpty,
|
tmEmpty,
|
||||||
eSwiper
|
tmGrid,
|
||||||
|
eSwiper,
|
||||||
|
NotifyDialog
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -126,6 +153,11 @@ export default {
|
|||||||
noticeList: [],
|
noticeList: [],
|
||||||
articleList: [],
|
articleList: [],
|
||||||
categoryList: [],
|
categoryList: [],
|
||||||
|
notify: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
navList: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -145,14 +177,40 @@ export default {
|
|||||||
mockJson() {
|
mockJson() {
|
||||||
return this.$tm.vx.getters().getMockJson;
|
return this.$tm.vx.getters().getMockJson;
|
||||||
},
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
|
calcIsShowQuickNavigationEnabled() {
|
||||||
|
console.log('this.haloConfigs?.pageConfig?.homeConfig', this.haloConfigs?.pageConfig?.homeConfig, !!this.haloConfigs?.pageConfig?.homeConfig?.useQuickNavigation)
|
||||||
|
return !!this.haloConfigs?.pageConfig?.homeConfig?.useQuickNavigation
|
||||||
|
},
|
||||||
calcIsShowCategory() {
|
calcIsShowCategory() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled && this.categoryList.length !== 0) {
|
if (this.calcAuditModeEnabled && this.categoryList.length !== 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return this.haloConfigs.pageConfig.homeConfig.useCategory
|
return this.haloConfigs.pageConfig.homeConfig.useCategory
|
||||||
|
},
|
||||||
|
calcVotePluginEnabled() {
|
||||||
|
return !!this.haloConfigs?.pluginConfig?.votePlugin?.enabled
|
||||||
|
},
|
||||||
|
calcLinksPluginEnabled() {
|
||||||
|
return !!this.haloConfigs?.pluginConfig?.linksPlugin?.enabled;
|
||||||
|
},
|
||||||
|
bannerConfig() {
|
||||||
|
return this.haloConfigs.pageConfig.homeConfig.bannerConfig
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
haloConfigs: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val) return;
|
||||||
|
this.fnGetNavList();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
@@ -167,7 +225,7 @@ export default {
|
|||||||
this.fnQuery();
|
this.fnQuery();
|
||||||
},
|
},
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
title: '没有更多数据了'
|
title: '没有更多数据了'
|
||||||
@@ -187,13 +245,12 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fnQuery() {
|
fnQuery() {
|
||||||
console.log('this.mockJson', this.mockJson)
|
|
||||||
this.fnGetBanner();
|
this.fnGetBanner();
|
||||||
this.fnGetArticleList();
|
this.fnGetArticleList();
|
||||||
this.fnGetCategoryList();
|
this.fnGetCategoryList();
|
||||||
},
|
},
|
||||||
fnGetCategoryList() {
|
fnGetCategoryList() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
this.categoryList = this.mockJson.home.categoryList.map((item) => {
|
this.categoryList = this.mockJson.home.categoryList.map((item) => {
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -215,10 +272,13 @@ export default {
|
|||||||
|
|
||||||
this.$httpApi.v2
|
this.$httpApi.v2
|
||||||
.getCategoryList({
|
.getCategoryList({
|
||||||
fieldSelector:['spec.hideFromList==false']
|
fieldSelector: ['spec.hideFromList=false']
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.categoryList = res.items.sort((a, b) => {
|
this.categoryList = res.items.map(item=>{
|
||||||
|
item.postCount = item.postCount ?? 0
|
||||||
|
return item;
|
||||||
|
}).sort((a, b) => {
|
||||||
return b.postCount - a.postCount;
|
return b.postCount - a.postCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -239,7 +299,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// 获取轮播图
|
// 获取轮播图
|
||||||
fnGetBanner() {
|
fnGetBanner() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
this.bannerList = this.mockJson.home.bannerList.map((item) => {
|
this.bannerList = this.mockJson.home.bannerList.map((item) => {
|
||||||
return {
|
return {
|
||||||
mp4: '',
|
mp4: '',
|
||||||
@@ -250,27 +310,37 @@ export default {
|
|||||||
createTime: item.time,
|
createTime: item.time,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
src: this.$utils.checkThumbnailUrl(item.cover),
|
src: this.$utils.checkThumbnailUrl(item.cover),
|
||||||
image: this.$utils.checkThumbnailUrl(item.cover)
|
image: this.$utils.checkThumbnailUrl(item.cover),
|
||||||
|
type: "custom",
|
||||||
|
content: "",
|
||||||
|
url: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const _this = this;
|
|
||||||
const _format = function (list) {
|
|
||||||
return list.map((item, index) => {
|
if (!this.bannerConfig.enabled) return;
|
||||||
|
|
||||||
|
if (this.bannerConfig.type === 'custom') {
|
||||||
|
this.bannerList = this.bannerConfig.list.map((item) => {
|
||||||
return {
|
return {
|
||||||
mp4: '',
|
mp4: '',
|
||||||
id: item.metadata.name,
|
id: Date.now() * Math.random(),
|
||||||
nickname: item.owner.displayName,
|
nickname: this.haloConfigs.authorConfig.blogger.nickname,
|
||||||
avatar: _this.$utils.checkAvatarUrl(item.owner.avatar),
|
avatar: this.$utils.checkAvatarUrl(this.haloConfigs.authorConfig.blogger.avatar),
|
||||||
address: '',
|
address: '',
|
||||||
createTime: uni.$tm.dayjs(item.spec.publishTime).fromNow(),
|
createTime: "",
|
||||||
title: item.spec.title,
|
title: item.title,
|
||||||
src: _this.$utils.checkThumbnailUrl(item.spec.cover),
|
src: this.$utils.checkThumbnailUrl(item.cover),
|
||||||
image: _this.$utils.checkThumbnailUrl(item.spec.cover)
|
image: this.$utils.checkThumbnailUrl(item.cover),
|
||||||
};
|
type: "custom",
|
||||||
});
|
content: item.content,
|
||||||
};
|
url: item.url
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const paramsStr = qs.stringify(this.queryParams, {
|
const paramsStr = qs.stringify(this.queryParams, {
|
||||||
allowDots: true,
|
allowDots: true,
|
||||||
@@ -283,20 +353,52 @@ export default {
|
|||||||
url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
|
url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
this.bannerList = _format(res.data.items);
|
this.bannerList = res.data.items.map((item, index) => {
|
||||||
|
return {
|
||||||
|
mp4: '',
|
||||||
|
id: item.metadata.name,
|
||||||
|
nickname: item.owner.displayName,
|
||||||
|
avatar: this.$utils.checkAvatarUrl(item.owner.avatar),
|
||||||
|
address: '',
|
||||||
|
createTime: uni.$tm.dayjs(item.spec.publishTime).fromNow(),
|
||||||
|
title: item.spec.title,
|
||||||
|
src: this.$utils.checkThumbnailUrl(item.spec.cover),
|
||||||
|
image: this.$utils.checkThumbnailUrl(item.spec.cover),
|
||||||
|
type: "post",
|
||||||
|
content: item.status.excerpt,
|
||||||
|
url: ""
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
fnOnBannerChange(e) {
|
fnOnNotifyChange(e) {
|
||||||
this.bannerCurrent = e.current;
|
this.notify.show = e;
|
||||||
},
|
},
|
||||||
fnOnBannerClick(item) {
|
fnOnBannerClick(item) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (item.type === 'custom') {
|
||||||
|
if (item.content) {
|
||||||
|
this.notify.data = item
|
||||||
|
this.notify.show = true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (uni.$utils.checkIsUrl(item.url)) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pagesC/website/website?data=' +
|
||||||
|
JSON.stringify({
|
||||||
|
title: item.title || "加载中...",
|
||||||
|
url: encodeURIComponent(item.url)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.id === '') return;
|
if (item.id === '') return;
|
||||||
this.fnToArticleDetail({
|
this.fnToArticleDetail({
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -306,7 +408,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// 文章列表
|
// 文章列表
|
||||||
fnGetArticleList() {
|
fnGetArticleList() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
this.articleList = this.mockJson.home.postList.map((item) => {
|
this.articleList = this.mockJson.home.postList.map((item) => {
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -371,7 +473,7 @@ export default {
|
|||||||
},
|
},
|
||||||
//跳转文章详情
|
//跳转文章详情
|
||||||
fnToArticleDetail(article) {
|
fnToArticleDetail(article) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@@ -409,7 +511,7 @@ export default {
|
|||||||
|
|
||||||
// 根据slug查询分类下的文章
|
// 根据slug查询分类下的文章
|
||||||
fnToCategoryBy(category) {
|
fnToCategoryBy(category) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@@ -435,6 +537,70 @@ export default {
|
|||||||
uni.switchTab({
|
uni.switchTab({
|
||||||
url: '/pages/tabbar/about/about'
|
url: '/pages/tabbar/about/about'
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
fnClickNav(data) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: data.path
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fnGetNavList() {
|
||||||
|
this.navList = [{
|
||||||
|
key: 'archives',
|
||||||
|
title: this.calcAuditModeEnabled ? '内容归档' : '文章归档',
|
||||||
|
bgColor: "rgba(3, 169, 244, 0.95)",
|
||||||
|
shadow: "rgba(3, 169, 244, 0.4)",
|
||||||
|
bgClass: 'bg-gradient-blue-accent',
|
||||||
|
icon: 'icon-news',
|
||||||
|
iconColor: '',
|
||||||
|
path: '/pagesA/archives/archives',
|
||||||
|
type: 'page',
|
||||||
|
show: true
|
||||||
|
}, {
|
||||||
|
key: 'vote',
|
||||||
|
title: '投票中心',
|
||||||
|
bgColor: "rgba(0, 188, 212,0.95)",
|
||||||
|
shadow: "rgba(0, 188, 212, 0.4)",
|
||||||
|
bgClass: 'bg-gradient-blue-accent',
|
||||||
|
icon: 'icon-box',
|
||||||
|
iconColor: '',
|
||||||
|
path: '/pagesA/votes/votes',
|
||||||
|
type: 'page',
|
||||||
|
show: !this.calcAuditModeEnabled && !!this.calcVotePluginEnabled,
|
||||||
|
}, {
|
||||||
|
key: 'disclaimers',
|
||||||
|
title: '友情链接',
|
||||||
|
bgColor: "rgba(0, 150, 136, 0.95)",
|
||||||
|
shadow: "rgba(0, 150, 136, 0.4)",
|
||||||
|
bgClass: 'bg-gradient-blue-accent',
|
||||||
|
icon: 'icon-lianjie',
|
||||||
|
iconColor: '',
|
||||||
|
path: '/pagesA/friend-links/friend-links',
|
||||||
|
type: 'page',
|
||||||
|
show: !!this.calcLinksPluginEnabled,
|
||||||
|
}, {
|
||||||
|
key: 'love',
|
||||||
|
title: '恋爱日记',
|
||||||
|
bgColor: "rgba(255,76,103, 0.95)",
|
||||||
|
shadow: "rgba(255,76,103, 0.4)",
|
||||||
|
bgClass: 'bg-gradient-blue-accent',
|
||||||
|
icon: 'icon-like',
|
||||||
|
iconColor: '',
|
||||||
|
path: '/pagesA/love/love',
|
||||||
|
type: 'page',
|
||||||
|
show: this.haloConfigs.loveConfig.loveEnabled
|
||||||
|
}, {
|
||||||
|
key: 'contact-blogger',
|
||||||
|
title: '联系博主',
|
||||||
|
bgColor: "rgba(255, 152, 0, 0.95)",
|
||||||
|
shadow: "rgba(255, 152, 0, 0.4)",
|
||||||
|
bgClass: 'bg-gradient-blue-accent',
|
||||||
|
icon: 'icon-paper-plane',
|
||||||
|
iconColor: '',
|
||||||
|
rightText: '博主主常用常用联系方式',
|
||||||
|
path: '/pagesA/contact/contact',
|
||||||
|
type: 'page',
|
||||||
|
show: this.haloConfigs.authorConfig.social.enabled
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -472,11 +638,9 @@ export default {
|
|||||||
margin-right: 24rpx;
|
margin-right: 24rpx;
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
&_icon {
|
&_icon {}
|
||||||
}
|
|
||||||
|
|
||||||
&_text {
|
&_text {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-more {
|
.show-more {
|
||||||
@@ -553,4 +717,40 @@ export default {
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-box {
|
||||||
|
background-color: #ffff;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
// justify-content: space-between;
|
||||||
|
//justify-content: space-around;
|
||||||
|
//justify-content: start;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
font-size: 26rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-icon {
|
||||||
|
padding: 24rpx;
|
||||||
|
// border-radius: 24rpx 32rpx 24rpx 32rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
// border: 2rpx solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="app-page">
|
<view class="app-page" :class="[uniHaloPluginPageClass]">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
|
||||||
|
:error-text="uniHaloPluginAvailableError" />
|
||||||
|
<template v-else>
|
||||||
<view v-if="loading !== 'success'" class="loading-wrap">
|
<view v-if="loading !== 'success'" class="loading-wrap">
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
@@ -12,9 +15,9 @@
|
|||||||
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
|
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
|
||||||
</view>
|
</view>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<block v-for="(moment, index) in dataList" :key="index">
|
|
||||||
<!-- 卡片 -->
|
<!-- 卡片 -->
|
||||||
<tm-translate v-if="moment.spec.visible==='PUBLIC'" animation-name="fadeUp" :wait="calcAniWait()">
|
<tm-translate v-for="(moment, index) in dataList" :key="moment.metadata.name"
|
||||||
|
animation-name="fadeUp" :wait="calcAniWait(index)">
|
||||||
<view class="moment-card">
|
<view class="moment-card">
|
||||||
<view class="head" style="display: flex;align-items: center;">
|
<view class="head" style="display: flex;align-items: center;">
|
||||||
<view class="avatar" style="flex-shrink: 0;">
|
<view class="avatar" style="flex-shrink: 0;">
|
||||||
@@ -30,37 +33,52 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="content">
|
<view class="content" @click.stop="handleToMomentDetail(moment)">
|
||||||
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
||||||
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
||||||
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||||
:content="moment.spec.content.html" :markdown="true" :showLineNumber="true"
|
:content="moment.spec.newHtml" :markdown="true" :showLineNumber="true"
|
||||||
:showLanguageName="true" :copyByLongPress="true" />
|
:showLanguageName="true" :copyByLongPress="true" />
|
||||||
</view>
|
</view>
|
||||||
<view class="mb-12 mt--12" v-if="moment.videos.length!==0"
|
<view v-if="moment.images && moment.images.length!==0" class="images"
|
||||||
style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx; ">
|
|
||||||
<video
|
|
||||||
style="width:100%;height: 400rpx;border-radius: 12rpx;"
|
|
||||||
v-for="(video,index) in moment.videos"
|
|
||||||
:key="index" :src="video.url"></video>
|
|
||||||
</view>
|
|
||||||
<view v-if="moment.images.length!==0" class="images"
|
|
||||||
:class="['images-'+moment.images.length]">
|
:class="['images-'+moment.images.length]">
|
||||||
<view class="image-item"
|
<view class="image-item" v-for="(image,mediumIndex) in moment.images"
|
||||||
v-for="(image,mediumIndex) in moment.images"
|
|
||||||
:key="mediumIndex">
|
:key="mediumIndex">
|
||||||
<image mode="aspectFill" style="width: 100%;height: 100%;border-radius: 6rpx;"
|
<image mode="aspectFill" style="width: 100%;height: 100%;border-radius: 6rpx;"
|
||||||
:src="image.url"
|
:src="image.url" @click="handlePreview(mediumIndex,moment.images)" />
|
||||||
@click="handlePreview(mediumIndex,moment.images)"/>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="moment.audios && moment.audios.length!==0" class="mb-12"
|
||||||
|
style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx;padding-right:28rpx;">
|
||||||
|
<audio v-for="(audio,index) in moment.audios" :controls="true" :key="index"
|
||||||
|
:id="audio.url" :poster="bloggerInfo.avatar"
|
||||||
|
:name="'来自' + (startConfig.title||bloggerInfo.nickname) + '的声音'"
|
||||||
|
:author="bloggerInfo.nickname" :src="audio.url"></audio>
|
||||||
|
</view>
|
||||||
|
<view v-if="moment.videos && moment.videos.length!==0" class="mb-12"
|
||||||
|
style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx; ">
|
||||||
|
<video style="width:100%;height: 400rpx;border-radius: 12rpx;"
|
||||||
|
v-for="(video,index) in moment.videos" :key="index" :src="video.url"
|
||||||
|
:id="'video_' + video.id" :show-mute-btn="true" :controls="true"
|
||||||
|
:show-center-play-btn="true" :enable-progress-gesture="true"
|
||||||
|
@play="onVideoPlay(video.id)" @pause="onVideoPause(video.id)"
|
||||||
|
@ended="onVideoEnded(video.id)"></video>
|
||||||
|
</view>
|
||||||
|
<view v-if="moment.spec.tags && moment.spec.tags.length!==0"
|
||||||
|
class="mt-12 px-16 pb-24 flex flex-wrap">
|
||||||
|
<tm-tags v-for="(tag,tagIndex) in moment.spec.tags" :key="tagIndex"
|
||||||
|
:color="randomTagColor()" size="m" model="text">
|
||||||
|
{{ tag }}
|
||||||
|
</tm-tags>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</tm-translate>
|
</tm-translate>
|
||||||
</block>
|
<tm-flotbutton @click="fnToTopPage" :width="90" size="xs" color="light-blue" :icon-size="24"
|
||||||
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
|
icon="icon-angle-up"></tm-flotbutton>
|
||||||
<view class="load-text">{{ loadMoreText }}</view>
|
<view class="load-text">{{ loadMoreText }}</view>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -69,17 +87,30 @@ import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
|||||||
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||||
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
||||||
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
|
||||||
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||||
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||||
|
import {
|
||||||
|
getRandomNumberByRange
|
||||||
|
} from "@/utils/random.js";
|
||||||
|
import {
|
||||||
|
generateUUID
|
||||||
|
} from '@/utils/uuid.js';
|
||||||
|
|
||||||
|
import pluginAvailableMixin from "@/common/mixins/pluginAvailable.js"
|
||||||
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [pluginAvailableMixin],
|
||||||
components: {
|
components: {
|
||||||
tmSkeleton,
|
tmSkeleton,
|
||||||
tmFlotbutton,
|
tmFlotbutton,
|
||||||
tmTranslate,
|
tmTranslate,
|
||||||
tmEmpty,
|
tmEmpty,
|
||||||
mpHtml
|
tmTags,
|
||||||
|
mpHtml,
|
||||||
|
PluginUnavailable
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -92,7 +123,10 @@ export default {
|
|||||||
hasNext: false,
|
hasNext: false,
|
||||||
dataList: [],
|
dataList: [],
|
||||||
isLoadMore: false,
|
isLoadMore: false,
|
||||||
loadMoreText: '加载中...'
|
loadMoreText: '加载中...',
|
||||||
|
tagColors: ['orange', 'green', 'red', 'blue'],
|
||||||
|
videoContexts: {},
|
||||||
|
currentVideoId: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -107,20 +141,40 @@ export default {
|
|||||||
},
|
},
|
||||||
mockJson() {
|
mockJson() {
|
||||||
return this.$tm.vx.getters().getMockJson;
|
return this.$tm.vx.getters().getMockJson;
|
||||||
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
|
calcUseTagRandomColor() {
|
||||||
|
return this.haloConfigs.pageConfig.momentConfig.useTagRandomColor
|
||||||
|
},
|
||||||
|
startConfig() {
|
||||||
|
return this.haloConfigs.appConfig.startConfig;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async onLoad() {
|
||||||
|
// 检查插件
|
||||||
|
this.setPluginId(this.NeedPluginIds.PluginMoments)
|
||||||
|
this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用瞬间功能哦,请联系管理员")
|
||||||
|
if (!await this.checkPluginAvailable()) return
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
onPullDownRefresh() {
|
onPullDownRefresh() {
|
||||||
|
if (!this.uniHaloPluginAvailable) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return
|
||||||
|
}
|
||||||
this.isLoadMore = false;
|
this.isLoadMore = false;
|
||||||
this.queryParams.page = 0;
|
this.queryParams.page = 0;
|
||||||
|
this.videoContexts = {};
|
||||||
|
this.currentVideoId = null;
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
|
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (!this.uniHaloPluginAvailable) return;
|
||||||
|
if (this.calcAuditModeEnabled) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
title: '没有更多数据了'
|
title: '没有更多数据了'
|
||||||
@@ -140,7 +194,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fnGetData() {
|
fnGetData() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
this.dataList = this.mockJson.moments.list.map((item) => {
|
this.dataList = this.mockJson.moments.list.map((item) => {
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -188,7 +242,7 @@ export default {
|
|||||||
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
|
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
|
||||||
this.hasNext = res.hasNext;
|
this.hasNext = res.hasNext;
|
||||||
|
|
||||||
const tempItems = res.items.map(item => {
|
const tempItems = res.items.filter(x => x.spec.visible === 'PUBLIC').map(item => {
|
||||||
item.spec.user = {
|
item.spec.user = {
|
||||||
displayName: this.bloggerInfo.nickname,
|
displayName: this.bloggerInfo.nickname,
|
||||||
avatar: this.$utils.checkAvatarUrl(this.bloggerInfo.avatar)
|
avatar: this.$utils.checkAvatarUrl(this.bloggerInfo.avatar)
|
||||||
@@ -197,11 +251,18 @@ export default {
|
|||||||
medium.url = this.$utils.checkThumbnailUrl(medium.url, true)
|
medium.url = this.$utils.checkThumbnailUrl(medium.url, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
item.spec.newHtml = this.removeTagLinksCompletely(item.spec.content.html, '')
|
||||||
item['images'] = item.spec.content.medium
|
item['images'] = item.spec.content.medium
|
||||||
.filter(x => x.type === 'PHOTO')
|
.filter(x => x.type === 'PHOTO')
|
||||||
|
|
||||||
item['videos'] = item.spec.content.medium
|
item['videos'] = item.spec.content.medium
|
||||||
.filter(x => x.type === 'VIDEO')
|
.filter(x => x.type === 'VIDEO').map(item => {
|
||||||
|
item.id = generateUUID()
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
|
||||||
|
item['audios'] = item.spec.content.medium
|
||||||
|
.filter(x => x.type === 'AUDIO')
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -210,6 +271,11 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.dataList = tempItems;
|
this.dataList = tempItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.createVideoContexts(tempItems);
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -228,6 +294,49 @@ export default {
|
|||||||
current: index,
|
current: index,
|
||||||
urls: list.map(item => item.url)
|
urls: list.map(item => item.url)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
removeTagLinksCompletely(htmlString) {
|
||||||
|
const regex = /<a\s+(?:[^>]*?\s+)?class=(['"])[^'"]*?\btag\b[^'"]*?\1[^>]*?>.*?<\/a>/gi;
|
||||||
|
const newHtml = htmlString.replace(regex, '');
|
||||||
|
return newHtml
|
||||||
|
},
|
||||||
|
randomTagColor() {
|
||||||
|
if (!this.calcUseTagRandomColor) return "blue";
|
||||||
|
const randomIndex = getRandomNumberByRange(0, this.tagColors.length);
|
||||||
|
return this.tagColors[randomIndex];
|
||||||
|
},
|
||||||
|
createVideoContexts(list) {
|
||||||
|
this.stopAllVideos()
|
||||||
|
list.map(item => item.videos).flat().forEach(item => {
|
||||||
|
this.videoContexts[item.id] = uni.createVideoContext(`video_${item.id}`, this);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onVideoPlay(videoId) {
|
||||||
|
this.currentVideoId = videoId;
|
||||||
|
this.stopAllVideos(videoId)
|
||||||
|
},
|
||||||
|
onVideoPause(videoId) {
|
||||||
|
if (this.currentVideoId == videoId) {
|
||||||
|
this.currentVideoId = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onVideoEnded(videoId) {
|
||||||
|
this.currentVideoId = null;
|
||||||
|
},
|
||||||
|
stopAllVideos(excludesVideoId = null) {
|
||||||
|
Object.keys(this.videoContexts).forEach(videoId => {
|
||||||
|
if (!excludesVideoId || excludesVideoId != videoId) {
|
||||||
|
const videoContext = this.videoContexts[videoId]
|
||||||
|
videoContext?.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleToMomentDetail(moment) {
|
||||||
|
if (this.calcAuditModeEnabled) return;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pagesA/moment-detail/moment-detail?name=' + moment.metadata.name,
|
||||||
|
animationType: 'slide-in-right'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -235,7 +344,9 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.app-page {
|
.app-page {
|
||||||
|
box-sizing: border-box;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 24rpx 0;
|
padding: 24rpx 0;
|
||||||
@@ -291,4 +402,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep .uni-audio-default {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<!-- 加载完成区域 -->
|
<!-- 加载完成区域 -->
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<view v-if="dataList.length === 0" class="list-empty flex flex-center">
|
<view v-if="dataList.length === 0" class="list-empty flex flex-center">
|
||||||
<tm-empty v-if="haloConfigs.basicConfig.auditModeEnabled" icon="icon-shiliangzhinengduixiang-" label="暂无归档的内容"></tm-empty>
|
<tm-empty v-if="calcAuditModeEnabled" icon="icon-shiliangzhinengduixiang-" label="暂无归档的内容"></tm-empty>
|
||||||
<tm-empty v-else icon="icon-shiliangzhinengduixiang-" label="暂无归档的文章"></tm-empty>
|
<tm-empty v-else icon="icon-shiliangzhinengduixiang-" label="暂无归档的文章"></tm-empty>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="e-timeline tm-timeline mt-24">
|
<view v-else class="e-timeline tm-timeline mt-24">
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<view class="flex time text-weight-b mb-24">
|
<view class="flex time text-weight-b mb-24">
|
||||||
<text>{{ item.year }}年</text>
|
<text>{{ item.year }}年</text>
|
||||||
<text v-if="tab.activeIndex === 0">{{ item.month }}月</text>
|
<text v-if="tab.activeIndex === 0">{{ item.month }}月</text>
|
||||||
<view v-if="haloConfigs.basicConfig.auditModeEnabled" class="text-size-s text-grey-darken-1 ml-12">
|
<view v-if="calcAuditModeEnabled" class="text-size-s text-grey-darken-1 ml-12">
|
||||||
(共 {{ item.posts.length }} 篇内容)
|
(共 {{ item.posts.length }} 篇内容)
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="text-size-s text-grey-darken-1 ml-12">
|
<view v-else class="text-size-s text-grey-darken-1 ml-12">
|
||||||
@@ -128,7 +128,10 @@ export default {
|
|||||||
},
|
},
|
||||||
mockJson() {
|
mockJson() {
|
||||||
return this.$tm.vx.getters().getMockJson;
|
return this.$tm.vx.getters().getMockJson;
|
||||||
}
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
@@ -139,7 +142,7 @@ export default {
|
|||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
title: '没有更多数据了'
|
title: '没有更多数据了'
|
||||||
@@ -165,7 +168,7 @@ export default {
|
|||||||
this.fnToTopPage();
|
this.fnToTopPage();
|
||||||
},
|
},
|
||||||
fnGetData() {
|
fnGetData() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
const dataList = this.mockJson.archives.list.map(item => {
|
const dataList = this.mockJson.archives.list.map(item => {
|
||||||
const date = new Date(item.time)
|
const date = new Date(item.time)
|
||||||
const year = date.getFullYear()
|
const year = date.getFullYear()
|
||||||
@@ -353,7 +356,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
fnToArticleDetail(article) {
|
fnToArticleDetail(article) {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="app-page" :class="{ 'is-balck grey-darken-6': isBlackTheme }">
|
<view class="app-page" :class="[uniHaloPluginPageClass]">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
|
||||||
|
:error-text="uniHaloPluginAvailableError" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<!-- 顶部切换 -->
|
<!-- 顶部切换 -->
|
||||||
<view class="e-fixed shadow-2">
|
<view class="e-fixed shadow-1">
|
||||||
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue"
|
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue"
|
||||||
insert-color="light-blue" :clear="true" @input="fnOnSearch" @confirm="fnOnSearch"></tm-search>
|
insert-color="light-blue" :clear="true" @input="fnOnSearch" @confirm="fnOnSearch"></tm-search>
|
||||||
<tm-tabs v-if="false" color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list"
|
<tm-tabs v-if="false" color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list"
|
||||||
@@ -10,39 +14,57 @@
|
|||||||
<!-- 占位区域 -->
|
<!-- 占位区域 -->
|
||||||
<view style="width: 100vw;height: 100rpx;"></view>
|
<view style="width: 100vw;height: 100rpx;"></view>
|
||||||
<!-- 加载区域 -->
|
<!-- 加载区域 -->
|
||||||
<view v-if="loading != 'success'" class="loading-wrap pa-24">
|
<view v-if="loading == 'loading'" class="loading-wrap pa-24">
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-else-if="loading == 'error'" class="content-empty flex flex-center">
|
||||||
|
<tm-empty icon="icon-wind-cry" label="搜索异常"></tm-empty>
|
||||||
|
</view>
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view v-else class="content">
|
<view v-else class="content">
|
||||||
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
|
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
|
||||||
<!-- 空布局 -->
|
<!-- 空布局 -->
|
||||||
<tm-empty v-if="!queryParams.keyword" icon="icon-shiliangzhinengduixiang-" label="请输入关键词搜索"></tm-empty>
|
<tm-empty v-if="!queryParams.keyword" icon="icon-shiliangzhinengduixiang-"
|
||||||
|
label="请输入关键词搜索"></tm-empty>
|
||||||
<tm-empty v-else icon="icon-shiliangzhinengduixiang-"
|
<tm-empty v-else icon="icon-shiliangzhinengduixiang-"
|
||||||
:label="`未搜到 ${queryParams.keyword} 相关内容`"></tm-empty>
|
:label="`未搜到 ${queryParams.keyword} 相关内容`"></tm-empty>
|
||||||
</view>
|
</view>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<!-- 文章卡片 -->
|
<tm-translate v-for="(item, index) in dataList" :key="item.metadataName" animation-name="fadeUp"
|
||||||
<tm-translate v-for="(article, index) in dataList" :key="article.metadataName" animation-name="fadeUp"
|
|
||||||
:wait="calcAniWait(index)">
|
:wait="calcAniWait(index)">
|
||||||
<view class="article-card" @click="fnToArticleDetail(article)">
|
<view class="article-card" @click="fnToDetail(item)">
|
||||||
<rich-text style="font-size: 32rpx;font-weight: bold;color: #333;"
|
<view class="mb-12 flex flex-start">
|
||||||
:nodes="article.title"></rich-text>
|
<view class="flex-shrink ml--12">
|
||||||
<rich-text style="font-size: 28rpx;margin-top: 16rpx;color: #555;"
|
<tm-tags v-if="isArticle(item)" color="blue" size="n" model="text">文章</tm-tags>
|
||||||
:nodes="article.content"></rich-text>
|
<tm-tags v-else color="green" size="n" model="text">瞬间</tm-tags>
|
||||||
<text style="font-size: 24rpx;margin-top: 24rpx;color:#888">
|
</view>
|
||||||
最近更新:{{ {d: article.updateTimestamp, f: 'yyyy年MM月dd日 HH点mm分ss秒'} | formatTime }}
|
<text class="ml-2 text-overflow text-size-n text-weight-b"
|
||||||
|
style="color: #333;">{{ item.title }}</text>
|
||||||
|
</view>
|
||||||
|
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
||||||
|
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
||||||
|
:tag-style="markdownConfig.tagStyle" :content="item.description || item.content"
|
||||||
|
:markdown="true" :showLineNumber="true" :showLanguageName="true"
|
||||||
|
:copyByLongPress="true" />
|
||||||
|
<view class="mt-12 flex flex-center flex-between">
|
||||||
|
<text style="font-size: 24rpx;color:#888">
|
||||||
|
最近更新时间:{{ {d: item.updateTimestamp, f: 'yyyy年MM月dd日 HH点mm分ss秒'} | formatTime }}
|
||||||
</text>
|
</text>
|
||||||
|
<!-- <tm-tags v-if="isArticle(item)" color="blue" size="n" model="text">文章</tm-tags>
|
||||||
|
<tm-tags v-else color="green" size="n" model="text">瞬间</tm-tags> -->
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</tm-translate>
|
</tm-translate>
|
||||||
|
|
||||||
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
|
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue"
|
||||||
|
icon="icon-angle-up"></tm-flotbutton>
|
||||||
|
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -53,19 +75,30 @@ import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
|||||||
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
|
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
|
||||||
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||||
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
|
||||||
|
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||||
|
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||||
|
|
||||||
|
import pluginAvailableMixin from "@/common/mixins/pluginAvailable.js"
|
||||||
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [pluginAvailableMixin],
|
||||||
components: {
|
components: {
|
||||||
tmSkeleton,
|
tmSkeleton,
|
||||||
tmSearch,
|
tmSearch,
|
||||||
tmTranslate,
|
tmTranslate,
|
||||||
tmTabs,
|
tmTabs,
|
||||||
tmFlotbutton,
|
tmFlotbutton,
|
||||||
tmEmpty
|
tmEmpty,
|
||||||
|
tmTags,
|
||||||
|
mpHtml,
|
||||||
|
PluginUnavailable
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isBlackTheme: false,
|
markdownConfig: MarkdownConfig,
|
||||||
loading: 'loading',
|
loading: 'loading',
|
||||||
tab: {
|
tab: {
|
||||||
activeIndex: 0,
|
activeIndex: 0,
|
||||||
@@ -73,9 +106,9 @@ export default {
|
|||||||
},
|
},
|
||||||
queryParams: {
|
queryParams: {
|
||||||
keyword: "",
|
keyword: "",
|
||||||
limit: 5,
|
limit: 50,
|
||||||
highlightPreTag: "<text>",
|
highlightPreTag: "",
|
||||||
highlightPostTag: "</text>"
|
highlightPostTag: ""
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
dataList: [],
|
dataList: [],
|
||||||
@@ -88,41 +121,41 @@ export default {
|
|||||||
haloConfigs() {
|
haloConfigs() {
|
||||||
return this.$tm.vx.getters().getConfigs;
|
return this.$tm.vx.getters().getConfigs;
|
||||||
},
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
},
|
},
|
||||||
onLoad() {
|
},
|
||||||
|
async onLoad() {
|
||||||
this.fnSetPageTitle('内容搜索');
|
this.fnSetPageTitle('内容搜索');
|
||||||
},
|
// 检查插件
|
||||||
created() {
|
this.setPluginId(this.NeedPluginIds.PluginSearchWidget)
|
||||||
|
this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用搜索功能哦,请联系管理员")
|
||||||
|
if (!await this.checkPluginAvailable()) return
|
||||||
if (!this.queryParams.keyword) {
|
if (!this.queryParams.keyword) {
|
||||||
this.loading = 'success'
|
this.loading = 'success'
|
||||||
} else {
|
} else {
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onPullDownRefresh() {
|
onPullDownRefresh() {
|
||||||
this.fnResetSetAniWaitIndex();
|
if (!this.uniHaloPluginAvailable) {
|
||||||
this.fnGetData();
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fnOnSearch()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fnOnTabChange(index) {
|
fnOnTabChange(index) {
|
||||||
this.fnResetSetAniWaitIndex();
|
this.fnResetSetAniWaitIndex();
|
||||||
this.dataList = [];
|
this.dataList = [];
|
||||||
const _sorts = {
|
|
||||||
0: '',
|
|
||||||
1: 'topPriority,createTime,desc',
|
|
||||||
2: 'topPriority,visits,desc',
|
|
||||||
3: 'topPriority,updateTime,desc',
|
|
||||||
4: 'topPriority,likes,desc'
|
|
||||||
};
|
|
||||||
this.queryParams.sort = _sorts[index];
|
|
||||||
this.dataList = [];
|
|
||||||
this.fnToTopPage();
|
this.fnToTopPage();
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
fnOnSearch() {
|
fnOnSearch() {
|
||||||
this.fnResetSetAniWaitIndex();
|
this.fnResetSetAniWaitIndex();
|
||||||
|
|
||||||
if (!this.queryParams.keyword) {
|
if (!this.queryParams.keyword) {
|
||||||
this.dataList = []
|
this.dataList = []
|
||||||
} else {
|
} else {
|
||||||
@@ -130,7 +163,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fnGetData() {
|
fnGetData() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 设置状态为加载中
|
// 设置状态为加载中
|
||||||
@@ -152,13 +185,24 @@ export default {
|
|||||||
}, 800);
|
}, 800);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isArticle(item) {
|
||||||
//跳转文章详情
|
return item.type == 'post.content.halo.run'
|
||||||
fnToArticleDetail(article) {
|
},
|
||||||
|
//跳转详情
|
||||||
|
fnToDetail(item) {
|
||||||
|
if (this.calcAuditModeEnabled) return;
|
||||||
|
if (this.isArticle(item)) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pagesA/article-detail/article-detail?name=' + article.metadataName,
|
url: '/pagesA/article-detail/article-detail?name=' + item.metadataName,
|
||||||
animationType: 'slide-in-right'
|
animationType: 'slide-in-right'
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pagesA/moment-detail/moment-detail?name=' + item.metadataName,
|
||||||
|
animationType: 'slide-in-right'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -180,11 +224,11 @@ export default {
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 24rpx;
|
padding-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.content-empty {
|
.content-empty {
|
||||||
height: 60vh;
|
height: 60vh;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.article-card {
|
.article-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ export default {
|
|||||||
this.queryParams.page = 0;
|
this.queryParams.page = 0;
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
|
onShareAppMessage() {
|
||||||
|
return {
|
||||||
|
title: this.pageTitle,
|
||||||
|
path: `/pagesA/category-detail/category-detail?name=${this.name}&title=${this.pageTitle}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShareTimeline() {
|
||||||
|
return {
|
||||||
|
title: this.pageTitle,
|
||||||
|
path: `/pagesA/category-detail/category-detail?name=${this.name}&title=${this.pageTitle}`
|
||||||
|
}
|
||||||
|
},
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
if (this.hasNext) {
|
if (this.hasNext) {
|
||||||
this.queryParams.page += 1;
|
this.queryParams.page += 1;
|
||||||
|
|||||||
@@ -0,0 +1,549 @@
|
|||||||
|
<template>
|
||||||
|
<view class="app-page flex flex-col pa-24" :class="[uniHaloPluginPageClass]">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId" :error-text="uniHaloPluginAvailableError" />
|
||||||
|
<template v-else>
|
||||||
|
<!-- 加载区域 -->
|
||||||
|
<view v-if="loading == 'loading'" class="loading-wrap pa-24">
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="loading == 'error'" class="content-empty flex flex-center">
|
||||||
|
<tm-empty icon="icon-wind-cry" label="加载异常"></tm-empty>
|
||||||
|
</view>
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<view v-else class="content flex flex-col uh-gap-y-12">
|
||||||
|
<!-- 标签统计 -->
|
||||||
|
<view class="card bg-white pa-24 round-4">
|
||||||
|
<view class="card-head flex items-center justify-between">
|
||||||
|
<view class="card-head_title flex items-end uh-gap-x-4">
|
||||||
|
<text class="card-head_text">标签统计</text>
|
||||||
|
<text class="card-head_subtext">(全部标签的文章数量占比)</text>
|
||||||
|
</view>
|
||||||
|
<view @click="tagChart.isExpand = !tagChart.isExpand">
|
||||||
|
<tm-icons v-if="tagChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
|
||||||
|
<tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-show="tagChart.isExpand" class="card-body flex">
|
||||||
|
<view class="chart-box">
|
||||||
|
<qiun-data-charts :type="tagChart.type" :opts="tagChart.opts" :chartData="tagChart.data" :tooltipFormat="tagChart.tooltipFormat" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 分类统计 -->
|
||||||
|
<view class="card bg-white pa-24 round-4">
|
||||||
|
<view class="card-head flex items-center justify-between">
|
||||||
|
<view class="card-head_title flex items-end uh-gap-x-4">
|
||||||
|
<text class="card-head_text">分类统计</text>
|
||||||
|
<text class="card-head_subtext">(全部分类的文章数量占比)</text>
|
||||||
|
</view>
|
||||||
|
<view @click="categoryChart.isExpand = !categoryChart.isExpand">
|
||||||
|
<tm-icons v-if="categoryChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
|
||||||
|
<tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-show="categoryChart.isExpand" class="card-body flex">
|
||||||
|
<view class="chart-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
:canvasId="categoryChart.id"
|
||||||
|
:canvas2d="true"
|
||||||
|
:ontouch="true"
|
||||||
|
:type="categoryChart.type"
|
||||||
|
:opts="categoryChart.opts"
|
||||||
|
:chartData="categoryChart.data"
|
||||||
|
:tooltipFormat="categoryChart.tooltipFormat"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 文章发布趋势 -->
|
||||||
|
<view class="card bg-white pa-24 round-4">
|
||||||
|
<view class="card-head flex items-center justify-between">
|
||||||
|
<view class="card-head_title flex items-end uh-gap-x-4">
|
||||||
|
<text class="card-head_text">文章发布趋势</text>
|
||||||
|
<text class="card-head_subtext">(按日期统计文章发布数量)</text>
|
||||||
|
</view>
|
||||||
|
<view @click="trandArticleChart.isExpand = !trandArticleChart.isExpand">
|
||||||
|
<tm-icons v-if="trandArticleChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
|
||||||
|
<tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-show="trandArticleChart.isExpand" class="card-body flex">
|
||||||
|
<heatmap style="width: 100%" :chartData="trandArticleChart.data"></heatmap>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 评论活跃用户 -->
|
||||||
|
<view class="card bg-white pa-24 round-4">
|
||||||
|
<view class="card-head flex items-center justify-between">
|
||||||
|
<view class="card-head_title flex items-end uh-gap-x-4">
|
||||||
|
<text class="card-head_text">评论活跃用户</text>
|
||||||
|
<text class="card-head_subtext">(按评论作者统计评论数量)</text>
|
||||||
|
</view>
|
||||||
|
<view @click="userCommentsChart.isExpand = !userCommentsChart.isExpand">
|
||||||
|
<tm-icons v-if="userCommentsChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
|
||||||
|
<tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-show="userCommentsChart.isExpand" class="card-body flex">
|
||||||
|
<view class="chart-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
:canvasId="userCommentsChart.id"
|
||||||
|
:canvas2d="true"
|
||||||
|
:ontouch="true"
|
||||||
|
:type="userCommentsChart.type"
|
||||||
|
:opts="userCommentsChart.opts"
|
||||||
|
:chartData="userCommentsChart.data"
|
||||||
|
:tooltipFormat="userCommentsChart.tooltipFormat"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 热门文章 Top10 -->
|
||||||
|
<view class="card bg-white pa-24 round-4">
|
||||||
|
<view class="card-head flex items-center justify-between">
|
||||||
|
<view class="card-head_title flex items-end uh-gap-x-4">
|
||||||
|
<text class="card-head_text">热门文章前10</text>
|
||||||
|
<text class="card-head_subtext">(按访问量排序的热门文章)</text>
|
||||||
|
</view>
|
||||||
|
<view @click="top10ArticlesChart.isExpand = !top10ArticlesChart.isExpand">
|
||||||
|
<tm-icons v-if="top10ArticlesChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
|
||||||
|
<tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="card-body flex">
|
||||||
|
<view class="chart-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
:canvasId="top10ArticlesChart.id"
|
||||||
|
:canvas2d="true"
|
||||||
|
:ontouch="true"
|
||||||
|
:type="top10ArticlesChart.type"
|
||||||
|
:opts="top10ArticlesChart.opts"
|
||||||
|
:chartData="top10ArticlesChart.data"
|
||||||
|
:tooltipFormat="top10ArticlesChart.tooltipFormat"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dataStatisticsApi from '@/api/v2/plugin.data-statistics.js';
|
||||||
|
import { NeedPluginIds, NeedPlugins, checkNeedPluginAvailable } from '@/utils/plugin.js';
|
||||||
|
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||||
|
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
||||||
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
|
||||||
|
|
||||||
|
import pluginAvailableMixin from '@/common/mixins/pluginAvailable.js';
|
||||||
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue';
|
||||||
|
|
||||||
|
import heatmap from '@/components/heatmap/heatmap.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [pluginAvailableMixin],
|
||||||
|
name: 'DataVisual',
|
||||||
|
components: {
|
||||||
|
tmSkeleton,
|
||||||
|
tmTranslate,
|
||||||
|
tmEmpty,
|
||||||
|
tmButton,
|
||||||
|
tmIcons,
|
||||||
|
heatmap,
|
||||||
|
PluginUnavailable
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: 'loading',
|
||||||
|
statistics: null,
|
||||||
|
tagChart: {
|
||||||
|
id: 'tagChart',
|
||||||
|
isExpand: true,
|
||||||
|
type: 'ring',
|
||||||
|
data: {},
|
||||||
|
tooltipFormat: 'tooltipTag',
|
||||||
|
opts: {
|
||||||
|
rotate: false,
|
||||||
|
rotateLock: false,
|
||||||
|
color: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#ea7ccc', '#0EA5E9'],
|
||||||
|
padding: [5, 5, 5, 5],
|
||||||
|
dataLabel: false,
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
position: 'right',
|
||||||
|
lineHeight: 18,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
name: '',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#666666'
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
name: '',
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#eee'
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
ring: {
|
||||||
|
ringWidth: 36,
|
||||||
|
activeOpacity: 0.5,
|
||||||
|
activeRadius: 10,
|
||||||
|
offsetAngle: -90,
|
||||||
|
labelWidth: 15,
|
||||||
|
border: true,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#FFFFFF'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
legendShape: 'circle',
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
categoryChart: {
|
||||||
|
id: 'categoryChart',
|
||||||
|
isExpand: true,
|
||||||
|
type: 'line',
|
||||||
|
data: {},
|
||||||
|
tooltipFormat: 'tooltipCategory',
|
||||||
|
opts: {
|
||||||
|
color: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#ea7ccc', '#0EA5E9'],
|
||||||
|
padding: [20, 15, 10, 15],
|
||||||
|
touchMoveLimit: 24,
|
||||||
|
enableScroll: true,
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
fontSize: 11,
|
||||||
|
scrollShow: true,
|
||||||
|
itemCount: 6,
|
||||||
|
boundaryGap: 'center',
|
||||||
|
format: 'xAxisCategory'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 4,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
line: {
|
||||||
|
type: 'curve',
|
||||||
|
width: 2,
|
||||||
|
activeType: 'hollow'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
legendShape: 'circle',
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trandArticleChart: {
|
||||||
|
isExpand: true,
|
||||||
|
type: 'hotmap',
|
||||||
|
data: [],
|
||||||
|
opts: {}
|
||||||
|
},
|
||||||
|
userCommentsChart: {
|
||||||
|
id: 'userCommentsChart',
|
||||||
|
isExpand: true,
|
||||||
|
loading: true,
|
||||||
|
type: 'column',
|
||||||
|
data: {},
|
||||||
|
tooltipFormat: 'tooltipUserComments',
|
||||||
|
opts: {
|
||||||
|
color: ['#EF4444', '#F59E0B', '#14B8A6', '#3B82F6', '#10B981', '#8B5CF6', '#EC4899', '#F97316', '#ea7ccc', '#0EA5E9'],
|
||||||
|
padding: [20, 15, 10, 10],
|
||||||
|
touchMoveLimit: 24,
|
||||||
|
enableScroll: true,
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
fontSize: 10,
|
||||||
|
scrollShow: true,
|
||||||
|
itemCount: 5,
|
||||||
|
format: 'xAxisUserComments'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 4,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
min: 0,
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
column: {
|
||||||
|
type: 'group',
|
||||||
|
width: 22,
|
||||||
|
activeBgColor: '#000000',
|
||||||
|
activeBgOpacity: 0.08,
|
||||||
|
linearType: 'custom',
|
||||||
|
seriesGap: 5,
|
||||||
|
linearOpacity: 0.7,
|
||||||
|
barBorderCircle: true,
|
||||||
|
customColor: ['#F59E0B']
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
legendShape: 'circle',
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
top10ArticlesChart: {
|
||||||
|
id: 'top10ArticlesChart',
|
||||||
|
isExpand: true,
|
||||||
|
type: 'column',
|
||||||
|
data: {},
|
||||||
|
tooltipFormat: 'tooltipTop10Articles',
|
||||||
|
opts: {
|
||||||
|
color: ['#EF4444', '#F59E0B', '#14B8A6', '#3B82F6', '#10B981', '#8B5CF6', '#EC4899', '#F97316', '#ea7ccc', '#0EA5E9'],
|
||||||
|
padding: [20, 15, 10, 10],
|
||||||
|
touchMoveLimit: 24,
|
||||||
|
enableScroll: true,
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
fontSize: 10,
|
||||||
|
scrollShow: true,
|
||||||
|
itemCount: 5,
|
||||||
|
format: 'xAxisTop10Article'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 4,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
min: 0,
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
column: {
|
||||||
|
type: 'group',
|
||||||
|
width: 22,
|
||||||
|
activeBgColor: '#000000',
|
||||||
|
activeBgOpacity: 0.08,
|
||||||
|
linearType: 'custom',
|
||||||
|
seriesGap: 5,
|
||||||
|
linearOpacity: 0.7,
|
||||||
|
barBorderCircle: true,
|
||||||
|
customColor: ['#F59E0B']
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
legendShape: 'circle',
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async onReady() {
|
||||||
|
// 检查插件
|
||||||
|
this.setPluginId(this.NeedPluginIds.PluginDataStatistics);
|
||||||
|
this.setPluginError('阿偶,检测到当前插件没有安装或者启用,无法使用功能哦,请联系管理员');
|
||||||
|
if (!(await this.checkPluginAvailable())) return;
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
if (!this.uniHaloPluginAvailable) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fnGetData() {
|
||||||
|
uni.showLoading({
|
||||||
|
mask: true,
|
||||||
|
title: '加载中...'
|
||||||
|
});
|
||||||
|
// 设置状态为加载中
|
||||||
|
if (!this.isLoadMore) {
|
||||||
|
this.loading = 'loading';
|
||||||
|
}
|
||||||
|
this.loadMoreText = '加载中...';
|
||||||
|
dataStatisticsApi
|
||||||
|
.getChartData()
|
||||||
|
.then((res) => {
|
||||||
|
console.log('获取到统计数据:', res);
|
||||||
|
this.statistics = res;
|
||||||
|
this.handleTagChart();
|
||||||
|
this.handleCategoriesChart();
|
||||||
|
this.handleTrendArticlesChart();
|
||||||
|
this.handleUserCommentsChart();
|
||||||
|
this.handleTop10ArticlesChart();
|
||||||
|
this.loading = 'success';
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.loading = 'error';
|
||||||
|
this.loadMoreText = '加载失败,请下拉刷新!';
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 处理标签统计
|
||||||
|
handleTagChart() {
|
||||||
|
const data = this.statistics.tags.sort((a, b) => b.count - a.count);
|
||||||
|
this.tagChart.data = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: data.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
value: item.count
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 处理分类统计
|
||||||
|
handleCategoriesChart() {
|
||||||
|
const data = this.statistics.categories.sort((a, b) => b.total - a.total);
|
||||||
|
const seriesItemData = data.map((item) => item.total);
|
||||||
|
if (Math.max(...seriesItemData) < 10) {
|
||||||
|
this.categoryChart.opts.yAxis.data[0].max = 10;
|
||||||
|
}
|
||||||
|
this.categoryChart.data = {
|
||||||
|
categories: data.map((item) => item.name),
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '分类',
|
||||||
|
data: seriesItemData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 处理文章趋势
|
||||||
|
handleTrendArticlesChart() {
|
||||||
|
this.trandArticleChart.data = this.statistics.articles;
|
||||||
|
},
|
||||||
|
// 处理评论活跃用户
|
||||||
|
handleUserCommentsChart() {
|
||||||
|
this.userCommentsChart.loading = true;
|
||||||
|
const data = this.statistics.comments.sort((a, b) => b.count - a.count).slice(0, 10);
|
||||||
|
const seriesItemData = data.map((item) => item.count);
|
||||||
|
if (Math.max(...seriesItemData) < 10) {
|
||||||
|
this.userCommentsChart.opts.yAxis.data[0].max = 10;
|
||||||
|
}
|
||||||
|
this.userCommentsChart.data = {
|
||||||
|
categories: data.map((item) => item.username),
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '评论',
|
||||||
|
data: seriesItemData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
this.userCommentsChart.loading = false;
|
||||||
|
},
|
||||||
|
// 处理热门文章TOP10
|
||||||
|
handleTop10ArticlesChart() {
|
||||||
|
const data = this.statistics.top10Articles.sort((a, b) => b.views - a.views).slice(0, 10);
|
||||||
|
const seriesItemData = data.map((item) => item.views);
|
||||||
|
if (Math.max(...seriesItemData) < 10) {
|
||||||
|
this.top10ArticlesChart.opts.yAxis.data[0].max = 10;
|
||||||
|
}
|
||||||
|
this.top10ArticlesChart.data = {
|
||||||
|
categories: data.map((item) => item.name),
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '评论',
|
||||||
|
data: seriesItemData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-page {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #353437;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
box-shadow: 0 0 12rpx rgba(226, 232, 240, 0.35);
|
||||||
|
backdrop-filter: blur(6rpx);
|
||||||
|
// border: 2rpx solid #e8edf4;
|
||||||
|
}
|
||||||
|
.card-head {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&_title {
|
||||||
|
}
|
||||||
|
&_text {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 8rpx;
|
||||||
|
height: 70%;
|
||||||
|
background-color: #03a9f4;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_subtext {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #fcfdfe;
|
||||||
|
border: 2rpx solid #e9eef3;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
.chart-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 320rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="app-page card-shadow">
|
<view class="app-page card-shadow" :class="[uniHaloPluginPageClass]">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
|
||||||
|
:error-text="uniHaloPluginAvailableError" />
|
||||||
|
<template v-else>
|
||||||
<view v-if="loading != 'success'" class="loading-wrap">
|
<view v-if="loading != 'success'" class="loading-wrap">
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
<tm-skeleton model="listAvatr"></tm-skeleton>
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
@@ -65,7 +68,7 @@
|
|||||||
|
|
||||||
<!-- 返回顶部 -->
|
<!-- 返回顶部 -->
|
||||||
<tm-flotbutton color="light-blue" @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
|
<tm-flotbutton color="light-blue" @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
|
||||||
<tm-flotbutton v-if="haloPluginConfigs.submitLink.enabled" :offset="[16,80]" label="申请"
|
<tm-flotbutton v-if="haloPluginConfigs.linksSubmitPlugin.enabled" :offset="[16,80]" label="申请"
|
||||||
actions-pos="left" :show-text="true" color="bg-gradient-orange-accent"
|
actions-pos="left" :show-text="true" color="bg-gradient-orange-accent"
|
||||||
@click="toSubmitLinkPage"></tm-flotbutton>
|
@click="toSubmitLinkPage"></tm-flotbutton>
|
||||||
<!-- 详情弹窗 -->
|
<!-- 详情弹窗 -->
|
||||||
@@ -73,10 +76,12 @@
|
|||||||
<view class="poup pa-36" v-if="detail.data">
|
<view class="poup pa-36" v-if="detail.data">
|
||||||
<view class="info flex">
|
<view class="info flex">
|
||||||
<view class="poup-logo bg-gradient-amber-accent pa-4 shadow-24">
|
<view class="poup-logo bg-gradient-amber-accent pa-4 shadow-24">
|
||||||
<image class="poup-logo_img" :src="$utils.checkImageUrl(detail.data.spec.logo)" mode="aspectFill"></image>
|
<image class="poup-logo_img" :src="$utils.checkImageUrl(detail.data.spec.logo)"
|
||||||
|
mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="pl-24 info-detail">
|
<view class="pl-24 info-detail">
|
||||||
<view class="poup-name text-size-lg text-weight-b">{{ detail.data.spec.displayName }}</view>
|
<view class="poup-name text-size-lg text-weight-b">{{ detail.data.spec.displayName }}
|
||||||
|
</view>
|
||||||
<view class="poup-tag ml--10">
|
<view class="poup-tag ml--10">
|
||||||
<tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">
|
<tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">
|
||||||
{{ detail.data.spec.groupName }}
|
{{ detail.data.spec.groupName }}
|
||||||
@@ -103,6 +108,7 @@
|
|||||||
|
|
||||||
<view class="load-text">{{ loadMoreText }}</view>
|
<view class="load-text">{{ loadMoreText }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -115,7 +121,11 @@ import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
|||||||
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
|
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
|
||||||
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
||||||
|
|
||||||
|
import pluginAvailableMixin from "@/common/mixins/pluginAvailable.js"
|
||||||
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [pluginAvailableMixin],
|
||||||
components: {
|
components: {
|
||||||
tmSkeleton,
|
tmSkeleton,
|
||||||
tmTranslate,
|
tmTranslate,
|
||||||
@@ -123,7 +133,8 @@ export default {
|
|||||||
tmTags,
|
tmTags,
|
||||||
tmEmpty,
|
tmEmpty,
|
||||||
tmImages,
|
tmImages,
|
||||||
tmPoup
|
tmPoup,
|
||||||
|
PluginUnavailable
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -166,19 +177,32 @@ export default {
|
|||||||
}
|
}
|
||||||
return 'https://image.thum.io/get/width/1000/crop/800/' + val;
|
return 'https://image.thum.io/get/width/1000/crop/800/' + val;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onLoad() {
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async onLoad() {
|
||||||
this.fnSetPageTitle('友情链接');
|
this.fnSetPageTitle('友情链接');
|
||||||
|
// 检查插件
|
||||||
|
this.setPluginId(this.NeedPluginIds.PluginLinks)
|
||||||
|
this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用友情链接功能哦,请联系管理员")
|
||||||
|
if (!await this.checkPluginAvailable()) return
|
||||||
this.fnGetLinkGroupData();
|
this.fnGetLinkGroupData();
|
||||||
},
|
},
|
||||||
onPullDownRefresh() {
|
onPullDownRefresh() {
|
||||||
|
if (!this.uniHaloPluginAvailable) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return
|
||||||
|
}
|
||||||
this.isLoadMore = false;
|
this.isLoadMore = false;
|
||||||
this.queryParams.page = 1;
|
this.queryParams.page = 1;
|
||||||
this.dataList = []
|
this.dataList = []
|
||||||
this.fnGetData();
|
this.fnGetData();
|
||||||
},
|
},
|
||||||
onReachBottom(e) {
|
onReachBottom(e) {
|
||||||
|
if (!this.uniHaloPluginAvailable) return;
|
||||||
if (this.hasNext) {
|
if (this.hasNext) {
|
||||||
this.queryParams.page += 1;
|
this.queryParams.page += 1;
|
||||||
this.isLoadMore = true;
|
this.isLoadMore = true;
|
||||||
@@ -198,19 +222,23 @@ export default {
|
|||||||
size: 0
|
size: 0
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.linkGroupList = res.items;
|
console.log("友情链接分组数据:", res);
|
||||||
|
this.linkGroupList = res;
|
||||||
this.fnGetData()
|
this.fnGetData()
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
this.loading = 'error';
|
||||||
|
this.loadMoreText = err;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
findLinkGroupDisplayNameByGroupMetadataName(groupName) {
|
findLinkGroupDisplayNameByGroupMetadataName(groupName) {
|
||||||
if (this.linkGroupList.length === 0) return groupName || "未分组"
|
if (this.linkGroupList.length === 0) return groupName || "未分组"
|
||||||
return this.linkGroupList.find(item => item.metadata.name === groupName)?.spec?.displayName || groupName || "未分组"
|
return this.linkGroupList.find(item => item.metadata.name === groupName)?.spec?.displayName || groupName ||
|
||||||
|
"未分组"
|
||||||
},
|
},
|
||||||
fnGetData() {
|
fnGetData() {
|
||||||
if (this.haloConfigs.basicConfig.auditModeEnabled) {
|
if (this.calcAuditModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.isLoadMore) {
|
if (!this.isLoadMore) {
|
||||||
@@ -225,7 +253,8 @@ export default {
|
|||||||
this.hasNext = res.hasNext;
|
this.hasNext = res.hasNext;
|
||||||
const list = res.items.map(item => {
|
const list = res.items.map(item => {
|
||||||
item.spec.logo = this.$utils.checkAvatarUrl(item.spec?.logo)
|
item.spec.logo = this.$utils.checkAvatarUrl(item.spec?.logo)
|
||||||
item.spec.groupName = this.findLinkGroupDisplayNameByGroupMetadataName(item.spec?.groupName)
|
item.spec.groupName = this.findLinkGroupDisplayNameByGroupMetadataName(item.spec
|
||||||
|
?.groupName)
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
this.dataList = this.dataList.concat(list);
|
this.dataList = this.dataList.concat(list);
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
<template>
|
||||||
|
<view class="app-page">
|
||||||
|
<view v-if="loading !== 'success'" class="loading-wrap">
|
||||||
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
|
<tm-skeleton model="card"></tm-skeleton>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<view class="moment-card">
|
||||||
|
<view class="card flex flex-start">
|
||||||
|
<view class="avatar" style="flex-shrink: 0;">
|
||||||
|
<image style="width: 80rpx;height: 80rpx;border-radius: 50%;" :src="moment.spec.user.avatar" />
|
||||||
|
</view>
|
||||||
|
<view class="nickname" style="margin-left: 12rpx;">
|
||||||
|
<view style="font-size: 30rpx;font-weight: bold;color: #333333;">
|
||||||
|
{{ moment.spec.user.displayName }}
|
||||||
|
</view>
|
||||||
|
<view style="margin-top: 6rpx;font-size: 24rpx;color: #666;">
|
||||||
|
{{ {d: moment.spec.releaseTime, f: 'yyyy年MM月dd日 星期w'} | formatTime }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="moment.spec.tags && moment.spec.tags.length!==0" class="card flex flex-wrap flex-start"
|
||||||
|
style="padding-top:12rpx;padding-bottom:12rpx;">
|
||||||
|
<text class="text-size-m">标签列表:</text>
|
||||||
|
<tm-tags v-for="(tag,tagIndex) in moment.spec.tags" :key="tagIndex" :color="randomTagColor()"
|
||||||
|
size="m" model="text">
|
||||||
|
{{ tag }}
|
||||||
|
</tm-tags>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card" style="padding:0">
|
||||||
|
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
||||||
|
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
||||||
|
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||||
|
:content="moment.spec.newHtml" :markdown="true" :showLineNumber="true" :showLanguageName="true"
|
||||||
|
:copyByLongPress="true" />
|
||||||
|
</view>
|
||||||
|
<view v-if="moment.images && moment.images.length!==0" class="card">
|
||||||
|
<view class="card-head text-size-m">
|
||||||
|
图片附件:
|
||||||
|
</view>
|
||||||
|
<view class="images" :class="['images-'+moment.images.length]">
|
||||||
|
<view class="image-item" v-for="(image,mediumIndex) in moment.images" :key="mediumIndex">
|
||||||
|
<image mode="aspectFill" style="width: 100%;height: 100%;border-radius: 6rpx;"
|
||||||
|
:src="image.url" @click="handlePreview(mediumIndex,moment.images)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="moment.audios && moment.audios.length!==0" class="card">
|
||||||
|
<view class="card-head text-size-m">
|
||||||
|
音频附件:
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx;padding-right:28rpx;">
|
||||||
|
<audio v-for="(audio,index) in moment.audios" :controls="true" :key="index" :id="audio.url"
|
||||||
|
:poster="bloggerInfo.avatar"
|
||||||
|
:name="'来自' + (startConfig.title||bloggerInfo.nickname) + '的声音'"
|
||||||
|
:author="bloggerInfo.nickname" :src="audio.url"></audio>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="moment.videos && moment.videos.length!==0" class="card">
|
||||||
|
<view class="card-head text-size-m">
|
||||||
|
视频附件:
|
||||||
|
</view>
|
||||||
|
<view style="margin-top:24rpx;width:100%;display: flex; flex-direction: column; gap: 12rpx 0;">
|
||||||
|
<video style="width:100%;height: 400rpx;border-radius: 12rpx;"
|
||||||
|
v-for="(video,index) in moment.videos" :key="index" :src="video.url"
|
||||||
|
:id="'video_' + video.id" :show-mute-btn="true" :controls="true"
|
||||||
|
:show-center-play-btn="true" :enable-progress-gesture="true" @play="onVideoPlay(video.id)"
|
||||||
|
@pause="onVideoPause(video.id)" @ended="onVideoEnded(video.id)"></video>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 返回顶部 -->
|
||||||
|
<tm-flotbutton :width="90" :offset="[16, 80]" icon="icon-angle-up" color="bg-gradient-light-blue-accent"
|
||||||
|
@click="fnToTopPage()"></tm-flotbutton>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||||
|
|
||||||
|
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||||
|
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
|
||||||
|
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||||
|
import {
|
||||||
|
getAppConfigs
|
||||||
|
} from '@/config/index.js'
|
||||||
|
import HaloTokenConfig from "@/config/uhalo.config";
|
||||||
|
import {
|
||||||
|
getRandomNumberByRange
|
||||||
|
} from "@/utils/random.js";
|
||||||
|
import {
|
||||||
|
generateUUID
|
||||||
|
} from '@/utils/uuid.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
tmSkeleton,
|
||||||
|
tmFlotbutton,
|
||||||
|
tmButton,
|
||||||
|
tmEmpty,
|
||||||
|
mpHtml,
|
||||||
|
tmTags
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: 'loading',
|
||||||
|
markdownConfig: MarkdownConfig,
|
||||||
|
queryParams: {
|
||||||
|
name: null
|
||||||
|
},
|
||||||
|
moment: null,
|
||||||
|
tagColors: ['orange', 'green', 'red', 'blue'],
|
||||||
|
videoContexts: {},
|
||||||
|
currentVideoId: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
haloConfigs() {
|
||||||
|
return this.$tm.vx.getters().getConfigs;
|
||||||
|
},
|
||||||
|
calcUrl() {
|
||||||
|
return url => {
|
||||||
|
if (this.$utils.checkIsUrl(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return getApp().globalData.baseApiUrl + url;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 获取博主信息
|
||||||
|
bloggerInfo() {
|
||||||
|
const blogger = this.haloConfigs.authorConfig.blogger;
|
||||||
|
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
|
||||||
|
return blogger;
|
||||||
|
},
|
||||||
|
calcUseTagRandomColor() {
|
||||||
|
return this.haloConfigs.pageConfig.momentConfig.useTagRandomColor;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoad(e) {
|
||||||
|
this.fnSetPageTitle('瞬间加载中...');
|
||||||
|
this.queryParams.name = e.name;
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.videoContexts = {};
|
||||||
|
this.currentVideoId = null;
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
onShareAppMessage() {
|
||||||
|
return {
|
||||||
|
path: '/pagesA/moment-detail/moment-detail?name=' + this.moment.metadata.name,
|
||||||
|
title: this.moment.spec.title,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShareTimeline() {
|
||||||
|
return {
|
||||||
|
title: this.moment.spec.title,
|
||||||
|
query: {
|
||||||
|
name: this.moment.metadata.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fnGetData() {
|
||||||
|
this.loading = 'loading';
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getMomentByName(this.queryParams.name)
|
||||||
|
.then(res => {
|
||||||
|
console.log('获取详情', res);
|
||||||
|
let _tempResult = res;
|
||||||
|
this.fnSetPageTitle('瞬间详情');
|
||||||
|
|
||||||
|
_tempResult.spec.user = {
|
||||||
|
displayName: this.bloggerInfo.nickname,
|
||||||
|
avatar: this.$utils.checkAvatarUrl(this.bloggerInfo.avatar)
|
||||||
|
}
|
||||||
|
_tempResult.spec.content.medium.map(medium => {
|
||||||
|
medium.url = this.$utils.checkThumbnailUrl(medium.url, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
_tempResult.spec.newHtml = this.removeTagLinksCompletely(_tempResult.spec.content.html, '')
|
||||||
|
_tempResult['images'] = _tempResult.spec.content.medium
|
||||||
|
.filter(x => x.type === 'PHOTO')
|
||||||
|
|
||||||
|
_tempResult['videos'] = _tempResult.spec.content.medium
|
||||||
|
.filter(x => x.type === 'VIDEO').map(_tempResult => {
|
||||||
|
_tempResult.id = generateUUID()
|
||||||
|
return _tempResult;
|
||||||
|
})
|
||||||
|
|
||||||
|
_tempResult['audios'] = _tempResult.spec.content.medium
|
||||||
|
.filter(x => x.type === 'AUDIO')
|
||||||
|
|
||||||
|
this.moment = _tempResult;
|
||||||
|
this.loading = 'success';
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.createVideoContexts(_tempResult.videos);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log("错误", err)
|
||||||
|
this.loading = 'error';
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handlePreview(index, list) {
|
||||||
|
uni.previewImage({
|
||||||
|
current: index,
|
||||||
|
urls: list.map(item => item.url)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeTagLinksCompletely(htmlString) {
|
||||||
|
const regex = /<a\s+(?:[^>]*?\s+)?class=(['"])[^'"]*?\btag\b[^'"]*?\1[^>]*?>.*?<\/a>/gi;
|
||||||
|
const newHtml = htmlString.replace(regex, '');
|
||||||
|
return newHtml
|
||||||
|
},
|
||||||
|
randomTagColor() {
|
||||||
|
if (!this.calcUseTagRandomColor) return "blue";
|
||||||
|
const randomIndex = getRandomNumberByRange(0, this.tagColors.length);
|
||||||
|
return this.tagColors[randomIndex];
|
||||||
|
},
|
||||||
|
createVideoContexts(videos) {
|
||||||
|
this.stopAllVideos()
|
||||||
|
videos.forEach(item => {
|
||||||
|
this.videoContexts[item.id] = uni.createVideoContext(`video_${item.id}`, this);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onVideoPlay(videoId) {
|
||||||
|
this.currentVideoId = videoId;
|
||||||
|
this.stopAllVideos(videoId)
|
||||||
|
},
|
||||||
|
onVideoPause(videoId) {
|
||||||
|
if (this.currentVideoId == videoId) {
|
||||||
|
this.currentVideoId = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onVideoEnded(videoId) {
|
||||||
|
this.currentVideoId = null;
|
||||||
|
},
|
||||||
|
stopAllVideos(excludesVideoId = null) {
|
||||||
|
Object.keys(this.videoContexts).forEach(videoId => {
|
||||||
|
if (!excludesVideoId || excludesVideoId != videoId) {
|
||||||
|
const videoContext = this.videoContexts[videoId]
|
||||||
|
videoContext?.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-page {
|
||||||
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #fafafd;
|
||||||
|
padding-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wrap {
|
||||||
|
padding: 0 24rpx;
|
||||||
|
height: inherit;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moment-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
gap: 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #ffff;
|
||||||
|
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 24rpx;
|
||||||
|
|
||||||
|
&-head {
|
||||||
|
position: relative;
|
||||||
|
// padding-left: 24rpx;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
content: "";
|
||||||
|
width: 6rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: #2196F3;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 6rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.images {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 24rpx;
|
||||||
|
|
||||||
|
.image-item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 6rpx;
|
||||||
|
width: 33%;
|
||||||
|
height: 200rpx
|
||||||
|
}
|
||||||
|
|
||||||
|
&-1 {
|
||||||
|
>.image-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 350rpx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-2 {
|
||||||
|
>.image-item {
|
||||||
|
width: 50%;
|
||||||
|
height: 250rpx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .uni-audio-default {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<video v-else class="video-bg" :object-fit="startConfig.bgVideoFit" :src="startConfig.bgVideo" :loop="true"
|
<video v-else class="video-bg" :object-fit="startConfig.bgVideoFit" :src="$utils.checkUrl(startConfig.bgVideo)" :loop="true"
|
||||||
:autoplay="true" :muted="true" :controls="false" :show-fullscreen-btn="false" :show-play-btn="false"
|
:autoplay="true" :muted="true" :controls="false" :show-fullscreen-btn="false" :show-play-btn="false"
|
||||||
:show-center-play-btn="false" :show-loading="false" :enable-progress-gesture="false"
|
:show-center-play-btn="false" :show-loading="false" :enable-progress-gesture="false"
|
||||||
:show-progress="false"></video>
|
:show-progress="false"></video>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<view class="poup pa-36" scroll-y="auto">
|
<view class="poup pa-36" scroll-y="auto">
|
||||||
<view class="info flex">
|
<view class="info flex">
|
||||||
<view class="poup-logo pa-4 shadow-24">
|
<view class="poup-logo pa-4 shadow-24">
|
||||||
<image :src="blogDetail.blogLogo" class="poup-logo_img" mode="aspectFill"></image>
|
<image :src="$utils.checkAvatarUrl(blogDetail.blogLogo)" class="poup-logo_img" mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="pl-24 info-detail">
|
<view class="pl-24 info-detail">
|
||||||
<view class="poup-name text-size-lg text-weight-b">{{ blogDetail.blogName }}</view>
|
<view class="poup-name text-size-lg text-weight-b">{{ blogDetail.blogName }}</view>
|
||||||
@@ -97,6 +97,7 @@ import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue'
|
|||||||
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue'
|
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue'
|
||||||
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue'
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue'
|
||||||
import tmCoupon from '@/tm-vuetify/components/tm-coupon/tm-coupon.vue'
|
import tmCoupon from '@/tm-vuetify/components/tm-coupon/tm-coupon.vue'
|
||||||
|
import $utils from "@/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -128,11 +129,14 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
$utils() {
|
||||||
|
return $utils
|
||||||
|
},
|
||||||
haloPluginConfigs(){
|
haloPluginConfigs(){
|
||||||
return this.$tm.vx.getters().getConfigs.pluginConfig;
|
return this.$tm.vx.getters().getConfigs.pluginConfig;
|
||||||
},
|
},
|
||||||
blogDetail() {
|
blogDetail() {
|
||||||
return this.haloPluginConfigs.submitLink;
|
return this.haloPluginConfigs.linksSubmitPlugin;
|
||||||
},
|
},
|
||||||
calcSiteThumbnail(val) {
|
calcSiteThumbnail(val) {
|
||||||
return val => {
|
return val => {
|
||||||
@@ -147,13 +151,13 @@ export default {
|
|||||||
return `
|
return `
|
||||||
博客名称:${this.blogDetail.blogName}
|
博客名称:${this.blogDetail.blogName}
|
||||||
博客地址:${this.blogDetail.blogUrl}
|
博客地址:${this.blogDetail.blogUrl}
|
||||||
博客logo:${this.blogDetail.blogLogo}
|
博客logo:${$utils.checkAvatarUrl(this.blogDetail.blogLogo)}
|
||||||
博客简介:${this.blogDetail.blogDesc}
|
博客简介:${this.blogDetail.blogDesc}
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
calcBlogCoupon() {
|
calcBlogCoupon() {
|
||||||
return {
|
return {
|
||||||
img: this.blogDetail.blogLogo,
|
img: $utils.checkAvatarUrl(this.blogDetail.blogLogo),
|
||||||
title: this.blogDetail.blogName,
|
title: this.blogDetail.blogName,
|
||||||
time: this.blogDetail.blogDesc,
|
time: this.blogDetail.blogDesc,
|
||||||
btnText: '友链详情'
|
btnText: '友链详情'
|
||||||
|
|||||||
@@ -0,0 +1,729 @@
|
|||||||
|
<template>
|
||||||
|
<view class="app-page">
|
||||||
|
<view v-if="loading != 'success'" class="loading-wrap">
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<view v-if="!detail" class="empty">
|
||||||
|
<tm-empty icon="icon-shiliangzhinengduixiang-" label="未查询到数据"></tm-empty>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<view class="vote-card vote-info-card">
|
||||||
|
<view class="sub-title"> 投票信息 </view>
|
||||||
|
<view class="vote-card-body flex flex-col uh-mt-8">
|
||||||
|
<view class="">
|
||||||
|
投票类型:<tm-tags color="light-blue" :shadow="0" size="xs" model="fill">
|
||||||
|
{{ vote.spec._uh_type }} </tm-tags>
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
投票状态:<tm-tags :color="vote.spec._uh_state.color" size="xs" :shadow="0"
|
||||||
|
model="fill">{{vote.spec._uh_state.state}}</tm-tags>
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
投票方式:<tm-tags v-if="vote.spec.canAnonymously" color="light-blue" size="xs" :shadow="0"
|
||||||
|
model="fill">匿名</tm-tags>
|
||||||
|
<tm-tags v-else color="red" size="xs" :shadow="0" model="fill">不匿名</tm-tags>
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
开始时间:{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
<text v-if="vote.spec.timeLimit==='permanent'">
|
||||||
|
结束时间:永久有效
|
||||||
|
</text>
|
||||||
|
<text v-else>
|
||||||
|
结束时间:{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="vote-card">
|
||||||
|
<view class="vote-card-head flex flex-col items-start mb-12">
|
||||||
|
<view class="sub-title"> 投票内容 </view>
|
||||||
|
<view class="sub-content">
|
||||||
|
{{ vote.spec.title }}
|
||||||
|
</view>
|
||||||
|
<view v-if="vote.spec.remark" class="sub-remark">
|
||||||
|
{{ vote.spec.remark }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="vote-card-body uh-mt-8">
|
||||||
|
<view class="sub-title"> 投票选项 <text v-if="vote.spec.type==='multiple'"
|
||||||
|
class="sub-title-count">(最多选择 {{ vote.spec.maxVotes }} 项)</text> </view>
|
||||||
|
<view v-if="vote.spec.type==='single'" class="single">
|
||||||
|
<view class="w-full flex flex-col uh-gap-8">
|
||||||
|
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
||||||
|
'--percent':option._uh_percent + '%'
|
||||||
|
}">
|
||||||
|
<view class="is-voted-item-content flex w-full flex-between uh-gap-8">
|
||||||
|
<view class="flex-1 text-align-left text-break">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="vote-select-option flex-1 w-full text-break"
|
||||||
|
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
||||||
|
@click="handleSelectSingleOption(option)">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="vote.spec.type==='multiple'" class="multiple">
|
||||||
|
<view class="w-full flex flex-col uh-gap-8">
|
||||||
|
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
||||||
|
'--percent':option._uh_percent + '%'
|
||||||
|
}">
|
||||||
|
<view class="is-voted-item-content flex w-full flex-between uh-gap-8">
|
||||||
|
<view class="flex-1 text-align-left text-break">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="vote-select-option flex-1 w-full text-break"
|
||||||
|
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
||||||
|
@click="handleSelectCheckboxOption(option)">
|
||||||
|
{{option.title }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="vote.spec.type==='pk'" class="pk">
|
||||||
|
<view class="pk-container">
|
||||||
|
<view class="radio-item" v-for="(option,optionIndex) in vote.spec.options"
|
||||||
|
:key="optionIndex" :class="[optionIndex==0?'radio-left':'radio-right']"
|
||||||
|
:style="{width: option._uh_percent + '%'}">
|
||||||
|
<view class="option-item"
|
||||||
|
:class="[optionIndex==0?'option-item-left':'option-item-right']">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="option-foot w-full flex flex-col uh-gap-8">
|
||||||
|
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
||||||
|
'--percent': option._uh_percent + '%'
|
||||||
|
}">
|
||||||
|
<view class="is-voted-item-content flex w-full flex-between uh-gap-8">
|
||||||
|
<view class="flex-1 text-align-left text-break">
|
||||||
|
选项{{ optionIndex+1}}:{{option.title }}
|
||||||
|
</view>
|
||||||
|
<view class="flex-shrink">
|
||||||
|
{{option._uh_percent }}%
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||||
|
class="vote-select-option flex-1 w-full text-break"
|
||||||
|
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
||||||
|
@click="handleSelectSingleOption(option)">
|
||||||
|
选项{{ optionIndex+1}}:{{option.title }}
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="vote-card">
|
||||||
|
<view class="vote-card-body">
|
||||||
|
<view class="sub-title"> 投票统计 </view>
|
||||||
|
<view class="">
|
||||||
|
<tm-tags color="grey-darken-4" size="s" model="text">{{ vote.stats.voteCount }}
|
||||||
|
人已参与</tm-tags>
|
||||||
|
<!-- <tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags> -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 暂时不做,目前没有意义 -->
|
||||||
|
<!-- <view v-if="vote.spec.canSeeVoters" class="vote-card">
|
||||||
|
<view class="vote-card-body">
|
||||||
|
<view class="sub-title"> 投票用户 </view>
|
||||||
|
<view class="">
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<view class="vote-submit flex w-full flex-center" :style="{
|
||||||
|
paddingBottom:safeAreaBottom + 'rpx'
|
||||||
|
}">
|
||||||
|
<tm-button v-if="fnCalcIsVoted()" theme="white" text :block="true" class="w-full">您已参与投票</tm-button>
|
||||||
|
<tm-button v-else-if="vote.spec._uh_state.state==='未开始'" theme="orange" text class="w-full"
|
||||||
|
:block="true" @click="handleSubmitTip('投票未开始')">投票未开始</tm-button>
|
||||||
|
<tm-button v-else-if="vote.spec._uh_state.state==='已结束'" theme="red" text class="w-full"
|
||||||
|
:block="true" @click="handleSubmitTip('投票已结束')">投票已结束</tm-button>
|
||||||
|
<tm-button v-else-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" text
|
||||||
|
:block="true" @click="handleSubmit()">不支持匿名投票</tm-button>
|
||||||
|
<tm-button v-else-if="submitForm.voteData.length===0" theme="white" text class="w-full"
|
||||||
|
:block="true" @click="handleSubmitTip('请选择选项')">提交投票(请选择选项)</tm-button>
|
||||||
|
<tm-button v-else theme="light-blue" class="w-full" :block="true" :loading="submitLoading"
|
||||||
|
:disabled="submitLoading" @click="handleSubmit()">提交投票</tm-button>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||||
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
VOTE_TYPES,
|
||||||
|
calcVoteState,
|
||||||
|
calcVotePercent,
|
||||||
|
voteCacheUtil
|
||||||
|
} from '@/utils/vote.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
tmSkeleton,
|
||||||
|
tmEmpty,
|
||||||
|
tmButton,
|
||||||
|
tmTags,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
safeAreaBottom: 24,
|
||||||
|
loading: 'loading',
|
||||||
|
submitLoading: false,
|
||||||
|
pageTitle: '加载中...',
|
||||||
|
|
||||||
|
name: '',
|
||||||
|
detail: null,
|
||||||
|
vote: null,
|
||||||
|
submitForm: {
|
||||||
|
voteData: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(e) {
|
||||||
|
this.name = e.name;
|
||||||
|
this.fnGetData();
|
||||||
|
// this.fnGetVoteUserList();
|
||||||
|
// #ifndef H5
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
this.safeAreaBottom = systemInfo.safeAreaInsets.bottom + 12;
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.fnGetData();
|
||||||
|
// this.fnGetVoteUserList();
|
||||||
|
},
|
||||||
|
onShareAppMessage() {
|
||||||
|
return {
|
||||||
|
path: '/pagesA/vote-detail/vote-detail?name=' + this.name,
|
||||||
|
title: this.vote?.spec?.title ?? "来投个票吧",
|
||||||
|
imageUrl: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShareTimeline() {
|
||||||
|
return {
|
||||||
|
title: this.vote?.spec?.title ?? "来投个票吧",
|
||||||
|
query: {
|
||||||
|
name: this.name
|
||||||
|
},
|
||||||
|
imageUrl: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fnGetData() {
|
||||||
|
// 设置状态为加载中
|
||||||
|
this.loading = 'loading';
|
||||||
|
this.pageTitle = "加载中..."
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getVoteDetail(this.name)
|
||||||
|
.then(res => {
|
||||||
|
this.pageTitle = "投票详情" + `(${VOTE_TYPES[res.vote.spec.type]})`
|
||||||
|
|
||||||
|
const tempVoteRes = res;
|
||||||
|
|
||||||
|
tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
|
||||||
|
tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
|
||||||
|
tempVoteRes.vote.spec._uh_state = calcVoteState(tempVoteRes.vote)
|
||||||
|
tempVoteRes.vote.spec._uh_type = VOTE_TYPES[tempVoteRes.vote.spec.type]
|
||||||
|
|
||||||
|
tempVoteRes.vote.spec.options.map((option, index) => {
|
||||||
|
option.value = option.id
|
||||||
|
option.label = option.title
|
||||||
|
option.isVoted = this.fnCalcIsVoted()
|
||||||
|
option.checked = this.fnCalcIsChecked(option)
|
||||||
|
option._uh_percent = calcVotePercent(tempVoteRes.vote, option);
|
||||||
|
|
||||||
|
option.dataStr = JSON.stringify(option)
|
||||||
|
|
||||||
|
return option
|
||||||
|
})
|
||||||
|
|
||||||
|
this.vote = tempVoteRes.vote
|
||||||
|
this.detail = tempVoteRes;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = 'success';
|
||||||
|
}, 200);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.loading = 'error';
|
||||||
|
this.pageTitle = "加载失败,请重试..."
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
this.fnSetPageTitle(this.pageTitle);
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fnGetVoteUserList() {
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getVoteUserList(this.name)
|
||||||
|
.then(res => {
|
||||||
|
console.log("查询投票用户列表")
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log("err")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fnCalcIsVoted() {
|
||||||
|
return voteCacheUtil.has(this.name)
|
||||||
|
},
|
||||||
|
|
||||||
|
fnCalcIsChecked(option) {
|
||||||
|
const data = voteCacheUtil.get(this.name)
|
||||||
|
if (!data) return false;
|
||||||
|
const checked = data.selected.includes(option.id)
|
||||||
|
return checked
|
||||||
|
},
|
||||||
|
onOptionRadioChange(e) {
|
||||||
|
this.submitForm.voteData = e.map(item => this.vote.spec.options[item.index]?.id);
|
||||||
|
},
|
||||||
|
onOptionCheckboxChange(e) {
|
||||||
|
this.submitForm.voteData = e.map(item => this.vote.spec.options[item.index]?.id);
|
||||||
|
},
|
||||||
|
onOptionPkChange(e) {
|
||||||
|
this.submitForm.voteData = e.map(item => this.vote.spec.options[item.index]?.id);
|
||||||
|
},
|
||||||
|
formatJsonStr(jsonStr) {
|
||||||
|
return jsonStr ? JSON.parse(jsonStr) : {}
|
||||||
|
},
|
||||||
|
handleSubmitTip(text) {
|
||||||
|
this.showToast(text)
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
if (!this.vote.spec.canAnonymously) {
|
||||||
|
uni.showModal({
|
||||||
|
icon: "none",
|
||||||
|
title: "提示",
|
||||||
|
content: "该投票不支持匿名,请到博主的 网站端 进行投票!",
|
||||||
|
cancelColor: "#666666",
|
||||||
|
cancelText: "关闭",
|
||||||
|
confirmText: "复制地址",
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
this.$utils.copyText(this.$baseApiUrl, "复制成功")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitLoading = true
|
||||||
|
uni.showLoading({
|
||||||
|
title: "正在保存..."
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$httpApi.v2
|
||||||
|
.submitVote(this.name, this.submitForm, this.vote.spec.canAnonymously)
|
||||||
|
.then(res => {
|
||||||
|
|
||||||
|
this.showToast("提交成功")
|
||||||
|
voteCacheUtil.set(this.name, {
|
||||||
|
selected: [...this.submitForm.voteData],
|
||||||
|
data: this.vote
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.startPullDownRefresh()
|
||||||
|
this.submitLoading = false
|
||||||
|
}, 1500);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.showToast("提交失败,请重试")
|
||||||
|
this.submitLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSelectSingleOption(option) {
|
||||||
|
if (this.vote.spec._uh_state.state == '未开始') {
|
||||||
|
this.showToast(`投票未开始`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.vote.spec.hasEnded) return
|
||||||
|
if (this.vote.spec.disabled) return
|
||||||
|
this.vote.spec.options.map(item => {
|
||||||
|
if (option.id == item.id) {
|
||||||
|
item.checked = true
|
||||||
|
} else {
|
||||||
|
item.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSelectCheckboxOption(option) {
|
||||||
|
if (this.vote.spec._uh_state.state == '未开始') {
|
||||||
|
this.showToast(`投票未开始`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.vote.spec.hasEnded) return
|
||||||
|
if (this.vote.spec.disabled) return
|
||||||
|
|
||||||
|
const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id)
|
||||||
|
|
||||||
|
if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) {
|
||||||
|
this.showToast(`最多选择 ${this.vote.spec.maxVotes} 项`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vote.spec.options.map(item => {
|
||||||
|
if (option.id == item.id) {
|
||||||
|
item.checked = !item.checked
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
|
||||||
|
},
|
||||||
|
showToast(content) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: content,
|
||||||
|
mask: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-page {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
padding-bottom: 160rpx;
|
||||||
|
background-color: #fafafd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wrap {
|
||||||
|
padding: 0 24rpx;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-50 {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #ffff;
|
||||||
|
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-info-card {
|
||||||
|
.vote-card-body {
|
||||||
|
font-size: 28rpx;
|
||||||
|
gap: 12rpx 0;
|
||||||
|
background: #F3F4F6;
|
||||||
|
color: #3F3F3F;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-head {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-satrt;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
.title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-body {
|
||||||
|
|
||||||
|
.remark {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12rpx 6rpx;
|
||||||
|
padding-top: 0;
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-card-foot {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 6px;
|
||||||
|
border-top: 2rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-voted-item {
|
||||||
|
min-height: 72rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(229, 229, 229, 0.75);
|
||||||
|
font-size: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: var(--percent);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(208, 208, 208, 1);
|
||||||
|
z-index: 0;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: rgba(3, 169, 244, 0.35);
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: rgba(3, 169, 244, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-voted-item-content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 72rpx;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-select-option {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.tm-button-label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.single {
|
||||||
|
::v-deep {}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiple {
|
||||||
|
::v-deep {}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
|
||||||
|
.pk-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 30% !important;
|
||||||
|
max-width: 70% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-left {}
|
||||||
|
|
||||||
|
.radio-right {}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item-left {
|
||||||
|
background: linear-gradient(90deg, #3B82F6, #60A5FA);
|
||||||
|
color: white;
|
||||||
|
clip-path: polygon(0 0, calc(100% - 40rpx) 0, 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item-right {
|
||||||
|
background: linear-gradient(90deg, #F87171, #EF4444);
|
||||||
|
color: white;
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 100%, 40rpx 100%);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-foot {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.tm-groupradio {
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-checkbox {
|
||||||
|
// width: 100%;
|
||||||
|
max-width: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.flex-start.fulled {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-content {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2B2F33;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-remark {
|
||||||
|
margin-bottom: 36rpx;
|
||||||
|
padding-top: 12rpx;
|
||||||
|
color: #3F3F3F;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
// font-weight: bold;
|
||||||
|
font-size: 30rpx;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
width: 8rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 6rpx;
|
||||||
|
background: #03A9F4;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-submit {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 24rpx 36rpx;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(255, 255, 255, 0.98);
|
||||||
|
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
||||||
|
border-top: 2rpx solid #eee;
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.tm-button {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-button-btn {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
<template>
|
||||||
|
<view class="app-page" :class="[uniHaloPluginPageClass]">
|
||||||
|
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
|
||||||
|
:error-text="uniHaloPluginAvailableError" />
|
||||||
|
<template v-else>
|
||||||
|
<!-- 顶部切换 -->
|
||||||
|
<view class="e-fixed filter-box">
|
||||||
|
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue"
|
||||||
|
insert-color="light-blue" :clear="true" @input="fnOnSearch" @confirm="fnOnSearch"></tm-search>
|
||||||
|
<tm-dropDownMenu :shadow="0" color="light-blue" active-color="light-blue"
|
||||||
|
:default-selected="filterOption.selected" :list="filterOption.list"
|
||||||
|
@confirm="fnOnFilterConfirm"></tm-dropDownMenu>
|
||||||
|
</view>
|
||||||
|
<!-- 占位区域 -->
|
||||||
|
<view style="width: 100vw;height: 180rpx;"></view>
|
||||||
|
<!-- 加载区域 -->
|
||||||
|
<view v-if="loading == 'loading'" class="loading-wrap pa-24">
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
<tm-skeleton model="listAvatr"></tm-skeleton>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="loading == 'error'" class="content-empty flex flex-center">
|
||||||
|
<tm-empty icon="icon-wind-cry" label="加载异常"></tm-empty>
|
||||||
|
</view>
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<view v-else class="content">
|
||||||
|
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
|
||||||
|
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<tm-translate v-for="(item, index) in dataList" :key="item.metadata.name" animation-name="fadeUp"
|
||||||
|
:wait="calcAniWait(index)">
|
||||||
|
<VoteCard :vote="item" :index="index" @on-click="fnToDetail"></VoteCard>
|
||||||
|
</tm-translate>
|
||||||
|
<view class="load-text">{{ loadMoreText }}</view>
|
||||||
|
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue"
|
||||||
|
icon="icon-angle-up"></tm-flotbutton>
|
||||||
|
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||||
|
import tmSearch from '@/tm-vuetify/components/tm-search/tm-search.vue';
|
||||||
|
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
||||||
|
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
|
||||||
|
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||||
|
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||||
|
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
||||||
|
import tmDropDownMenu from '@/tm-vuetify/components/tm-dropDownMenu/tm-dropDownMenu.vue';
|
||||||
|
|
||||||
|
import VoteCard from '@/components/vote-card/vote-card.vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
VOTE_TYPES,
|
||||||
|
calcVoteState,
|
||||||
|
voteCacheUtil
|
||||||
|
} from '@/utils/vote.js'
|
||||||
|
import pluginAvailableMixin from "@/common/mixins/pluginAvailable.js"
|
||||||
|
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
},
|
||||||
|
mixins: [pluginAvailableMixin],
|
||||||
|
components: {
|
||||||
|
tmSkeleton,
|
||||||
|
tmSearch,
|
||||||
|
tmTranslate,
|
||||||
|
tmTabs,
|
||||||
|
tmFlotbutton,
|
||||||
|
tmEmpty,
|
||||||
|
tmTags,
|
||||||
|
tmDropDownMenu,
|
||||||
|
VoteCard,
|
||||||
|
PluginUnavailable
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: 'loading',
|
||||||
|
hasNext: false,
|
||||||
|
isLoadMore: false,
|
||||||
|
loadMoreText: '加载中...',
|
||||||
|
filterOption: {
|
||||||
|
selected: [],
|
||||||
|
list: [{
|
||||||
|
title: '类型',
|
||||||
|
children: [{
|
||||||
|
title: "",
|
||||||
|
model: "list",
|
||||||
|
name: "type",
|
||||||
|
children: [{
|
||||||
|
title: "全部",
|
||||||
|
id: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "单选",
|
||||||
|
id: 'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "多选",
|
||||||
|
id: 'multiple'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "双选PK",
|
||||||
|
id: 'pk'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
title: '状态',
|
||||||
|
children: [{
|
||||||
|
title: "",
|
||||||
|
model: "list",
|
||||||
|
name: "hasEnded",
|
||||||
|
children: [{
|
||||||
|
title: "全部",
|
||||||
|
id: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "进行中",
|
||||||
|
id: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "已结束",
|
||||||
|
id: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
title: '排序',
|
||||||
|
children: [{
|
||||||
|
title: "",
|
||||||
|
model: "list",
|
||||||
|
name: "sort",
|
||||||
|
children: [{
|
||||||
|
title: "默认排序",
|
||||||
|
id: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "较近创建",
|
||||||
|
id: 'metadata.creationTimestamp,desc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "较早创建",
|
||||||
|
id: 'metadata.creationTimestamp,asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
title: '是否已投',
|
||||||
|
children: [{
|
||||||
|
title: "",
|
||||||
|
model: "list",
|
||||||
|
name: "isVoted",
|
||||||
|
children: [{
|
||||||
|
title: "全部",
|
||||||
|
id: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "未投票",
|
||||||
|
id: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "已投票",
|
||||||
|
id: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
filterIsVoted: undefined,
|
||||||
|
queryParams: {
|
||||||
|
keyword: "",
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
sort: undefined,
|
||||||
|
type: undefined,
|
||||||
|
hasEnded: undefined
|
||||||
|
},
|
||||||
|
dataList: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
haloConfigs() {
|
||||||
|
return this.$tm.vx.getters().getConfigs;
|
||||||
|
},
|
||||||
|
calcAuditModeEnabled() {
|
||||||
|
return this.haloConfigs.auditConfig.auditModeEnabled
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async onLoad() {
|
||||||
|
this.fnSetPageTitle('投票列表');
|
||||||
|
// 检查插件
|
||||||
|
this.setPluginId(this.NeedPluginIds.PluginVote)
|
||||||
|
this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用投票功能哦,请联系管理员")
|
||||||
|
if (!await this.checkPluginAvailable()) return
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
if (!this.uniHaloPluginAvailable) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fnResetSetAniWaitIndex();
|
||||||
|
this.isLoadMore = false;
|
||||||
|
this.queryParams.page = 0;
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
onReachBottom(e) {
|
||||||
|
if (!this.uniHaloPluginAvailable) return;
|
||||||
|
if (this.calcAuditModeEnabled) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '没有更多数据了'
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.hasNext) {
|
||||||
|
this.queryParams.page += 1;
|
||||||
|
this.isLoadMore = true;
|
||||||
|
this.fnGetData();
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '没有更多数据了'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fnOnSearch() {
|
||||||
|
this.fnResetSetAniWaitIndex();
|
||||||
|
this.fnToTopPage();
|
||||||
|
this.fnGetData();
|
||||||
|
},
|
||||||
|
fnGetData() {
|
||||||
|
if (this.calcAuditModeEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showLoading({
|
||||||
|
mask: true,
|
||||||
|
title: '加载中...'
|
||||||
|
});
|
||||||
|
// 设置状态为加载中
|
||||||
|
if (!this.isLoadMore) {
|
||||||
|
this.loading = 'loading';
|
||||||
|
}
|
||||||
|
this.loadMoreText = '加载中...';
|
||||||
|
this.$httpApi.v2
|
||||||
|
.getVoteList(this.queryParams)
|
||||||
|
.then(res => {
|
||||||
|
this.loading = 'success';
|
||||||
|
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
|
||||||
|
this.hasNext = res.hasNext;
|
||||||
|
|
||||||
|
const tempItems = res.items.map(item => {
|
||||||
|
item.spec.disabled = true
|
||||||
|
item.spec.isVoted = this.fnCalcIsVoted(item.metadata.name)
|
||||||
|
item.spec._uh_state = calcVoteState(item)
|
||||||
|
item.spec._uh_type = VOTE_TYPES[item.spec.type]
|
||||||
|
item.spec.options.map((option, index) => {
|
||||||
|
|
||||||
|
option.checked = this.fnCalcIsChecked(item.metadata.name, option)
|
||||||
|
option.value = option.id
|
||||||
|
option.label = option.title
|
||||||
|
|
||||||
|
// 计算当前的选择占比
|
||||||
|
if (item.spec.type === 'single') {
|
||||||
|
option.percent = this.fnCalcPercent(option, item.stats);
|
||||||
|
} else if (item.spec.type === 'multiple') {
|
||||||
|
option.percent = this.fnCalcPercent(option, item.stats);
|
||||||
|
} else if (item.spec.type === 'pk') {
|
||||||
|
option.percent = this.fnCalcPercent(option, item.stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
return option
|
||||||
|
})
|
||||||
|
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.isLoadMore) {
|
||||||
|
this.dataList = this.dataList.concat(tempItems);
|
||||||
|
} else {
|
||||||
|
this.dataList = tempItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataList = this.dataList.sort((a, b) => {
|
||||||
|
return a.spec.isVoted - b.spec.isVoted
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.filterIsVoted != undefined) {
|
||||||
|
this.dataList = this.dataList.filter(x => x.spec.isVoted == this.filterIsVoted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.loading = 'error';
|
||||||
|
this.loadMoreText = '加载失败,请下拉刷新!';
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fnCalcPercent(voteOption, stats) {
|
||||||
|
if (!stats?.voteDataList) return 0;
|
||||||
|
const option = stats.voteDataList.find(x => x.id == voteOption.id)
|
||||||
|
if (!option) return 0;
|
||||||
|
const percent = (option.voteCount / stats.voteCount) * 100
|
||||||
|
return Math.round(percent)
|
||||||
|
},
|
||||||
|
|
||||||
|
fnCalcIsVoted(name) {
|
||||||
|
return voteCacheUtil.has(name)
|
||||||
|
},
|
||||||
|
fnCalcIsChecked(name, option) {
|
||||||
|
const data = voteCacheUtil.get(name)
|
||||||
|
if (!data) return false;
|
||||||
|
const checked = data.selected.includes(option.id)
|
||||||
|
return checked
|
||||||
|
},
|
||||||
|
|
||||||
|
//跳转详情
|
||||||
|
fnToDetail(item) {
|
||||||
|
if (this.calcAuditModeEnabled) return;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pagesA/vote-detail/vote-detail?name=' + item.metadata.name,
|
||||||
|
animationType: 'slide-in-right'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fnOnFilterConfirm(e) {
|
||||||
|
// 类型
|
||||||
|
const type = e.find(x => x.name == 'type')
|
||||||
|
if (type.children.length == 0) {
|
||||||
|
this.queryParams.type = undefined
|
||||||
|
} else {
|
||||||
|
this.queryParams.type = type.children[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const hasEnded = e.find(x => x.name == 'hasEnded')
|
||||||
|
if (hasEnded.children.length == 0) {
|
||||||
|
this.queryParams.hasEnded = undefined
|
||||||
|
} else {
|
||||||
|
this.queryParams.hasEnded = hasEnded.children[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
const sort = e.find(x => x.name == 'sort')
|
||||||
|
if (sort.children.length == 0) {
|
||||||
|
this.queryParams.sort = undefined
|
||||||
|
} else {
|
||||||
|
this.queryParams.sort = sort.children[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否已经投
|
||||||
|
const isVoted = e.find(x => x.name == 'isVoted')
|
||||||
|
if (isVoted.children.length == 0) {
|
||||||
|
this.filterIsVoted = undefined
|
||||||
|
} else {
|
||||||
|
this.filterIsVoted = isVoted.children[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queryParams.page = 0;
|
||||||
|
this.isLoadMore = false;
|
||||||
|
this.fnResetSetAniWaitIndex();
|
||||||
|
this.fnToTopPage();
|
||||||
|
this.fnGetData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-page {
|
||||||
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-bottom: 24rpx;
|
||||||
|
background-color: #fafafd;
|
||||||
|
|
||||||
|
&.is-balck {
|
||||||
|
background-color: #212121;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-box {
|
||||||
|
box-shadow: 0rpx 0rpx 12rpx rgba(0, 0, 0, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-empty {
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,10 +13,13 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad(e) {
|
onLoad(e) {
|
||||||
const {title, url} = JSON.parse(e.query.data);
|
const {title, url} = JSON.parse(e.data);
|
||||||
this.webUrl = url;
|
this.webUrl = decodeURIComponent(url);
|
||||||
|
console.log("webUrl" + this.webUrl)
|
||||||
|
if (title) {
|
||||||
this.fnSetPageTitle(title);
|
this.fnSetPageTitle(title);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
+9
-4
@@ -5,9 +5,10 @@
|
|||||||
* 时间:2024年06月22日 12:00:44
|
* 时间:2024年06月22日 12:00:44
|
||||||
* 版本:v0.1.0
|
* 版本:v0.1.0
|
||||||
*/
|
*/
|
||||||
import {DefaultAppConfigs, getAppConfigs, setAppConfigs,setAppMockJson,getAppMockJson} from '@/config/index.js'
|
import {DefaultAppConfigs, getAppConfigs, getAppMockJson, setAppConfigs, setAppMockJson} from '@/config/index.js'
|
||||||
import v2Config from '@/api/v2/all.config.js'
|
import v2Config from '@/api/v2/all.config.js'
|
||||||
import utils from '@/utils/index.js'
|
import utils from '@/utils/index.js'
|
||||||
|
import {setTokens} from "@/utils/token";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -36,12 +37,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
fetchConfigs({commit, dispatch}) {
|
fetchConfigs({state, commit, dispatch}) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const res = await v2Config.getAppConfigs()
|
const res = await v2Config.getAppConfigs()
|
||||||
if (res) {
|
if (res) {
|
||||||
commit('setConfigs', res)
|
commit('setConfigs', utils.deepMerge(DefaultAppConfigs, res))
|
||||||
|
|
||||||
|
// 存储token
|
||||||
|
if (res?.basicConfig?.tokenConfig) {
|
||||||
|
setTokens(res.basicConfig.tokenConfig)
|
||||||
|
}
|
||||||
resolve(res)
|
resolve(res)
|
||||||
} else {
|
} else {
|
||||||
dispatch("setDefaultAppSettings");
|
dispatch("setDefaultAppSettings");
|
||||||
@@ -65,7 +71,6 @@ export default {
|
|||||||
url: mockJsonUrl,
|
url: mockJsonUrl,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log("mockJson", res.data)
|
|
||||||
commit('setMockJson', res.data)
|
commit('setMockJson', res.data)
|
||||||
resolve({
|
resolve({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<view @click="onclick" class=" tm-checkbox " :class="[dense?'':'pa-20',inline?'d-inline-block':'']">
|
<view @click="onclick" class=" tm-checkbox " :class="[dense?'':'pa-20',inline?'d-inline-block':'']">
|
||||||
<view class=" flex-start">
|
<view class=" flex-start">
|
||||||
|
|
||||||
<slot name="default" :checkData="{label:label,checked:changValue}" :on="onclick">
|
<slot name="default" :checkData="{label:label,checked:changValue,extendData}" :on="onclick">
|
||||||
<view :style="{width: sizes.wk,height: sizes.wk}" class="tm-checkbox-boey relative d-inline-block"
|
<view :style="{width: sizes.wk,height: sizes.wk}" class="tm-checkbox-boey relative d-inline-block"
|
||||||
:class="[black?'bk':'','flex-shrink mr-10',
|
:class="[black?'bk':'','flex-shrink mr-10',
|
||||||
changValue?'ani':'',
|
changValue?'ani':'',
|
||||||
@@ -51,6 +51,10 @@
|
|||||||
*/
|
*/
|
||||||
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
|
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
|
||||||
export default {
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
},
|
||||||
components:{tmIcons},
|
components:{tmIcons},
|
||||||
name: 'tm-checkbox',
|
name: 'tm-checkbox',
|
||||||
model: {
|
model: {
|
||||||
@@ -117,6 +121,10 @@
|
|||||||
fllowTheme:{
|
fllowTheme:{
|
||||||
type:Boolean|String,
|
type:Boolean|String,
|
||||||
default:true
|
default:true
|
||||||
|
},
|
||||||
|
extendData:{
|
||||||
|
type:Object,
|
||||||
|
default:()=>({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ export default {
|
|||||||
},
|
},
|
||||||
//向列表添加数据
|
//向列表添加数据
|
||||||
pushData(list) {
|
pushData(list) {
|
||||||
console.log('添加图片数据----------',list)
|
|
||||||
|
|
||||||
let prIdx_i = this.list2.length;
|
let prIdx_i = this.list2.length;
|
||||||
if (!Array.isArray(list) || typeof list == 'undefined') {
|
if (!Array.isArray(list) || typeof list == 'undefined') {
|
||||||
return false;
|
return false;
|
||||||
@@ -122,8 +120,6 @@ export default {
|
|||||||
return this.dataList;
|
return this.dataList;
|
||||||
},
|
},
|
||||||
async loadimg(event, isLoad, index) {
|
async loadimg(event, isLoad, index) {
|
||||||
console.log('图片加载事件----------',event)
|
|
||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
let ps = this.list2[index];
|
let ps = this.list2[index];
|
||||||
ps.isLoad = true;
|
ps.isLoad = true;
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
},
|
||||||
name:'tm-groupcheckbox',
|
name:'tm-groupcheckbox',
|
||||||
props:{
|
props:{
|
||||||
// 最大选择数量
|
// 最大选择数量
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<view @click="onclick" class=" tm-checkbox " :class="[dense?'':'pa-20',inline?'d-inline-block ':'fulled']">
|
<view @click="onclick" class=" tm-checkbox " :class="[dense?'':'pa-20',inline?'d-inline-block ':'fulled']">
|
||||||
<view class="flex-start fulled">
|
<view class="flex-start fulled">
|
||||||
|
|
||||||
<slot name="default" :checkData="{label:label,checked:changValue}" :on="onclick">
|
<slot name="default" :checkData="{label:label,checked:changValue,extendData}" :on="onclick">
|
||||||
<view :style="{width: sizes.wk,height: sizes.wk}" class="tm-checkbox-boey relative d-inline-block"
|
<view :style="{width: sizes.wk,height: sizes.wk}" class="tm-checkbox-boey relative d-inline-block"
|
||||||
:class="[black?'bk':'','flex-shrink mr-10 ',
|
:class="[black?'bk':'','flex-shrink mr-10 ',
|
||||||
changValue?'ani':'',
|
changValue?'ani':'',
|
||||||
@@ -119,8 +119,11 @@
|
|||||||
fllowTheme:{
|
fllowTheme:{
|
||||||
type:Boolean|String,
|
type:Boolean|String,
|
||||||
default:true
|
default:true
|
||||||
}
|
},
|
||||||
|
extendData:{
|
||||||
|
type:Object,
|
||||||
|
default:()=>({})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
## 1.0.2(2023-09-14)
|
||||||
|
更新使用文档:
|
||||||
|
引入本插件 然后使用,手动滑稽,教程简单吗?
|
||||||
|
<k-touch-listen @touchUp="touchUp" @touchDown="touchDown" @touchLeft="touchLeft" @touchRight="touchRight">
|
||||||
|
<view>
|
||||||
|
//您需要监听的区域的代码
|
||||||
|
</view>
|
||||||
|
</k-touch-listen>
|
||||||
|
## 1.0.1(2023-09-14)
|
||||||
|
更新使用文档:
|
||||||
|
引入本插件 然后使用,手动滑稽,教程简单吗?
|
||||||
|
<k-touch-listen @touchUp="touchUp" @touchDown="touchDown" @touchLeft="touchLeft" @touchRight="touchRight">
|
||||||
|
<view>
|
||||||
|
//您需要监听的区域的代码
|
||||||
|
</view>
|
||||||
|
</k-touch-listen>
|
||||||
|
## 1.0.0(2023-09-14)
|
||||||
|
首次提交
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<view class="touch-box" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'k-touch-listen',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
touchStartX: 0, // 触摸时的原点
|
||||||
|
touchStartY: 0, // 触摸时的原点
|
||||||
|
time: 0, // 时间记录,用于滑动时且时间小于1s则执行左右滑动
|
||||||
|
interval: '', // 记录/清理时间记录
|
||||||
|
touchMoveX: 0, // x轴方向移动的距离
|
||||||
|
touchMoveY: 0, // y轴方向移动的距离
|
||||||
|
touchMoveTag: false // 定义滑动状态
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'all'
|
||||||
|
},
|
||||||
|
distance: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['touchUp', 'touchDown', 'touchLeft', 'touchRight'],
|
||||||
|
methods: {
|
||||||
|
// 触摸开始事件
|
||||||
|
touchStart(e) {
|
||||||
|
this.touchStartX = e.touches[0].pageX // 获取触摸时的原点
|
||||||
|
this.touchStartY = e.touches[0].pageY // 获取触摸时的原点
|
||||||
|
// 使用js计时器记录时间
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.time++
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
// 触摸移动事件
|
||||||
|
touchMove(e) {
|
||||||
|
this.touchMoveTag = true
|
||||||
|
this.touchMoveX = e.touches[0].pageX
|
||||||
|
this.touchMoveY = e.touches[0].pageY
|
||||||
|
},
|
||||||
|
ressetData() {
|
||||||
|
this.touchStartX = 0 // 触摸时的原点
|
||||||
|
this.touchStartY = 0 // 触摸时的原点
|
||||||
|
this.time = 0 // 时间记录,用于滑动时且时间小于1s则执行左右滑动
|
||||||
|
this.interval = '' // 记录/清理时间记录
|
||||||
|
this.touchMoveX = 0 // x轴方向移动的距离
|
||||||
|
this.touchMoveY = 0 // y轴方向移动的距离
|
||||||
|
this.touchMoveTag = false // 定义滑动状态
|
||||||
|
},
|
||||||
|
// 触摸结束事件
|
||||||
|
touchEnd(e) {
|
||||||
|
let moveX = this.touchMoveX - this.touchStartX
|
||||||
|
let moveY = this.touchMoveY - this.touchStartY
|
||||||
|
if (Math.sign(moveX) == -1) {
|
||||||
|
moveX *= -1
|
||||||
|
}
|
||||||
|
if (Math.sign(moveY) == -1) {
|
||||||
|
moveY *= -1
|
||||||
|
}
|
||||||
|
if (2 * moveX <= moveY && this.touchMoveTag) {
|
||||||
|
// 上下
|
||||||
|
if (this.direction != 'all' && this.direction != 'vertical') return
|
||||||
|
// 向上滑动
|
||||||
|
if (
|
||||||
|
this.touchMoveY - this.touchStartY <= -this.distance &&
|
||||||
|
this.time < 10
|
||||||
|
) {
|
||||||
|
this.$emit('touchUp')
|
||||||
|
}
|
||||||
|
// 向下滑动
|
||||||
|
if (
|
||||||
|
this.touchMoveY - this.touchStartY >= this.distance &&
|
||||||
|
this.time < 10
|
||||||
|
) {
|
||||||
|
this.$emit('touchDown')
|
||||||
|
}
|
||||||
|
} else if (moveX > 2 * moveY && this.touchMoveTag) {
|
||||||
|
// 左右
|
||||||
|
if (this.direction != 'all' && this.direction != 'horizontal') return
|
||||||
|
// 向左滑动
|
||||||
|
if (
|
||||||
|
this.touchMoveX - this.touchStartX <= -this.distance &&
|
||||||
|
this.time < 10
|
||||||
|
) {
|
||||||
|
this.$emit('touchLeft')
|
||||||
|
}
|
||||||
|
// 向右滑动
|
||||||
|
if (
|
||||||
|
this.touchMoveX - this.touchStartX >= this.distance &&
|
||||||
|
this.time < 10
|
||||||
|
) {
|
||||||
|
this.$emit('touchRight')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearInterval(this.interval) // 清除setInterval
|
||||||
|
this.ressetData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.touch-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"id": "k-touch-listen",
|
||||||
|
"displayName": "k-touch-listen 手势监听 全面兼容小程序、vue2、vue3等多端",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"description": "用于监听用户滑动手势,左滑右滑,上滑下滑等一系列手势监听",
|
||||||
|
"keywords": [
|
||||||
|
"touch",
|
||||||
|
"手势",
|
||||||
|
"手势监听",
|
||||||
|
"手势监听",
|
||||||
|
"vue3"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.7.6"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "插件不采集任何数据",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"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": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y",
|
||||||
|
"钉钉": "u",
|
||||||
|
"快手": "u",
|
||||||
|
"飞书": "u",
|
||||||
|
"京东": "u"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
## 1.9.6.6(2024-09-25)
|
|
||||||
- fix: 修复background-position无效的问题
|
|
||||||
## 1.9.6.5(2024-04-14)
|
|
||||||
- fix: 修复`nvue`无法生图的问题
|
|
||||||
## 1.9.6.4(2024-03-10)
|
|
||||||
- fix: 修复代理ctx导致H5不能使用ctx.save
|
|
||||||
## 1.9.6.3(2024-03-08)
|
|
||||||
- fix: 修复支付宝真机无法使用的问题
|
|
||||||
## 1.9.6.2(2024-02-22)
|
|
||||||
- fix: 修复使用render函数报错的问题
|
|
||||||
## 1.9.6.1(2023-12-22)
|
|
||||||
- fix: 修复字节小程序非2d字体偏移
|
|
||||||
- fix: 修复`canvasToTempFilePathSync`会触发两次的问题
|
|
||||||
- fix: 修复`parser`图片没有宽度的问题
|
|
||||||
## 1.9.6(2023-12-06)
|
|
||||||
- fix: 修复背景图受padding影响
|
|
||||||
- fix: 修复因字节报错改了代理实现导致微信报错
|
|
||||||
- 1.9.5.8(2023-11-16)
|
|
||||||
- fix: 修复margin问题
|
|
||||||
- fix: 修复borderWidth问题
|
|
||||||
- fix: 修复textBox问题
|
|
||||||
- fix: 修复字节开发工具报`could not be cloned.`问题
|
|
||||||
## 1.9.5.7(2023-07-27)
|
|
||||||
- fix: 去掉多余的方法
|
|
||||||
- chore: 更新文档,增加自定义字体说明
|
|
||||||
## 1.9.5.6(2023-07-21)
|
|
||||||
- feat: 有限的支持富文本
|
|
||||||
- feat: H5和APP 增加 `hidpi` prop,主要用于大尺寸无法生成图片时用
|
|
||||||
- fix: 修复 钉钉小程序 缺少 `measureText` 方法
|
|
||||||
- chore: 由于微信小程序 pc 端的 canvas 2d 时不时抽风,故不使用canvas 2d
|
|
||||||
## 1.9.5.5(2023-06-27)
|
|
||||||
- fix: 修复把`emoji`表情字符拆分成多个字符的情况
|
|
||||||
## 1.9.5.4(2023-06-05)
|
|
||||||
- fix: 修复因`canvasToTempFilePathSync`监听导致重复调用
|
|
||||||
## 1.9.5.3(2023-05-23)
|
|
||||||
- fix: 因isPc错写成了isPC导致小程序PC不能生成图片
|
|
||||||
## 1.9.5.2(2023-05-22)
|
|
||||||
- feat: 删除多余文件
|
|
||||||
## 1.9.5.1(2023-05-22)
|
|
||||||
- fix: 修复 文字行数与`line-clamp`相同但不满一行时也加了省略号的问题
|
|
||||||
## 1.9.5(2023-05-14)
|
|
||||||
- feat: 增加 `text-indent` 和 `calc` 方法
|
|
||||||
- feat: 优化 布局时间
|
|
||||||
## 1.9.4.4(2023-04-15)
|
|
||||||
- fix: 修复无法匹配负值
|
|
||||||
- fix: 修复 Nvue IOS getImageInfo `useCORS` 为 undefined
|
|
||||||
## 1.9.4.3(2023-04-01)
|
|
||||||
- feat: 增加支持文字描边 `text-stroke: '5rpx #fff'`
|
|
||||||
## 1.9.4.2(2023-03-30)
|
|
||||||
- fix: 修复 支付宝小程序 isPC 在手机也为true的问题
|
|
||||||
- feat: 由 微信开发工具 3060 版 无法获取图片尺寸,现 微信开发工具 3220 版 修复该问题,故还原上一版的获取图片方式。
|
|
||||||
## 1.9.4.1(2023-03-28)
|
|
||||||
- fix: 修复固定高度不正确问题
|
|
||||||
## 1.9.4(2023-03-17)
|
|
||||||
- fix: nvue ios getImageInfo缺少this报错
|
|
||||||
- fix: pathType 非2d无效问题
|
|
||||||
- fix: 修复 小米9se 可能会存在多次init 导致画面多次放大
|
|
||||||
- fix: 修复 border 分开写 width style无效问题
|
|
||||||
- fix: 修复 支付宝小程序IOS 再次进入不渲染的问题
|
|
||||||
- fix: 修复 支付宝小程序安卓Zindex排序错乱问题
|
|
||||||
- fix: 修复 微信开发工具 3060 版 无法获取图片的问题
|
|
||||||
- feat: 把 for in 改为 forEach
|
|
||||||
- feat: 增加 hidden
|
|
||||||
- feat: 根节点 box-sizing 默认 `border-box`
|
|
||||||
- feat: 增加支持 `vw` `wh`
|
|
||||||
- chore: pathType 取消 默认值,因为字节开发工具不能显示
|
|
||||||
- chore: 支付宝小程序开发工具不支持 生成图片 请以真机调试为准
|
|
||||||
- bug: 企业微信 2.20.3无法使用
|
|
||||||
## 1.9.3.5(2022-06-29)
|
|
||||||
- feat: justifyContent 增加 `space-around`、`space-between`
|
|
||||||
- feat: canvas 2d 也使用`getImageInfo`
|
|
||||||
- fix: 修复 `text`的 `text-decoration`错位
|
|
||||||
## 1.9.3.4(2022-06-20)
|
|
||||||
- fix: 修复 因创建节点速度问题导致顺序出错。
|
|
||||||
- fix: 修复 微信小程序 PC 无法显示本地图片
|
|
||||||
- fix: 修复 flex-box 对齐问题
|
|
||||||
- feat: 增加 `text-shadow`
|
|
||||||
- feat: 重写 `text` 对齐方式
|
|
||||||
- chore: 更新文档
|
|
||||||
## 1.9.3.3(2022-06-17)
|
|
||||||
- fix: 修复 支付宝小程序 canvas 2d 存在ctx.draw问题导致报错
|
|
||||||
- fix: 修复 支付宝小程序 toDataURL 存在权限问题改用 `toTempFilePath`
|
|
||||||
- fix: 修复 支付宝小程序 image size 问题导致 `objectFit` 无效
|
|
||||||
## 1.9.3.2(2022-06-14)
|
|
||||||
- fix: 修复 image 设置背景色不生效问题
|
|
||||||
- fix: 修复 nvue 环境判断缺少参数问题
|
|
||||||
## 1.9.3.1(2022-06-14)
|
|
||||||
- fix: 修复 bottom 定位不对问题
|
|
||||||
- fix: 修复 因小数导致计算出错换行问题
|
|
||||||
- feat: 增加 `useCORS` h5端图片跨域 在设置请求头无效果后试一下设置这个值
|
|
||||||
- chore: 更新文档
|
|
||||||
## 1.9.3(2022-06-13)
|
|
||||||
- feat: 增加 `zIndex`
|
|
||||||
- feat: 增加 `flex-box` 该功能处于原始阶段,非常简陋。
|
|
||||||
- tips: QQ小程序 vue3 不支持, 为 uni 官方BUG
|
|
||||||
## 1.9.2.9(2022-06-10)
|
|
||||||
- fix: 修复`text-align`及`margin`居中问题
|
|
||||||
## 1.9.2.8(2022-06-10)
|
|
||||||
- fix: 修复 Nvue `canvasToTempFilePathSync` 不生效问题
|
|
||||||
## 1.9.2.7(2022-06-10)
|
|
||||||
- fix: 修复 margin及padding的bug
|
|
||||||
- fix: 修复 Nvue `isCanvasToTempFilePath` 不生效问题
|
|
||||||
## 1.9.2.6(2022-06-09)
|
|
||||||
- fix: 修复 Nvue 不显示
|
|
||||||
- feat: 增加支持字体渐变
|
|
||||||
```html
|
|
||||||
<l-painter-text
|
|
||||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
|
||||||
css="background: linear-gradient(,#ff971b 0%, #1989fa 100%); background-clip: text" />
|
|
||||||
```
|
|
||||||
## 1.9.2.5(2022-06-09)
|
|
||||||
- chore: 更变获取父级宽度的设定
|
|
||||||
- chore: `pathType` 在canvas 2d 默认为 `url`
|
|
||||||
## 1.9.2.4(2022-06-08)
|
|
||||||
- fix: 修复 `pathType` 不生效问题
|
|
||||||
## 1.9.2.3(2022-06-08)
|
|
||||||
- fix: 修复 `canvasToTempFilePath` 漏写 `success` 参数
|
|
||||||
## 1.9.2.2(2022-06-07)
|
|
||||||
- chore: 更新文档
|
|
||||||
## 1.9.2.1(2022-06-07)
|
|
||||||
- fix: 修复 vue3 赋值给this再传入导致image无法绘制
|
|
||||||
- fix: 修复 `canvasToTempFilePathSync` 时机问题
|
|
||||||
- feat: canvas 2d 更改图片生成方式 `toDataURL`
|
|
||||||
## 1.9.2(2022-05-30)
|
|
||||||
- fix: 修复 `canvasToTempFilePathSync` 在 vue3 下只生成一次
|
|
||||||
## 1.9.1.7(2022-05-28)
|
|
||||||
- fix: 修复 `qrcode`显示不全问题
|
|
||||||
## 1.9.1.6(2022-05-28)
|
|
||||||
- fix: 修复 `canvasToTempFilePathSync` 会重复多次问题
|
|
||||||
- fix: 修复 `view` css `backgroundImage` 图片下载失败导致 子节点不渲染
|
|
||||||
## 1.9.1.5(2022-05-27)
|
|
||||||
- fix: 修正支付宝小程序 canvas 2d版本号 2.7.15
|
|
||||||
## 1.9.1.4(2022-05-22)
|
|
||||||
- fix: 修复字节小程序无法使用xml方式
|
|
||||||
- fix: 修复字节小程序无法使用base64(非2D情况下工具上无法显示)
|
|
||||||
- fix: 修复支付宝小程序 `canvasToTempFilePath` 报错
|
|
||||||
## 1.9.1.3(2022-04-29)
|
|
||||||
- fix: 修复vue3打包后uni对象为空后的报错
|
|
||||||
## 1.9.1.2(2022-04-25)
|
|
||||||
- fix: 删除多余文件
|
|
||||||
## 1.9.1.1(2022-04-25)
|
|
||||||
- fix: 修复图片不显示问题
|
|
||||||
## 1.9.1(2022-04-12)
|
|
||||||
- fix: 因四舍五入导致有些机型错位
|
|
||||||
- fix: 修复无views报错
|
|
||||||
- chore: nvue下因ios无法读取插件内static文件,改由下载方式
|
|
||||||
## 1.9.0(2022-03-20)
|
|
||||||
- fix: 因无法固定尺寸导致生成图片不全
|
|
||||||
- fix: 特定情况下text判断无效
|
|
||||||
- chore: 本地化APP Nvue webview
|
|
||||||
## 1.8.9(2022-02-20)
|
|
||||||
- fix: 修复 小程序下载最多10次并发的问题
|
|
||||||
- fix: 修复 APP端无法获取本地图片
|
|
||||||
- fix: 修复 APP Nvue端不执行问题
|
|
||||||
- chore: 增加图片缓存机制
|
|
||||||
## 1.8.8.8(2022-01-27)
|
|
||||||
- fix: 修复 主动调用尺寸问题
|
|
||||||
## 1.8.8.6(2022-01-26)
|
|
||||||
- fix: 修复 nvue 下无宽度时获取父级宽度
|
|
||||||
- fix: 修复 ios app 无法渲染问题
|
|
||||||
## 1.8.8(2022-01-23)
|
|
||||||
- fix: 修复 主动调用时无节点问题
|
|
||||||
- fix: 修复 `box-shadow` 颜色问题
|
|
||||||
- fix: 修复 `transform:rotate` 角度位置问题
|
|
||||||
- feat: 增加 `overflow:hidden`
|
|
||||||
## 1.8.7(2022-01-07)
|
|
||||||
- fix: 修复 image 方向为 `right` 时原始宽高问题
|
|
||||||
- feat: 支持 view 设置背景图 `background-image: url(xxx)`
|
|
||||||
- chore: 去掉可选链
|
|
||||||
## 1.8.6(2021-11-28)
|
|
||||||
- feat: 支持`view`对`inline-block`的子集使用`text-align`
|
|
||||||
## 1.8.5.5(2021-08-17)
|
|
||||||
- chore: 更新文档,删除 replace
|
|
||||||
- fix: 修复 text 值为 number时报错
|
|
||||||
## 1.8.5.4(2021-08-16)
|
|
||||||
- fix: 字节小程序兼容
|
|
||||||
## 1.8.5.3(2021-08-15)
|
|
||||||
- fix: 修复线性渐变与css现实效果不一致的问题
|
|
||||||
- chore: 更新文档
|
|
||||||
## 1.8.5.2(2021-08-13)
|
|
||||||
- chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
|
|
||||||
- 注意:这个功能H5暂时无法使用,因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
|
|
||||||
## 1.8.5.1(2021-08-10)
|
|
||||||
- fix: 修复因`margin`报错问题
|
|
||||||
## 1.8.5(2021-08-09)
|
|
||||||
- chore: 增加margin支持`auto`,以达到居中效果
|
|
||||||
## 1.8.4(2021-08-06)
|
|
||||||
- chore: 增加判断缓存文件条件
|
|
||||||
- fix: 修复css 多余空格报错问题
|
|
||||||
## 1.8.3(2021-08-04)
|
|
||||||
- tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位:position: 'absolute'
|
|
||||||
- fix: 修复只有一个view子元素时不计算高度的问题
|
|
||||||
## 1.8.2(2021-08-03)
|
|
||||||
- fix: 修复 path-type 为 `url` 无效问题
|
|
||||||
- fix: 修复 qrcode `text` 为空时报错问题
|
|
||||||
- fix: 修复 image `src` 动态设置时不生效问题
|
|
||||||
- feat: 增加 css 属性 `min-width` `max-width`
|
|
||||||
## 1.8.1(2021-08-02)
|
|
||||||
- fix: 修复无法加载本地图片
|
|
||||||
## 1.8.0(2021-08-02)
|
|
||||||
- chore 文档更新
|
|
||||||
- 使用旧版的同学不要升级!
|
|
||||||
## 1.8.0-beta(2021-07-30)
|
|
||||||
- ## 全新布局方式 不兼容旧版!
|
|
||||||
- chore: 布局方式变更
|
|
||||||
- tips: 微信canvas 2d 不支持真机调试
|
|
||||||
## 1.6.6(2021-07-09)
|
|
||||||
- chore: 统一命名规范,无须主动引入组件
|
|
||||||
## 1.6.5(2021-06-08)
|
|
||||||
- chore: 去掉console
|
|
||||||
## 1.6.4(2021-06-07)
|
|
||||||
- fix: 修复 数字 为纯字符串时不转换的BUG
|
|
||||||
## 1.6.3(2021-06-06)
|
|
||||||
- fix: 修复 PC 端放大的BUG
|
|
||||||
## 1.6.2(2021-05-31)
|
|
||||||
- fix: 修复 报`adaptor is not a function`错误
|
|
||||||
- fix: 修复 text 多行高度
|
|
||||||
- fix: 优化 默认文字的基准线
|
|
||||||
- feat: `@progress`事件,监听绘制进度
|
|
||||||
## 1.6.1(2021-02-28)
|
|
||||||
- 删除多余节点
|
|
||||||
## 1.6.0(2021-02-26)
|
|
||||||
- 调整为uni_modules目录规范
|
|
||||||
- 修复:transform的rotate不能为负数问题
|
|
||||||
- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
const styles = (v ='') => v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
|
|
||||||
const key = v.slice(0, v.indexOf(':'))
|
|
||||||
const value = v.slice(v.indexOf(':')+1)
|
|
||||||
return {
|
|
||||||
[key
|
|
||||||
.replace(/-([a-z])/g, function() { return arguments[1].toUpperCase()})
|
|
||||||
.replace(/\s+/g, '')
|
|
||||||
]: value.replace(/^\s+/, '').replace(/\s+$/, '') || ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
export function parent(parent) {
|
|
||||||
return {
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
[parent]: this
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
el: {
|
|
||||||
id: null,
|
|
||||||
css: {},
|
|
||||||
views: []
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
css: {
|
|
||||||
handler(v) {
|
|
||||||
if(this.canvasId) {
|
|
||||||
this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
|
|
||||||
this.canvasWidth = this.el.css && this.el.css.width || this.canvasWidth
|
|
||||||
this.canvasHeight = this.el.css && this.el.css.height || this.canvasHeight
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function children(parent, options = {}) {
|
|
||||||
const indexKey = options.indexKey || 'index'
|
|
||||||
return {
|
|
||||||
inject: {
|
|
||||||
[parent]: {
|
|
||||||
default: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
el: {
|
|
||||||
handler(v, o) {
|
|
||||||
if(JSON.stringify(v) != JSON.stringify(o))
|
|
||||||
this.bindRelation()
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
src: {
|
|
||||||
handler(v, o) {
|
|
||||||
if(v != o)
|
|
||||||
this.bindRelation()
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
handler(v, o) {
|
|
||||||
if(v != o) this.bindRelation()
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
css: {
|
|
||||||
handler(v, o) {
|
|
||||||
if(v != o)
|
|
||||||
this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
replace: {
|
|
||||||
handler(v, o) {
|
|
||||||
if(JSON.stringify(v) != JSON.stringify(o))
|
|
||||||
this.bindRelation()
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if(!this._uid) {
|
|
||||||
this._uid = this._.uid
|
|
||||||
}
|
|
||||||
Object.defineProperty(this, 'parent', {
|
|
||||||
get: () => this[parent] || [],
|
|
||||||
})
|
|
||||||
Object.defineProperty(this, 'index', {
|
|
||||||
get: () => {
|
|
||||||
this.bindRelation();
|
|
||||||
const {parent: {el: {views=[]}={}}={}} = this
|
|
||||||
return views.indexOf(this.el)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.el.type = this.type
|
|
||||||
if(this.uid) {
|
|
||||||
this.el.uid = this.uid
|
|
||||||
}
|
|
||||||
this.bindRelation()
|
|
||||||
},
|
|
||||||
// #ifdef VUE3
|
|
||||||
beforeUnmount() {
|
|
||||||
this.removeEl()
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
// #ifdef VUE2
|
|
||||||
beforeDestroy() {
|
|
||||||
this.removeEl()
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
methods: {
|
|
||||||
removeEl() {
|
|
||||||
if (this.parent) {
|
|
||||||
this.parent.el.views = this.parent.el.views.filter(
|
|
||||||
(item) => item._uid !== this._uid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bindRelation() {
|
|
||||||
if(!this.el._uid) {
|
|
||||||
this.el._uid = this._uid
|
|
||||||
}
|
|
||||||
if(['text','qrcode'].includes(this.type)) {
|
|
||||||
this.el.text = this.$slots && this.$slots.default && this.$slots.default[0].text || `${this.text || ''}`.replace(/\\n/g, '\n')
|
|
||||||
}
|
|
||||||
if(this.type == 'image') {
|
|
||||||
this.el.src = this.src
|
|
||||||
}
|
|
||||||
if (!this.parent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let views = this.parent.el.views || [];
|
|
||||||
if(views.indexOf(this.el) !== -1) {
|
|
||||||
this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
|
|
||||||
} else {
|
|
||||||
this.parent.el.views = [...views, this.el];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// this.bindRelation()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {parent, children} from '../common/relation';
|
|
||||||
export default {
|
|
||||||
name: 'lime-painter-image',
|
|
||||||
mixins:[children('painter')],
|
|
||||||
props: {
|
|
||||||
id: String,
|
|
||||||
css: [String, Object],
|
|
||||||
src: String
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
type: 'image',
|
|
||||||
el: {
|
|
||||||
css: {},
|
|
||||||
src: null
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {parent, children} from '../common/relation';
|
|
||||||
export default {
|
|
||||||
name: 'lime-painter-qrcode',
|
|
||||||
mixins:[children('painter')],
|
|
||||||
props: {
|
|
||||||
id: String,
|
|
||||||
css: [String, Object],
|
|
||||||
text: String
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
type: 'qrcode',
|
|
||||||
el: {
|
|
||||||
css: {},
|
|
||||||
text: null
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<text style="opacity: 0;height: 0;"><slot/></text>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {parent, children} from '../common/relation';
|
|
||||||
export default {
|
|
||||||
name: 'lime-painter-text',
|
|
||||||
mixins:[children('painter')],
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'text'
|
|
||||||
},
|
|
||||||
uid: String,
|
|
||||||
css: [String, Object],
|
|
||||||
text: [String, Number],
|
|
||||||
replace: Object,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// type: 'text',
|
|
||||||
el: {
|
|
||||||
css: {},
|
|
||||||
text: null
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view><slot/></view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {parent, children} from '../common/relation';
|
|
||||||
export default {
|
|
||||||
name: 'lime-painter-view',
|
|
||||||
mixins:[children('painter'), parent('painter')],
|
|
||||||
props: {
|
|
||||||
id: String,
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'view'
|
|
||||||
},
|
|
||||||
css: [String, Object],
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// type: 'view',
|
|
||||||
el: {
|
|
||||||
css: {},
|
|
||||||
views:[]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
@@ -1,461 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="lime-painter" ref="limepainter">
|
|
||||||
<view v-if="canvasId && size" :style="styles">
|
|
||||||
<!-- #ifndef APP-NVUE -->
|
|
||||||
<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
|
|
||||||
<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"
|
|
||||||
:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>
|
|
||||||
|
|
||||||
<!-- #endif -->
|
|
||||||
<!-- #ifdef APP-NVUE -->
|
|
||||||
<web-view :style="size" ref="webview"
|
|
||||||
src="/uni_modules/lime-painter/hybrid/html/index.html"
|
|
||||||
class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">
|
|
||||||
</web-view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
<slot />
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { parent } from '../common/relation'
|
|
||||||
import props from './props'
|
|
||||||
import {toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo }from './utils';
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
import { canIUseCanvas2d, isPC} from './utils';
|
|
||||||
import Painter from './painter';
|
|
||||||
// import Painter from '@painter'
|
|
||||||
const nvue = {}
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
import nvue from './nvue'
|
|
||||||
// #endif
|
|
||||||
export default {
|
|
||||||
name: 'lime-painter',
|
|
||||||
mixins: [props, parent('painter'), nvue],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
use2dCanvas: false,
|
|
||||||
canvasHeight: 150,
|
|
||||||
canvasWidth: null,
|
|
||||||
parentWidth: 0,
|
|
||||||
inited: false,
|
|
||||||
progress: 0,
|
|
||||||
firstRender: 0,
|
|
||||||
done: false,
|
|
||||||
tasks: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
styles() {
|
|
||||||
return `${this.size}${this.customStyle||''};` + (this.hidden && 'position: fixed; left: 1500rpx;')
|
|
||||||
},
|
|
||||||
canvasId() {
|
|
||||||
return `l-painter${this._ && this._.uid || this._uid}`
|
|
||||||
},
|
|
||||||
size() {
|
|
||||||
if (this.boardWidth && this.boardHeight) {
|
|
||||||
return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dpr() {
|
|
||||||
return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
|
|
||||||
},
|
|
||||||
boardWidth() {
|
|
||||||
const {width = 0} = (this.elements && this.elements.css) || this.elements || this
|
|
||||||
const w = toPx(width||this.width)
|
|
||||||
return w || Math.max(w, toPx(this.canvasWidth));
|
|
||||||
},
|
|
||||||
boardHeight() {
|
|
||||||
const {height = 0} = (this.elements && this.elements.css) || this.elements || this
|
|
||||||
const h = toPx(height||this.height)
|
|
||||||
return h || Math.max(h, toPx(this.canvasHeight));
|
|
||||||
},
|
|
||||||
hasBoard() {
|
|
||||||
return this.board && Object.keys(this.board).length
|
|
||||||
},
|
|
||||||
elements() {
|
|
||||||
return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
await sleep(30)
|
|
||||||
await this.getParentWeith()
|
|
||||||
this.$nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.$watch('elements', this.watchRender, {
|
|
||||||
deep: true,
|
|
||||||
immediate: true
|
|
||||||
});
|
|
||||||
}, 30)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// #ifdef VUE3
|
|
||||||
unmounted() {
|
|
||||||
this.done = false
|
|
||||||
this.inited = false
|
|
||||||
this.firstRender = 0
|
|
||||||
this.progress = 0
|
|
||||||
this.painter = null
|
|
||||||
clearTimeout(this.rendertimer)
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
// #ifdef VUE2
|
|
||||||
destroyed() {
|
|
||||||
this.done = false
|
|
||||||
this.inited = false
|
|
||||||
this.firstRender = 0
|
|
||||||
this.progress = 0
|
|
||||||
this.painter = null
|
|
||||||
clearTimeout(this.rendertimer)
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
methods: {
|
|
||||||
async watchRender(val, old) {
|
|
||||||
if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;
|
|
||||||
this.firstRender = 1
|
|
||||||
this.progress = 0
|
|
||||||
this.done = false
|
|
||||||
clearTimeout(this.rendertimer)
|
|
||||||
this.rendertimer = setTimeout(() => {
|
|
||||||
this.render(val);
|
|
||||||
}, this.beforeDelay)
|
|
||||||
},
|
|
||||||
async setFilePath(path, param) {
|
|
||||||
let filePath = path
|
|
||||||
const {pathType = this.pathType} = param || this
|
|
||||||
if (pathType == 'base64' && !isBase64(path)) {
|
|
||||||
filePath = await pathToBase64(path)
|
|
||||||
} else if (pathType == 'url' && isBase64(path)) {
|
|
||||||
filePath = await base64ToPath(path)
|
|
||||||
}
|
|
||||||
if (param && param.isEmit) {
|
|
||||||
this.$emit('success', filePath);
|
|
||||||
}
|
|
||||||
return filePath
|
|
||||||
},
|
|
||||||
async getSize(args) {
|
|
||||||
const {width} = args.css || args
|
|
||||||
const {height} = args.css || args
|
|
||||||
if (!this.size) {
|
|
||||||
if (width || height) {
|
|
||||||
this.canvasWidth = width || this.canvasWidth
|
|
||||||
this.canvasHeight = height || this.canvasHeight
|
|
||||||
await sleep(30);
|
|
||||||
} else {
|
|
||||||
await this.getParentWeith()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
canvasToTempFilePathSync(args) {
|
|
||||||
// this.stopWatch && this.stopWatch()
|
|
||||||
// this.stopWatch = this.$watch('done', (v) => {
|
|
||||||
// if (v) {
|
|
||||||
// this.canvasToTempFilePath(args)
|
|
||||||
// this.stopWatch && this.stopWatch()
|
|
||||||
// }
|
|
||||||
// }, {
|
|
||||||
// immediate: true
|
|
||||||
// })
|
|
||||||
this.tasks.push(args)
|
|
||||||
if(this.done){
|
|
||||||
this.runTask()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runTask(){
|
|
||||||
while(this.tasks.length){
|
|
||||||
const task = this.tasks.shift()
|
|
||||||
this.canvasToTempFilePath(task)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
getParentWeith() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
uni.createSelectorQuery()
|
|
||||||
.in(this)
|
|
||||||
.select(`.lime-painter`)
|
|
||||||
.boundingClientRect()
|
|
||||||
.exec(res => {
|
|
||||||
const {width, height} = res[0]||{}
|
|
||||||
this.parentWidth = Math.ceil(width||0)
|
|
||||||
this.canvasWidth = this.parentWidth || 300
|
|
||||||
this.canvasHeight = height || this.canvasHeight||150
|
|
||||||
resolve(res[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async render(args = {}) {
|
|
||||||
if(!Object.keys(args).length) {
|
|
||||||
return console.error('空对象')
|
|
||||||
}
|
|
||||||
this.progress = 0
|
|
||||||
this.done = false
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
this.tempFilePath.length = 0
|
|
||||||
// #endif
|
|
||||||
await this.getSize(args)
|
|
||||||
const ctx = await this.getContext();
|
|
||||||
|
|
||||||
let {
|
|
||||||
use2dCanvas,
|
|
||||||
boardWidth,
|
|
||||||
boardHeight,
|
|
||||||
canvas,
|
|
||||||
afterDelay
|
|
||||||
} = this;
|
|
||||||
if (use2dCanvas && !canvas) {
|
|
||||||
return Promise.reject(new Error('canvas 没创建'));
|
|
||||||
}
|
|
||||||
this.boundary = {
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: boardWidth,
|
|
||||||
height: boardHeight
|
|
||||||
};
|
|
||||||
this.painter = null
|
|
||||||
if (!this.painter) {
|
|
||||||
const {width} = args.css || args
|
|
||||||
const {height} = args.css || args
|
|
||||||
if(!width && this.parentWidth) {
|
|
||||||
Object.assign(args, {width: this.parentWidth})
|
|
||||||
}
|
|
||||||
const param = {
|
|
||||||
context: ctx,
|
|
||||||
canvas,
|
|
||||||
width: boardWidth,
|
|
||||||
height: boardHeight,
|
|
||||||
pixelRatio: this.dpr,
|
|
||||||
useCORS: this.useCORS,
|
|
||||||
createImage: getImageInfo.bind(this),
|
|
||||||
performance: this.performance,
|
|
||||||
listen: {
|
|
||||||
onProgress: (v) => {
|
|
||||||
this.progress = v
|
|
||||||
this.$emit('progress', v)
|
|
||||||
},
|
|
||||||
onEffectFail: (err) => {
|
|
||||||
this.$emit('faill', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.painter = new Painter(param)
|
|
||||||
}
|
|
||||||
try{
|
|
||||||
// vue3 赋值给data会引起图片无法绘制
|
|
||||||
const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))
|
|
||||||
this.boundary.height = this.canvasHeight = height
|
|
||||||
this.boundary.width = this.canvasWidth = width
|
|
||||||
await sleep(this.sleep);
|
|
||||||
await this.painter.render()
|
|
||||||
await new Promise(resolve => this.$nextTick(resolve));
|
|
||||||
if (!use2dCanvas) {
|
|
||||||
await this.canvasDraw();
|
|
||||||
}
|
|
||||||
if (afterDelay && use2dCanvas) {
|
|
||||||
await sleep(afterDelay);
|
|
||||||
}
|
|
||||||
this.$emit('done');
|
|
||||||
this.done = true
|
|
||||||
if (this.isCanvasToTempFilePath) {
|
|
||||||
this.canvasToTempFilePath()
|
|
||||||
.then(res => {
|
|
||||||
this.$emit('success', res.tempFilePath)
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.$emit('fail', new Error(JSON.stringify(err)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.runTask()
|
|
||||||
return Promise.resolve({
|
|
||||||
ctx,
|
|
||||||
draw: this.painter,
|
|
||||||
node: this.node
|
|
||||||
});
|
|
||||||
}catch(e){
|
|
||||||
//TODO handle the exception
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
canvasDraw(flag = false) {
|
|
||||||
return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this
|
|
||||||
.afterDelay)));
|
|
||||||
},
|
|
||||||
async getContext() {
|
|
||||||
if (!this.canvasWidth) {
|
|
||||||
this.$emit('fail', 'painter no size')
|
|
||||||
console.error('[lime-painter]: 给画板或父级设置尺寸')
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
if (this.ctx && this.inited) {
|
|
||||||
return Promise.resolve(this.ctx);
|
|
||||||
}
|
|
||||||
const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
|
|
||||||
const _getContext = () => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
uni.createSelectorQuery()
|
|
||||||
.in(this)
|
|
||||||
.select(`#${this.canvasId}`)
|
|
||||||
.boundingClientRect()
|
|
||||||
.exec(res => {
|
|
||||||
if (res) {
|
|
||||||
const ctx = uni.createCanvasContext(this.canvasId, this);
|
|
||||||
if (!this.inited) {
|
|
||||||
this.inited = true;
|
|
||||||
this.use2dCanvas = false;
|
|
||||||
this.canvas = res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 钉钉小程序框架不支持 measureText 方法,用此方法 mock
|
|
||||||
if (!ctx.measureText) {
|
|
||||||
function 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 => {
|
|
||||||
let fontSize = ctx.state && ctx.state.fontSize || 12;
|
|
||||||
const font = ctx.__font
|
|
||||||
if (font && fontSize == 12) {
|
|
||||||
fontSize = parseInt(font.split(' ')[3], 10);
|
|
||||||
}
|
|
||||||
fontSize /= 2;
|
|
||||||
return {
|
|
||||||
width: strLen(text) * fontSize
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
ctx.scale(dpr, dpr);
|
|
||||||
// #endif
|
|
||||||
this.ctx = ctx
|
|
||||||
resolve(this.ctx);
|
|
||||||
} else {
|
|
||||||
console.error('[lime-painter] no node')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (!use2dCanvas) {
|
|
||||||
return _getContext();
|
|
||||||
}
|
|
||||||
return new Promise(resolve => {
|
|
||||||
uni.createSelectorQuery()
|
|
||||||
.in(this)
|
|
||||||
.select(`#${this.canvasId}`)
|
|
||||||
.node()
|
|
||||||
.exec(res => {
|
|
||||||
let {node: canvas} = res && res[0]||{};
|
|
||||||
if(canvas) {
|
|
||||||
const ctx = canvas.getContext(type);
|
|
||||||
if (!this.inited) {
|
|
||||||
this.inited = true;
|
|
||||||
this.use2dCanvas = true;
|
|
||||||
this.canvas = canvas;
|
|
||||||
}
|
|
||||||
this.ctx = ctx
|
|
||||||
resolve(this.ctx);
|
|
||||||
} else {
|
|
||||||
console.error('[lime-painter]: no size')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
canvasToTempFilePath(args = {}) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
|
|
||||||
const success = async (res) => {
|
|
||||||
try {
|
|
||||||
const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)
|
|
||||||
const result = Object.assign(res, {tempFilePath})
|
|
||||||
args.success && args.success(result)
|
|
||||||
resolve(result)
|
|
||||||
} catch (e) {
|
|
||||||
this.$emit('fail', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
|
|
||||||
// let destWidth = width * dpr;
|
|
||||||
// let destHeight = height * dpr;
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
// width = destWidth;
|
|
||||||
// height = destHeight;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
const copyArgs = Object.assign({
|
|
||||||
// x,
|
|
||||||
// y,
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// destWidth,
|
|
||||||
// destHeight,
|
|
||||||
canvasId,
|
|
||||||
id: canvasId,
|
|
||||||
fileType,
|
|
||||||
quality,
|
|
||||||
}, args, {success});
|
|
||||||
// if(this.isPC || use2dCanvas) {
|
|
||||||
// copyArgs.canvas = this.canvas
|
|
||||||
// }
|
|
||||||
if (use2dCanvas) {
|
|
||||||
copyArgs.canvas = this.canvas
|
|
||||||
try{
|
|
||||||
// #ifndef MP-ALIPAY
|
|
||||||
const oFilePath = this.canvas.toDataURL(`image/${args.fileType||fileType}`.replace(/pg/, 'peg'), args.quality||quality)
|
|
||||||
if(/data:,/.test(oFilePath)) {
|
|
||||||
uni.canvasToTempFilePath(copyArgs, this);
|
|
||||||
} else {
|
|
||||||
const tempFilePath = await this.setFilePath(oFilePath, args)
|
|
||||||
args.success && args.success({tempFilePath})
|
|
||||||
resolve({tempFilePath})
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
this.canvas.toTempFilePath(copyArgs)
|
|
||||||
// #endif
|
|
||||||
}catch(e){
|
|
||||||
args.fail && args.fail(e)
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
if(this.ctx.toTempFilePath) {
|
|
||||||
// 钉钉
|
|
||||||
const ctx = uni.createCanvasContext(canvasId);
|
|
||||||
ctx.toTempFilePath(copyArgs);
|
|
||||||
} else {
|
|
||||||
my.canvasToTempFilePath(copyArgs);
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-ALIPAY
|
|
||||||
uni.canvasToTempFilePath(copyArgs, this);
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.lime-painter,
|
|
||||||
.lime-painter__canvas {
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
width: 100%;
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
flex: 1;
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
// #ifdef APP-NVUE
|
|
||||||
import {
|
|
||||||
sleep,
|
|
||||||
getImageInfo,
|
|
||||||
isBase64,
|
|
||||||
networkReg
|
|
||||||
} from './utils';
|
|
||||||
const dom = weex.requireModule('dom')
|
|
||||||
import {
|
|
||||||
version
|
|
||||||
} from '../../package.json'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tempFilePath: [],
|
|
||||||
isInitFile: false,
|
|
||||||
osName: uni.getSystemInfoSync().osName
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getParentWeith() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
dom.getComponentRect(this.$refs.limepainter, (res) => {
|
|
||||||
this.parentWidth = Math.ceil(res.size.width)
|
|
||||||
this.canvasWidth = this.canvasWidth || this.parentWidth || 300
|
|
||||||
this.canvasHeight = res.size.height || this.canvasHeight || 150
|
|
||||||
resolve(res.size)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onPageFinish() {
|
|
||||||
this.webview = this.$refs.webview
|
|
||||||
this.webview.evalJS(`init(${this.dpr})`)
|
|
||||||
},
|
|
||||||
onMessage(e) {
|
|
||||||
const res = e.detail.data[0] || null;
|
|
||||||
if (res.event) {
|
|
||||||
if (res.event == 'inited') {
|
|
||||||
this.inited = true
|
|
||||||
}
|
|
||||||
if (res.event == 'fail') {
|
|
||||||
this.$emit('fail', res)
|
|
||||||
}
|
|
||||||
if (res.event == 'layoutChange') {
|
|
||||||
const data = typeof res.data == 'string' ? JSON.parse(res.data) : res.data
|
|
||||||
this.canvasWidth = Math.ceil(data.width);
|
|
||||||
this.canvasHeight = Math.ceil(data.height);
|
|
||||||
}
|
|
||||||
if (res.event == 'progressChange') {
|
|
||||||
this.progress = res.data * 1
|
|
||||||
}
|
|
||||||
if (res.event == 'file') {
|
|
||||||
this.tempFilePath.push(res.data)
|
|
||||||
if (this.tempFilePath.length > 7) {
|
|
||||||
this.tempFilePath.shift()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (res.event == 'success') {
|
|
||||||
if (res.data) {
|
|
||||||
this.tempFilePath.push(res.data)
|
|
||||||
if (this.tempFilePath.length > 8) {
|
|
||||||
this.tempFilePath.shift()
|
|
||||||
}
|
|
||||||
if (this.isCanvasToTempFilePath) {
|
|
||||||
this.setFilePath(this.tempFilePath.join(''), {
|
|
||||||
isEmit: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$emit('fail', 'canvas no data')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$emit(res.event, JSON.parse(res.data));
|
|
||||||
} else if (res.file) {
|
|
||||||
this.file = res.data;
|
|
||||||
} else {
|
|
||||||
console.info(res[0])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getWebViewInited() {
|
|
||||||
if (this.inited) return Promise.resolve(this.inited);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.$watch(
|
|
||||||
'inited',
|
|
||||||
async val => {
|
|
||||||
if (val) {
|
|
||||||
resolve(val)
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getTempFilePath() {
|
|
||||||
if (this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.$watch(
|
|
||||||
'tempFilePath',
|
|
||||||
async val => {
|
|
||||||
if (val.length == 8) {
|
|
||||||
resolve(val.join(''))
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getWebViewDone() {
|
|
||||||
if (this.progress == 1) return Promise.resolve(this.progress);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.$watch(
|
|
||||||
'progress',
|
|
||||||
async val => {
|
|
||||||
if (val == 1) {
|
|
||||||
this.$emit('done')
|
|
||||||
this.done = true
|
|
||||||
this.runTask()
|
|
||||||
resolve(val)
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async render(args) {
|
|
||||||
try {
|
|
||||||
await this.getSize(args)
|
|
||||||
const {
|
|
||||||
width
|
|
||||||
} = args.css || args
|
|
||||||
if (!width && this.parentWidth) {
|
|
||||||
Object.assign(args, {
|
|
||||||
width: this.parentWidth
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const newNode = await this.calcImage(args);
|
|
||||||
await this.getWebViewInited()
|
|
||||||
this.webview.evalJS(`source(${JSON.stringify(newNode)})`)
|
|
||||||
await this.getWebViewDone()
|
|
||||||
await sleep(this.afterDelay)
|
|
||||||
if (this.isCanvasToTempFilePath) {
|
|
||||||
const params = {
|
|
||||||
fileType: this.fileType,
|
|
||||||
quality: this.quality
|
|
||||||
}
|
|
||||||
this.webview.evalJS(`save(${JSON.stringify(params)})`)
|
|
||||||
}
|
|
||||||
return Promise.resolve()
|
|
||||||
} catch (e) {
|
|
||||||
this.$emit('fail', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async calcImage(args) {
|
|
||||||
let node = JSON.parse(JSON.stringify(args))
|
|
||||||
const urlReg = /url\((.+)\)/
|
|
||||||
const {
|
|
||||||
backgroundImage
|
|
||||||
} = node.css || {}
|
|
||||||
const isBG = backgroundImage && urlReg.exec(backgroundImage)[1]
|
|
||||||
const url = node.url || node.src || isBG
|
|
||||||
if (['text', 'qrcode'].includes(node.type)) {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
if ((node.type === "image" || isBG) && url && !isBase64(url) && (this.osName == 'ios' || !networkReg
|
|
||||||
.test(url))) {
|
|
||||||
let {
|
|
||||||
path
|
|
||||||
} = await getImageInfo(url, true)
|
|
||||||
if (isBG) {
|
|
||||||
node.css.backgroundImage = `url(${path})`
|
|
||||||
} else {
|
|
||||||
node.src = path
|
|
||||||
}
|
|
||||||
} else if (node.views && node.views.length) {
|
|
||||||
for (let i = 0; i < node.views.length; i++) {
|
|
||||||
node.views[i] = await this.calcImage(node.views[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
},
|
|
||||||
async canvasToTempFilePath(args = {}) {
|
|
||||||
if (!this.inited) {
|
|
||||||
return this.$emit('fail', 'no init')
|
|
||||||
}
|
|
||||||
this.tempFilePath = []
|
|
||||||
if (args.fileType == 'jpg') {
|
|
||||||
args.fileType = 'jpeg'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.webview.evalJS(`save(${JSON.stringify(args)})`)
|
|
||||||
try {
|
|
||||||
let tempFilePath = await this.getTempFilePath()
|
|
||||||
|
|
||||||
tempFilePath = await this.setFilePath(tempFilePath, args)
|
|
||||||
args.success({
|
|
||||||
errMsg: "canvasToTempFilePath:ok",
|
|
||||||
tempFilePath
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.log('e', e)
|
|
||||||
args.fail({
|
|
||||||
error: e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,56 +0,0 @@
|
|||||||
export default {
|
|
||||||
props: {
|
|
||||||
board: Object,
|
|
||||||
pathType: String, // 'base64'、'url'
|
|
||||||
fileType: {
|
|
||||||
type: String,
|
|
||||||
default: 'png'
|
|
||||||
},
|
|
||||||
hidden: Boolean,
|
|
||||||
quality: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
css: [String, Object],
|
|
||||||
// styles: [String, Object],
|
|
||||||
width: [Number, String],
|
|
||||||
height: [Number, String],
|
|
||||||
pixelRatio: Number,
|
|
||||||
customStyle: String,
|
|
||||||
isCanvasToTempFilePath: Boolean,
|
|
||||||
// useCanvasToTempFilePath: Boolean,
|
|
||||||
sleep: {
|
|
||||||
type: Number,
|
|
||||||
default: 1000 / 30
|
|
||||||
},
|
|
||||||
beforeDelay: {
|
|
||||||
type: Number,
|
|
||||||
default: 100
|
|
||||||
},
|
|
||||||
afterDelay: {
|
|
||||||
type: Number,
|
|
||||||
default: 100
|
|
||||||
},
|
|
||||||
performance: Boolean,
|
|
||||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: '2d'
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
hybrid: Boolean,
|
|
||||||
timeout: {
|
|
||||||
type: Number,
|
|
||||||
default: 2000
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5 || APP-PLUS
|
|
||||||
useCORS: Boolean,
|
|
||||||
hidpi: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,368 +0,0 @@
|
|||||||
export const networkReg = /^(http|\/\/)/;
|
|
||||||
export const isBase64 = (path) => /^data:image\/(\w+);base64/.test(path);
|
|
||||||
export function sleep(delay) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, delay))
|
|
||||||
}
|
|
||||||
let {platform, SDKVersion} = uni.getSystemInfoSync()
|
|
||||||
export const isPC = /windows|mac/.test(platform)
|
|
||||||
// 缓存图片
|
|
||||||
let cache = {}
|
|
||||||
export function isNumber(value) {
|
|
||||||
return /^-?\d+(\.\d+)?$/.test(value);
|
|
||||||
}
|
|
||||||
export function toPx(value, baseSize, isDecimal = false) {
|
|
||||||
// 如果是数字
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
// 如果是字符串数字
|
|
||||||
if (isNumber(value)) {
|
|
||||||
return value * 1
|
|
||||||
}
|
|
||||||
// 如果有单位
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g
|
|
||||||
const results = reg.exec(value);
|
|
||||||
if (!value || !results) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const unit = results[3];
|
|
||||||
value = parseFloat(value);
|
|
||||||
let res = 0;
|
|
||||||
if (unit === 'rpx') {
|
|
||||||
res = uni.upx2px(value);
|
|
||||||
} else if (unit === 'px') {
|
|
||||||
res = value * 1;
|
|
||||||
} else if (unit === '%') {
|
|
||||||
res = value * toPx(baseSize) / 100;
|
|
||||||
} else if (unit === 'em') {
|
|
||||||
res = value * toPx(baseSize || 14);
|
|
||||||
}
|
|
||||||
return isDecimal ? res.toFixed(2) * 1 : Math.round(res);
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算版本
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
function gte(version) {
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
SDKVersion = my.SDKVersion
|
|
||||||
// #endif
|
|
||||||
return compareVersion(SDKVersion, version) >= 0;
|
|
||||||
}
|
|
||||||
export function canIUseCanvas2d() {
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
return gte('2.9.2');
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
return gte('2.7.15');
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-TOUTIAO
|
|
||||||
return gte('1.78.0');
|
|
||||||
// #endif
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifdef MP
|
|
||||||
export const prefix = () => {
|
|
||||||
// #ifdef MP-TOUTIAO
|
|
||||||
return tt
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
return wx
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-BAIDU
|
|
||||||
return swan
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
return my
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-QQ
|
|
||||||
return qq
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-360
|
|
||||||
return qh
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* base64转路径
|
|
||||||
* @param {Object} base64
|
|
||||||
*/
|
|
||||||
export function base64ToPath(base64) {
|
|
||||||
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// #ifdef MP
|
|
||||||
const fs = uni.getFileSystemManager()
|
|
||||||
//自定义文件名
|
|
||||||
if (!format) {
|
|
||||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
|
||||||
}
|
|
||||||
const time = new Date().getTime();
|
|
||||||
let pre = prefix()
|
|
||||||
// #ifdef MP-TOUTIAO
|
|
||||||
const filePath = `${pre.getEnvInfoSync().common.USER_DATA_PATH}/${time}.${format}`
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
|
|
||||||
// #endif
|
|
||||||
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 (!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()
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
}, (error) => {
|
|
||||||
bitmap.clear()
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路径转base64
|
|
||||||
* @param {Object} string
|
|
||||||
*/
|
|
||||||
export function pathToBase64(path) {
|
|
||||||
if (/^data:/.test(path)) return path
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// #ifdef H5
|
|
||||||
let image = new Image();
|
|
||||||
image.setAttribute("crossOrigin", 'Anonymous');
|
|
||||||
image.onload = function() {
|
|
||||||
let canvas = document.createElement('canvas');
|
|
||||||
canvas.width = this.naturalWidth;
|
|
||||||
canvas.height = this.naturalHeight;
|
|
||||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
||||||
let result = canvas.toDataURL('image/png')
|
|
||||||
resolve(result);
|
|
||||||
canvas.height = canvas.width = 0
|
|
||||||
}
|
|
||||||
image.src = path + '?v=' + Math.random()
|
|
||||||
image.onerror = (error) => {
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP
|
|
||||||
if (uni.canIUse('getFileSystemManager')) {
|
|
||||||
uni.getFileSystemManager().readFile({
|
|
||||||
filePath: path,
|
|
||||||
encoding: 'base64',
|
|
||||||
success: (res) => {
|
|
||||||
resolve('data:image/png;base64,' + res.data)
|
|
||||||
},
|
|
||||||
fail: (error) => {
|
|
||||||
console.error({error, path})
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
|
|
||||||
entry.file((file) => {
|
|
||||||
const fileReader = new plus.io.FileReader()
|
|
||||||
fileReader.onload = (data) => {
|
|
||||||
resolve(data.target.result)
|
|
||||||
}
|
|
||||||
fileReader.onerror = (error) => {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
fileReader.readAsDataURL(file)
|
|
||||||
}, reject)
|
|
||||||
}, reject)
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function getImageInfo(path, useCORS) {
|
|
||||||
const isCanvas2D = this && this.canvas && this.canvas.createImage
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
// let time = +new Date()
|
|
||||||
let src = path.replace(/^@\//,'/')
|
|
||||||
if (cache[path] && cache[path].errMsg) {
|
|
||||||
resolve(cache[path])
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// #ifdef MP || APP-PLUS
|
|
||||||
if (isBase64(path) && (isCanvas2D ? isPC : true)) {
|
|
||||||
src = await base64ToPath(path)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5
|
|
||||||
if(useCORS) {
|
|
||||||
src = await pathToBase64(path)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
} catch (error) {
|
|
||||||
reject({
|
|
||||||
...error,
|
|
||||||
src
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
if(isCanvas2D && !isPC) {
|
|
||||||
const img = this.canvas.createImage()
|
|
||||||
img.onload = function() {
|
|
||||||
const image = {
|
|
||||||
path: img,
|
|
||||||
width: img.width,
|
|
||||||
height: img.height
|
|
||||||
}
|
|
||||||
cache[path] = image
|
|
||||||
resolve(cache[path])
|
|
||||||
}
|
|
||||||
img.onerror = function(err) {
|
|
||||||
reject({err,path})
|
|
||||||
}
|
|
||||||
img.src = src
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
uni.getImageInfo({
|
|
||||||
src,
|
|
||||||
success: (image) => {
|
|
||||||
const localReg = /^\.|^\/(?=[^\/])/;
|
|
||||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
|
||||||
image.path = localReg.test(src) ? `/${image.path}` : image.path;
|
|
||||||
// #endif
|
|
||||||
if(isCanvas2D) {
|
|
||||||
const img = this.canvas.createImage()
|
|
||||||
img.onload = function() {
|
|
||||||
image.path = img
|
|
||||||
cache[path] = image
|
|
||||||
resolve(cache[path])
|
|
||||||
}
|
|
||||||
img.onerror = function(err) {
|
|
||||||
reject({err,path})
|
|
||||||
}
|
|
||||||
img.src = src
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
// console.log('getImageInfo', +new Date() - time)
|
|
||||||
// ios 比较严格 可能需要设置跨域
|
|
||||||
if(uni.getSystemInfoSync().osName == 'ios' && useCORS) {
|
|
||||||
pathToBase64(image.path).then(base64 => {
|
|
||||||
image.path = base64
|
|
||||||
cache[path] = image
|
|
||||||
resolve(cache[path])
|
|
||||||
}).catch(err => {
|
|
||||||
console.error({err, path})
|
|
||||||
reject({err,path})
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
cache[path] = image
|
|
||||||
resolve(cache[path])
|
|
||||||
},
|
|
||||||
fail(err) {
|
|
||||||
console.error({err, path})
|
|
||||||
reject({err,path})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
const getLocalFilePath = (path) => {
|
|
||||||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path
|
|
||||||
.indexOf('_downloads') === 0) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if (path.indexOf('file://') === 0) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if (path.indexOf('/storage/emulated/0/') === 0) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if (path.indexOf('/') === 0) {
|
|
||||||
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
|
|
||||||
if (localFilePath !== path) {
|
|
||||||
return localFilePath
|
|
||||||
} else {
|
|
||||||
path = path.substr(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '_www/' + path
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,119 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title></title>
|
|
||||||
<style type="text/css">
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
canvas {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: hidden;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<canvas id="lime-painter"></canvas>
|
|
||||||
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
|
|
||||||
<script type="text/javascript" src="./painter.js"></script>
|
|
||||||
<script>
|
|
||||||
var cache = [];
|
|
||||||
var painter = null;
|
|
||||||
var canvas = null;
|
|
||||||
var context = null;
|
|
||||||
var timer = null;
|
|
||||||
var pixelRatio = 1;
|
|
||||||
console.log = function (...args) {
|
|
||||||
postMessage(args);
|
|
||||||
};
|
|
||||||
// function stringify(key, value) {
|
|
||||||
// if (typeof value === 'object' && value !== null) {
|
|
||||||
// if (cache.indexOf(value) !== -1) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// cache.push(value);
|
|
||||||
// }
|
|
||||||
// return value;
|
|
||||||
// };
|
|
||||||
|
|
||||||
function emit(event, data) {
|
|
||||||
postMessage({
|
|
||||||
event,
|
|
||||||
data: (typeof data !== 'object' && data !== null ? data : JSON.stringify(data))
|
|
||||||
});
|
|
||||||
cache = [];
|
|
||||||
};
|
|
||||||
function postMessage(data) {
|
|
||||||
uni.postMessage({
|
|
||||||
data
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function init(dpr) {
|
|
||||||
canvas = document.querySelector('#lime-painter');
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
pixelRatio = dpr || window.devicePixelRatio;
|
|
||||||
painter = new Painter({
|
|
||||||
id: 'lime-painter',
|
|
||||||
context,
|
|
||||||
canvas,
|
|
||||||
pixelRatio,
|
|
||||||
width: canvas.offsetWidth,
|
|
||||||
height: canvas.offsetHeight,
|
|
||||||
listen: {
|
|
||||||
onProgress(v) {
|
|
||||||
emit('progressChange', v);
|
|
||||||
},
|
|
||||||
onEffectFail(err) {
|
|
||||||
//console.error(err)
|
|
||||||
emit('fail', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
emit('inited', true);
|
|
||||||
};
|
|
||||||
function save(args) {
|
|
||||||
delete args.success;
|
|
||||||
delete args.fail;
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
const path = painter.save(args);
|
|
||||||
if (typeof path == 'string') {
|
|
||||||
const index = Math.ceil(path.length / 8);
|
|
||||||
for (var i = 0; i < 8; i++) {
|
|
||||||
if (i == 7) {
|
|
||||||
emit('success', path.substr(i * index, index));
|
|
||||||
} else {
|
|
||||||
emit('file', path.substr(i * index, index));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// console.log('canvas no data')
|
|
||||||
emit('fail', 'canvas no data');
|
|
||||||
};
|
|
||||||
}, 30);
|
|
||||||
};
|
|
||||||
async function source(args) {
|
|
||||||
let size = await painter.source(args);
|
|
||||||
emit('layoutChange', size);
|
|
||||||
if(!canvas.height) {
|
|
||||||
console.log('canvas no size')
|
|
||||||
emit('fail', 'canvas no size');
|
|
||||||
}
|
|
||||||
painter.render().catch(err => {
|
|
||||||
// console.error(err)
|
|
||||||
emit('fail', err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,388 +0,0 @@
|
|||||||
/*
|
|
||||||
* HTML5 Parser By Sam Blowes
|
|
||||||
*
|
|
||||||
* Designed for HTML5 documents
|
|
||||||
*
|
|
||||||
* Original code by John Resig (ejohn.org)
|
|
||||||
* http://ejohn.org/blog/pure-javascript-html-parser/
|
|
||||||
* Original code by Erik Arvidsson, Mozilla Public License
|
|
||||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
||||||
*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* License
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* This code is triple licensed using Apache Software License 2.0,
|
|
||||||
* Mozilla Public License or GNU Public License
|
|
||||||
*
|
|
||||||
* ////////////////////////////////////////////////////////////////////////////
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
|
||||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* ////////////////////////////////////////////////////////////////////////////
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License
|
|
||||||
* Version 1.1 (the "License"); you may not use this file except in
|
|
||||||
* compliance with the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS"
|
|
||||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing rights and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* The Original Code is Simple HTML Parser.
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Erik Arvidsson.
|
|
||||||
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
|
|
||||||
* Reserved.
|
|
||||||
*
|
|
||||||
* ////////////////////////////////////////////////////////////////////////////
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* Usage
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* // Use like so:
|
|
||||||
* HTMLParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // or to get an XML string:
|
|
||||||
* HTMLtoXML(htmlString);
|
|
||||||
*
|
|
||||||
* // or to get an XML DOM Document
|
|
||||||
* HTMLtoDOM(htmlString);
|
|
||||||
*
|
|
||||||
* // or to inject into an existing document/DOM node
|
|
||||||
* HTMLtoDOM(htmlString, document);
|
|
||||||
* HTMLtoDOM(htmlString, document.body);
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// Regular Expressions for parsing tags and attributes
|
|
||||||
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
|
|
||||||
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
|
|
||||||
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
|
|
||||||
|
|
||||||
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
|
|
||||||
// fixed by xxx 将 ins 标签从块级名单中移除
|
|
||||||
|
|
||||||
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
|
|
||||||
|
|
||||||
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
|
|
||||||
// (and which close themselves)
|
|
||||||
|
|
||||||
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
|
|
||||||
|
|
||||||
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
|
|
||||||
|
|
||||||
var special = makeMap('script,style');
|
|
||||||
function HTMLParser(html, handler) {
|
|
||||||
var index;
|
|
||||||
var chars;
|
|
||||||
var match;
|
|
||||||
var stack = [];
|
|
||||||
var last = html;
|
|
||||||
|
|
||||||
stack.last = function () {
|
|
||||||
return this[this.length - 1];
|
|
||||||
};
|
|
||||||
|
|
||||||
while (html) {
|
|
||||||
chars = true; // Make sure we're not in a script or style element
|
|
||||||
|
|
||||||
if (!stack.last() || !special[stack.last()]) {
|
|
||||||
// Comment
|
|
||||||
if (html.indexOf('<!--') == 0) {
|
|
||||||
index = html.indexOf('-->');
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
if (handler.comment) {
|
|
||||||
handler.comment(html.substring(4, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
html = html.substring(index + 3);
|
|
||||||
chars = false;
|
|
||||||
} // end tag
|
|
||||||
|
|
||||||
} else if (html.indexOf('</') == 0) {
|
|
||||||
match = html.match(endTag);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
html = html.substring(match[0].length);
|
|
||||||
match[0].replace(endTag, parseEndTag);
|
|
||||||
chars = false;
|
|
||||||
} // start tag
|
|
||||||
|
|
||||||
} else if (html.indexOf('<') == 0) {
|
|
||||||
match = html.match(startTag);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
html = html.substring(match[0].length);
|
|
||||||
match[0].replace(startTag, parseStartTag);
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chars) {
|
|
||||||
index = html.indexOf('<');
|
|
||||||
var text = index < 0 ? html : html.substring(0, index);
|
|
||||||
html = index < 0 ? '' : html.substring(index);
|
|
||||||
|
|
||||||
if (handler.chars) {
|
|
||||||
handler.chars(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
|
|
||||||
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
|
|
||||||
|
|
||||||
if (handler.chars) {
|
|
||||||
handler.chars(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
parseEndTag('', stack.last());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (html == last) {
|
|
||||||
throw 'Parse Error: ' + html;
|
|
||||||
}
|
|
||||||
|
|
||||||
last = html;
|
|
||||||
} // Clean up any remaining tags
|
|
||||||
|
|
||||||
|
|
||||||
parseEndTag();
|
|
||||||
|
|
||||||
function parseStartTag(tag, tagName, rest, unary) {
|
|
||||||
tagName = tagName.toLowerCase();
|
|
||||||
if (block[tagName]) {
|
|
||||||
while (stack.last() && inline[stack.last()]) {
|
|
||||||
parseEndTag('', stack.last());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeSelf[tagName] && stack.last() == tagName) {
|
|
||||||
parseEndTag('', tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
unary = empty[tagName] || !!unary;
|
|
||||||
|
|
||||||
if (!unary) {
|
|
||||||
stack.push(tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler.start) {
|
|
||||||
var attrs = [];
|
|
||||||
rest.replace(attr, function (match, name) {
|
|
||||||
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
|
|
||||||
attrs.push({
|
|
||||||
name: name,
|
|
||||||
value: value,
|
|
||||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (handler.start) {
|
|
||||||
handler.start(tagName, attrs, unary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEndTag(tag, tagName) {
|
|
||||||
// If no tag name is provided, clean shop
|
|
||||||
if (!tagName) {
|
|
||||||
var pos = 0;
|
|
||||||
} // Find the closest opened tag of the same type
|
|
||||||
else {
|
|
||||||
for (var pos = stack.length - 1; pos >= 0; pos--) {
|
|
||||||
if (stack[pos] == tagName) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos >= 0) {
|
|
||||||
// Close all the open elements, up the stack
|
|
||||||
for (var i = stack.length - 1; i >= pos; i--) {
|
|
||||||
if (handler.end) {
|
|
||||||
handler.end(stack[i]);
|
|
||||||
}
|
|
||||||
} // Remove the open elements from the stack
|
|
||||||
|
|
||||||
|
|
||||||
stack.length = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeMap(str) {
|
|
||||||
var obj = {};
|
|
||||||
var items = str.split(',');
|
|
||||||
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
obj[items[i]] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDOCTYPE(html) {
|
|
||||||
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAttrs(attrs) {
|
|
||||||
return attrs.reduce(function (pre, attr) {
|
|
||||||
var value = attr.value;
|
|
||||||
var name = attr.name;
|
|
||||||
if (pre[name]) {
|
|
||||||
pre[name] = pre[name] + " " + value;
|
|
||||||
} else {
|
|
||||||
pre[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
function convertStyleStringToJSON(styleString) {
|
|
||||||
var styles = styleString.split(";"); // 通过分号将样式字符串分割为多个样式声明
|
|
||||||
var result = {};
|
|
||||||
|
|
||||||
styles.forEach(function(style) {
|
|
||||||
var styleParts = style.split(":"); // 通过冒号将样式声明分割为属性和值
|
|
||||||
var property = styleParts[0].trim();
|
|
||||||
var value = styleParts[1] && styleParts[1].trim();
|
|
||||||
|
|
||||||
if (property && value) {
|
|
||||||
result[property] = value; // 将属性和值添加到结果对象中
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
function parseHtml(html) {
|
|
||||||
html = removeDOCTYPE(html);
|
|
||||||
var stacks = [];
|
|
||||||
var results = {
|
|
||||||
node: 'root',
|
|
||||||
children: []
|
|
||||||
};
|
|
||||||
HTMLParser(html, {
|
|
||||||
start: function start(tag, attrs, unary) {
|
|
||||||
var node = {
|
|
||||||
name: tag
|
|
||||||
};
|
|
||||||
|
|
||||||
if (attrs.length !== 0) {
|
|
||||||
node.attrs = parseAttrs(attrs);
|
|
||||||
node.styles = node.attrs.style ? convertStyleStringToJSON(node.attrs.style) : {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!node.type) {
|
|
||||||
if(inline[node.name] && node.name !== 'img' ) {
|
|
||||||
node.type = 'text';
|
|
||||||
if(node.name == 'br') {
|
|
||||||
node.text = '\n'
|
|
||||||
} else if(node.name == 'strong'){
|
|
||||||
node.styles.fontWeight = 'bold'
|
|
||||||
}
|
|
||||||
} else if(node.name == 'img'){
|
|
||||||
node.type = 'image'
|
|
||||||
node.src = node.attrs.src
|
|
||||||
} else {
|
|
||||||
node.type = 'view'
|
|
||||||
if(['h1','h2','h3','h4','h5','h6'].includes(node.name)) {
|
|
||||||
node.styles.fontWeight = 'bold'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (unary) {
|
|
||||||
var parent = stacks[0] || results;
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
} else {
|
|
||||||
stacks.unshift(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function end(tag) {
|
|
||||||
var node = stacks.shift();
|
|
||||||
if (node.name !== tag) console.error('invalid state: mismatch end tag');
|
|
||||||
if (stacks.length === 0) {
|
|
||||||
results.children.push(node);
|
|
||||||
} else {
|
|
||||||
var parent = stacks[0];
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
const isTextBox = node.children && node.children.length > 1 && node.children.every(child => {
|
|
||||||
return ['text','image'].includes(child.type)
|
|
||||||
})
|
|
||||||
if(isTextBox) {
|
|
||||||
node.type = 'textBox'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chars: function chars(text) {
|
|
||||||
var node = {
|
|
||||||
type: 'text',
|
|
||||||
text: text
|
|
||||||
};
|
|
||||||
|
|
||||||
if (stacks.length === 0) {
|
|
||||||
results.children.push(node);
|
|
||||||
} else {
|
|
||||||
var parent = stacks[0];
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
comment: function comment(text) {
|
|
||||||
var node = {
|
|
||||||
node: 'comment',
|
|
||||||
text: text
|
|
||||||
};
|
|
||||||
var parent = stacks[0];
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return results.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default parseHtml;
|
|
||||||
@@ -1,961 +0,0 @@
|
|||||||
# Painter 画板 测试版
|
|
||||||
|
|
||||||
> uniapp 海报画板,更优雅的海报生成方案
|
|
||||||
> [查看更多](https://limeui.qcoon.cn/#/painter)
|
|
||||||
|
|
||||||
## 平台兼容
|
|
||||||
|
|
||||||
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
|
|
||||||
| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
|
|
||||||
| √ | √ | √ | 未测 | √ | √ | √ |
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
在市场导入**[海报画板](https://ext.dcloud.net.cn/plugin?id=2389)uni_modules**版本的即可,无需`import`
|
|
||||||
|
|
||||||
## 代码演示
|
|
||||||
|
|
||||||
### 插件demo
|
|
||||||
- lime-painter 为 demo
|
|
||||||
- 位于 uni_modules/lime-painter/components/lime-painter
|
|
||||||
- 导入插件后直接使用可查看demo
|
|
||||||
```vue
|
|
||||||
<lime-painter />
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 基本用法
|
|
||||||
|
|
||||||
- 插件提供 JSON 及 Template 的方式绘制海报
|
|
||||||
- 参考 css 块状流布局模拟 css schema。
|
|
||||||
- 另外flex布局还不是成完善,请谨慎使用,普通的流布局我觉得已经够用了。
|
|
||||||
|
|
||||||
#### 方式一 Template
|
|
||||||
|
|
||||||
- 提供`l-painter-view`、`l-painter-text`、`l-painter-image`、`l-painter-qrcode`四种类型组件
|
|
||||||
- 通过 `css` 属性绘制样式,与 style 使用方式保持一致。
|
|
||||||
```html
|
|
||||||
<l-painter>
|
|
||||||
//如果使用Template出现顺序错乱,可使用`template` 等所有变量完成再显示
|
|
||||||
<template v-if="show">
|
|
||||||
<l-painter-view
|
|
||||||
css="background: #07c160; height: 120rpx; width: 120rpx; display: inline-block"
|
|
||||||
></l-painter-view>
|
|
||||||
<l-painter-view
|
|
||||||
css="background: #1989fa; height: 120rpx; width: 120rpx; border-top-right-radius: 60rpx; border-bottom-left-radius: 60rpx; display: inline-block; margin: 0 30rpx;"
|
|
||||||
></l-painter-view>
|
|
||||||
<l-painter-view
|
|
||||||
css="background: #ff9d00; height: 120rpx; width: 120rpx; border-radius: 50%; display: inline-block"
|
|
||||||
></l-painter-view>
|
|
||||||
<template>
|
|
||||||
</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二 JSON
|
|
||||||
|
|
||||||
- 在 json 里四种类型组件的`type`为`view`、`text`、`image`、`qrcode`
|
|
||||||
- 通过 `board` 设置海报所需的 JSON 数据进行绘制或`ref`获取组件实例调用组件内的`render(json)`
|
|
||||||
- 所有类型的 schema 都具有`css`字段,css 的 key 值使用**驼峰**如:`lineHeight`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter :board="poster"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
poster: {
|
|
||||||
css: {
|
|
||||||
// 根节点若无尺寸,自动获取父级节点
|
|
||||||
width: '750rpx'
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
css: {
|
|
||||||
background: "#07c160",
|
|
||||||
height: "120rpx",
|
|
||||||
width: "120rpx",
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
type: "view"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
css: {
|
|
||||||
background: "#1989fa",
|
|
||||||
height: "120rpx",
|
|
||||||
width: "120rpx",
|
|
||||||
borderTopRightRadius: "60rpx",
|
|
||||||
borderBottomLeftRadius: "60rpx",
|
|
||||||
display: "inline-block",
|
|
||||||
margin: "0 30rpx"
|
|
||||||
},
|
|
||||||
views: [],
|
|
||||||
type: "view"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
css: {
|
|
||||||
background: "#ff9d00",
|
|
||||||
height: "120rpx",
|
|
||||||
width: "120rpx",
|
|
||||||
borderRadius: "50%",
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
views: [],
|
|
||||||
type: "view"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### View 容器
|
|
||||||
|
|
||||||
- 类似于 `div` 可以嵌套承载更多的 view、text、image,qrcode 共同构建一颗完整的节点树
|
|
||||||
- 在 JSON 里具有 `views` 的数组字段,用于嵌套承载节点。
|
|
||||||
|
|
||||||
#### 方式一 Template
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter>
|
|
||||||
<l-painter-view css="background: #f0f0f0; padding-top: 100rpx;">
|
|
||||||
<l-painter-view
|
|
||||||
css="background: #d9d9d9; width: 33.33%; height: 100rpx; display: inline-block"
|
|
||||||
></l-painter-view>
|
|
||||||
<l-painter-view
|
|
||||||
css="background: #bfbfbf; width: 66.66%; height: 100rpx; display: inline-block"
|
|
||||||
></l-painter-view>
|
|
||||||
</l-painter-view>
|
|
||||||
</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二 JSON
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
css: {},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
type: 'view',
|
|
||||||
css: {
|
|
||||||
background: '#f0f0f0',
|
|
||||||
paddingTop: '100rpx'
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
type: 'view',
|
|
||||||
css: {
|
|
||||||
background: '#d9d9d9',
|
|
||||||
width: '33.33%',
|
|
||||||
height: '100rpx',
|
|
||||||
display: 'inline-block'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'view',
|
|
||||||
css: {
|
|
||||||
background: '#bfbfbf',
|
|
||||||
width: '66.66%',
|
|
||||||
height: '100rpx',
|
|
||||||
display: 'inline-block'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text 文本
|
|
||||||
|
|
||||||
- 通过 `text` 属性填写文本内容。
|
|
||||||
- 支持`\n`换行符
|
|
||||||
- 支持省略号,使用 css 的`line-clamp`设置行数,当文字内容超过会显示省略号。
|
|
||||||
- 支持`text-decoration`
|
|
||||||
|
|
||||||
#### 方式一 Template
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter>
|
|
||||||
<l-painter-view css="background: #e0e2db; padding: 30rpx; color: #222a29">
|
|
||||||
<l-painter-text
|
|
||||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
|
||||||
/>
|
|
||||||
<l-painter-text
|
|
||||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
|
||||||
css="text-align:center; padding-top: 20rpx; text-decoration: line-through "
|
|
||||||
/>
|
|
||||||
<l-painter-text
|
|
||||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
|
||||||
css="text-align:right; padding-top: 20rpx"
|
|
||||||
/>
|
|
||||||
<l-painter-text
|
|
||||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
|
||||||
css="line-clamp: 3; padding-top: 20rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%); background-clip: text"
|
|
||||||
/>
|
|
||||||
</l-painter-view>
|
|
||||||
</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二 JSON
|
|
||||||
|
|
||||||
```js
|
|
||||||
// 基础用法
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
|
||||||
css: {
|
|
||||||
// 设置居中对齐
|
|
||||||
textAlign: 'center',
|
|
||||||
// 设置中划线
|
|
||||||
textDecoration: 'line-through'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
|
||||||
css: {
|
|
||||||
// 设置右对齐
|
|
||||||
textAlign: 'right',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
|
||||||
css: {
|
|
||||||
// 设置行数,超出显示省略号
|
|
||||||
lineClamp: 3,
|
|
||||||
// 渐变文字
|
|
||||||
background: 'linear-gradient(,#ff971b 0%, #1989fa 100%)',
|
|
||||||
backgroundClip: 'text'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image 图片
|
|
||||||
|
|
||||||
- 通过 `src` 属性填写图片路径。
|
|
||||||
- 图片路径支持:网络图片,本地 static 里的图片路径,缓存路径,**字节的static目录是写相对路径**
|
|
||||||
- 通过 `css` 的 `object-fit`属性可以设置图片的填充方式,可选值见下方 CSS 表格。
|
|
||||||
- 通过 `css` 的 `object-position`配合 `object-fit` 可以设置图片的对齐方式,类似于`background-position`,详情见下方 CSS 表格。
|
|
||||||
- 使用网络图片时:小程序需要去公众平台配置 [downloadFile](https://mp.weixin.qq.com/) 域名
|
|
||||||
- 使用网络图片时:**H5 和 Nvue 需要决跨域问题**
|
|
||||||
|
|
||||||
#### 方式一 Template
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter>
|
|
||||||
<!-- 基础用法 -->
|
|
||||||
<l-painter-image
|
|
||||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
|
||||||
css="width: 200rpx; height: 200rpx"
|
|
||||||
/>
|
|
||||||
<!-- 填充方式 -->
|
|
||||||
<!-- css object-fit 设置 填充方式 见下方表格-->
|
|
||||||
<l-painter-image
|
|
||||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
|
||||||
css="width: 200rpx; height: 200rpx; object-fit: contain; background: #eee"
|
|
||||||
/>
|
|
||||||
<!-- css object-position 设置 图片的对齐方式-->
|
|
||||||
<l-painter-image
|
|
||||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
|
||||||
css="width: 200rpx; height: 200rpx; object-fit: contain; object-position: 50% 50%; background: #eee"
|
|
||||||
/>
|
|
||||||
</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二 JSON
|
|
||||||
|
|
||||||
```js
|
|
||||||
// 基础用法
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
|
||||||
css: {
|
|
||||||
width: '200rpx',
|
|
||||||
height: '200rpx'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 填充方式
|
|
||||||
// css objectFit 设置 填充方式 见下方表格
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
|
||||||
css: {
|
|
||||||
width: '200rpx',
|
|
||||||
height: '200rpx',
|
|
||||||
objectFit: 'contain'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// css objectPosition 设置 图片的对齐方式
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
|
||||||
css: {
|
|
||||||
width: '200rpx',
|
|
||||||
height: '200rpx',
|
|
||||||
objectFit: 'contain',
|
|
||||||
objectPosition: '50% 50%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Qrcode 二维码
|
|
||||||
|
|
||||||
- 通过`text`属性填写需要生成二维码的文本。
|
|
||||||
- 通过 `css` 里的 `color` 可设置生成码点的颜色。
|
|
||||||
- 通过 `css` 里的 `background`可设置背景色。
|
|
||||||
- 通过 `css `里的 `width`、`height`设置尺寸。
|
|
||||||
|
|
||||||
#### 方式一 Template
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter>
|
|
||||||
<l-painter-qrcode
|
|
||||||
text="limeui.qcoon.cn"
|
|
||||||
css="width: 200rpx; height: 200rpx"
|
|
||||||
/>
|
|
||||||
</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二 JSON
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
type: 'qrcode',
|
|
||||||
text: 'limeui.qcoon.cn',
|
|
||||||
css: {
|
|
||||||
width: '200rpx',
|
|
||||||
height: '200rpx',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 富文本
|
|
||||||
- 这是一个有限支持的测试能力,只能通过JSON方式,不要抱太大希望!
|
|
||||||
- 首先需要把富文本转成JSON,这需要引入`parser`这个包,如果你不使用是不会进入主包
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter ref="painter"/>
|
|
||||||
```
|
|
||||||
```js
|
|
||||||
import parseHtml from '@/uni_modules/lime-painter/parser'
|
|
||||||
const json = parseHtml(`<p><span>测试测试</span><img src="/static/logo.png"/></p>`)
|
|
||||||
this.$refs.painter.render(json)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生成图片
|
|
||||||
|
|
||||||
- 方式1、通过设置`isCanvasToTempFilePath`自动生成图片并在 `@success` 事件里接收海报临时路径
|
|
||||||
- 方式2、通过调用内部方法生成图片:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter ref="painter">...code</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
this.$refs.painter.canvasToTempFilePathSync({
|
|
||||||
fileType: "jpg",
|
|
||||||
// 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
|
|
||||||
pathType: 'url',
|
|
||||||
quality: 1,
|
|
||||||
success: (res) => {
|
|
||||||
console.log(res.tempFilePath);
|
|
||||||
// 非H5 保存到相册
|
|
||||||
// H5 提示用户长按图另存
|
|
||||||
uni.saveImageToPhotosAlbum({
|
|
||||||
filePath: res.tempFilePath,
|
|
||||||
success: function () {
|
|
||||||
console.log('save success');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 主动调用方式
|
|
||||||
|
|
||||||
- 通过获取组件实例内部的`render`函数 传递`JSON`即可
|
|
||||||
|
|
||||||
```html
|
|
||||||
<l-painter ref="painter" />
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
// 渲染
|
|
||||||
this.$refs.painter.render(jsonSchema);
|
|
||||||
// 生成图片
|
|
||||||
this.$refs.painter.canvasToTempFilePathSync({
|
|
||||||
fileType: "jpg",
|
|
||||||
// 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
|
|
||||||
pathType: 'url',
|
|
||||||
quality: 1,
|
|
||||||
success: (res) => {
|
|
||||||
console.log(res.tempFilePath);
|
|
||||||
// 非H5 保存到相册
|
|
||||||
uni.saveImageToPhotosAlbum({
|
|
||||||
filePath: res.tempFilePath,
|
|
||||||
success: function () {
|
|
||||||
console.log('save success');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### H5跨域
|
|
||||||
- 一般是需要后端或管理OSS资源的大佬处理
|
|
||||||
- 一般OSS的处理方式:
|
|
||||||
|
|
||||||
1、设置来源
|
|
||||||
```cmd
|
|
||||||
*
|
|
||||||
```
|
|
||||||
|
|
||||||
2、允许Methods
|
|
||||||
```html
|
|
||||||
GET
|
|
||||||
```
|
|
||||||
|
|
||||||
3、允许Headers
|
|
||||||
```html
|
|
||||||
access-control-allow-origin:*
|
|
||||||
```
|
|
||||||
|
|
||||||
4、最后如果还是不行,可试下给插件设置`useCORS`
|
|
||||||
```html
|
|
||||||
<l-painter useCORS>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 海报示例
|
|
||||||
|
|
||||||
- 提供一份示例,只把插件当成生成图片的工具,非必要不要在弹窗里使用。
|
|
||||||
- 通过设置`isCanvasToTempFilePath`主动生成图片,再由 `@success` 事件接收海报临时路径
|
|
||||||
- 设置`hidden`隐藏画板。
|
|
||||||
请注意,示例用到了图片,海报的渲染是包括下载图片的时间,也许在某天图片会失效或访问超级慢,请更换为你的图片再查看,另外如果你是小程序请在使用示例时把**不校验合法域名**勾上!!!!!不然不显示还以为是插件的锅,求求了大佬们!
|
|
||||||
#### 方式一 Template
|
|
||||||
|
|
||||||
```html
|
|
||||||
<image :src="path" mode="widthFix"></image>
|
|
||||||
<l-painter
|
|
||||||
isCanvasToTempFilePath
|
|
||||||
@success="path = $event"
|
|
||||||
hidden
|
|
||||||
css="width: 750rpx; padding-bottom: 40rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%)"
|
|
||||||
>
|
|
||||||
<l-painter-image
|
|
||||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
|
||||||
css="margin-left: 40rpx; margin-top: 40rpx; width: 84rpx; height: 84rpx; border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
<l-painter-view
|
|
||||||
css="margin-top: 40rpx; padding-left: 20rpx; display: inline-block"
|
|
||||||
>
|
|
||||||
<l-painter-text
|
|
||||||
text="隔壁老王"
|
|
||||||
css="display: block; padding-bottom: 10rpx; color: #fff; font-size: 32rpx; fontWeight: bold"
|
|
||||||
/>
|
|
||||||
<l-painter-text
|
|
||||||
text="为您挑选了一个好物"
|
|
||||||
css="color: rgba(255,255,255,.7); font-size: 24rpx"
|
|
||||||
/>
|
|
||||||
</l-painter-view>
|
|
||||||
<l-painter-view
|
|
||||||
css="margin-left: 40rpx; margin-top: 30rpx; padding: 32rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; width: 670rpx; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15)"
|
|
||||||
>
|
|
||||||
<l-painter-image
|
|
||||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
|
||||||
css="object-fit: cover; object-position: 50% 50%; width: 606rpx; height: 606rpx; border-radius: 12rpx;"
|
|
||||||
/>
|
|
||||||
<l-painter-view
|
|
||||||
css="margin-top: 32rpx; color: #FF0000; font-weight: bold; font-size: 28rpx; line-height: 1em;"
|
|
||||||
>
|
|
||||||
<l-painter-text text="¥" css="vertical-align: bottom" />
|
|
||||||
<l-painter-text
|
|
||||||
text="39"
|
|
||||||
css="vertical-align: bottom; font-size: 58rpx"
|
|
||||||
/>
|
|
||||||
<l-painter-text text=".39" css="vertical-align: bottom" />
|
|
||||||
<l-painter-text
|
|
||||||
text="¥59.99"
|
|
||||||
css="vertical-align: bottom; padding-left: 10rpx; font-weight: normal; text-decoration: line-through; color: #999999"
|
|
||||||
/>
|
|
||||||
</l-painter-view>
|
|
||||||
<l-painter-view css="margin-top: 32rpx; font-size: 26rpx; color: #8c5400">
|
|
||||||
<l-painter-text text="自营" css="color: #212121; background: #ffb400;" />
|
|
||||||
<l-painter-text
|
|
||||||
text="30天最低价"
|
|
||||||
css="margin-left: 16rpx; background: #fff4d9; text-decoration: line-through;"
|
|
||||||
/>
|
|
||||||
<l-painter-text
|
|
||||||
text="满减优惠"
|
|
||||||
css="margin-left: 16rpx; background: #fff4d9"
|
|
||||||
/>
|
|
||||||
<l-painter-text
|
|
||||||
text="超高好评"
|
|
||||||
css="margin-left: 16rpx; background: #fff4d9"
|
|
||||||
/>
|
|
||||||
</l-painter-view>
|
|
||||||
<l-painter-view css="margin-top: 30rpx">
|
|
||||||
<l-painter-text
|
|
||||||
css="line-clamp: 2; color: #333333; line-height: 1.8em; font-size: 36rpx; width: 478rpx; padding-right:32rpx; box-sizing: border-box"
|
|
||||||
text="360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝"
|
|
||||||
></l-painter-text>
|
|
||||||
<l-painter-qrcode
|
|
||||||
css="width: 128rpx; height: 128rpx;"
|
|
||||||
text="limeui.qcoon.cn"
|
|
||||||
></l-painter-qrcode>
|
|
||||||
</l-painter-view>
|
|
||||||
</l-painter-view>
|
|
||||||
</l-painter>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
path: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二 JSON
|
|
||||||
|
|
||||||
```html
|
|
||||||
<image :src="path" mode="widthFix"></image>
|
|
||||||
<l-painter
|
|
||||||
:board="poster"
|
|
||||||
isCanvasToTempFilePath
|
|
||||||
@success="path = $event"
|
|
||||||
hidden
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
path: '',
|
|
||||||
poster: {
|
|
||||||
css: {
|
|
||||||
width: "750rpx",
|
|
||||||
paddingBottom: "40rpx",
|
|
||||||
background: "linear-gradient(,#000 0%, #ff5000 100%)"
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
|
||||||
type: "image",
|
|
||||||
css: {
|
|
||||||
background: "#fff",
|
|
||||||
objectFit: "cover",
|
|
||||||
marginLeft: "40rpx",
|
|
||||||
marginTop: "40rpx",
|
|
||||||
width: "84rpx",
|
|
||||||
border: "2rpx solid #fff",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
height: "84rpx",
|
|
||||||
borderRadius: "50%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "view",
|
|
||||||
css: {
|
|
||||||
marginTop: "40rpx",
|
|
||||||
paddingLeft: "20rpx",
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
text: "隔壁老王",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
display: "block",
|
|
||||||
paddingBottom: "10rpx",
|
|
||||||
color: "#fff",
|
|
||||||
fontSize: "32rpx",
|
|
||||||
fontWeight: "bold"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "为您挑选了一个好物",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
color: "rgba(255,255,255,.7)",
|
|
||||||
fontSize: "24rpx"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
css: {
|
|
||||||
marginLeft: "40rpx",
|
|
||||||
marginTop: "30rpx",
|
|
||||||
padding: "32rpx",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
background: "#fff",
|
|
||||||
borderRadius: "16rpx",
|
|
||||||
width: "670rpx",
|
|
||||||
boxShadow: "0 20rpx 58rpx rgba(0,0,0,.15)"
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
|
||||||
type: "image",
|
|
||||||
css: {
|
|
||||||
objectFit: "cover",
|
|
||||||
objectPosition: "50% 50%",
|
|
||||||
width: "606rpx",
|
|
||||||
height: "606rpx"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
css: {
|
|
||||||
marginTop: "32rpx",
|
|
||||||
color: "#FF0000",
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontSize: "28rpx",
|
|
||||||
lineHeight: "1em"
|
|
||||||
},
|
|
||||||
views: [{
|
|
||||||
text: "¥",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
verticalAlign: "bottom"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: "39",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
verticalAlign: "bottom",
|
|
||||||
fontSize: "58rpx"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: ".39",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
verticalAlign: "bottom"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: "¥59.99",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
verticalAlign: "bottom",
|
|
||||||
paddingLeft: "10rpx",
|
|
||||||
fontWeight: "normal",
|
|
||||||
textDecoration: "line-through",
|
|
||||||
color: "#999999"
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
type: "view"
|
|
||||||
}, {
|
|
||||||
css: {
|
|
||||||
marginTop: "32rpx",
|
|
||||||
fontSize: "26rpx",
|
|
||||||
color: "#8c5400"
|
|
||||||
},
|
|
||||||
views: [{
|
|
||||||
text: "自营",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
color: "#212121",
|
|
||||||
background: "#ffb400"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: "30天最低价",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
marginLeft: "16rpx",
|
|
||||||
background: "#fff4d9",
|
|
||||||
textDecoration: "line-through"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: "满减优惠",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
marginLeft: "16rpx",
|
|
||||||
background: "#fff4d9"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: "超高好评",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
marginLeft: "16rpx",
|
|
||||||
background: "#fff4d9"
|
|
||||||
},
|
|
||||||
|
|
||||||
}],
|
|
||||||
|
|
||||||
type: "view"
|
|
||||||
}, {
|
|
||||||
css: {
|
|
||||||
marginTop: "30rpx"
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
text: "360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝",
|
|
||||||
type: "text",
|
|
||||||
css: {
|
|
||||||
paddingRight: "32rpx",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
lineClamp: 2,
|
|
||||||
color: "#333333",
|
|
||||||
lineHeight: "1.8em",
|
|
||||||
fontSize: "36rpx",
|
|
||||||
width: "478rpx"
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: "limeui.qcoon.cn",
|
|
||||||
type: "qrcode",
|
|
||||||
css: {
|
|
||||||
width: "128rpx",
|
|
||||||
height: "128rpx",
|
|
||||||
},
|
|
||||||
|
|
||||||
}],
|
|
||||||
type: "view"
|
|
||||||
}],
|
|
||||||
type: "view"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 自定义字体
|
|
||||||
- 需要平台的支持,已知微信小程序支持,其它的没试过,如果可行请告之
|
|
||||||
|
|
||||||
```
|
|
||||||
// 需要在app.vue中下载字体
|
|
||||||
uni.loadFontFace({
|
|
||||||
global:true,
|
|
||||||
scopes: ['native'],
|
|
||||||
family: '自定义字体名称',
|
|
||||||
source: 'url("https://sungd.github.io/Pacifico.ttf")',
|
|
||||||
|
|
||||||
success() {
|
|
||||||
console.log('success')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// 然后就可以在插件的css中写font-family: '自定义字体名称'
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Nvue
|
|
||||||
- 必须为HBX 3.4.11及以上
|
|
||||||
|
|
||||||
|
|
||||||
### 原生小程序
|
|
||||||
|
|
||||||
- 插件里的`painter.js`支持在原生小程序中使用
|
|
||||||
- new Painter 之后在`source`里传入 JSON
|
|
||||||
- 再调用`render`绘制海报
|
|
||||||
- 如需生成图片,请查看微信小程序 cavnas 的[canvasToTempFilePath](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html)
|
|
||||||
|
|
||||||
```html
|
|
||||||
<canvas type="2d" id="painter" style="width: 100%"></canvas>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { Painter } from "./painter";
|
|
||||||
page({
|
|
||||||
data: {
|
|
||||||
poster: {
|
|
||||||
css: {
|
|
||||||
width: "750rpx",
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
type: "view",
|
|
||||||
css: {
|
|
||||||
background: "#d2d4c8",
|
|
||||||
paddingTop: "100rpx",
|
|
||||||
},
|
|
||||||
views: [
|
|
||||||
{
|
|
||||||
type: "view",
|
|
||||||
css: {
|
|
||||||
background: "#5f7470",
|
|
||||||
width: "33.33%",
|
|
||||||
height: "100rpx",
|
|
||||||
display: "inline-block",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "view",
|
|
||||||
css: {
|
|
||||||
background: "#889696",
|
|
||||||
width: "33.33%",
|
|
||||||
height: "100rpx",
|
|
||||||
display: "inline-block",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "view",
|
|
||||||
css: {
|
|
||||||
background: "#b8bdb5",
|
|
||||||
width: "33.33%",
|
|
||||||
height: "100rpx",
|
|
||||||
display: "inline-block",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async onLoad() {
|
|
||||||
const res = await this.getCentext();
|
|
||||||
const painter = new Painter(res);
|
|
||||||
// 返回计算布局后的整个内容尺寸
|
|
||||||
const { width, height } = await painter.source(this.data.poster);
|
|
||||||
// 得到计算后的尺寸后 可给canvas尺寸赋值,达到动态响应效果
|
|
||||||
// 渲染
|
|
||||||
await painter.render();
|
|
||||||
},
|
|
||||||
// 获取canvas 2d
|
|
||||||
// 非2d 需要传一个 createImage 方法用于获取图片信息 即把 getImageInfo 的 success 通过 promise resolve 返回
|
|
||||||
getCentext() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
wx.createSelectorQuery()
|
|
||||||
.select(`#painter`)
|
|
||||||
.node()
|
|
||||||
.exec((res) => {
|
|
||||||
let { node: canvas } = res[0];
|
|
||||||
resolve({
|
|
||||||
canvas,
|
|
||||||
context: canvas.getContext("2d"),
|
|
||||||
width: canvas.width,
|
|
||||||
height: canvas.height,
|
|
||||||
// createImage: getImageInfo()
|
|
||||||
pixelRatio: 2,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 旧版(1.6.x)更新
|
|
||||||
|
|
||||||
- 由于 1.8.x 版放弃了以定位的方式,所以 1.6.x 版更新之后要每个样式都加上`position: absolute`
|
|
||||||
- 旧版的 `image` mode 模式被放弃,使用`object-fit`
|
|
||||||
- 旧版的 `isRenderImage` 改成 `is-canvas-to-temp-file-path`
|
|
||||||
- 旧版的 `maxLines` 改成 `line-clamp`
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### Props
|
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 |
|
|
||||||
| -------------------------- | ------------------------------------------------------------ | ---------------- | ------------ |
|
|
||||||
| board | JSON 方式的海报元素对象集 | <em>object</em> | - |
|
|
||||||
| css | 海报内容最外层的样式,可以理解为`body` | <em>object</em> | 参数请向下看 |
|
|
||||||
| custom-style | canvas 元素的样式 | <em>string</em> | |
|
|
||||||
| hidden | 隐藏画板 | <em>boolean</em> | `false` |
|
|
||||||
| is-canvas-to-temp-file-path | 是否生成图片,在`@success`事件接收图片地址 | <em>boolean</em> | `false` |
|
|
||||||
| after-delay | 生成图片错乱,可延时生成图片 | <em>number</em> | `100` |
|
|
||||||
| type | canvas 类型,对微信头条支付宝小程序可有效,可选值:`2d`,`''` | <em>string</em> | `2d` |
|
|
||||||
| file-type | 生成图片的后缀类型, 可选值:`png`、`jpg` | <em>string</em> | `png` |
|
|
||||||
| path-type | 生成图片路径类型,可选值`url`、`base64` | <em>string</em> | `-` |
|
|
||||||
| pixel-ratio | 生成图片的像素密度,默认为对应手机的像素密度,`nvue`无效 | <em>number</em> | `-` |
|
|
||||||
| hidpi | H5和APP是否使用高清处理 | <em>boolean</em> | `true` |
|
|
||||||
| width | **废弃** 画板的宽度,一般只用于通过内部方法时加上 | <em>number</em> | `` |
|
|
||||||
| height | **废弃** 画板的高度 ,同上 | <em>number</em> | `` |
|
|
||||||
|
|
||||||
### css
|
|
||||||
| 属性名 | 支持的值或类型 | 默认值 |
|
|
||||||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
|
|
||||||
| (min\max)width | 支持`%`、`rpx`、`px` | - |
|
|
||||||
| height | 同上 | - |
|
|
||||||
| color | `string` | - |
|
|
||||||
| position | 定位,可选值:`absolute`、`fixed` | - |
|
|
||||||
| ↳ left、top、right、bottom | 配合`position`才生效,支持`%`、`rpx`、`px` | - |
|
|
||||||
| margin | 可简写或各方向分别写,如:`margin-top`,支持`auto`、`rpx`、`px` | - |
|
|
||||||
| padding | 可简写或各方向分别写,支持`rpx`、`px` | - |
|
|
||||||
| border | 可简写或各个值分开写:`border-width`、`border-style` 、`border-color`,简写请按顺序写 | - |
|
|
||||||
| line-clamp | `number`,超过行数显示省略号 | - |
|
|
||||||
| vertical-align | 文字垂直对齐,可选值:`bottom`、`top`、`middle` | `middle` |
|
|
||||||
| line-height | 文字行高,支持`rpx`、`px`、`em` | `1.4em` |
|
|
||||||
| font-weight | 文字粗细,可选值:`normal`、`bold` | `normal` |
|
|
||||||
| font-size | 文字大小,`string`,支持`rpx`、`px` | `14px` |
|
|
||||||
| text-decoration | 文本修饰,可选值:`underline` 、`line-through`、`overline` | - |
|
|
||||||
| text-stroke | 文字描边,可简写或各个值分开写,如:`text-stroke-color`, `text-stroke-width` | - |
|
|
||||||
| text-align | 文本水平对齐,可选值:`right` 、`center` | `left` |
|
|
||||||
| display | 框类型,可选值:`block`、`inline-block`、`flex`、`none`,当为`none`时是不渲染该段, `flex`功能简陋。 | - |
|
|
||||||
| flex | 配合 display: flex; 属性定义了在分配多余空间,目前只用为数值如: flex: 1 | - |
|
|
||||||
| align-self | 配合 display: flex; 单个项目垂直轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
|
||||||
| justify-content | 配合 display: flex; 水平轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
|
||||||
| align-items | 配合 display: flex; 垂直轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
|
||||||
| border-radius | 圆角边框,支持`%`、`rpx`、`px` | - |
|
|
||||||
| box-sizing | 可选值:`border-box` | - |
|
|
||||||
| box-shadow | 投影 | - |
|
|
||||||
| background(color) | 支持渐变,但必须写百分比!如:`linear-gradient(,#ff971b 0%, #ff5000 100%)`、`radial-gradient(#0ff 15%, #f0f 60%)`,目前 radial-gradient 渐变的圆心为元素中点,半径为最长边,不支持设置 | - |
|
|
||||||
| background-clip | 文字渐变,配合`background`背景渐变,设置`background-clip: text` 达到文字渐变效果 | - |
|
|
||||||
| background-image | view 元素背景:`url(src)`,若只是设置背景图,请不要设置`background-repeat` | - |
|
|
||||||
| background-repeat | 设置是否及如何重复背景纹理,可选值:`repeat`、`repeat-x`、`repeat-y`、`no-repeat` | `repeat` |
|
|
||||||
| [object-fit](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit/) | 图片元素适应容器方式,类似于`mode`,可选值:`cover`、 `contain`、 `fill`、 `none` | - |
|
|
||||||
| [object-position](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position) | 图片的对齐方式,配合`object-fit`使用 | - |
|
|
||||||
|
|
||||||
### 图片填充模式 object-fit
|
|
||||||
|
|
||||||
| 名称 | 含义 |
|
|
||||||
| ------- | ------------------------------------------------------ |
|
|
||||||
| contain | 保持宽高缩放图片,使图片的长边能完全显示出来 |
|
|
||||||
| cover | 保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边 |
|
|
||||||
| fill | 拉伸图片,使图片填满元素 |
|
|
||||||
| none | 保持图片原有尺寸 |
|
|
||||||
|
|
||||||
### 事件 Events
|
|
||||||
|
|
||||||
| 事件名 | 说明 | 返回值 |
|
|
||||||
| -------- | ---------------------------------------------------------------- | ------ |
|
|
||||||
| success | 生成图片成功,若使用`is-canvas-to-temp-filePath` 可以接收图片地址 | path |
|
|
||||||
| fail | 生成图片失败 | error |
|
|
||||||
| done | 绘制成功 | |
|
|
||||||
| progress | 绘制进度 | number |
|
|
||||||
|
|
||||||
### 暴露函数 Expose
|
|
||||||
| 事件名 | 说明 | 返回值 |
|
|
||||||
| -------- | ---------------------------------------------------------------- | ------ |
|
|
||||||
| render(object) | 渲染器,传入JSON 绘制海报 | promise |
|
|
||||||
| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(object) | 把当前画布指定区域的内容导出生成指定大小的图片,并返回文件临时路径。 | |
|
|
||||||
| canvasToTempFilePathSync(object) | 同步接口,同上 | |
|
|
||||||
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
- 1、H5 端使用网络图片需要解决跨域问题。
|
|
||||||
- 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
|
|
||||||
- 3、H5 端生成图片是 base64,有时显示只有一半可以使用原生标签`<IMG/>`
|
|
||||||
- 4、发生保存图片倾斜变形或提示 native buffer exceed size limit 时,使用 pixel-ratio="2"参数,降分辨率。
|
|
||||||
- 5、h5 保存图片不需要调接口,提示用户长按图片保存。
|
|
||||||
- 6、画板不能隐藏,包括`v-if`,`v-show`、`display:none`、`opacity:0`,另外也不要把画板放在弹窗里。如果需要隐藏画板请设置 `custom-style="position: fixed; left: 200%"`
|
|
||||||
- 7、微信小程序真机调试请使用 **真机调试2.0**,不支持1.0。
|
|
||||||
- 8、微信小程序打开调试时可以生但并闭无法生成时,这种情况一般是没有在公众号配置download域名
|
|
||||||
- 9、HBX 3.4.5之前的版本不支持vue3
|
|
||||||
- 10、在微信开发工具上 canvas 层级最高无法zindex,并不影响真机
|
|
||||||
- 11、请不要导入非uni_modules插件
|
|
||||||
- 12、关于QQ小程序 报 Propertyor method"toJSON"is not defined 请把基础库调到 1.50.3
|
|
||||||
- 13、支付宝小程序 IDE 不支持 生成图片 请以真机调试结果为准
|
|
||||||
- 14、返回值为字符串 `data:,` 大概是尺寸超过限制,设置 pixel-ratio="2"
|
|
||||||
- 华为手机 APP 上无法生成图片,请使用 HBX2.9.11++(已过时,忽略这条)
|
|
||||||
- IOS APP 请勿使用 HBX2.9.3.20201014 的版本!这个版本无法生成图片。(已过时,忽略这条)
|
|
||||||
- 苹果微信 7.0.20 存在闪退和图片无法 onload 为微信 bug(已过时,忽略这条)
|
|
||||||
- 微信小程序 IOS 旧接口 如父级设置圆角,子级也设会导致子级的失效,为旧接口BUG。
|
|
||||||
- 微信小程序 安卓 旧接口 如使用图片必须加背景色,为旧接口BUG。
|
|
||||||
- 微信小程序 安卓端 [图片可能在首次可以加载成功,再次加载会不触发任何事件](https://developers.weixin.qq.com/community/develop/doc/000ee2b8dacf4009337f51f4556800?highLine=canvas%25202d%2520createImage),临时解决方法是给图片加个时间戳
|
|
||||||
## 打赏
|
|
||||||
|
|
||||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
@@ -12,7 +12,9 @@
|
|||||||
],
|
],
|
||||||
"repository": "",
|
"repository": "",
|
||||||
"engines": {
|
"engines": {
|
||||||
"HBuilderX": "^3.1.0"
|
"HBuilderX": "^3.1.0",
|
||||||
|
"uni-app": "^3.1.0",
|
||||||
|
"uni-app-x": "^3.1.0"
|
||||||
},
|
},
|
||||||
"dcloudext": {
|
"dcloudext": {
|
||||||
"type": "component-vue",
|
"type": "component-vue",
|
||||||
@@ -32,52 +34,65 @@
|
|||||||
"data": "无",
|
"data": "无",
|
||||||
"permissions": "无"
|
"permissions": "无"
|
||||||
},
|
},
|
||||||
"npmurl": ""
|
"npmurl": "",
|
||||||
|
"darkmode": "-",
|
||||||
|
"i18n": "-",
|
||||||
|
"widescreen": "-"
|
||||||
},
|
},
|
||||||
"uni_modules": {
|
"uni_modules": {
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"encrypt": [],
|
"encrypt": [],
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"tcb": "y",
|
"tcb": "√",
|
||||||
"aliyun": "y"
|
"aliyun": "√"
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"Vue": {
|
"uni-app": {
|
||||||
"vue2": "y",
|
"vue": {
|
||||||
"vue3": "u"
|
"vue2": "-",
|
||||||
|
"vue3": "-"
|
||||||
},
|
},
|
||||||
"App": {
|
"web": {
|
||||||
"app-vue": "u",
|
"safari": "-",
|
||||||
"app-nvue": "u"
|
"chrome": "-"
|
||||||
},
|
},
|
||||||
"H5-mobile": {
|
"app": {
|
||||||
"Safari": "y",
|
"vue": "-",
|
||||||
"Android Browser": "y",
|
"nvue": "-",
|
||||||
"微信浏览器(Android)": "y",
|
"android": "-",
|
||||||
"QQ浏览器(Android)": "y"
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
},
|
},
|
||||||
"H5-pc": {
|
"mp": {
|
||||||
"Chrome": "u",
|
"weixin": "-",
|
||||||
"IE": "u",
|
"alipay": "-",
|
||||||
"Edge": "u",
|
"toutiao": "-",
|
||||||
"Firefox": "u",
|
"baidu": "-",
|
||||||
"Safari": "u"
|
"kuaishou": "-",
|
||||||
|
"jd": "-",
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "-",
|
||||||
|
"lark": "-"
|
||||||
},
|
},
|
||||||
"小程序": {
|
"quickapp": {
|
||||||
"微信": "y",
|
"huawei": "-",
|
||||||
"阿里": "u",
|
"union": "-"
|
||||||
"百度": "u",
|
}
|
||||||
"字节跳动": "u",
|
|
||||||
"QQ": "u",
|
|
||||||
"钉钉": "u",
|
|
||||||
"快手": "u",
|
|
||||||
"飞书": "u",
|
|
||||||
"京东": "u"
|
|
||||||
},
|
},
|
||||||
"快应用": {
|
"uni-app-x": {
|
||||||
"华为": "u",
|
"web": {
|
||||||
"联盟": "u"
|
"safari": "-",
|
||||||
|
"chrome": "-"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": "-",
|
||||||
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "-"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
## 2.1.9(2022-07-13)
|
|
||||||
[修复] app端选择文件后初始化设置的文件列表被清空问题
|
|
||||||
## 2.1.8(2022-07-13)
|
|
||||||
[新增] ref方法初始化文件列表,用于已提交后再次编辑时需带入已上传文件:setFiles(files),可传入数组或Map对象,传入格式请与组件选择返回格式保持一致,且name为必须属性。
|
|
||||||
## 2.1.7(2022-07-12)
|
|
||||||
修复ios端偶现创建webview初始化参数未生效的问题
|
|
||||||
## 2.1.6(2022-07-11)
|
|
||||||
[修复]:修复上个版本更新导致nvue窗口组件不能选择文件的问题;
|
|
||||||
[新增]:
|
|
||||||
1.应群友建议(填写禁止格式太多)格式限制formats由原来填写禁止选择的格式改为填写允许被选择的格式;
|
|
||||||
2.应群友建议(增加上传结束回调事件),上传结束回调事件@uploadEnd
|
|
||||||
3.如能帮到你请留下你的免费好评,组件使用过程中有问题可以加QQ群交流,至于Map对象怎么使用这类前端基础问题请自行百度
|
|
||||||
## 2.1.5(2022-07-01)
|
|
||||||
app端组件销毁时添加自动销毁webview功能,避免v-if销毁组件的情况控件还能被点击的问题
|
|
||||||
## 2.1.4(2022-07-01)
|
|
||||||
修复小程序端回显问题
|
|
||||||
## 2.1.3(2022-06-30)
|
|
||||||
回调事件返回参数新增path字段(文件临时地址),用于回显
|
|
||||||
## 2.1.2(2022-06-16)
|
|
||||||
修复APP端Tabbar窗口无法选择文件的问题
|
|
||||||
## 2.1.1(2022-06-16)
|
|
||||||
优化:
|
|
||||||
1.组件优化为允许在v-if中使用;
|
|
||||||
2.允许option直接在data赋值,不再强制在onRead中初始化;
|
|
||||||
## 2.1.0(2022-06-13)
|
|
||||||
h5 pc端更改为单次可多选
|
|
||||||
## 2.0.9(2022-06-10)
|
|
||||||
更新演示内容,部分同学不知道怎么获取服务端返回的数据
|
|
||||||
## 2.0.8(2022-06-09)
|
|
||||||
优化动态更新上传参数函数,具体查看下方说明:动态更新参数演示
|
|
||||||
## 2.0.7(2022-06-07)
|
|
||||||
新增wxFileType属性,用于小程序端选择附件时可选文件类型
|
|
||||||
## 2.0.6(2022-06-07)
|
|
||||||
修复小程序端真机选择文件提示失败的问题
|
|
||||||
## 2.0.5(2022-06-02)
|
|
||||||
优化小程序端调用hide()后未阻止触发文件选择问题
|
|
||||||
## 2.0.4(2022-06-01)
|
|
||||||
优化APP端选择器初始定位
|
|
||||||
## 2.0.3(2022-05-31)
|
|
||||||
修复nvue窗口选择文件报错问题
|
|
||||||
## 2.0.2(2022-05-20)
|
|
||||||
修复ios端opiton设置过早未传入webview导致不自动上传问题
|
|
||||||
## 2.0.1(2022-05-19)
|
|
||||||
修复APP端子窗口点击选择文件不响应问题
|
|
||||||
## 2.0.0(2022-05-18)
|
|
||||||
此次组件更新至2.0版本,与1.0版本使用上略有差异,已使用1.0的同学请自行斟酌是否需要升级!
|
|
||||||
部分差异:
|
|
||||||
一、 2.0新增异步触发上传功能;
|
|
||||||
二、2.0新增文件批量上传功能;
|
|
||||||
三、2.0优化option,剔除属性,只保留上传接口所需字段,且允许异步更改option的值;
|
|
||||||
四、组件增加size(文件大小限制)、count(文件个数限制)、formats(文件后缀限制)、accept(文件类型限制)、instantly(是否立即自动上传)、debug(日志打印)等属性;
|
|
||||||
五、回调事件取消input事件、callback事件,新增change事件和progress事件;
|
|
||||||
六、ref事件新增upload事件、clear事件;
|
|
||||||
七、优化组件代码,show和hide函数改为显示隐藏,不再重复开关webview;
|
|
||||||
|
|
||||||
## 1.2.3(2022-03-22)
|
|
||||||
修复Demo里传入待完善功能[手动上传属性manual=true]导致不自动上传的问题,手动提交上传待下个版本更新
|
|
||||||
## 1.2.2(2022-02-21)
|
|
||||||
修复上版本APP优化导致H5和小程序端不自动初始化的问题,此次更新仅修复此问题。异步提交功能下个版本更新~
|
|
||||||
## 1.2.1(2022-01-25)
|
|
||||||
QQ1群已满,已开放2群:469580165
|
|
||||||
## 1.2.0(2021-12-09)
|
|
||||||
优化APP端页面中DOM重排后每次需要重新定位的问题
|
|
||||||
## 1.1.1(2021-12-09)
|
|
||||||
优化,与上版本使用方式有改变,请检查后确认是否需要更新,create更名为show, close更名为hide,取消初始化时手动create, 传参方式改为props=>option
|
|
||||||
## 1.1.0(2021-12-09)
|
|
||||||
新增refresh方法,用于DOM发生重排时重新定位控件(APP端)
|
|
||||||
## 1.0.9(2021-07-15)
|
|
||||||
修复上传进度未同步渲染,直接返回100%的BUG
|
|
||||||
## 1.0.8(2021-07-12)
|
|
||||||
修复H5端传入height和width未生效的bug
|
|
||||||
## 1.0.7(2021-07-07)
|
|
||||||
修复h5和小程序端上传完成callback未返回fileName字段问题
|
|
||||||
## 1.0.6(2021-07-07)
|
|
||||||
修复h5端提示信息debug
|
|
||||||
## 1.0.5(2021-06-29)
|
|
||||||
感谢小伙伴找出bug,上传成功回调success未置为true,已修复
|
|
||||||
## 1.0.4(2021-06-28)
|
|
||||||
新增兼容APP,H5,小程序手动关闭控件,关闭后不再弹出文件选择框,需要重新create再次开启
|
|
||||||
## 1.0.3(2021-06-28)
|
|
||||||
close增加条件编译,除app端外不需要close
|
|
||||||
## 1.0.2(2021-06-28)
|
|
||||||
1.修复页面滚动位置后再create控件导致控件位置不正确的问题;
|
|
||||||
2.修复nvue无法create控件;
|
|
||||||
3.示例项目新增nvue使用案例;
|
|
||||||
## 1.0.1(2021-06-28)
|
|
||||||
因为有的朋友不清楚app端切换tab时应该怎么处理webview,现重新上传一版示例项目,需要做tab切换的朋友可以导入示例项目查看
|
|
||||||
## 1.0.0(2021-06-25)
|
|
||||||
此插件为l-file插件中上传功能改版,更新内容为:
|
|
||||||
1. 按钮内嵌入页面,不再强制固定底部,可跟随页面滚动
|
|
||||||
2.无需再单独弹框点击上传,减去中间层
|
|
||||||
3.通过slot自定义按钮样式
|
|
||||||
@@ -1,392 +0,0 @@
|
|||||||
export class LsjFile {
|
|
||||||
constructor(data) {
|
|
||||||
this.dom = null;
|
|
||||||
// files.type = waiting(等待上传)|| loading(上传中)|| success(成功) || fail(失败)
|
|
||||||
this.files = new Map();
|
|
||||||
this.debug = data.debug || false;
|
|
||||||
this.id = data.id;
|
|
||||||
this.width = data.width;
|
|
||||||
this.height = data.height;
|
|
||||||
this.option = data.option;
|
|
||||||
this.instantly = data.instantly;
|
|
||||||
this.prohibited = data.prohibited;
|
|
||||||
this.onchange = data.onchange;
|
|
||||||
this.onprogress = data.onprogress;
|
|
||||||
this.uploadHandle = this._uploadHandle;
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
this.uploadHandle = this._uploadHandleWX;
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建File节点
|
|
||||||
* @param {string}path webview地址
|
|
||||||
*/
|
|
||||||
create(path) {
|
|
||||||
if (!this.dom) {
|
|
||||||
// #ifdef H5
|
|
||||||
let dom = document.createElement('input');
|
|
||||||
dom.type = 'file'
|
|
||||||
dom.value = ''
|
|
||||||
dom.style.height = this.height
|
|
||||||
dom.style.width = this.width
|
|
||||||
dom.style.position = 'absolute'
|
|
||||||
dom.style.top = 0
|
|
||||||
dom.style.left = 0
|
|
||||||
dom.style.right = 0
|
|
||||||
dom.style.bottom = 0
|
|
||||||
dom.style.opacity = 0
|
|
||||||
dom.style.zIndex = 999
|
|
||||||
dom.accept = this.prohibited.accept;
|
|
||||||
if (this.prohibited.count > 1) {
|
|
||||||
dom.multiple = 'multiple';
|
|
||||||
}
|
|
||||||
dom.onchange = event => {
|
|
||||||
for (let file of event.target.files) {
|
|
||||||
this.addFile(file);
|
|
||||||
}
|
|
||||||
this.dom.value = '';
|
|
||||||
};
|
|
||||||
this.dom = dom;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
let styles = {
|
|
||||||
top: '-500px',
|
|
||||||
left: 0,
|
|
||||||
width: '1px',
|
|
||||||
height: '1px',
|
|
||||||
background: 'transparent'
|
|
||||||
};
|
|
||||||
let extras = {
|
|
||||||
debug: this.debug,
|
|
||||||
instantly: this.instantly,
|
|
||||||
prohibited: this.prohibited,
|
|
||||||
}
|
|
||||||
this.dom = plus.webview.create(path, this.id, styles,extras);
|
|
||||||
this.setData(this.option);
|
|
||||||
this._overrideUrlLoading();
|
|
||||||
// #endif
|
|
||||||
return this.dom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copyObject(obj) {
|
|
||||||
if (typeof obj !== "undefined") {
|
|
||||||
return JSON.parse(JSON.stringify(obj));
|
|
||||||
} else {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动根据字符串路径设置对象中的值 支持.和[]
|
|
||||||
* @param {Object} dataObj 数据源
|
|
||||||
* @param {String} name 支持a.b 和 a[b]
|
|
||||||
* @param {String} value 值
|
|
||||||
* setValue(dataObj, name, value);
|
|
||||||
*/
|
|
||||||
setValue(dataObj, name, value) {
|
|
||||||
// 通过正则表达式 查找路径数据
|
|
||||||
let dataValue;
|
|
||||||
if (typeof value === "object") {
|
|
||||||
dataValue = this.copyObject(value);
|
|
||||||
} else {
|
|
||||||
dataValue = value;
|
|
||||||
}
|
|
||||||
let regExp = new RegExp("([\\w$]+)|\\[(:\\d)\\]", "g");
|
|
||||||
const patten = name.match(regExp);
|
|
||||||
// 遍历路径 逐级查找 最后一级用于直接赋值
|
|
||||||
for (let i = 0; i < patten.length - 1; i++) {
|
|
||||||
let keyName = patten[i];
|
|
||||||
if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {};
|
|
||||||
dataObj = dataObj[keyName];
|
|
||||||
}
|
|
||||||
// 最后一级
|
|
||||||
dataObj[patten[patten.length - 1]] = dataValue;
|
|
||||||
this.debug&&console.log('参数更新后',JSON.stringify(this.option));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置上传参数
|
|
||||||
* @param {object|string}name 上传参数,支持a.b 和 a[b]
|
|
||||||
*/
|
|
||||||
setData() {
|
|
||||||
let [name,value = ''] = arguments;
|
|
||||||
if (typeof name === 'object') {
|
|
||||||
Object.assign(this.option,name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.setValue(this.option,name,value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.debug&&console.log(JSON.stringify(this.option));
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`);
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传
|
|
||||||
* @param {string}name 文件名称
|
|
||||||
*/
|
|
||||||
async upload(name='') {
|
|
||||||
if (!this.option.url) {
|
|
||||||
throw Error('未设置上传地址');
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifndef APP-PLUS
|
|
||||||
if (name && this.files.has(name)) {
|
|
||||||
await this.uploadHandle(this.files.get(name));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (let item of this.files.values()) {
|
|
||||||
if (item.type === 'waiting' || item.type === 'fail') {
|
|
||||||
await this.uploadHandle(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.dom&&this.dom.evalJS(`vm.upload('${name}')`);
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择文件change
|
|
||||||
addFile(file) {
|
|
||||||
|
|
||||||
let name = file.name;
|
|
||||||
this.debug&&console.log('文件名称',name,'大小',file.size);
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
// 限制文件格式
|
|
||||||
let path = '';
|
|
||||||
let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
|
|
||||||
let formats = this.prohibited.formats.toLowerCase();
|
|
||||||
if (formats&&!formats.includes(suffix)) {
|
|
||||||
this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 限制文件大小
|
|
||||||
if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
|
|
||||||
this.toast(`附件大小请勿超过${this.prohibited.size}M`)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
path = URL.createObjectURL(file);
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
path = file.path;
|
|
||||||
// #endif
|
|
||||||
this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
|
|
||||||
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
this.onchange(this.files);
|
|
||||||
this.instantly&&this.upload();
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
return true;
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除文件
|
|
||||||
* @param {string}name 不传name默认移除所有文件,传入name移除指定name的文件
|
|
||||||
*/
|
|
||||||
clear(name='') {
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.dom&&this.dom.evalJS(`vm.clear('${name}')`);
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
this.files.clear();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.files.delete(name);
|
|
||||||
}
|
|
||||||
return this.onchange(this.files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提示框
|
|
||||||
* @param {string}msg 轻提示内容
|
|
||||||
*/
|
|
||||||
toast(msg) {
|
|
||||||
uni.showToast({
|
|
||||||
title: msg,
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 微信小程序选择文件
|
|
||||||
* @param {number}count 可选择文件数量
|
|
||||||
*/
|
|
||||||
chooseMessageFile(type,count) {
|
|
||||||
wx.chooseMessageFile({
|
|
||||||
count: count,
|
|
||||||
type: type,
|
|
||||||
success: ({ tempFiles }) => {
|
|
||||||
for (let file of tempFiles) {
|
|
||||||
let next = this.addFile(file);
|
|
||||||
if (!next) {return}
|
|
||||||
}
|
|
||||||
this.onchange(this.files);
|
|
||||||
this.instantly&&this.upload();
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
this.toast(`打开失败`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_overrideUrlLoading() {
|
|
||||||
this.dom.overrideUrlLoading({ mode: 'reject' }, e => {
|
|
||||||
let {retype,item,files,end} = this._getRequest(
|
|
||||||
e.url
|
|
||||||
);
|
|
||||||
let _this = this;
|
|
||||||
switch (retype) {
|
|
||||||
case 'updateOption':
|
|
||||||
this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`);
|
|
||||||
break
|
|
||||||
case 'change':
|
|
||||||
try {
|
|
||||||
_this.files = new Map([..._this.files,...JSON.parse(unescape(files))]);
|
|
||||||
} catch (e) {
|
|
||||||
return console.error('出错了,请检查代码')
|
|
||||||
}
|
|
||||||
_this.onchange(_this.files);
|
|
||||||
break
|
|
||||||
case 'progress':
|
|
||||||
try {
|
|
||||||
item = JSON.parse(unescape(item));
|
|
||||||
} catch (e) {
|
|
||||||
return console.error('出错了,请检查代码')
|
|
||||||
}
|
|
||||||
_this._changeFilesItem(item,end);
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_getRequest(url) {
|
|
||||||
let theRequest = new Object()
|
|
||||||
let index = url.indexOf('?')
|
|
||||||
if (index != -1) {
|
|
||||||
let str = url.substring(index + 1)
|
|
||||||
let strs = str.split('&')
|
|
||||||
for (let i = 0; i < strs.length; i++) {
|
|
||||||
theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return theRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
_changeFilesItem(item,end=false) {
|
|
||||||
this.debug&&console.log('onprogress',JSON.stringify(item));
|
|
||||||
this.onprogress(item,end);
|
|
||||||
this.files.set(item.name,item);
|
|
||||||
}
|
|
||||||
|
|
||||||
_uploadHandle(item) {
|
|
||||||
item.type = 'loading';
|
|
||||||
delete item.responseText;
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
this.debug&&console.log('option',JSON.stringify(this.option));
|
|
||||||
let {url,name,method='POST',header,formData} = this.option;
|
|
||||||
let form = new FormData();
|
|
||||||
for (let keys in formData) {
|
|
||||||
form.append(keys, formData[keys])
|
|
||||||
}
|
|
||||||
form.append(name, item.file);
|
|
||||||
let xmlRequest = new XMLHttpRequest();
|
|
||||||
xmlRequest.open(method, url, true);
|
|
||||||
for (let keys in header) {
|
|
||||||
xmlRequest.setRequestHeader(keys, header[keys])
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlRequest.upload.addEventListener(
|
|
||||||
'progress',
|
|
||||||
event => {
|
|
||||||
if (event.lengthComputable) {
|
|
||||||
let progress = Math.ceil((event.loaded * 100) / event.total)
|
|
||||||
if (progress <= 100) {
|
|
||||||
item.progress = progress;
|
|
||||||
this._changeFilesItem(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xmlRequest.ontimeout = () => {
|
|
||||||
console.error('请求超时')
|
|
||||||
item.type = 'fail';
|
|
||||||
this._changeFilesItem(item,true);
|
|
||||||
return resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlRequest.onreadystatechange = ev => {
|
|
||||||
if (xmlRequest.readyState == 4) {
|
|
||||||
if (xmlRequest.status == 200) {
|
|
||||||
this.debug&&console.log('上传完成:' + xmlRequest.responseText)
|
|
||||||
item['responseText'] = xmlRequest.responseText;
|
|
||||||
item.type = 'success';
|
|
||||||
this._changeFilesItem(item,true);
|
|
||||||
return resolve(true);
|
|
||||||
} else if (xmlRequest.status == 0) {
|
|
||||||
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求')
|
|
||||||
}
|
|
||||||
console.error('--ERROR--:status = ' + xmlRequest.status)
|
|
||||||
item.type = 'fail';
|
|
||||||
this._changeFilesItem(item,true);
|
|
||||||
return resolve(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xmlRequest.send(form)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_uploadHandleWX(item) {
|
|
||||||
item.type = 'loading';
|
|
||||||
delete item.responseText;
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
this.debug&&console.log('option',JSON.stringify(this.option));
|
|
||||||
let form = {filePath: item.file.path,...this.option };
|
|
||||||
form['fail'] = ({ errMsg = '' }) => {
|
|
||||||
console.error('--ERROR--:' + errMsg)
|
|
||||||
item.type = 'fail';
|
|
||||||
this._changeFilesItem(item,true);
|
|
||||||
return resolve(false);
|
|
||||||
}
|
|
||||||
form['success'] = res => {
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
this.debug&&console.log('上传完成,微信端返回不一定是字符串,根据接口返回格式判断是否需要JSON.parse:' + res.data)
|
|
||||||
item['responseText'] = res.data;
|
|
||||||
item.type = 'success';
|
|
||||||
this._changeFilesItem(item,true);
|
|
||||||
return resolve(true);
|
|
||||||
}
|
|
||||||
item.type = 'fail';
|
|
||||||
this._changeFilesItem(item,true);
|
|
||||||
return resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let xmlRequest = uni.uploadFile(form);
|
|
||||||
xmlRequest.onProgressUpdate(({ progress = 0 }) => {
|
|
||||||
if (progress <= 100) {
|
|
||||||
item.progress = progress;
|
|
||||||
this._changeFilesItem(item);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="lsj-file" :style="[getStyles]">
|
|
||||||
<view ref="lsj" class="hFile" :style="[getStyles]" @click="onClick">
|
|
||||||
<slot><view class="defview" :style="[getStyles]">附件上传</view></slot>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 查看文档:https://ext.dcloud.net.cn/plugin?id=5459
|
|
||||||
import {LsjFile} from './LsjFile.js'
|
|
||||||
export default {
|
|
||||||
name: 'Lsj-upload',
|
|
||||||
props: {
|
|
||||||
// 打印日志
|
|
||||||
debug: {type: Boolean,default: false},
|
|
||||||
// 自动上传
|
|
||||||
instantly: {type: Boolean,default: false},
|
|
||||||
// 上传接口参数设置
|
|
||||||
option: {type: Object,default: ()=>{}},
|
|
||||||
// 文件大小上限
|
|
||||||
size: { type: Number, default: 10 },
|
|
||||||
// 文件选择个数上限,超出后不触发点击
|
|
||||||
count: { type: Number, default: 9 },
|
|
||||||
// 允许上传的文件格式(多个以逗号隔开)
|
|
||||||
formats: { type: String, default:''},
|
|
||||||
// input file选择限制
|
|
||||||
accept: {type: String,default: ''},
|
|
||||||
// 微信选择文件类型
|
|
||||||
//all=从所有文件选择,
|
|
||||||
//video=只能选择视频文件,
|
|
||||||
//image=只能选择图片文件,
|
|
||||||
//file=可以选择除了图片和视频之外的其它的文件
|
|
||||||
wxFileType: { type: String, default: 'all' },
|
|
||||||
// webviewID需唯一,不同窗口也不要同Id
|
|
||||||
childId: { type: String, default: 'lsjUpload' },
|
|
||||||
// 文件选择触发面宽度
|
|
||||||
width: { type: String, default: '100%' },
|
|
||||||
// 文件选择触发面高度
|
|
||||||
height: { type: String, default: '80rpx' },
|
|
||||||
|
|
||||||
// top,left,bottom,right仅position=absolute时才需要传入
|
|
||||||
top: { type: [String, Number], default: '' },
|
|
||||||
left: { type: [String, Number], default: '' },
|
|
||||||
bottom: { type: [String, Number], default: '' },
|
|
||||||
right: { type: [String, Number], default: '' },
|
|
||||||
// nvue不支持跟随窗口滚动
|
|
||||||
position: {
|
|
||||||
type: String,
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
default: 'absolute',
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
default: 'static',
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
option(v) {
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.lsjFile&&this.show();
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
if (this.isShow) {
|
|
||||||
this.lsjFile&&this.show();
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
getStyles() {
|
|
||||||
let styles = {
|
|
||||||
width: this.width,
|
|
||||||
height: this.height
|
|
||||||
}
|
|
||||||
if (this.position == 'absolute') {
|
|
||||||
styles['top'] = this.top
|
|
||||||
styles['bottom'] = this.bottom
|
|
||||||
styles['left'] = this.left
|
|
||||||
styles['right'] = this.right
|
|
||||||
styles['position'] = 'fixed'
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this._size = 0;
|
|
||||||
this.lsjFile = new LsjFile({
|
|
||||||
debug: this.debug,
|
|
||||||
id: this.childId,
|
|
||||||
width: this.width,
|
|
||||||
height: this.height,
|
|
||||||
option: this.option,
|
|
||||||
instantly: this.instantly,
|
|
||||||
// 限制条件
|
|
||||||
prohibited: {
|
|
||||||
// 大小
|
|
||||||
size: this.size,
|
|
||||||
// 允许上传的格式
|
|
||||||
formats: this.formats,
|
|
||||||
// 限制选择的格式
|
|
||||||
accept: this.accept,
|
|
||||||
count: this.count
|
|
||||||
},
|
|
||||||
onchange: this.onchange,
|
|
||||||
onprogress: this.onprogress,
|
|
||||||
});
|
|
||||||
this.create();
|
|
||||||
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.lsjFile.dom.close();
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setFiles(array) {
|
|
||||||
if (array instanceof Map) {
|
|
||||||
for (let [key, item] of array) {
|
|
||||||
item['progress'] = 100;
|
|
||||||
item['type'] = 'success';
|
|
||||||
this.lsjFile.files.set(key,item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Array.isArray(array)) {
|
|
||||||
array.forEach(item=>{
|
|
||||||
if (item.name) {
|
|
||||||
item['progress'] = 100;
|
|
||||||
item['type'] = 'success';
|
|
||||||
this.lsjFile.files.set(item.name,item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.onchange(this.lsjFile.files);
|
|
||||||
},
|
|
||||||
setData() {
|
|
||||||
this.lsjFile&&this.lsjFile.setData(...arguments);
|
|
||||||
},
|
|
||||||
getDomStyles(callback) {
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
let view = uni
|
|
||||||
.createSelectorQuery()
|
|
||||||
.in(this)
|
|
||||||
.select('.lsj-file')
|
|
||||||
view.fields(
|
|
||||||
{
|
|
||||||
size: true,
|
|
||||||
rect: true
|
|
||||||
},
|
|
||||||
({ height, width, top, left, right, bottom }) => {
|
|
||||||
uni.createSelectorQuery()
|
|
||||||
.selectViewport()
|
|
||||||
.scrollOffset(({ scrollTop }) => {
|
|
||||||
return callback({
|
|
||||||
top: parseInt(top) + parseInt(scrollTop) + 'px',
|
|
||||||
left: parseInt(left) + 'px',
|
|
||||||
width: parseInt(width) + 'px',
|
|
||||||
height: parseInt(height) + 'px'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.exec()
|
|
||||||
}
|
|
||||||
).exec()
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
const dom = weex.requireModule('dom')
|
|
||||||
dom.getComponentRect(this.$refs.lsj, ({ size: { height, width, top, left, right, bottom } }) => {
|
|
||||||
return callback({
|
|
||||||
top: parseInt(top) + 'px',
|
|
||||||
left: parseInt(left) + 'px',
|
|
||||||
width: parseInt(width) + 'px',
|
|
||||||
height: parseInt(height) + 'px',
|
|
||||||
right: parseInt(right) + 'px',
|
|
||||||
bottom: parseInt(bottom) + 'px'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
show() {
|
|
||||||
this.isShow = true;
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.lsjFile&&this.getDomStyles(styles => {
|
|
||||||
this.lsjFile.dom.setStyle(styles)
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5
|
|
||||||
this.lsjFile.dom.style.display = 'inline'
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.isShow = false;
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
this.lsjFile&&this.lsjFile.dom.setStyle({
|
|
||||||
top: '-500px',
|
|
||||||
left:'0px',
|
|
||||||
width: '1px',
|
|
||||||
height: '1px',
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5
|
|
||||||
this.lsjFile.dom.style.display = 'none'
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 手动提交上传
|
|
||||||
* @param {string}name 文件名称,不传则上传所有type等于waiting和fail的文件
|
|
||||||
*/
|
|
||||||
upload(name) {
|
|
||||||
this.lsjFile&&this.lsjFile.upload(name);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @returns {Map} 已选择的文件Map集
|
|
||||||
*/
|
|
||||||
onchange(files) {
|
|
||||||
this.$emit('change',files);
|
|
||||||
this._size = files.size;
|
|
||||||
return files.size >= this.count ? this.hide() : this.show();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @returns {object} 当前上传中的对象
|
|
||||||
*/
|
|
||||||
onprogress(item,end=false) {
|
|
||||||
this.$emit('progress',item);
|
|
||||||
if (end) {
|
|
||||||
setTimeout(()=>{
|
|
||||||
this.$emit('uploadEnd',item);
|
|
||||||
},0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 移除组件内缓存的某条数据
|
|
||||||
* @param {string}name 文件名称,不指定默认清除所有文件
|
|
||||||
*/
|
|
||||||
clear(name) {
|
|
||||||
this.lsjFile.clear(name);
|
|
||||||
},
|
|
||||||
// 创建选择器
|
|
||||||
create() {
|
|
||||||
// 若iOS端服务端处理不了跨域就将hybrid目录内的html放到服务端去,并将此处path改成服务器上的地址
|
|
||||||
let path = '/uni_modules/lsj-upload/hybrid/html/uploadFile.html';
|
|
||||||
let dom = this.lsjFile.create(path);
|
|
||||||
// #ifdef H5
|
|
||||||
this.$refs.lsj.$el.appendChild(dom);
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS
|
|
||||||
this.show();
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
dom.setStyle({position: this.position});
|
|
||||||
dom.loadURL(path);
|
|
||||||
setTimeout(()=>{
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
plus.webview.currentWebview().append(dom);
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
this.$root.$scope.$getAppWebview().append(dom);
|
|
||||||
// #endif
|
|
||||||
this.show();
|
|
||||||
},300)
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
// 点击选择附件
|
|
||||||
onClick() {
|
|
||||||
if (this._size >= this.count) {
|
|
||||||
this.toast(`只允许上传${this.count}个文件`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
if (!this.isShow) {return;}
|
|
||||||
let count = this.count - this._size;
|
|
||||||
this.lsjFile.chooseMessageFile(this.wxFileType,count);
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
toast(msg) {
|
|
||||||
uni.showToast({
|
|
||||||
title: msg,
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.lsj-file {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.defview {
|
|
||||||
background-color: #007aff;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
.hFile {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,179 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-cn">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title class="title">[文件管理器]</title>
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
|
||||||
<style type="text/css">
|
|
||||||
.content {background: transparent;}
|
|
||||||
.btn {position: relative;top: 0;left: 0;bottom: 0;right: 0;}
|
|
||||||
.btn .file {position: fixed;z-index: 93;left: 0;right: 0;top: 0;bottom: 0;width: 100%;opacity: 0;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="content" class="content">
|
|
||||||
<div class="btn">
|
|
||||||
<input @change="onChange" :accept="accept" ref="file" class="file" type="file" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="js/vue.min.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
let _this;
|
|
||||||
var vm = new Vue({
|
|
||||||
el: '#content',
|
|
||||||
data: {
|
|
||||||
accept: '',
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
console.log('加载webview');
|
|
||||||
_this = this;
|
|
||||||
this.files = new Map();
|
|
||||||
document.addEventListener('plusready', (e)=>{
|
|
||||||
let {debug,instantly,prohibited} = plus.webview.currentWebview();
|
|
||||||
this.debug = debug;
|
|
||||||
this.instantly = instantly;
|
|
||||||
this.prohibited = prohibited;
|
|
||||||
this.accept = prohibited.accept;
|
|
||||||
location.href = 'callback?retype=updateOption';
|
|
||||||
}, false);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toast(msg) {
|
|
||||||
plus.nativeUI.toast(msg);
|
|
||||||
},
|
|
||||||
clear(name) {
|
|
||||||
if (!name) {
|
|
||||||
this.files.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.files.delete(name);
|
|
||||||
},
|
|
||||||
setData(option='{}') {
|
|
||||||
this.debug&&console.log('更新参数:'+option);
|
|
||||||
try{
|
|
||||||
_this.option = JSON.parse(option);
|
|
||||||
}catch(e){
|
|
||||||
console.error('参数设置错误')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async upload(name=''){
|
|
||||||
if (name && this.files.has(name)) {
|
|
||||||
await this.createUpload(this.files.get(name));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (let item of this.files.values()) {
|
|
||||||
if (item.type === 'waiting' || item.type === 'fail') {
|
|
||||||
await this.createUpload(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChange(e) {
|
|
||||||
let fileDom = this.$refs.file;
|
|
||||||
let file = fileDom.files[0];
|
|
||||||
let name = file.name;
|
|
||||||
fileDom.value = '';
|
|
||||||
this.debug&&console.log('文件名称',name,'大小',file.size);
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
// 限制文件格式
|
|
||||||
let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
|
|
||||||
let formats = this.prohibited.formats.toLowerCase();
|
|
||||||
if (formats&&!formats.includes(suffix)) {
|
|
||||||
this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 限制文件大小
|
|
||||||
if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
|
|
||||||
this.toast(`附件大小请勿超过${this.prohibited.size}M`)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let path = URL.createObjectURL(file);
|
|
||||||
this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
|
|
||||||
this.callChange();
|
|
||||||
this.instantly&&this.upload();
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @returns {Map} 已选择的文件Map集
|
|
||||||
*/
|
|
||||||
callChange() {
|
|
||||||
location.href = 'callback?retype=change&files=' + escape(JSON.stringify([...this.files]));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @returns {object} 正在处理的当前对象
|
|
||||||
*/
|
|
||||||
changeFilesItem(item,end='') {
|
|
||||||
this.files.set(item.name,item);
|
|
||||||
location.href = 'callback?retype=progress&end='+ end +'&item=' + escape(JSON.stringify(item));
|
|
||||||
},
|
|
||||||
createUpload(item) {
|
|
||||||
this.debug&&console.log('准备上传,option=:'+JSON.stringify(this.option));
|
|
||||||
item.type = 'loading';
|
|
||||||
delete item.responseText;
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let {url,name,method='POST',header={},formData={}} = this.option;
|
|
||||||
let form = new FormData();
|
|
||||||
for (let keys in formData) {
|
|
||||||
form.append(keys, formData[keys])
|
|
||||||
}
|
|
||||||
form.append(name, item.file);
|
|
||||||
let xmlRequest = new XMLHttpRequest();
|
|
||||||
xmlRequest.open(method, url, true);
|
|
||||||
for (let keys in header) {
|
|
||||||
xmlRequest.setRequestHeader(keys, header[keys])
|
|
||||||
}
|
|
||||||
xmlRequest.upload.addEventListener(
|
|
||||||
'progress',
|
|
||||||
event => {
|
|
||||||
if (event.lengthComputable) {
|
|
||||||
let progress = Math.ceil((event.loaded * 100) / event.total)
|
|
||||||
if (progress <= 100) {
|
|
||||||
item.progress = progress;
|
|
||||||
this.changeFilesItem(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xmlRequest.ontimeout = () => {
|
|
||||||
console.error('请求超时')
|
|
||||||
item.type = 'fail';
|
|
||||||
this.changeFilesItem(item,true);
|
|
||||||
return resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlRequest.onreadystatechange = ev => {
|
|
||||||
if (xmlRequest.readyState == 4) {
|
|
||||||
if (xmlRequest.status == 200) {
|
|
||||||
this.debug && console.log('上传完成:' + xmlRequest.responseText)
|
|
||||||
item['responseText'] = xmlRequest.responseText;
|
|
||||||
item.type = 'success';
|
|
||||||
this.changeFilesItem(item,true);
|
|
||||||
return resolve(true);
|
|
||||||
} else if (xmlRequest.status == 0) {
|
|
||||||
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求')
|
|
||||||
}
|
|
||||||
console.error('--ERROR--:status = ' + xmlRequest.status)
|
|
||||||
item.type = 'fail';
|
|
||||||
this.changeFilesItem(item,true);
|
|
||||||
return resolve(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xmlRequest.send(form)
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "lsj-upload",
|
|
||||||
"displayName": "全文件上传选择非原生2.0版",
|
|
||||||
"version": "2.1.9",
|
|
||||||
"description": "文件选择上传-支持APP-H5网页-微信小程序",
|
|
||||||
"keywords": [
|
|
||||||
"附件",
|
|
||||||
"file",
|
|
||||||
"upload",
|
|
||||||
"上传",
|
|
||||||
"文件管理器"
|
|
||||||
],
|
|
||||||
"repository": "",
|
|
||||||
"engines": {
|
|
||||||
"HBuilderX": "^3.2.0"
|
|
||||||
},
|
|
||||||
"dcloudext": {
|
|
||||||
"category": [
|
|
||||||
"前端组件",
|
|
||||||
"通用组件"
|
|
||||||
],
|
|
||||||
"sale": {
|
|
||||||
"regular": {
|
|
||||||
"price": "0.00"
|
|
||||||
},
|
|
||||||
"sourcecode": {
|
|
||||||
"price": "0.00"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"contact": {
|
|
||||||
"qq": ""
|
|
||||||
},
|
|
||||||
"declaration": {
|
|
||||||
"ads": "无",
|
|
||||||
"data": "无",
|
|
||||||
"permissions": "无"
|
|
||||||
},
|
|
||||||
"npmurl": ""
|
|
||||||
},
|
|
||||||
"uni_modules": {
|
|
||||||
"platforms": {
|
|
||||||
"cloud": {
|
|
||||||
"tcb": "y",
|
|
||||||
"aliyun": "y"
|
|
||||||
},
|
|
||||||
"client": {
|
|
||||||
"App": {
|
|
||||||
"app-vue": "y",
|
|
||||||
"app-nvue": "y"
|
|
||||||
},
|
|
||||||
"H5-mobile": {
|
|
||||||
"Safari": "y",
|
|
||||||
"Android Browser": "y",
|
|
||||||
"微信浏览器(Android)": "y",
|
|
||||||
"QQ浏览器(Android)": "y"
|
|
||||||
},
|
|
||||||
"H5-pc": {
|
|
||||||
"Chrome": "y",
|
|
||||||
"IE": "y",
|
|
||||||
"Edge": "y",
|
|
||||||
"Firefox": "y",
|
|
||||||
"Safari": "y"
|
|
||||||
},
|
|
||||||
"小程序": {
|
|
||||||
"微信": "y",
|
|
||||||
"阿里": "u",
|
|
||||||
"百度": "u",
|
|
||||||
"字节跳动": "u",
|
|
||||||
"QQ": "u"
|
|
||||||
},
|
|
||||||
"快应用": {
|
|
||||||
"华为": "y",
|
|
||||||
"联盟": "y"
|
|
||||||
},
|
|
||||||
"Vue": {
|
|
||||||
"vue2": "y",
|
|
||||||
"vue3": "y"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
# lsj-upload
|
|
||||||
|
|
||||||
### 插件地址:https://ext.dcloud.net.cn/plugin?id=5459
|
|
||||||
|
|
||||||
### 不清楚使用方式可点击右侧导入示例项目运行完整示例
|
|
||||||
### 此次更新2.0与1.0使用方式略有差异,已使用1.0的同学自行斟酌是否更新到2.0版本!!!
|
|
||||||
|
|
||||||
使用插件有任何问题欢迎加入QQ讨论群:
|
|
||||||
- 群1:701468256(已满)
|
|
||||||
- 群2:469580165
|
|
||||||
|
|
||||||
若能帮到你请高抬贵手点亮5颗星~
|
|
||||||
------
|
|
||||||
## 重要提示
|
|
||||||
### 组件是窗口级滚动,不要在scroll-view内使用!!
|
|
||||||
### 组件是窗口级滚动,不要在scroll-view内使用!!
|
|
||||||
### 组件是窗口级滚动,不要在scroll-view内使用!!
|
|
||||||
|
|
||||||
### 控件的height高度应与slot自定义内容高度保持一致
|
|
||||||
### nvue窗口只能使用固定模式position=absolute
|
|
||||||
### show() 当DOM重排后在this.$nextTick内调用show(),控件定位会更加准确
|
|
||||||
### hide() APP端webview层级比view高,如不希望触发点击时,应调用hide隐藏控件,反之调用show
|
|
||||||
### 若iOS端跨域服务端同学实在配置不好,可把hybrid下html目录放到服务器去,同源则不存在跨域问题。
|
|
||||||
### 小程序端因hybrid不能使用本地HTML,所以插件提供的是从微信消息列表拉取文件并选择,请知悉。
|
|
||||||
------
|
|
||||||
|
|
||||||
## 使用说明
|
|
||||||
| 属性 | 是否必填 | 值类型 | 默认值 | 说明 |
|
|
||||||
| --------- | -------- | -----: | --: | :------------:|
|
|
||||||
| width | 否 | String |100% | 容器宽度 |
|
|
||||||
| height | 是 | String |80rpx | 容器高度 |
|
|
||||||
| debug | 否 | Boolean |false | 打印调试日志 |
|
|
||||||
| option | 是 | Object |- | [文件上传接口相关参数](#p1)|
|
|
||||||
| instantly | 否 | Boolean |false | true=自动上传 |
|
|
||||||
| count | 否 | Number |10 | 附件选择上限(个)|
|
|
||||||
| size | 否 | Number |10 | 附件大小上限(M)|
|
|
||||||
| wxFileType | 否 | String |all | 微信小程序文件选择器格式限制(all=从所有文件选择,video=只能选择视频文件,image=只能选择图片文件,file=可以选择除了图片和视频之外的其它的文件)|
|
|
||||||
| accept | 否 | String |- | 文件选择器input file格式限制(部分机型不兼容,建议使用formats)|
|
|
||||||
| formats | 否 | String |- | 限制允许上传的格式,空串=不限制,默认为空,多个格式以逗号隔开,例如png,jpg,pdf|
|
|
||||||
| childId | 否 | String |lsjUpload| 控件的id(仅APP有效,应用内每个控件命名一个唯一Id,不同窗口也不要同名Id)|
|
|
||||||
| position | 否 | String |static | 控件的定位模式(static=控件随页面滚动;absolute=控件在页面中绝对定位,不随窗口内容滚动)|
|
|
||||||
| top,left,right,bottom | 否 | [Number,String] |0 | 设置控件绝对位置,position=absolute时有效|
|
|
||||||
| @change | 否 | Function |Map | 选择文件触发,返回所有已选择文件Map集合|
|
|
||||||
| @progress | 否 | Function |Object | 上传过程中发生状态变化的文件对象,需通过set更新至Map集合|
|
|
||||||
| @uploadEnd| 否 | Function |Object | 上传结束回调,返回参数与progress一致|
|
|
||||||
|
|
||||||
## <a id="p1">option说明</a>
|
|
||||||
|参数 | 是否必填 | 说明|
|
|
||||||
|---- | ---- | :--: |
|
|
||||||
|url | 是 | 上传接口地址|
|
|
||||||
|name| 否 |上传接口文件key,默认为file|
|
|
||||||
|header| 否 |上传接口请求头|
|
|
||||||
|formData| 否 |上传接口额外参数|
|
|
||||||
|
|
||||||
## ref调用
|
|
||||||
|作用 | 方法名| 传入参数| 说明|
|
|
||||||
|---- | --------- | -------- | :--: |
|
|
||||||
|显示控件| show|-| 控件显示状态下可触发点击|
|
|
||||||
|隐藏控件| hide|-| 控件隐藏状态下不触发点击|
|
|
||||||
|动态设置文件列表| setFiles|[Array,Map] files| 传入格式请与组件选择返回格式保持一致,且name为必须属性,可查看下方演示|
|
|
||||||
|动态更新参数| setData|[String] name,[any] value| name支持a.b 和 a[b],可查看下方演示|
|
|
||||||
|移除选择的文件| clear|[String] name| 不传参数清空所有文件,传入文件name时删除该name的文件|
|
|
||||||
|手动上传| upload|[String] name| 不传参数默认依次上传所有type=waiting的文件,传入文件name时不关心type是否为waiting,单独上传指定name的文件|
|
|
||||||
|
|
||||||
## progress返回对象字段说明
|
|
||||||
|字段 | 说明|
|
|
||||||
|---- | :--: |
|
|
||||||
|file | 文件对象|
|
|
||||||
|name |文件名称|
|
|
||||||
|size |文件大小|
|
|
||||||
|type |文件上传状态:waiting(等待上传)、loading(上传中)、success(成功) 、fail(失败)|
|
|
||||||
|responseText|上传成功后服务端返回数据(仅type为success时存在)|
|
|
||||||
|
|
||||||
## 以下演示为vue窗口使用方式,nvue使用区别是必须传入控件绝对位置如top,bottom,left,right,且position只能为absolute,如不清楚可点击右侧导入示例项目有详细演示代码。
|
|
||||||
|
|
||||||
### vue:
|
|
||||||
``` javascript
|
|
||||||
<lsj-upload
|
|
||||||
ref="lsjUpload"
|
|
||||||
childId="upload1"
|
|
||||||
:width="width"
|
|
||||||
:height="height"
|
|
||||||
:option="option"
|
|
||||||
:size="size"
|
|
||||||
:formats="formats"
|
|
||||||
:debug="debug"
|
|
||||||
:instantly="instantly"
|
|
||||||
@progress="onprogress"
|
|
||||||
@change="onChange">
|
|
||||||
<view class="btn" :style="{width: width,height: height}">选择附件</view>
|
|
||||||
</lsj-upload>
|
|
||||||
|
|
||||||
|
|
||||||
<view class="padding">
|
|
||||||
|
|
||||||
<view>已选择文件列表:</view>
|
|
||||||
|
|
||||||
<!-- #ifndef MP-WEIXIN -->
|
|
||||||
<view v-for="(item,index) in files.values()" :key="index">
|
|
||||||
<image style="width: 100rpx;height: 100rpx;" :src="item.path" mode="widthFix"></image>
|
|
||||||
<text>{{item.path}}</text>
|
|
||||||
<text>{{item.name}}</text>
|
|
||||||
<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
|
|
||||||
<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
|
|
||||||
<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
|
|
||||||
<!-- <text style="margin-left: 10rpx;" v-if="item.responseText">服务端返回演示:{{item.responseText.code}}</text> -->
|
|
||||||
<text @click="clear(item.name)" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">删除</text>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<view v-for="(item,index) in wxFiles" :key="index">
|
|
||||||
<text>{{item.name}}</text>
|
|
||||||
<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
|
|
||||||
<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
|
|
||||||
<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
|
|
||||||
<view>
|
|
||||||
<button>删除</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
</view>
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
* 函数说明
|
|
||||||
|
|
||||||
|
|
||||||
``` javascript
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// 上传接口参数
|
|
||||||
option: {
|
|
||||||
// 上传服务器地址,此地址需要替换为你的接口地址
|
|
||||||
url: 'http://hlapi.j56.com/dropbox/document/upload',
|
|
||||||
// 上传附件的key
|
|
||||||
name: 'file',
|
|
||||||
// 根据你接口需求自定义请求头
|
|
||||||
header: {
|
|
||||||
'Authorization': 'bearer eyJhbGciOiJSUzI1NiIsI',
|
|
||||||
'uid': '27682',
|
|
||||||
'client': 'app',
|
|
||||||
'accountid': 'DP',
|
|
||||||
},
|
|
||||||
// 根据你接口需求自定义body参数
|
|
||||||
formData: {
|
|
||||||
// 'orderId': 1000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 选择文件后是否立即自动上传,true=选择后立即上传
|
|
||||||
instantly: false,
|
|
||||||
// 必传宽高且宽高应与slot宽高保持一致
|
|
||||||
width: '180rpx',
|
|
||||||
height: '180rpx',
|
|
||||||
// 限制允许选择的格式,空串=不限制,默认为空
|
|
||||||
formats: 'png,jpg,mp4',
|
|
||||||
// 文件上传大小限制
|
|
||||||
size: 10,
|
|
||||||
// 文件回显列表
|
|
||||||
files: new Map(),
|
|
||||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
|
||||||
wxFiles: [],
|
|
||||||
// 是否打印日志
|
|
||||||
debug: true,
|
|
||||||
|
|
||||||
|
|
||||||
// 演示用
|
|
||||||
tabIndex: 0,
|
|
||||||
list:[],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReady() {
|
|
||||||
setTimeout(()=>{
|
|
||||||
console.log('----演示动态更新参数-----');
|
|
||||||
this.$refs.lsjUpload.setData('formData.orderId','动态设置的参数');
|
|
||||||
|
|
||||||
console.log('以下注释内容为-动态更新参数更多演示,放开后可查看演示效果');
|
|
||||||
// 修改option对象的name属性
|
|
||||||
// this.$refs.lsjUpload.setData('name','myFile');
|
|
||||||
|
|
||||||
// 修改option对象的formData内的属性
|
|
||||||
// this.$refs.lsjUpload.setData('formData.appid','1111');
|
|
||||||
|
|
||||||
// 替换option对象的formData
|
|
||||||
// this.$refs.lsjUpload.setData('formData',{appid:'222'});
|
|
||||||
|
|
||||||
// option对象的formData新增属性
|
|
||||||
// this.$refs.lsjUpload.setData('formData.newkey','新插入到formData的属性');
|
|
||||||
|
|
||||||
// ---------演示初始化值,用于已提交后再次编辑时需带入已上传文件-------
|
|
||||||
// 方式1=传入数组
|
|
||||||
let files1 = [{
|
|
||||||
name: '1.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '2.png',
|
|
||||||
}];
|
|
||||||
|
|
||||||
// 方式2=传入Map对象
|
|
||||||
let files2 = new Map();
|
|
||||||
files2.set('1.png',{name: '1.png'})
|
|
||||||
|
|
||||||
// 设置初始files列表
|
|
||||||
this.$refs.lsjUpload.setFiles(files1);
|
|
||||||
|
|
||||||
},2000)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 某文件上传结束回调(成功失败都回调)
|
|
||||||
onuploadEnd(item) {
|
|
||||||
console.log(`${item.name}已上传结束,上传状态=${item.type}`);
|
|
||||||
|
|
||||||
// 更新当前状态变化的文件
|
|
||||||
this.files.set(item.name,item);
|
|
||||||
|
|
||||||
// 演示上传完成后取服务端数据
|
|
||||||
if (item['responseText']) {
|
|
||||||
console.log('演示服务器返回的字符串JSON转对象');
|
|
||||||
this.files.get(item.name).responseText = JSON.parse(item.responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
this.wxFiles = [...this.files.values()];
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 强制更新视图
|
|
||||||
this.$forceUpdate();
|
|
||||||
|
|
||||||
|
|
||||||
// ---可删除--演示判断是否所有文件均已上传成功
|
|
||||||
let isAll = [...this.files.values()].find(item=>item.type!=='success');
|
|
||||||
if (!isAll) {
|
|
||||||
console.log('已全部上传完毕');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(isAll.name+'待上传');
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
// 上传进度回调
|
|
||||||
onprogress(item) {
|
|
||||||
// 更新当前状态变化的文件
|
|
||||||
this.files.set(item.name,item);
|
|
||||||
|
|
||||||
console.log('打印对象',JSON.stringify(this.files.get(item.name)));
|
|
||||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
this.wxFiles = [...this.files.values()];
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 强制更新视图
|
|
||||||
this.$forceUpdate();
|
|
||||||
|
|
||||||
},
|
|
||||||
// 文件选择回调
|
|
||||||
onChange(files) {
|
|
||||||
// 更新选择的文件
|
|
||||||
this.files = files;
|
|
||||||
// 强制更新视图
|
|
||||||
this.$forceUpdate();
|
|
||||||
|
|
||||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
this.wxFiles = [...this.files.values()];
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
// 手动上传
|
|
||||||
upload() {
|
|
||||||
// name=指定文件名,不指定则上传所有type等于waiting和fail的文件
|
|
||||||
this.$refs.lsjUpload.upload();
|
|
||||||
},
|
|
||||||
// 移除某个文件
|
|
||||||
clear(name) {
|
|
||||||
// name=指定文件名,不传name默认移除所有文件
|
|
||||||
this.$refs.lsjUpload.clear(name);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 以下为演示
|
|
||||||
*/
|
|
||||||
// DOM重排演示,重排后组件内部updated默认会触发show方法,若特殊情况未能触发updated也可以手动调用一次show()
|
|
||||||
// 什么是DOM重排?自行百度去~
|
|
||||||
add() {
|
|
||||||
this.list.push('DOM重排测试');
|
|
||||||
},
|
|
||||||
// 切换视图演示,APP端因为是webview,层级比view高,
|
|
||||||
// 此时若不希望点击触发选择文件,需要手动调用hide()
|
|
||||||
// 手动调用hide后,需要调用show()才能恢复触发面
|
|
||||||
onTab(tabIndex) {
|
|
||||||
this.tabIndex = tabIndex;
|
|
||||||
|
|
||||||
if (tabIndex == 0 ) {
|
|
||||||
this.$nextTick(()=>{
|
|
||||||
this.$refs.lsjUpload.show();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.$refs.lsjUpload.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 打开nvue窗口
|
|
||||||
open() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/nvue-demo/nvue-demo'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## 温馨提示
|
|
||||||
|
|
||||||
* 文件上传
|
|
||||||
0. 如说明表达还不够清楚,不清楚怎么使用可导入完整示例项目运行体验和查看
|
|
||||||
1. APP端请优先联调Android,上传成功后再运行iOS端,如iOS返回status=0则需要后端开启允许跨域;
|
|
||||||
2. header的Content-Type类型需要与服务端要求一致,否则收不到附件(服务端若没有明文规定则可不写,使用默认匹配)
|
|
||||||
3. 服务端不清楚怎么配置跨域可加群咨询,具体百度~
|
|
||||||
4. 欢迎加入QQ讨论群:701468256(已满)
|
|
||||||
5. 欢迎加入QQ讨论群:469580165
|
|
||||||
6. 欢迎加入QQ讨论群:469580165
|
|
||||||
7. 若能帮到你还请点亮5颗小星星以作鼓励哈~
|
|
||||||
8. 若能帮到你还请点亮5颗小星星以作鼓励哈~
|
|
||||||
9. 若能帮到你还请点亮5颗小星星以作鼓励哈~
|
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
## 2.5.0-20230101(2023-01-01)
|
||||||
|
- 秋云图表组件 修改条件编译顺序,确保uniapp的cli方式的项目依赖不完整时可以正常显示
|
||||||
|
- 秋云图表组件 恢复props属性directory的使用,以修复vue3项目中,开启echarts后,echarts目录识别错误的bug
|
||||||
|
- uCharts.js 修复区域图、混合图只有一个数据时图表显示不正确的bug
|
||||||
|
- uCharts.js 修复折线图、区域图中时间轴类别图表tooltip指示点显示不正确的bug
|
||||||
|
- uCharts.js 修复x轴使用labelCount时,并且boundaryGap = 'justify' 并且关闭Y轴显示的时候,最后一个坐标值不显示的bug
|
||||||
|
- uCharts.js 修复折线图只有一组数据时 ios16 渲染颜色不正确的bug
|
||||||
|
- uCharts.js 修复玫瑰图半径显示不正确的bug
|
||||||
|
- uCharts.js 柱状图、山峰图增加正负图功能,y轴网格如果需要显示0轴则由 min max 及 splitNumber 确定,后续版本优化自动显示0轴
|
||||||
|
- uCharts.js 柱状图column增加 opts.extra.column.labelPosition,数据标签位置,有效值为 outside外部, insideTop内顶部, center内中间, bottom内底部
|
||||||
|
- uCharts.js 雷达图radar增加 opts.extra.radar.labelShow,否显示各项标识文案是,默认true
|
||||||
|
- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.boxPadding,提示窗边框填充距离,默认3px
|
||||||
|
- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.fontSize,提示窗字体大小配置,默认13px
|
||||||
|
- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.lineHeight,提示窗文字行高,默认20px
|
||||||
|
- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.legendShow,是否显示左侧图例,默认true
|
||||||
|
- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.legendShape,图例形状,图例标识样式,有效值为 auto自动跟随图例, diamond◆, circle●, triangle▲, square■, rect▬, line-
|
||||||
|
- uCharts.js 标记线markLine增加 opts.extra.markLine.labelFontSize,字体大小配置,默认13px
|
||||||
|
- uCharts.js 标记线markLine增加 opts.extra.markLine.labelPadding,标签边框内填充距离,默认6px
|
||||||
|
- uCharts.js 折线图line增加 opts.extra.line.linearType,渐变色类型,可选值 none关闭渐变色,custom 自定义渐变色。使用自定义渐变色时请赋值serie.linearColor作为颜色值
|
||||||
|
- uCharts.js 折线图line增加 serie.linearColor,渐变色数组,格式为2维数组[起始位置,颜色值],例如[[0,'#0EE2F8'],[0.3,'#2BDCA8'],[0.6,'#1890FF'],[1,'#9A60B4']]
|
||||||
|
- uCharts.js 折线图line增加 opts.extra.line.onShadow,是否开启折线阴影,开启后请赋值serie.setShadow阴影设置
|
||||||
|
- uCharts.js 折线图line增加 serie.setShadow,阴影配置,格式为4位数组:[offsetX,offsetY,blur,color]
|
||||||
|
- uCharts.js 折线图line增加 opts.extra.line.animation,动画效果方向,可选值为vertical 垂直动画效果,horizontal 水平动画效果
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.lineHeight,X轴字体行高,默认20px
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.marginTop,X轴文字距离轴线的距离,默认0px
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.title,当前X轴标题
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.titleFontSize,标题字体大小,默认13px
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.titleOffsetY,标题纵向偏移距离,负数为向上偏移,正数向下偏移
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.titleOffsetX,标题横向偏移距离,负数为向左偏移,正数向右偏移
|
||||||
|
- uCharts.js X轴xAxis增加 opts.xAxis.titleFontColor,标题字体颜色,默认#666666
|
||||||
|
|
||||||
|
## 报错TypeError: Cannot read properties of undefined (reading 'length')
|
||||||
|
- 如果是uni-modules版本组件,请先登录HBuilderX账号;
|
||||||
|
- 在HBuilderX中的manifest.json,点击重新获取uniapp的appid,或者删除appid重新粘贴,重新运行;
|
||||||
|
- 如果是cli项目请使用码云上的非uniCloud版本组件;
|
||||||
|
- 或者添加uniCloud的依赖;
|
||||||
|
- 或者使用原生uCharts;
|
||||||
|
## 2.4.5-20221130(2022-11-30)
|
||||||
|
- uCharts.js 优化tooltip当文字很多变为左侧显示时,如果画布仍显显示不下,提示框错位置变为以左侧0位置起画
|
||||||
|
- uCharts.js 折线图修复特殊情况下只有单点数据,并改变线宽后点变为圆形的bug
|
||||||
|
- uCharts.js 修复Y轴disabled启用后无效并报错的bug
|
||||||
|
- uCharts.js 修复仪表盘起始结束角度特殊情况下显示不正确的bug
|
||||||
|
- uCharts.js 雷达图新增参数 opts.extra.radar.radius , 自定义雷达图半径
|
||||||
|
- uCharts.js 折线图、区域图增加tooltip指示点,opts.extra.line.activeType/opts.extra.area.activeType,可选值"none"不启用激活指示点,"hollow"空心点模式,"solid"实心点模式
|
||||||
|
## 2.4.4-20221102(2022-11-02)
|
||||||
|
- 秋云图表组件 修复使用echarts时reload、reshow无法调用重新渲染的bug,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/40)
|
||||||
|
- 秋云图表组件 修复使用echarts时,初始化时宽高不正确的bug,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/42)
|
||||||
|
- 秋云图表组件 修复uniapp的h5使用history模式时,无法加载echarts的bug
|
||||||
|
- 秋云图表组件 小程序端@complete、@scrollLeft、@scrollRight、@getTouchStart、@getTouchMove、@getTouchEnd事件增加opts参数传出,方便一些特殊需求的交互获取数据。
|
||||||
|
|
||||||
|
- uCharts.js 修复calTooltipYAxisData方法内formatter格式化方法未与y轴方法同步的问题,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/43)
|
||||||
|
- uCharts.js 地图新增参数opts.series[i].fillOpacity,以透明度方式来设置颜色过度效果,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/38)
|
||||||
|
- uCharts.js 地图新增参数opts.extra.map.active,是否启用点击激活变色
|
||||||
|
- uCharts.js 地图新增参数opts.extra.map.activeTextColor,是否启用点击激活变色
|
||||||
|
- uCharts.js 地图新增渲染完成事件renderComplete
|
||||||
|
- uCharts.js 漏斗图修复当部分数据相同时tooltip提示窗点击错误的bug
|
||||||
|
- uCharts.js 漏斗图新增参数series.data[i].centerText 居中标签文案
|
||||||
|
- uCharts.js 漏斗图新增参数series.data[i].centerTextSize 居中标签文案字体大小,默认opts.fontSize
|
||||||
|
- uCharts.js 漏斗图新增参数series.data[i].centerTextColor 居中标签文案字体颜色,默认#FFFFFF
|
||||||
|
- uCharts.js 漏斗图新增参数opts.extra.funnel.minSize 最小值的最小宽度,默认0
|
||||||
|
- uCharts.js 进度条新增参数opts.extra.arcbar.direction,动画方向,可选值为cw顺时针、ccw逆时针
|
||||||
|
- uCharts.js 混合图新增参数opts.extra.mix.line.width,折线的宽度,默认2
|
||||||
|
- uCharts.js 修复tooltip开启horizentalLine水平横线标注时,图表显示错位的bug
|
||||||
|
- uCharts.js 优化tooltip当文字很多变为左侧显示时,如果画布仍显显示不下,提示框错位置变为以左侧0位置起画
|
||||||
|
- uCharts.js 修复开启滚动条后X轴文字超出绘图区域后的隐藏逻辑
|
||||||
|
- uCharts.js 柱状图、条状图修复堆叠模式不能通过{value,color}赋值单个柱子颜色的问题
|
||||||
|
- uCharts.js 气泡图修复不识别series.textSize和series.textColor的bug
|
||||||
|
|
||||||
|
## 报错TypeError: Cannot read properties of undefined (reading 'length')
|
||||||
|
1. 如果是uni-modules版本组件,请先登录HBuilderX账号;
|
||||||
|
2. 在HBuilderX中的manifest.json,点击重新获取uniapp的appid,或者删除appid重新粘贴,重新运行;
|
||||||
|
3. 如果是cli项目请使用码云上的非uniCloud版本组件;
|
||||||
|
4. 或者添加uniCloud的依赖;
|
||||||
|
5. 或者使用原生uCharts;
|
||||||
|
## 2.4.3-20220505(2022-05-05)
|
||||||
|
- 秋云图表组件 修复开启canvas2d后将series赋值为空数组显示加载图标时,再次赋值后画布闪动的bug
|
||||||
|
- 秋云图表组件 修复升级hbx最新版后ECharts的highlight方法报错的bug
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.gridEval,数据点位网格抽希,默认1
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.axisLabel, 是否显示刻度点值,默认false
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.axisLabelTofix,刻度点值小数位数,默认0
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.labelPointShow,是否显示末端刻度圆点,默认false
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.labelPointRadius,刻度圆点的半径,默认3
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.labelPointColor,刻度圆点的颜色,默认#cccccc
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.linearType,渐变色类型,可选值"none"关闭渐变,"custom"开启渐变
|
||||||
|
- uCharts.js 雷达图新增参数opts.extra.radar.customColor,自定义渐变颜色,数组类型对应series的数组长度以匹配不同series颜色的不同配色方案,例如["#FA7D8D", "#EB88E2"]
|
||||||
|
- uCharts.js 雷达图优化支持series.textColor、series.textSize属性
|
||||||
|
- uCharts.js 柱状图中温度计式图标,优化支持全圆角类型,修复边框有缝隙的bug,详见官网【演示】中的温度计图表
|
||||||
|
- uCharts.js 柱状图新增参数opts.extra.column.activeWidth,当前点击柱状图的背景宽度,默认一个单元格单位
|
||||||
|
- uCharts.js 混合图增加opts.extra.mix.area.gradient 区域图是否开启渐变色
|
||||||
|
- uCharts.js 混合图增加opts.extra.mix.area.opacity 区域图透明度,默认0.2
|
||||||
|
- uCharts.js 饼图、圆环图、玫瑰图、漏斗图,增加opts.series[0].data[i].labelText,自定义标签文字,避免formatter格式化的繁琐,详见官网【演示】中的饼图
|
||||||
|
- uCharts.js 饼图、圆环图、玫瑰图、漏斗图,增加opts.series[0].data[i].labelShow,自定义是否显示某一个指示标签,避免因饼图类别太多导致标签重复或者居多导致图形变形的问题,详见官网【演示】中的饼图
|
||||||
|
- uCharts.js 增加opts.series[i].legendText/opts.series[0].data[i].legendText(与series.name同级)自定义图例显示文字的方法
|
||||||
|
- uCharts.js 优化X轴、Y轴formatter格式化方法增加形参,统一为fromatter:function(value,index,opts){}
|
||||||
|
- uCharts.js 修复横屏模式下无法使用双指缩放方法的bug
|
||||||
|
- uCharts.js 修复当只有一条数据或者多条数据值相等的时候Y轴自动计算的最大值错误的bug
|
||||||
|
- 【官网模板】增加外部自定义图例与图表交互的例子,[点击跳转](https://www.ucharts.cn/v2/#/layout/info?id=2)
|
||||||
|
|
||||||
|
## 注意:非unimodules 版本如因更新 hbx 至 3.4.7 导致报错如下,请到码云更新非 unimodules 版本组件,[点击跳转](https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6)
|
||||||
|
> Error in callback for immediate watcher "uchartsOpts": "SyntaxError: Unexpected token u in JSON at position 0"
|
||||||
|
## 2.4.2-20220421(2022-04-21)
|
||||||
|
- 秋云图表组件 修复HBX升级3.4.6.20220420版本后echarts报错的问题
|
||||||
|
## 2.4.2-20220420(2022-04-20)
|
||||||
|
## 重要!此版本uCharts新增了很多功能,修复了诸多已知问题
|
||||||
|
- 秋云图表组件 新增onzoom开启双指缩放功能(仅uCharts),前提需要直角坐标系类图表类型,并且ontouch为true、opts.enableScroll为true,详见实例项目K线图
|
||||||
|
- 秋云图表组件 新增optsWatch是否监听opts变化,关闭optsWatch后,动态修改opts不会触发图表重绘
|
||||||
|
- 秋云图表组件 修复开启canvas2d功能后,动态更新数据后画布闪动的bug
|
||||||
|
- 秋云图表组件 去除directory属性,改为自动获取echarts.min.js路径(升级不受影响)
|
||||||
|
- 秋云图表组件 增加getImage()方法及@getImage事件,通过ref调用getImage()方法获,触发@getImage事件获取当前画布的base64图片文件流。
|
||||||
|
- 秋云图表组件 支付宝、字节跳动、飞书、快手小程序支持开启canvas2d同层渲染设置。
|
||||||
|
- 秋云图表组件 新增加【非uniCloud】版本组件,避免有些不需要uniCloud的使用组件发布至小程序需要提交隐私声明问题,请到码云[【非uniCloud版本】](https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6),或npm[【非uniCloud版本】](https://www.npmjs.com/package/@qiun/uni-ucharts)下载使用。
|
||||||
|
- uCharts.js 新增dobuleZoom双指缩放功能
|
||||||
|
- uCharts.js 新增山峰图type="mount",数据格式为饼图类格式,不需要传入categories,具体详见新版官网在线演示
|
||||||
|
- uCharts.js 修复折线图当数据中存在null时tooltip报错的bug
|
||||||
|
- uCharts.js 修复饼图类当画布比较小时自动计算的半径是负数报错的bug
|
||||||
|
- uCharts.js 统一各图表类型的series.formatter格式化方法的形参为(val, index, series, opts),方便格式化时有更多参数可用
|
||||||
|
- uCharts.js 标记线功能增加labelText自定义显示文字,增加labelAlign标签显示位置(左侧或右侧),增加标签显示位置微调labelOffsetX、labelOffsetY
|
||||||
|
- uCharts.js 修复条状图当数值很小时开启圆角后样式错误的bug
|
||||||
|
- uCharts.js 修复X轴开启disabled后,X轴仍占用空间的bug
|
||||||
|
- uCharts.js 修复X轴开启滚动条并且开启rotateLabel后,X轴文字与滚动条重叠的bug
|
||||||
|
- uCharts.js 增加X轴rotateAngle文字旋转自定义角度,取值范围(-90至90)
|
||||||
|
- uCharts.js 修复地图文字标签层级显示不正确的bug
|
||||||
|
- uCharts.js 修复饼图、圆环图、玫瑰图当数据全部为0的时候不显示数据标签的bug
|
||||||
|
- uCharts.js 修复当opts.padding上边距为0时,Y轴顶部刻度标签位置不正确的bug
|
||||||
|
|
||||||
|
## 另外我们还开发了各大原生小程序组件,已发布至码云和npm
|
||||||
|
[https://gitee.com/uCharts/uCharts](https://gitee.com/uCharts/uCharts)
|
||||||
|
[https://www.npmjs.com/~qiun](https://www.npmjs.com/~qiun)
|
||||||
|
|
||||||
|
## 对于原生uCharts文档我们已上线新版官方网站,详情点击下面链接进入官网
|
||||||
|
[https://www.uCharts.cn/v2/](https://www.ucharts.cn/v2/)
|
||||||
|
## 2.3.7-20220122(2022-01-22)
|
||||||
|
## 重要!使用vue3编译,请使用cli模式并升级至最新依赖,HbuilderX编译需要使用3.3.8以上版本
|
||||||
|
- uCharts.js 修复uni-app平台组件模式使用vue3编译到小程序报错的bug。
|
||||||
|
## 2.3.7-20220118(2022-01-18)
|
||||||
|
## 注意,使用vue3的前提是需要3.3.8.20220114-alpha版本的HBuilder!
|
||||||
|
## 2.3.67-20220118(2022-01-18)
|
||||||
|
- 秋云图表组件 组件初步支持vue3,全端编译会有些问题,具体详见下面修改:
|
||||||
|
1. 小程序端运行时,在uni_modules文件夹的qiun-data-charts.js中搜索 new uni_modules_qiunDataCharts_js_sdk_uCharts_uCharts.uCharts,将.uCharts去掉。
|
||||||
|
2. 小程序端发行时,在uni_modules文件夹的qiun-data-charts.js中搜索 new e.uCharts,将.uCharts去掉,变为 new e。
|
||||||
|
3. 如果觉得上述步骤比较麻烦,如果您的项目只编译到小程序端,可以修改u-charts.js最后一行导出方式,将 export default uCharts;变更为 export default { uCharts: uCharts }; 这样变更后,H5和App端的renderjs会有问题,请开发者自行选择。(此问题非组件问题,请等待DC官方修复Vue3的小程序端)
|
||||||
|
## 2.3.6-20220111(2022-01-11)
|
||||||
|
- 秋云图表组件 修改组件 props 属性中的 background 默认值为 rgba(0,0,0,0)
|
||||||
|
## 2.3.6-20211201(2021-12-01)
|
||||||
|
- uCharts.js 修复bar条状图开启圆角模式时,值很小时圆角渲染错误的bug
|
||||||
|
## 2.3.5-20211014(2021-10-15)
|
||||||
|
- uCharts.js 增加vue3的编译支持(仅原生uCharts,qiun-data-charts组件后续会支持,请关注更新)
|
||||||
|
## 2.3.4-20211012(2021-10-12)
|
||||||
|
- 秋云图表组件 修复 mac os x 系统 mouseover 事件丢失的 bug
|
||||||
|
## 2.3.3-20210706(2021-07-06)
|
||||||
|
- uCharts.js 增加雷达图开启数据点值(opts.dataLabel)的显示
|
||||||
|
## 2.3.2-20210627(2021-06-27)
|
||||||
|
- 秋云图表组件 修复tooltipCustom个别情况下传值不正确报错TypeError: Cannot read property 'name' of undefined的bug
|
||||||
|
## 2.3.1-20210616(2021-06-16)
|
||||||
|
- uCharts.js 修复圆角柱状图使用4角圆角时,当数值过大时不正确的bug
|
||||||
|
## 2.3.0-20210612(2021-06-12)
|
||||||
|
- uCharts.js 【重要】uCharts增加nvue兼容,可在nvue项目中使用gcanvas组件渲染uCharts,[详见码云uCharts-demo-nvue](https://gitee.com/uCharts/uCharts)
|
||||||
|
- 秋云图表组件 增加tapLegend属性,是否开启图例点击交互事件
|
||||||
|
- 秋云图表组件 getIndex事件中增加返回uCharts实例中的opts参数,以便在页面中调用参数
|
||||||
|
- 示例项目 pages/other/other.vue增加app端自定义tooltip的方法,详见showOptsTooltip方法
|
||||||
|
## 2.2.1-20210603(2021-06-03)
|
||||||
|
- uCharts.js 修复饼图、圆环图、玫瑰图,当起始角度不为0时,tooltip位置不准确的bug
|
||||||
|
- uCharts.js 增加温度计式柱状图开启顶部半圆形的配置
|
||||||
|
## 2.2.0-20210529(2021-05-29)
|
||||||
|
- uCharts.js 增加条状图type="bar"
|
||||||
|
- 示例项目 pages/ucharts/ucharts.vue增加条状图的demo
|
||||||
|
## 2.1.7-20210524(2021-05-24)
|
||||||
|
- uCharts.js 修复大数据量模式下曲线图不平滑的bug
|
||||||
|
## 2.1.6-20210523(2021-05-23)
|
||||||
|
- 秋云图表组件 修复小程序端开启滚动条更新数据后滚动条位置不符合预期的bug
|
||||||
|
## 2.1.5-2021051702(2021-05-17)
|
||||||
|
- uCharts.js 修复自定义Y轴min和max值为0时不能正确显示的bug
|
||||||
|
## 2.1.5-20210517(2021-05-17)
|
||||||
|
- uCharts.js 修复Y轴自定义min和max时,未按指定的最大值最小值显示坐标轴刻度的bug
|
||||||
|
## 2.1.4-20210516(2021-05-16)
|
||||||
|
- 秋云图表组件 优化onWindowResize防抖方法
|
||||||
|
- 秋云图表组件 修复APP端uCharts更新数据时,清空series显示loading图标后再显示图表,图表抖动的bug
|
||||||
|
- uCharts.js 修复开启canvas2d后,x轴、y轴、series自定义字体大小未按比例缩放的bug
|
||||||
|
- 示例项目 修复format-e.vue拼写错误导致app端使用uCharts渲染图表
|
||||||
|
## 2.1.3-20210513(2021-05-13)
|
||||||
|
- 秋云图表组件 修改uCharts变更chartData数据为updateData方法,支持带滚动条的数据动态打点
|
||||||
|
- 秋云图表组件 增加onWindowResize防抖方法 fix by ど誓言,如尘般染指流年づ
|
||||||
|
- 秋云图表组件 H5或者APP变更chartData数据显示loading图表时,原数据闪现的bug
|
||||||
|
- 秋云图表组件 props增加errorReload禁用错误点击重新加载的方法
|
||||||
|
- uCharts.js 增加tooltip显示category(x轴对应点位)标题的功能,opts.extra.tooltip.showCategory,默认为false
|
||||||
|
- uCharts.js 修复mix混合图只有柱状图时,tooltip的分割线显示位置不正确的bug
|
||||||
|
- uCharts.js 修复开启滚动条,图表在拖动中动态打点,滚动条位置不正确的bug
|
||||||
|
- uCharts.js 修复饼图类数据格式为echarts数据格式,series为空数组报错的bug
|
||||||
|
- 示例项目 修改uCharts.js更新到v2.1.2版本后,@getIndex方法获取索引值变更为e.currentIndex.index
|
||||||
|
- 示例项目 pages/updata/updata.vue增加滚动条拖动更新(数据动态打点)的demo
|
||||||
|
- 示例项目 pages/other/other.vue增加errorReload禁用错误点击重新加载的demo
|
||||||
|
## 2.1.2-20210509(2021-05-09)
|
||||||
|
秋云图表组件 修复APP端初始化时就传入chartData或lacaldata不显示图表的bug
|
||||||
|
## 2.1.1-20210509(2021-05-09)
|
||||||
|
- 秋云图表组件 变更ECharts的eopts配置在renderjs内执行,支持在config-echarts.js配置文件内写function配置。
|
||||||
|
- 秋云图表组件 修复APP端报错Prop being mutated: "onmouse"错误的bug。
|
||||||
|
- 秋云图表组件 修复APP端报错Error: Not Found:Page[6][-1,27] at view.umd.min.js:1的bug。
|
||||||
|
## 2.1.0-20210507(2021-05-07)
|
||||||
|
- 秋云图表组件 修复初始化时就有数据或者数据更新的时候loading加载动画闪动的bug
|
||||||
|
- uCharts.js 修复x轴format方法categories为字符串类型时返回NaN的bug
|
||||||
|
- uCharts.js 修复series.textColor、legend.fontColor未执行全局默认颜色的bug
|
||||||
|
## 2.1.0-20210506(2021-05-06)
|
||||||
|
- 秋云图表组件 修复极个别情况下报错item.properties undefined的bug
|
||||||
|
- 秋云图表组件 修复极个别情况下关闭加载动画reshow不起作用,无法显示图表的bug
|
||||||
|
- 示例项目 pages/ucharts/ucharts.vue 增加时间轴折线图(type="tline")、时间轴区域图(type="tarea")、散点图(type="scatter")、气泡图demo(type="bubble")、倒三角形漏斗图(opts.extra.funnel.type="triangle")、金字塔形漏斗图(opts.extra.funnel.type="pyramid")
|
||||||
|
- 示例项目 pages/format-u/format-u.vue 增加X轴format格式化示例
|
||||||
|
- uCharts.js 升级至v2.1.0版本
|
||||||
|
- uCharts.js 修复 玫瑰图面积模式点击tooltip位置不正确的bug
|
||||||
|
- uCharts.js 修复 玫瑰图点击图例,只剩一个类别显示空白的bug
|
||||||
|
- uCharts.js 修复 饼图类图点击图例,其他图表tooltip位置某些情况下不准的bug
|
||||||
|
- uCharts.js 修复 x轴为矢量轴(时间轴)情况下,点击tooltip位置不正确的bug
|
||||||
|
- uCharts.js 修复 词云图获取点击索引偶尔不准的bug
|
||||||
|
- uCharts.js 增加 直角坐标系图表X轴format格式化方法(原生uCharts.js用法请使用formatter)
|
||||||
|
- uCharts.js 增加 漏斗图扩展配置,倒三角形(opts.extra.funnel.type="triangle"),金字塔形(opts.extra.funnel.type="pyramid")
|
||||||
|
- uCharts.js 增加 散点图(opts.type="scatter")、气泡图(opts.type="bubble")
|
||||||
|
- 后期计划 完善散点图、气泡图,增加markPoints标记点,增加横向条状图。
|
||||||
|
## 2.0.0-20210502(2021-05-02)
|
||||||
|
- uCharts.js 修复词云图获取点击索引不正确的bug
|
||||||
|
## 2.0.0-20210501(2021-05-01)
|
||||||
|
- 秋云图表组件 修复QQ小程序、百度小程序在关闭动画效果情况下,v-for循环使用图表,显示不正确的bug
|
||||||
|
## 2.0.0-20210426(2021-04-26)
|
||||||
|
- 秋云图表组件 修复QQ小程序不支持canvas2d的bug
|
||||||
|
- 秋云图表组件 修复钉钉小程序某些情况点击坐标计算错误的bug
|
||||||
|
- uCharts.js 增加 extra.column.categoryGap 参数,柱状图类每个category点位(X轴点)柱子组之间的间距
|
||||||
|
- uCharts.js 增加 yAxis.data[i].titleOffsetY 参数,标题纵向偏移距离,负数为向上偏移,正数向下偏移
|
||||||
|
- uCharts.js 增加 yAxis.data[i].titleOffsetX 参数,标题横向偏移距离,负数为向左偏移,正数向右偏移
|
||||||
|
- uCharts.js 增加 extra.gauge.labelOffset 参数,仪表盘标签文字径向便宜距离,默认13px
|
||||||
|
## 2.0.0-20210422-2(2021-04-22)
|
||||||
|
秋云图表组件 修复 formatterAssign 未判断 args[key] == null 的情况导致栈溢出的 bug
|
||||||
|
## 2.0.0-20210422(2021-04-22)
|
||||||
|
- 秋云图表组件 修复H5、APP、支付宝小程序、微信小程序canvas2d模式下横屏模式的bug
|
||||||
|
## 2.0.0-20210421(2021-04-21)
|
||||||
|
- uCharts.js 修复多行图例的情况下,图例在上方或者下方时,图例float为左侧或者右侧时,第二行及以后的图例对齐方式不正确的bug
|
||||||
|
## 2.0.0-20210420(2021-04-20)
|
||||||
|
- 秋云图表组件 修复微信小程序开启canvas2d模式后,windows版微信小程序不支持canvas2d模式的bug
|
||||||
|
- 秋云图表组件 修改非uni_modules版本为v2.0版本qiun-data-charts组件
|
||||||
|
## 2.0.0-20210419(2021-04-19)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
|
||||||
|
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,如仍不好用,请重启电脑;
|
||||||
|
## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。
|
||||||
|
## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。
|
||||||
|
## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)
|
||||||
|
## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font>
|
||||||
|
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
- uCharts.js 修复混合图中柱状图单独设置颜色不生效的bug
|
||||||
|
- uCharts.js 修复多Y轴单独设置fontSize时,开启canvas2d后,未对应放大字体的bug
|
||||||
|
## 2.0.0-20210418(2021-04-18)
|
||||||
|
- 秋云图表组件 增加directory配置,修复H5端history模式下如果发布到二级目录无法正确加载echarts.min.js的bug
|
||||||
|
## 2.0.0-20210416(2021-04-16)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
|
||||||
|
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,如仍不好用,请重启电脑;
|
||||||
|
## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。
|
||||||
|
## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。
|
||||||
|
## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)
|
||||||
|
## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font>
|
||||||
|
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
- 秋云图表组件 修复APP端某些情况下报错`Not Found Page`的bug,fix by 高级bug开发技术员
|
||||||
|
- 示例项目 修复APP端v-for循环某些情况下报错`Not Found Page`的bug,fix by 高级bug开发技术员
|
||||||
|
- uCharts.js 修复非直角坐标系tooltip提示窗右侧超出未变换方向显示的bug
|
||||||
|
## 2.0.0-20210415(2021-04-15)
|
||||||
|
- 秋云图表组件 修复H5端发布到二级目录下echarts无法加载的bug
|
||||||
|
- 秋云图表组件 修复某些情况下echarts.off('finished')移除监听事件报错的bug
|
||||||
|
## 2.0.0-20210414(2021-04-14)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
|
||||||
|
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,如仍不好用,请重启电脑;
|
||||||
|
## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。
|
||||||
|
## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。
|
||||||
|
## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)
|
||||||
|
## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font>
|
||||||
|
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
- 秋云图表组件 修复H5端在cli项目下ECharts引用地址错误的bug
|
||||||
|
- 示例项目 增加ECharts的formatter用法的示例(详见示例项目format-e.vue)
|
||||||
|
- uCharts.js 增加圆环图中心背景色的配置extra.ring.centerColor
|
||||||
|
- uCharts.js 修复微信小程序安卓端柱状图开启透明色后显示不正确的bug
|
||||||
|
## 2.0.0-20210413(2021-04-13)
|
||||||
|
- 秋云图表组件 修复百度小程序多个图表真机未能正确获取根元素dom尺寸的bug
|
||||||
|
- 秋云图表组件 修复百度小程序横屏模式方向不正确的bug
|
||||||
|
- 秋云图表组件 修改ontouch时,@getTouchStart@getTouchMove@getTouchEnd的触发条件
|
||||||
|
- uCharts.js 修复饼图类数据格式series属性不生效的bug
|
||||||
|
- uCharts.js 增加时序区域图 详见示例项目中ucharts.vue
|
||||||
|
## 2.0.0-20210412-2(2021-04-12)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
|
||||||
|
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX。如仍不好用,请重启电脑,此问题已于DCloud官方确认,HBuilderX下个版本会修复。
|
||||||
|
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
- 秋云图表组件 修复uCharts在APP端横屏模式下不能正确渲染的bug
|
||||||
|
- 示例项目 增加ECharts柱状图渐变色、圆角柱状图、横向柱状图(条状图)的示例
|
||||||
|
## 2.0.0-20210412(2021-04-12)
|
||||||
|
- 秋云图表组件 修复created中判断echarts导致APP端无法识别,改回mounted中判断echarts初始化
|
||||||
|
- uCharts.js 修复2d模式下series.textOffset未乘像素比的bug
|
||||||
|
## 2.0.0-20210411(2021-04-11)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
|
||||||
|
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,并清空小程序开发者工具缓存。
|
||||||
|
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
- uCharts.js 折线图区域图增加connectNulls断点续连的功能,详见示例项目中ucharts.vue
|
||||||
|
- 秋云图表组件 变更初始化方法为created,变更type2d默认值为true,优化2d模式下组件初始化后dom获取不到的bug
|
||||||
|
- 秋云图表组件 修复左右布局时,右侧图表点击坐标错误的bug,修复tooltip柱状图自定义颜色显示object的bug
|
||||||
|
## 2.0.0-20210410(2021-04-10)
|
||||||
|
- 修复左右布局时,右侧图表点击坐标错误的bug,修复柱状图自定义颜色tooltip显示object的bug
|
||||||
|
- 增加标记线及柱状图自定义颜色的demo
|
||||||
|
## 2.0.0-20210409(2021-04-08)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧【使用HBuilderX导入插件】即可体验,DEMO演示及在线生成工具(v2.0文档)[https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
- uCharts.js 修复钉钉小程序百度小程序measureText不准确的bug,修复2d模式下饼图类activeRadius为按比例放大的bug
|
||||||
|
- 修复组件在支付宝小程序端点击位置不准确的bug
|
||||||
|
## 2.0.0-20210408(2021-04-07)
|
||||||
|
- 修复组件在支付宝小程序端不能显示的bug(目前支付宝小程不能点击交互,后续修复)
|
||||||
|
- uCharts.js 修复高分屏下柱状图类,圆弧进度条 自定义宽度不能按比例放大的bug
|
||||||
|
## 2.0.0-20210407(2021-04-06)
|
||||||
|
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧【使用HBuilderX导入插件】即可体验,DEMO演示及在线生成工具(v2.0文档)[https://demo.ucharts.cn](https://demo.ucharts.cn)
|
||||||
|
## 增加 通过tofix和unit快速格式化y轴的demo add by `howcode`
|
||||||
|
## 增加 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651)
|
||||||
|
## 2.0.0-20210406(2021-04-05)
|
||||||
|
# 秋云图表组件+uCharts v2.0版本同步上线,使用方法详见https://demo.ucharts.cn帮助页
|
||||||
|
## 2.0.0(2021-04-05)
|
||||||
|
# 秋云图表组件+uCharts v2.0版本同步上线,使用方法详见https://demo.ucharts.cn帮助页
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading1">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading1',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.container.loading1 {
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890FF;
|
||||||
|
}
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91CB74;
|
||||||
|
}
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #FAC858;
|
||||||
|
}
|
||||||
|
.container .shape.shape4 {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #EE6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading1 .shape1 {
|
||||||
|
-webkit-animation: animation1shape1 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation1shape1 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation1shape1 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(16px, 16px);
|
||||||
|
transform: translate(16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape1 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(16px, 16px);
|
||||||
|
transform: translate(16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading1 .shape2 {
|
||||||
|
-webkit-animation: animation1shape2 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation1shape2 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation1shape2 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-16px, 16px);
|
||||||
|
transform: translate(-16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape2 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-16px, 16px);
|
||||||
|
transform: translate(-16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading1 .shape3 {
|
||||||
|
-webkit-animation: animation1shape3 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation1shape3 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation1shape3 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(16px, -16px);
|
||||||
|
transform: translate(16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape3 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(16px, -16px);
|
||||||
|
transform: translate(16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading1 .shape4 {
|
||||||
|
-webkit-animation: animation1shape4 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation1shape4 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation1shape4 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-16px, -16px);
|
||||||
|
transform: translate(-16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape4 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-16px, -16px);
|
||||||
|
transform: translate(-16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading2">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading2',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading2 {
|
||||||
|
-webkit-transform: rotate(10deg);
|
||||||
|
transform: rotate(10deg);
|
||||||
|
}
|
||||||
|
.container.loading2 .shape {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.container.loading2{
|
||||||
|
-webkit-animation: rotation 1s infinite;
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890FF;
|
||||||
|
}
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91CB74;
|
||||||
|
}
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #FAC858;
|
||||||
|
}
|
||||||
|
.container .shape.shape4 {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #EE6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loading2 .shape1 {
|
||||||
|
-webkit-animation: animation2shape1 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape1 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation2shape1 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(20px, 20px);
|
||||||
|
transform: translate(20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape1 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(20px, 20px);
|
||||||
|
transform: translate(20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading2 .shape2 {
|
||||||
|
-webkit-animation: animation2shape2 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape2 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation2shape2 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-20px, 20px);
|
||||||
|
transform: translate(-20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape2 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-20px, 20px);
|
||||||
|
transform: translate(-20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading2 .shape3 {
|
||||||
|
-webkit-animation: animation2shape3 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape3 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation2shape3 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(20px, -20px);
|
||||||
|
transform: translate(20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape3 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(20px, -20px);
|
||||||
|
transform: translate(20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading2 .shape4 {
|
||||||
|
-webkit-animation: animation2shape4 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape4 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation2shape4 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-20px, -20px);
|
||||||
|
transform: translate(-20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape4 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-20px, -20px);
|
||||||
|
transform: translate(-20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading3">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading3',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading3 {
|
||||||
|
-webkit-animation: rotation 1s infinite;
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
}
|
||||||
|
.container.loading3 .shape1 {
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
}
|
||||||
|
.container.loading3 .shape2 {
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
}
|
||||||
|
.container.loading3 .shape3 {
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
}
|
||||||
|
.container.loading3 .shape4 {
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890FF;
|
||||||
|
}
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91CB74;
|
||||||
|
}
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #FAC858;
|
||||||
|
}
|
||||||
|
.container .shape.shape4 {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #EE6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading3 .shape1 {
|
||||||
|
-webkit-animation: animation3shape1 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape1 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation3shape1 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(5px, 5px);
|
||||||
|
transform: translate(5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape1 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(5px, 5px);
|
||||||
|
transform: translate(5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading3 .shape2 {
|
||||||
|
-webkit-animation: animation3shape2 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape2 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation3shape2 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-5px, 5px);
|
||||||
|
transform: translate(-5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape2 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-5px, 5px);
|
||||||
|
transform: translate(-5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading3 .shape3 {
|
||||||
|
-webkit-animation: animation3shape3 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape3 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation3shape3 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(5px, -5px);
|
||||||
|
transform: translate(5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape3 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(5px, -5px);
|
||||||
|
transform: translate(5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading3 .shape4 {
|
||||||
|
-webkit-animation: animation3shape4 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape4 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation3shape4 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-5px, -5px);
|
||||||
|
transform: translate(-5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape4 {
|
||||||
|
from {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: translate(-5px, -5px);
|
||||||
|
transform: translate(-5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading5">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading5',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading5 .shape {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890FF;
|
||||||
|
}
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91CB74;
|
||||||
|
}
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #FAC858;
|
||||||
|
}
|
||||||
|
.container .shape.shape4 {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #EE6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading5 .shape1 {
|
||||||
|
animation: animation5shape1 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation5shape1 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(15px, 15px);
|
||||||
|
transform: translate(15px, 15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape1 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(15px, 15px);
|
||||||
|
transform: translate(15px, 15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading5 .shape2 {
|
||||||
|
animation: animation5shape2 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation5shape2 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-15px, 15px);
|
||||||
|
transform: translate(-15px, 15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape2 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-15px, 15px);
|
||||||
|
transform: translate(-15px, 15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading5 .shape3 {
|
||||||
|
animation: animation5shape3 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation5shape3 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(15px, -15px);
|
||||||
|
transform: translate(15px, -15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape3 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(15px, -15px);
|
||||||
|
transform: translate(15px, -15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading5 .shape4 {
|
||||||
|
animation: animation5shape4 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation5shape4 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-15px, -15px);
|
||||||
|
transform: translate(-15px, -15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape4 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-15px, -15px);
|
||||||
|
transform: translate(-15px, -15px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading6">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading6',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading6 {
|
||||||
|
-webkit-animation: rotation 1s infinite;
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
}
|
||||||
|
.container.loading6 .shape {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890FF;
|
||||||
|
}
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91CB74;
|
||||||
|
}
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #FAC858;
|
||||||
|
}
|
||||||
|
.container .shape.shape4 {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #EE6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loading6 .shape1 {
|
||||||
|
-webkit-animation: animation6shape1 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape1 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation6shape1 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(18px, 18px);
|
||||||
|
transform: translate(18px, 18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape1 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(18px, 18px);
|
||||||
|
transform: translate(18px, 18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading6 .shape2 {
|
||||||
|
-webkit-animation: animation6shape2 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape2 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation6shape2 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-18px, 18px);
|
||||||
|
transform: translate(-18px, 18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape2 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-18px, 18px);
|
||||||
|
transform: translate(-18px, 18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading6 .shape3 {
|
||||||
|
-webkit-animation: animation6shape3 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape3 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation6shape3 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(18px, -18px);
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape3 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(18px, -18px);
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.loading6 .shape4 {
|
||||||
|
-webkit-animation: animation6shape4 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape4 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes animation6shape4 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-18px, -18px);
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape4 {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translate(-18px, -18px);
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<Loading1 v-if="loadingType==1"/>
|
||||||
|
<Loading2 v-if="loadingType==2"/>
|
||||||
|
<Loading3 v-if="loadingType==3"/>
|
||||||
|
<Loading4 v-if="loadingType==4"/>
|
||||||
|
<Loading5 v-if="loadingType==5"/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading1 from "./loading1.vue";
|
||||||
|
import Loading2 from "./loading2.vue";
|
||||||
|
import Loading3 from "./loading3.vue";
|
||||||
|
import Loading4 from "./loading4.vue";
|
||||||
|
import Loading5 from "./loading5.vue";
|
||||||
|
export default {
|
||||||
|
components:{Loading1,Loading2,Loading3,Loading4,Loading5},
|
||||||
|
name: 'qiun-loading',
|
||||||
|
props: {
|
||||||
|
loadingType: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* uCharts®
|
||||||
|
* 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台
|
||||||
|
* Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.
|
||||||
|
* Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||||
|
* 复制使用请保留本段注释,感谢支持开源!
|
||||||
|
*
|
||||||
|
* uCharts®官方网站
|
||||||
|
* https://www.uCharts.cn
|
||||||
|
*
|
||||||
|
* 开源地址:
|
||||||
|
* https://gitee.com/uCharts/uCharts
|
||||||
|
*
|
||||||
|
* uni-app插件市场地址:
|
||||||
|
* http://ext.dcloud.net.cn/plugin?id=271
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 通用配置项
|
||||||
|
|
||||||
|
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
|
||||||
|
const color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'];
|
||||||
|
|
||||||
|
const cfe = {
|
||||||
|
//demotype为自定义图表类型
|
||||||
|
"type": ["pie", "ring", "rose", "funnel", "line", "column", "area", "radar", "gauge","candle","demotype"],
|
||||||
|
//增加自定义图表类型,如果需要categories,请在这里加入您的图表类型例如最后的"demotype"
|
||||||
|
"categories": ["line", "column", "area", "radar", "gauge", "candle","demotype"],
|
||||||
|
//instance为实例变量承载属性,option为eopts承载属性,不要删除
|
||||||
|
"instance": {},
|
||||||
|
"option": {},
|
||||||
|
//下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
|
||||||
|
"formatter":{
|
||||||
|
"tooltipDemo1":function(res){
|
||||||
|
let result = ''
|
||||||
|
for (let i in res) {
|
||||||
|
if (i == 0) {
|
||||||
|
result += res[i].axisValueLabel + '年销售额'
|
||||||
|
}
|
||||||
|
let value = '--'
|
||||||
|
if (res[i].data !== null) {
|
||||||
|
value = res[i].data
|
||||||
|
}
|
||||||
|
// #ifdef H5
|
||||||
|
result += '\n' + res[i].seriesName + ':' + value + ' 万元'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
result += '<br/>' + res[i].marker + res[i].seriesName + ':' + value + ' 万元'
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
legendFormat:function(name){
|
||||||
|
return "自定义图例+"+name;
|
||||||
|
},
|
||||||
|
yAxisFormatDemo:function (value, index) {
|
||||||
|
return value + '元';
|
||||||
|
},
|
||||||
|
seriesFormatDemo:function(res){
|
||||||
|
return res.name + '年' + res.value + '元';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在eopts参数,会将demotype与eopts中option合并后渲染图表。
|
||||||
|
"demotype":{
|
||||||
|
"color": color,
|
||||||
|
//在这里填写echarts的option即可
|
||||||
|
|
||||||
|
},
|
||||||
|
//下面是自定义配置,请添加项目所需的通用配置
|
||||||
|
"column": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'axis'
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"top": 30,
|
||||||
|
"bottom": 50,
|
||||||
|
"right": 15,
|
||||||
|
"left": 40
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"bottom": 'left',
|
||||||
|
},
|
||||||
|
"toolbox": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"type": 'category',
|
||||||
|
"axisLabel": {
|
||||||
|
"color": '#666666'
|
||||||
|
},
|
||||||
|
"axisLine": {
|
||||||
|
"lineStyle": {
|
||||||
|
"color": '#CCCCCC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boundaryGap": true,
|
||||||
|
"data": []
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"type": 'value',
|
||||||
|
"axisTick": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"axisLabel": {
|
||||||
|
"color": '#666666'
|
||||||
|
},
|
||||||
|
"axisLine": {
|
||||||
|
"lineStyle": {
|
||||||
|
"color": '#CCCCCC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'bar',
|
||||||
|
"data": [],
|
||||||
|
"barwidth": 20,
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"color": "#666666",
|
||||||
|
"position": 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"line": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'axis'
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"top": 30,
|
||||||
|
"bottom": 50,
|
||||||
|
"right": 15,
|
||||||
|
"left": 40
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"bottom": 'left',
|
||||||
|
},
|
||||||
|
"toolbox": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"type": 'category',
|
||||||
|
"axisLabel": {
|
||||||
|
"color": '#666666'
|
||||||
|
},
|
||||||
|
"axisLine": {
|
||||||
|
"lineStyle": {
|
||||||
|
"color": '#CCCCCC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boundaryGap": true,
|
||||||
|
"data": []
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"type": 'value',
|
||||||
|
"axisTick": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"axisLabel": {
|
||||||
|
"color": '#666666'
|
||||||
|
},
|
||||||
|
"axisLine": {
|
||||||
|
"lineStyle": {
|
||||||
|
"color": '#CCCCCC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'line',
|
||||||
|
"data": [],
|
||||||
|
"barwidth": 20,
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"color": "#666666",
|
||||||
|
"position": 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'axis'
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"top": 30,
|
||||||
|
"bottom": 50,
|
||||||
|
"right": 15,
|
||||||
|
"left": 40
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"bottom": 'left',
|
||||||
|
},
|
||||||
|
"toolbox": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"type": 'category',
|
||||||
|
"axisLabel": {
|
||||||
|
"color": '#666666'
|
||||||
|
},
|
||||||
|
"axisLine": {
|
||||||
|
"lineStyle": {
|
||||||
|
"color": '#CCCCCC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boundaryGap": true,
|
||||||
|
"data": []
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"type": 'value',
|
||||||
|
"axisTick": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"axisLabel": {
|
||||||
|
"color": '#666666'
|
||||||
|
},
|
||||||
|
"axisLine": {
|
||||||
|
"lineStyle": {
|
||||||
|
"color": '#CCCCCC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'line',
|
||||||
|
"data": [],
|
||||||
|
"areaStyle": {},
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"color": "#666666",
|
||||||
|
"position": 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pie": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'item'
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"top": 40,
|
||||||
|
"bottom": 30,
|
||||||
|
"right": 15,
|
||||||
|
"left": 15
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"bottom": 'left',
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'pie',
|
||||||
|
"data": [],
|
||||||
|
"radius": '50%',
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"color": "#666666",
|
||||||
|
"position": 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ring": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'item'
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"top": 40,
|
||||||
|
"bottom": 30,
|
||||||
|
"right": 15,
|
||||||
|
"left": 15
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"bottom": 'left',
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'pie',
|
||||||
|
"data": [],
|
||||||
|
"radius": ['40%', '70%'],
|
||||||
|
"avoidLabelOverlap": false,
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"color": "#666666",
|
||||||
|
"position": 'top',
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rose": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'item'
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"top": 'bottom'
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'pie',
|
||||||
|
"data": [],
|
||||||
|
"radius": "55%",
|
||||||
|
"center": ['50%', '50%'],
|
||||||
|
"roseType": 'area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"funnel": {
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"trigger": 'item',
|
||||||
|
"formatter": "{b} : {c}%"
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"top": 'bottom'
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'funnel',
|
||||||
|
"left": '10%',
|
||||||
|
"top": 60,
|
||||||
|
"bottom": 60,
|
||||||
|
"width": '80%',
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"minSize": '0%',
|
||||||
|
"maxSize": '100%',
|
||||||
|
"sort": 'descending',
|
||||||
|
"gap": 2,
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"position": 'inside'
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"length": 10,
|
||||||
|
"lineStyle": {
|
||||||
|
"width": 1,
|
||||||
|
"type": 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itemStyle": {
|
||||||
|
"bordercolor": '#fff',
|
||||||
|
"borderwidth": 1
|
||||||
|
},
|
||||||
|
"emphasis": {
|
||||||
|
"label": {
|
||||||
|
"fontSize": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"gauge": {
|
||||||
|
"color": color,
|
||||||
|
"tooltip": {
|
||||||
|
"formatter": '{a} <br/>{b} : {c}%'
|
||||||
|
},
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '业务指标',
|
||||||
|
"type": 'gauge',
|
||||||
|
"detail": {"formatter": '{value}%'},
|
||||||
|
"data": [{"value": 50, "name": '完成率'}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"candle": {
|
||||||
|
"xAxis": {
|
||||||
|
"data": []
|
||||||
|
},
|
||||||
|
"yAxis": {},
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"text": ''
|
||||||
|
},
|
||||||
|
"dataZoom": [{
|
||||||
|
"type": 'inside',
|
||||||
|
"xAxisIndex": [0, 1],
|
||||||
|
"start": 10,
|
||||||
|
"end": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"show": true,
|
||||||
|
"xAxisIndex": [0, 1],
|
||||||
|
"type": 'slider',
|
||||||
|
"bottom": 10,
|
||||||
|
"start": 10,
|
||||||
|
"end": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"seriesTemplate": {
|
||||||
|
"name": '',
|
||||||
|
"type": 'k',
|
||||||
|
"data": [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cfe;
|
||||||
@@ -0,0 +1,637 @@
|
|||||||
|
/*
|
||||||
|
* uCharts®
|
||||||
|
* 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台
|
||||||
|
* Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.
|
||||||
|
* Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||||
|
* 复制使用请保留本段注释,感谢支持开源!
|
||||||
|
*
|
||||||
|
* uCharts®官方网站
|
||||||
|
* https://www.uCharts.cn
|
||||||
|
*
|
||||||
|
* 开源地址:
|
||||||
|
* https://gitee.com/uCharts/uCharts
|
||||||
|
*
|
||||||
|
* uni-app插件市场地址:
|
||||||
|
* http://ext.dcloud.net.cn/plugin?id=271
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
|
||||||
|
const color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'];
|
||||||
|
|
||||||
|
//事件转换函数,主要用作格式化x轴为时间轴,根据需求自行修改
|
||||||
|
const formatDateTime = (timeStamp, returnType)=>{
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(timeStamp * 1000);
|
||||||
|
var y = date.getFullYear();
|
||||||
|
var m = date.getMonth() + 1;
|
||||||
|
m = m < 10 ? ('0' + m) : m;
|
||||||
|
var d = date.getDate();
|
||||||
|
d = d < 10 ? ('0' + d) : d;
|
||||||
|
var h = date.getHours();
|
||||||
|
h = h < 10 ? ('0' + h) : h;
|
||||||
|
var minute = date.getMinutes();
|
||||||
|
var second = date.getSeconds();
|
||||||
|
minute = minute < 10 ? ('0' + minute) : minute;
|
||||||
|
second = second < 10 ? ('0' + second) : second;
|
||||||
|
if(returnType == 'full'){return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;}
|
||||||
|
if(returnType == 'y-m-d'){return y + '-' + m + '-' + d;}
|
||||||
|
if(returnType == 'h:m'){return h +':' + minute;}
|
||||||
|
if(returnType == 'h:m:s'){return h +':' + minute +':' + second;}
|
||||||
|
return [y, m, d, h, minute, second];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cfu = {
|
||||||
|
//demotype为自定义图表类型,一般不需要自定义图表类型,只需要改根节点上对应的类型即可
|
||||||
|
"type":["pie","ring","rose","word","funnel","map","arcbar","line","column","mount","bar","area","radar","gauge","candle","mix","tline","tarea","scatter","bubble","demotype"],
|
||||||
|
"range":["饼状图","圆环图","玫瑰图","词云图","漏斗图","地图","圆弧进度条","折线图","柱状图","山峰图","条状图","区域图","雷达图","仪表盘","K线图","混合图","时间轴折线","时间轴区域","散点图","气泡图","自定义类型"],
|
||||||
|
//增加自定义图表类型,如果需要categories,请在这里加入您的图表类型,例如最后的"demotype"
|
||||||
|
//自定义类型时需要注意"tline","tarea","scatter","bubble"等时间轴(矢量x轴)类图表,没有categories,不需要加入categories
|
||||||
|
"categories":["line","column","mount","bar","area","radar","gauge","candle","mix","demotype"],
|
||||||
|
//instance为实例变量承载属性,不要删除
|
||||||
|
"instance":{},
|
||||||
|
//option为opts及eopts承载属性,不要删除
|
||||||
|
"option":{},
|
||||||
|
//下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
|
||||||
|
"formatter":{
|
||||||
|
"yAxisDemo1":function(val, index, opts){return val+'元'},
|
||||||
|
"yAxisDemo2":function(val, index, opts){return val.toFixed(2)},
|
||||||
|
"xAxisDemo1":function(val, index, opts){return val+'年';},
|
||||||
|
"xAxisDemo2":function(val, index, opts){return formatDateTime(val,'h:m')},
|
||||||
|
"seriesDemo1":function(val, index, series, opts){return val+'元'},
|
||||||
|
"tooltipDemo1":function(item, category, index, opts){
|
||||||
|
if(index==0){
|
||||||
|
return '随便用'+item.data+'年'
|
||||||
|
}else{
|
||||||
|
return '其他我没改'+item.data+'天'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tooltipTag":function(item, category, index, opts){
|
||||||
|
return item.name + ':' + item.data + '篇文章'
|
||||||
|
},
|
||||||
|
"tooltipCategory":function(item, category, index, opts){
|
||||||
|
return category + ':' + item.data + '篇文章'
|
||||||
|
},
|
||||||
|
"tooltipTop10Articles":function(item, category, index, opts){
|
||||||
|
const name = category.slice(0,6) + '...';
|
||||||
|
return name + ':访问量' + item.data + '次';
|
||||||
|
},
|
||||||
|
"tooltipUserComments":function(item, category, index, opts){
|
||||||
|
return category + ':评论' + item.data + '次'
|
||||||
|
},
|
||||||
|
"pieDemo":function(val, index, series, opts){
|
||||||
|
if(index !== undefined){
|
||||||
|
return series[index].name+':'+series[index].data+'元'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xAxisTop10Article":function(val, index, opts){
|
||||||
|
if(val.length>6){
|
||||||
|
return val.slice(0,6) +'...'
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
"xAxisUserComments":function(val, index, opts){
|
||||||
|
if(val.length>6){
|
||||||
|
return val.slice(0,6) +'...'
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
"xAxisCategory":function(val, index, opts){
|
||||||
|
if(val.length>6){
|
||||||
|
return val.slice(0,6) +'...'
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在opts参数,会将demotype与opts中option合并后渲染图表。
|
||||||
|
"demotype":{
|
||||||
|
//我这里把曲线图当做了自定义图表类型,您可以根据需要随意指定类型或配置
|
||||||
|
"type": "line",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,10,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"gridType": "dash",
|
||||||
|
"dashLength": 2,
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"line": {
|
||||||
|
"type": "curve",
|
||||||
|
"width": 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//下面是自定义配置,请添加项目所需的通用配置
|
||||||
|
"pie":{
|
||||||
|
"type": "pie",
|
||||||
|
"color": color,
|
||||||
|
"padding": [5,5,5,5],
|
||||||
|
"extra": {
|
||||||
|
"pie": {
|
||||||
|
"activeOpacity": 0.5,
|
||||||
|
"activeRadius": 10,
|
||||||
|
"offsetAngle": 0,
|
||||||
|
"labelWidth": 15,
|
||||||
|
"border": true,
|
||||||
|
"borderWidth": 3,
|
||||||
|
"borderColor": "#FFFFFF"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ring":{
|
||||||
|
"type": "ring",
|
||||||
|
"color": color,
|
||||||
|
"padding": [5,5,5,5],
|
||||||
|
"rotate": false,
|
||||||
|
"dataLabel": true,
|
||||||
|
"legend": {
|
||||||
|
"show": true,
|
||||||
|
"position": "right",
|
||||||
|
"lineHeight": 25,
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "收益率",
|
||||||
|
"fontSize": 15,
|
||||||
|
"color": "#666666"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"name": "70%",
|
||||||
|
"fontSize": 25,
|
||||||
|
"color": "#7cb5ec"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"ring": {
|
||||||
|
"ringWidth":30,
|
||||||
|
"activeOpacity": 0.5,
|
||||||
|
"activeRadius": 10,
|
||||||
|
"offsetAngle": 0,
|
||||||
|
"labelWidth": 15,
|
||||||
|
"border": true,
|
||||||
|
"borderWidth": 3,
|
||||||
|
"borderColor": "#FFFFFF"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rose":{
|
||||||
|
"type": "rose",
|
||||||
|
"color": color,
|
||||||
|
"padding": [5,5,5,5],
|
||||||
|
"legend": {
|
||||||
|
"show": true,
|
||||||
|
"position": "left",
|
||||||
|
"lineHeight": 25,
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"rose": {
|
||||||
|
"type": "area",
|
||||||
|
"minRadius": 50,
|
||||||
|
"activeOpacity": 0.5,
|
||||||
|
"activeRadius": 10,
|
||||||
|
"offsetAngle": 0,
|
||||||
|
"labelWidth": 15,
|
||||||
|
"border": false,
|
||||||
|
"borderWidth": 2,
|
||||||
|
"borderColor": "#FFFFFF"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"word":{
|
||||||
|
"type": "word",
|
||||||
|
"color": color,
|
||||||
|
"extra": {
|
||||||
|
"word": {
|
||||||
|
"type": "normal",
|
||||||
|
"autoColors": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"funnel":{
|
||||||
|
"type": "funnel",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,15,0,15],
|
||||||
|
"extra": {
|
||||||
|
"funnel": {
|
||||||
|
"activeOpacity": 0.3,
|
||||||
|
"activeWidth": 10,
|
||||||
|
"border": true,
|
||||||
|
"borderWidth": 2,
|
||||||
|
"borderColor": "#FFFFFF",
|
||||||
|
"fillOpacity": 1,
|
||||||
|
"labelAlign": "right"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map":{
|
||||||
|
"type": "map",
|
||||||
|
"color": color,
|
||||||
|
"padding": [0,0,0,0],
|
||||||
|
"dataLabel": true,
|
||||||
|
"extra": {
|
||||||
|
"map": {
|
||||||
|
"border": true,
|
||||||
|
"borderWidth": 1,
|
||||||
|
"borderColor": "#666666",
|
||||||
|
"fillOpacity": 0.6,
|
||||||
|
"activeBorderColor": "#F04864",
|
||||||
|
"activeFillColor": "#FACC14",
|
||||||
|
"activeFillOpacity": 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"arcbar":{
|
||||||
|
"type": "arcbar",
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"name": "百分比",
|
||||||
|
"fontSize": 25,
|
||||||
|
"color": "#00FF00"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"name": "默认标题",
|
||||||
|
"fontSize": 15,
|
||||||
|
"color": "#666666"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"arcbar": {
|
||||||
|
"type": "default",
|
||||||
|
"width": 12,
|
||||||
|
"backgroundColor": "#E9E9E9",
|
||||||
|
"startAngle": 0.75,
|
||||||
|
"endAngle": 0.25,
|
||||||
|
"gap": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"line":{
|
||||||
|
"type": "line",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,10,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"gridType": "dash",
|
||||||
|
"dashLength": 2,
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"line": {
|
||||||
|
"type": "straight",
|
||||||
|
"width": 2,
|
||||||
|
"activeType": "hollow"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tline":{
|
||||||
|
"type": "line",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,10,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": false,
|
||||||
|
"boundaryGap":"justify",
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"gridType": "dash",
|
||||||
|
"dashLength": 2,
|
||||||
|
"data":[
|
||||||
|
{
|
||||||
|
"min":0,
|
||||||
|
"max":80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"line": {
|
||||||
|
"type": "curve",
|
||||||
|
"width": 2,
|
||||||
|
"activeType": "hollow"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tarea":{
|
||||||
|
"type": "area",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,10,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
"boundaryGap":"justify",
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"gridType": "dash",
|
||||||
|
"dashLength": 2,
|
||||||
|
"data":[
|
||||||
|
{
|
||||||
|
"min":0,
|
||||||
|
"max":80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"area": {
|
||||||
|
"type": "curve",
|
||||||
|
"opacity": 0.2,
|
||||||
|
"addLine": true,
|
||||||
|
"width": 2,
|
||||||
|
"gradient": true,
|
||||||
|
"activeType": "hollow"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"column":{
|
||||||
|
"type": "column",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,15,0,5],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"data":[{"min":0}]
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"column": {
|
||||||
|
"type": "group",
|
||||||
|
"width": 30,
|
||||||
|
"activeBgColor": "#000000",
|
||||||
|
"activeBgOpacity": 0.08
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mount":{
|
||||||
|
"type": "mount",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,15,0,5],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"data":[{"min":0}]
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"mount": {
|
||||||
|
"type": "mount",
|
||||||
|
"widthRatio": 1.5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bar":{
|
||||||
|
"type": "bar",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,30,0,5],
|
||||||
|
"xAxis": {
|
||||||
|
"boundaryGap":"justify",
|
||||||
|
"disableGrid":false,
|
||||||
|
"min":0,
|
||||||
|
"axisLine":false
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"bar": {
|
||||||
|
"type": "group",
|
||||||
|
"width": 30,
|
||||||
|
"meterBorde": 1,
|
||||||
|
"meterFillColor": "#FFFFFF",
|
||||||
|
"activeBgColor": "#000000",
|
||||||
|
"activeBgOpacity": 0.08
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"area":{
|
||||||
|
"type": "area",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,15,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"gridType": "dash",
|
||||||
|
"dashLength": 2,
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"area": {
|
||||||
|
"type": "straight",
|
||||||
|
"opacity": 0.2,
|
||||||
|
"addLine": true,
|
||||||
|
"width": 2,
|
||||||
|
"gradient": false,
|
||||||
|
"activeType": "hollow"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radar":{
|
||||||
|
"type": "radar",
|
||||||
|
"color": color,
|
||||||
|
"padding": [5,5,5,5],
|
||||||
|
"dataLabel": false,
|
||||||
|
"legend": {
|
||||||
|
"show": true,
|
||||||
|
"position": "right",
|
||||||
|
"lineHeight": 25,
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"radar": {
|
||||||
|
"gridType": "radar",
|
||||||
|
"gridColor": "#CCCCCC",
|
||||||
|
"gridCount": 3,
|
||||||
|
"opacity": 0.2,
|
||||||
|
"max": 200,
|
||||||
|
"labelShow": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gauge":{
|
||||||
|
"type": "gauge",
|
||||||
|
"color": color,
|
||||||
|
"title": {
|
||||||
|
"name": "66Km/H",
|
||||||
|
"fontSize": 25,
|
||||||
|
"color": "#2fc25b",
|
||||||
|
"offsetY": 50
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"name": "实时速度",
|
||||||
|
"fontSize": 15,
|
||||||
|
"color": "#1890ff",
|
||||||
|
"offsetY": -50
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"gauge": {
|
||||||
|
"type": "default",
|
||||||
|
"width": 30,
|
||||||
|
"labelColor": "#666666",
|
||||||
|
"startAngle": 0.75,
|
||||||
|
"endAngle": 0.25,
|
||||||
|
"startNumber": 0,
|
||||||
|
"endNumber": 100,
|
||||||
|
"labelFormat": "",
|
||||||
|
"splitLine": {
|
||||||
|
"fixRadius": 0,
|
||||||
|
"splitNumber": 10,
|
||||||
|
"width": 30,
|
||||||
|
"color": "#FFFFFF",
|
||||||
|
"childNumber": 5,
|
||||||
|
"childWidth": 12
|
||||||
|
},
|
||||||
|
"pointer": {
|
||||||
|
"width": 24,
|
||||||
|
"color": "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"candle":{
|
||||||
|
"type": "candle",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,15,0,15],
|
||||||
|
"enableScroll": true,
|
||||||
|
"enableMarkLine": true,
|
||||||
|
"dataLabel": false,
|
||||||
|
"xAxis": {
|
||||||
|
"labelCount": 4,
|
||||||
|
"itemCount": 40,
|
||||||
|
"disableGrid": true,
|
||||||
|
"gridColor": "#CCCCCC",
|
||||||
|
"gridType": "solid",
|
||||||
|
"dashLength": 4,
|
||||||
|
"scrollShow": true,
|
||||||
|
"scrollAlign": "left",
|
||||||
|
"scrollColor": "#A6A6A6",
|
||||||
|
"scrollBackgroundColor": "#EFEBEF"
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"candle": {
|
||||||
|
"color": {
|
||||||
|
"upLine": "#f04864",
|
||||||
|
"upFill": "#f04864",
|
||||||
|
"downLine": "#2fc25b",
|
||||||
|
"downFill": "#2fc25b"
|
||||||
|
},
|
||||||
|
"average": {
|
||||||
|
"show": true,
|
||||||
|
"name": ["MA5","MA10","MA30"],
|
||||||
|
"day": [5,10,20],
|
||||||
|
"color": ["#1890ff","#2fc25b","#facc14"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"markLine": {
|
||||||
|
"type": "dash",
|
||||||
|
"dashLength": 5,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"value": 2150,
|
||||||
|
"lineColor": "#f04864",
|
||||||
|
"showLabel": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 2350,
|
||||||
|
"lineColor": "#f04864",
|
||||||
|
"showLabel": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mix":{
|
||||||
|
"type": "mix",
|
||||||
|
"color": color,
|
||||||
|
"padding": [15,15,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": true,
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"disabled": false,
|
||||||
|
"disableGrid": false,
|
||||||
|
"splitNumber": 5,
|
||||||
|
"gridType": "dash",
|
||||||
|
"dashLength": 4,
|
||||||
|
"gridColor": "#CCCCCC",
|
||||||
|
"padding": 10,
|
||||||
|
"showTitle": true,
|
||||||
|
"data": []
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"mix": {
|
||||||
|
"column": {
|
||||||
|
"width": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scatter":{
|
||||||
|
"type": "scatter",
|
||||||
|
"color":color,
|
||||||
|
"padding":[15,15,0,15],
|
||||||
|
"dataLabel":false,
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": false,
|
||||||
|
"gridType":"dash",
|
||||||
|
"splitNumber":5,
|
||||||
|
"boundaryGap":"justify",
|
||||||
|
"min":0
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"disableGrid": false,
|
||||||
|
"gridType":"dash",
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"scatter": {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bubble":{
|
||||||
|
"type": "bubble",
|
||||||
|
"color":color,
|
||||||
|
"padding":[15,15,0,15],
|
||||||
|
"xAxis": {
|
||||||
|
"disableGrid": false,
|
||||||
|
"gridType":"dash",
|
||||||
|
"splitNumber":5,
|
||||||
|
"boundaryGap":"justify",
|
||||||
|
"min":0,
|
||||||
|
"max":250
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"disableGrid": false,
|
||||||
|
"gridType":"dash",
|
||||||
|
"data":[{
|
||||||
|
"min":0,
|
||||||
|
"max":150
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"bubble": {
|
||||||
|
"border":2,
|
||||||
|
"opacity": 0.5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cfu;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# uCharts JSSDK说明
|
||||||
|
1、如不使用uCharts组件,可直接引用u-charts.js,打包编译后会`自动压缩`,压缩后体积约为`120kb`。
|
||||||
|
2、如果120kb的体积仍需压缩,请手到uCharts官网通过在线定制选择您需要的图表。
|
||||||
|
3、config-ucharts.js为uCharts组件的用户配置文件,升级前请`自行备份config-ucharts.js`文件,以免被强制覆盖。
|
||||||
|
4、config-echarts.js为ECharts组件的用户配置文件,升级前请`自行备份config-echarts.js`文件,以免被强制覆盖。
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user