Compare commits
132
Commits
c6c3c820be
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7dbbfda16 | ||
|
|
fd78b504a6 | ||
|
|
a26fc1a4a7 | ||
|
|
5d31c3658b | ||
|
|
5f9a346960 | ||
|
|
7180e176eb | ||
|
|
e3a852cb43 | ||
|
|
0a51901807 | ||
|
|
8a879df4b4 | ||
|
|
18c7f7386d | ||
|
|
b5cc11ddd3 | ||
|
|
4a22470f38 | ||
|
|
6cdd8b90db | ||
|
|
cbc427e41c | ||
|
|
45a9478790 | ||
|
|
c6ce198c31 | ||
|
|
f4fe516224 | ||
|
|
80772749b2 | ||
|
|
0fa310e422 | ||
|
|
761a8fdaa4 | ||
|
|
ca69dfbe4b | ||
|
|
4042df6762 | ||
|
|
4e3a30b863 | ||
|
|
2831de027c | ||
|
|
08e3ba22c5 | ||
|
|
40f5ddf4a5 | ||
|
|
1fa822ed72 | ||
|
|
89848ba321 | ||
|
|
7fb1200528 | ||
|
|
b833a00d65 | ||
|
|
e8e9c84cdf | ||
|
|
1e99b3a082 | ||
|
|
efcc7d9603 | ||
|
|
54f8132cee | ||
|
|
ddbfac11c3 | ||
|
|
1de18aa228 | ||
|
|
799471090f | ||
|
|
ae9a961a42 | ||
|
|
db9ca707ae | ||
|
|
a7b2a673a9 | ||
|
|
d06ee397cf | ||
|
|
034744959f | ||
|
|
cf6df28e8c | ||
|
|
00d0ec8a9b | ||
|
|
56c645a75c | ||
|
|
8025b2abab | ||
|
|
c86aa03f01 | ||
|
|
22104881f1 | ||
|
|
f4c8df1950 | ||
|
|
1785e9309e | ||
|
|
da3659412d | ||
|
|
51367c48c9 | ||
|
|
41079458dc | ||
|
|
60531b339e | ||
|
|
9788e723a2 | ||
|
|
91726b87c5 | ||
|
|
6b37f0899d | ||
|
|
f4b1ef932d | ||
|
|
76e619b85d | ||
|
|
2cfc225b55 | ||
|
|
2cfa4bdc53 | ||
|
|
9e0a5d2512 | ||
|
|
589092ec62 | ||
|
|
1255ce8aa5 | ||
|
|
4dca51b0a3 | ||
|
|
8d9ad1ca51 | ||
|
|
e7cebcb00c | ||
|
|
dcade02e47 | ||
|
|
f7d44b9c27 | ||
|
|
d1a4f610d3 | ||
|
|
6b238c2d33 | ||
|
|
7c46a3487a | ||
|
|
38d13d33ce | ||
|
|
5d2d8cc9ff | ||
|
|
5af6f8c906 | ||
|
|
1e134f236c | ||
|
|
4816054b96 | ||
|
|
ea320f4e54 | ||
|
|
d44cd1516a | ||
|
|
2a1f4a2405 | ||
|
|
2532aa055c | ||
|
|
b16601baa6 | ||
|
|
8b1e9bcb41 | ||
|
|
dd9b2984b2 | ||
|
|
65b2d269c5 | ||
|
|
570e47698c | ||
|
|
71136a03a6 | ||
|
|
053063dc88 | ||
|
|
4703b22d25 | ||
|
|
df4d95993a | ||
|
|
36cda41b14 | ||
|
|
9e78b974a2 | ||
|
|
bdd2ba278e | ||
|
|
70eeb19867 | ||
|
|
ae08b222af | ||
|
|
b884a8f6fc | ||
|
|
215b3298d0 | ||
|
|
7ce2ad8d8e | ||
|
|
1ad67cf0dd | ||
|
|
d827caed33 | ||
|
|
1556ea2ec7 | ||
|
|
d40f65de65 | ||
|
|
c711c53959 | ||
|
|
ad39805b83 | ||
|
|
b8f6257bf1 | ||
|
|
8a38bafabe | ||
|
|
dc17a06548 | ||
|
|
a5b24e54b2 | ||
|
|
397cf0c88e | ||
|
|
9a26193ea4 | ||
|
|
4659f30400 | ||
|
|
e86f8e77a7 | ||
|
|
b068fb00cc | ||
|
|
84f59b20f6 | ||
|
|
f89d0e6295 | ||
|
|
1cc611d68d | ||
|
|
92bfc36d51 | ||
|
|
82e0a5c10d | ||
|
|
1f4c0834fa | ||
|
|
7270482bd9 | ||
|
|
c9dcab1e36 | ||
|
|
2542848ce7 | ||
|
|
da6e4ae551 | ||
|
|
cc0ddd0bc9 | ||
|
|
42ef4d312e | ||
|
|
58d9fb3d7a | ||
|
|
ef5b3fb59f | ||
|
|
b6120d9a40 | ||
|
|
d53228c901 | ||
|
|
ccd33df9e0 | ||
|
|
6a6ee5e195 | ||
|
|
0de0b6732b |
@@ -31,3 +31,4 @@ node_modules
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
components.d.ts
|
||||
.hbuilderx/*
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
changed 1 package, and audited 42 packages in 5s
|
||||
|
||||
7 packages are looking for funding
|
||||
run `npm fund` for details
|
||||
|
||||
3 high severity vulnerabilities
|
||||
|
||||
Some issues need review, and may require choosing
|
||||
a different dependency.
|
||||
|
||||
Run `npm audit` for details.
|
||||
@@ -1,53 +1,264 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
import version from '@/utils/version.js'
|
||||
export default {
|
||||
globalData: {
|
||||
plAd: true,
|
||||
dotNumber: {},
|
||||
apiHost: "https://dev-app.igandan.com/app",
|
||||
//BASE_URL='https://dev-app.igandan.com/app'
|
||||
},
|
||||
onLaunch(options) {
|
||||
//uni.setStorageSync('apiHost', 'value')
|
||||
console.log("打印options");
|
||||
console.log(options);
|
||||
if (
|
||||
options &&
|
||||
options.referrerInfo.extraData &&
|
||||
options.referrerInfo.extraData.token
|
||||
) {
|
||||
uni.setStorageSync(
|
||||
"AUTH_TOKEN_App",
|
||||
options.referrerInfo.extraData.token
|
||||
);
|
||||
uni.setStorageSync("userInfo", options.referrerInfo.extraData.userInfo);
|
||||
if(process.env.UNI_PLATFORM =="mp-weixin"){
|
||||
uni.setStorageSync("app_version",version);
|
||||
}else{
|
||||
let app_version=options.referrerInfo.extraData.app_version?options.referrerInfo.extraData.app_version:version;
|
||||
uni.setStorageSync("app_version",app_version);
|
||||
};
|
||||
|
||||
//getApp({allowDefault: true}).globalData.apiHost = options.referrerInfo.extraData.apiHost;
|
||||
}
|
||||
if (
|
||||
options &&
|
||||
options.referrerInfo.extraData &&
|
||||
options.referrerInfo.extraData.targetPath
|
||||
) {
|
||||
let src=options.referrerInfo.extraData.targetPath
|
||||
|
||||
if(src.indexOf('downLoadVideo') > -1 ){
|
||||
uni.navigateTo({
|
||||
url: src+'?from=mine',
|
||||
});
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: src,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("app传递登录信息onLaunch-------------------");
|
||||
try {
|
||||
// #ifdef APP
|
||||
uni.onNativeEventReceive((event, data) => {
|
||||
console.log("event", event);
|
||||
console.log("data", data);
|
||||
if (event.indexOf("clearToken") > -1) {
|
||||
uni.setStorageSync("AUTH_TOKEN_App", "");
|
||||
plus.runtime.quit();
|
||||
}
|
||||
if(event.indexOf("goPage") > -1){
|
||||
if(data.indexOf('downLoadVideo') > -1){
|
||||
uni.navigateTo({
|
||||
url: data+'?from=mine'
|
||||
});
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: data,
|
||||
});
|
||||
}
|
||||
}
|
||||
if(event.indexOf('paySuccess') > -1){
|
||||
console.log('app.vue监听到成功支付');
|
||||
if(data.type=='GandanFile'){
|
||||
uni.$emit('paySuccess',{
|
||||
type:'GandanFile',
|
||||
});
|
||||
}else if(data.type=='BuyPoint'){
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.log("app传递登录信息error-------------------");
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
try {
|
||||
// #ifdef APP
|
||||
let checkTokenCallBackFlag = false;
|
||||
console.log("执行onshow");
|
||||
|
||||
uni.sendNativeEvent(
|
||||
"checkToken",
|
||||
{
|
||||
msg: "checkToken",
|
||||
},
|
||||
(ret) => {
|
||||
checkTokenCallBackFlag = true;
|
||||
console.log("check回调");
|
||||
console.log(ret);
|
||||
if (ret.code == 0) {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
showCancel: false,
|
||||
content: "您已退出登录!",
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
uni.sendNativeEvent(
|
||||
"goTabbarPage",
|
||||
{
|
||||
msg: "home",
|
||||
},
|
||||
(ret) => {
|
||||
console.log(ret);
|
||||
}
|
||||
);
|
||||
plus.runtime.quit();
|
||||
} else if (res.cancel) {
|
||||
console.log("用户点击取消");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// setTimeout(function(){
|
||||
// console.log("执行 setTimeout")
|
||||
// if(!checkTokenCallBackFlag){
|
||||
// uni.showModal({
|
||||
// title: '提示',
|
||||
// showCancel: false,
|
||||
// content: '您已退出登录',
|
||||
// success: function (res) {
|
||||
// if (res.confirm) {
|
||||
// uni.sendNativeEvent('goTabbarPage', {
|
||||
// msg: 'home'
|
||||
// }, ret => {
|
||||
// console.log(ret);
|
||||
// })
|
||||
// } else if (res.cancel) {
|
||||
// console.log('用户点击取消');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }, 1000)
|
||||
|
||||
|
||||
let main = plus.android.runtimeMainActivity();
|
||||
//为了防止快速点按返回键导致程序退出重写quit方法改为隐藏至后台
|
||||
plus.runtime.quit = function () {
|
||||
main.moveTaskToBack(false);
|
||||
};
|
||||
//重写toast方法如果内容为 ‘再按一次退出应用’ 就隐藏应用,其他正常toast
|
||||
plus.nativeUI.toast = function (str) {
|
||||
if (str == "再按一次退出应用") {
|
||||
main.moveTaskToBack(false);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: str,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// uni.onNativeEventReceive((event, data) => {
|
||||
// console.log("app传递登录信息onshow-------------------");
|
||||
// console.log("event", event);
|
||||
// console.log("data", data);
|
||||
// if (event == "loginInfo") {
|
||||
// uni.setStorageSync("DEV_AUTH_YX_TOKEN_App", data.token);
|
||||
// uni.setStorageSync("userInfo", data.userInfo);
|
||||
// }else if(event == "showRedDot"){
|
||||
// console.log("showRedDot");
|
||||
// console.log(data);
|
||||
// getApp().globalData.dotNumber=data;
|
||||
// }
|
||||
// });
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.log("error");
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
onHide() {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<GlobalDialog />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
/*每个页面公共css */
|
||||
@import "@/uni_modules/uni-scss/index.scss";
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "@/static/customicons.css";
|
||||
|
||||
/*每个页面公共css */
|
||||
@import '@/uni_modules/uni-scss/index.scss';
|
||||
/* #ifndef APP-NVUE */
|
||||
@import '@/static/customicons.css';
|
||||
// 设置整个项目的背景色
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
// 设置整个项目的背景色
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-nav-bar-text{
|
||||
font-weight: bold;
|
||||
font-size: 34rpx!important;
|
||||
}
|
||||
.example-info {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
.twoline{
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
.uni-navbar__header-btns-right {
|
||||
min-width: 120rpx;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.oneline{
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.uni-navbar .uniui-left{
|
||||
font-weight: bold;
|
||||
font-size: 50rpx!important;
|
||||
}
|
||||
::-webkit-scrollbar { display: none; }
|
||||
/* #endif */
|
||||
.uni-nav-bar-text {
|
||||
|
||||
font-size: 40rpx !important;
|
||||
}
|
||||
|
||||
.example-info {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.twoline {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oneline {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uni-navbar .uniui-left {
|
||||
font-weight: bold;
|
||||
font-size: 50rpx !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status_bar {
|
||||
height: var(--status-bar-height);
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.navbox {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(var(--status-bar-height) + 44px);
|
||||
z-index: 9999;
|
||||
}
|
||||
.share-content .share-item:last-child{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
# 订单记录页面悬浮发票按钮
|
||||
|
||||
## 功能说明
|
||||
|
||||
在订单记录页面添加了一个悬浮的开具发票按钮,用户可以随时点击进入发票开具流程,无需返回首页。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 悬浮按钮设计
|
||||
- **位置**: 固定在页面右下角,距离右边距30rpx,距离底部120rpx
|
||||
- **尺寸**: 120rpx × 120rpx 的圆形按钮
|
||||
- **样式**: 渐变背景色(红色到橙色),带有阴影效果
|
||||
- **层级**: z-index: 999,确保按钮始终显示在最上层
|
||||
|
||||
### 2. 按钮内容
|
||||
- **图标**: 使用 `paperplane` 图标,表示发票功能
|
||||
- **文字**: "开具发票" 标签,清晰说明按钮功能
|
||||
- **布局**: 图标在上,文字在下,垂直居中对齐
|
||||
|
||||
### 3. 交互效果
|
||||
- **点击反馈**: 点击时有缩放和阴影变化效果
|
||||
- **过渡动画**: 所有状态变化都有平滑的过渡效果
|
||||
- **悬停效果**: 按钮具有现代化的视觉反馈
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 访问订单记录页面
|
||||
- 在课程相关页面中进入"订单记录"
|
||||
- 页面右下角会显示悬浮的发票按钮
|
||||
|
||||
### 2. 点击开具发票
|
||||
- 点击悬浮按钮
|
||||
- 自动跳转到发票选择页面
|
||||
- 选择要开票的课程后进入发票信息填写页面
|
||||
|
||||
### 3. 完成开票流程
|
||||
- 填写发票信息
|
||||
- 提交表单
|
||||
- 返回订单记录页面
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 按钮结构
|
||||
```vue
|
||||
<!-- 悬浮发票按钮 -->
|
||||
<view class="floating-invoice-btn" @click="goToInvoice">
|
||||
<uni-icons type="paperplane" size="24" color="#fff"></uni-icons>
|
||||
<text class="btn-text">开具发票</text>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 跳转方法
|
||||
```javascript
|
||||
// 跳转到开具发票页面
|
||||
const goToInvoice = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_course/invoice/invoice'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 样式特点
|
||||
- 使用 `position: fixed` 实现悬浮定位
|
||||
- 渐变背景色增强视觉效果
|
||||
- 阴影效果提升层次感
|
||||
- 响应式设计适配不同设备
|
||||
|
||||
## 页面流程
|
||||
|
||||
1. **订单记录页面** → 显示悬浮发票按钮
|
||||
2. **点击按钮** → 跳转到发票选择页面
|
||||
3. **选择课程** → 进入发票信息填写页面
|
||||
4. **填写信息** → 提交发票申请
|
||||
5. **返回页面** → 回到订单记录页面
|
||||
|
||||
## 设计优势
|
||||
|
||||
### 1. 用户体验
|
||||
- **便捷访问**: 无需返回首页,直接在订单页面即可开票
|
||||
- **视觉突出**: 悬浮按钮醒目,用户容易发现
|
||||
- **操作简单**: 一键点击即可进入开票流程
|
||||
|
||||
### 2. 界面设计
|
||||
- **现代化**: 渐变色彩和阴影效果符合现代UI设计趋势
|
||||
- **一致性**: 与整体页面风格保持一致
|
||||
- **响应性**: 支持触摸反馈和动画效果
|
||||
|
||||
### 3. 功能完整
|
||||
- **流程完整**: 从订单记录到发票开具的完整流程
|
||||
- **页面跳转**: 正确的路由配置和页面跳转
|
||||
- **状态管理**: 按钮状态和页面状态的有效管理
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 悬浮按钮使用固定定位,不会随页面滚动而移动
|
||||
2. 按钮层级较高,确保在各种情况下都能正常显示
|
||||
3. 支持触摸反馈,提供良好的移动端体验
|
||||
4. 按钮位置经过精心设计,不会遮挡页面内容
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以添加按钮的显示/隐藏控制
|
||||
2. 可以增加按钮的动画效果
|
||||
3. 可以添加按钮的权限控制
|
||||
4. 可以优化按钮在不同设备上的显示效果
|
||||
5. 可以添加按钮的使用统计功能
|
||||
@@ -0,0 +1,83 @@
|
||||
# 精品课开具发票页面
|
||||
|
||||
## 功能说明
|
||||
|
||||
这是一个精品课开具发票的页面,完全按照设计图100%还原实现。
|
||||
|
||||
## 页面特性
|
||||
|
||||
### 1. 状态栏
|
||||
- 显示时间 "9:41"
|
||||
- 显示信号、WiFi、电池等状态图标
|
||||
|
||||
### 2. 导航栏
|
||||
- 左侧:红色返回按钮
|
||||
- 中间:红色标题 "开具发票"
|
||||
- 右侧:红色圆形信息按钮
|
||||
|
||||
### 3. 标签页
|
||||
- "待开发票" - 当前激活状态
|
||||
- "开票历史" - 可切换查看
|
||||
- 标签页之间有灰色分隔线
|
||||
|
||||
### 4. 课程列表
|
||||
- 显示5个课程项目
|
||||
- 每个项目包含:
|
||||
- 圆形复选框(可选中/取消)
|
||||
- 课程缩略图(使用医生头像占位图)
|
||||
- 课程描述文字
|
||||
- 实际支付价格(红色显示)
|
||||
- 默认第3个课程被选中
|
||||
|
||||
### 5. 底部操作栏
|
||||
- 左侧:全选复选框和"全选"文字
|
||||
- 中间:显示开票金额和已选课程数量
|
||||
- 右侧:红色"开具发票"按钮
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 访问页面
|
||||
在首页功能网格中点击"开具发票"按钮,即可跳转到发票页面。
|
||||
|
||||
### 2. 选择课程
|
||||
- 点击单个课程的复选框来选中/取消选中
|
||||
- 点击底部的"全选"复选框来全选/取消全选所有课程
|
||||
|
||||
### 3. 开具发票
|
||||
- 选择要开票的课程后,底部会显示总金额
|
||||
- 点击"开具发票"按钮进行开票操作
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
pages_course/
|
||||
└── invoice/
|
||||
└── invoice.vue # 发票页面主文件
|
||||
```
|
||||
|
||||
### 主要功能
|
||||
- 响应式布局,适配不同屏幕尺寸
|
||||
- 复选框状态管理
|
||||
- 动态计算选中课程数量和总金额
|
||||
- 页面路由配置已添加到 pages.json
|
||||
|
||||
### 样式特点
|
||||
- 使用红色作为主色调(#ff0000)
|
||||
- 橙色作为选中状态颜色(#ff6b35)
|
||||
- 现代化的UI设计,包含圆角、阴影等效果
|
||||
- 响应式设计,支持不同设备
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 页面使用了自定义导航栏样式
|
||||
2. 课程图片使用了占位图 `/static/placeholder_doctor.png`
|
||||
3. 页面已配置为无导航栏模式(navigationStyle: "custom")
|
||||
4. 支持返回上一页功能
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以添加真实的课程数据接口
|
||||
2. 可以增加发票类型选择功能
|
||||
3. 可以添加发票信息填写表单
|
||||
4. 可以增加开票记录查询功能
|
||||
@@ -0,0 +1,119 @@
|
||||
# 发票页面设计优化
|
||||
|
||||
## 优化概述
|
||||
|
||||
对发票页面进行了全面的视觉设计优化,提升了用户体验和界面美观度。
|
||||
|
||||
## 主要优化内容
|
||||
|
||||
### 1. 整体视觉风格
|
||||
- **渐变背景**: 从浅灰色到白色的渐变背景,增加层次感
|
||||
- **现代化设计**: 采用卡片式设计,增加阴影和圆角
|
||||
- **色彩统一**: 使用橙色系主色调(#ff6b35 到 #ff8c42)
|
||||
|
||||
### 2. 导航栏优化
|
||||
- **渐变背景**: 添加微妙的渐变背景
|
||||
- **阴影效果**: 增加底部阴影,提升层次感
|
||||
- **交互反馈**: 按钮点击时有缩放和背景色变化
|
||||
- **标题样式**: 使用渐变文字效果,更加醒目
|
||||
|
||||
### 3. 标签页优化
|
||||
- **活跃状态**: 活跃标签使用圆角胶囊背景
|
||||
- **渐变效果**: 橙色渐变背景,白色文字
|
||||
- **底部指示器**: 渐变色的底部指示条
|
||||
- **悬停效果**: 非活跃标签悬停时显示橙色
|
||||
|
||||
### 4. 开票历史记录优化
|
||||
- **卡片设计**: 每个记录项都是独立的卡片
|
||||
- **左侧装饰条**: 橙色渐变装饰条,增加视觉层次
|
||||
- **渐变背景**: 微妙的渐变背景
|
||||
- **阴影效果**: 柔和的阴影,提升立体感
|
||||
- **状态标签**: 状态标签使用渐变背景和阴影
|
||||
- **交互反馈**: 点击时有轻微的下移和阴影变化
|
||||
|
||||
## 设计特点
|
||||
|
||||
### 1. 色彩搭配
|
||||
- **主色调**: 橙色系(#ff6b35, #ff8c42)
|
||||
- **辅助色**: 绿色(成功)、红色(失败)、蓝色(处理中)
|
||||
- **中性色**: 灰色系用于文字和边框
|
||||
|
||||
### 2. 视觉效果
|
||||
- **渐变**: 多处使用渐变效果,增加现代感
|
||||
- **阴影**: 不同层级的阴影,营造立体感
|
||||
- **圆角**: 统一的圆角设计,更加友好
|
||||
- **过渡**: 所有交互都有平滑的过渡动画
|
||||
|
||||
### 3. 交互体验
|
||||
- **点击反馈**: 按钮和卡片都有点击反馈
|
||||
- **悬停效果**: 标签页有悬停状态
|
||||
- **动画过渡**: 平滑的状态切换动画
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. CSS特性
|
||||
```scss
|
||||
// 渐变背景
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
|
||||
// 渐变文字
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
// 阴影效果
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
// 过渡动画
|
||||
transition: all 0.3s ease;
|
||||
```
|
||||
|
||||
### 2. 伪元素装饰
|
||||
```scss
|
||||
// 左侧装饰条
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6rpx;
|
||||
background: linear-gradient(180deg, #ff6b35 0%, #ff8c42 100%);
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 状态样式
|
||||
```scss
|
||||
// 成功状态
|
||||
&.success {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
## 用户体验提升
|
||||
|
||||
### 1. 视觉层次
|
||||
- 清晰的信息层级
|
||||
- 合理的间距和留白
|
||||
- 突出的重要信息
|
||||
|
||||
### 2. 交互反馈
|
||||
- 即时的视觉反馈
|
||||
- 平滑的动画过渡
|
||||
- 直观的操作提示
|
||||
|
||||
### 3. 美观度
|
||||
- 现代化的设计风格
|
||||
- 协调的色彩搭配
|
||||
- 精致的细节处理
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **深色模式**: 可以添加深色主题支持
|
||||
2. **动画效果**: 可以增加更多的微交互动画
|
||||
3. **响应式**: 可以优化不同屏幕尺寸的显示
|
||||
4. **无障碍**: 可以增加无障碍访问支持
|
||||
5. **主题定制**: 可以支持用户自定义主题色彩
|
||||
@@ -0,0 +1,185 @@
|
||||
# 开票历史功能
|
||||
|
||||
## 功能说明
|
||||
|
||||
在发票页面添加了"开票历史"标签页,用户可以查看历史开票记录,包括开票状态、课程数量、开票金额等信息。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 标签页切换
|
||||
- **待开发票**: 显示可选择的课程列表,支持批量选择和开票
|
||||
- **开票历史**: 显示历史开票记录,包含详细的开票信息
|
||||
|
||||
### 2. 开票历史记录
|
||||
- **日期显示**: 显示开票日期(如:2021年6月11日)
|
||||
- **课程数量**: 显示该次开票包含的课程数量(如:共计3个课程)
|
||||
- **开票状态**: 显示开票的当前状态
|
||||
- **开票金额**: 显示开票的金额信息
|
||||
|
||||
### 3. 开票状态类型
|
||||
- **已开票**: 绿色显示,表示开票成功
|
||||
- **开票失败**: 红色显示,表示开票失败
|
||||
- **开票中**: 蓝色显示,表示正在处理中
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 1. 标签页导航
|
||||
```vue
|
||||
<view class="tabs">
|
||||
<view class="tab-item" :class="{ active: activeTab === 'pending' }">
|
||||
<text>待开发票</text>
|
||||
</view>
|
||||
<view class="tab-divider"></view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 'history' }">
|
||||
<text>开票历史</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 内容区域
|
||||
- **待开发票**: 显示课程列表和底部操作栏
|
||||
- **开票历史**: 显示历史记录列表
|
||||
|
||||
### 3. 历史记录项
|
||||
```vue
|
||||
<view class="history-item" v-for="item in historyList">
|
||||
<view class="history-left">
|
||||
<text class="history-date">{{ item.date }}</text>
|
||||
<text class="history-count">{{ item.courseCount }}</text>
|
||||
</view>
|
||||
<view class="history-right">
|
||||
<text class="history-status">{{ item.statusText }}</text>
|
||||
<view class="history-arrow">›</view>
|
||||
<text class="history-amount">{{ item.amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 1. 历史记录数据
|
||||
```javascript
|
||||
historyList: [
|
||||
{
|
||||
id: 1,
|
||||
date: '2021年6月11日',
|
||||
courseCount: '共计3个课程',
|
||||
status: 'success',
|
||||
statusText: '已开票',
|
||||
amount: '¥0.00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2021年6月11日',
|
||||
courseCount: '共计3个课程',
|
||||
status: 'failed',
|
||||
statusText: '开票失败',
|
||||
amount: '¥0.00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
date: '2021年6月11日',
|
||||
courseCount: '共计3个课程',
|
||||
status: 'processing',
|
||||
statusText: '开票中',
|
||||
amount: '¥0.00'
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 2. 状态枚举
|
||||
- `success`: 已开票(绿色)
|
||||
- `failed`: 开票失败(红色)
|
||||
- `processing`: 开票中(蓝色)
|
||||
|
||||
## 交互功能
|
||||
|
||||
### 1. 标签页切换
|
||||
- 点击"待开发票"标签显示课程选择界面
|
||||
- 点击"开票历史"标签显示历史记录界面
|
||||
- 标签页之间有视觉分隔线
|
||||
|
||||
### 2. 历史记录查看
|
||||
- 点击任意历史记录项可查看详情
|
||||
- 显示相应的状态提示信息
|
||||
- 支持后续扩展详情页面
|
||||
|
||||
### 3. 状态颜色区分
|
||||
- 不同状态使用不同颜色显示
|
||||
- 提供清晰的视觉反馈
|
||||
- 符合用户认知习惯
|
||||
|
||||
## 样式设计
|
||||
|
||||
### 1. 历史记录项样式
|
||||
- 浅灰色背景(#f8f8f8)
|
||||
- 圆角设计(8rpx)
|
||||
- 卡片式布局,带有间距
|
||||
- 底部边框分隔线
|
||||
|
||||
### 2. 状态颜色
|
||||
- 成功状态:绿色(#52c41a)
|
||||
- 失败状态:红色(#ff4d4f)
|
||||
- 处理中状态:蓝色(#1890ff)
|
||||
- 默认状态:灰色(#666666)
|
||||
|
||||
### 3. 布局特点
|
||||
- 左右分布,信息清晰
|
||||
- 响应式设计,适配不同屏幕
|
||||
- 触摸友好的交互区域
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 查看开票历史
|
||||
- 在发票页面点击"开票历史"标签
|
||||
- 浏览历史开票记录
|
||||
- 查看开票状态和详细信息
|
||||
|
||||
### 2. 切换标签页
|
||||
- 点击"待开发票"返回课程选择界面
|
||||
- 点击"开票历史"查看历史记录
|
||||
- 标签页状态实时更新
|
||||
|
||||
### 3. 查看记录详情
|
||||
- 点击任意历史记录项
|
||||
- 系统显示相应的状态信息
|
||||
- 支持后续功能扩展
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 条件渲染
|
||||
```vue
|
||||
<view v-if="activeTab === 'pending'" class="pending-content">
|
||||
<!-- 待开发票内容 -->
|
||||
</view>
|
||||
<view v-if="activeTab === 'history'" class="history-content">
|
||||
<!-- 开票历史内容 -->
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 数据绑定
|
||||
- 使用Vue的响应式数据绑定
|
||||
- 支持动态数据更新
|
||||
- 状态切换实时响应
|
||||
|
||||
### 3. 事件处理
|
||||
```javascript
|
||||
viewHistoryDetail(item) {
|
||||
console.log('查看开票历史详情:', item)
|
||||
uni.showToast({
|
||||
title: `查看${item.statusText}详情`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **详情页面**: 可以添加开票历史详情页面
|
||||
2. **状态筛选**: 可以添加按状态筛选历史记录
|
||||
3. **时间筛选**: 可以添加按时间范围筛选
|
||||
4. **搜索功能**: 可以添加搜索特定开票记录
|
||||
5. **导出功能**: 可以添加导出开票历史记录
|
||||
6. **分页加载**: 可以添加分页加载更多历史记录
|
||||
7. **实时更新**: 可以添加开票状态的实时更新
|
||||
8. **通知功能**: 可以添加开票完成的通知提醒
|
||||
@@ -0,0 +1,95 @@
|
||||
# 填写发票信息页面
|
||||
|
||||
## 功能说明
|
||||
|
||||
这是一个填写发票信息的页面,完全按照设计图100%还原实现。用户在选择课程后,通过此页面填写详细的发票信息。
|
||||
|
||||
## 页面特性
|
||||
|
||||
### 1. 状态栏
|
||||
- 显示时间 "9:41"
|
||||
- 显示信号、WiFi、电池等状态图标
|
||||
|
||||
### 2. 导航栏
|
||||
- 左侧:红色返回按钮
|
||||
- 中间:红色标题 "填写发票信息"
|
||||
- 右侧:红色圆形信息按钮
|
||||
|
||||
### 3. 表单内容
|
||||
- **发票抬头**: 输入框,必填项
|
||||
- **单位税号**: 输入框,必填项
|
||||
- **发票内容**: 显示固定值 "培训费"
|
||||
- **发票金额**: 显示固定值 "¥59.00"(红色显示)
|
||||
- **电子邮箱**: 输入框,必填项
|
||||
|
||||
### 4. 提交按钮
|
||||
- 底部固定位置的青色按钮
|
||||
- 白色文字 "提交"
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 访问页面
|
||||
在发票页面选择课程后,点击"开具发票"按钮即可跳转到此页面。
|
||||
|
||||
### 2. 填写信息
|
||||
- 发票抬头:输入公司或个人名称
|
||||
- 单位税号:输入税务登记号
|
||||
- 电子邮箱:输入接收发票的邮箱地址
|
||||
|
||||
### 3. 提交表单
|
||||
- 点击"提交"按钮
|
||||
- 系统会验证必填字段和邮箱格式
|
||||
- 提交成功后自动返回上一页
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
pages_course/
|
||||
└── invoice_info/
|
||||
└── invoice_info.vue # 发票信息填写页面
|
||||
```
|
||||
|
||||
### 主要功能
|
||||
- 表单验证(必填字段、邮箱格式)
|
||||
- 响应式布局设计
|
||||
- 自定义导航栏
|
||||
- 表单数据双向绑定
|
||||
|
||||
### 样式特点
|
||||
- 使用红色作为主色调(#ff0000)
|
||||
- 青色作为提交按钮颜色(#20b2aa)
|
||||
- 白色卡片式表单设计
|
||||
- 浅灰色分隔线
|
||||
- 现代化UI设计,包含圆角、阴影等效果
|
||||
|
||||
## 表单验证规则
|
||||
|
||||
1. **发票抬头**: 必填,不能为空
|
||||
2. **单位税号**: 必填,不能为空
|
||||
3. **电子邮箱**: 必填,必须符合邮箱格式
|
||||
|
||||
## 页面流程
|
||||
|
||||
1. 用户在发票页面选择课程
|
||||
2. 点击"开具发票"按钮
|
||||
3. 跳转到填写发票信息页面
|
||||
4. 填写必要信息
|
||||
5. 点击提交
|
||||
6. 验证通过后返回上一页
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 页面使用了自定义导航栏样式
|
||||
2. 发票内容和金额为固定值,不可编辑
|
||||
3. 页面已配置为无导航栏模式(navigationStyle: "custom")
|
||||
4. 支持返回上一页功能
|
||||
5. 表单验证失败会显示相应的提示信息
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 可以添加发票类型选择功能
|
||||
2. 可以增加发票抬头历史记录
|
||||
3. 可以添加发票预览功能
|
||||
4. 可以增加发票模板选择
|
||||
5. 可以添加发票状态查询功能
|
||||
@@ -0,0 +1,117 @@
|
||||
# 会议页面更新说明
|
||||
|
||||
## 更新内容
|
||||
|
||||
### 1. API集成
|
||||
- 集成了真实的会议列表API `meetingListV2U`
|
||||
- 替换了原有的模拟数据
|
||||
- 添加了完整的错误处理机制
|
||||
|
||||
### 2. 数据处理
|
||||
- 将API返回的数据转换为页面所需的格式
|
||||
- 支持分页加载(下拉刷新和上拉加载更多)
|
||||
- 根据会议状态动态生成标签颜色
|
||||
- 支持按月份动态筛选会议数据
|
||||
- 支持月份和地点组合筛选
|
||||
- 统一的搜索筛选接口
|
||||
|
||||
### 3. 会议状态显示
|
||||
- 预告 (status: 1) - 橙色标签
|
||||
- 直播中 (status: 2) - 红色标签
|
||||
- 已结束 (status: 3) - 灰色标签
|
||||
|
||||
### 4. 图片处理
|
||||
- 支持相对路径和绝对URL的图片
|
||||
- 自动添加基础URL前缀
|
||||
- 提供默认占位符图片
|
||||
|
||||
### 5. 交互功能
|
||||
- 会议已结束:点击跳转到详情页
|
||||
- 会议进行中/预告:点击跳转到直播页面
|
||||
- 支持webview页面跳转
|
||||
|
||||
## API数据结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"pageSize": 10,
|
||||
"isFirstPage": true,
|
||||
"isLastPage": true,
|
||||
"pageNum": 1,
|
||||
"pages": 1,
|
||||
"list": [
|
||||
{
|
||||
"title": "会议标题",
|
||||
"begin_date": "2023-09-26 18:30:00",
|
||||
"end_date": "2025-09-26 22:30:00",
|
||||
"liveimg": "live/img/2021/20211202152116.png",
|
||||
"liveurl": "http://zhibo.igandan.com/watch/1633941",
|
||||
"status": 3,
|
||||
"location": "线上",
|
||||
"id": 41,
|
||||
"path": "https://dev-wx.igandan.com/conference/conference_info.htm?id=41"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 主要函数
|
||||
|
||||
### `loadMeetingList()`
|
||||
- 获取会议列表数据
|
||||
- 处理分页逻辑
|
||||
- 数据格式转换
|
||||
|
||||
### `loadMeetingListByMonth(monthValue)`
|
||||
- 按月份筛选会议列表
|
||||
- 支持时间范围查询
|
||||
- 自动计算月份起止时间
|
||||
|
||||
### `meetingListBySearchU(filters)`
|
||||
- 统一的搜索和筛选接口
|
||||
- 支持月份和地点组合筛选
|
||||
- 自动构建筛选参数
|
||||
- 支持分页加载
|
||||
- API参数:
|
||||
- `month`: 月份筛选(格式:9,表示9月份;选择"所有"时传空字符串)
|
||||
- `location`: 地点筛选(省份名称,如:北京市、广东省;选择"全国"时传空字符串)
|
||||
- `page`: 页码
|
||||
|
||||
### `getTagColor(status, beginDate)`
|
||||
- 根据会议状态和日期生成标签颜色
|
||||
- 支持动态颜色生成
|
||||
|
||||
### `generateMonthList()`
|
||||
- 动态生成月份列表(当前月份往后12个月)
|
||||
- 智能标签显示(本月、下月、年份月份)
|
||||
- 支持跨年月份计算
|
||||
|
||||
### `getImageUrl(imagePath)`
|
||||
- 处理图片URL
|
||||
- 支持相对路径和绝对URL
|
||||
|
||||
### `playVideo(item)`
|
||||
- 处理会议点击事件
|
||||
- 根据状态跳转不同页面
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 需要确保 `api.meetingListV2U` 函数已正确定义
|
||||
2. 图片基础URL可能需要根据实际环境调整
|
||||
3. webview页面路径需要确保存在
|
||||
4. 建议添加默认的会议海报占位符图片
|
||||
5. 月份筛选功能使用 `month` 参数(格式:9,表示9月份)
|
||||
6. 地点筛选功能使用 `location` 参数(省份名称,如:北京市、广东省等)
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 添加会议搜索功能
|
||||
2. 支持按时间、地点筛选
|
||||
3. 添加会议收藏功能
|
||||
4. 优化图片加载性能
|
||||
5. 添加会议提醒功能
|
||||
@@ -1,272 +1,29 @@
|
||||
# 精品课页面功能改进完成
|
||||
# TODO List
|
||||
|
||||
## ✅ 已完成功能
|
||||
## 已完成任务
|
||||
- [x] 调用API里的指南标签接口并集成到筛选功能
|
||||
- [x] 为指南列表页面添加scroll-view的上拉加载和下拉刷新功能
|
||||
- [x] 为指南列表标题添加超过2行显示省略号功能
|
||||
- [x] 修复PPT页面的上拉加载功能
|
||||
- [x] 修复页码递增逻辑问题
|
||||
- [x] 添加详细的调试日志
|
||||
- [x] 添加测试按钮用于手动触发
|
||||
- [x] 修复排序功能
|
||||
|
||||
### 🎯 **主横幅板块升级**
|
||||
1. **多个横幅图片** - 从单个图片改为5个轮播横幅
|
||||
2. **自动滑动功能** - 使用swiper组件实现自动轮播
|
||||
3. **丰富的内容展示** - 每个横幅包含标题、副标题、专家信息等
|
||||
4. **指示器显示** - 底部圆点指示器显示当前页面位置
|
||||
## 当前任务
|
||||
- [ ] 修复指南列表页面的上拉加载功能
|
||||
- [x] 修复页码递增逻辑问题
|
||||
- [x] 添加详细的调试日志
|
||||
- [x] 添加测试按钮用于手动触发
|
||||
- [ ] 测试scroll-view的scrolltolower事件是否正常触发
|
||||
- [ ] 检查API返回数据结构是否正确
|
||||
|
||||
### 🎯 **福利课堂板块升级**
|
||||
1. **多个福利项目** - 从单个横幅改为5个福利项目
|
||||
2. **左右滑动功能** - 使用scroll-view实现横向滚动
|
||||
3. **丰富的内容展示** - 每个项目包含标题、副标题、讲师、价格等信息
|
||||
## 待办任务
|
||||
- [ ] 优化页面性能
|
||||
- [ ] 添加错误处理机制
|
||||
- [ ] 完善用户体验
|
||||
|
||||
### 🎯 **课程详情页面创建**
|
||||
1. **100%还原设计图** - 完全按照图片设计实现
|
||||
2. **完整的页面结构** - 包含所有设计元素和功能
|
||||
3. **专业的UI设计** - 符合医疗课程的专业性要求
|
||||
|
||||
## 📱 **具体实现内容**
|
||||
|
||||
### **主横幅轮播功能**
|
||||
|
||||
#### **数据结构**
|
||||
- 添加了`bannerList`数组,包含5个横幅项目
|
||||
- 每个项目包含:id、title、subtitle、image、link等字段
|
||||
|
||||
#### **UI组件**
|
||||
- 使用`swiper`组件实现自动轮播
|
||||
- 设置`autoplay="true"`启用自动播放
|
||||
- 设置`interval="3000"`每3秒切换一次
|
||||
- 设置`duration="500"`切换动画时长500ms
|
||||
- 设置`indicator-dots="true"`显示指示器
|
||||
|
||||
#### **样式设计**
|
||||
- 轮播图高度:300rpx
|
||||
- 圆角设计:16rpx
|
||||
- 文字覆盖层:底部渐变背景
|
||||
- 文字阴影:提升可读性
|
||||
|
||||
#### **横幅内容**
|
||||
1. **小懂医生讲HIV和感染** - 黄湛镰 副主任医师
|
||||
2. **肝脏肿瘤临床影像学习** - 王学浩 教授
|
||||
3. **慢性肝病营养治疗指南** - 段钟平 教授
|
||||
4. **肝移植术后管理要点** - 郑树森 院士
|
||||
5. **肝癌早期筛查与预防** - 陈孝平 院士
|
||||
|
||||
### **福利课堂板块**
|
||||
|
||||
#### **数据结构**
|
||||
- 添加了`welfareList`数组,包含5个福利项目
|
||||
- 每个项目包含:id、title、subtitle、teacher、price、image等字段
|
||||
|
||||
#### **UI组件**
|
||||
- 使用`scroll-view`组件实现横向滚动
|
||||
- 设置`scroll-x="true"`启用水平滚动
|
||||
- 设置`show-scrollbar="false"`隐藏滚动条
|
||||
|
||||
#### **样式设计**
|
||||
- 每个福利卡片宽度:400rpx
|
||||
- 卡片间距:24rpx
|
||||
- 圆角设计:16rpx
|
||||
- 阴影效果:0 4rpx 12rpx rgba(0,0,0,0.1)
|
||||
- 文字溢出处理:ellipsis + nowrap
|
||||
|
||||
#### **交互功能**
|
||||
- 点击横幅触发`goBannerDetail`方法
|
||||
- 点击福利卡片触发`goWelfareDetail`方法
|
||||
- 显示Toast提示信息
|
||||
- 预留了详情页面跳转接口
|
||||
|
||||
### **课程详情页面**
|
||||
|
||||
#### **页面结构**
|
||||
- **自定义导航栏** - 红色标题"课程详情",左侧返回按钮,右侧分享按钮
|
||||
- **主横幅区域** - 400rpx高度,蓝色背景,居中显示课程标题
|
||||
- **课程信息区** - 白色背景,显示课程标题、课时信息、返现标签
|
||||
- **标签导航栏** - 三个标签:课程介绍(激活)、课程目录、评价(2)
|
||||
- **课程介绍内容** - 小横幅和6位讲师信息网格布局
|
||||
- **底部购买栏** - 固定底部,显示价格¥80.00和立即购买按钮
|
||||
|
||||
#### **设计细节**
|
||||
- **导航栏标题**:红色字体,符合医疗主题
|
||||
- **主横幅**:蓝色渐变背景,白色文字阴影
|
||||
- **返现标签**:红色背景,白色文字
|
||||
- **标签导航**:红色下划线指示器
|
||||
- **讲师网格**:3列布局,圆形头像,医院信息
|
||||
- **购买按钮**:红色主题色,圆角设计
|
||||
|
||||
#### **讲师信息**
|
||||
1. **王建设 教授** - 复旦大学附属儿童医院
|
||||
2. **黄燕 教授** - 中南大学湘雅医院
|
||||
3. **田沂 教授** - 中南大学湘雅二医院
|
||||
4. **李教授** - 知名医院
|
||||
5. **张教授** - 知名医院
|
||||
6. **刘教授** - 知名医院
|
||||
|
||||
## 🖼️ **图片资源**
|
||||
- 使用现有的static目录图片
|
||||
- 包括:lunbo_bg.png、paper_bg.png、pap_bg.png、bo_bg.png、livebg.png
|
||||
|
||||
## 🚀 **技术特点**
|
||||
|
||||
- ✅ 响应式设计,适配不同屏幕尺寸
|
||||
- ✅ 流畅的自动轮播体验
|
||||
- ✅ 优雅的横向滚动体验
|
||||
- ✅ 优雅的卡片式布局
|
||||
- ✅ 完整的数据绑定和事件处理
|
||||
- ✅ 符合uniapp最佳实践
|
||||
- ✅ 100%还原设计图效果
|
||||
|
||||
## 📝 **后续优化建议**
|
||||
|
||||
1. **添加分页指示器** - 显示当前滚动位置
|
||||
2. **实现手动滑动** - 支持用户手动滑动轮播图
|
||||
3. **添加加载动画** - 提升用户体验
|
||||
4. **图片懒加载** - 优化性能
|
||||
5. **缓存机制** - 减少重复请求
|
||||
6. **轮播图暂停** - 触摸时暂停自动播放
|
||||
7. **课程目录功能** - 实现课程目录标签页
|
||||
8. **评价系统** - 实现评价标签页功能
|
||||
9. **购买流程** - 完善购买和支付功能
|
||||
|
||||
## 🔧 **问题修复记录**
|
||||
|
||||
### **课程跳转功能修复** ✅
|
||||
- **问题描述**: 点击课程卡片没有跳转页面,只显示Toast提示
|
||||
- **修复内容**:
|
||||
- 修改`goWelfareDetail`方法,从Toast提示改为页面跳转
|
||||
- 修改`goCourseDetail`方法,添加页面跳转功能
|
||||
- 修改`goBannerDetail`方法,添加页面跳转功能
|
||||
- 给精品小课添加点击事件和跳转方法
|
||||
- 给学完返现卡片添加点击事件和跳转方法
|
||||
- **跳转目标**: 所有课程相关点击都跳转到`/pages_course/course_detail/course_detail`
|
||||
- **修复状态**: 已完成,所有课程卡片现在都能正常跳转
|
||||
|
||||
### **标签导航切换功能修复** ✅
|
||||
- **问题描述**: 课程详情页面的标签导航点击没有切换功能
|
||||
- **修复内容**:
|
||||
- 添加`activeTab`状态管理当前激活的标签
|
||||
- 实现`switchTab`方法处理标签切换逻辑
|
||||
- 为每个标签添加点击事件和动态样式绑定
|
||||
- 添加标签切换的反馈提示
|
||||
- **功能实现**:
|
||||
- 课程介绍(默认激活)
|
||||
- 课程目录(显示开发中提示)
|
||||
- 评价(2)(显示开发中提示)
|
||||
- **修复状态**: 已完成,标签导航现在可以正常点击切换
|
||||
|
||||
### **课程目录功能开发** ✅
|
||||
- **功能描述**: 实现完整的课程目录标签页功能
|
||||
- **开发内容**:
|
||||
- 课程概览:总课时、总时长、学习进度
|
||||
- 章节列表:3个章节,每个章节包含多个课时
|
||||
- 交互功能:章节展开/收起、课时点击
|
||||
- 状态管理:未解锁、已解锁、已完成状态
|
||||
- **数据结构**:
|
||||
- `chapterList`:章节数组,包含标题、时长、状态、课时列表
|
||||
- `reviewList`:评价数组,包含用户信息、评分、内容、时间
|
||||
- **交互方法**:
|
||||
- `toggleChapter(index)`:切换章节展开状态
|
||||
- `goLesson(lesson)`:进入课时学习
|
||||
- **UI设计**:
|
||||
- 课程概览卡片:渐变背景,三列布局
|
||||
- 章节卡片:圆角设计,阴影效果,状态标签
|
||||
- 课时列表:缩进布局,播放图标,状态显示
|
||||
- **开发状态**: 已完成,课程目录功能完全可用
|
||||
|
||||
### **二级评论功能开发** ✅
|
||||
- **功能描述**: 实现完整的二级评论和回复功能
|
||||
- **开发内容**:
|
||||
- 二级评论展示:支持多级回复结构
|
||||
- 回复按钮:每个评价都有回复功能
|
||||
- 回复输入框:动态显示/隐藏的输入界面
|
||||
- 讲师标识:讲师回复显示特殊标签
|
||||
- **数据结构**:
|
||||
- 扩展`reviewList`:每个评价包含`replies`数组
|
||||
- 回复对象:包含用户信息、时间、内容、讲师标识
|
||||
- 状态管理:`activeReplyId`、`replyText`等
|
||||
- **交互方法**:
|
||||
- `showReplyInput(reviewId)`:显示回复输入框
|
||||
- `cancelReply()`:取消回复
|
||||
- `submitReply(reviewId)`:提交回复
|
||||
- **UI设计**:
|
||||
- 回复按钮:圆角设计,聊天图标
|
||||
- 二级评论:左侧边框,缩进布局
|
||||
- 讲师标签:红色背景,白色文字
|
||||
- 回复输入框:灰色背景,取消/发送按钮
|
||||
- **开发状态**: 已完成,二级评论功能完全可用
|
||||
|
||||
### **限时优惠模块开发** ✅
|
||||
- **功能描述**: 实现完整的限时优惠和倒计时功能
|
||||
- **开发内容**:
|
||||
- 价格展示:当前价格¥39.00,原价¥390.00(划线显示)
|
||||
- 优惠标签:白色背景的"限时优惠"标签
|
||||
- 倒计时功能:实时倒计时显示剩余时间
|
||||
- 自动更新:每秒更新倒计时数据
|
||||
- **数据结构**:
|
||||
- `countdown`:包含天、时、分、秒的倒计时对象
|
||||
- `countdownTimer`:倒计时定时器引用
|
||||
- **交互方法**:
|
||||
- `startCountdown()`:启动倒计时,计算剩余时间
|
||||
- 自动清理:页面卸载时清除定时器
|
||||
- **UI设计**:
|
||||
- 渐变背景:红色到橙色的渐变效果
|
||||
- 左侧布局:价格信息和优惠标签(占2/3宽度)
|
||||
- 右侧布局:倒计时文字和时间显示(占1/3宽度)
|
||||
- 时间盒子:橙色背景的时分秒显示框
|
||||
- **开发状态**: 已完成,限时优惠功能完全可用
|
||||
|
||||
### **课程评论页面开发** ✅
|
||||
- **功能描述**: 创建专门的课程评价提交页面
|
||||
- **开发内容**:
|
||||
- 自定义导航栏:红色"评价课程"标题,左侧返回按钮
|
||||
- 星级评分系统:5颗星星,点击选择评分
|
||||
- 动态反馈文字:根据评分显示对应的评价反馈
|
||||
- 评价输入框:300字限制,多行提示文字
|
||||
- 字数统计:实时显示已输入字数/总字数
|
||||
- 提交按钮:青色主题色,圆角设计
|
||||
- 奖励信息:显示积分奖励说明
|
||||
- **具体实现**:
|
||||
- **评分系统**:1-5星评分,对应不同反馈文字
|
||||
- **输入验证**:必须选择评分和填写内容才能提交
|
||||
- **页面跳转**:从课程详情页"评价"标签页进入
|
||||
- **返回逻辑**:提交成功后自动返回上一页
|
||||
- **数据结构**:
|
||||
- `currentRating`:当前选择的星级评分
|
||||
- `reviewContent`:评价内容文本
|
||||
- `ratingFeedback`:评分对应的反馈文字
|
||||
- **交互方法**:
|
||||
- `setRating(rating)`:设置星级评分
|
||||
- `updateRatingFeedback()`:更新评分反馈文字
|
||||
- `submitReview()`:提交评价内容
|
||||
- `goToReview()`:跳转到评论页面
|
||||
- **UI设计**:
|
||||
- 导航栏:红色标题,符合医疗主题
|
||||
- 星星图标:48rpx大小,黄色填充,灰色未填充
|
||||
- 输入框:浅灰色背景,圆角设计,300rpx高度
|
||||
- 提交按钮:青色背景,白色文字,88rpx高度
|
||||
- **开发状态**: 已完成,课程评论功能完全可用
|
||||
|
||||
### **我的课程页面开发** ✅
|
||||
- **功能描述**: 创建完整的"我的课程"页面,包含标签切换和课程管理
|
||||
- **开发内容**:
|
||||
- 自定义导航栏:红色"我的课程"标题,左侧返回按钮,右侧搜索按钮
|
||||
- 标签切换功能:学习中/已学完两个标签,带分隔线设计
|
||||
- 课程列表展示:课程图片、标题、课时信息、学习进度
|
||||
- 特殊标签显示:福利课堂、学完返现等特殊标识
|
||||
- 订单记录浮动按钮:青色圆形按钮,固定位置显示
|
||||
- 底部导航栏:精品课(未激活)、我的课程(激活状态)
|
||||
- **具体实现**:
|
||||
- **标签系统**:默认激活"学习中"标签,点击切换显示不同课程列表
|
||||
- **课程数据结构**:包含id、标题、课时数、状态、已学课时、图片、标签等
|
||||
- **响应式布局**:课程卡片自适应,文字溢出处理,标签动态显示
|
||||
- **交互功能**:点击课程跳转详情页,点击标签切换内容,底部导航跳转
|
||||
- **数据结构**:
|
||||
- `activeTab`:当前激活的标签(learning/completed)
|
||||
- `courseList`:按标签分组的课程数据
|
||||
- `currentCourseList`:计算属性,根据当前标签返回对应课程列表
|
||||
- **交互方法**:
|
||||
- `switchTab(tab)`:切换标签,更新课程列表显示
|
||||
- `goCourseDetail(course)`:跳转到课程详情页面
|
||||
- `goOrderRecord()`:进入订单记录功能
|
||||
- `goPremiumCourses()`:跳转到精品课页面
|
||||
- **UI设计**:
|
||||
- 导航栏:红色主题色,符合医疗应用风格
|
||||
- 标签设计:居中布局,分隔线分隔,激活状态红色高亮
|
||||
- 课程卡片:白色背景,圆角设计,阴影效果,左侧图片右侧信息
|
||||
- 浮动按钮:青色背景,圆形图标,固定位置,阴影效果
|
||||
- 底部导航:红色激活状态,图标+文字布局
|
||||
- **开发状态**: 已完成,"我的课程"页面功能完全可用
|
||||
## 技术债务
|
||||
- [ ] 代码重构和优化
|
||||
- [ ] 添加单元测试
|
||||
- [ ] 文档更新
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"prompt" : "none"
|
||||
}
|
||||
+697
-5
@@ -1,5 +1,10 @@
|
||||
import {request} from '@/utils/request.js'
|
||||
|
||||
const api = {
|
||||
|
||||
expertWxLogin(jscode){
|
||||
return request('/miniprogram/expertLogin', {jscode: jscode}, 'get', true);
|
||||
},
|
||||
// 微信登录
|
||||
wxLogin(data) {
|
||||
return request('/login/wechat/mobile', data, 'post', true);
|
||||
@@ -19,27 +24,714 @@ const api = {
|
||||
smsLogin(data,header){
|
||||
return request('/expertAPI/smsLogin', data, 'post', true,'application/json',header);
|
||||
},
|
||||
|
||||
// 密码登录
|
||||
pwdLogin(data){
|
||||
return request('/expertAPI/login', data, 'post', true);
|
||||
},
|
||||
|
||||
// 获取短信验证码
|
||||
getCode(data,header) {
|
||||
return request('/expertAPI/smsSend', data, 'post', true,'application/json',header);
|
||||
},
|
||||
getHomeData(){
|
||||
return request('/expertAPI/index', {}, 'post', false);
|
||||
},
|
||||
//肝胆课件列表
|
||||
ganDanFileByKeyWords(data){
|
||||
return request('/expertAPI/ganDanFileByKeyWords', data, 'post', false);
|
||||
},
|
||||
//肝胆课件详情
|
||||
ganDanFileDetials(data){
|
||||
return request('/expertAPI/ganDanFileDetials', data, 'post', false);
|
||||
},
|
||||
//是否存在正在进行中的公益咨询
|
||||
isConsultIng(data){
|
||||
return request('/expertAPI/isConsultIng', data, 'post', false);
|
||||
},
|
||||
//视频浏览记录列表
|
||||
videoWatchRecord(data){
|
||||
return request('/expertAPI/VideoWatchRecord', data, 'post', false);
|
||||
},
|
||||
//修改备注和描述
|
||||
updateNicknameNote(data){
|
||||
return request('/expertAPI/updateNicknameNote', data, 'post', false);
|
||||
},
|
||||
//获取未读数量
|
||||
unReadList(data){
|
||||
return request('/expertAPI/unReadList', data, 'post', false);
|
||||
},
|
||||
//消息列表
|
||||
appMesageList(data){
|
||||
return request('/expertAPI/appMesageList', data, 'post', false);
|
||||
},
|
||||
//阅读消息
|
||||
appMesageRead(data){
|
||||
return request('/expertAPI/appMesageRead', data, 'post', false);
|
||||
},
|
||||
//阅读消息
|
||||
appMesageRead(data){
|
||||
return request('/expertAPI/appMesageRead', data, 'post', false);
|
||||
},
|
||||
//最近30天的随访记录
|
||||
relationRecordLately(data){
|
||||
return request('/expertAPI/relationRecordLately', data, 'post', false);
|
||||
},
|
||||
//我的页面(信息与统计)
|
||||
getMyInfo(data){
|
||||
return request('/expertAPI/my', data, 'post', false);
|
||||
},
|
||||
|
||||
// 视频相关API
|
||||
getVideoList(data) {
|
||||
return request('/video/list', data, 'get', true);
|
||||
},
|
||||
getVideoDetail(data) {
|
||||
return request('/video/detail', data, 'get', true);
|
||||
},
|
||||
getBannerVideo(data) {
|
||||
return request('/video/banner', data, 'get', true);
|
||||
getVideoHistory(data) {
|
||||
return request('/expertAPI/VideoWatchRecord', data, 'post', false);
|
||||
},
|
||||
deleteRecord(data) {
|
||||
return request('/expertAPI/deleteRecord', data, 'post', false);
|
||||
},
|
||||
|
||||
getCollectList(data) {
|
||||
return request('/expertAPI/collectList', data, 'post', false);
|
||||
},
|
||||
|
||||
getCollectionList(data) {
|
||||
return request('/expertAPI/collectionList', data, 'post', false);
|
||||
},
|
||||
//我的账户
|
||||
getMyAccount(data) {
|
||||
return request('/expertPay/getBalanceU', data, 'post', false);
|
||||
},
|
||||
|
||||
// 订单列表
|
||||
getOrderList(data) {
|
||||
return request('/expertPay/orderList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 我的积分
|
||||
myBonusPoints(data) {
|
||||
return request('/expertAPI/myBonusPoints', data, 'post', false);
|
||||
},
|
||||
// 新闻详情
|
||||
getNewsDetail(data) {
|
||||
return request('/expertAPI/newsDetail', data, 'post', false);
|
||||
},
|
||||
// 积分支付列表
|
||||
bonusPointsPayList(data) {
|
||||
return request('/expertAPI/bonusPointsPayList', data, 'post', true);
|
||||
},
|
||||
// 积分列表
|
||||
bonusPointsList(data) {
|
||||
return request('/expertAPI/bonusPointsList', data, 'post', true);
|
||||
},
|
||||
|
||||
getWelfarePage(data) {
|
||||
return request('/expertAPI/getWelfarePage', data, 'post', false);
|
||||
},
|
||||
|
||||
useWelfarePage(data) {
|
||||
return request('/expertAPI/useWelfarePage', data, 'post', false);
|
||||
},
|
||||
|
||||
getFlowerList(data) {
|
||||
return request('/expertPay/getFlowerList', data, 'post', false);
|
||||
},
|
||||
|
||||
getGandanfileMyDownload(data) {
|
||||
return request('/expertAPI/gandanfile/myDownload', data, 'post', false);
|
||||
},
|
||||
|
||||
getGandanfileMyShare(data) {
|
||||
return request('/expertAPI/gandanfile/myShare', data, 'post', false);
|
||||
},
|
||||
|
||||
// 课件详情
|
||||
getGandanFileDetail(data) {
|
||||
return request('/expertAPI/gandanFileDetail', data, 'post', false);
|
||||
},
|
||||
|
||||
// 直播详情
|
||||
getLiveDetail(data) {
|
||||
return request('/expertAPI/liveDetail', data, 'post', false);
|
||||
},
|
||||
|
||||
// 视频详情
|
||||
getVideoDetail2(data) {
|
||||
return request('/expertAPI/videoDetail2', data, 'post', false);
|
||||
},
|
||||
|
||||
// 搜索医院列表
|
||||
getHospitalList(data) {
|
||||
return request('/expertAPI/hospitalList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 省市地区列表
|
||||
getAreaList(data) {
|
||||
return request('/expertAPI/areaList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 积分商城相关API
|
||||
// 商品列表
|
||||
getGoodsList(data) {
|
||||
return request('/expertAPI/goodsList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 商品列表_V2
|
||||
getGoodsListV2(data) {
|
||||
return request('/expertAPI/goodsListV2', data, 'post', false);
|
||||
},
|
||||
|
||||
// 积分商城商品类型列表
|
||||
getGoodsTagList(data) {
|
||||
return request('/expertAPI/goodsTagList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 积分商城-查询快递100
|
||||
getOrderTrack(data) {
|
||||
return request('/expertAPI/getOrderTrack', data, 'post', false);
|
||||
},
|
||||
|
||||
// 福利版块相关API
|
||||
// 我的福利卡
|
||||
getMyWelfareCard(data) {
|
||||
return request('/expertAPI/myWelfareCard', data, 'post', false);
|
||||
},
|
||||
|
||||
// 兑换福利卡
|
||||
exchangeWelfareCard(data) {
|
||||
return request('/expertAPI/exchangeWelfareCard', data, 'post', false);
|
||||
},
|
||||
|
||||
// U盘福利剩余个数
|
||||
getUpanWelfareCount(data) {
|
||||
return request('/expertAPI/getUpanWelfareCount', data, 'post', false);
|
||||
},
|
||||
|
||||
// 添加积分
|
||||
addBonusPointsN(data) {
|
||||
return request('/expertAPI/addBonusPointsN', data, 'post', false);
|
||||
},
|
||||
|
||||
//视频顶部轮播
|
||||
videoRoll(data){
|
||||
return request('/expertAPI/videoRoll', data, 'post', false);
|
||||
},
|
||||
//视频标签
|
||||
videoTagList(data){
|
||||
return request('/expertAPI/videoTagList', data, 'post', false);
|
||||
},
|
||||
//视频首页
|
||||
videoIndexN(data){
|
||||
return request('/expertAPI/videoIndexN', data, 'post', false);
|
||||
},
|
||||
//视频类型
|
||||
expertVideoTypeList(data){
|
||||
return request('/expertAPI/expertVideoTypeList', data, 'post', false);
|
||||
},
|
||||
//视频搜索
|
||||
videoByKeyWordsNew(data){
|
||||
return request('/expertAPI/videoByKeyWordsNew', data, 'post', false);
|
||||
},
|
||||
|
||||
// 指南杂志 - 治疗指南列表
|
||||
searchLibraryU(data){
|
||||
return request('/expertAPI/searchLibraryU', data, 'post', false);
|
||||
},
|
||||
// 指南杂志 - 治疗指南最火TOP10
|
||||
top10ByType(data){
|
||||
return request('/expertAPI/top10ByType', data, 'post', false);
|
||||
},
|
||||
// 指南杂志 - 治疗指南分类
|
||||
guideType(data){
|
||||
return request('/expertAPI/guideType', data, 'post', false);
|
||||
},
|
||||
//指南标签
|
||||
guideTag(data){
|
||||
return request('/expertAPI/tagList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 会议列表
|
||||
meetingListV2U(data){
|
||||
return request('/expertAPI/meetingListV2U', data, 'post', false);
|
||||
},
|
||||
|
||||
// 会议列表
|
||||
meetingListBySearchU(data){
|
||||
return request('/expertAPI/meetingListBySearchU', data, 'post', false);
|
||||
},
|
||||
|
||||
myWelfareCard(data){
|
||||
return request('/expertAPI/myWelfareCard', data, 'post', false);
|
||||
},
|
||||
|
||||
exchangeWelfareCard(data){
|
||||
return request('/expertAPI/exchangeWelfareCard', data, 'post', false);
|
||||
},
|
||||
|
||||
unReadList(data){
|
||||
return request('/expertAPI/unReadList', data, 'post', false);
|
||||
},
|
||||
|
||||
appMesageList(data){
|
||||
return request('/expertAPI/appMesageList', data, 'post', false);
|
||||
},
|
||||
|
||||
appMesageRead(data){
|
||||
return request('/expertAPI/appMesageRead', data, 'post', false);
|
||||
},
|
||||
|
||||
// bankCardList(data){
|
||||
// return request('/pingExpertV2/bankCardList', data, 'post', false);
|
||||
// },
|
||||
|
||||
identificationBankCardNew(data){
|
||||
return request('/pingExpertV2/identificationBankCardNew', data, 'post', false);
|
||||
},
|
||||
|
||||
changePassword(data){
|
||||
return request('/expertAPI/modifyPwd', data, 'post', false);
|
||||
},
|
||||
|
||||
changeMobile(data){
|
||||
return request('/expertAPI/modifyMobile', data, 'post', false);
|
||||
},
|
||||
|
||||
smsSend(data){
|
||||
return request('/expertAPI/smsSend', data, 'post', false);
|
||||
},
|
||||
pointTicketlist(data){
|
||||
return request('/expertAPI/pointTicketlist', data, 'post', false);
|
||||
},
|
||||
|
||||
pointTicketExchange(data){
|
||||
return request('/expertAPI/updateStatus', data, 'post', false);
|
||||
},
|
||||
|
||||
addFeedBack(data){
|
||||
return request('/expertAPI/addFeedBack', data, 'post', false);
|
||||
},
|
||||
// 肝胆新闻相关API
|
||||
// 顶部轮播
|
||||
newsRollNew(data){
|
||||
return request('/expertAPI/newsRollNew', data, 'post', false);
|
||||
},
|
||||
// 新闻列表
|
||||
defaultNewsListNew(data){
|
||||
return request('/expertAPI/defaultNewsListNew', data, 'post', false);
|
||||
},
|
||||
// 根据标签查询新闻列表
|
||||
newsListNew(data){
|
||||
return request('/expertAPI/newsListNew', data, 'post', false);
|
||||
},
|
||||
// 新闻标签列表
|
||||
newsTagList(data){
|
||||
return request('/expertAPI/newsTagList', data, 'post', false);
|
||||
},
|
||||
|
||||
meetingListV2U(data){
|
||||
return request('/expertAPI/meetingListV2U', data, 'post', false);
|
||||
},
|
||||
applyList(data){
|
||||
return request('/expertAPI/applyList', data, 'post', false);
|
||||
},
|
||||
patientListByGBKPage(data){
|
||||
return request('/expertAPI/patientListByGBKPage', data, 'post', false);
|
||||
},
|
||||
patientListByGBK(data){
|
||||
return request('/expertAPI/patientListByGBK', data, 'post', false);
|
||||
},
|
||||
patientListByGBKU(data){
|
||||
return request('/expertAPI/patientListByGBKU', data, 'post', false);
|
||||
},
|
||||
followUpList(data){
|
||||
return request('/expertAPI/followUpList', data, 'post', false);
|
||||
},
|
||||
relationRecordLately(data){
|
||||
return request('/expertAPI/relationRecordLately', data, 'post', false);
|
||||
},
|
||||
applyListOperate(data){
|
||||
return request('/expertAPI/applyListOperate', data, 'post', false);
|
||||
},
|
||||
groupList(data){
|
||||
return request('/expertAPI/groupListU', data, 'post', false);
|
||||
},
|
||||
patientListByGroup(data){
|
||||
return request('/expertAPI/patientListByGroupU', data, 'post', false);
|
||||
},
|
||||
groupDelete(data){
|
||||
return request('/expertAPI/deleteGroupU', data, 'post', false);
|
||||
},
|
||||
groupAdd(data){
|
||||
return request('/expertAPI/addGroupU', data, 'post', false);
|
||||
},
|
||||
groupUpdate(data){
|
||||
return request('/expertAPI/updateGroupU', data, 'post', false);
|
||||
},
|
||||
updateNicknameNote(data){
|
||||
return request('/expertAPI/updateNicknameNote', data, 'post', false);
|
||||
},
|
||||
patientCard(data){
|
||||
return request('/expertAPI/patientCard', data, 'post', false);
|
||||
},
|
||||
patientCardUpdateGroup(data){
|
||||
return request('/expertAPI/patientCardUpdateGroup', data, 'post', false);
|
||||
},
|
||||
addFollowUps(data){
|
||||
return request('/expertAPI/addFollowUps', data, 'post', false);
|
||||
},
|
||||
getFollowUp(data){
|
||||
return request('/expertAPI/getFollowUp', data, 'post', false);
|
||||
},
|
||||
deleteFollowUp(data){
|
||||
return request('/expertAPI/deleteFollowUp', data, 'post', false);
|
||||
},
|
||||
cancelRes(data){
|
||||
return request('/expertAPI/cancelRes', data, 'post', false);
|
||||
},
|
||||
polularScienceArticleListByKeywordsNew(data){
|
||||
return request('/expertAPI/polularScienceArticleListByKeywordsNew', data, 'post', false);
|
||||
},
|
||||
patientVideoByKeyWordsNew(data){
|
||||
return request('/expertAPI/patientVideoByKeyWordsNew', data, 'post', false);
|
||||
},
|
||||
patientVideoNew(data){
|
||||
return request('/expertAPI/patientVideoNew', data, 'post', false);
|
||||
},
|
||||
top10ByType(data){
|
||||
return request('/expertAPI/top10ByType', data, 'post', false);
|
||||
},
|
||||
videoBySearchNew(data){
|
||||
return request('/expertAPI/videoBySearchNew', data, 'post', false);
|
||||
},
|
||||
patientVideoBySearchNew(data){
|
||||
return request('/expertAPI/patientVideoBySearchNew', data, 'post', false);
|
||||
},
|
||||
polularScienceArticleListIndexNew(data){
|
||||
return request('/expertAPI/polularScienceArticleListIndexNew', data, 'post', false);
|
||||
},
|
||||
|
||||
bankCardList(data){
|
||||
return request('/expertPay/bankCardList', data, 'post', false);
|
||||
},
|
||||
|
||||
deleteBankCard(data){
|
||||
return request('/expertPay/deleteBankCard', data, 'post', false);
|
||||
},
|
||||
getIncomeTax(data){
|
||||
return request('/expertPay/getIncomeTax', data, 'post', false);
|
||||
},
|
||||
createWithdrawal(data){
|
||||
return request('/expertPay/createWithdrawal', data, 'post', false);
|
||||
},
|
||||
|
||||
meetingListBySearch(data){
|
||||
return request('/expertAPI/meetingListBySearch', data, 'post', false);
|
||||
},
|
||||
getExpertByUuid(data){
|
||||
return request('/expertAPI/getExpertByUuid', data, 'post', false);
|
||||
},
|
||||
getUserIcon(data){
|
||||
return request('/expertAPI/getUserIcon', data, 'post', false);
|
||||
},
|
||||
addUserIcon(data){
|
||||
return request('/expertAPI/addUserIcon', data, 'post', false);
|
||||
},
|
||||
getDiseaseList(data){
|
||||
return request('/expertAPI/disease', data, 'post', false);
|
||||
},
|
||||
getPositionList(data){
|
||||
return request('/expertAPI/positionList', data, 'post', false);
|
||||
},
|
||||
getOfficeList(data){
|
||||
return request('/expertAPI/officeList', data, 'post', false);
|
||||
},
|
||||
newConsultList(data){
|
||||
return request('/expertAPI/newConsultList', data, 'post', false);
|
||||
},
|
||||
consultListHis(data){
|
||||
return request('/expertAPI/consultListHis', data, 'post', false);
|
||||
},
|
||||
listNewInterrogation(data){
|
||||
return request('/expertAPI/listNewInterrogation', data, 'post', true);
|
||||
},
|
||||
listMyAnsweredInterrogation(data){
|
||||
return request('/expertAPI/listMyAnsweredInterrogation', data, 'post', true);
|
||||
},
|
||||
getInterrogation(data){
|
||||
return request('/expertAPI/getInterrogation', data, 'post', false);
|
||||
},
|
||||
interrogationPatientInfo(data){
|
||||
return request('/expertAPI/InterrogationPatientInfo', data, 'post', false);
|
||||
},
|
||||
updateInterrogationAnswer(data){
|
||||
return request('/expertAPI/updateInterrogationAnswerU', data, 'post', true);
|
||||
},
|
||||
addInterrogationAnswer(data){
|
||||
return request('/expertAPI/addInterrogationAnswerU', data, 'post', true);
|
||||
},
|
||||
videoDetail(data){
|
||||
return request('/expertAPI/videoDetail', data, 'post', false);
|
||||
},
|
||||
addVideoWatchRecord(data){
|
||||
return request('/expertAPI/addVideoWatchRecord', data, 'post', false);
|
||||
},
|
||||
videoCommentListV2(data){
|
||||
return request('/expertAPI/videoCommentListV2', data, 'post', false);
|
||||
},
|
||||
isVideoDownloadRecord(data){
|
||||
return request('/expertAPI/isVideoDownloadRecord', data, 'post', false);
|
||||
},
|
||||
getWelfareNum(data){
|
||||
return request('/expertAPI/getWelfareNum', data, 'post', false);
|
||||
},
|
||||
payVideoDownload(data){
|
||||
return request('/expertAPI/payVideoDownload', data, 'post', false);
|
||||
},
|
||||
addCommentV2(data){
|
||||
return request('/expertAPI/addCommentV2', data, 'post', false);
|
||||
},
|
||||
collection(data){
|
||||
return request('/expertAPI/collection', data, 'post', false);
|
||||
},
|
||||
discollection(data){
|
||||
return request('/expertAPI/discollection', data, 'post', false);
|
||||
},
|
||||
meetingV2Video(data){
|
||||
return request('/expertAPI/meetingV2Video', data, 'post', false);
|
||||
},
|
||||
listGroupSendMsg(data){
|
||||
return request('/expertAPI/listGroupSendMsg', data, 'post', false);
|
||||
},
|
||||
addGroupSendMsg4YunXin(data){
|
||||
return request('/expertAPI/addGroupSendMsg4YunXin', data, 'post', false);
|
||||
},
|
||||
quickReplyList(data){
|
||||
return request('/expertAPI/QuickReplyList', data, 'post', false);
|
||||
},
|
||||
addQuickReply(data){
|
||||
return request('/expertAPI/addQuickReply', data, 'post', false);
|
||||
},
|
||||
deleteQuickReply(data){
|
||||
return request('/expertAPI/deleteQuickReply', data, 'post', false);
|
||||
},
|
||||
listOutPatient(data){
|
||||
return request('/expertAPI/listOutPatient', data, 'post', false);
|
||||
},
|
||||
stopOutPatientList(data){
|
||||
return request('/expertAPI/stopOutPatientList', data, 'post', false);
|
||||
},
|
||||
listWorkPlace(data){
|
||||
return request('/expertAPI/listWorkPlace', data, 'post', false);
|
||||
},
|
||||
addOutPatient(data){
|
||||
return request('/expertAPI/addOutPatient', data, 'post', false);
|
||||
},
|
||||
updateOutPatient(data){
|
||||
return request('/expertAPI/updateOutPatientA', data, 'post', false);
|
||||
},
|
||||
deleteOutPatient(data){
|
||||
return request('/expertAPI/deleteOutPatient', data, 'post', false);
|
||||
},
|
||||
addWorkPlace(data){
|
||||
return request('/expertAPI/addWorkPlace', data, 'post', false);
|
||||
},
|
||||
updateWorkPlace(data){
|
||||
return request('/expertAPI/editWorkPlace', data, 'post', false);
|
||||
},
|
||||
deleteWorkPlace(data){
|
||||
return request('/expertAPI/deleteWorkPlace', data, 'post', false);
|
||||
},
|
||||
hospitalList(data){
|
||||
return request('/expertAPI/hospitalList', data, 'post', false);
|
||||
},
|
||||
addStopPatient(data){
|
||||
return request('/expertAPI/addStopOutPatient', data, 'post', false);
|
||||
},
|
||||
deleteStopPatient(data){
|
||||
return request('/expertAPI/deleteStopOutPatient', data, 'post', false);
|
||||
},
|
||||
addOutPatientA(data){
|
||||
return request('/expertAPI/addOutPatientA', data, 'post', false);
|
||||
},
|
||||
delGroupSendMsg(data){
|
||||
return request('/expertAPI/delGroupSendMsg', data, 'post', false);
|
||||
},
|
||||
patientDetail(data){
|
||||
return request('/expertAPI/patientDetail', data, 'post', false);
|
||||
},
|
||||
toAddNickname(data){
|
||||
return request('/expertAPI/toAddNickname', data, 'post', false);
|
||||
},
|
||||
caseDetail(data){
|
||||
return request('/expertAPI/caseDetail', data, 'post', false);
|
||||
},
|
||||
conditionRecordList(data){
|
||||
return request('/expertAPI/conditionRecordList', data, 'post', false);
|
||||
},
|
||||
addConditionRecord(data){
|
||||
return request('/expertAPI/addConditionRecord', data, 'post', false);
|
||||
},
|
||||
updateConditionRecord(data){
|
||||
return request('/expertAPI/upConditionRecord', data, 'post', false);
|
||||
},
|
||||
deleteConditionRecord(data){
|
||||
return request('/expertAPI/delConditionRecord', data, 'post', false);
|
||||
},
|
||||
consultDetail(data){
|
||||
return request('/expertAPI/consultDetail', data, 'post', false);
|
||||
},
|
||||
isConsultIng(data){
|
||||
return request('/expertAPI/isConsultIng', data, 'post', false);
|
||||
},
|
||||
resConsult(data){
|
||||
return request('/expertAPI/resConsult', data, 'post', false);
|
||||
},
|
||||
countConsult(data){
|
||||
return request('/expertAPI/countConsult', data, 'post', false);
|
||||
},
|
||||
closeConsult(data){
|
||||
return request('/expertAPI/closeConsult', data, 'post', false);
|
||||
},
|
||||
checkUser(data){
|
||||
return request('/expertAPI/checkUser', data, 'post', false);
|
||||
},
|
||||
ganDanFileDetials(data){
|
||||
return request('/expertAPI/ganDanFileDetials', data, 'post', false);
|
||||
},
|
||||
createGanDanFileOrder(data){
|
||||
return request('/expertPay/createGanDanFileOrder', data, 'post', false);
|
||||
},
|
||||
getOrderStatus(data){
|
||||
return request('/expertPay/getOrderStatus', data, 'post', false);
|
||||
},
|
||||
|
||||
getOrderStatus(data){
|
||||
return request('/expertPay/getOrderStatus', data, 'post', false);
|
||||
},
|
||||
getBalance(data){
|
||||
return request('/expertPay/getBalance', data, 'post', false);
|
||||
},
|
||||
payGanDanFile(data){
|
||||
return request('/expertPay/payGanDanFileOrder', data, 'post', false);
|
||||
},
|
||||
// discollection(data){
|
||||
// return request('/expert/discollection', data, 'post', false);
|
||||
// },
|
||||
// collection(data){
|
||||
// return request('/expert/collection', data, 'post', false);
|
||||
// },
|
||||
getTypeUuidByName(data){
|
||||
return request('/expertAPI/getTypeUuidByName', data, 'post', false);
|
||||
},
|
||||
videoByTypeNew(data){
|
||||
return request('/expertAPI/videoByTypeNew', data, 'post', false);
|
||||
},
|
||||
meetingHistoryList(data){
|
||||
return request('/expertAPI/meetingHistoryList', data, 'post', false);
|
||||
},
|
||||
|
||||
// 扫码登录相关API
|
||||
// 确认扫码登录
|
||||
confirmScanLogin(data){
|
||||
return request('/expertAPI/confirmScanLogin', data, 'post', false);
|
||||
},
|
||||
|
||||
// 取消扫码登录
|
||||
cancelScanLogin(data){
|
||||
return request('/expertAPI/cancelScanLogin', data, 'post', false);
|
||||
},
|
||||
|
||||
// 获取扫码登录状态
|
||||
getScanLoginStatus(data){
|
||||
return request('/expertAPI/getScanLoginStatus', data, 'post', false);
|
||||
},
|
||||
gethonorDetail(data){
|
||||
return request('/expertAPI/gethonorDetail', data, 'post', false);
|
||||
},
|
||||
checkVersion(data){
|
||||
return request('/expertAPI/checkVersion', data, 'post', false);
|
||||
},
|
||||
getIsAccount(data){
|
||||
return request('/expertAPI/getIsAccount', data, 'post', false);
|
||||
},
|
||||
patientVideoByJingHuaNew(data){
|
||||
return request('/expertAPI/patientVideoByJingHuaNew', data, 'post', false);
|
||||
},
|
||||
useWelfareNum(data){
|
||||
return request('/expertAPI/useWelfareNum', data, 'post', false);
|
||||
},
|
||||
deleteComment(data){
|
||||
return request('/expertAPI/deleteComment', data, 'post', false);
|
||||
},
|
||||
downloadGanDanFile(data){
|
||||
return request('/expertAPI/downloadGanDanFile', data, 'post', false,'application/json',{},'arraybuffer');
|
||||
},
|
||||
downloadGanDanFileV2(data){
|
||||
return request('/expertAPI/downloadGanDanFileV2', data, 'post', false);
|
||||
},
|
||||
feedBack(data){
|
||||
return request('/expertAPI/addFeedBack', data, 'post', false);
|
||||
},
|
||||
getPointUnitPrice(data){
|
||||
return request('/expertAPI/getPointUnitPrice', data, 'post', false);
|
||||
},
|
||||
welfareList(data){
|
||||
return request('/expertAPI/welfareList', data, 'post', false);
|
||||
},
|
||||
|
||||
getUGoods(data){
|
||||
return request('/expertAPI/getGoodsDetailU', data, 'post', false);
|
||||
},
|
||||
createGoodsOrder(data){
|
||||
return request('/expertAPI/createGoodsOrder', data, 'post', false);
|
||||
},
|
||||
useWelfareNum(data){
|
||||
return request('/expertAPI/useWelfareNum', data, 'post', false);
|
||||
},
|
||||
createBointsOrder(data){
|
||||
return request('/expertPay/createBointsOrder', data, 'post', false);
|
||||
},
|
||||
receiveWelfare(data){
|
||||
return request('/expertAPI/receiveWelfare', data, 'post', false);
|
||||
},
|
||||
getKePuCollection(data){
|
||||
return request('/expertAPI/getKePuCollection', data, 'post', false);
|
||||
},
|
||||
getRead(data){
|
||||
return request('/expertAPI/read', data, 'post', false);
|
||||
},
|
||||
getOutRead(data){
|
||||
return request('/expertAPI/outread', data, 'post', false);
|
||||
},
|
||||
getAgree(data){
|
||||
return request('/expertAPI/agree', data, 'post', false);
|
||||
},
|
||||
getDisagree(data){
|
||||
return request('/expertAPI/disagree', data, 'post', false);
|
||||
},
|
||||
getCollect(data){
|
||||
return request('/expertAPI/collection', data, 'post', false);
|
||||
},
|
||||
getDiscollect(data){
|
||||
return request('/expertAPI/discollection', data, 'post', false);
|
||||
},
|
||||
getGanDanFileOrder(data){
|
||||
return request('/expertPay/getGanDanFileOrder', data, 'post', false);
|
||||
},
|
||||
publishOutPatient(data){
|
||||
return request('/expertAPI/addGroupSendMsg4YunXin', data, 'post', false);
|
||||
},
|
||||
polularScienceArticleListIndexNew(data){
|
||||
return request('/expertAPI/polularScienceArticleListIndexNew', data, 'post', false);
|
||||
},
|
||||
getNewsDetail(data){
|
||||
return request('/expertAPI/newsDetial', data, 'post', false);
|
||||
},
|
||||
|
||||
//https://dev-app.igandan.com/app/expertPay/getGanDanFileOrder
|
||||
|
||||
}
|
||||
|
||||
export default api
|
||||
+69
-2
@@ -22,8 +22,35 @@ const course_api = {
|
||||
},
|
||||
|
||||
// 创建订单
|
||||
createExcellencourseMixedOrder(id, order_pay_type) {
|
||||
return request('/expertPay/createExcellencourseOrder', {appid:"wx061c1f4e16a5f20f", openid:"", excellencourse_id: id, order_pay_type: order_pay_type}, 'post', true);
|
||||
createExcellencourseMixedOrder(id, order_pay_type, openid) {
|
||||
let appid = "wx061c1f4e16a5f20f"
|
||||
if (process.env.UNI_PLATFORM == "h5") {
|
||||
appid = "wx061c1f4e16a5f20f"
|
||||
} else if(process.env.UNI_PLATFORM == "mp-weixin") {
|
||||
appid = "wx061c1f4e16a5f20f"
|
||||
}else{
|
||||
//app
|
||||
appid = "wxbf3658f5e674667c"
|
||||
}
|
||||
|
||||
return request('/expertPay/createExcellencourseOrder', {appid:appid, openid: openid, excellencourse_id: id, order_pay_type: order_pay_type}, 'post', true);
|
||||
},
|
||||
|
||||
payExcellencourseOrderContinue(order_id, openid) {
|
||||
let appid = "wx061c1f4e16a5f20f"
|
||||
if (process.env.UNI_PLATFORM == "h5") {
|
||||
appid = "wx061c1f4e16a5f20f"
|
||||
} else if(process.env.UNI_PLATFORM == "mp-weixin") {
|
||||
appid = "wx061c1f4e16a5f20f"
|
||||
}else{
|
||||
//app
|
||||
appid = "wxbf3658f5e674667c"
|
||||
}
|
||||
return request('/expertPay/payExcellencourseOrderContinue', {order_id: order_id, appid:appid, openid: openid}, 'post', true);
|
||||
},
|
||||
|
||||
cancelExcellencourseOrder(order_id) {
|
||||
return request('/expertPay/cancelExcellencourseOrder', {excellencourse_order_id: order_id}, 'post', true);
|
||||
},
|
||||
|
||||
// 订单列表
|
||||
@@ -31,6 +58,46 @@ const course_api = {
|
||||
return request('/expertAPI/listExcellencourseOrder', {order_status: order_status, page:page}, 'post', true);
|
||||
},
|
||||
|
||||
// 我的课程
|
||||
listMyExcellencourse(state, page) {
|
||||
return request('/expertAPI/listMyExcellencourse', {state:state,page:page}, 'post', true);
|
||||
},
|
||||
|
||||
// 添加评论
|
||||
addExcellencourseComment(data) {
|
||||
return request('/expertAPI/addExcellencourseComment', data, 'post', true);
|
||||
},
|
||||
|
||||
// 一级分类
|
||||
listExcellencourseFirstType() {
|
||||
return request('/expertAPI/listExcellencourseFirstType', {}, 'post', true);
|
||||
},
|
||||
|
||||
// 二级分类
|
||||
listExcellencourseSecondType(type_id) {
|
||||
return request('/expertAPI/listExcellencourseSecondType', {first_id: type_id}, 'post', true);
|
||||
},
|
||||
|
||||
// 筛选列表
|
||||
excellencourseList(data) {
|
||||
return request('/expertAPI/excellencourseScreen', data, 'post', true);
|
||||
},
|
||||
|
||||
// 未开票订单列表
|
||||
listExcellencourseOrderNoInvoice() {
|
||||
return request('/expertAPI/listExcellencourseOrderNoInvoice', {}, 'post', true);
|
||||
},
|
||||
|
||||
// 开票历史列表
|
||||
listExcellencourseOrderInvoiceHistory(page) {
|
||||
return request('/expertAPI/listExcellencourseOrderInvoice', {page:page}, 'post', true);
|
||||
},
|
||||
|
||||
// 获取订单发票
|
||||
getExcellencourseOrderInvoice(order_id) {
|
||||
return request('/expertAPI/getExcellencourseOrderInvoice', {id:order_id}, 'post', true);
|
||||
},
|
||||
|
||||
// 短信验证码登录
|
||||
smsLogin(data,header){
|
||||
return request('/expertAPI/smsLogin', data, 'post', true,'application/json',header);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import {request} from '@/utils/request.js'
|
||||
const goods_api = {
|
||||
goodsNewsList() {
|
||||
return request('/expertAPI/goodsNewsList', {}, 'post', true);
|
||||
},
|
||||
|
||||
goodsList(data) {
|
||||
return request('/expertAPI/goodsListV2', data, 'post', true);
|
||||
},
|
||||
|
||||
goodsTagList(data) {
|
||||
return request('/expertAPI/goodsTagList', data, 'post', true);
|
||||
},
|
||||
|
||||
getGoodsDetail(data) {
|
||||
return request('/expertAPI/getGoodsDetail', data, 'post', true);
|
||||
},
|
||||
|
||||
getTotalPoints(data) {
|
||||
return request('/expertAPI/getTotalPointsU', data, 'post', true);
|
||||
},
|
||||
|
||||
createGoodsOrder(data) {
|
||||
return request('/expertAPI/createGoodsOrder', data, 'post', true);
|
||||
},
|
||||
payGoodsOrder(data) {
|
||||
return request('/expertAPI/payGoodsOrder', data, 'post', true);
|
||||
},
|
||||
getGoodsOrderList(data) {
|
||||
return request('/expertAPI/goodsOrderList', data, 'post', true);
|
||||
},
|
||||
getUpanWelfareCount(data){
|
||||
return request('/expertAPI/getUpanWelfareCount', data, 'post', false);
|
||||
},
|
||||
getGoodsOrderDetail(data) {
|
||||
return request('/expertAPI/getGoodsOrder', data, 'post', true);
|
||||
},
|
||||
getOrderTrack(data) {
|
||||
return request('/expertAPI/getOrderTrack', data, 'post', true);
|
||||
},
|
||||
cancelGoodsOrder(data) {
|
||||
return request('/expertAPI/cancleGoodsOrder', data, 'post', true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default goods_api;
|
||||
@@ -0,0 +1,31 @@
|
||||
import {request} from '@/utils/request.js'
|
||||
const my_api = {
|
||||
isBoundWechat() {
|
||||
return request('/expertAPI/isBoundWechatByUuid', {}, 'post', true);
|
||||
},
|
||||
|
||||
unBindWechat(data) {
|
||||
return request('/expertAPI/delWechatExpert', data, 'post', true);
|
||||
},
|
||||
|
||||
bindWechat(data) {
|
||||
return request('/expertAPI/addWechatExpert', data, 'post', true);
|
||||
},
|
||||
|
||||
expertWxLogin(data,header) {
|
||||
return request('/expertAPI/wechatLogin', data, 'post', true,'application/json',header);
|
||||
},
|
||||
|
||||
checkVersion(data) {
|
||||
return request('/expertAPI/checkVersion', data, 'post', true);
|
||||
},
|
||||
|
||||
startpage() {
|
||||
return request('/expertAPI/startpage', {}, 'post', true);
|
||||
},
|
||||
addUniappCid(data) {
|
||||
return request('/expertAPI/addUniappCid', data, 'post', true);
|
||||
}
|
||||
}
|
||||
|
||||
export default my_api;
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<span
|
||||
class="appellation"
|
||||
:style="{ color: color, fontSize: fontSize + 'px' }"
|
||||
>{{ appellation }}</span
|
||||
>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { autorun } from 'mobx'
|
||||
import { onUnmounted, ref, withDefaults } from 'vue'
|
||||
|
||||
const appellation = ref('')
|
||||
|
||||
const { account, teamId, ignoreAlias, nickFromMsg } = withDefaults(
|
||||
defineProps<{
|
||||
account: string
|
||||
teamId?: string
|
||||
ignoreAlias?: boolean
|
||||
nickFromMsg?: string
|
||||
color?: string
|
||||
fontSize?: number
|
||||
}>(),
|
||||
{
|
||||
teamId: undefined,
|
||||
ignoreAlias: false,
|
||||
nickFromMsg: undefined,
|
||||
color: '#333',
|
||||
fontSize: 16,
|
||||
}
|
||||
)
|
||||
|
||||
const uninstallAppellationWatch = autorun(() => {
|
||||
appellation.value = uni.$UIKitStore.uiStore.getAppellation({
|
||||
account,
|
||||
teamId,
|
||||
ignoreAlias,
|
||||
nickFromMsg,
|
||||
})
|
||||
})
|
||||
onUnmounted(() => {
|
||||
uninstallAppellationWatch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.appellation {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div
|
||||
class="avatar"
|
||||
:style="{ width: avatarSize + 'px', height: avatarSize + 'px' }"
|
||||
@click="handleAvatarClick"
|
||||
@longpress="longpress"
|
||||
@touchend="touchend"
|
||||
>
|
||||
|
||||
<!-- 使用遮罩层避免android长按头像会出现保存图片的弹窗 -->
|
||||
<div class="img-mask"></div>
|
||||
<image
|
||||
:lazy-load="true"
|
||||
class="avatar-img"
|
||||
v-if="avatarUrl"
|
||||
:src="docUrl+avatarUrl.replace('null','')"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<image
|
||||
:lazy-load="true"
|
||||
class="avatar-img"
|
||||
v-else-if="userInfo.uuid && account==userInfo.uuid.toLowerCase()"
|
||||
:src="docUrl+userInfo.photo"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<div class="avatar-name-wrapper" :style="{ backgroundColor: color }">
|
||||
<div class="avatar-name-text" :style="{ fontSize: fontSize + 'px' }">
|
||||
{{ appellation }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { customNavigateTo, customRedirectTo } from '@/utils/im/customNavigate'
|
||||
import { autorun } from 'mobx'
|
||||
import { ref, computed, onUnmounted, withDefaults } from 'vue'
|
||||
import docUrl from "@/utils/docUrl"
|
||||
import { onShow,onLoad,onReady } from "@dcloudio/uni-app";
|
||||
import { V2NIMUser } from 'nim-web-sdk-ng/dist/v2/NIM_UNIAPP_SDK/V2NIMUserService'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
account: string
|
||||
teamId?: string
|
||||
avatar?: string
|
||||
size?: string
|
||||
gotoUserCard?: boolean
|
||||
fontSize?: string
|
||||
isRedirect?: boolean
|
||||
}>(),
|
||||
{
|
||||
teamId: '',
|
||||
avatar: '',
|
||||
size: '',
|
||||
gotoUserCard: false,
|
||||
fontSize: '',
|
||||
isRedirect: false,
|
||||
}
|
||||
)
|
||||
const userInfo=ref({
|
||||
uuid:''
|
||||
})
|
||||
const $emit = defineEmits(['onLongpress'])
|
||||
|
||||
const avatarSize = props.size || 42
|
||||
const user = ref<V2NIMUser>()
|
||||
let isLongPress = false // uniapp 长按事件也会触发点击事件,此时需要处理
|
||||
|
||||
const appellation = ref<string>('')
|
||||
|
||||
const appellationWatch = autorun(() => {
|
||||
appellation.value = uni.$UIKitStore.uiStore
|
||||
.getAppellation({
|
||||
account: props.account,
|
||||
ignoreAlias: false,
|
||||
})
|
||||
?.slice(0, 2)
|
||||
})
|
||||
|
||||
const userInfoWatch = autorun(() => {
|
||||
uni.$UIKitStore?.userStore?.getUserActive(props.account).then((data) => {
|
||||
user.value = data
|
||||
})
|
||||
})
|
||||
|
||||
const avatarUrl = computed(() => {
|
||||
user.value = uni.$UIKitStore?.userStore?.users?.get(props.account)
|
||||
return props.avatar || user.value?.avatar
|
||||
})
|
||||
|
||||
const key = `__yx_avatar_color_${props.account}__`
|
||||
let color = uni.getStorageSync(key)
|
||||
if (!color) {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: '#60CFA7',
|
||||
1: '#53C3F3',
|
||||
2: '#537FF4',
|
||||
3: '#854FE2',
|
||||
4: '#BE65D9',
|
||||
5: '#E9749D',
|
||||
6: '#F9B751',
|
||||
}
|
||||
const _color = colorMap[Math.floor(Math.random() * 7)]
|
||||
uni.setStorageSync(key, _color)
|
||||
color = _color
|
||||
}
|
||||
|
||||
const handleAvatarClick = () => {
|
||||
if (props.gotoUserCard && !isLongPress) {
|
||||
if (props.isRedirect) {
|
||||
if (props.account === uni.$UIKitStore?.userStore?.myUserInfo.accountId) {
|
||||
customRedirectTo({
|
||||
url: `/pages/User/my-detail/index`,
|
||||
})
|
||||
} else {
|
||||
customRedirectTo({
|
||||
url: `/pages/User/friend/index?account=${props.account}`,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (props.account === uni.$UIKitStore?.userStore?.myUserInfo.accountId) {
|
||||
customNavigateTo({
|
||||
url: `/pages/User/my-detail/index`,
|
||||
})
|
||||
} else {
|
||||
customNavigateTo({
|
||||
url: `/pages/User/friend/index?account=${props.account}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const longpress = () => {
|
||||
isLongPress = true
|
||||
$emit('onLongpress')
|
||||
}
|
||||
|
||||
const touchend = () => {
|
||||
const timeOut = setTimeout(() => {
|
||||
isLongPress = false
|
||||
clearTimeout(timeOut)
|
||||
}, 200)
|
||||
}
|
||||
onShow(()=>{
|
||||
userInfo.value=uni.getStorageSync('userInfo');
|
||||
})
|
||||
onReady(()=>{
|
||||
userInfo.value=uni.getStorageSync('userInfo');
|
||||
})
|
||||
onUnmounted(() => {
|
||||
userInfoWatch()
|
||||
appellationWatch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.avatar {
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.img-mask {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-name-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-name-text {
|
||||
color: #fff;
|
||||
size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="dot" v-if="dot" :style="customStyle"></div>
|
||||
<div class="badge" v-else-if="text" :style="customStyle">{{ text }}</div>
|
||||
<div class="hidden">{{ props.num }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { StyleValue, computed, withDefaults } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
num: number
|
||||
max?: number
|
||||
dot?: boolean
|
||||
customStyle?: StyleValue
|
||||
}>(),
|
||||
{
|
||||
max: 99,
|
||||
dot: false,
|
||||
}
|
||||
)
|
||||
const max = props.max || 99
|
||||
const text = computed(() => {
|
||||
return props.num > 0 ? (props.num > max ? `${max}+` : props.num + '') : ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dot {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
line-height: 19px;
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
z-index: 99;
|
||||
position: relative;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="empty-wrapper">
|
||||
<image
|
||||
class="empty-img"
|
||||
src="https://yx-web-nosdn.netease.im/common/e0f58096f06c18cdd101f2614e6afb09/empty.png"
|
||||
/>
|
||||
<div class="empty-text">{{ text }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
text?: string
|
||||
}>(),
|
||||
{
|
||||
text: '',
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.empty-wrapper {
|
||||
margin: 75px 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.empty-img {
|
||||
display: block;
|
||||
width: 125px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
color: #a6adb6;
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="inputClass">
|
||||
<slot name="addonBefore" />
|
||||
<input
|
||||
class="input"
|
||||
:type="type"
|
||||
:value="inputValue"
|
||||
@input="handleInput"
|
||||
:focus="inputFocus"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
/>
|
||||
<div class="clear-icon" @tap="clearInput()">
|
||||
<icon v-show="modelValue && allowClear" type="clear" size="16" />
|
||||
</div>
|
||||
<slot name="addonAfter" />
|
||||
</div>
|
||||
|
||||
<div v-if="inputError && rule" class="error-tips">{{ rule.message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
const $emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'input',
|
||||
'focus',
|
||||
'blur',
|
||||
'clear',
|
||||
])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
className?: string
|
||||
type?: string
|
||||
modelValue?: string
|
||||
placeholder?: string
|
||||
allowClear?: boolean
|
||||
rule?: any
|
||||
maxlength?: number
|
||||
}>(),
|
||||
{
|
||||
className: '',
|
||||
type: 'text',
|
||||
modelValue: '',
|
||||
placeholder: '',
|
||||
allowClear: false,
|
||||
rule: null,
|
||||
maxlength: 140,
|
||||
}
|
||||
)
|
||||
|
||||
const inputFocus = ref(false)
|
||||
const inputError = ref(false)
|
||||
// const inputKey = ref(0);
|
||||
|
||||
const inputClass = computed(() => {
|
||||
return [
|
||||
props.className,
|
||||
'form-input-item',
|
||||
{ focus: inputFocus.value, error: inputError.value },
|
||||
]
|
||||
})
|
||||
|
||||
const inputValue = computed(() => {
|
||||
return props.modelValue || ''
|
||||
})
|
||||
|
||||
const handleBlur = (event: any) => {
|
||||
inputFocus.value = false
|
||||
if (props.rule && props.rule.trigger === 'blur') {
|
||||
inputError.value = !props.rule.reg.test(props.modelValue || '')
|
||||
}
|
||||
$emit('blur', event)
|
||||
}
|
||||
|
||||
const handleFocus = (event: any) => {
|
||||
inputFocus.value = true
|
||||
$emit('blur', event)
|
||||
}
|
||||
|
||||
const handleInput = (event: any) => {
|
||||
if (!(props.maxlength && event.detail.value.length > props.maxlength)) {
|
||||
$emit('update:modelValue', event.detail.value)
|
||||
$emit('input', event.detail.value)
|
||||
}
|
||||
// 强制刷新input
|
||||
// inputKey.value++;
|
||||
}
|
||||
|
||||
const clearInput = () => {
|
||||
$emit('update:modelValue', null)
|
||||
$emit('input', null)
|
||||
$emit('clear', '')
|
||||
|
||||
inputFocus.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$primary-color: #337eff;
|
||||
$error-color: #f56c6c;
|
||||
|
||||
.form-input-item {
|
||||
border-bottom: 1px solid #dcdfe5;
|
||||
padding: 10px 10px 5px 0px;
|
||||
display: flex;
|
||||
height: 44px;
|
||||
align-items: center;
|
||||
|
||||
&.focus {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: $error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
height: 30px;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
.clear-icon {
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.error-tips {
|
||||
color: $error-color;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view v-show="visible" class="gd-mask" :class="alignClass" :style="maskStyle">
|
||||
<view
|
||||
class="gd-dialog"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<view class="gd-title">{{ title }}</view>
|
||||
<scroll-view class="gd-content" scroll-y>
|
||||
<text class="gd-text">{{ content }}</text>
|
||||
</scroll-view>
|
||||
<!-- <view class="gd-footer">
|
||||
<view class="gd-btn gd-confirm" @click="handleConfirm">{{ confirmText }}</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
|
||||
const visible = ref(false)
|
||||
const title = ref('消息通知')
|
||||
const content = ref('')
|
||||
const confirmText = ref('我知道了')
|
||||
const position = ref('center') // 'center' | 'top'
|
||||
const offsetTop = ref('20rpx')
|
||||
const autoHideTimer = ref(null)
|
||||
|
||||
const startY = ref(0)
|
||||
const hasSwipedUp = ref(false)
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
function handleConfirm(){
|
||||
visible.value = false
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
function show(payload = {}){
|
||||
title.value = payload.title || '消息通知'
|
||||
content.value = payload.content || ''
|
||||
confirmText.value = payload.confirmText || '我知道了'
|
||||
position.value = payload.position === 'top' ? 'top' : 'center'
|
||||
offsetTop.value = payload.offsetTop || '20rpx'
|
||||
visible.value = true
|
||||
// 自动 5 秒后消失
|
||||
if (autoHideTimer.value) clearTimeout(autoHideTimer.value)
|
||||
autoHideTimer.value = setTimeout(() => {
|
||||
hide()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
function hide(){
|
||||
visible.value = false
|
||||
if (autoHideTimer.value) {
|
||||
clearTimeout(autoHideTimer.value)
|
||||
autoHideTimer.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchStart(e){
|
||||
const touch = e.changedTouches ? e.changedTouches[0] : (e.touches ? e.touches[0] : null)
|
||||
if (!touch) return
|
||||
startY.value = touch.clientY || touch.pageY || 0
|
||||
hasSwipedUp.value = false
|
||||
}
|
||||
|
||||
function onTouchMove(e){
|
||||
const touch = e.changedTouches ? e.changedTouches[0] : (e.touches ? e.touches[0] : null)
|
||||
if (!touch) return
|
||||
const currentY = touch.clientY || touch.pageY || 0
|
||||
const deltaY = currentY - startY.value
|
||||
if (deltaY < -60) {
|
||||
hasSwipedUp.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchEnd(){
|
||||
if (hasSwipedUp.value) {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('[GlobalDialog] mounted')
|
||||
uni.$on('global-dialog:show', show)
|
||||
uni.$on('global-dialog:hide', hide)
|
||||
// 便于全局手动触发测试
|
||||
// @ts-ignore
|
||||
uni.$showGlobalDialog = (payload = {}) => {
|
||||
console.log('[GlobalDialog] uni.$showGlobalDialog called', payload)
|
||||
uni.$emit('global-dialog:show', payload)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
uni.$off('global-dialog:show', show)
|
||||
uni.$off('global-dialog:hide', hide)
|
||||
})
|
||||
|
||||
const alignClass = computed(() => position.value === 'top' ? 'is-top' : 'is-center')
|
||||
const maskStyle = computed(() => position.value === 'top' ? { paddingTop: offsetTop.value } : {})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.gd-mask{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// 非模态:容器不截获事件
|
||||
pointer-events: none;
|
||||
}
|
||||
.gd-mask.is-top{
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
.gd-dialog{
|
||||
width: 600rpx;
|
||||
max-width: 90%;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
// 允许对话框自己接收事件(容器为非模态)
|
||||
pointer-events: auto;
|
||||
// 轻微上滑消失的过渡体验
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
.gd-title{
|
||||
padding: 28rpx 32rpx 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #111;
|
||||
text-align: left;
|
||||
}
|
||||
.gd-content{
|
||||
max-height: 420rpx;
|
||||
padding: 0 32rpx 24rpx;
|
||||
}
|
||||
.gd-text{
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
}
|
||||
.gd-footer{
|
||||
padding: 16rpx 24rpx 24rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.gd-btn{
|
||||
min-width: 200rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
text-align: center;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.gd-confirm{
|
||||
background: #2979ff;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<view :class="className" :style="iconStyle">
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<image
|
||||
:src="iconUrl"
|
||||
:style="{
|
||||
width: (width || size) + 'px',
|
||||
height: (height || size) + 'px',
|
||||
}"
|
||||
class="icon"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<img
|
||||
:src="iconUrl"
|
||||
:style="{
|
||||
width: (width || size) + 'px',
|
||||
height: (height || size) + 'px',
|
||||
}"
|
||||
class="icon"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, withDefaults } from 'vue'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
type: string
|
||||
size?: number
|
||||
width?: number
|
||||
height?: number
|
||||
iconClassName?: string
|
||||
iconStyle?: any
|
||||
}>(),
|
||||
{
|
||||
size: 16,
|
||||
}
|
||||
)
|
||||
const urlMap: any = {
|
||||
'icon-a-1':
|
||||
'https://yx-web-nosdn.netease.im/common/7c2e2c6ff08f4ed60f3ca7d5ab6d38ac/icon-a-1.png',
|
||||
'icon-a-2':
|
||||
'https://yx-web-nosdn.netease.im/common/bb61c05013350e980b3acf77a00aa7b7/icon-a-2.png',
|
||||
'icon-a-3':
|
||||
'https://yx-web-nosdn.netease.im/common/bfe80222635ec3f07e41b2819d002a4c/icon-a-3.png',
|
||||
'icon-a-4':
|
||||
'https://yx-web-nosdn.netease.im/common/e6dd252af94a4716edc191f7d3812042/icon-a-4.png',
|
||||
'icon-a-5':
|
||||
'https://yx-web-nosdn.netease.im/common/4e01e93f3e795d8aeb140e924d8b1206/icon-a-5.png',
|
||||
'icon-a-6':
|
||||
'https://yx-web-nosdn.netease.im/common/a1fa1174a588a984845ba871b1f1e3b0/icon-a-6.png',
|
||||
'icon-a-7':
|
||||
'https://yx-web-nosdn.netease.im/common/e0e30470561cf90446ec31c6d5b206a5/icon-a-7.png',
|
||||
'icon-a-8':
|
||||
'https://yx-web-nosdn.netease.im/common/e44ceea2c764bfbe773e61483ae857eb/icon-a-8.png',
|
||||
'icon-a-9':
|
||||
'https://yx-web-nosdn.netease.im/common/f4e5b303cb77d9ce67a0baf4efaa1125/icon-a-9.png',
|
||||
'icon-a-10':
|
||||
'https://yx-web-nosdn.netease.im/common/28e46e6cc468c4ff6f44532bd6b7f464/icon-a-10.png',
|
||||
'icon-a-11':
|
||||
'https://yx-web-nosdn.netease.im/common/f265ea5a782cebabae08a079045caf2c/icon-a-11.png',
|
||||
'icon-a-12':
|
||||
'https://yx-web-nosdn.netease.im/common/20043e6f8d619ab7bca69f1a0d5a0e56/icon-a-12.png',
|
||||
'icon-a-13':
|
||||
'https://yx-web-nosdn.netease.im/common/c2b2d52a97eea83efb10c9bbc34dc104/icon-a-13.png',
|
||||
'icon-a-14':
|
||||
'https://yx-web-nosdn.netease.im/common/37486551a8bd31e3446ae862cddd3067/icon-a-14.png',
|
||||
'icon-a-15':
|
||||
'https://yx-web-nosdn.netease.im/common/e383cc57dcb7a903887031ed0d341fef/icon-a-15.png',
|
||||
'icon-a-16':
|
||||
'https://yx-web-nosdn.netease.im/common/73e2f9507f8aac7238896a3f1ee8305a/icon-a-16.png',
|
||||
'icon-a-17':
|
||||
'https://yx-web-nosdn.netease.im/common/51a80e8e81a8aa531d1c68e6f79f7d58/icon-a-17.png',
|
||||
'icon-a-18':
|
||||
'https://yx-web-nosdn.netease.im/common/20f8c6153ba0f51c038e04f2fbedf767/icon-a-18.png',
|
||||
'icon-a-19':
|
||||
'https://yx-web-nosdn.netease.im/common/47d420b5251ca7b9ddc564b42564eb01/icon-a-19.png',
|
||||
'icon-a-20':
|
||||
'https://yx-web-nosdn.netease.im/common/8fad6b9427b49f5bfd1a8ec0f8a77fac/icon-a-20.png',
|
||||
'icon-a-21':
|
||||
'https://yx-web-nosdn.netease.im/common/6fa17035135cebe4ca1512b454fa38c2/icon-a-21.png',
|
||||
'icon-a-22':
|
||||
'https://yx-web-nosdn.netease.im/common/12189563eea22258b847cf900dcbc4d4/icon-a-22.png',
|
||||
'icon-a-23':
|
||||
'https://yx-web-nosdn.netease.im/common/3b7dcfe14788bd7d8c2da8aad6c24baa/icon-a-23.png',
|
||||
'icon-a-24':
|
||||
'https://yx-web-nosdn.netease.im/common/6bb0879272c8a3c73524f3b728a93c10/icon-a-24.png',
|
||||
'icon-a-25':
|
||||
'https://yx-web-nosdn.netease.im/common/1f93dac2bdef1f91ca2f7302065954d5/icon-a-25.png',
|
||||
'icon-a-26':
|
||||
'https://yx-web-nosdn.netease.im/common/aa3c8e63ee6c6d605f4606a8094f2094/icon-a-26.png',
|
||||
'icon-a-27':
|
||||
'https://yx-web-nosdn.netease.im/common/000447b3172d70d1b739a170c377eae2/icon-a-27.png',
|
||||
'icon-a-28':
|
||||
'https://yx-web-nosdn.netease.im/common/27c3b8d1acb6f234a7591c2358b549bf/icon-a-28.png',
|
||||
'icon-a-29':
|
||||
'https://yx-web-nosdn.netease.im/common/fbae68926f43367022d0b0c95c15adcf/icon-a-29.png',
|
||||
'icon-a-30':
|
||||
'https://yx-web-nosdn.netease.im/common/61c0b6c12bc6ad2b7e0511d843b03265/icon-a-30.png',
|
||||
'icon-a-31':
|
||||
'https://yx-web-nosdn.netease.im/common/202e7abeaecf651f6da3f7f695ef8752/icon-a-31.png',
|
||||
'icon-a-32':
|
||||
'https://yx-web-nosdn.netease.im/common/6ef4958d39866165212c1b60bc7c6a55/icon-a-32.png',
|
||||
'icon-a-33':
|
||||
'https://yx-web-nosdn.netease.im/common/18f8b53d57a6b47c3876b6938f2f57b9/icon-a-33.png',
|
||||
'icon-a-34':
|
||||
'https://yx-web-nosdn.netease.im/common/84d7491e6d5a6d53848b6a3d12a739b2/icon-a-34.png',
|
||||
'icon-a-35':
|
||||
'https://yx-web-nosdn.netease.im/common/f03fe01986e9e25f3faf1a17c0ef78c8/icon-a-35.png',
|
||||
'icon-a-36':
|
||||
'https://yx-web-nosdn.netease.im/common/4617253245617bcd38ed7165bdb592d8/icon-a-36.png',
|
||||
'icon-a-37':
|
||||
'https://yx-web-nosdn.netease.im/common/9168b11333f59b0ff948003dedb24c84/icon-a-37.png',
|
||||
'icon-a-38':
|
||||
'https://yx-web-nosdn.netease.im/common/1a3cd5d62e92895dbab3d356112b5079/icon-a-38.png',
|
||||
'icon-a-39':
|
||||
'https://yx-web-nosdn.netease.im/common/b465db737c737ab4308c9272a00a1fdf/icon-a-39.png',
|
||||
'icon-a-40':
|
||||
'https://yx-web-nosdn.netease.im/common/6bca766a4904e10934ea2f2450c341b5/icon-a-40.png',
|
||||
'icon-a-41':
|
||||
'https://yx-web-nosdn.netease.im/common/12d5055618b619b568342f0619cacf0c/icon-a-41.png',
|
||||
'icon-a-42':
|
||||
'https://yx-web-nosdn.netease.im/common/8851ccd815375180bd7f8728ead1cdf1/icon-a-42.png',
|
||||
'icon-a-43':
|
||||
'https://yx-web-nosdn.netease.im/common/1ac7cacc9d0063d165f8e2bc3020bfff/icon-a-43.png',
|
||||
'icon-a-44':
|
||||
'https://yx-web-nosdn.netease.im/common/5735260c7e240beaabc360365e6d60c5/icon-a-44.png',
|
||||
'icon-a-45':
|
||||
'https://yx-web-nosdn.netease.im/common/07372c06915a7d0a977a4f042522e2a7/icon-a-45.png',
|
||||
'icon-a-46':
|
||||
'https://yx-web-nosdn.netease.im/common/40c5b2b5769166b51c968942f4abed95/icon-a-46.png',
|
||||
'icon-a-47':
|
||||
'https://yx-web-nosdn.netease.im/common/9334c3ec8c8d54a9a3271a3536ff7c62/icon-a-47.png',
|
||||
'icon-a-48':
|
||||
'https://yx-web-nosdn.netease.im/common/5630ab646533dd6de7e3c1accc3d2ca1/icon-a-48.png',
|
||||
'icon-a-49':
|
||||
'https://yx-web-nosdn.netease.im/common/f2412538fa0da38549c23fd44b25bdfb/icon-a-49.png',
|
||||
'icon-a-50':
|
||||
'https://yx-web-nosdn.netease.im/common/1f28ffd1413aa3fcce1c17aff3694d41/icon-a-50.png',
|
||||
'icon-a-51':
|
||||
'https://yx-web-nosdn.netease.im/common/a99b57b586a85c23cbd8ed65d1a16765/icon-a-51.png',
|
||||
'icon-a-52':
|
||||
'https://yx-web-nosdn.netease.im/common/ce6f64bbe45a42108fd1b1a7b1dae606/icon-a-52.png',
|
||||
'icon-a-53':
|
||||
'https://yx-web-nosdn.netease.im/common/ba5dc56bf8f550da8c2dc1c94e2ee7fb/icon-a-53.png',
|
||||
'icon-a-54':
|
||||
'https://yx-web-nosdn.netease.im/common/81f5b3b693d9133030c013f0f21462ab/icon-a-54.png',
|
||||
'icon-a-55':
|
||||
'https://yx-web-nosdn.netease.im/common/758ed38dea207d60855969689a5dd68b/icon-a-55.png',
|
||||
'icon-a-56':
|
||||
'https://yx-web-nosdn.netease.im/common/a20bee2372cbe399191af3d76a5aad31/icon-a-56.png',
|
||||
'icon-a-57':
|
||||
'https://yx-web-nosdn.netease.im/common/a17fbdbc64cf4b55be377f18ed81879a/icon-a-57.png',
|
||||
'icon-a-58':
|
||||
'https://yx-web-nosdn.netease.im/common/784b31cda55da76d8278a5bd53c2c7e4/icon-a-58.png',
|
||||
'icon-a-59':
|
||||
'https://yx-web-nosdn.netease.im/common/c716afbc25b8809dfb9ca80a09051f6f/icon-a-59.png',
|
||||
'icon-a-60':
|
||||
'https://yx-web-nosdn.netease.im/common/54de44be20c5d21ffad08176b24560a0/icon-a-60.png',
|
||||
'icon-a-61':
|
||||
'https://yx-web-nosdn.netease.im/common/0628fd4507e2a3455028f376d8d5d80a/icon-a-61.png',
|
||||
'icon-a-62':
|
||||
'https://yx-web-nosdn.netease.im/common/fa336dc373d5e395a1e9a541dc9953d2/icon-a-62.png',
|
||||
'icon-a-63':
|
||||
'https://yx-web-nosdn.netease.im/common/868ee8c664b32b34d554d02fc406ab70/icon-a-63.png',
|
||||
'icon-a-64':
|
||||
'https://yx-web-nosdn.netease.im/common/4e33aadf3ca3b918e73ef07e21eb96ac/icon-a-64.png',
|
||||
'icon-a-65':
|
||||
'https://yx-web-nosdn.netease.im/common/c6e5563811d94c82426036b9f96d6de5/icon-a-65.png',
|
||||
'icon-a-66':
|
||||
'https://yx-web-nosdn.netease.im/common/8baa1f43b4e523e524c54f3340ef21cb/icon-a-66.png',
|
||||
'icon-a-67':
|
||||
'https://yx-web-nosdn.netease.im/common/307c3426dccf1252b5967956bcdcf58a/icon-a-67.png',
|
||||
'icon-a-68':
|
||||
'https://yx-web-nosdn.netease.im/common/196f62aa8e8a38bbbcd818ad42729714/icon-a-68.png',
|
||||
'icon-a-70':
|
||||
'https://yx-web-nosdn.netease.im/common/fb54482390faf9d8d9d607d7e3ab691f/icon-a-70.png',
|
||||
'icon-a-Frame7':
|
||||
'https://yx-web-nosdn.netease.im/common/c5c3d0ee0a4000736827cedfd0610172/icon-a-Frame7.png',
|
||||
'icon-a-Frame8':
|
||||
'https://yx-web-nosdn.netease.im/common/9f77f834a99c29ed5c4b4b54b7ade468/icon-a-Frame8.png',
|
||||
'icon-addition':
|
||||
'https://yx-web-nosdn.netease.im/common/6302fcf17e9c4ac65b392553aaccf9b1/icon-addition.png',
|
||||
'icon-biaoqing':
|
||||
'https://yx-web-nosdn.netease.im/common/1a98df356ed629193e50bea570e02a53/icon-biaoqing.png',
|
||||
'icon-chehui':
|
||||
'https://yx-web-nosdn.netease.im/common/958cb6797f69bdfbf5441da25ed997c3/icon-chehui.png',
|
||||
'icon-chuangjianqunzu':
|
||||
'https://yx-web-nosdn.netease.im/common/7b00839704359b7c10f32af7cc8b5911/icon-chuangjianqunzu.png',
|
||||
'icon-computed':
|
||||
'https://yx-web-nosdn.netease.im/common/352cd94c93347f01936b50bb1487b5a4/icon-computed.png',
|
||||
'icon-erfenzhiyiyidu':
|
||||
'https://yx-web-nosdn.netease.im/common/aa6b529567ee6aeb801f34f228600b04/icon-erfenzhiyiyidu.png',
|
||||
'icon-Excel':
|
||||
'https://yx-web-nosdn.netease.im/common/a9793bbb8a7237e9e92f57ad1b469baf/icon-Excel.png',
|
||||
'icon-fasong':
|
||||
'https://yx-web-nosdn.netease.im/common/f8d855dde4840989b4c88e904e1c23bf/icon-fasong.png',
|
||||
'icon-fuzhi1':
|
||||
'https://yx-web-nosdn.netease.im/common/9e0ba675eb4548bffe19da823fb712e3/icon-fuzhi1.png',
|
||||
'icon-guanbi':
|
||||
'https://yx-web-nosdn.netease.im/common/2d07f146ecb4b616632f0fcfdd02b5be/icon-guanbi.png',
|
||||
'icon-guanbi1':
|
||||
'https://yx-web-nosdn.netease.im/common/51bad0b424c2462df8213883e211fe07/icon-guanbi1.png',
|
||||
'icon-guanyu':
|
||||
'https://yx-web-nosdn.netease.im/common/24d2344c49b551d5a605b3e5e3e6f6da/icon-guanyu.png',
|
||||
'icon-huifu':
|
||||
'https://yx-web-nosdn.netease.im/common/153c273cb7b075fc0c37487655c5cbfc/icon-huifu.png',
|
||||
'icon-im-xuanzhong':
|
||||
'https://yx-web-nosdn.netease.im/common/468aaecf148e23c84b821835021dfee1/icon-im-xuanzhong.png',
|
||||
'icon-jiantou':
|
||||
'https://yx-web-nosdn.netease.im/common/c9bb28670c41e208b1d9bcedd6de88a1/icon-jiantou.png',
|
||||
'icon-jiaruqunzu':
|
||||
'https://yx-web-nosdn.netease.im/common/a27ba47b93131b7c52d829b7012fa966/icon-jiaruqunzu.png',
|
||||
'icon-kefu':
|
||||
'https://yx-web-nosdn.netease.im/common/4ddc1b9557e6672b0b45fb8fb992c992/icon-kefu.png',
|
||||
'icon-lahei':
|
||||
'https://yx-web-nosdn.netease.im/common/e9054279ca62db944ef6e5a76687a93a/icon-lahei.png',
|
||||
'icon-lishixiaoxi':
|
||||
'https://yx-web-nosdn.netease.im/common/77888d982110f3c709a3ff4fd24d48dd/icon-lishixiaoxi.png',
|
||||
'icon-More':
|
||||
'https://yx-web-nosdn.netease.im/common/137ad07f3245dc220d5546433db63786/icon-More.png',
|
||||
'icon-PPT':
|
||||
'https://yx-web-nosdn.netease.im/common/1991f8d57d1d432fcd930e4049403958/icon-PPT.png',
|
||||
'icon-qita':
|
||||
'https://yx-web-nosdn.netease.im/common/c6f687c792ef029bef1e59ccce986922/icon-qita.png',
|
||||
'icon-quxiaoxiaoximiandarao':
|
||||
'https://yx-web-nosdn.netease.im/common/f6f54973789b69939c57ae94a38e8d25/icon-quxiaoxiaoximiandarao.png',
|
||||
'icon-quxiaozhiding':
|
||||
'https://yx-web-nosdn.netease.im/common/ad2d451e8f1ee36ec9cec6787d6074c0/icon-quxiaozhiding.png',
|
||||
'icon-RAR1':
|
||||
'https://yx-web-nosdn.netease.im/common/27a743e5696c1ca4416b75c5dde0252c/icon-RAR1.png',
|
||||
'icon-shanchu':
|
||||
'https://yx-web-nosdn.netease.im/common/d96f9c0113af4d86dd4e5ecf2e49711b/icon-shanchu.png',
|
||||
'icon-shandiao':
|
||||
'https://yx-web-nosdn.netease.im/common/14eea1edc7801449a30700a3c9d604c6/icon-shandiao.png',
|
||||
'icon-shezhi':
|
||||
'https://yx-web-nosdn.netease.im/common/698d483a7aacade68ea976f727184e1e/setting.png',
|
||||
'icon-shezhi1':
|
||||
'https://yx-web-nosdn.netease.im/common/eced271ac35864b7c716262f1a37217e/icon-shezhi1.png',
|
||||
'icon-shipin':
|
||||
'https://yx-web-nosdn.netease.im/common/455c333219318f3b96f748d6753eda4a/icon-shipin.png',
|
||||
'icon-shipin8':
|
||||
'https://yx-web-nosdn.netease.im/common/73fededbe5b97dec0246e438d33ae614/icon-shipin8.png',
|
||||
'icon-shipinyuyin':
|
||||
'https://yx-web-nosdn.netease.im/common/09b16fe9a2b824ded4162f25333e44e1/icon-shipinyuyin.png',
|
||||
'icon-sifenzhisanyidu':
|
||||
'https://yx-web-nosdn.netease.im/common/b1234a4255d3187fa9a767bfed8f4d96/icon-sifenzhisanyidu.png',
|
||||
'icon-sifenzhiyiyidu':
|
||||
'https://yx-web-nosdn.netease.im/common/11c7a7e79c57eda20c5ca9d98b0d7e8a/icon-sifenzhiyiyidu.png',
|
||||
'icon-sousuo':
|
||||
'https://yx-web-nosdn.netease.im/common/2ccfdfa640c72167f228e7d76068e5f5/icon-sousuo.png',
|
||||
'icon-team':
|
||||
'https://yx-web-nosdn.netease.im/common/140e8fbd1cc2df4c878acb17405471f7/icon-team.png',
|
||||
'icon-zuojiantou':
|
||||
'https://yx-web-nosdn.netease.im/common/9ab796030ac24a126dedc60fd60613ce/icon-zuojiantou.png',
|
||||
'icon-zhuanfa':
|
||||
'https://yx-web-nosdn.netease.im/common/163a3343b0262a76d72d00bbbb4f8ac9/icon-zhuanfa.png',
|
||||
'icon-zhongyingwen':
|
||||
'https://yx-web-nosdn.netease.im/common/80a7dbae8c0207f1896e63746ac1a18a/icon-zhongyingwen.png',
|
||||
'icon-zhankai':
|
||||
'https://yx-web-nosdn.netease.im/common/d4f655bff9e278ea732adb6a5317bbca/icon-zhankai.png',
|
||||
'icon-yinle':
|
||||
'https://yx-web-nosdn.netease.im/common/98a2a366c7e06ab1b1b6ddf9b0c01d73/icon-yinle.png',
|
||||
'icon-yidu':
|
||||
'https://yx-web-nosdn.netease.im/common/d17d4aa7866faca55a6a1180c1e15bf6/icon-yidu.png',
|
||||
'icon-yanzheng':
|
||||
'https://yx-web-nosdn.netease.im/common/5a0c2769626284ff646298a7ef1f66c2/icon-yanzheng.png',
|
||||
'icon-xiaoxizhiding':
|
||||
'https://yx-web-nosdn.netease.im/common/cef2e824e603dde3d333d128434f90c1/icon-xiaoxizhiding.png',
|
||||
'icon-xiaoximiandarao':
|
||||
'https://yx-web-nosdn.netease.im/common/1c92731bb3fa91fa3fc5ff45bf9e4dbe/icon-xiaoximiandarao.png',
|
||||
'icon-Word':
|
||||
'https://yx-web-nosdn.netease.im/common/af0e4fa22c4b30a263a7f534b0504c23/icon-Word.png',
|
||||
'icon-wenjian':
|
||||
'https://yx-web-nosdn.netease.im/common/d3b36fc953447f9d9630f3b73aaa6ef1/icon-wenjian.png',
|
||||
'icon-weizhiwenjian':
|
||||
'https://yx-web-nosdn.netease.im/common/d51b39a07b3b482ab3b50a4b068588c6/icon-weizhiwenjian.png',
|
||||
'icon-weidu':
|
||||
'https://yx-web-nosdn.netease.im/common/5d50477d2afa387a59a67e30fcdceabd/icon-weidu.png',
|
||||
'icon-tupian2':
|
||||
'https://yx-web-nosdn.netease.im/common/51eb954ad971eb6890d0934858f950aa/icon-tupian2.png',
|
||||
'icon-tupian1':
|
||||
'https://yx-web-nosdn.netease.im/common/0737f1e187aa250d5090f38925672485/icon-tupian1.png',
|
||||
'icon-tupian':
|
||||
'https://yx-web-nosdn.netease.im/common/aa93aa9ffd0197b9a961455506f75078/icon-tupian.png',
|
||||
'icon-tuigejian':
|
||||
'https://yx-web-nosdn.netease.im/common/7bca7dffd1f8c3cd66b8ede7f176e4a8/icon-tuigejian.png',
|
||||
'icon-tuichudenglu':
|
||||
'https://yx-web-nosdn.netease.im/common/ce42192020620522a46763c758951b76/icon-tuichudenglu.png',
|
||||
'icon-touxiang5':
|
||||
'https://yx-web-nosdn.netease.im/common/769a3ba0615b3157d6b493fa5d2352c4/icon-touxiang5.png',
|
||||
'icon-touxiang4':
|
||||
'https://yx-web-nosdn.netease.im/common/c6ea2b6557913d2fe8017b68eb688515/icon-touxiang4.png',
|
||||
'icon-touxiang3':
|
||||
'https://yx-web-nosdn.netease.im/common/89bbfa21ce6d43fda25a8c4121284db6/icon-touxiang3.png',
|
||||
'icon-touxiang2':
|
||||
'https://yx-web-nosdn.netease.im/common/e2e3fdafb9201a0b693b36514eb378ae/icon-touxiang2.png',
|
||||
'icon-touxiang1':
|
||||
'https://yx-web-nosdn.netease.im/common/4e639e380f246e804b2f0f115f84215f/icon-touxiang1.png',
|
||||
'icon-tongxunlu-xuanzhong':
|
||||
'https://yx-web-nosdn.netease.im/common/f49a558e193325d185223f55b65711bf/icon-tongxunlu-xuanzhong.png',
|
||||
'icon-tongxunlu-weixuanzhong':
|
||||
'https://yx-web-nosdn.netease.im/common/e33c24318c1faeb73c79fa1b0b1c9c53/icon-tongxunlu-weixuanzhong.png',
|
||||
'icon-tianjiahaoyou':
|
||||
'https://yx-web-nosdn.netease.im/common/c5f19ef12df64f466bba0a611cf224d5/icon-tianjiahaoyou.png',
|
||||
'icon-tianjiaanniu':
|
||||
'https://yx-web-nosdn.netease.im/common/181feb34fc6324198f4d9d887a8759e1/icon-tianjiaanniu.png',
|
||||
'icon-team2':
|
||||
'https://yx-web-nosdn.netease.im/common/f9d8ea13b9b5d769f75e7f01edcab1df/icon-team2.png',
|
||||
'icon-lahei2':
|
||||
'https://yx-web-nosdn.netease.im/common/1ee2a3bffb33b81727583189a2562658/icon-lahei2.png',
|
||||
'icon-yuyin1':
|
||||
'https://yx-web-nosdn.netease.im/common/2c785931baec5a9b00cd5f59f81f4482/语音1.png',
|
||||
'icon-yuyin2':
|
||||
'https://yx-web-nosdn.netease.im/common/20043a54056311986c867edf93f51f62/语音2.png',
|
||||
'icon-yuyin3':
|
||||
'https://yx-web-nosdn.netease.im/common/786476a9bd129f6a7b027621f9818883/语音3.png',
|
||||
'icon-yuyin8':
|
||||
'https://yx-web-nosdn.netease.im/common/40d631410c18983f6ee0ca880976c2e9/icon-yuyin8.png',
|
||||
'icon-audio':
|
||||
'https://yx-web-nosdn.netease.im/common/27c4c8b528fac12d3f79bb3154be87d4/audio1.png',
|
||||
'audio-btn':
|
||||
'https://yx-web-nosdn.netease.im/common/abfd3577b9d29bb6d3445979336a5770/Vector.png',
|
||||
'audio-btn-selected':
|
||||
'https://yx-web-nosdn.netease.im/common/7f17c648e9b63f40b91832664668ac7a/Frame.png',
|
||||
'send-more':
|
||||
'https://yx-web-nosdn.netease.im/common/270ebff9ad75056b857c21f40b55d72d/send-more.png',
|
||||
'icon-paishe':
|
||||
'https://yx-web-nosdn.netease.im/common/be9638b843a70f307ecb6803ffe5775c/paishe.png',
|
||||
'icon-shipin2':
|
||||
'https://yx-web-nosdn.netease.im/common/3865bf597f9f5ca03b2b222ca07344e1/icon-shipin2.png',
|
||||
'icon-audio-call':
|
||||
'https://yx-web-nosdn.netease.im/common/99438364d757b51e7e36c18d254e70e7/icon-audio-call.png',
|
||||
'icon-video-call':
|
||||
'https://yx-web-nosdn.netease.im/common/ed7c85a59de3e247d10ecfc684b05226/icon-video-call.png',
|
||||
'icon-read':
|
||||
'https://yx-web-nosdn.netease.im/common/271c53e493cfd6ea98c51650d8b39b79/read.png',
|
||||
'icon-file':
|
||||
'https://yx-web-nosdn.netease.im/common/90485f277f50fc081970ded8772ec7c5/file.png',
|
||||
'icon-pin':
|
||||
'https://yx-web-nosdn.netease.im/common/6eb4fafcca008d2e93e90311696d6b96/black-pin.png',
|
||||
'icon-green-pin':
|
||||
'https://yx-web-nosdn.netease.im/common/4a1f15eff2f53563c4f1cf6ecde82d2c/green-pin.png',
|
||||
'choose-picture':
|
||||
'https://yx-web-nosdn.netease.im/common/97b3ca79a589d5753cbc0e8e8ec09501/choose-picture.png',
|
||||
'icon-collection':
|
||||
'https://yx-web-nosdn.netease.im/common/aa1bad3410009dea83d34d513dcd20f3/add-collection.png',
|
||||
'blue-collection':
|
||||
'https://yx-web-nosdn.netease.im/common/fb3836a8731b57720fcfdd3b589b3d5f/collection.png',
|
||||
}
|
||||
|
||||
//以上链接访问有频率控制,建议您将静态资源放到您服务器上,然后修改上面的链接即可
|
||||
|
||||
const iconUrl = computed(() => {
|
||||
return urlMap[props.type]
|
||||
})
|
||||
|
||||
const className = `${props.iconClassName || ''} icon-wrapper`
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon-wrapper {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<template v-for="item in textArr" :key="item.key">
|
||||
<template v-if="item.type === 'text'" class="msg-reply-text">
|
||||
{{ item.value }}
|
||||
</template>
|
||||
<template class="icon" v-else-if="item.type === 'emoji'">
|
||||
<Icon
|
||||
:type="EMOJI_ICON_MAP_CONFIG[item.value]"
|
||||
:size="14"
|
||||
:iconStyle="{
|
||||
margin: '3px',
|
||||
verticalAlign: 'bottom',
|
||||
display: 'inline-block',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Icon from './Icon.vue'
|
||||
import { EMOJI_ICON_MAP_CONFIG, emojiRegExp } from '@/utils/im/emoji'
|
||||
import { computed, withDefaults } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ text: string }>(), {})
|
||||
|
||||
// 筛选出文本和表情
|
||||
const parseText = (text: string) => {
|
||||
if (!text) return []
|
||||
const regexEmoji = emojiRegExp
|
||||
const matches: {
|
||||
type: 'emoji' | 'text'
|
||||
value: string
|
||||
index: number
|
||||
}[] = []
|
||||
let match
|
||||
|
||||
while ((match = regexEmoji.exec(text)) !== null) {
|
||||
matches.push({
|
||||
type: 'emoji',
|
||||
value: match[0],
|
||||
index: match.index,
|
||||
})
|
||||
const fillText = ' '.repeat(match[0].length)
|
||||
text = text.replace(match[0], fillText)
|
||||
}
|
||||
|
||||
text = text.replace(regexEmoji, ' ')
|
||||
|
||||
if (text) {
|
||||
text
|
||||
.split(' ')
|
||||
.filter((item) => item.trim())
|
||||
.map((item) => {
|
||||
const index = text?.indexOf(item)
|
||||
matches.push({
|
||||
type: 'text',
|
||||
value: item,
|
||||
index,
|
||||
})
|
||||
const fillText = ' '.repeat(item.length)
|
||||
text = text.replace(item, fillText)
|
||||
})
|
||||
}
|
||||
|
||||
return matches
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
key: index + item.type,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const textArr = computed(() => {
|
||||
return parseText(props.text)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.msg-reply-text {
|
||||
font-size: 13px !important;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
width: 100%;
|
||||
display: inline;
|
||||
}
|
||||
.ellipsis {
|
||||
display: inline-block;
|
||||
flex-basis: 25px;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="modal" v-if="visible">
|
||||
<div class="mask" @tap="handleMaskClick"></div>
|
||||
<div class="content">
|
||||
<div class="title">{{ title }}</div>
|
||||
<slot></slot>
|
||||
<div class="buttons">
|
||||
<div class="button cancel" @tap="handleCancelClick">
|
||||
{{ cancelText }}
|
||||
</div>
|
||||
<div class="button confirm" @tap="handleConfirmClick">
|
||||
{{ confirmText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
confirmText: string
|
||||
cancelText: string
|
||||
visible: boolean
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel'])
|
||||
|
||||
const handleMaskClick = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
const handleCancelClick = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
z-index: 99999999999;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
touch-action: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 276px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
margin: 16px 0 0 16px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
border-top: 1px solid #e1e6e8;
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
color: #666666;
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
border-right: 1px solid #e1e6e8;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
color: #337eff;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<!-- domId 是要截图的容器 id -->
|
||||
<view class="my-html2canvas" :prop="domId" :change:prop="h2cRender.watchDomId">
|
||||
<!-- 用于触发 renderjs 截图 -->
|
||||
<text :prop="expOver" :change:prop="h2cRender.watchExpOver"></text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
domId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expOver: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 业务层调用这个方法开始截图
|
||||
h2cRenderDom() {
|
||||
// #ifdef H5
|
||||
// H5 直接在当前环境(有 DOM)里跑 renderDom
|
||||
this.$emit('renderDomInH5')
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 非 H5(APP 等)通过 expOver 触发 renderjs 中的截图逻辑
|
||||
this.expOver++
|
||||
// #endif
|
||||
},
|
||||
// renderjs 截图完成后会 callMethod 回调到这里
|
||||
renderOver(base64) {
|
||||
this.$emit('renderOver', base64)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- renderjs 只能内联 -->
|
||||
<script module="h2cRender" lang="renderjs">
|
||||
import html2canvas from 'html2canvas'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
domIdValue: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async renderDom() {
|
||||
try {
|
||||
const el = document.getElementById(this.domIdValue)
|
||||
if (!el) {
|
||||
console.error('[MyHtml2canvas] dom 未找到,请检查 domId:', this.domIdValue)
|
||||
return
|
||||
}
|
||||
const canvas = await html2canvas(el, {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
x: 0,
|
||||
y: 0,
|
||||
useCORS: true,
|
||||
scale: 2
|
||||
})
|
||||
const base64 = canvas.toDataURL('image/png', 1)
|
||||
this.$ownerInstance.callMethod('renderOver', base64)
|
||||
} catch (err) {
|
||||
console.error('[MyHtml2canvas] html2canvas 失败:', err && err.message)
|
||||
}
|
||||
},
|
||||
// script 部分通过 :prop 传入 domId
|
||||
watchDomId(newVal) {
|
||||
this.domIdValue = newVal
|
||||
},
|
||||
// expOver 变化时触发渲染
|
||||
watchExpOver(newVal) {
|
||||
if (newVal !== 0) {
|
||||
this.renderDom()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.my-html2canvas {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<!-- 样式兼容微信小程序 -->
|
||||
<div>
|
||||
<div
|
||||
class="nav-bar-wrapper"
|
||||
:style="{
|
||||
backgroundColor: backgroundColor || '#ffffff',
|
||||
backgroundImage: `url(${title})`,
|
||||
height: isWeixinApp ? '55px' : '40px',
|
||||
alignItems: isWeixinApp ? 'flex-end' : 'center',
|
||||
}"
|
||||
>
|
||||
<slot v-if="showLeft" name="left"></slot>
|
||||
<div v-else @tap="back">
|
||||
<Icon type="icon-zuojiantou" :size="22"></Icon>
|
||||
</div>
|
||||
<div class="title-container">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="subTitle" v-if="subTitle">{{ subTitle }}</div>
|
||||
<slot name="icon"></slot>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="isWeixinApp ? 'block-wx' : 'block'"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getUniPlatform } from '../utils'
|
||||
import Icon from './Icon.vue'
|
||||
import { withDefaults } from 'vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
subTitle?: string
|
||||
backgroundColor?: string
|
||||
showLeft?: boolean
|
||||
}>(),
|
||||
{
|
||||
subTitle: '',
|
||||
backgroundColor: '',
|
||||
}
|
||||
)
|
||||
|
||||
const isWeixinApp = getUniPlatform() === 'mp-weixin'
|
||||
|
||||
const back = () => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../pages/styles/common.scss';
|
||||
|
||||
.nav-bar-wrapper {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--status-bar-height) 10px 5px 10px;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.title-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 230px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 100%;
|
||||
height: calc(45px + var(--status-bar-height));
|
||||
}
|
||||
|
||||
.block-wx {
|
||||
width: 100%;
|
||||
height: calc(55px + var(--status-bar-height));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div v-if="!isConnected && text" class="network-alert">
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { autorun } from 'mobx'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { t } from '@/utils/im/i18n'
|
||||
import { V2NIMConst } from '@/utils/im/nim'
|
||||
|
||||
const isConnected = ref(true)
|
||||
const text = ref(t('connectingText'))
|
||||
// uni.onNetworkStatusChange((res) => {
|
||||
// if (!res.isConnected) {
|
||||
// isConnected.value = false;
|
||||
// text.value = t('offlineText');
|
||||
// } else {
|
||||
// text.value = t('connectingText');
|
||||
// }
|
||||
// });
|
||||
|
||||
onMounted(() => {
|
||||
if (
|
||||
uni.$UIKitStore?.connectStore?.connectStatus ===
|
||||
V2NIMConst.V2NIMConnectStatus.V2NIM_CONNECT_STATUS_CONNECTED
|
||||
) {
|
||||
isConnected.value = true
|
||||
} else if (
|
||||
uni.$UIKitStore?.connectStore?.connectStatus ===
|
||||
V2NIMConst.V2NIMConnectStatus.V2NIM_CONNECT_STATUS_DISCONNECTED
|
||||
) {
|
||||
isConnected.value = false
|
||||
text.value = t('offlineText')
|
||||
} else {
|
||||
isConnected.value = false
|
||||
text.value = t('connectingText')
|
||||
}
|
||||
})
|
||||
|
||||
const uninstallConnectWatch = autorun(() => {
|
||||
if (
|
||||
uni.$UIKitStore?.connectStore?.connectStatus ===
|
||||
V2NIMConst.V2NIMConnectStatus.V2NIM_CONNECT_STATUS_CONNECTED
|
||||
) {
|
||||
isConnected.value = true
|
||||
} else if (
|
||||
uni.$UIKitStore?.connectStore?.connectStatus ===
|
||||
V2NIMConst.V2NIMConnectStatus.V2NIM_CONNECT_STATUS_DISCONNECTED
|
||||
) {
|
||||
isConnected.value = false
|
||||
text.value = t('offlineText')
|
||||
} else {
|
||||
isConnected.value = false
|
||||
text.value = t('connectingText')
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
uninstallConnectWatch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.network-alert {
|
||||
font-size: 14px;
|
||||
background: #fee3e6;
|
||||
color: #fc596a;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div v-if="personList.length > 0" class="friend-select-wrapper">
|
||||
<div class="member-wrapper">
|
||||
<radio-group v-if="radio" @change="checkboxChange">
|
||||
<div
|
||||
class="member-item"
|
||||
v-for="item in personList"
|
||||
:key="item.accountId"
|
||||
>
|
||||
<radio
|
||||
class="checkbox"
|
||||
:value="item.accountId"
|
||||
:checked="item.checked"
|
||||
:disabled="
|
||||
item.disabled ||
|
||||
(selectAccount.length >= max &&
|
||||
!selectAccount.includes(item.accountId))
|
||||
"
|
||||
/>
|
||||
<Avatar class="user-avatar" size="36" :account="item.accountId" />
|
||||
<div class="user-name">
|
||||
<Appellation :account="item.accountId" :teamId="item.teamId" />
|
||||
</div>
|
||||
</div>
|
||||
</radio-group>
|
||||
<checkbox-group v-else @change="checkboxChange">
|
||||
<div
|
||||
class="member-item"
|
||||
v-for="item in personList"
|
||||
:key="item.accountId"
|
||||
>
|
||||
<checkbox
|
||||
class="checkbox"
|
||||
:value="item.accountId"
|
||||
:checked="item.checked"
|
||||
:disabled="
|
||||
item.disabled ||
|
||||
(selectAccount.length >= max &&
|
||||
!selectAccount.includes(item.accountId))
|
||||
"
|
||||
/>
|
||||
<Avatar class="user-avatar" size="36" :account="item.accountId" />
|
||||
<div class="user-name">
|
||||
<Appellation :account="item.accountId" :teamId="item.teamId" />
|
||||
</div>
|
||||
</div>
|
||||
</checkbox-group>
|
||||
</div>
|
||||
<div
|
||||
:style="{ border: '1px solid #ccc' }"
|
||||
v-if="!!showBtn"
|
||||
@tap="onBtnClick"
|
||||
class="ok-btn"
|
||||
>
|
||||
{{ btnText || t('okText') }}
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-else :text="t('noFriendText')"></Empty>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Avatar from './Avatar.vue'
|
||||
import Appellation from './Appellation.vue'
|
||||
import Empty from './Empty.vue'
|
||||
import { t } from '../utils/i18n'
|
||||
import { events } from '../utils/constants'
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
export type PersonSelectItem = {
|
||||
accountId: string
|
||||
teamId?: string
|
||||
disabled?: boolean
|
||||
checked?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
personList: PersonSelectItem[]
|
||||
showBtn?: boolean
|
||||
btnText?: string
|
||||
radio?: boolean
|
||||
max?: number
|
||||
}>(),
|
||||
{
|
||||
showBtn: true,
|
||||
btnText: '',
|
||||
radio: false,
|
||||
max: Number.MAX_SAFE_INTEGER,
|
||||
}
|
||||
)
|
||||
|
||||
const $emit = defineEmits<{
|
||||
(event: 'checkboxChange', selectList: string | string[]): void
|
||||
(event: 'onBtnClick'): void
|
||||
}>()
|
||||
|
||||
const onBtnClick = () => {
|
||||
uni.$emit(events.FRIEND_SELECT)
|
||||
$emit('onBtnClick')
|
||||
}
|
||||
|
||||
const checkboxChange = (event: any) => {
|
||||
const value = event.detail.value
|
||||
selectAccount.value = value
|
||||
$emit('checkboxChange', value)
|
||||
}
|
||||
|
||||
const selectAccount = ref<string[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
selectAccount.value = props.personList
|
||||
.filter((item) => item.checked)
|
||||
.map((item) => item.accountId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../pages/styles/common.scss';
|
||||
|
||||
.friend-select-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.member-wrapper {
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.member-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px 20px;
|
||||
// width: 100vw;
|
||||
|
||||
.user-avatar {
|
||||
margin: 0 14px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
max-width: 70%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ok-btn {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ok-btn-mp {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<view
|
||||
class="zb-tooltip"
|
||||
:style="{
|
||||
'--theme-bg-color': color,
|
||||
}"
|
||||
>
|
||||
<view class="zb_tooltip_content" @longpress.stop="handleClick">
|
||||
<slot></slot>
|
||||
<view
|
||||
class="zb_tooltip__mask"
|
||||
@longpress.stop
|
||||
v-show="isShow"
|
||||
@touchstart="close"
|
||||
></view>
|
||||
<view
|
||||
class="zb_tooltip__popper"
|
||||
@tap.stop="() => {}"
|
||||
:style="[
|
||||
style,
|
||||
{
|
||||
visibility: isShow ? 'visible' : 'hidden',
|
||||
color: color === 'white' ? '' : '#fff',
|
||||
boxShadow:
|
||||
color === 'white'
|
||||
? '0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d'
|
||||
: '',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<slot name="content">{{ content }}</slot>
|
||||
<!-- <view
|
||||
class="zb_popper__icon"
|
||||
:style="[arrowStyle]"
|
||||
:class="[
|
||||
{
|
||||
zb_popper__up: placement.indexOf('bottom') === 0,
|
||||
zb_popper__arrow: placement.indexOf('top') === 0,
|
||||
zb_popper__right: placement.indexOf('right') === 0,
|
||||
zb_popper__left: placement.indexOf('left') === 0,
|
||||
},
|
||||
]"
|
||||
>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Tooltip',
|
||||
props: {
|
||||
visible: Boolean,
|
||||
align: Boolean,
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133',
|
||||
},
|
||||
// placement: {
|
||||
// type: String,
|
||||
// default: 'top',
|
||||
// },
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isShow: this.visible,
|
||||
title: 'Hello',
|
||||
arrowLeft: 0,
|
||||
query: null,
|
||||
style: {},
|
||||
arrowStyle: {},
|
||||
placement: 'top',
|
||||
}
|
||||
},
|
||||
onLoad() {},
|
||||
watch: {
|
||||
isShow: {
|
||||
handler(val) {
|
||||
this.$emit('update:visible', val)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
visible: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.getPosition()
|
||||
})
|
||||
}
|
||||
this.isShow = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5
|
||||
window.addEventListener('click', () => {
|
||||
this.isShow = false
|
||||
})
|
||||
// #endif
|
||||
this.getPosition()
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.isShow = false
|
||||
},
|
||||
fixedWrap() {
|
||||
this.isShow = false
|
||||
},
|
||||
async handleClick() {
|
||||
if (this.isShow) {
|
||||
return (this.isShow = false)
|
||||
}
|
||||
await this.getPosition()
|
||||
this.isShow = true
|
||||
},
|
||||
getPosition() {
|
||||
return new Promise((resolve) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.selectAll('.zb_tooltip_content,.zb_tooltip__popper')
|
||||
.boundingClientRect(async (data) => {
|
||||
let { left, bottom, right, top, width, height } = data[0] || {}
|
||||
let obj1 = data[1] || {}
|
||||
let objStyle = {}
|
||||
let objStyle1 = {}
|
||||
if (top <= 300) {
|
||||
this.placement = 'bottom'
|
||||
} else {
|
||||
this.placement = 'top'
|
||||
}
|
||||
switch (this.placement) {
|
||||
case 'top':
|
||||
// if (obj1.width > width) {
|
||||
// objStyle.left = `-${(obj1.width - width + 120) / 2}px`
|
||||
// } else {
|
||||
// objStyle.left = `${Math.abs(obj1.width - width) / 2}px`
|
||||
// }
|
||||
if (this.align) {
|
||||
objStyle.left = '-100px'
|
||||
if (width < 90) {
|
||||
objStyle.left = '-200px'
|
||||
}
|
||||
} else {
|
||||
objStyle.left = '50px'
|
||||
}
|
||||
objStyle.bottom = `${height + 8}px`
|
||||
// objStyle.left = '30%'
|
||||
// objStyle1.left = obj1.width - 6 + 'px'
|
||||
break
|
||||
case 'bottom':
|
||||
// if (obj1.width > width) {
|
||||
// objStyle.left = `-${(obj1.width - width) / 2}px`
|
||||
// } else {
|
||||
// objStyle.left = `${Math.abs(obj1.width - width) / 2}px`
|
||||
// }
|
||||
if (this.align) {
|
||||
objStyle.left = '-100px'
|
||||
if (width < 100) {
|
||||
objStyle.left = '-200px'
|
||||
}
|
||||
} else {
|
||||
objStyle.left = '50px'
|
||||
}
|
||||
// objStyle.left = `-${obj1.width - width - 100}px`
|
||||
objStyle.top = `${height + 8}px`
|
||||
// objStyle1.left = obj1.width / 2 - 6 + 'px'
|
||||
break
|
||||
// case 'top-start':
|
||||
// objStyle.left = `0px`
|
||||
// objStyle.bottom = `${height + 8}px`
|
||||
// break;
|
||||
// case 'top-end':
|
||||
// objStyle.right = `0px`
|
||||
// objStyle.bottom = `${height + 8}px`
|
||||
// objStyle1.right = `8px`
|
||||
// break;
|
||||
// case 'bottom-start':
|
||||
// objStyle.left = `0px`
|
||||
// objStyle.top = `${height + 8}px`
|
||||
// objStyle1.left = `8px`
|
||||
// break;
|
||||
|
||||
// case 'bottom-end':
|
||||
// objStyle.right = `0px`
|
||||
// objStyle.top = `${height + 8}px`
|
||||
// objStyle1.right = `8px`
|
||||
// break;
|
||||
|
||||
// case 'right':
|
||||
// objStyle.left = `${width + 8}px`
|
||||
// if (obj1.height > height) {
|
||||
// objStyle.top = `-${(obj1.height - height) / 2}px`
|
||||
// } else {
|
||||
// objStyle.top = `${Math.abs((obj1.height - height) / 2)}px`
|
||||
// }
|
||||
|
||||
// objStyle1.top = `${obj1.height / 2 - 6}px`
|
||||
// break;
|
||||
// case 'right-start':
|
||||
// objStyle.left = `${width + 8}px`
|
||||
// objStyle.top = `0px`
|
||||
// objStyle1.top = `8px`
|
||||
// break;
|
||||
|
||||
// case 'right-end':
|
||||
// objStyle.left = `${width + 8}px`
|
||||
// objStyle.bottom = `0px`
|
||||
// objStyle1.bottom = `8px`
|
||||
// break;
|
||||
|
||||
// case 'left':
|
||||
// objStyle.right = `${width + 8}px`
|
||||
|
||||
// if (obj1.height > height) {
|
||||
// objStyle.top = `-${(obj1.height - height) / 2}px`
|
||||
// } else {
|
||||
// objStyle.top = `${Math.abs((obj1.height - height) / 2)}px`
|
||||
// }
|
||||
|
||||
// objStyle1.top = `${obj1.height / 2 - 6}px`
|
||||
// break;
|
||||
|
||||
// case 'left-start':
|
||||
// objStyle.right = `${width + 8}px`
|
||||
// objStyle.top = `0px`
|
||||
// objStyle1.top = `8px`
|
||||
// break;
|
||||
|
||||
// case 'left-end':
|
||||
// objStyle.right = `${width + 8}px`
|
||||
// objStyle.bottom = `0px`
|
||||
// objStyle1.bottom = `8px`
|
||||
// break;
|
||||
}
|
||||
this.style = objStyle
|
||||
// 三角形箭头
|
||||
this.arrowStyle = objStyle1
|
||||
resolve()
|
||||
})
|
||||
.exec()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$theme-bg-color: var(--theme-bg-color);
|
||||
|
||||
.zb-tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.zb_tooltip_content {
|
||||
height: 100%;
|
||||
/* float: left; */
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
|
||||
.zb_tooltip__popper {
|
||||
/* transform-origin: center top; */
|
||||
background: $theme-bg-color;
|
||||
|
||||
visibility: hidden;
|
||||
// color:'#fff';
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 10px;
|
||||
min-width: 10px;
|
||||
word-wrap: break-word;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.zb_tooltip__mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: rgba(256, 256, 256, 0);
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.zb_popper__icon {
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.zb_popper__arrow {
|
||||
bottom: -5px;
|
||||
/* transform-origin: center top; */
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid $theme-bg-color;
|
||||
}
|
||||
|
||||
.zb_popper__right {
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-right: 6px solid $theme-bg-color;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.zb_popper__left {
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-left: 6px solid $theme-bg-color;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.zb_popper__up {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid $theme-bg-color;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
pointer-events: auto;
|
||||
background: red;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="user-wrapper">
|
||||
<div class="avatar-wrapper">
|
||||
<Avatar v-if="props.account" size="70" :account="props.account"></Avatar>
|
||||
</div>
|
||||
<div class="account-wrapper">
|
||||
<div v-if="alias">
|
||||
<div class="main">{{ alias }}</div>
|
||||
<div class="deputy">{{ t('name') }}:{{ nick || account }}</div>
|
||||
</div>
|
||||
<div v-else class="main">{{ nick || account }}</div>
|
||||
<div class="deputy">
|
||||
{{ t('accountText') }}:{{ props.account }}
|
||||
<div @tap.stop="copyAccount">
|
||||
<Icon
|
||||
class="copy-icon"
|
||||
type="icon-fuzhi1"
|
||||
color="#A6ADB6"
|
||||
:size="20"
|
||||
></Icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Avatar from './Avatar.vue'
|
||||
import Icon from './Icon.vue'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { autorun } from 'mobx'
|
||||
import { t } from '../utils/i18n'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
account?: string
|
||||
nick?: string
|
||||
}>(),
|
||||
{
|
||||
account: '',
|
||||
nick: '',
|
||||
}
|
||||
)
|
||||
const alias = ref<string>()
|
||||
let uninstallFriendsWatch = () => {}
|
||||
|
||||
onLoad((props) => {
|
||||
let account = props ? props.account : ''
|
||||
uninstallFriendsWatch = autorun(() => {
|
||||
const friend = { ...uni.$UIKitStore.friendStore.friends.get(account) }
|
||||
alias.value = friend ? friend.alias : ''
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
uninstallFriendsWatch()
|
||||
})
|
||||
|
||||
const copyAccount = () => {
|
||||
uni.setClipboardData({
|
||||
data: props.account,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: t('copySuccessText'),
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: t('copyFailText'),
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../pages/styles/common.scss';
|
||||
|
||||
.user-wrapper {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
height: 140px;
|
||||
align-items: center;
|
||||
|
||||
.avatar-wrapper {
|
||||
margin: 0 15px;
|
||||
flex: 0 0 70px;
|
||||
}
|
||||
|
||||
.account-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding-right: 40px;
|
||||
|
||||
.main {
|
||||
font-size: 20px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.deputy {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.copy-icon {
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 当父组件传递的prop属性数据发生变化的时候 ,生成海报图片-->
|
||||
<view :prop="domId" :change:prop="html2canvas.emitData" class="html2canvas">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { base64ToPath } from '@/utils/image-tools.js';
|
||||
export default {
|
||||
props: {
|
||||
domId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mounted:function(){
|
||||
console.log("组件加载完成");
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 接收renderjs发回的数据
|
||||
async receiveRenderData(imgData) {
|
||||
let posterHeight=imgData.height;
|
||||
let posterWidth=imgData.width;
|
||||
let imgPath =await base64ToPath(imgData.imgVal, '.jpeg');
|
||||
this.$emit('renderFinish', {"posterPath":imgPath,"posterHeight":posterHeight,"posterWidth":posterWidth});
|
||||
uni.hideLoading();
|
||||
},
|
||||
showLoading() {
|
||||
uni.showLoading({
|
||||
title: "正在生成导出",
|
||||
icon: "none",
|
||||
mask: true,
|
||||
duration: 3000
|
||||
})
|
||||
},
|
||||
hideLoading() {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script module="html2canvas" lang="renderjs">
|
||||
import html2canvas from 'html2canvas';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted:function(){
|
||||
console.log("视图层渲染完成");
|
||||
},
|
||||
methods: {
|
||||
// 发送数据到逻辑层,下面这里最好是加一个settimeout延迟,因为主界面数据还没加载渲染完成
|
||||
emitData(domId) {
|
||||
this.$ownerInstance.callMethod('showLoading', true);
|
||||
const dom = document.getElementById('poster');
|
||||
console.log("dom",dom);
|
||||
let width= dom.offsetWidth;
|
||||
let height=dom.offsetHeight;
|
||||
let windowHeight= uni.getSystemInfoSync().windowHeight;
|
||||
let windowWidth=uni.getSystemInfoSync().windowWidth;
|
||||
html2canvas(dom, {
|
||||
width:width,
|
||||
height:height,
|
||||
//scale:2.5, // 缩放倍数
|
||||
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
|
||||
scrollX: 0,
|
||||
x:0,
|
||||
y:0,
|
||||
windowWidth:windowWidth,
|
||||
windowHeight:windowHeight,
|
||||
useCORS: true, //支持跨域,但好像没什么用
|
||||
}).then((canvas) => {
|
||||
let imgVal= canvas.toDataURL('image/png');
|
||||
this.$ownerInstance.callMethod('receiveRenderData',{imgVal:imgVal,height:height,width:width});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 当父组件传递的prop属性数据发生变化的时候 ,生成海报图片-->
|
||||
<view :prop="domId" :change:prop="html2canvas.emitData" class="html2canvas">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { base64ToPath } from '@/utils/image-tools.js';
|
||||
export default {
|
||||
props: {
|
||||
domId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mounted:function(){
|
||||
console.log("组件加载完成");
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 接收renderjs发回的数据
|
||||
async receiveRenderData(imgData) {
|
||||
let posterHeight=imgData.height;
|
||||
let posterWidth=imgData.width;
|
||||
let imgPath =await base64ToPath(imgData.imgVal, '.jpeg');
|
||||
this.$emit('renderFinish', {"posterPath":imgPath,"posterHeight":posterHeight,"posterWidth":posterWidth});
|
||||
uni.hideLoading();
|
||||
},
|
||||
showLoading() {
|
||||
uni.showLoading({
|
||||
title: "正在生成导出",
|
||||
icon: "none",
|
||||
mask: true,
|
||||
duration: 3000
|
||||
})
|
||||
},
|
||||
hideLoading() {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script module="html2canvas" lang="renderjs">
|
||||
import html2canvas from 'html2canvas';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted:function(){
|
||||
console.log("视图层渲染完成");
|
||||
},
|
||||
methods: {
|
||||
// 发送数据到逻辑层,下面这里最好是加一个settimeout延迟,因为主界面数据还没加载渲染完成
|
||||
emitData(domId) {
|
||||
this.$ownerInstance.callMethod('showLoading', true);
|
||||
const dom = document.getElementById('poster');
|
||||
console.log("dom",dom);
|
||||
let width= dom.offsetWidth;
|
||||
let height=dom.offsetHeight;
|
||||
let windowHeight= uni.getSystemInfoSync().windowHeight;
|
||||
let windowWidth=uni.getSystemInfoSync().windowWidth;
|
||||
html2canvas(dom, {
|
||||
width:width,
|
||||
height:height,
|
||||
//scale:2.5, // 缩放倍数
|
||||
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
|
||||
scrollX: 0,
|
||||
x:0,
|
||||
y:0,
|
||||
windowWidth:windowWidth,
|
||||
windowHeight:windowHeight,
|
||||
useCORS: true, //支持跨域,但好像没什么用
|
||||
}).then((canvas) => {
|
||||
let imgVal= canvas.toDataURL('image/png');
|
||||
this.$ownerInstance.callMethod('receiveRenderData',{imgVal:imgVal,height:height,width:width});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<view class="dialog-overlay" v-if="visible" @click="handleOverlayClick">
|
||||
<view class="dialog-container" @click.stop>
|
||||
<!-- 弹窗标题 -->
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title" :style="{ color: titleColor }">{{ title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<view class="dialog-content">
|
||||
<slot name="content">
|
||||
<view class="default-content" >
|
||||
<view v-for="item in formatContent(content)" :key="item">{{item}}</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 弹窗底部按钮 -->
|
||||
<view class="dialog-footer cancelFooter" v-if="showCancel">
|
||||
<view
|
||||
class="dialog-btn"
|
||||
|
||||
@click="oncancel"
|
||||
>
|
||||
<text class="btn-text">{{cancelText}}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="dialog-footer okFooter" v-else-if="showConfirm">
|
||||
<view
|
||||
class="dialog-btn okBtn"
|
||||
|
||||
@click="onconfirm"
|
||||
>
|
||||
<text class="btn-text">{{confirmText}}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="dialog-footer" v-else>
|
||||
<view
|
||||
class="dialog-btn cancel"
|
||||
|
||||
@click="oncancel"
|
||||
>
|
||||
<text class="btn-text">{{ cancelText }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="dialog-btn confirm"
|
||||
@click="onconfirm"
|
||||
>
|
||||
<text class="btn-text">{{ confirmText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#8B2316'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '温馨提示'
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
cancelText:{
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText:{
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
showCancel:{
|
||||
type:Boolean,
|
||||
default:false,
|
||||
},
|
||||
showConfirm:{
|
||||
type:Boolean,
|
||||
default:false,
|
||||
},
|
||||
closeOnOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const formatContent=(val)=>{
|
||||
console.log(val)
|
||||
if(!val) return [];
|
||||
// 将字符串按 \n 切割成数组,并过滤掉空字符串
|
||||
const lines = val.split('<br>').filter(line => line.trim() !== '');
|
||||
console.log('格式化后的内容数组:', lines);
|
||||
return lines;
|
||||
}
|
||||
const emit = defineEmits(['close','confirm'])
|
||||
|
||||
|
||||
|
||||
const oncancel=()=>{
|
||||
emit('close');
|
||||
|
||||
}
|
||||
const onconfirm=()=>{
|
||||
emit('confirm')
|
||||
emit('close');
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
position: relative;
|
||||
z-index:100;
|
||||
background-color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
width: 92%;
|
||||
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
padding: 40rpx 40rpx 20rpx;
|
||||
text-align: center;
|
||||
.dialog-title {
|
||||
font-size: 36rpx;
|
||||
color: #8B2316;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 40rpx;
|
||||
min-height: 120rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.default-content {
|
||||
white-space:pre-wrap;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
padding:0 40rpx;
|
||||
|
||||
.dialog-btn {
|
||||
flex: 1;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
.btn-text {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
.btn-text {
|
||||
color: #8B2316;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&.default {
|
||||
.btn-text {
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cancelFooter,.okFooter{
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<view class="emptybox">
|
||||
<view class="empty-image-wrap" :style="imageWrapStyle">
|
||||
<up-image
|
||||
:src="emptyImage || emptyImg"
|
||||
:width="renderWidth"
|
||||
:height="renderHeight"
|
||||
:mode="imgMode"
|
||||
></up-image>
|
||||
</view>
|
||||
<text class="empty-text">{{ emptyDesc }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import emptyImg from "@/static/icon_empty.png"
|
||||
const props = defineProps({
|
||||
emptyImage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
imgWidth: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
imgHeight: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
emptyDesc: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
}
|
||||
})
|
||||
|
||||
const normalizeSize = (size) => {
|
||||
if (size === '' || size === null || size === undefined) return '';
|
||||
// 直传字符串(如 50%、206rpx、120px),数字交给 up-image 按组件规则处理
|
||||
return typeof size === 'string' ? size.trim() : size;
|
||||
};
|
||||
const isPercentSize = (size) => typeof size === 'string' && size.endsWith('%');
|
||||
|
||||
const imgWidth = computed(() => {
|
||||
const width = normalizeSize(props.imgWidth);
|
||||
const height = normalizeSize(props.imgHeight);
|
||||
if (width !== '') return width;
|
||||
if (height === '') return '206rpx';
|
||||
return '';
|
||||
});
|
||||
const imgHeight = computed(() => normalizeSize(props.imgHeight));
|
||||
const renderWidth = computed(() => (isPercentSize(imgWidth.value) ? '100%' : imgWidth.value));
|
||||
const renderHeight = computed(() => (isPercentSize(imgHeight.value) ? '100%' : imgHeight.value));
|
||||
const imageWrapStyle = computed(() => {
|
||||
const style = {};
|
||||
if (isPercentSize(imgWidth.value)) style.width = imgWidth.value;
|
||||
if (isPercentSize(imgHeight.value)) style.height = imgHeight.value;
|
||||
return style;
|
||||
});
|
||||
const imgMode = computed(() => {
|
||||
// 只传宽/高时按比例缩放,避免图片被拉伸
|
||||
if (imgWidth.value && !imgHeight.value) return 'widthFix';
|
||||
if (!imgWidth.value && imgHeight.value) return 'heightFix';
|
||||
return 'aspectFit';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.emptybox{
|
||||
width:100%;
|
||||
height:800rpx;
|
||||
min-width:600rpx;
|
||||
display: flex;
|
||||
margin:0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
.empty-image-wrap{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.empty-text{
|
||||
font-size: 30rpx;
|
||||
color:#999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbox">
|
||||
<view class="status_bar"></view>
|
||||
<uni-nav-bar
|
||||
:left-icon="showLeft?'left':''"
|
||||
:title="title"
|
||||
@clickLeft="goBack"
|
||||
color="#8B2316"
|
||||
:border="false"
|
||||
backgroundColor="#eeeeee"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
const props=defineProps({
|
||||
showLeft:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
noticeBack:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
title:{
|
||||
type:String,
|
||||
default:''
|
||||
}
|
||||
})
|
||||
// 方法
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
fail() {
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navbox{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height:(var(--status-bar-height)+44px);
|
||||
z-index: 9999;
|
||||
}
|
||||
.status_bar{
|
||||
height: var(--status-bar-height);
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file 拖动进度提示组件
|
||||
*/
|
||||
<template>
|
||||
<view class="plv-player-drag-tips">
|
||||
<view class="plv-player-drag-tips__box">
|
||||
<image class="plv-player-drag-tips__direction-img" :src="imgSrc"
|
||||
@error="imageError">
|
||||
</image>
|
||||
<text class="plv-player-drag-tips__time">
|
||||
{{changeTimeFormat}} / {{durationFormat}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '@/common/util.js';
|
||||
import backImg from '@/static/skin/back.png';
|
||||
import forwardImg from '@/static/skin/forward.png'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
currentTime: {
|
||||
type: Number
|
||||
},
|
||||
|
||||
duration: {
|
||||
type: Number
|
||||
},
|
||||
|
||||
changeTime: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isForward: false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
changeTime(newValue, oldValue){
|
||||
this.isForward = newValue > oldValue;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
timeFormatType() {
|
||||
this.duration >= 3600 ? 2 : 1;
|
||||
},
|
||||
|
||||
changeTimeFormat() {
|
||||
return utils.formatTimeByDuration(this.changeTime, this.timeFormatType);
|
||||
},
|
||||
|
||||
durationFormat() {
|
||||
return utils.formatTimeByDuration(this.duration, this.timeFormatType)
|
||||
},
|
||||
|
||||
imgSrc() {
|
||||
return this.isForward ? forwardImg : backImg;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.plv-player-drag-tips {
|
||||
width: 250rpx;
|
||||
height: 160rpx;
|
||||
background: rgba(0,0,0,.7);
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.plv-player-drag-tips__box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plv-player-drag-tips__direction-img {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.plv-player-drag-tips__time {
|
||||
font-size: 24rpx;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<view class="plv-player-gesture"
|
||||
@touchstart="touchstart"
|
||||
@touchmove="touchmove"
|
||||
@longpress="handleLongPress"
|
||||
@touchcancel="handleTouchCancel"
|
||||
@touchend="touchend">
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 屏幕宽度(物理像素)
|
||||
holeWidth: Number,
|
||||
// 视频总时长
|
||||
duration: Number,
|
||||
// 视频当前播放时间
|
||||
currentTime: Number
|
||||
},
|
||||
|
||||
computed: {
|
||||
halfWidth() {
|
||||
return parseInt(this.holeWidth / 2);
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// 是否在左区域按下手势
|
||||
downLeft: false,
|
||||
// 滑动方向
|
||||
swipeDir: null
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.addHandler();
|
||||
},
|
||||
|
||||
methods:{
|
||||
addHandler() {
|
||||
// 增加父容器左右滑动监听
|
||||
let finalTime = -1;
|
||||
// 计算左右滑动seek进度
|
||||
const countTime = (position) => {
|
||||
const { holeWidth, duration, currentTime } = this;
|
||||
const precent = Math.abs(position) / holeWidth;
|
||||
const nowTime = currentTime || 0;
|
||||
const constSecond = duration < 60 ? duration : 60;
|
||||
let plusTime = parseInt(Math.abs(precent * constSecond));
|
||||
if (position < 0) plusTime = plusTime * -1;
|
||||
finalTime = nowTime + plusTime;
|
||||
finalTime = this.limit(finalTime, 0, duration);
|
||||
return finalTime;
|
||||
};
|
||||
|
||||
this.addGesture((type, position) => {
|
||||
switch (type) {
|
||||
// 下滑
|
||||
case -1:
|
||||
// console.log('//下滑');
|
||||
this.$emit('onGestureEvent', {
|
||||
type: this.downLeft ? 'LEFT_DOWN' : 'RIGHT_DOWN',
|
||||
position
|
||||
});
|
||||
break;
|
||||
// 上滑
|
||||
case -2:
|
||||
// console.log('//上滑');
|
||||
this.$emit('onGestureEvent', {
|
||||
type: this.downLeft ? 'LEFT_UP' : 'RIGHT_UP',
|
||||
position
|
||||
});
|
||||
break;
|
||||
// 左滑
|
||||
case 0:
|
||||
// console.log('//左滑');
|
||||
this.swipeDir = type;
|
||||
countTime(position);
|
||||
this.$emit('onGestureEvent', {
|
||||
type: 'SEEK_TIME_UPDATE',
|
||||
finalTime
|
||||
});
|
||||
break;
|
||||
// 右滑
|
||||
case 1:
|
||||
// console.log('//右滑');
|
||||
this.swipeDir = type;
|
||||
countTime(position);
|
||||
this.$emit('onGestureEvent', {
|
||||
type: 'SEEK_TIME_UPDATE',
|
||||
finalTime
|
||||
});
|
||||
break;
|
||||
// touchend for swipe
|
||||
case 3:
|
||||
if (this.swipeDir > -1) {
|
||||
this.$emit('onGestureEvent', {
|
||||
type: this.swipeDir ? 'SWIPE_RIGHT' : 'SWIPE_LEFT'
|
||||
});
|
||||
this.swipeDir = -1;
|
||||
}
|
||||
|
||||
if (finalTime > -1) {
|
||||
this.$emit('onGestureSeekTo', finalTime);
|
||||
finalTime = -1;
|
||||
}
|
||||
break;
|
||||
// doubleTap
|
||||
case 4:
|
||||
// console.log('>>>> 双击');
|
||||
this.$emit('onGestureEvent', {
|
||||
type: 'DOUBLE_CLICK'
|
||||
});
|
||||
break;
|
||||
// tap
|
||||
case 5:
|
||||
// console.log('>>>> 单击');
|
||||
this.handleClick();
|
||||
break;
|
||||
// longTap
|
||||
case 6:
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
addGesture(callback) {
|
||||
const { options } = this;
|
||||
let startX, startY, moveEndX, moveEndY, X, Y, position, hasType;
|
||||
let T, lastTap, isJustTouch = true;
|
||||
|
||||
|
||||
const excuteCb = (type) => {
|
||||
// type 手势类型 -2 上滑 -1 下滑 0 左滑 1 右滑 2 just touch 3 touchend 4 doubleTap 5 tap 6 longTap
|
||||
if (callback && typeof callback === 'function')
|
||||
callback(type, position);
|
||||
};
|
||||
|
||||
const YPlus = 20;
|
||||
const YReduce = YPlus * -1;
|
||||
const XPlus = 50;
|
||||
const XReduce = XPlus * -1;
|
||||
|
||||
const isXchange = () => {
|
||||
if (X > XPlus || X < XReduce) {
|
||||
position = X;
|
||||
hasType = 'X';
|
||||
isJustTouch = false;
|
||||
}
|
||||
|
||||
if (X > XPlus) excuteCb(1);
|
||||
// 左滑
|
||||
else if (X < XReduce) excuteCb(0);
|
||||
};
|
||||
|
||||
const isYchange = () => {
|
||||
if (Y > YPlus || Y < YReduce) {
|
||||
position = Y;
|
||||
hasType = 'Y';
|
||||
isJustTouch = false;
|
||||
}
|
||||
|
||||
if (Y > YPlus) excuteCb(-1);
|
||||
// 上滑
|
||||
else if (Y < YReduce) excuteCb(-2);
|
||||
};
|
||||
|
||||
const countDirect = () => {
|
||||
if (hasType) {
|
||||
hasType === 'X' ? isXchange() : isYchange();
|
||||
return;
|
||||
}
|
||||
isXchange();
|
||||
isYchange();
|
||||
};
|
||||
|
||||
this.handleTouch = (type, e) => {
|
||||
switch(type) {
|
||||
case 'start':
|
||||
startX = e.changedTouches[0].pageX;
|
||||
startY = e.changedTouches[0].pageY;
|
||||
T = Date.now();
|
||||
hasType = '';
|
||||
isJustTouch = true;
|
||||
this.downLeft = startX < this.halfWidth ? true : false;
|
||||
|
||||
break;
|
||||
case 'move':
|
||||
moveEndX = e.changedTouches[0].pageX;
|
||||
moveEndY = e.changedTouches[0].pageY;
|
||||
X = moveEndX - startX;
|
||||
Y = moveEndY - startY;
|
||||
countDirect();
|
||||
break;
|
||||
case 'end':
|
||||
if (isJustTouch)
|
||||
if (lastTap && Date.now() - lastTap <= 300)
|
||||
excuteCb(4);
|
||||
else if (Date.now() - T < 1000)
|
||||
excuteCb(5);
|
||||
else
|
||||
excuteCb(6);
|
||||
else
|
||||
excuteCb(3);
|
||||
|
||||
lastTap = Date.now();
|
||||
isJustTouch = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
handleClick() {
|
||||
this.$emit('onGestureClick');
|
||||
},
|
||||
|
||||
touchstart(e) {
|
||||
this.handleTouch('start', e);
|
||||
},
|
||||
|
||||
touchmove(e) {
|
||||
this.handleTouch('move', e);
|
||||
},
|
||||
|
||||
touchend(e) {
|
||||
this.handleTouch('end', e);
|
||||
this.$emit('onTouchEnd');
|
||||
},
|
||||
|
||||
handleTouchCancel() {
|
||||
this.$emit('onTouchCancel');
|
||||
},
|
||||
|
||||
handleLongPress() {
|
||||
this.$emit('onLongPress');
|
||||
},
|
||||
|
||||
/**
|
||||
* 控制最小值,最大值
|
||||
* @param num
|
||||
* @param min
|
||||
* @param max
|
||||
* @returns {*}
|
||||
*/
|
||||
limit(num, min, max) {
|
||||
if (num < min) return min;
|
||||
if (num > max) return max;
|
||||
return num;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.plv-player-gesture {
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.plv-player-gesture__left {
|
||||
flex: 5;
|
||||
}
|
||||
|
||||
.plv-player-gesture__right {
|
||||
flex: 5;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<view v-if="isShow" class="plv-player-icon">
|
||||
<image class="plv-player-icon__img" :src="imgUrl"></image>
|
||||
<text class="plv-player-icon__text">{{ precent + '%' }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
imgUrl: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
precent: {
|
||||
type: 'Number',
|
||||
default: 100,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
precent(num) {
|
||||
if (!this.isShow) this.isShow = true;
|
||||
this.startClock();
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isShow: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
startClock() {
|
||||
this.clearClock();
|
||||
|
||||
this.clock = setTimeout(() => {
|
||||
this.isShow = false;
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
clearClock() {
|
||||
if (this.clock) clearTimeout(this.clock);
|
||||
this.clock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.plv-player-icon {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.plv-player-icon__img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.plv-player-icon__text {
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<view v-if="isShow" class="plv-player-volume-wrap">
|
||||
<image class="plv-player-volume__img" :src="value === 0 ? muteImgUrl : imgUrl "></image>
|
||||
<view class="plv-player-volume__progress">
|
||||
<view class="plv-player-volume__progress__played" :style="'flex:' + value"></view>
|
||||
<view class="plv-player-volume__progress_surplus" :style="'flex:' + valueLeft"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
imgUrl: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
muteImgUrl: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: 'Number',
|
||||
default: 0.5,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(num) {
|
||||
if (!this.isShow) this.isShow = true;
|
||||
this.startClock();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
valueLeft() {
|
||||
return 1 - this.value;
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isShow: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
startClock() {
|
||||
this.clearClock();
|
||||
|
||||
this.clock = setTimeout(() => {
|
||||
this.isShow = false;
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
clearClock() {
|
||||
if (this.clock) clearTimeout(this.clock);
|
||||
this.clock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.plv-player-volume-wrap {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.plv-player-volume__img {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.plv-player-volume__progress {
|
||||
width: 260rpx;
|
||||
height: 8rpx;
|
||||
flex-direction: row;
|
||||
background-color: #09BB07;
|
||||
}
|
||||
|
||||
.plv-player-volume__progress__played {
|
||||
background-color: #007AFF;
|
||||
}
|
||||
|
||||
.plv-player-volume__progress_surplus {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,855 @@
|
||||
/**
|
||||
* 图片编辑器-手势监听
|
||||
* 1. 支持编译到app-vue(uni-app 2.5.5及以上版本)、H5上
|
||||
*/
|
||||
/** 图片偏移量 */
|
||||
var offset = { x: 0, y: 0 };
|
||||
/** 图片缩放比例 */
|
||||
var scale = 1;
|
||||
/** 图片最小缩放比例 */
|
||||
var minScale = 1;
|
||||
/** 图片旋转角度 */
|
||||
var rotate = 0;
|
||||
/** 触摸点 */
|
||||
var touches = [];
|
||||
/** 图片布局信息 */
|
||||
var img = {};
|
||||
/** 系统信息 */
|
||||
var sys = {};
|
||||
/** 裁剪区域布局信息 */
|
||||
var area = {};
|
||||
/** 触摸行为类型 */
|
||||
var touchType = '';
|
||||
/** 操作角的位置 */
|
||||
var activeAngle = 0;
|
||||
/** 裁剪区域布局信息偏移量 */
|
||||
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
/** 元素ID */
|
||||
var elIds = {
|
||||
'imageStyles': 'crop-image',
|
||||
'maskStylesList': 'crop-mask-block',
|
||||
'borderStyles': 'crop-border',
|
||||
'circleBoxStyles': 'crop-circle-box',
|
||||
'circleStyles': 'crop-circle',
|
||||
'gridStylesList': 'crop-grid',
|
||||
'angleStylesList': 'crop-angle',
|
||||
}
|
||||
/** 记录上次初始化时间戳,排除APP重复更新 */
|
||||
var timestamp = 0;
|
||||
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
|
||||
// #ifdef H5
|
||||
var platform = 'H5';
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
var platform = 'APP';
|
||||
// #endif
|
||||
/** 容错值 */
|
||||
var fault = 0.000001;
|
||||
/**
|
||||
* 获取a、b两数中的最小正数
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
function minimum(a, b) {
|
||||
if (a > 0 && b < 0) return a;
|
||||
if (a < 0 && b > 0) return b;
|
||||
if (a > 0 && b > 0) return Math.min(a, b);
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* 在容错访问内获取n近似值
|
||||
* @param n
|
||||
*/
|
||||
function num(n) {
|
||||
var m = parseFloat((n).toFixed(6));
|
||||
return m === fault || m === -fault ? 0 : m;
|
||||
}
|
||||
/**
|
||||
* 比较a值在容错值范围内是否等于b值
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
function equalsByFault(a, b) {
|
||||
return Math.abs(a - b) <= fault;
|
||||
}
|
||||
/**
|
||||
* 比较a值在容错值范围内是否小于b值
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
function lessThanByFault(a, b) {
|
||||
var c = a - b;
|
||||
return c < 0 ? c < -fault : c < fault;
|
||||
}
|
||||
/**
|
||||
* 验证并获取有效最大值
|
||||
* @param v
|
||||
* @param max
|
||||
* @param isInclude
|
||||
* @param x
|
||||
* @param y
|
||||
* @param rate
|
||||
* @returns
|
||||
*/
|
||||
function validMax(v, max, isInclude, x, y, rate) {
|
||||
if(typeof max === 'number') {
|
||||
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||
var n = num(max * rate);
|
||||
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||
}
|
||||
return max;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
/**
|
||||
* 样式对象转字符串
|
||||
* @param {Object} style 样式对象
|
||||
*/
|
||||
function styleToString(style) {
|
||||
if(typeof style === 'string') return style;
|
||||
var str = '';
|
||||
for (let k in style) {
|
||||
str += k + ':' + style[k] + ';';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Object} instance 页面实例对象
|
||||
* @param {Object} key 要修改样式的key
|
||||
* @param {Object|Array} style 样式
|
||||
*/
|
||||
function setStyle(instance, key, style) {
|
||||
// console.log('setStyle', instance, key, JSON.stringify(style))
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP') {
|
||||
if(Object.prototype.toString.call(style) === '[object Array]') {
|
||||
for (var i = 0, len = style.length; i < len; i++) {
|
||||
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
|
||||
el && (el.style = styleToString(style[i]));
|
||||
}
|
||||
} else {
|
||||
var el = window.document.getElementById(elIds[key]);
|
||||
el && (el.style = styleToString(style));
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5') instance[key] = style;
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 触发页面实例指定方法
|
||||
* @param {Object} instance 页面实例对象
|
||||
* @param {Object} name 方法名称
|
||||
* @param {Object} obj 传递参数
|
||||
*/
|
||||
function callMethod(instance, name, obj) {
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP') instance.callMethod(name, obj);
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5') instance[name](obj);
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 计算两点间距
|
||||
* @param {Object} touches 触摸点信息
|
||||
*/
|
||||
function getDistanceByTouches(touches) {
|
||||
// 根据勾股定理求两点间距离
|
||||
var a = touches[1].pageX - touches[0].pageX;
|
||||
var b = touches[1].pageY - touches[0].pageY;
|
||||
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||
// 求两点间的中点坐标
|
||||
// 1. a、b可能为负值
|
||||
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||
var x = touches[1].pageX - a / 2;
|
||||
var y = touches[1].pageY - b / 2;
|
||||
return { c, x, y };
|
||||
};
|
||||
|
||||
/**
|
||||
* 修正取值
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @param {Object} c
|
||||
* @param {Object} reverse 是否反向
|
||||
*/
|
||||
function correctValue(a, b, c, reverse) {
|
||||
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
* @param {Object} xReverse x是否反向
|
||||
* @param {Object} yReverse y是否反向
|
||||
*/
|
||||
function checkRotateRange(e, xReverse, yReverse) {
|
||||
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||
return {
|
||||
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
*/
|
||||
function checkRange(e) {
|
||||
var r = rotate / 90 % 2;
|
||||
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||
if (area.width === area.height) {
|
||||
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||
}
|
||||
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||
if (img.width < area.height || img.height < area.width) {
|
||||
if (area.width < area.height && img.width < img.height) {
|
||||
return isInclude
|
||||
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||
: checkRotateRange(e, false, true);
|
||||
}
|
||||
if (area.height < area.width && img.height < img.width) {
|
||||
return isInclude
|
||||
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||
: checkRotateRange(e, true, false);
|
||||
}
|
||||
}
|
||||
if (img.height >= area.width && img.width >= area.height) {
|
||||
return checkRotateRange(e, false, false);
|
||||
}
|
||||
if (isInclude) {
|
||||
return area.height < area.width
|
||||
? checkRotateRange(e, true, true)
|
||||
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||
}
|
||||
if (img.height < area.width && !img.width < area.height) {
|
||||
return checkRotateRange(e, true, false);
|
||||
}
|
||||
if (!img.height < area.width && img.width < area.height) {
|
||||
return checkRotateRange(e, false, true);
|
||||
}
|
||||
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||
}
|
||||
return {
|
||||
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 变更图片布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeImageRect(e) {
|
||||
// console.log('changeImageRect', e)
|
||||
offset.x += e.x || 0;
|
||||
offset.y += e.y || 0;
|
||||
if(e.check && area.checkRange) { // 检查边界
|
||||
var point = checkRange(offset);
|
||||
if(offset.x !== point.x || offset.y !== point.y) {
|
||||
offset = point;
|
||||
}
|
||||
}
|
||||
|
||||
// 因频繁修改 width/height 会造成大量的内存消耗,改为scale
|
||||
// e.instance.imageStyles = {
|
||||
// width: img.width + 'px',
|
||||
// height: img.height + 'px',
|
||||
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
|
||||
// };
|
||||
var ox = (img.width - img.oldWidth) / 2;
|
||||
var oy = (img.height - img.oldHeight) / 2;
|
||||
// e.instance.imageStyles = {
|
||||
// width: img.oldWidth + 'px',
|
||||
// height: img.oldHeight + 'px',
|
||||
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
// };
|
||||
setStyle(e.instance, 'imageStyles', {
|
||||
width: img.oldWidth + 'px',
|
||||
height: img.oldHeight + 'px',
|
||||
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
});
|
||||
callMethod(e.instance, 'dataChange', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
rotate: rotate
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 变更裁剪区域布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeAreaRect(e) {
|
||||
// console.log('changeAreaRect', e)
|
||||
// 变更蒙版样式
|
||||
setStyle(e.instance, 'maskStylesList', [
|
||||
{
|
||||
left: 0,
|
||||
width: (area.left + areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.right + areaOffset.right) + 'px',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
height: (area.top + areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
}
|
||||
]);
|
||||
// 变更边框样式
|
||||
if(area.showBorder) {
|
||||
setStyle(e.instance, 'borderStyles', {
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
});
|
||||
}
|
||||
|
||||
// 变更参考线样式
|
||||
if(area.showGrid) {
|
||||
setStyle(e.instance, 'gridStylesList', [
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// 变更四个伸缩角样式
|
||||
if(area.showAngle) {
|
||||
setStyle(e.instance, 'angleStylesList', [
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// 变更圆角样式
|
||||
if(area.radius > 0) {
|
||||
var radius = area.radius;
|
||||
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||
radius = (area.width / 2);
|
||||
} else { // 圆角矩形
|
||||
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||
}
|
||||
}
|
||||
setStyle(e.instance, 'circleBoxStyles', {
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
});
|
||||
setStyle(e.instance, 'circleStyles', {
|
||||
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||
'border-radius': radius + 'px'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 缩放图片
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function scaleImage(e) {
|
||||
// console.log('scaleImage', e)
|
||||
var last = scale;
|
||||
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||
if(last !== scale) {
|
||||
img.width = num(img.oldWidth * scale);
|
||||
img.height = num(img.oldHeight * scale);
|
||||
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||
e.y = num((e.y - offset.y) * (1 - scale / last));
|
||||
changeImageRect(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 获取触摸点在哪个角
|
||||
* @param {number} x 触摸点x轴坐标
|
||||
* @param {number} y 触摸点y轴坐标
|
||||
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||
*/
|
||||
function getToucheAngle(x, y) {
|
||||
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||
var oy = sys.navigation ? 0 : sys.windowTop;
|
||||
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 1; // 左上角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 2; // 右上角
|
||||
}
|
||||
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 3; // 左下角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 4; // 右下角
|
||||
}
|
||||
}
|
||||
return 0; // 无触摸到角
|
||||
};
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
function resetData() {
|
||||
offset = { x: 0, y: 0 };
|
||||
scale = 1;
|
||||
minScale = img.minScale;
|
||||
rotate = 0;
|
||||
};
|
||||
function getTouchs(touches) {
|
||||
var result = [];
|
||||
var len = touches ? touches.length : 0
|
||||
for (var i = 0; i < len; i++) {
|
||||
result[i] = {
|
||||
pageX: touches[i].pageX,
|
||||
// h5无标题栏时,窗口顶部距离仍为标题栏高度,且触摸点y轴坐标还是有标题栏的值,即减去标题栏高度的值
|
||||
pageY: touches[i].pageY + sys.windowTop
|
||||
};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var mouseEvent = false;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imageStyles: {},
|
||||
maskStylesList: [{}, {}, {}, {}],
|
||||
borderStyles: {},
|
||||
gridStylesList: [{}, {}, {}, {}],
|
||||
angleStylesList: [{}, {}, {}, {}],
|
||||
circleBoxStyles: {},
|
||||
circleStyles: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听 PC 端鼠标滚轮
|
||||
// #ifdef H5
|
||||
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
|
||||
var touchs = getTouchs([e])
|
||||
img.src && scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
check: true,
|
||||
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||
scale: e.deltaY > 0 ? -0.05 : 0.05,
|
||||
x: touchs[0].pageX,
|
||||
y: touchs[0].pageY
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
// #ifdef H5
|
||||
mounted() {
|
||||
platform === 'H5' && this.initH5Events();
|
||||
},
|
||||
// #endif
|
||||
setPlatform(p) {
|
||||
platform = p;
|
||||
},
|
||||
methods: {
|
||||
// #ifdef H5
|
||||
getTouchEvent(e) {
|
||||
e.touches = [
|
||||
{ pageX: e.pageX, pageY: e.pageY }
|
||||
];
|
||||
return e;
|
||||
},
|
||||
initH5Events() {
|
||||
const preview = document.getElementById('pic-preview');
|
||||
preview?.addEventListener('mousedown', (e, ev) => {
|
||||
mouseEvent = true;
|
||||
this.touchstart(this.getTouchEvent(e));
|
||||
});
|
||||
preview?.addEventListener('mousemove', (e) => {
|
||||
if (!mouseEvent) return;
|
||||
this.touchmove(this.getTouchEvent(e));
|
||||
});
|
||||
preview?.addEventListener('mouseup', (e) => {
|
||||
mouseEvent = false;
|
||||
this.touchend(this.getTouchEvent(e))
|
||||
});
|
||||
preview?.addEventListener('mouseleave', (e) => {
|
||||
mouseEvent = false;
|
||||
this.touchend(this.getTouchEvent(e))
|
||||
});
|
||||
},
|
||||
// #endif
|
||||
async getInstance() {
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP')
|
||||
return this.$ownerInstance
|
||||
? Promise.resolve(this.$ownerInstance)
|
||||
: new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
resolve(await this.getInstance());
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5')
|
||||
return Promise.resolve(this);
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* 初始化:观察数据变更
|
||||
* @param {Object} newVal 新数据
|
||||
* @param {Object} oldVal 旧数据
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
initObserver: async function(newVal, oldVal, o, i) {
|
||||
// console.log('initObserver', newVal, oldVal, o, i)
|
||||
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
|
||||
timestamp = newVal.timestamp;
|
||||
img = newVal.img;
|
||||
sys = newVal.sys;
|
||||
area = newVal.area;
|
||||
minScale = img.minScale;
|
||||
resetData();
|
||||
const instance = await this.getInstance()
|
||||
img.src && changeImageRect({
|
||||
instance,
|
||||
x: (sys.windowWidth - img.width) / 2,
|
||||
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
|
||||
});
|
||||
changeAreaRect({
|
||||
instance
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 鼠标滚轮滚动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
mousewheel: function(e, o) {
|
||||
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
|
||||
},
|
||||
/**
|
||||
* 触摸开始
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchstart: function(e, o) {
|
||||
if(!img.src) return;
|
||||
touches = getTouchs(e.touches);
|
||||
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||
if(touches.length === 1 && activeAngle !== 0) {
|
||||
touchType = 'stretch'; // 伸缩裁剪区域
|
||||
} else {
|
||||
touchType = '';
|
||||
}
|
||||
// console.log('touchstart', e, activeAngle)
|
||||
},
|
||||
/**
|
||||
* 触摸移动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchmove: async function(e, o) {
|
||||
if(!img.src) return;
|
||||
// console.log('touchmove', e, o)
|
||||
e.touches = getTouchs(e.touches);
|
||||
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||
var point = e.touches[0];
|
||||
var start = touches[0];
|
||||
var x = point.pageX - start.pageX;
|
||||
var y = point.pageY - start.pageY;
|
||||
if(x !== 0 || y !== 0) {
|
||||
var maxX = num(area.width * (1 - area.minScale));
|
||||
var maxY = num(area.height * (1 - area.minScale));
|
||||
// console.log(x, y, maxX, maxY, offset, area)
|
||||
touches[0] = point;
|
||||
var r = rotate / 90 % 2;
|
||||
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||
var isInclude = xCompare && yCompare;
|
||||
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||
switch(activeAngle) {
|
||||
case 1: // 左上角
|
||||
x = num(x + areaOffset.left);
|
||||
y = num(y + areaOffset.top);
|
||||
if(x >= 0 && y >= 0) { // 有效滑动
|
||||
var t = num(offset.y + m - area.top);
|
||||
var l = num(offset.x - m - area.left);
|
||||
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(x > maxX) x = maxX;
|
||||
y = num(x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(y > maxY) y = maxY;
|
||||
x = num(y * area.width / area.height);
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 2: // 右上角
|
||||
x = num(x + areaOffset.right);
|
||||
y = num(y + areaOffset.top);
|
||||
if(x <= 0 && y >= 0) { // 有效滑动
|
||||
var w = (r === 1 ? img.height : img.width);
|
||||
var t = num(offset.y + m - area.top);
|
||||
var l = num(area.right + m - offset.x - w);
|
||||
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = num(-x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(y > maxY) y = maxY;
|
||||
x = num(-y * area.width / area.height);
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 3: // 左下角
|
||||
x += num(x + areaOffset.left);
|
||||
y += num(y + areaOffset.bottom);
|
||||
if(x >= 0 && y <= 0) { // 有效滑动
|
||||
var w = (r === 1 ? img.width : img.height);
|
||||
var t = num(area.bottom - m - offset.y - w);
|
||||
var l = num(offset.x - m - area.left);
|
||||
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(x > maxX) x = maxX;
|
||||
y = num(-x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = num(-y * area.width / area.height);
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
case 4: // 右下角
|
||||
x = num(x + areaOffset.right);
|
||||
y = num(y + areaOffset.bottom);
|
||||
if(x <= 0 && y <= 0) { // 有效滑动
|
||||
var w = (r === 1 ? img.height : img.width);
|
||||
var h = (r === 1 ? img.width : img.height);
|
||||
var t = num(area.bottom - offset.y - h - m);
|
||||
var l = num(area.right + m - offset.x - w);
|
||||
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = num(x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = num(y * area.width / area.height);
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log(x, y, JSON.stringify(areaOffset))
|
||||
changeAreaRect({
|
||||
instance: await this.getInstance(),
|
||||
});
|
||||
// this.draw();
|
||||
}
|
||||
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||
var start = getDistanceByTouches(touches);
|
||||
var end = getDistanceByTouches(e.touches);
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
check: !area.bounce,
|
||||
scale: (end.c - start.c) / 100,
|
||||
x: end.x,
|
||||
y: end.y
|
||||
});
|
||||
touchType = 'scale';
|
||||
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||
touchType = 'move';
|
||||
} else {
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: !area.bounce,
|
||||
x: e.touches[0].pageX - touches[0].pageX,
|
||||
y: e.touches[0].pageY - touches[0].pageY
|
||||
});
|
||||
touchType = 'move';
|
||||
}
|
||||
touches = e.touches;
|
||||
},
|
||||
/**
|
||||
* 触摸结束
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchend: async function(e, o) {
|
||||
if(!img.src) return;
|
||||
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||
// 裁剪区域宽度被缩放到多少
|
||||
var left = areaOffset.left;
|
||||
var right = areaOffset.right;
|
||||
var top = areaOffset.top;
|
||||
var bottom = areaOffset.bottom;
|
||||
var w = area.width + right - left;
|
||||
var h = area.height + bottom - top;
|
||||
// 图像放大倍数
|
||||
var p = scale * (area.width / w) - scale;
|
||||
// 复原裁剪区域
|
||||
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
changeAreaRect({
|
||||
instance: await this.getInstance(),
|
||||
});
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
scale: p,
|
||||
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||
});
|
||||
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: true
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
rotateImage: async function(r) {
|
||||
rotate = (rotate + (r || 90)) % 360;
|
||||
|
||||
if(img.minScale >= 1 && area.checkRange) {
|
||||
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||
minScale = 1;
|
||||
if(img.width < area.height) {
|
||||
minScale = area.height / img.oldWidth;
|
||||
} else if(img.height < area.width) {
|
||||
minScale = area.width / img.oldHeight;
|
||||
}
|
||||
if(minScale !== 1) {
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
scale: minScale - scale,
|
||||
x: sys.windowWidth / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: true,
|
||||
x: -ox - oy,
|
||||
y: -oy + ox
|
||||
});
|
||||
},
|
||||
rotateImage90: function() {
|
||||
this.rotateImage(90)
|
||||
},
|
||||
rotateImage270: function() {
|
||||
this.rotateImage(270)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,746 @@
|
||||
<template>
|
||||
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
|
||||
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
|
||||
width: `${canvansWidth}px`,
|
||||
height: `${canvansHeight}px`
|
||||
}"></canvas>
|
||||
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
|
||||
width: `${canvansWidth}px`,
|
||||
height: `${canvansHeight}px`
|
||||
}"></canvas>
|
||||
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
|
||||
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image>
|
||||
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view>
|
||||
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
|
||||
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
|
||||
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
|
||||
</view>
|
||||
<block v-if="showGrid">
|
||||
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view>
|
||||
</block>
|
||||
<block v-if="showAngle">
|
||||
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]">
|
||||
<view :style="[{
|
||||
width: `${angleSize}px`,
|
||||
height: `${angleSize}px`
|
||||
}]"></view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<slot />
|
||||
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
|
||||
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
|
||||
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
|
||||
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
|
||||
</view>
|
||||
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
|
||||
<block v-else-if="!!imgSrc">
|
||||
<view class="rechoose" @click="chooseImage">重选</view>
|
||||
<button class="button" size="mini" @click="cropClick">确定</button>
|
||||
</block>
|
||||
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- #ifdef APP-VUE -->
|
||||
<script module="cropper" lang="renderjs">
|
||||
import cropper from './qf-image-cropper.render.js';
|
||||
// vue3 app renderjs中条件编译无效
|
||||
cropper.setPlatform('APP');
|
||||
export default {
|
||||
mixins: [ cropper ]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<script module="cropper" lang="renderjs">
|
||||
import cropper from './qf-image-cropper.render.js';
|
||||
export default {
|
||||
mixins: [ cropper ]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ -->
|
||||
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
|
||||
<!-- #endif -->
|
||||
<script>
|
||||
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
|
||||
const AREA_SIZE = 75;
|
||||
/** 图片默认宽高 */
|
||||
const IMG_SIZE = 300;
|
||||
|
||||
export default {
|
||||
name:"qf-image-cropper",
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
|
||||
styleIsolation: "isolated"
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
/** 图片资源地址 */
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 裁剪宽度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||
width: {
|
||||
type: Number,
|
||||
default: IMG_SIZE
|
||||
},
|
||||
/** 裁剪高度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: IMG_SIZE
|
||||
},
|
||||
/** 是否绘制裁剪区域边框 */
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否绘制裁剪区域网格参考线 */
|
||||
showGrid: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否展示四个支持伸缩的角 */
|
||||
showAngle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 裁剪区域最小缩放倍数 */
|
||||
areaScale: {
|
||||
type: Number,
|
||||
default: 0.3
|
||||
},
|
||||
/** 图片最小缩放倍数 */
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/** 图片最大缩放倍数 */
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
|
||||
checkRange: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
|
||||
backgroundColor: {
|
||||
type: String
|
||||
},
|
||||
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
|
||||
bounce: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否支持翻转 */
|
||||
rotatable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否支持逆向翻转 */
|
||||
reverseRotatable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 是否支持从本地选择素材 */
|
||||
choosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
|
||||
gpu: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 四个角尺寸,单位px */
|
||||
angleSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
/** 四个角边框宽度,单位px */
|
||||
angleBorderWidth: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String]
|
||||
},
|
||||
/** 裁剪图片圆角半径,单位px */
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'png'
|
||||
},
|
||||
/**
|
||||
* 图片从绘制到生成所需时间,单位ms
|
||||
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
|
||||
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间
|
||||
*/
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
// #ifdef H5
|
||||
/**
|
||||
* 页面是否是原生标题栏
|
||||
* H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
|
||||
* 注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的
|
||||
*/
|
||||
navigation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
emits: ["crop"],
|
||||
data() {
|
||||
return {
|
||||
// 用不同 id 使 v-for key 不重复
|
||||
maskList: [
|
||||
{ id: 'crop-mask-block-1' },
|
||||
{ id: 'crop-mask-block-2' },
|
||||
{ id: 'crop-mask-block-3' },
|
||||
{ id: 'crop-mask-block-4' },
|
||||
],
|
||||
gridList: [
|
||||
{ id: 'crop-grid-1' },
|
||||
{ id: 'crop-grid-2' },
|
||||
{ id: 'crop-grid-3' },
|
||||
{ id: 'crop-grid-4' },
|
||||
],
|
||||
angleList: [
|
||||
{ id: 'crop-angle-1' },
|
||||
{ id: 'crop-angle-2' },
|
||||
{ id: 'crop-angle-3' },
|
||||
{ id: 'crop-angle-4' },
|
||||
],
|
||||
/** 本地缓存的图片路径 */
|
||||
imgSrc: '',
|
||||
/** 图片的裁剪宽度 */
|
||||
imgWidth: IMG_SIZE,
|
||||
/** 图片的裁剪高度 */
|
||||
imgHeight: IMG_SIZE,
|
||||
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
|
||||
widthPercent: AREA_SIZE,
|
||||
/** 裁剪区域最大高度所占屏幕宽度百分比 */
|
||||
heightPercent: AREA_SIZE,
|
||||
/** 裁剪区域布局信息 */
|
||||
area: {},
|
||||
/** 未被缩放过的图片宽 */
|
||||
oldWidth: 0,
|
||||
/** 未被缩放过的图片高 */
|
||||
oldHeight: 0,
|
||||
/** 系统信息 */
|
||||
sys: uni.getSystemInfoSync(),
|
||||
scaleWidth: 0,
|
||||
scaleHeight: 0,
|
||||
rotate: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
use2d: false,
|
||||
canvansWidth: 0,
|
||||
canvansHeight: 0,
|
||||
// imageStyles: {},
|
||||
// maskStylesList: [{}, {}, {}, {}],
|
||||
// borderStyles: {},
|
||||
// gridStylesList: [{}, {}, {}, {}],
|
||||
// angleStylesList: [{}, {}, {}, {}],
|
||||
// circleBoxStyles: {},
|
||||
// circleStyles: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
initData() {
|
||||
// console.log('initData')
|
||||
return {
|
||||
timestamp: new Date().getTime(),
|
||||
area: {
|
||||
...this.area,
|
||||
bounce: this.bounce,
|
||||
showBorder: this.showBorder,
|
||||
showGrid: this.showGrid,
|
||||
showAngle: this.showAngle,
|
||||
angleSize: this.angleSize,
|
||||
angleBorderWidth: this.angleBorderWidth,
|
||||
minScale: this.areaScale,
|
||||
widthPercent: this.widthPercent,
|
||||
heightPercent: this.heightPercent,
|
||||
radius: this.radius,
|
||||
checkRange: this.checkRange,
|
||||
zIndex: +this.zIndex || 0,
|
||||
},
|
||||
sys: this.sys,
|
||||
img: {
|
||||
minScale: this.minScale,
|
||||
maxScale: this.maxScale,
|
||||
src: this.imgSrc,
|
||||
width: this.oldWidth,
|
||||
height: this.oldHeight,
|
||||
oldWidth: this.oldWidth,
|
||||
oldHeight: this.oldHeight,
|
||||
gpu: this.gpu,
|
||||
}
|
||||
}
|
||||
},
|
||||
imgProps() {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
src: this.src,
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imgProps: {
|
||||
handler(val, oldVal) {
|
||||
// 自定义裁剪尺,示例如下:
|
||||
this.imgWidth = Number(val.width) || IMG_SIZE;
|
||||
this.imgHeight = Number(val.height) || IMG_SIZE;
|
||||
let use2d = true;
|
||||
// #ifndef MP-WEIXIN
|
||||
use2d = false;
|
||||
// #endif
|
||||
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
|
||||
// use2d = false;
|
||||
// }
|
||||
let canvansWidth = this.imgWidth;
|
||||
let canvansHeight = this.imgHeight;
|
||||
let size = Math.max(canvansWidth, canvansHeight)
|
||||
let scalc = 1;
|
||||
if(size > 1365) {
|
||||
scalc = 1365 / size;
|
||||
}
|
||||
this.canvansWidth = canvansWidth * scalc;
|
||||
this.canvansHeight = canvansHeight * scalc;
|
||||
this.use2d = use2d;
|
||||
this.initArea();
|
||||
const src = val.src || this.imgSrc;
|
||||
src && this.initImage(src, oldVal === undefined);
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 提供给wxs调用,用来接收图片变更数据 */
|
||||
dataChange(e) {
|
||||
// console.log('dataChange', e)
|
||||
this.scaleWidth = e.width;
|
||||
this.scaleHeight = e.height;
|
||||
this.rotate = e.rotate;
|
||||
this.offsetX = e.x;
|
||||
this.offsetY = e.y;
|
||||
},
|
||||
/** 初始化裁剪区域布局信息 */
|
||||
initArea() {
|
||||
// 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度
|
||||
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
|
||||
// #ifndef H5
|
||||
this.sys.windowTop = 0;
|
||||
this.sys.navigation = true;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
// h5平台的窗口高度是包含标题栏的
|
||||
this.sys.windowTop = this.sys.windowTop || 44;
|
||||
this.sys.navigation = this.navigation;
|
||||
// #endif
|
||||
let wp = this.widthPercent;
|
||||
let hp = this.heightPercent;
|
||||
if (this.imgWidth > this.imgHeight) {
|
||||
hp = hp * this.imgHeight / this.imgWidth;
|
||||
} else if (this.imgWidth < this.imgHeight) {
|
||||
wp = wp * this.imgWidth / this.imgHeight;
|
||||
}
|
||||
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
|
||||
const width = size * wp / 100;
|
||||
const height = size * hp / 100;
|
||||
const left = (this.sys.windowWidth - width) / 2;
|
||||
const right = left + width;
|
||||
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
|
||||
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
|
||||
this.area = { width, height, left, right, top, bottom };
|
||||
this.scaleWidth = width;
|
||||
this.scaleHeight = height;
|
||||
},
|
||||
/** 从本地选取图片 */
|
||||
chooseImage(options) {
|
||||
// #ifdef MP-WEIXIN || MP-JD
|
||||
if(uni.chooseMedia) {
|
||||
uni.chooseMedia({
|
||||
...options,
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
success: (res) => {
|
||||
this.resetData();
|
||||
this.initImage(res.tempFiles[0].tempFilePath);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
uni.chooseImage({
|
||||
...options,
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
this.resetData();
|
||||
this.initImage(res.tempFiles[0].path);
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 重置数据 */
|
||||
resetData() {
|
||||
this.imgSrc = '';
|
||||
this.rotate = 0;
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
this.initArea();
|
||||
},
|
||||
/**
|
||||
* 初始化图片信息
|
||||
* @param {String} url 图片链接
|
||||
*/
|
||||
initImage(url, isFirst) {
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: async (res) => {
|
||||
if (isFirst && this.src === url) await (new Promise((resolve) => setTimeout(resolve, 50)));
|
||||
this.imgSrc = res.path;
|
||||
let scale = res.width / res.height;
|
||||
let areaScale = this.area.width / this.area.height;
|
||||
if (scale > 1) { // 横向图片
|
||||
if (scale >= areaScale) { // 图片宽不小于目标宽,则高固定,宽自适应
|
||||
this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth);
|
||||
} else { // 否则宽固定、高自适应
|
||||
this.scaleHeight = res.height * this.scaleWidth / res.width;
|
||||
}
|
||||
} else { // 纵向图片
|
||||
if (scale <= areaScale) { // 图片高不小于目标高,宽固定,高自适应
|
||||
this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height);
|
||||
} else { // 否则高固定,宽自适应
|
||||
this.scaleWidth = res.width * this.scaleHeight / res.height;
|
||||
}
|
||||
}
|
||||
// 记录原始宽高,为缩放比列做限制
|
||||
this.oldWidth = +this.scaleWidth.toFixed(2);
|
||||
this.oldHeight = +this.scaleHeight.toFixed(2);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(err)
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 剪切图片圆角
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {Number} radius 圆角半径
|
||||
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||
* @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切
|
||||
*/
|
||||
drawClipImage(ctx, radius, scale, drawImage) {
|
||||
if(radius > 0) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
const w = this.canvansWidth;
|
||||
const h = this.canvansHeight;
|
||||
if(w === h && radius >= w / 2) { // 圆形
|
||||
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
|
||||
} else { // 圆角矩形
|
||||
if(w !== h) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(w / 2, h / 2, radius);
|
||||
// radius = Math.min(Math.max(w, h) / 2, radius);
|
||||
}
|
||||
ctx.moveTo(radius, 0);
|
||||
ctx.arcTo(w, 0, w, h, radius);
|
||||
ctx.arcTo(w, h, 0, h, radius);
|
||||
ctx.arcTo(0, h, 0, 0, radius);
|
||||
ctx.arcTo(0, 0, w, 0, radius);
|
||||
ctx.closePath();
|
||||
}
|
||||
ctx.clip();
|
||||
drawImage && drawImage(true);
|
||||
ctx.restore();
|
||||
} else {
|
||||
drawImage && drawImage(false);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 旋转图片
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {Number} rotate 旋转角度
|
||||
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||
*/
|
||||
drawRotateImage(ctx, rotate, scale) {
|
||||
if(rotate !== 0) {
|
||||
// 1. 以图片中心点为旋转中心点
|
||||
const x = this.scaleWidth * scale / 2;
|
||||
const y = this.scaleHeight * scale / 2;
|
||||
ctx.translate(x, y);
|
||||
// 2. 旋转画布
|
||||
ctx.rotate(rotate * Math.PI / 180);
|
||||
// 3. 旋转完画布后恢复设置旋转中心时所做的偏移
|
||||
ctx.translate(-x, -y);
|
||||
}
|
||||
},
|
||||
drawImage(ctx, image, callback) {
|
||||
// 生成图片的实际尺寸与截取区域比
|
||||
const scale = this.canvansWidth / this.area.width;
|
||||
if(this.backgroundColor) {
|
||||
if(ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
|
||||
else ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
}
|
||||
this.drawClipImage(ctx, this.radius, scale, () => {
|
||||
this.drawRotateImage(ctx, this.rotate, scale);
|
||||
const r = this.rotate / 90;
|
||||
ctx.drawImage(
|
||||
image,
|
||||
[
|
||||
(this.offsetX - this.area.left),
|
||||
(this.offsetY - this.area.top),
|
||||
-(this.offsetX - this.area.left),
|
||||
-(this.offsetY - this.area.top)
|
||||
][r] * scale,
|
||||
[
|
||||
(this.offsetY - this.area.top),
|
||||
-(this.offsetX - this.area.left),
|
||||
-(this.offsetY - this.area.top),
|
||||
(this.offsetX - this.area.left)
|
||||
][r] * scale,
|
||||
this.scaleWidth * scale,
|
||||
this.scaleHeight * scale
|
||||
);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 绘图
|
||||
* @param {Object} canvas
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {String} src 图片路径
|
||||
* @param {Function} callback 开始绘制时回调
|
||||
*/
|
||||
draw2DImage(canvas, ctx, src, callback) {
|
||||
// console.log('draw2DImage', canvas, ctx, src, callback)
|
||||
if(canvas) {
|
||||
const image = canvas.createImage();
|
||||
image.onload = () => {
|
||||
this.drawImage(ctx, image);
|
||||
// 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间
|
||||
callback && setTimeout(callback, this.delay);
|
||||
};
|
||||
image.onerror = (err) => {
|
||||
console.error(err)
|
||||
uni.hideLoading();
|
||||
};
|
||||
image.src = src;
|
||||
} else {
|
||||
this.drawImage(ctx, src);
|
||||
setTimeout(() => {
|
||||
ctx.draw(false, callback);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 画布转图片到本地缓存
|
||||
* @param {Object} canvas
|
||||
* @param {String} canvasId
|
||||
*/
|
||||
canvasToTempFilePath(canvas, canvasId) {
|
||||
// console.log('canvasToTempFilePath', canvas, canvasId)
|
||||
uni.canvasToTempFilePath({
|
||||
canvas,
|
||||
canvasId,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.canvansWidth,
|
||||
height: this.canvansHeight,
|
||||
destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响
|
||||
destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响
|
||||
fileType: this.fileType, // 目标文件的类型,默认png
|
||||
success: (res) => {
|
||||
// 生成的图片临时文件路径
|
||||
this.handleImage(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' });
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
/** 确认裁剪 */
|
||||
cropClick() {
|
||||
uni.showLoading({ title: '裁剪中...', mask: true });
|
||||
if(!this.use2d) {
|
||||
const ctx = uni.createCanvasContext('imgCanvas', this);
|
||||
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
this.draw2DImage(null, ctx, this.imgSrc, () => {
|
||||
this.canvasToTempFilePath(null, 'imgCanvas');
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('#imgCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
const canvas = res[0].node;
|
||||
|
||||
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||
canvas.width = res[0].width * dpr;
|
||||
canvas.height = res[0].height * dpr;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
|
||||
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
|
||||
this.canvasToTempFilePath(canvas);
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
handleImage(tempFilePath){
|
||||
// 在H5平台下,tempFilePath 为 base64
|
||||
// console.log(tempFilePath)
|
||||
uni.hideLoading();
|
||||
this.$emit('crop', { tempFilePath });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-bg-color-grey:#666;
|
||||
$uni-color-primary:#333;
|
||||
$uni-spacing-row-lg:20rpx;
|
||||
.image-cropper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #000;
|
||||
.img-canvas {
|
||||
position: absolute !important;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.pic-preview {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.crop-mask-block {
|
||||
background-color: rgba(51, 51, 51, 0.8);
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
.crop-circle-box {
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
.crop-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.crop-image {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
display: block !important;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
.crop-border {
|
||||
position: fixed;
|
||||
border: 1px solid #fff;
|
||||
box-sizing: border-box;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
}
|
||||
.crop-grid {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
border-style: dashed;
|
||||
border-color: #fff;
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.crop-angle {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
border-style: solid;
|
||||
border-color: #fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-bottom {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: $uni-bg-color-grey;
|
||||
|
||||
.action-bar {
|
||||
position: absolute;
|
||||
top: -90rpx;
|
||||
left: 10rpx;
|
||||
display: flex;
|
||||
.rotate-icon {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=');
|
||||
background-size: 60% 60%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
&.is-reverse {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rechoose {
|
||||
color: $uni-color-primary;
|
||||
padding: 0 $uni-spacing-row-lg;
|
||||
line-height: 100rpx;
|
||||
}
|
||||
|
||||
.choose-btn {
|
||||
color: $uni-color-primary;
|
||||
text-align: center;
|
||||
line-height: 100rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: auto $uni-spacing-row-lg auto auto;
|
||||
background-color: $uni-color-primary;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.safe-area-inset-bottom {
|
||||
padding-bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
|
||||
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,727 @@
|
||||
/**
|
||||
* 图片编辑器-手势监听
|
||||
* 1. wxs 暂不支持 es6 语法
|
||||
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上(uni-app 2.2.5及以上版本)
|
||||
*/
|
||||
/** 图片偏移量 */
|
||||
var offset = { x: 0, y: 0 };
|
||||
/** 图片缩放比例 */
|
||||
var scale = 1;
|
||||
/** 图片最小缩放比例 */
|
||||
var minScale = 1;
|
||||
/** 图片旋转角度 */
|
||||
var rotate = 0;
|
||||
/** 触摸点 */
|
||||
var touches = [];
|
||||
/** 图片布局信息 */
|
||||
var img = {};
|
||||
/** 系统信息 */
|
||||
var sys = {};
|
||||
/** 裁剪区域布局信息 */
|
||||
var area = {};
|
||||
/** 触摸行为类型 */
|
||||
var touchType = '';
|
||||
/** 操作角的位置 */
|
||||
var activeAngle = 0;
|
||||
/** 裁剪区域布局信息偏移量 */
|
||||
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
/** 容错值 */
|
||||
var fault = 0.000001;
|
||||
/**
|
||||
* 获取a、b两数中的最小正数
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
function minimum(a, b) {
|
||||
if (a > 0 && b < 0) return a;
|
||||
if (a < 0 && b > 0) return b;
|
||||
if (a > 0 && b > 0) return Math.min(a, b);
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* 在容错访问内获取n近似值
|
||||
* @param n
|
||||
*/
|
||||
function num(n) {
|
||||
var m = parseFloat((n).toFixed(6));
|
||||
return m === fault || m === -fault ? 0 : m;
|
||||
}
|
||||
/**
|
||||
* 比较a值在容错值范围内是否等于b值
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
function equalsByFault(a, b) {
|
||||
return Math.abs(a - b) <= fault;
|
||||
}
|
||||
/**
|
||||
* 比较a值在容错值范围内是否小于b值
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
function lessThanByFault(a, b) {
|
||||
var c = a - b;
|
||||
return c < 0 ? c < -fault : c < fault;
|
||||
}
|
||||
/**
|
||||
* 验证并获取有效最大值
|
||||
* @param v
|
||||
* @param max
|
||||
* @param isInclude
|
||||
* @param x
|
||||
* @param y
|
||||
* @param rate
|
||||
* @returns
|
||||
*/
|
||||
function validMax(v, max, isInclude, x, y, rate) {
|
||||
if(typeof max === 'number') {
|
||||
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||
var n = num(max * rate);
|
||||
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||
}
|
||||
return max;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
/**
|
||||
* 计算两点间距
|
||||
* @param {Object} touches 触摸点信息
|
||||
*/
|
||||
function getDistanceByTouches(touches) {
|
||||
// 根据勾股定理求两点间距离
|
||||
var a = touches[1].pageX - touches[0].pageX;
|
||||
var b = touches[1].pageY - touches[0].pageY;
|
||||
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||
// 求两点间的中点坐标
|
||||
// 1. a、b可能为负值
|
||||
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||
var x = touches[1].pageX - a / 2;
|
||||
var y = touches[1].pageY - b / 2;
|
||||
return { c, x, y };
|
||||
};
|
||||
/**
|
||||
* 修正取值
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @param {Object} c
|
||||
* @param {Object} reverse 是否反向
|
||||
*/
|
||||
function correctValue(a, b, c, reverse) {
|
||||
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
* @param {Object} xReverse x是否反向
|
||||
* @param {Object} yReverse y是否反向
|
||||
*/
|
||||
function checkRotateRange(e, xReverse, yReverse) {
|
||||
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||
return {
|
||||
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
*/
|
||||
function checkRange(e) {
|
||||
var r = rotate / 90 % 2;
|
||||
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||
if (area.width === area.height) {
|
||||
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||
}
|
||||
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||
if (img.width < area.height || img.height < area.width) {
|
||||
if (area.width < area.height && img.width < img.height) {
|
||||
return isInclude
|
||||
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||
: checkRotateRange(e, false, true);
|
||||
}
|
||||
if (area.height < area.width && img.height < img.width) {
|
||||
return isInclude
|
||||
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||
: checkRotateRange(e, true, false);
|
||||
}
|
||||
}
|
||||
if (img.height >= area.width && img.width >= area.height) {
|
||||
return checkRotateRange(e, false, false);
|
||||
}
|
||||
if (isInclude) {
|
||||
return area.height < area.width
|
||||
? checkRotateRange(e, true, true)
|
||||
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||
}
|
||||
if (img.height < area.width && !img.width < area.height) {
|
||||
return checkRotateRange(e, true, false);
|
||||
}
|
||||
if (!img.height < area.width && img.width < area.height) {
|
||||
return checkRotateRange(e, false, true);
|
||||
}
|
||||
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||
}
|
||||
return {
|
||||
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||
};
|
||||
};
|
||||
/**
|
||||
* 变更图片布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeImageRect(e) {
|
||||
offset.x += e.x || 0;
|
||||
offset.y += e.y || 0;
|
||||
var image = e.instance.selectComponent('.crop-image');
|
||||
if(e.check && area.checkRange) { // 检查边界
|
||||
var point = checkRange(offset);
|
||||
if(offset.x !== point.x || offset.y !== point.y) {
|
||||
offset = point;
|
||||
}
|
||||
}
|
||||
// image.setStyle({
|
||||
// width: img.width + 'px',
|
||||
// height: img.height + 'px',
|
||||
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
|
||||
// });
|
||||
var ox = (img.width - img.oldWidth) / 2;
|
||||
var oy = (img.height - img.oldHeight) / 2;
|
||||
image.setStyle({
|
||||
width: img.oldWidth + 'px',
|
||||
height: img.oldHeight + 'px',
|
||||
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
});
|
||||
|
||||
e.instance.callMethod('dataChange', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
rotate: rotate
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 变更裁剪区域布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeAreaRect(e) {
|
||||
// 变更蒙版样式
|
||||
var masks = e.instance.selectAllComponents('.crop-mask-block');
|
||||
var maskStyles = [
|
||||
{
|
||||
left: 0,
|
||||
width: (area.left + areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.right + areaOffset.right) + 'px',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
height: (area.top + areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
}
|
||||
];
|
||||
var len = masks.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
masks[i].setStyle(maskStyles[i]);
|
||||
}
|
||||
|
||||
// 变更边框样式
|
||||
if(area.showBorder) {
|
||||
var border = e.instance.selectComponent('.crop-border');
|
||||
border.setStyle({
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
});
|
||||
}
|
||||
|
||||
// 变更参考线样式
|
||||
if(area.showGrid) {
|
||||
var grids = e.instance.selectAllComponents('.crop-grid');
|
||||
var gridStyles = [
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
];
|
||||
var len = grids.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
grids[i].setStyle(gridStyles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 变更四个伸缩角样式
|
||||
if(area.showAngle) {
|
||||
var angles = e.instance.selectAllComponents('.crop-angle');
|
||||
var angleStyles = [
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
];
|
||||
var len = angles.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
angles[i].setStyle(angleStyles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 变更圆角样式
|
||||
if(area.radius > 0) {
|
||||
var circleBox = e.instance.selectComponent('.crop-circle-box');
|
||||
var circle = e.instance.selectComponent('.crop-circle');
|
||||
var radius = area.radius;
|
||||
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||
radius = (area.width / 2);
|
||||
} else { // 圆角矩形
|
||||
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||
}
|
||||
}
|
||||
circleBox.setStyle({
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
});
|
||||
circle.setStyle({
|
||||
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||
'border-radius': radius + 'px'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 缩放图片
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function scaleImage(e) {
|
||||
var last = scale;
|
||||
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||
if(last !== scale) {
|
||||
img.width = num(img.oldWidth * scale);
|
||||
img.height = num(img.oldHeight * scale);
|
||||
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||
e.y = num((e.y - offset.y) * (1 - scale / last));
|
||||
changeImageRect(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 获取触摸点在哪个角
|
||||
* @param {number} x 触摸点x轴坐标
|
||||
* @param {number} y 触摸点y轴坐标
|
||||
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||
*/
|
||||
function getToucheAngle(x, y) {
|
||||
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 1; // 左上角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 2; // 右上角
|
||||
}
|
||||
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 3; // 左下角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 4; // 右下角
|
||||
}
|
||||
}
|
||||
return 0; // 无触摸到角
|
||||
};
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
function resetData() {
|
||||
offset = { x: 0, y: 0 };
|
||||
scale = 1;
|
||||
minScale = img.minScale;
|
||||
rotate = 0;
|
||||
};
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
function rotateImage(e, o, r) {
|
||||
rotate = (rotate + r) % 360;
|
||||
if(img.minScale >= 1 && area.checkRange) {
|
||||
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||
minScale = 1;
|
||||
if(img.width < area.height) {
|
||||
minScale = area.height / img.oldWidth;
|
||||
} else if(img.height < area.width) {
|
||||
minScale = area.width / img.oldHeight;
|
||||
}
|
||||
if(minScale !== 1) {
|
||||
scaleImage({
|
||||
instance: o,
|
||||
scale: minScale - scale,
|
||||
x: sys.windowWidth / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: true,
|
||||
x: -ox - oy,
|
||||
y: -oy + ox
|
||||
});
|
||||
};
|
||||
module.exports = {
|
||||
/**
|
||||
* 初始化:观察数据变更
|
||||
* @param {Object} newVal 新数据
|
||||
* @param {Object} oldVal 旧数据
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
initObserver: function(newVal, oldVal, o, i) {
|
||||
if(newVal) {
|
||||
img = newVal.img;
|
||||
sys = newVal.sys;
|
||||
area = newVal.area;
|
||||
minScale = img.minScale;
|
||||
resetData();
|
||||
img.src && changeImageRect({
|
||||
instance: o,
|
||||
x: (sys.windowWidth - img.width) / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
|
||||
});
|
||||
changeAreaRect({
|
||||
instance: o
|
||||
});
|
||||
// console.log('initRect', JSON.stringify(newVal))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 鼠标滚轮滚动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
mousewheel: function(e, o) {
|
||||
if(!img.src) return;
|
||||
scaleImage({
|
||||
instance: o,
|
||||
check: true,
|
||||
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 触摸开始
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchstart: function(e, o) {
|
||||
if(!img.src) return;
|
||||
touches = e.touches;
|
||||
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||
if(touches.length === 1 && activeAngle !== 0) {
|
||||
touchType = 'stretch'; // 伸缩裁剪区域
|
||||
} else {
|
||||
touchType = '';
|
||||
}
|
||||
// console.log('touchstart', JSON.stringify(e), activeAngle)
|
||||
},
|
||||
/**
|
||||
* 触摸移动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchmove: function(e, o) {
|
||||
if(!img.src) return;
|
||||
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
|
||||
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||
var point = e.touches[0];
|
||||
var start = touches[0];
|
||||
var x = point.pageX - start.pageX;
|
||||
var y = point.pageY - start.pageY;
|
||||
if(x !== 0 || y !== 0) {
|
||||
var maxX = num(area.width * (1 - area.minScale));
|
||||
var maxY = num(area.height * (1 - area.minScale));
|
||||
// console.log(x, y, maxX, maxY, offset, area)
|
||||
touches[0] = point;
|
||||
var r = rotate / 90 % 2;
|
||||
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||
var isInclude = xCompare && yCompare;
|
||||
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||
switch(activeAngle) {
|
||||
case 1: // 左上角
|
||||
x = num(x + areaOffset.left);
|
||||
y = num(y + areaOffset.top);
|
||||
if(x >= 0 && y >= 0) { // 有效滑动
|
||||
var t = num(offset.y + m - area.top);
|
||||
var l = num(offset.x - m - area.left);
|
||||
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(x > maxX) x = maxX;
|
||||
y = num(x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(y > maxY) y = maxY;
|
||||
x = num(y * area.width / area.height);
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 2: // 右上角
|
||||
x = num(x + areaOffset.right);
|
||||
y = num(y + areaOffset.top);
|
||||
if(x <= 0 && y >= 0) { // 有效滑动
|
||||
var w = (r === 1 ? img.height : img.width);
|
||||
var t = num(offset.y + m - area.top);
|
||||
var l = num(area.right + m - offset.x - w);
|
||||
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
|
||||
// : false;
|
||||
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
|
||||
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
|
||||
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = num(-x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(y > maxY) y = maxY;
|
||||
x = num(-y * area.width / area.height);
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 3: // 左下角
|
||||
x += num(x + areaOffset.left);
|
||||
y += num(y + areaOffset.bottom);
|
||||
if(x >= 0 && y <= 0) { // 有效滑动
|
||||
var w = (r === 1 ? img.width : img.height);
|
||||
var t = num(area.bottom - m - offset.y - w);
|
||||
var l = num(offset.x - m - area.left);
|
||||
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(x > maxX) x = maxX;
|
||||
y = num(-x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = num(-y * area.width / area.height);
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
case 4: // 右下角
|
||||
x = num(x + areaOffset.right);
|
||||
y = num(y + areaOffset.bottom);
|
||||
if(x <= 0 && y <= 0) { // 有效滑动
|
||||
var w = (r === 1 ? img.height : img.width);
|
||||
var h = (r === 1 ? img.width : img.height);
|
||||
var t = num(area.bottom - offset.y - h - m);
|
||||
var l = num(area.right + m - offset.x - w);
|
||||
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||
? minimum(t, l)
|
||||
: false;
|
||||
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = num(x * area.height / area.width);
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = num(y * area.width / area.height);
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log(x, y, JSON.stringify(areaOffset))
|
||||
changeAreaRect({
|
||||
instance: o,
|
||||
});
|
||||
// this.draw();
|
||||
}
|
||||
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||
var start = getDistanceByTouches(touches);
|
||||
var end = getDistanceByTouches(e.touches);
|
||||
scaleImage({
|
||||
instance: o,
|
||||
check: !area.bounce,
|
||||
scale: (end.c - start.c) / 100,
|
||||
x: end.x,
|
||||
y: end.y
|
||||
});
|
||||
touchType = 'scale';
|
||||
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||
touchType = 'move';
|
||||
} else {
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: !area.bounce,
|
||||
x: e.touches[0].pageX - touches[0].pageX,
|
||||
y: e.touches[0].pageY - touches[0].pageY
|
||||
});
|
||||
touchType = 'move';
|
||||
}
|
||||
touches = e.touches;
|
||||
},
|
||||
/**
|
||||
* 触摸结束
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchend: function(e, o) {
|
||||
if(!img.src) return;
|
||||
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||
// 裁剪区域宽度被缩放到多少
|
||||
var left = areaOffset.left;
|
||||
var right = areaOffset.right;
|
||||
var top = areaOffset.top;
|
||||
var bottom = areaOffset.bottom;
|
||||
var w = area.width + right - left;
|
||||
var h = area.height + bottom - top;
|
||||
// 图像放大倍数
|
||||
var p = scale * (area.width / w) - scale;
|
||||
// 复原裁剪区域
|
||||
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
changeAreaRect({
|
||||
instance: o,
|
||||
});
|
||||
scaleImage({
|
||||
instance: o,
|
||||
scale: p,
|
||||
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||
});
|
||||
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: true
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
rotateImage: function(e, o) {
|
||||
rotateImage(e, o, 90);
|
||||
},
|
||||
rotateImage90: function(e, o) {
|
||||
rotateImage(e, o, 90)
|
||||
},
|
||||
rotateImage270: function(e, o) {
|
||||
rotateImage(e, o, 270)
|
||||
},
|
||||
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
|
||||
imageStyles: '',
|
||||
maskStylesList: ['', '', '', ''],
|
||||
borderStyles: '',
|
||||
gridStylesList: ['', '', '', ''],
|
||||
angleStylesList: ['', '', '', ''],
|
||||
circleBoxStyles: '',
|
||||
circleStyles: '',
|
||||
}
|
||||
@@ -5,27 +5,23 @@
|
||||
v-for="(item, index) in tabList"
|
||||
:key="index"
|
||||
@click="switchTab(index, item)"
|
||||
:class="{ active: currentTab === index }"
|
||||
:class="{ active: 2 === index }"
|
||||
>
|
||||
<!-- 图标容器 -->
|
||||
<view class="icon-container">
|
||||
<!-- <uni-icons
|
||||
:type="currentTab === index ? item.activeIcon : item.icon"
|
||||
:size="currentTab === index ? 28 : 24"
|
||||
:color="currentTab === index ? item.activeColor : item.color"
|
||||
></uni-icons> -->
|
||||
|
||||
<image class="img" :src="currentTab === index ? item.activeIcon : item.icon" alt="" />
|
||||
<!-- 徽章 -->
|
||||
<view class="badge" v-if="item.badge && item.badge > 0">
|
||||
<!-- <view class="badge" v-if="item.badge && item.badge > 0">
|
||||
<uni-badge
|
||||
:text="item.badge > 99 ? '99+' : item.badge.toString()"
|
||||
type="error"
|
||||
size="small"
|
||||
></uni-badge>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 红点 -->
|
||||
<view class="red-dot" v-if="item.showRedDot"></view>
|
||||
<!-- <view class="red-dot" v-if="item.showRedDot"></view> -->
|
||||
</view>
|
||||
|
||||
<!-- 文字 -->
|
||||
@@ -37,11 +33,13 @@
|
||||
<!-- <view class="active-indicator" v-if="currentTab === index"></view> -->
|
||||
</view>
|
||||
</view>
|
||||
<GlobalDialog />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue';
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import GlobalDialog from "@/components/GlobalDialog.vue"
|
||||
import home from '@/static/home_nor.png'
|
||||
import homeOn from '@/static/home_sel.png'
|
||||
import classroom from "@/static/classroom.png"
|
||||
@@ -74,7 +72,7 @@
|
||||
activeColor: '#007aff',
|
||||
badge: 0,
|
||||
showRedDot: false,
|
||||
pagePath: '/pages/index/index'
|
||||
pagePath: '/pages/index/index?from=push'
|
||||
},
|
||||
{
|
||||
icon: classroom,
|
||||
@@ -82,9 +80,9 @@
|
||||
text: '患教学堂',
|
||||
color: '#999999',
|
||||
activeColor: '#007aff',
|
||||
badge: 5,
|
||||
badge:0,
|
||||
showRedDot: false,
|
||||
pagePath: '/pages/education/education'
|
||||
pagePath: '/pages/patientClass/patientClass'
|
||||
},
|
||||
{
|
||||
icon: live,
|
||||
@@ -93,8 +91,8 @@
|
||||
color: '#999999',
|
||||
activeColor: '#007aff',
|
||||
badge: 0,
|
||||
showRedDot: true,
|
||||
pagePath: '/pages/meeting/meeting'
|
||||
showRedDot: false,
|
||||
pagePath: '/pages/live/live'
|
||||
},
|
||||
{
|
||||
icon: education,
|
||||
@@ -102,9 +100,9 @@
|
||||
text: '继续教育',
|
||||
color: '#999999',
|
||||
activeColor: '#007aff',
|
||||
badge: 12,
|
||||
badge: 0,
|
||||
showRedDot: false,
|
||||
pagePath: '/pages/education/continuing'
|
||||
pagePath: '/pages/education/education'
|
||||
},
|
||||
{
|
||||
icon: my,
|
||||
@@ -114,7 +112,7 @@
|
||||
activeColor: '#007aff',
|
||||
badge: 0,
|
||||
showRedDot: false,
|
||||
pagePath: '/pages/profile/profile'
|
||||
pagePath: '/pages/my/my'
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -123,35 +121,56 @@
|
||||
if (currentTab.value === index) return;
|
||||
|
||||
// 更新当前tab
|
||||
currentTab.value = index;
|
||||
|
||||
// 清除红点
|
||||
if (item.showRedDot) {
|
||||
item.showRedDot = false;
|
||||
//currentTab.value = index;
|
||||
let msg="home"
|
||||
if(index == 0){
|
||||
msg="home"
|
||||
}else if(index == 1){
|
||||
msg="patientClass"
|
||||
}else if(index == 2){
|
||||
msg="live"
|
||||
}else if(index == 3){
|
||||
msg="education"
|
||||
}else if(index == 4){
|
||||
msg="mine"
|
||||
}
|
||||
|
||||
try{
|
||||
uni.sendNativeEvent('goTabbarPage', {
|
||||
msg: msg
|
||||
},ret => {
|
||||
|
||||
console.log(ret);
|
||||
})
|
||||
}catch(e){
|
||||
console.log(e);
|
||||
}
|
||||
plus.runtime.quit()
|
||||
// 清除红点
|
||||
// if (item.showRedDot) {
|
||||
// item.showRedDot = false;
|
||||
// }
|
||||
|
||||
// 页面跳转
|
||||
uni.switchTab({
|
||||
url: item.pagePath,
|
||||
fail: () => {
|
||||
// 如果页面不存在,使用navigateTo
|
||||
uni.navigateTo({
|
||||
url: item.pagePath,
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '页面开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// uni.redirectTo({
|
||||
// url: item.pagePath,
|
||||
// fail: () => {
|
||||
// // 如果页面不存在,使用navigateTo
|
||||
// uni.navigateTo({
|
||||
// url: item.pagePath,
|
||||
// fail: () => {
|
||||
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// 触发父组件事件
|
||||
emit('tabChange', {
|
||||
index,
|
||||
item
|
||||
});
|
||||
// emit('tabChange', {
|
||||
// index,
|
||||
// item
|
||||
// });
|
||||
|
||||
};
|
||||
|
||||
// 设置徽章数量
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<view class="top-tip" v-if="visible">
|
||||
<view class="title">{{ title }}</view>
|
||||
<view class="content">
|
||||
{{ content }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
}
|
||||
const close = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.top-tip{
|
||||
position: fixed;
|
||||
top: 180rpx;
|
||||
left: 40rpx;
|
||||
right:40rpx;
|
||||
padding: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
z-index: 99999999999;
|
||||
.title{
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
|
||||
}
|
||||
.content{
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
margin-top: 25rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="isShowA"
|
||||
class="uni-link"
|
||||
:href="href"
|
||||
:class="{
|
||||
'uni-link--withline': showUnderLine === true || showUnderLine === 'true',
|
||||
}"
|
||||
:style="{ color, fontSize: fontSize + 'px' }"
|
||||
:download="download"
|
||||
>
|
||||
<slot>{{ text }}</slot>
|
||||
</a>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<text
|
||||
v-else
|
||||
class="uni-link"
|
||||
:class="{
|
||||
'uni-link--withline': showUnderLine === true || showUnderLine === 'true',
|
||||
}"
|
||||
:style="{ color, fontSize: fontSize + 'px' }"
|
||||
@click="openURL"
|
||||
>
|
||||
<slot>{{ text }}</slot>
|
||||
</text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<text
|
||||
v-else
|
||||
class="uni-link"
|
||||
:class="{
|
||||
'uni-link--withline': showUnderLine === true || showUnderLine === 'true',
|
||||
}"
|
||||
:style="{ color, fontSize: fontSize + 'px' }"
|
||||
@click="openURL"
|
||||
>
|
||||
{{ text }}
|
||||
</text>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Link 外部网页超链接组件
|
||||
* @description uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=1182
|
||||
* @property {String} href 点击后打开的外部网页url
|
||||
* @property {String} text 显示的文字
|
||||
* @property {String} downlaod H5平台下载文件名
|
||||
* @property {Boolean} showUnderLine 是否显示下划线
|
||||
* @property {String} copyTips 在小程序端复制链接时显示的提示语
|
||||
* @property {String} color 链接文字颜色
|
||||
* @property {String} fontSize 链接文字大小
|
||||
* @example * <uni-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn"></uni-link>
|
||||
*/
|
||||
export default {
|
||||
name: 'uniLink',
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
download: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showUnderLine: {
|
||||
type: [Boolean, String],
|
||||
default: true,
|
||||
},
|
||||
copyTips: {
|
||||
type: String,
|
||||
default: '已自动复制网址,请在手机浏览器里粘贴该网址',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#999999',
|
||||
},
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 14,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isShowA() {
|
||||
// #ifdef H5
|
||||
this._isH5 = true
|
||||
// #endif
|
||||
if ((this.isMail() || this.isTel()) && this._isH5 === true) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this._isH5 = null
|
||||
},
|
||||
methods: {
|
||||
isMail() {
|
||||
return this.href.startsWith('mailto:')
|
||||
},
|
||||
isTel() {
|
||||
return this.href.startsWith('tel:')
|
||||
},
|
||||
openURL() {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.isTel()) {
|
||||
this.makePhoneCall(this.href.replace('tel:', ''))
|
||||
} else {
|
||||
plus.runtime.openURL(this.href)
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
window.open(this.href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: this.href,
|
||||
showToast: false,
|
||||
})
|
||||
uni.showModal({
|
||||
content: this.copyTips,
|
||||
showCancel: false,
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
makePhoneCall(phoneNumber) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-link--withline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
// #ifdef H5
|
||||
export default {
|
||||
name: 'Keypress',
|
||||
props: {
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const keyNames = {
|
||||
esc: ['Esc', 'Escape'],
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
space: [' ', 'Spacebar'],
|
||||
up: ['Up', 'ArrowUp'],
|
||||
left: ['Left', 'ArrowLeft'],
|
||||
right: ['Right', 'ArrowRight'],
|
||||
down: ['Down', 'ArrowDown'],
|
||||
delete: ['Backspace', 'Delete', 'Del'],
|
||||
}
|
||||
const listener = ($event) => {
|
||||
if (this.disable) {
|
||||
return
|
||||
}
|
||||
const keyName = Object.keys(keyNames).find((key) => {
|
||||
const keyName = $event.key
|
||||
const value = keyNames[key]
|
||||
return (
|
||||
value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||
)
|
||||
})
|
||||
if (keyName) {
|
||||
// 避免和其他按键事件冲突
|
||||
setTimeout(() => {
|
||||
this.$emit(keyName, {})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keyup', listener)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('keyup', listener)
|
||||
})
|
||||
},
|
||||
render: () => {
|
||||
return null
|
||||
},
|
||||
}
|
||||
// #endif
|
||||
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<view class="uni-popup-dialog">
|
||||
<view class="uni-dialog-title">
|
||||
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
|
||||
</view>
|
||||
<view v-if="mode === 'base'" class="uni-dialog-content">
|
||||
<slot>
|
||||
<text class="uni-dialog-content-text">{{content}}</text>
|
||||
</slot>
|
||||
</view>
|
||||
<view v-else class="uni-dialog-content">
|
||||
<slot>
|
||||
<input class="uni-dialog-input" v-model="val" :type="inputType" :placeholder="placeholderText" :focus="focus" >
|
||||
</slot>
|
||||
</view>
|
||||
<view class="uni-dialog-button-group">
|
||||
<view class="uni-dialog-button" @click="closeDialog">
|
||||
<text class="uni-dialog-button-text">{{closeText}}</text>
|
||||
</view>
|
||||
<view class="uni-dialog-button uni-border-left" @click="onOk">
|
||||
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popup from '../uni-popup/popup.js'
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import messages from '../uni-popup/i18n/index.js'
|
||||
const { t } = initVueI18n(messages)
|
||||
/**
|
||||
* PopUp 弹出层-对话框样式
|
||||
* @description 弹出层-对话框样式
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} value input 模式下的默认值
|
||||
* @property {String} placeholder input 模式下输入提示
|
||||
* @property {String} type = [success|warning|info|error] 主题样式
|
||||
* @value success 成功
|
||||
* @value warning 提示
|
||||
* @value info 消息
|
||||
* @value error 错误
|
||||
* @property {String} mode = [base|input] 模式、
|
||||
* @value base 基础对话框
|
||||
* @value input 可输入对话框
|
||||
* @property {String} content 对话框内容
|
||||
* @property {Boolean} beforeClose 是否拦截取消事件
|
||||
* @event {Function} confirm 点击确认按钮触发
|
||||
* @event {Function} close 点击取消按钮触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "uniPopupDialog",
|
||||
mixins: [popup],
|
||||
emits:['confirm','close'],
|
||||
props: {
|
||||
inputType:{
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'base'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
beforeClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cancelText:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
confirmText:{
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogType: 'error',
|
||||
focus: false,
|
||||
val: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
okText() {
|
||||
return this.confirmText || t("uni-popup.ok")
|
||||
},
|
||||
closeText() {
|
||||
return this.cancelText || t("uni-popup.cancel")
|
||||
},
|
||||
placeholderText() {
|
||||
return this.placeholder || t("uni-popup.placeholder")
|
||||
},
|
||||
titleText() {
|
||||
return this.title || t("uni-popup.title")
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
type(val) {
|
||||
this.dialogType = val
|
||||
},
|
||||
mode(val) {
|
||||
if (val === 'input') {
|
||||
this.dialogType = 'info'
|
||||
}
|
||||
},
|
||||
value(val) {
|
||||
this.val = val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 对话框遮罩不可点击
|
||||
this.popup.disableMask()
|
||||
// this.popup.closeMask()
|
||||
if (this.mode === 'input') {
|
||||
this.dialogType = 'info'
|
||||
this.val = this.value
|
||||
} else {
|
||||
this.dialogType = this.type
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.focus = true
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 点击确认按钮
|
||||
*/
|
||||
onOk() {
|
||||
if (this.mode === 'input'){
|
||||
this.$emit('confirm', this.val)
|
||||
}else{
|
||||
this.$emit('confirm')
|
||||
}
|
||||
if(this.beforeClose) return
|
||||
this.popup.close()
|
||||
},
|
||||
/**
|
||||
* 点击取消按钮
|
||||
*/
|
||||
closeDialog() {
|
||||
this.$emit('close')
|
||||
if(this.beforeClose) return
|
||||
this.popup.close()
|
||||
},
|
||||
close(){
|
||||
this.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
.uni-popup-dialog {
|
||||
width: 300px;
|
||||
border-radius: 11px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-dialog-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.uni-dialog-title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.uni-dialog-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.uni-dialog-content-text {
|
||||
font-size: 14px;
|
||||
color: #6C6C6C;
|
||||
}
|
||||
|
||||
.uni-dialog-button-group {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
border-top-color: #f5f5f5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-dialog-button {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.uni-border-left {
|
||||
border-left-color: #f0f0f0;
|
||||
border-left-style: solid;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.uni-dialog-button-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-button-color {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.uni-dialog-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
border: 1px #eee solid;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
border-radius: 5px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.uni-popup__success {
|
||||
color: #4cd964;
|
||||
}
|
||||
|
||||
.uni-popup__warn {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.uni-popup__error {
|
||||
color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-popup__info {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<view class="uni-popup-message">
|
||||
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
|
||||
<slot>
|
||||
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popup from '../uni-popup/popup.js'
|
||||
/**
|
||||
* PopUp 弹出层-消息提示
|
||||
* @description 弹出层-消息提示
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} type = [success|warning|info|error] 主题样式
|
||||
* @value success 成功
|
||||
* @value warning 提示
|
||||
* @value info 消息
|
||||
* @value error 错误
|
||||
* @property {String} message 消息提示文字
|
||||
* @property {String} duration 显示时间,设置为 0 则不会自动关闭
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniPopupMessage',
|
||||
mixins:[popup],
|
||||
props: {
|
||||
/**
|
||||
* 主题 success/warning/info/error 默认 success
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
/**
|
||||
* 消息文字
|
||||
*/
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 显示时间,设置为 0 则不会自动关闭
|
||||
*/
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
},
|
||||
maskShow:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
this.popup.maskShow = this.maskShow
|
||||
this.popup.messageChild = this
|
||||
},
|
||||
methods: {
|
||||
timerClose(){
|
||||
if(this.duration === 0) return
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(()=>{
|
||||
this.popup.close()
|
||||
},this.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
.uni-popup-message {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-popup-message__box {
|
||||
background-color: #e1f3d8;
|
||||
padding: 10px 15px;
|
||||
border-color: #eee;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.fixforpc-width {
|
||||
margin-top: 20px;
|
||||
border-radius: 4px;
|
||||
flex: none;
|
||||
min-width: 380px;
|
||||
/* #ifndef APP-NVUE */
|
||||
max-width: 50%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
max-width: 500px;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
|
||||
.uni-popup-message-text {
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.uni-popup__success {
|
||||
background-color: #e1f3d8;
|
||||
}
|
||||
|
||||
.uni-popup__success-text {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.uni-popup__warn {
|
||||
background-color: #faecd8;
|
||||
}
|
||||
|
||||
.uni-popup__warn-text {
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
.uni-popup__error {
|
||||
background-color: #fde2e2;
|
||||
}
|
||||
|
||||
.uni-popup__error-text {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.uni-popup__info {
|
||||
background-color: #F2F6FC;
|
||||
}
|
||||
|
||||
.uni-popup__info-text {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<view class="uni-popup-share">
|
||||
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
|
||||
<view class="uni-share-content">
|
||||
<view class="uni-share-content-box">
|
||||
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
|
||||
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
|
||||
<text class="uni-share-text">{{item.text}}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-share-button-box">
|
||||
<button class="uni-share-button" @click="close">{{cancelText}}</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popup from '../uni-popup/popup.js'
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import messages from '../uni-popup/i18n/index.js'
|
||||
const { t } = initVueI18n(messages)
|
||||
export default {
|
||||
name: 'UniPopupShare',
|
||||
mixins:[popup],
|
||||
emits:['select'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
beforeClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bottomData: [{
|
||||
text: '微信',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
|
||||
name: 'wx'
|
||||
},
|
||||
{
|
||||
text: '支付宝',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
|
||||
name: 'wx'
|
||||
},
|
||||
{
|
||||
text: 'QQ',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
|
||||
name: 'qq'
|
||||
},
|
||||
{
|
||||
text: '新浪',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
|
||||
name: 'sina'
|
||||
},
|
||||
// {
|
||||
// text: '百度',
|
||||
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
|
||||
// name: 'copy'
|
||||
// },
|
||||
// {
|
||||
// text: '其他',
|
||||
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
|
||||
// name: 'more'
|
||||
// }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
computed: {
|
||||
cancelText() {
|
||||
return t("uni-popup.cancel")
|
||||
},
|
||||
shareTitleText() {
|
||||
return this.title || t("uni-popup.shareTitle")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 选择内容
|
||||
*/
|
||||
select(item, index) {
|
||||
this.$emit('select', {
|
||||
item,
|
||||
index
|
||||
})
|
||||
this.close()
|
||||
|
||||
},
|
||||
/**
|
||||
* 关闭窗口
|
||||
*/
|
||||
close() {
|
||||
if(this.beforeClose) return
|
||||
this.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
.uni-popup-share {
|
||||
background-color: #fff;
|
||||
border-top-left-radius: 11px;
|
||||
border-top-right-radius: 11px;
|
||||
}
|
||||
.uni-share-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
}
|
||||
.uni-share-title-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.uni-share-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.uni-share-content-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.uni-share-content-item {
|
||||
width: 90px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-share-content-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.uni-share-image {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.uni-share-text {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #3B4144;
|
||||
}
|
||||
|
||||
.uni-share-button-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.uni-share-button {
|
||||
flex: 1;
|
||||
border-radius: 50px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.uni-share-button::after {
|
||||
border-radius: 50px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "cancel",
|
||||
"uni-popup.ok": "ok",
|
||||
"uni-popup.placeholder": "pleace enter",
|
||||
"uni-popup.title": "Hint",
|
||||
"uni-popup.shareTitle": "Share to"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import en from './en.json'
|
||||
import zhHans from './zh-Hans.json'
|
||||
import zhHant from './zh-Hant.json'
|
||||
export default {
|
||||
en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': zhHant,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "取消",
|
||||
"uni-popup.ok": "确定",
|
||||
"uni-popup.placeholder": "请输入",
|
||||
"uni-popup.title": "提示",
|
||||
"uni-popup.shareTitle": "分享到"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "取消",
|
||||
"uni-popup.ok": "確定",
|
||||
"uni-popup.placeholder": "請輸入",
|
||||
"uni-popup.title": "提示",
|
||||
"uni-popup.shareTitle": "分享到"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// #ifdef H5
|
||||
export default {
|
||||
name: 'Keypress',
|
||||
props: {
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const keyNames = {
|
||||
esc: ['Esc', 'Escape'],
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
space: [' ', 'Spacebar'],
|
||||
up: ['Up', 'ArrowUp'],
|
||||
left: ['Left', 'ArrowLeft'],
|
||||
right: ['Right', 'ArrowRight'],
|
||||
down: ['Down', 'ArrowDown'],
|
||||
delete: ['Backspace', 'Delete', 'Del'],
|
||||
}
|
||||
const listener = ($event) => {
|
||||
if (this.disable) {
|
||||
return
|
||||
}
|
||||
const keyName = Object.keys(keyNames).find((key) => {
|
||||
const keyName = $event.key
|
||||
const value = keyNames[key]
|
||||
return (
|
||||
value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||
)
|
||||
})
|
||||
if (keyName) {
|
||||
// 避免和其他按键事件冲突
|
||||
setTimeout(() => {
|
||||
this.$emit(keyName, {})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keyup', listener)
|
||||
// this.$once('hook:beforeDestroy', () => {
|
||||
// document.removeEventListener('keyup', listener)
|
||||
// })
|
||||
},
|
||||
render: () => {
|
||||
return null
|
||||
},
|
||||
}
|
||||
// #endif
|
||||
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
this.popup = this.getParent()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getParent(name = 'uniPopup') {
|
||||
let parent = this.$parent
|
||||
let parentName = parent.$options.name
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name
|
||||
}
|
||||
return parent
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="showPopup"
|
||||
class="uni-popup"
|
||||
:class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']"
|
||||
>
|
||||
<view @touchstart="touchstart">
|
||||
<UniTransition
|
||||
key="1"
|
||||
v-if="maskShow"
|
||||
name="mask"
|
||||
mode-class="fade"
|
||||
:styles="maskClass"
|
||||
:duration="duration"
|
||||
:show="showTrans"
|
||||
@click="onTap"
|
||||
/>
|
||||
<UniTransition
|
||||
key="2"
|
||||
:mode-class="ani"
|
||||
name="content"
|
||||
:styles="transClass"
|
||||
:duration="duration"
|
||||
:show="showTrans"
|
||||
@click="onTap"
|
||||
>
|
||||
<view
|
||||
class="uni-popup__wrapper"
|
||||
:style="{ backgroundColor: bg }"
|
||||
:class="[popupstyle]"
|
||||
@click="clear"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</UniTransition>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<keypress v-if="maskShow" @esc="onTap" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef H5
|
||||
import keypress from './keypress.js'
|
||||
// #endif
|
||||
import UniTransition from '../../../uni-transition/components/uni-transition/uni-transition.vue'
|
||||
/**
|
||||
* PopUp 弹出层
|
||||
* @description 弹出层组件,为了解决遮罩弹层的问题
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
|
||||
* @value top 顶部弹出
|
||||
* @value center 中间弹出
|
||||
* @value bottom 底部弹出
|
||||
* @value left 左侧弹出
|
||||
* @value right 右侧弹出
|
||||
* @value message 消息提示
|
||||
* @value dialog 对话框
|
||||
* @value share 底部分享示例
|
||||
* @property {Boolean} animation = [true|false] 是否开启动画
|
||||
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
|
||||
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
|
||||
* @property {String} backgroundColor 主窗口背景色
|
||||
* @property {String} maskBackgroundColor 蒙版颜色
|
||||
* @property {Boolean} safeArea 是否适配底部安全区
|
||||
* @event {Function} change 打开关闭弹窗触发,e={show: false}
|
||||
* @event {Function} maskClick 点击遮罩触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniPopup',
|
||||
components: {
|
||||
UniTransition,
|
||||
// #ifdef H5
|
||||
keypress,
|
||||
// #endif
|
||||
},
|
||||
emits: ['change', 'maskClick'],
|
||||
props: {
|
||||
// 开启动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
|
||||
// message: 消息提示 ; dialog : 对话框
|
||||
type: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
},
|
||||
// maskClick
|
||||
isMaskClick: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
// TODO 2 个版本后废弃属性 ,使用 isMaskClick
|
||||
maskClick: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'none',
|
||||
},
|
||||
safeArea: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
maskBackgroundColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.4)',
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
/**
|
||||
* 监听type类型
|
||||
*/
|
||||
type: {
|
||||
handler: function (type) {
|
||||
if (!this.config[type]) return
|
||||
this[this.config[type]](true)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
isDesktop: {
|
||||
handler: function (newVal) {
|
||||
if (!this.config[newVal]) return
|
||||
this[this.config[this.type]](true)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
/**
|
||||
* 监听遮罩是否可点击
|
||||
* @param {Object} val
|
||||
*/
|
||||
maskClick: {
|
||||
handler: function (val) {
|
||||
this.mkclick = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
isMaskClick: {
|
||||
handler: function (val) {
|
||||
this.mkclick = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
// H5 下禁止底部滚动
|
||||
showPopup(show) {
|
||||
// #ifdef H5
|
||||
// fix by mehaotian 处理 h5 滚动穿透的问题
|
||||
document.getElementsByTagName('body')[0].style.overflow = show
|
||||
? 'hidden'
|
||||
: 'visible'
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
duration: 300,
|
||||
ani: [],
|
||||
showPopup: false,
|
||||
showTrans: false,
|
||||
popupWidth: 0,
|
||||
popupHeight: 0,
|
||||
config: {
|
||||
top: 'top',
|
||||
bottom: 'bottom',
|
||||
center: 'center',
|
||||
left: 'left',
|
||||
right: 'right',
|
||||
message: 'top',
|
||||
dialog: 'center',
|
||||
share: 'bottom',
|
||||
},
|
||||
maskClass: {
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||
},
|
||||
transClass: {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
maskShow: true,
|
||||
mkclick: true,
|
||||
popupstyle: this.isDesktop ? 'fixforpc-top' : 'top',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDesktop() {
|
||||
return this.popupWidth >= 500 && this.popupHeight >= 500
|
||||
},
|
||||
bg() {
|
||||
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
|
||||
return 'transparent'
|
||||
}
|
||||
return this.backgroundColor
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const fixSize = () => {
|
||||
const {
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
windowTop,
|
||||
safeArea,
|
||||
screenHeight,
|
||||
safeAreaInsets,
|
||||
} = uni.getSystemInfoSync()
|
||||
this.popupWidth = windowWidth
|
||||
this.popupHeight = windowHeight + (windowTop || 0)
|
||||
// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
|
||||
if (safeArea && this.safeArea) {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.safeAreaInsets = screenHeight - safeArea.bottom
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.safeAreaInsets = safeAreaInsets.bottom
|
||||
// #endif
|
||||
} else {
|
||||
this.safeAreaInsets = 0
|
||||
}
|
||||
}
|
||||
fixSize()
|
||||
// #ifdef H5
|
||||
// window.addEventListener('resize', fixSize)
|
||||
// this.$once('hook:beforeDestroy', () => {
|
||||
// window.removeEventListener('resize', fixSize)
|
||||
// })
|
||||
// #endif
|
||||
},
|
||||
// #ifndef VUE3
|
||||
// TODO vue2
|
||||
destroyed() {
|
||||
this.setH5Visible()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
// TODO vue3
|
||||
unmounted() {
|
||||
this.setH5Visible()
|
||||
},
|
||||
// #endif
|
||||
created() {
|
||||
// this.mkclick = this.isMaskClick || this.maskClick
|
||||
if (this.isMaskClick === null && this.maskClick === null) {
|
||||
this.mkclick = true
|
||||
} else {
|
||||
this.mkclick =
|
||||
this.isMaskClick !== null ? this.isMaskClick : this.maskClick
|
||||
}
|
||||
if (this.animation) {
|
||||
this.duration = 300
|
||||
} else {
|
||||
this.duration = 0
|
||||
}
|
||||
// TODO 处理 message 组件生命周期异常的问题
|
||||
this.messageChild = null
|
||||
// TODO 解决头条冒泡的问题
|
||||
this.clearPropagation = false
|
||||
this.maskClass.backgroundColor = this.maskBackgroundColor
|
||||
},
|
||||
methods: {
|
||||
setH5Visible() {
|
||||
// #ifdef H5
|
||||
// fix by mehaotian 处理 h5 滚动穿透的问题
|
||||
document.getElementsByTagName('body')[0].style.overflow = 'visible'
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* 公用方法,不显示遮罩层
|
||||
*/
|
||||
closeMask() {
|
||||
this.maskShow = false
|
||||
},
|
||||
/**
|
||||
* 公用方法,遮罩层禁止点击
|
||||
*/
|
||||
disableMask() {
|
||||
this.mkclick = false
|
||||
},
|
||||
// TODO nvue 取消冒泡
|
||||
clear(e) {
|
||||
// #ifndef APP-NVUE
|
||||
e.stopPropagation()
|
||||
// #endif
|
||||
this.clearPropagation = true
|
||||
},
|
||||
|
||||
open(direction) {
|
||||
// fix by mehaotian 处理快速打开关闭的情况
|
||||
if (this.showPopup) {
|
||||
return
|
||||
}
|
||||
let innerType = [
|
||||
'top',
|
||||
'center',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'message',
|
||||
'dialog',
|
||||
'share',
|
||||
]
|
||||
if (!(direction && innerType?.indexOf(direction) !== -1)) {
|
||||
direction = this.type
|
||||
}
|
||||
if (!this.config[direction]) {
|
||||
console.error('缺少类型:', direction)
|
||||
return
|
||||
}
|
||||
this[this.config[direction]]()
|
||||
this.$emit('change', {
|
||||
show: true,
|
||||
type: direction,
|
||||
})
|
||||
},
|
||||
close(type) {
|
||||
this.showTrans = false
|
||||
this.$emit('change', {
|
||||
show: false,
|
||||
type: this.type,
|
||||
})
|
||||
clearTimeout(this.timer)
|
||||
// // 自定义关闭事件
|
||||
// this.customOpen && this.customClose()
|
||||
this.timer = setTimeout(() => {
|
||||
this.showPopup = false
|
||||
}, 300)
|
||||
},
|
||||
// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
|
||||
touchstart() {
|
||||
this.clearPropagation = false
|
||||
},
|
||||
|
||||
onTap() {
|
||||
if (this.clearPropagation) {
|
||||
// fix by mehaotian 兼容 nvue
|
||||
this.clearPropagation = false
|
||||
return
|
||||
}
|
||||
this.$emit('maskClick')
|
||||
if (!this.mkclick) return
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 顶部弹出样式处理
|
||||
*/
|
||||
top(type) {
|
||||
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
|
||||
this.ani = ['slide-top']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: this.bg,
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
this.$nextTick(() => {
|
||||
if (this.messageChild && this.type === 'message') {
|
||||
this.messageChild.timerClose()
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 底部弹出样式处理
|
||||
*/
|
||||
bottom(type) {
|
||||
this.popupstyle = 'bottom'
|
||||
this.ani = ['slide-bottom']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
paddingBottom: this.safeAreaInsets + 'px',
|
||||
backgroundColor: this.bg,
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
/**
|
||||
* 中间弹出样式处理
|
||||
*/
|
||||
center(type) {
|
||||
this.popupstyle = 'center'
|
||||
this.ani = ['zoom-out', 'fade']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
/* #ifndef APP-NVUE */
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
/* #endif */
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
left(type) {
|
||||
this.popupstyle = 'left'
|
||||
this.ani = ['slide-left']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
backgroundColor: this.bg,
|
||||
/* #ifndef APP-NVUE */
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
/* #endif */
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
right(type) {
|
||||
this.popupstyle = 'right'
|
||||
this.ani = ['slide-right']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
backgroundColor: this.bg,
|
||||
/* #ifndef APP-NVUE */
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
/* #endif */
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.uni-popup {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 999999;
|
||||
|
||||
/* #endif */
|
||||
&.top,
|
||||
&.left,
|
||||
&.right {
|
||||
/* #ifdef H5 */
|
||||
top: var(--window-top);
|
||||
/* #endif */
|
||||
/* #ifndef H5 */
|
||||
top: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-popup__wrapper {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
|
||||
/* iphonex 等安全区设置,底部安全区适配 */
|
||||
/* #ifndef APP-NVUE */
|
||||
// padding-bottom: constant(safe-area-inset-bottom);
|
||||
// padding-bottom: env(safe-area-inset-bottom);
|
||||
/* #endif */
|
||||
&.left,
|
||||
&.right {
|
||||
/* #ifdef H5 */
|
||||
padding-top: var(--window-top);
|
||||
/* #endif */
|
||||
/* #ifndef H5 */
|
||||
padding-top: 0;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fixforpc-z-index {
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 999;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.fixforpc-top {
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
## 1.0.3(2022-01-21)
|
||||
|
||||
- 优化 组件示例
|
||||
|
||||
## 1.0.2(2021-11-22)
|
||||
|
||||
- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
|
||||
|
||||
## 1.0.1(2021-11-22)
|
||||
|
||||
- 修复 vue3 中 scss 语法兼容问题
|
||||
|
||||
## 1.0.0(2021-11-18)
|
||||
|
||||
- init
|
||||
@@ -0,0 +1 @@
|
||||
@import './styles/index.scss';
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"id": "uni-scss",
|
||||
"displayName": "uni-scss 辅助样式",
|
||||
"version": "1.0.3",
|
||||
"description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。",
|
||||
"keywords": [
|
||||
"uni-scss",
|
||||
"uni-ui",
|
||||
"辅助样式"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"JS SDK",
|
||||
"通用 SDK"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
|
||||
|
||||
#### 如使用过程中有任何问题,或者您对 uni-ui 有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
@@ -0,0 +1,7 @@
|
||||
@import './setting/_variables.scss';
|
||||
@import './setting/_border.scss';
|
||||
@import './setting/_color.scss';
|
||||
@import './setting/_space.scss';
|
||||
@import './setting/_radius.scss';
|
||||
@import './setting/_text.scss';
|
||||
@import './setting/_styles.scss';
|
||||
@@ -0,0 +1,3 @@
|
||||
.uni-border {
|
||||
border: 1px $uni-border-1 solid;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐
|
||||
// @mixin get-styles($k,$c) {
|
||||
// @if $k == size or $k == weight{
|
||||
// font-#{$k}:#{$c}
|
||||
// }@else{
|
||||
// #{$k}:#{$c}
|
||||
// }
|
||||
// }
|
||||
$uni-ui-color:(
|
||||
// 主色
|
||||
primary: $uni-primary,
|
||||
primary-disable: $uni-primary-disable,
|
||||
primary-light: $uni-primary-light,
|
||||
// 辅助色
|
||||
success: $uni-success,
|
||||
success-disable: $uni-success-disable,
|
||||
success-light: $uni-success-light,
|
||||
warning: $uni-warning,
|
||||
warning-disable: $uni-warning-disable,
|
||||
warning-light: $uni-warning-light,
|
||||
error: $uni-error,
|
||||
error-disable: $uni-error-disable,
|
||||
error-light: $uni-error-light,
|
||||
info: $uni-info,
|
||||
info-disable: $uni-info-disable,
|
||||
info-light: $uni-info-light,
|
||||
// 中性色
|
||||
main-color: $uni-main-color,
|
||||
base-color: $uni-base-color,
|
||||
secondary-color: $uni-secondary-color,
|
||||
extra-color: $uni-extra-color,
|
||||
// 背景色
|
||||
bg-color: $uni-bg-color,
|
||||
// 边框颜色
|
||||
border-1: $uni-border-1,
|
||||
border-2: $uni-border-2,
|
||||
border-3: $uni-border-3,
|
||||
border-4: $uni-border-4,
|
||||
// 黑色
|
||||
black:$uni-black,
|
||||
// 白色
|
||||
white:$uni-white,
|
||||
// 透明
|
||||
transparent:$uni-transparent
|
||||
) !default;
|
||||
@each $key, $child in $uni-ui-color {
|
||||
.uni-#{"" + $key} {
|
||||
color: $child;
|
||||
}
|
||||
.uni-#{"" + $key}-bg {
|
||||
background-color: $child;
|
||||
}
|
||||
}
|
||||
.uni-shadow-sm {
|
||||
box-shadow: $uni-shadow-sm;
|
||||
}
|
||||
.uni-shadow-base {
|
||||
box-shadow: $uni-shadow-base;
|
||||
}
|
||||
.uni-shadow-lg {
|
||||
box-shadow: $uni-shadow-lg;
|
||||
}
|
||||
.uni-mask {
|
||||
background-color:$uni-mask;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@mixin radius($r,$d:null ,$important: false){
|
||||
$radius-value:map-get($uni-radius, $r) if($important, !important, null);
|
||||
// Key exists within the $uni-radius variable
|
||||
@if (map-has-key($uni-radius, $r) and $d){
|
||||
@if $d == t {
|
||||
border-top-left-radius:$radius-value;
|
||||
border-top-right-radius:$radius-value;
|
||||
}@else if $d == r {
|
||||
border-top-right-radius:$radius-value;
|
||||
border-bottom-right-radius:$radius-value;
|
||||
}@else if $d == b {
|
||||
border-bottom-left-radius:$radius-value;
|
||||
border-bottom-right-radius:$radius-value;
|
||||
}@else if $d == l {
|
||||
border-top-left-radius:$radius-value;
|
||||
border-bottom-left-radius:$radius-value;
|
||||
}@else if $d == tl {
|
||||
border-top-left-radius:$radius-value;
|
||||
}@else if $d == tr {
|
||||
border-top-right-radius:$radius-value;
|
||||
}@else if $d == br {
|
||||
border-bottom-right-radius:$radius-value;
|
||||
}@else if $d == bl {
|
||||
border-bottom-left-radius:$radius-value;
|
||||
}
|
||||
}@else{
|
||||
border-radius:$radius-value;
|
||||
}
|
||||
}
|
||||
|
||||
@each $key, $child in $uni-radius {
|
||||
@if($key){
|
||||
.uni-radius-#{"" + $key} {
|
||||
@include radius($key)
|
||||
}
|
||||
}@else{
|
||||
.uni-radius {
|
||||
@include radius($key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $direction in t, r, b, l,tl, tr, br, bl {
|
||||
@each $key, $child in $uni-radius {
|
||||
@if($key){
|
||||
.uni-radius-#{"" + $direction}-#{"" + $key} {
|
||||
@include radius($key,$direction,false)
|
||||
}
|
||||
}@else{
|
||||
.uni-radius-#{$direction} {
|
||||
@include radius($key,$direction,false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
@mixin fn($space,$direction,$size,$n) {
|
||||
@if $n {
|
||||
#{$space}-#{$direction}: #{$size*$uni-space-root}px
|
||||
} @else {
|
||||
#{$space}-#{$direction}: #{-$size*$uni-space-root}px
|
||||
}
|
||||
}
|
||||
@mixin get-styles($direction,$i,$space,$n){
|
||||
@if $direction == t {
|
||||
@include fn($space, top,$i,$n);
|
||||
}
|
||||
@if $direction == r {
|
||||
@include fn($space, right,$i,$n);
|
||||
}
|
||||
@if $direction == b {
|
||||
@include fn($space, bottom,$i,$n);
|
||||
}
|
||||
@if $direction == l {
|
||||
@include fn($space, left,$i,$n);
|
||||
}
|
||||
@if $direction == x {
|
||||
@include fn($space, left,$i,$n);
|
||||
@include fn($space, right,$i,$n);
|
||||
}
|
||||
@if $direction == y {
|
||||
@include fn($space, top,$i,$n);
|
||||
@include fn($space, bottom,$i,$n);
|
||||
}
|
||||
@if $direction == a {
|
||||
@if $n {
|
||||
#{$space}:#{$i*$uni-space-root}px;
|
||||
} @else {
|
||||
#{$space}:#{-$i*$uni-space-root}px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $orientation in m,p {
|
||||
$space: margin;
|
||||
@if $orientation == m {
|
||||
$space: margin;
|
||||
} @else {
|
||||
$space: padding;
|
||||
}
|
||||
@for $i from 0 through 16 {
|
||||
@each $direction in t, r, b, l, x, y, a {
|
||||
.uni-#{$orientation}#{$direction}-#{$i} {
|
||||
@include get-styles($direction,$i,$space,true);
|
||||
}
|
||||
.uni-#{$orientation}#{$direction}-n#{$i} {
|
||||
@include get-styles($direction,$i,$space,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/* #ifndef APP-NVUE */
|
||||
|
||||
$-color-white:#fff;
|
||||
$-color-black:#000;
|
||||
@mixin base-style($color) {
|
||||
color: #fff;
|
||||
background-color: $color;
|
||||
border-color: mix($-color-black, $color, 8%);
|
||||
&:not([hover-class]):active {
|
||||
background: mix($-color-black, $color, 10%);
|
||||
border-color: mix($-color-black, $color, 20%);
|
||||
color: $-color-white;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@mixin is-color($color) {
|
||||
@include base-style($color);
|
||||
&[loading] {
|
||||
@include base-style($color);
|
||||
&::before {
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
&[disabled] {
|
||||
&,
|
||||
&[loading],
|
||||
&:not([hover-class]):active {
|
||||
color: $-color-white;
|
||||
border-color: mix(darken($color,10%), $-color-white);
|
||||
background-color: mix($color, $-color-white);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@mixin base-plain-style($color) {
|
||||
color:$color;
|
||||
background-color: mix($-color-white, $color, 90%);
|
||||
border-color: mix($-color-white, $color, 70%);
|
||||
&:not([hover-class]):active {
|
||||
background: mix($-color-white, $color, 80%);
|
||||
color: $color;
|
||||
outline: none;
|
||||
border-color: mix($-color-white, $color, 50%);
|
||||
}
|
||||
}
|
||||
@mixin is-plain($color){
|
||||
&[plain] {
|
||||
@include base-plain-style($color);
|
||||
&[loading] {
|
||||
@include base-plain-style($color);
|
||||
&::before {
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
&[disabled] {
|
||||
&,
|
||||
&:active {
|
||||
color: mix($-color-white, $color, 40%);
|
||||
background-color: mix($-color-white, $color, 90%);
|
||||
border-color: mix($-color-white, $color, 80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.uni-btn {
|
||||
margin: 5px;
|
||||
color: #393939;
|
||||
border:1px solid #ccc;
|
||||
font-size: 16px;
|
||||
font-weight: 200;
|
||||
background-color: #F9F9F9;
|
||||
// TODO 暂时处理边框隐藏一边的问题
|
||||
overflow: visible;
|
||||
&::after{
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:not([type]),&[type=default] {
|
||||
color: #999;
|
||||
&[loading] {
|
||||
background: none;
|
||||
&::before {
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&[disabled]{
|
||||
color: mix($-color-white, #999, 60%);
|
||||
&,
|
||||
&[loading],
|
||||
&:active {
|
||||
color: mix($-color-white, #999, 60%);
|
||||
background-color: mix($-color-white,$-color-black , 98%);
|
||||
border-color: mix($-color-white, #999, 85%);
|
||||
}
|
||||
}
|
||||
|
||||
&[plain] {
|
||||
color: #999;
|
||||
background: none;
|
||||
border-color: $uni-border-1;
|
||||
&:not([hover-class]):active {
|
||||
background: none;
|
||||
color: mix($-color-white, $-color-black, 80%);
|
||||
border-color: mix($-color-white, $-color-black, 90%);
|
||||
outline: none;
|
||||
}
|
||||
&[disabled]{
|
||||
&,
|
||||
&[loading],
|
||||
&:active {
|
||||
background: none;
|
||||
color: mix($-color-white, #999, 60%);
|
||||
border-color: mix($-color-white, #999, 85%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not([hover-class]):active {
|
||||
color: mix($-color-white, $-color-black, 50%);
|
||||
}
|
||||
|
||||
&[size=mini] {
|
||||
font-size: 16px;
|
||||
font-weight: 200;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
&.uni-btn-small {
|
||||
font-size: 14px;
|
||||
}
|
||||
&.uni-btn-mini {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.uni-btn-radius {
|
||||
border-radius: 999px;
|
||||
}
|
||||
&[type=primary] {
|
||||
@include is-color($uni-primary);
|
||||
@include is-plain($uni-primary)
|
||||
}
|
||||
&[type=success] {
|
||||
@include is-color($uni-success);
|
||||
@include is-plain($uni-success)
|
||||
}
|
||||
&[type=error] {
|
||||
@include is-color($uni-error);
|
||||
@include is-plain($uni-error)
|
||||
}
|
||||
&[type=warning] {
|
||||
@include is-color($uni-warning);
|
||||
@include is-plain($uni-warning)
|
||||
}
|
||||
&[type=info] {
|
||||
@include is-color($uni-info);
|
||||
@include is-plain($uni-info)
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
@@ -0,0 +1,24 @@
|
||||
@mixin get-styles($k,$c) {
|
||||
@if $k == size or $k == weight{
|
||||
font-#{$k}:#{$c}
|
||||
}@else{
|
||||
#{$k}:#{$c}
|
||||
}
|
||||
}
|
||||
|
||||
@each $key, $child in $uni-headings {
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-#{$key} {
|
||||
@each $k, $c in $child {
|
||||
@include get-styles($k,$c)
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
.container .uni-#{$key} {
|
||||
@each $k, $c in $child {
|
||||
@include get-styles($k,$c)
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// @use "sass:math";
|
||||
@import '../tools/functions.scss';
|
||||
// 间距基础倍数
|
||||
$uni-space-root: 2 !default;
|
||||
// 边框半径默认值
|
||||
$uni-radius-root:5px !default;
|
||||
$uni-radius: () !default;
|
||||
// 边框半径断点
|
||||
$uni-radius: map-deep-merge(
|
||||
(
|
||||
0: 0,
|
||||
// TODO 当前版本暂时不支持 sm 属性
|
||||
// 'sm': math.div($uni-radius-root, 2),
|
||||
null: $uni-radius-root,
|
||||
'lg': $uni-radius-root * 2,
|
||||
'xl': $uni-radius-root * 6,
|
||||
'pill': 9999px,
|
||||
'circle': 50%
|
||||
),
|
||||
$uni-radius
|
||||
);
|
||||
// 字体家族
|
||||
$body-font-family: 'Roboto', sans-serif !default;
|
||||
// 文本
|
||||
$heading-font-family: $body-font-family !default;
|
||||
$uni-headings: () !default;
|
||||
$letterSpacing: -0.01562em;
|
||||
$uni-headings: map-deep-merge(
|
||||
(
|
||||
'h1': (
|
||||
size: 32px,
|
||||
weight: 300,
|
||||
line-height: 50px,
|
||||
// letter-spacing:-0.01562em
|
||||
),
|
||||
'h2': (
|
||||
size: 28px,
|
||||
weight: 300,
|
||||
line-height: 40px,
|
||||
// letter-spacing: -0.00833em
|
||||
),
|
||||
'h3': (
|
||||
size: 24px,
|
||||
weight: 400,
|
||||
line-height: 32px,
|
||||
// letter-spacing: normal
|
||||
),
|
||||
'h4': (
|
||||
size: 20px,
|
||||
weight: 400,
|
||||
line-height: 30px,
|
||||
// letter-spacing: 0.00735em
|
||||
),
|
||||
'h5': (
|
||||
size: 16px,
|
||||
weight: 400,
|
||||
line-height: 24px,
|
||||
// letter-spacing: normal
|
||||
),
|
||||
'h6': (
|
||||
size: 14px,
|
||||
weight: 500,
|
||||
line-height: 18px,
|
||||
// letter-spacing: 0.0125em
|
||||
),
|
||||
'subtitle': (
|
||||
size: 12px,
|
||||
weight: 400,
|
||||
line-height: 20px,
|
||||
// letter-spacing: 0.00937em
|
||||
),
|
||||
'body': (
|
||||
font-size: 14px,
|
||||
font-weight: 400,
|
||||
line-height: 22px,
|
||||
// letter-spacing: 0.03125em
|
||||
),
|
||||
'caption': (
|
||||
'size': 12px,
|
||||
'weight': 400,
|
||||
'line-height': 20px,
|
||||
// 'letter-spacing': 0.03333em,
|
||||
// 'text-transform': false
|
||||
)
|
||||
),
|
||||
$uni-headings
|
||||
);
|
||||
|
||||
|
||||
|
||||
// 主色
|
||||
$uni-primary: #2979ff !default;
|
||||
$uni-primary-disable:lighten($uni-primary,20%) !default;
|
||||
$uni-primary-light: lighten($uni-primary,25%) !default;
|
||||
|
||||
// 辅助色
|
||||
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
|
||||
$uni-success: #18bc37 !default;
|
||||
$uni-success-disable:lighten($uni-success,20%) !default;
|
||||
$uni-success-light: lighten($uni-success,25%) !default;
|
||||
|
||||
$uni-warning: #f3a73f !default;
|
||||
$uni-warning-disable:lighten($uni-warning,20%) !default;
|
||||
$uni-warning-light: lighten($uni-warning,25%) !default;
|
||||
|
||||
$uni-error: #e43d33 !default;
|
||||
$uni-error-disable:lighten($uni-error,20%) !default;
|
||||
$uni-error-light: lighten($uni-error,25%) !default;
|
||||
|
||||
$uni-info: #8f939c !default;
|
||||
$uni-info-disable:lighten($uni-info,20%) !default;
|
||||
$uni-info-light: lighten($uni-info,25%) !default;
|
||||
|
||||
// 中性色
|
||||
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
|
||||
$uni-main-color: #3a3a3a !default; // 主要文字
|
||||
$uni-base-color: #6a6a6a !default; // 常规文字
|
||||
$uni-secondary-color: #909399 !default; // 次要文字
|
||||
$uni-extra-color: #c7c7c7 !default; // 辅助说明
|
||||
|
||||
// 边框颜色
|
||||
$uni-border-1: #F0F0F0 !default;
|
||||
$uni-border-2: #EDEDED !default;
|
||||
$uni-border-3: #DCDCDC !default;
|
||||
$uni-border-4: #B9B9B9 !default;
|
||||
|
||||
// 常规色
|
||||
$uni-black: #000000 !default;
|
||||
$uni-white: #ffffff !default;
|
||||
$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
|
||||
|
||||
// 背景色
|
||||
$uni-bg-color: #f7f7f7 !default;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-sm: 8px !default;
|
||||
$uni-spacing-base: 15px !default;
|
||||
$uni-spacing-lg: 30px !default;
|
||||
|
||||
// 阴影
|
||||
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
|
||||
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
|
||||
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
|
||||
|
||||
// 蒙版
|
||||
$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;
|
||||
@@ -0,0 +1,19 @@
|
||||
// 合并 map
|
||||
@function map-deep-merge($parent-map, $child-map){
|
||||
$result: $parent-map;
|
||||
@each $key, $child in $child-map {
|
||||
$parent-has-key: map-has-key($result, $key);
|
||||
$parent-value: map-get($result, $key);
|
||||
$parent-type: type-of($parent-value);
|
||||
$child-type: type-of($child);
|
||||
$parent-is-map: $parent-type == map;
|
||||
$child-is-map: $child-type == map;
|
||||
|
||||
@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
|
||||
$result: map-merge($result, ( $key: $child ));
|
||||
}@else {
|
||||
$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
|
||||
}
|
||||
}
|
||||
@return $result;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// 间距基础倍数
|
||||
$uni-space-root: 2;
|
||||
// 边框半径默认值
|
||||
$uni-radius-root:5px;
|
||||
// 主色
|
||||
$uni-primary: #2979ff;
|
||||
// 辅助色
|
||||
$uni-success: #4cd964;
|
||||
// 警告色
|
||||
$uni-warning: #f0ad4e;
|
||||
// 错误色
|
||||
$uni-error: #dd524d;
|
||||
// 描述色
|
||||
$uni-info: #909399;
|
||||
// 中性色
|
||||
$uni-main-color: #303133;
|
||||
$uni-base-color: #606266;
|
||||
$uni-secondary-color: #909399;
|
||||
$uni-extra-color: #C0C4CC;
|
||||
// 背景色
|
||||
$uni-bg-color: #f5f5f5;
|
||||
// 边框颜色
|
||||
$uni-border-1: #DCDFE6;
|
||||
$uni-border-2: #E4E7ED;
|
||||
$uni-border-3: #EBEEF5;
|
||||
$uni-border-4: #F2F6FC;
|
||||
|
||||
// 常规色
|
||||
$uni-black: #000000;
|
||||
$uni-white: #ffffff;
|
||||
$uni-transparent: rgba($color: #000000, $alpha: 0);
|
||||
@@ -0,0 +1,62 @@
|
||||
@import './styles/setting/_variables.scss';
|
||||
// 间距基础倍数
|
||||
$uni-space-root: 2;
|
||||
// 边框半径默认值
|
||||
$uni-radius-root:5px;
|
||||
|
||||
// 主色
|
||||
$uni-primary: #2979ff;
|
||||
$uni-primary-disable:mix(#fff,$uni-primary,50%);
|
||||
$uni-primary-light: mix(#fff,$uni-primary,80%);
|
||||
|
||||
// 辅助色
|
||||
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
|
||||
$uni-success: #18bc37;
|
||||
$uni-success-disable:mix(#fff,$uni-success,50%);
|
||||
$uni-success-light: mix(#fff,$uni-success,80%);
|
||||
|
||||
$uni-warning: #f3a73f;
|
||||
$uni-warning-disable:mix(#fff,$uni-warning,50%);
|
||||
$uni-warning-light: mix(#fff,$uni-warning,80%);
|
||||
|
||||
$uni-error: #e43d33;
|
||||
$uni-error-disable:mix(#fff,$uni-error,50%);
|
||||
$uni-error-light: mix(#fff,$uni-error,80%);
|
||||
|
||||
$uni-info: #8f939c;
|
||||
$uni-info-disable:mix(#fff,$uni-info,50%);
|
||||
$uni-info-light: mix(#fff,$uni-info,80%);
|
||||
|
||||
// 中性色
|
||||
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
|
||||
$uni-main-color: #3a3a3a; // 主要文字
|
||||
$uni-base-color: #6a6a6a; // 常规文字
|
||||
$uni-secondary-color: #909399; // 次要文字
|
||||
$uni-extra-color: #c7c7c7; // 辅助说明
|
||||
|
||||
// 边框颜色
|
||||
$uni-border-1: #F0F0F0;
|
||||
$uni-border-2: #EDEDED;
|
||||
$uni-border-3: #DCDCDC;
|
||||
$uni-border-4: #B9B9B9;
|
||||
|
||||
// 常规色
|
||||
$uni-black: #000000;
|
||||
$uni-white: #ffffff;
|
||||
$uni-transparent: rgba($color: #000000, $alpha: 0);
|
||||
|
||||
// 背景色
|
||||
$uni-bg-color: #f7f7f7;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-sm: 8px;
|
||||
$uni-spacing-base: 15px;
|
||||
$uni-spacing-lg: 30px;
|
||||
|
||||
// 阴影
|
||||
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
|
||||
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
|
||||
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
|
||||
|
||||
// 蒙版
|
||||
$uni-mask: rgba($color: #000000, $alpha: 0.4);
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// const defaultOption = {
|
||||
// duration: 300,
|
||||
// timingFunction: 'linear',
|
||||
// delay: 0,
|
||||
// transformOrigin: '50% 50% 0'
|
||||
// }
|
||||
// #ifdef APP-NVUE
|
||||
const nvueAnimation = uni.requireNativePlugin('animation')
|
||||
// #endif
|
||||
class MPAnimation {
|
||||
constructor(options, _this) {
|
||||
this.options = options
|
||||
// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
|
||||
this.animation = uni.createAnimation({
|
||||
...options,
|
||||
})
|
||||
this.currentStepAnimates = {}
|
||||
this.next = 0
|
||||
this.$ = _this
|
||||
}
|
||||
|
||||
_nvuePushAnimates(type, args) {
|
||||
let aniObj = this.currentStepAnimates[this.next]
|
||||
let styles = {}
|
||||
if (!aniObj) {
|
||||
styles = {
|
||||
styles: {},
|
||||
config: {},
|
||||
}
|
||||
} else {
|
||||
styles = aniObj
|
||||
}
|
||||
if (animateTypes1.includes(type)) {
|
||||
if (!styles.styles.transform) {
|
||||
styles.styles.transform = ''
|
||||
}
|
||||
let unit = ''
|
||||
if (type === 'rotate') {
|
||||
unit = 'deg'
|
||||
}
|
||||
styles.styles.transform += `${type}(${args + unit}) `
|
||||
} else {
|
||||
styles.styles[type] = `${args}`
|
||||
}
|
||||
this.currentStepAnimates[this.next] = styles
|
||||
}
|
||||
_animateRun(styles = {}, config = {}) {
|
||||
let ref = this.$.$refs['ani'].ref
|
||||
if (!ref) return
|
||||
return new Promise((resolve, reject) => {
|
||||
nvueAnimation.transition(
|
||||
ref,
|
||||
{
|
||||
styles,
|
||||
...config,
|
||||
},
|
||||
(res) => {
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
_nvueNextAnimate(animates, step = 0, fn) {
|
||||
let obj = animates[step]
|
||||
if (obj) {
|
||||
let { styles, config } = obj
|
||||
this._animateRun(styles, config).then(() => {
|
||||
step += 1
|
||||
this._nvueNextAnimate(animates, step, fn)
|
||||
})
|
||||
} else {
|
||||
this.currentStepAnimates = {}
|
||||
typeof fn === 'function' && fn()
|
||||
this.isEnd = true
|
||||
}
|
||||
}
|
||||
|
||||
step(config = {}) {
|
||||
// #ifndef APP-NVUE
|
||||
this.animation.step(config)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.currentStepAnimates[this.next].config = Object.assign(
|
||||
{},
|
||||
this.options,
|
||||
config
|
||||
)
|
||||
this.currentStepAnimates[this.next].styles.transformOrigin =
|
||||
this.currentStepAnimates[this.next].config.transformOrigin
|
||||
this.next++
|
||||
// #endif
|
||||
return this
|
||||
}
|
||||
|
||||
run(fn) {
|
||||
// #ifndef APP-NVUE
|
||||
this.$.animationData = this.animation.export()
|
||||
this.$.timer = setTimeout(() => {
|
||||
typeof fn === 'function' && fn()
|
||||
}, this.$.durationTime)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.isEnd = false
|
||||
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
|
||||
if (!ref) return
|
||||
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
|
||||
this.next = 0
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
const animateTypes1 = [
|
||||
'matrix',
|
||||
'matrix3d',
|
||||
'rotate',
|
||||
'rotate3d',
|
||||
'rotateX',
|
||||
'rotateY',
|
||||
'rotateZ',
|
||||
'scale',
|
||||
'scale3d',
|
||||
'scaleX',
|
||||
'scaleY',
|
||||
'scaleZ',
|
||||
'skew',
|
||||
'skewX',
|
||||
'skewY',
|
||||
'translate',
|
||||
'translate3d',
|
||||
'translateX',
|
||||
'translateY',
|
||||
'translateZ',
|
||||
]
|
||||
const animateTypes2 = ['opacity', 'backgroundColor']
|
||||
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
|
||||
animateTypes1.concat(animateTypes2, animateTypes3).forEach((type) => {
|
||||
MPAnimation.prototype[type] = function (...args) {
|
||||
// #ifndef APP-NVUE
|
||||
this.animation[type](...args)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this._nvuePushAnimates(type, args)
|
||||
// #endif
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
export function createAnimation(option, _this) {
|
||||
if (!_this) return
|
||||
clearTimeout(_this.timer)
|
||||
return new MPAnimation(option, _this)
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createAnimation } from './createAnimation'
|
||||
|
||||
/**
|
||||
* Transition 过渡动画
|
||||
* @description 简单过渡动画组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
|
||||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
|
||||
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
|
||||
* @value fade 渐隐渐出过渡
|
||||
* @value slide-top 由上至下过渡
|
||||
* @value slide-right 由右至左过渡
|
||||
* @value slide-bottom 由下至上过渡
|
||||
* @value slide-left 由左至右过渡
|
||||
* @value zoom-in 由小到大过渡
|
||||
* @value zoom-out 由大到小过渡
|
||||
* @property {Number} duration 过渡动画持续时间
|
||||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
|
||||
*/
|
||||
export default {
|
||||
name: 'uniTransition',
|
||||
emits:['click','change'],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
modeClass: {
|
||||
type: [Array, String],
|
||||
default() {
|
||||
return 'fade'
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
customClass:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
onceRender:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
transform: '',
|
||||
opacity: 1,
|
||||
animationData: {},
|
||||
durationTime: 300,
|
||||
config: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.open()
|
||||
} else {
|
||||
// 避免上来就执行 close,导致动画错乱
|
||||
if (this.isShow) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 生成样式数据
|
||||
stylesObject() {
|
||||
let styles = {
|
||||
...this.styles,
|
||||
'transition-duration': this.duration / 1000 + 's'
|
||||
}
|
||||
let transform = ''
|
||||
for (let i in styles) {
|
||||
let line = this.toLine(i)
|
||||
transform += line + ':' + styles[i] + ';'
|
||||
}
|
||||
return transform
|
||||
},
|
||||
// 初始化动画条件
|
||||
transformStyles() {
|
||||
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 动画默认配置
|
||||
this.config = {
|
||||
duration: this.duration,
|
||||
timingFunction: 'ease',
|
||||
transformOrigin: '50% 50%',
|
||||
delay: 0
|
||||
}
|
||||
this.durationTime = this.duration
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* ref 触发 初始化动画
|
||||
*/
|
||||
init(obj = {}) {
|
||||
if (obj.duration) {
|
||||
this.durationTime = obj.duration
|
||||
}
|
||||
this.animation = createAnimation(Object.assign(this.config, obj),this)
|
||||
},
|
||||
/**
|
||||
* 点击组件触发回调
|
||||
*/
|
||||
onClick() {
|
||||
this.$emit('click', {
|
||||
detail: this.isShow
|
||||
})
|
||||
},
|
||||
/**
|
||||
* ref 触发 动画分组
|
||||
* @param {Object} obj
|
||||
*/
|
||||
step(obj, config = {}) {
|
||||
if (!this.animation) return
|
||||
for (let i in obj) {
|
||||
try {
|
||||
if(typeof obj[i] === 'object'){
|
||||
this.animation[i](...obj[i])
|
||||
}else{
|
||||
this.animation[i](obj[i])
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`方法 ${i} 不存在`)
|
||||
}
|
||||
}
|
||||
this.animation.step(config)
|
||||
return this
|
||||
},
|
||||
/**
|
||||
* ref 触发 执行动画
|
||||
*/
|
||||
run(fn) {
|
||||
if (!this.animation) return
|
||||
this.animation.run(fn)
|
||||
},
|
||||
// 开始过度动画
|
||||
open() {
|
||||
clearTimeout(this.timer)
|
||||
this.transform = ''
|
||||
this.isShow = true
|
||||
let { opacity, transform } = this.styleInit(false)
|
||||
if (typeof opacity !== 'undefined') {
|
||||
this.opacity = opacity
|
||||
}
|
||||
this.transform = transform
|
||||
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
|
||||
this.$nextTick(() => {
|
||||
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
|
||||
this.timer = setTimeout(() => {
|
||||
this.animation = createAnimation(this.config, this)
|
||||
this.tranfromInit(false).step()
|
||||
this.animation.run()
|
||||
this.$emit('change', {
|
||||
detail: this.isShow
|
||||
})
|
||||
}, 20)
|
||||
})
|
||||
},
|
||||
// 关闭过度动画
|
||||
close(type) {
|
||||
if (!this.animation) return
|
||||
this.tranfromInit(true)
|
||||
.step()
|
||||
.run(() => {
|
||||
this.isShow = false
|
||||
this.animationData = null
|
||||
this.animation = null
|
||||
let { opacity, transform } = this.styleInit(false)
|
||||
this.opacity = opacity || 1
|
||||
this.transform = transform
|
||||
this.$emit('change', {
|
||||
detail: this.isShow
|
||||
})
|
||||
})
|
||||
},
|
||||
// 处理动画开始前的默认样式
|
||||
styleInit(type) {
|
||||
let styles = {
|
||||
transform: ''
|
||||
}
|
||||
let buildStyle = (type, mode) => {
|
||||
if (mode === 'fade') {
|
||||
styles.opacity = this.animationType(type)[mode]
|
||||
} else {
|
||||
styles.transform += this.animationType(type)[mode] + ' '
|
||||
}
|
||||
}
|
||||
if (typeof this.modeClass === 'string') {
|
||||
buildStyle(type, this.modeClass)
|
||||
} else {
|
||||
this.modeClass.forEach(mode => {
|
||||
buildStyle(type, mode)
|
||||
})
|
||||
}
|
||||
return styles
|
||||
},
|
||||
// 处理内置组合动画
|
||||
tranfromInit(type) {
|
||||
let buildTranfrom = (type, mode) => {
|
||||
let aniNum = null
|
||||
if (mode === 'fade') {
|
||||
aniNum = type ? 0 : 1
|
||||
} else {
|
||||
aniNum = type ? '-100%' : '0'
|
||||
if (mode === 'zoom-in') {
|
||||
aniNum = type ? 0.8 : 1
|
||||
}
|
||||
if (mode === 'zoom-out') {
|
||||
aniNum = type ? 1.2 : 1
|
||||
}
|
||||
if (mode === 'slide-right') {
|
||||
aniNum = type ? '100%' : '0'
|
||||
}
|
||||
if (mode === 'slide-bottom') {
|
||||
aniNum = type ? '100%' : '0'
|
||||
}
|
||||
}
|
||||
this.animation[this.animationMode()[mode]](aniNum)
|
||||
}
|
||||
if (typeof this.modeClass === 'string') {
|
||||
buildTranfrom(type, this.modeClass)
|
||||
} else {
|
||||
this.modeClass.forEach(mode => {
|
||||
buildTranfrom(type, mode)
|
||||
})
|
||||
}
|
||||
|
||||
return this.animation
|
||||
},
|
||||
animationType(type) {
|
||||
return {
|
||||
fade: type ? 1 : 0,
|
||||
'slide-top': `translateY(${type ? '0' : '-100%'})`,
|
||||
'slide-right': `translateX(${type ? '0' : '100%'})`,
|
||||
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
|
||||
'slide-left': `translateX(${type ? '0' : '-100%'})`,
|
||||
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
|
||||
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
|
||||
}
|
||||
},
|
||||
// 内置动画类型与实际动画对应字典
|
||||
animationMode() {
|
||||
return {
|
||||
fade: 'opacity',
|
||||
'slide-top': 'translateY',
|
||||
'slide-right': 'translateX',
|
||||
'slide-bottom': 'translateY',
|
||||
'slide-left': 'translateX',
|
||||
'zoom-in': 'scale',
|
||||
'zoom-out': 'scale'
|
||||
}
|
||||
},
|
||||
// 驼峰转中横线
|
||||
toLine(name) {
|
||||
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -77,7 +77,7 @@
|
||||
this.videoVid = event.detail.value;
|
||||
},
|
||||
|
||||
setVid() {
|
||||
setVid(vid) {
|
||||
if(!this.videoVid) {
|
||||
uni.showToast({
|
||||
title: "请输入视频Vid",
|
||||
@@ -87,7 +87,7 @@
|
||||
}
|
||||
const { vodPlayer } = this;
|
||||
vodPlayer.setVid({
|
||||
vid:this.videoVid,
|
||||
vid:this.videoVid || vid,
|
||||
level:0
|
||||
}, (ret) => {
|
||||
this.text = JSON.stringify(ret);
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>生成占位图片</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.image-item {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.image-preview {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.download-btn {
|
||||
background: #007AFF;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.download-btn:hover {
|
||||
background: #0056CC;
|
||||
}
|
||||
.info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
color: #555;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>精品课页面占位图片生成器</h1>
|
||||
<p style="text-align: center; color: #666;">点击下载按钮保存图片到 /static/ 目录</p>
|
||||
|
||||
<!-- 主横幅 -->
|
||||
<div class="image-item">
|
||||
<h3>1. HIV横幅图片 (hiv_banner.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="hiv_banner" width="700" height="300"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('hiv_banner', 'hiv_banner.png')">下载</button>
|
||||
<div class="info">尺寸: 700x300px | 用途: 主横幅</div>
|
||||
</div>
|
||||
|
||||
<!-- 肝病图标 -->
|
||||
<div class="image-item">
|
||||
<h3>2. 肝病图标 (liver_icon.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="liver_icon" width="120" height="120"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('liver_icon', 'liver_icon.png')">下载</button>
|
||||
<div class="info">尺寸: 120x120px | 用途: 分类图标</div>
|
||||
</div>
|
||||
|
||||
<!-- 肿瘤图标 -->
|
||||
<div class="image-item">
|
||||
<h3>3. 肿瘤图标 (tumor_icon.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="tumor_icon" width="120" height="120"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('tumor_icon', 'tumor_icon.png')">下载</button>
|
||||
<div class="info">尺寸: 120x120px | 用途: 分类图标</div>
|
||||
</div>
|
||||
|
||||
<!-- 感染图标 -->
|
||||
<div class="image-item">
|
||||
<h3>4. 感染图标 (infection_icon.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="infection_icon" width="120" height="120"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('infection_icon', 'infection_icon.png')">下载</button>
|
||||
<div class="info">尺寸: 120x120px | 用途: 分类图标</div>
|
||||
</div>
|
||||
|
||||
<!-- 课程缩略图 -->
|
||||
<div class="image-item">
|
||||
<h3>5. 课程缩略图 (course_thumbnail.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="course_thumbnail" width="240" height="240"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('course_thumbnail', 'course_thumbnail.png')">下载</button>
|
||||
<div class="info">尺寸: 240x240px | 用途: 精品小课缩略图</div>
|
||||
</div>
|
||||
|
||||
<!-- 返现卡片1 -->
|
||||
<div class="image-item">
|
||||
<h3>6. 返现卡片1 (reward_card1.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="reward_card1" width="350" height="200"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('reward_card1', 'reward_card1.png')">下载</button>
|
||||
<div class="info">尺寸: 350x200px | 用途: 学克返现卡片</div>
|
||||
</div>
|
||||
|
||||
<!-- 返现卡片2 -->
|
||||
<div class="image-item">
|
||||
<h3>7. 返现卡片2 (reward_card2.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="reward_card2" width="350" height="200"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('reward_card2', 'reward_card2.png')">下载</button>
|
||||
<div class="info">尺寸: 350x200px | 用途: 学克返现卡片</div>
|
||||
</div>
|
||||
|
||||
<!-- 福利横幅 -->
|
||||
<div class="image-item">
|
||||
<h3>8. 福利横幅 (welfare_banner.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="welfare_banner" width="700" height="200"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('welfare_banner', 'welfare_banner.png')">下载</button>
|
||||
<div class="info">尺寸: 700x200px | 用途: 福利课堂横幅</div>
|
||||
</div>
|
||||
|
||||
<!-- HIV课程图 -->
|
||||
<div class="image-item">
|
||||
<h3>9. HIV课程图 (hiv_course.png)</h3>
|
||||
<div class="image-preview">
|
||||
<canvas id="hiv_course" width="240" height="240"></canvas>
|
||||
</div>
|
||||
<button class="download-btn" onclick="downloadImage('hiv_course', 'hiv_course.png')">下载</button>
|
||||
<div class="info">尺寸: 240x240px | 用途: 热门课程缩略图</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px; padding: 20px; background: #f8f9fa; border-radius: 8px;">
|
||||
<h3>使用说明:</h3>
|
||||
<ol>
|
||||
<li>点击每个图片下方的"下载"按钮</li>
|
||||
<li>将下载的图片保存到项目的 <code>/static/</code> 目录中</li>
|
||||
<li>确保文件名与页面代码中的引用名称一致</li>
|
||||
<li>重新运行uniapp项目即可看到占位图片效果</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 绘制图片函数
|
||||
function drawImage(canvasId, config) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 创建渐变背景
|
||||
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
|
||||
gradient.addColorStop(0, config.startColor);
|
||||
gradient.addColorStop(1, config.endColor);
|
||||
|
||||
// 填充背景
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 添加文字
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.font = `bold ${config.fontSize}px Arial, sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// 绘制主标题
|
||||
ctx.fillText(config.title, canvas.width / 2, canvas.height / 2 - 10);
|
||||
|
||||
// 绘制副标题(如果有)
|
||||
if (config.subtitle) {
|
||||
ctx.font = `${config.fontSize - 4}px Arial, sans-serif`;
|
||||
ctx.fillText(config.subtitle, canvas.width / 2, canvas.height / 2 + 20);
|
||||
}
|
||||
|
||||
// 添加图标/装饰元素
|
||||
if (config.icon) {
|
||||
ctx.font = `${config.fontSize + 10}px Arial, sans-serif`;
|
||||
ctx.fillText(config.icon, canvas.width / 2, canvas.height / 2 - 40);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载图片函数
|
||||
function downloadImage(canvasId, filename) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = canvas.toDataURL();
|
||||
link.click();
|
||||
}
|
||||
|
||||
// 页面加载完成后绘制所有图片
|
||||
window.onload = function() {
|
||||
// HIV横幅
|
||||
drawImage('hiv_banner', {
|
||||
startColor: '#64B5F6',
|
||||
endColor: '#2196F3',
|
||||
title: '小懂医生讲HIV和感染',
|
||||
subtitle: '专业医学知识分享',
|
||||
fontSize: 24,
|
||||
icon: '🏥'
|
||||
});
|
||||
|
||||
// 肝病图标
|
||||
drawImage('liver_icon', {
|
||||
startColor: '#FF8A65',
|
||||
endColor: '#FF5722',
|
||||
title: '肝病',
|
||||
fontSize: 16,
|
||||
icon: '🫀'
|
||||
});
|
||||
|
||||
// 肿瘤图标
|
||||
drawImage('tumor_icon', {
|
||||
startColor: '#FFD54F',
|
||||
endColor: '#FF9800',
|
||||
title: '肿瘤',
|
||||
fontSize: 16,
|
||||
icon: '🎯'
|
||||
});
|
||||
|
||||
// 感染图标
|
||||
drawImage('infection_icon', {
|
||||
startColor: '#64B5F6',
|
||||
endColor: '#2196F3',
|
||||
title: '感染',
|
||||
fontSize: 16,
|
||||
icon: '🦠'
|
||||
});
|
||||
|
||||
// 课程缩略图
|
||||
drawImage('course_thumbnail', {
|
||||
startColor: '#E1F5FE',
|
||||
endColor: '#81D4FA',
|
||||
title: '肝脏胖瘦临床',
|
||||
subtitle: '影像学习交流',
|
||||
fontSize: 16,
|
||||
icon: '📚'
|
||||
});
|
||||
|
||||
// 返现卡片1
|
||||
drawImage('reward_card1', {
|
||||
startColor: '#E8F5E8',
|
||||
endColor: '#4CAF50',
|
||||
title: '乙肝临床治疗肝肝',
|
||||
subtitle: '肝炎及关并发作用',
|
||||
fontSize: 14
|
||||
});
|
||||
|
||||
// 返现卡片2
|
||||
drawImage('reward_card2', {
|
||||
startColor: '#FFF3E0',
|
||||
endColor: '#FF9800',
|
||||
title: '胆汁淤积性诊断与制',
|
||||
subtitle: '定及临床手段',
|
||||
fontSize: 14
|
||||
});
|
||||
|
||||
// 福利横幅
|
||||
drawImage('welfare_banner', {
|
||||
startColor: '#F3E5F5',
|
||||
endColor: '#9C27B0',
|
||||
title: '福利课堂 - 免费学习医学知识',
|
||||
fontSize: 20,
|
||||
icon: '🎁'
|
||||
});
|
||||
|
||||
// HIV课程图
|
||||
drawImage('hiv_course', {
|
||||
startColor: '#E3F2FD',
|
||||
endColor: '#1976D2',
|
||||
title: 'HIV感染',
|
||||
subtitle: '抗菌药特性',
|
||||
fontSize: 16,
|
||||
icon: '💊'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
|
||||
*/
|
||||
|
||||
var isIos
|
||||
// #ifdef APP-PLUS
|
||||
isIos = (plus.os.name == "iOS")
|
||||
// #endif
|
||||
|
||||
// 判断推送权限是否开启
|
||||
function judgeIosPermissionPush() {
|
||||
var result = false;
|
||||
var UIApplication = plus.ios.import("UIApplication");
|
||||
var app = UIApplication.sharedApplication();
|
||||
var enabledTypes = 0;
|
||||
if (app.currentUserNotificationSettings) {
|
||||
var settings = app.currentUserNotificationSettings();
|
||||
enabledTypes = settings.plusGetAttribute("types");
|
||||
console.log("enabledTypes1:" + enabledTypes);
|
||||
if (enabledTypes == 0) {
|
||||
console.log("推送权限没有开启");
|
||||
} else {
|
||||
result = true;
|
||||
console.log("已经开启推送功能!")
|
||||
}
|
||||
plus.ios.deleteObject(settings);
|
||||
} else {
|
||||
enabledTypes = app.enabledRemoteNotificationTypes();
|
||||
if (enabledTypes == 0) {
|
||||
console.log("推送权限没有开启!");
|
||||
} else {
|
||||
result = true;
|
||||
console.log("已经开启推送功能!")
|
||||
}
|
||||
console.log("enabledTypes2:" + enabledTypes);
|
||||
}
|
||||
plus.ios.deleteObject(app);
|
||||
plus.ios.deleteObject(UIApplication);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断定位权限是否开启
|
||||
function judgeIosPermissionLocation() {
|
||||
var result = false;
|
||||
var cllocationManger = plus.ios.import("CLLocationManager");
|
||||
var status = cllocationManger.authorizationStatus();
|
||||
result = (status != 2)
|
||||
console.log("定位权限开启:" + result);
|
||||
// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
|
||||
/* var enable = cllocationManger.locationServicesEnabled();
|
||||
var status = cllocationManger.authorizationStatus();
|
||||
console.log("enable:" + enable);
|
||||
console.log("status:" + status);
|
||||
if (enable && status != 2) {
|
||||
result = true;
|
||||
console.log("手机定位服务已开启且已授予定位权限");
|
||||
} else {
|
||||
console.log("手机系统的定位没有打开或未给予定位权限");
|
||||
} */
|
||||
plus.ios.deleteObject(cllocationManger);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断麦克风权限是否开启
|
||||
function judgeIosPermissionRecord() {
|
||||
var result = false;
|
||||
var avaudiosession = plus.ios.import("AVAudioSession");
|
||||
var avaudio = avaudiosession.sharedInstance();
|
||||
var permissionStatus = avaudio.recordPermission();
|
||||
console.log("permissionStatus:" + permissionStatus);
|
||||
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
|
||||
console.log("麦克风权限没有开启");
|
||||
} else {
|
||||
result = true;
|
||||
console.log("麦克风权限已经开启");
|
||||
}
|
||||
plus.ios.deleteObject(avaudiosession);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断相机权限是否开启
|
||||
function judgeIosPermissionCamera() {
|
||||
var result = false;
|
||||
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
|
||||
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
|
||||
console.log("authStatus:" + authStatus);
|
||||
if (authStatus == 3) {
|
||||
result = true;
|
||||
console.log("相机权限已经开启");
|
||||
} else {
|
||||
console.log("相机权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(AVCaptureDevice);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断相册权限是否开启
|
||||
function judgeIosPermissionPhotoLibrary() {
|
||||
var result = false;
|
||||
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
|
||||
var authStatus = PHPhotoLibrary.authorizationStatus();
|
||||
console.log("authStatus:" + authStatus);
|
||||
if (authStatus == 3) {
|
||||
result = true;
|
||||
console.log("相册权限已经开启");
|
||||
} else {
|
||||
console.log("相册权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(PHPhotoLibrary);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断通讯录权限是否开启
|
||||
function judgeIosPermissionContact() {
|
||||
var result = false;
|
||||
var CNContactStore = plus.ios.import("CNContactStore");
|
||||
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
|
||||
if (cnAuthStatus == 3) {
|
||||
result = true;
|
||||
console.log("通讯录权限已经开启");
|
||||
} else {
|
||||
console.log("通讯录权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(CNContactStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断日历权限是否开启
|
||||
function judgeIosPermissionCalendar() {
|
||||
var result = false;
|
||||
var EKEventStore = plus.ios.import("EKEventStore");
|
||||
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
|
||||
if (ekAuthStatus == 3) {
|
||||
result = true;
|
||||
console.log("日历权限已经开启");
|
||||
} else {
|
||||
console.log("日历权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(EKEventStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断备忘录权限是否开启
|
||||
function judgeIosPermissionMemo() {
|
||||
var result = false;
|
||||
var EKEventStore = plus.ios.import("EKEventStore");
|
||||
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
|
||||
if (ekAuthStatus == 3) {
|
||||
result = true;
|
||||
console.log("备忘录权限已经开启");
|
||||
} else {
|
||||
console.log("备忘录权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(EKEventStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Android权限查询
|
||||
function requestAndroidPermission(permissionID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
plus.android.requestPermissions(
|
||||
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
|
||||
function(resultObj) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < resultObj.granted.length; i++) {
|
||||
var grantedPermission = resultObj.granted[i];
|
||||
console.log('已获取的权限:' + grantedPermission);
|
||||
result = 1
|
||||
}
|
||||
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
|
||||
var deniedPresentPermission = resultObj.deniedPresent[i];
|
||||
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
|
||||
result = 0
|
||||
}
|
||||
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
|
||||
var deniedAlwaysPermission = resultObj.deniedAlways[i];
|
||||
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
|
||||
result = -1
|
||||
}
|
||||
resolve(result);
|
||||
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
|
||||
// if (result != 1) {
|
||||
// gotoAppPermissionSetting()
|
||||
// }
|
||||
},
|
||||
function(error) {
|
||||
console.log('申请权限错误:' + error.code + " = " + error.message);
|
||||
resolve({
|
||||
code: error.code,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 使用一个方法,根据参数判断权限
|
||||
function judgeIosPermission(permissionID) {
|
||||
if (permissionID == "location") {
|
||||
return judgeIosPermissionLocation()
|
||||
} else if (permissionID == "camera") {
|
||||
return judgeIosPermissionCamera()
|
||||
} else if (permissionID == "photoLibrary") {
|
||||
return judgeIosPermissionPhotoLibrary()
|
||||
} else if (permissionID == "record") {
|
||||
return judgeIosPermissionRecord()
|
||||
} else if (permissionID == "push") {
|
||||
return judgeIosPermissionPush()
|
||||
} else if (permissionID == "contact") {
|
||||
return judgeIosPermissionContact()
|
||||
} else if (permissionID == "calendar") {
|
||||
return judgeIosPermissionCalendar()
|
||||
} else if (permissionID == "memo") {
|
||||
return judgeIosPermissionMemo()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 跳转到**应用**的权限页面
|
||||
function gotoAppPermissionSetting() {
|
||||
if (isIos) {
|
||||
var UIApplication = plus.ios.import("UIApplication");
|
||||
var application2 = UIApplication.sharedApplication();
|
||||
var NSURL2 = plus.ios.import("NSURL");
|
||||
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
|
||||
var setting2 = NSURL2.URLWithString("app-settings:");
|
||||
application2.openURL(setting2);
|
||||
|
||||
plus.ios.deleteObject(setting2);
|
||||
plus.ios.deleteObject(NSURL2);
|
||||
plus.ios.deleteObject(application2);
|
||||
} else {
|
||||
// console.log(plus.device.vendor);
|
||||
var Intent = plus.android.importClass("android.content.Intent");
|
||||
var Settings = plus.android.importClass("android.provider.Settings");
|
||||
var Uri = plus.android.importClass("android.net.Uri");
|
||||
var mainActivity = plus.android.runtimeMainActivity();
|
||||
var intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
mainActivity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查系统的设备服务是否开启
|
||||
// var checkSystemEnableLocation = async function () {
|
||||
function checkSystemEnableLocation() {
|
||||
if (isIos) {
|
||||
var result = false;
|
||||
var cllocationManger = plus.ios.import("CLLocationManager");
|
||||
var result = cllocationManger.locationServicesEnabled();
|
||||
console.log("系统定位开启:" + result);
|
||||
plus.ios.deleteObject(cllocationManger);
|
||||
return result;
|
||||
} else {
|
||||
var context = plus.android.importClass("android.content.Context");
|
||||
var locationManager = plus.android.importClass("android.location.LocationManager");
|
||||
var main = plus.android.runtimeMainActivity();
|
||||
var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
|
||||
var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
|
||||
console.log("系统定位开启:" + result);
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = {
|
||||
// judgeIosPermission: judgeIosPermission,
|
||||
// requestAndroidPermission: requestAndroidPermission,
|
||||
// checkSystemEnableLocation: checkSystemEnableLocation,
|
||||
// gotoAppPermissionSetting: gotoAppPermissionSetting
|
||||
// }
|
||||
export default {judgeIosPermission,requestAndroidPermission,checkSystemEnableLocation,gotoAppPermissionSetting}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"messageText": "Message",
|
||||
"cantactsText": "Cantact",
|
||||
"mineText": "Me"
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
// en.js
|
||||
export default {
|
||||
appText: 'NIM',
|
||||
messageText: 'Message',
|
||||
contactText: 'Contacts',
|
||||
mineText: 'Me',
|
||||
logoutText: 'Log out',
|
||||
commsEaseText: 'About NIM',
|
||||
logoutConfirmText: 'Are you sure you want to log out?',
|
||||
avatarText: 'Avatar',
|
||||
accountText: 'Account',
|
||||
name: 'Nickname',
|
||||
genderText: 'Gender',
|
||||
remark: 'remark',
|
||||
mobile: 'Phone',
|
||||
email: 'Email',
|
||||
sign: 'Signature',
|
||||
man: 'Male',
|
||||
woman: 'Female',
|
||||
unknow: 'Unknown',
|
||||
birthText: 'Birthday',
|
||||
userOnlineText: 'Online',
|
||||
userOfflineText: 'Offline',
|
||||
updateAvatarText: 'Update Avatar',
|
||||
updateNameText: 'Update Name',
|
||||
|
||||
FriendPageText: 'Friend Card',
|
||||
startConversationText: 'Start Chat',
|
||||
addText: 'Add',
|
||||
applyFriendSuccessText: 'Request Sent',
|
||||
applyFriendFailText: 'Request Failed',
|
||||
addFriendText: 'Add Friend',
|
||||
addFriendFailText: 'Add Friend Failed',
|
||||
deleteFriendText: 'Delete Friend',
|
||||
deleteFriendConfirmText: 'Are you sure you want to delete this friend?',
|
||||
deleteFriendSuccessText: 'Delete Friend Success',
|
||||
deleteFriendFailText: 'Delete Friend Failed',
|
||||
setBlackFailText: 'Add to blacklist failed',
|
||||
removeBlackFailText: 'Remove from blacklist failed',
|
||||
removeBlackSuccessText: 'Remove from blacklist success',
|
||||
addPermissionText:
|
||||
'Please add relevant permissions in the system settings page',
|
||||
FailAvatarText: 'Avatar upload failed',
|
||||
chooseDefaultImage: 'Choose default image',
|
||||
chatButtonText: 'Go to Chat',
|
||||
gotoChatFailText: 'Go to chat failed',
|
||||
chatWithFriendText: 'Chat',
|
||||
okText: 'OK',
|
||||
updateText: 'Update',
|
||||
cancelText: 'Cancel',
|
||||
friendSelectText: 'Select Friend',
|
||||
telErrorText: 'Invalid phone number format',
|
||||
emailErrorText: 'Invalid email format',
|
||||
createButtonText: 'Create',
|
||||
createP2PText: 'Start Chat',
|
||||
createTeamText: 'Create Group',
|
||||
addTeamMemberText: 'Add Group Member',
|
||||
discussionMemberText: '讨论组成员',
|
||||
addTeamMemberFailText: 'Add Group Member Failed',
|
||||
teamTitlePlaceholder: 'Enter Group Name',
|
||||
maxSelectedText: 'You can only select up to 200 friends',
|
||||
createTeamSuccessText: 'Create Group Success',
|
||||
createTeamFailedText: 'Create Group Failed',
|
||||
PersonalPageText: 'PersonalCard',
|
||||
deleteSessionText: 'Delete',
|
||||
sessionMuteText: 'Mute Notifications',
|
||||
sessionMuteFailText: 'Mute Notifications Failed',
|
||||
sessionUnMuteFailText: 'Unmute Notifications Failed',
|
||||
stickTopText: 'Pin Chat',
|
||||
deleteStickTopText: 'Unpin Chat',
|
||||
addStickTopText: 'Pin',
|
||||
deleteSessionFailText: 'Delete Conversation Failed',
|
||||
deleteStickTopFailText: 'Unpin Chat Failed',
|
||||
addStickTopFailText: 'Pin Message Failed',
|
||||
teamMemberText: 'Group Members',
|
||||
saveText: 'Save',
|
||||
setText: 'Settings',
|
||||
failText: 'Failed',
|
||||
saveSuccessText: 'Save Success',
|
||||
saveFailedText: 'Save Failed',
|
||||
searchTeamPlaceholder: 'Enter Group Name',
|
||||
teamTitle: 'Group Name',
|
||||
discussionTitle: 'Discussion Group Name',
|
||||
teamInfoText: 'Group Information',
|
||||
discussionInfoText: 'Discussion Group Information',
|
||||
teamAvatar: 'Group Avatar',
|
||||
discussionAvatarText: 'Discussion Group Avatar',
|
||||
networkError: 'Current network error',
|
||||
searchFailText: 'Search Failed',
|
||||
offlineText: 'Network is unavailable, please check network settings.',
|
||||
connectingText:
|
||||
'The current IM connection has been disconnected and is reconnecting…',
|
||||
teamMenuText: 'My Groups',
|
||||
teamChooseText: 'My Group Chats',
|
||||
chooseText: 'Choose',
|
||||
friendSelect: 'Please select a contact',
|
||||
getHistoryMsgFailedText: 'Failed to get message history',
|
||||
deleteText: 'Delete',
|
||||
recallText: 'Recall',
|
||||
copyText: 'Copy',
|
||||
forwardText: 'Forward',
|
||||
forwardComment: 'forward comment',
|
||||
replyText: 'Reply',
|
||||
replyNotFindText: 'Message has recalled or deleted',
|
||||
forwardToTeamText: 'Forward to Group',
|
||||
forwardToFriendText: 'Forward to Contact',
|
||||
sessionRecordText: 'Conversation Record',
|
||||
chatInputPlaceHolder: 'What would you like to say?',
|
||||
enableV2CloudConversationText: 'Enable V2 Cloud Conversation',
|
||||
SwitchToEnglishText: 'Switch to English',
|
||||
pinText: 'Mark',
|
||||
pinFailedText: 'Mark Failed',
|
||||
unpinText: 'Unmark',
|
||||
unpinFailedText: 'Unmark Failed',
|
||||
noPinListText: 'No Mark',
|
||||
collectionText: 'Collection',
|
||||
deleteCollectionText: 'Delete collection',
|
||||
addCollectionSuccessText: 'Add collection success',
|
||||
addCollectionFailedText: 'Add collection Failed',
|
||||
deleteCollectionFailedText: 'Delete collection Failed',
|
||||
noCollectionsText: 'No collection',
|
||||
teamBannedText: 'Message Banned',
|
||||
you: 'You',
|
||||
pinThisText: 'Marked this message',
|
||||
delete: 'Delete this message',
|
||||
recall: 'Recalled a message',
|
||||
recall2: 'This message has been recalled',
|
||||
recall3: 'Recall this message',
|
||||
recallMsgFailText: 'Recall Message Failed',
|
||||
reeditText: 'Edit',
|
||||
loadingMoreText: 'Loading…',
|
||||
noMoreText: 'No more',
|
||||
deleteMsgSuccessText: 'Delete Success',
|
||||
deleteMsgFailText: 'Delete Failed',
|
||||
conversationEmptyText: 'No conversation',
|
||||
teamOwner: 'Group Owner',
|
||||
teamManager: 'Group Manager',
|
||||
teamEmptyText: 'NoGroup Chats',
|
||||
blacklistEmptyText: 'No Blocked Contacts',
|
||||
blacklistSubTitle: 'You will not get messages from anyone on the list. ',
|
||||
validEmptyText: 'No Verification Messages',
|
||||
dismissTeamText: 'Dismiss Group',
|
||||
dismissTeamConfirmText: 'Are you sure you want to dismiss this group?',
|
||||
leaveTeamTitle: 'Leave Group',
|
||||
leaveDiscussionTitle: 'Leave Discussion Group',
|
||||
leaveTeamConfirmText: 'Are you sure you want to leave this group?',
|
||||
leaveTeamSuccessText: 'You have left this group',
|
||||
leaveTeamFailedText: 'Failed to leave this group',
|
||||
leaveDiscussionSuccessText: 'Successfully left the discussion group',
|
||||
leaveDiscussionFailedText: 'Failed to leave the discussion group',
|
||||
dismissTeamSuccessText: 'Dismiss Group Success',
|
||||
dismissTeamFailedText: 'Dismiss Group Failed',
|
||||
updateTeamSuccessText: 'Update Success',
|
||||
updateTeamFailedText: 'Update Failed',
|
||||
teamTitleConfirmText: 'Group name cannot be empty',
|
||||
teamIntroConfirmText: 'Group introduction cannot be empty',
|
||||
aliasConfirmText: 'Alias cannot be empty',
|
||||
sendImageFailedText: 'Failed to send Image',
|
||||
sendVideoFailedText: 'Failed to send Video',
|
||||
sendAudioFailedText: 'Failed to send Audio',
|
||||
sendFileFailedText: 'Failed to send File',
|
||||
sendMsgFailedText: 'Failed to send Message',
|
||||
weekText: 'Weeks ago',
|
||||
dayText: 'Days ago',
|
||||
hourText: 'Hours ago',
|
||||
minuteText: 'Minutes ago',
|
||||
nowText: 'Just now',
|
||||
sendText: 'Send',
|
||||
sendToText: 'Send to',
|
||||
textMsgText: 'Text Message',
|
||||
audioMsgText: 'Audio Message',
|
||||
videoMsgText: 'Video Message',
|
||||
fileMsgText: 'File Message',
|
||||
callMsgText: 'Call Message',
|
||||
geoMsgText: 'Location Message',
|
||||
imgMsgText: 'Image Message',
|
||||
notiMsgText: 'Notification Message',
|
||||
robotMsgText: 'Robot Message',
|
||||
tipMsgText: 'Tip Message',
|
||||
customMsgText: 'Custom Message',
|
||||
unknowMsgText: 'Unknown Message',
|
||||
noFriendText: 'No Friends',
|
||||
onDismissTeamText: 'This group has been dismissed',
|
||||
onRemoveTeamText: 'You have left the group',
|
||||
selectSessionFailText: 'Failed to select conversation',
|
||||
noExistUser: 'This user does not exist',
|
||||
enterAccount: 'Enter account',
|
||||
validMsgText: 'Verification Message',
|
||||
blacklistText: 'Blocked Contacts',
|
||||
applyTeamText: 'Apply to Join Group',
|
||||
acceptResultText: 'Accepted',
|
||||
rejectResultText: 'Rejected',
|
||||
rejectTeamInviteText: 'Rejected group invitation',
|
||||
beRejectResultText: 'Rejected friend request',
|
||||
passResultText: 'Accepted friend request',
|
||||
acceptedText: 'This request has been accepted',
|
||||
acceptFailedText: 'Failed to accept this request',
|
||||
rejectedText: 'This request has been rejected',
|
||||
rejectFailedText: 'Failed to reject this request',
|
||||
passFriendAskText:
|
||||
"I have accepted your friend request, let's start chatting!",
|
||||
applyFriendText: 'Add you as a friend',
|
||||
rejectText: 'Reject',
|
||||
acceptText: 'Accept',
|
||||
inviteTeamText: 'Invite to Group',
|
||||
addBlacklist: 'Block',
|
||||
removeBlacklist: 'Unblock',
|
||||
forwardSuccessText: 'Forward Success',
|
||||
forwardFailText: 'Forward Failed',
|
||||
getMessageFailed: 'Fail to get message',
|
||||
getForwardMessageFailed: 'Fail to get forward message',
|
||||
sendFailWithInBlackText:
|
||||
'The other party has blocked you, message sending failed',
|
||||
sendFailWithDeleteText:
|
||||
'The relationship between both parties has been terminated, please reapply for contact',
|
||||
friendVerificationText: 'Friend Verification',
|
||||
teamAll: 'all',
|
||||
chooseMentionText: 'Choose Mention',
|
||||
someoneText: 'someone',
|
||||
meText: 'mention',
|
||||
teamMutePlaceholder: 'The current group owner has set the group to mute',
|
||||
viewMoreText: 'View More',
|
||||
resendMsgFailText: 'Resend Failed',
|
||||
audioBtnText: 'Press and hold to speak',
|
||||
audioRemindText: 'Release to send',
|
||||
audioErrorText: 'Recording failed',
|
||||
videoPlayText: 'Video Play',
|
||||
voiceCallText: 'Voice Call',
|
||||
videoCallText: 'Video Call',
|
||||
callDurationText: 'call duration',
|
||||
callCancelText: 'canceled',
|
||||
callRejectedText: 'rejected',
|
||||
callTimeoutText: 'Missed call',
|
||||
callBusyText: 'busy line',
|
||||
callFailedText: 'call failed',
|
||||
wxAppFileCopyText: 'Please paste the URL in the mobile browser.',
|
||||
shootText: 'shoot',
|
||||
albumText: 'album',
|
||||
msgReadPageTitleText: 'Message Reading Status',
|
||||
readText: 'Read',
|
||||
unreadText: 'Unread',
|
||||
fileText: 'file',
|
||||
allUnReadText: 'All Unread',
|
||||
msgRecallTimeErrorText: 'Message recall time exceeded',
|
||||
allReadText: 'All Read',
|
||||
searchText: 'Search',
|
||||
searchTitleText: 'Search',
|
||||
friendText: 'Friend',
|
||||
teamText: 'Group',
|
||||
discussionText: 'Discussion Group',
|
||||
searchResultNullText: 'User does not exist',
|
||||
conversationSendFailText: '[Send Failed]',
|
||||
conversationNotificationText: '[Notification]',
|
||||
fileMsgTitleText: '[File Message]',
|
||||
collectionFromText: 'Collection from',
|
||||
copySuccessText: 'Copy Success',
|
||||
openUrlText: 'Copied. Open in browser.',
|
||||
teamManagerText: 'Team Manager',
|
||||
teamManagerSettingText: 'Team Manager Setting',
|
||||
updateTeamInfoText: 'Who is allowed to edit group details',
|
||||
teamOwnerAndManagerText: 'TeamOwnerAndManager',
|
||||
updateTeamInviteText: 'Who is allowed to invite group members',
|
||||
updateTeamAtText: 'Who is allowed to @',
|
||||
addMemberText: 'Add Member',
|
||||
removeText: 'remove',
|
||||
|
||||
securityTipText:
|
||||
'For test only. Beware of money transfer, lottery winnings & strange call scams.',
|
||||
createDiscussionText: 'Create Discussion',
|
||||
|
||||
// emoji 不能随便填,要用固定 key
|
||||
Laugh: '[Laugh]',
|
||||
Happy: '[Happy]',
|
||||
Sexy: '[Sexy]',
|
||||
Cool: '[Cool]',
|
||||
Mischievous: '[Mischievous]',
|
||||
Kiss: '[Kiss]',
|
||||
Spit: '[Spit]',
|
||||
Squint: '[Squint]',
|
||||
Cute: '[Cute]',
|
||||
Grimace: '[Grimace]',
|
||||
Snicker: '[Snicker]',
|
||||
Joy: '[Joy]',
|
||||
Ecstasy: '[Ecstasy]',
|
||||
Surprise: '[Surprise]',
|
||||
Tears: '[Tears]',
|
||||
Sweat: '[Sweat]',
|
||||
Angle: '[Angle]',
|
||||
Funny: '[Funny]',
|
||||
Awkward: '[Awkward]',
|
||||
Thrill: '[Thrill]',
|
||||
Cry: '[Cry]',
|
||||
Fretting: '[Fretting]',
|
||||
Terrorist: '[Terrorist]',
|
||||
Halo: '[Halo]',
|
||||
Shame: '[Shame]',
|
||||
Sleep: '[Sleep]',
|
||||
Tired: '[Tired]',
|
||||
Mask: '[Mask]',
|
||||
ok: '[ok]',
|
||||
AllRight: '[All right]',
|
||||
Despise: '[Despise]',
|
||||
Uncomfortable: '[Uncomfortable]',
|
||||
Disdain: '[Disdain]',
|
||||
ill: '[ill]',
|
||||
Mad: '[Mad]',
|
||||
Ghost: '[Ghost]',
|
||||
Angry: '[Angry]',
|
||||
Unhappy: '[Unhappy]',
|
||||
Frown: '[Frown]',
|
||||
Broken: '[Broken]',
|
||||
Beckoning: '[Beckoning]',
|
||||
Ok: '[Ok]',
|
||||
Low: '[Low]',
|
||||
Nice: '[Nice]',
|
||||
Applause: '[Applause]',
|
||||
GoodJob: '[Good job]',
|
||||
Hit: '[Hit]',
|
||||
Please: '[Please]',
|
||||
Bye: '[Bye]',
|
||||
First: '[First]',
|
||||
Fist: '[Fist]',
|
||||
GiveMeFive: '[Give me five]',
|
||||
Knife: '[Knife]',
|
||||
Hi: '[Hi]',
|
||||
No: '[No]',
|
||||
Hold: '[Hold]',
|
||||
Think: '[Think]',
|
||||
Pig: '[Pig]',
|
||||
NoListen: '[No listen]',
|
||||
NoLook: '[No look]',
|
||||
NoWords: '[No words]',
|
||||
Monkey: '[Monkey]',
|
||||
Bomb: '[Bomb]',
|
||||
Cloud: '[Cloud]',
|
||||
Rocket: '[Rocket]',
|
||||
Ambulance: '[Ambulance]',
|
||||
Poop: '[Poop]',
|
||||
// 错误码及提示内容
|
||||
// 通用错误码
|
||||
190001: 'internal error',
|
||||
190002: 'illegal state',
|
||||
191001: 'misuse',
|
||||
191002: 'cancelled',
|
||||
191003: 'callback failed',
|
||||
191004: 'invalid parameter',
|
||||
191005: 'timeout',
|
||||
191006: 'resource not exist',
|
||||
191007: 'resource already exist',
|
||||
// 连接相关
|
||||
192001: 'connect failed',
|
||||
192002: 'connect timeout',
|
||||
192003: 'disconnected',
|
||||
192004: 'protocol timeout',
|
||||
192005: 'protocol send failed',
|
||||
192006: 'request failed',
|
||||
// 消息相关
|
||||
107319: 'PIN limit exceeded',
|
||||
107320: 'PIN not exist',
|
||||
107322: 'PIN already exist',
|
||||
107327: 'PIN function disabled',
|
||||
189301: 'collection limit exceeded',
|
||||
189302: 'collection not exist',
|
||||
189449: 'collection concurrent operation failed',
|
||||
// 群相关
|
||||
109432: 'no permission',
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"messageText": "消息",
|
||||
"cantactsText": "通讯录",
|
||||
"mineText": "我的"
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// zn.js
|
||||
export default {
|
||||
appText: '云信IM',
|
||||
messageText: '消息',
|
||||
contactText: '通讯录',
|
||||
mineText: '我的',
|
||||
logoutText: '退出登录',
|
||||
commsEaseText: '关于云信',
|
||||
logoutConfirmText: '确定退出登录吗?',
|
||||
avatarText: '头像',
|
||||
updateAvatarText: '修改头像',
|
||||
updateNameText: '修改名称',
|
||||
accountText: '账号',
|
||||
name: '昵称',
|
||||
genderText: '性别',
|
||||
remarkText: '备注名',
|
||||
mobile: '手机',
|
||||
email: '邮箱',
|
||||
sign: '签名',
|
||||
man: '男',
|
||||
woman: '女',
|
||||
unknow: '未知',
|
||||
birthText: '生日',
|
||||
userOnlineText: '在线',
|
||||
userOfflineText: '离线',
|
||||
FriendPageText: '好友名片',
|
||||
startConversationText: '发起单聊',
|
||||
addText: '添加',
|
||||
removeText: '移除',
|
||||
addTeamManagerSuccessText: '添加管理员成功',
|
||||
addTeamManagerFailText: '添加管理员失败',
|
||||
confirmRemoveText: '是否移除?',
|
||||
removeManagerExplain: '移除后该成员将无管理权限',
|
||||
removeMemberExplain: '将移除该成员',
|
||||
removeSuccessText: '移除成功',
|
||||
removeFailText: '移除失败',
|
||||
userNotInTeam: '该用户不在群里',
|
||||
noTeamManager: '暂无群管理员',
|
||||
noTeamMember: '暂无群成员',
|
||||
applyFriendSuccessText: '好友申请已发送',
|
||||
applyFriendFailText: '发送申请失败',
|
||||
addFriendText: '添加好友',
|
||||
addMemberText: '添加成员',
|
||||
addFriendFailText: '添加好友失败',
|
||||
deleteFriendText: '删除好友',
|
||||
deleteFriendConfirmText: '是否确认删除好友',
|
||||
deleteFriendSuccessText: '删除好友成功',
|
||||
deleteFriendFailText: '删除好友失败',
|
||||
setBlackFailText: '加入黑名单失败',
|
||||
removeBlackFailText: '解除黑名单失败',
|
||||
removeBlackSuccessText: '解除黑名单成功',
|
||||
addPermissionText: '请在系统设置页添加相关权限',
|
||||
FailAvatarText: '上传头像失败',
|
||||
chooseDefaultImage: '选择默认图标',
|
||||
chatButtonText: '去聊天',
|
||||
gotoChatFailText: '去聊天失败',
|
||||
chatWithFriendText: '聊天',
|
||||
okText: '确定',
|
||||
updateText: '修改',
|
||||
cancelText: '取消',
|
||||
friendSelectText: '选择好友',
|
||||
telErrorText: '手机格式不正确',
|
||||
emailErrorText: '邮箱格式不正确',
|
||||
createButtonText: '创建',
|
||||
createP2PText: '发起单聊',
|
||||
createTeamText: '创建群聊',
|
||||
addTeamMemberText: '添加群成员',
|
||||
addTeamMemberFailText: '添加群成员失败',
|
||||
teamTitlePlaceholder: '请输入群名称',
|
||||
maxSelectedText: '最多只能选择200位好友',
|
||||
createTeamSuccessText: '创建群组成功',
|
||||
createDiscussionSuccessText: '创建讨论组成功',
|
||||
createTeamFailedText: '创建群组失败',
|
||||
createDiscussionFailedText: '创建讨论组失败',
|
||||
PersonalPageText: '个人名片',
|
||||
deleteSessionText: '删除会话',
|
||||
sessionMuteText: '开启消息提醒',
|
||||
sessionMuteFailText: '开启消息提醒失败',
|
||||
sessionUnMuteFailText: '关闭消息提醒失败',
|
||||
stickTopText: '聊天置顶',
|
||||
deleteStickTopText: '取消置顶',
|
||||
addStickTopText: '置顶消息',
|
||||
deleteSessionFailText: '删除会话失败',
|
||||
deleteStickTopFailText: '取消置顶失败',
|
||||
addStickTopFailText: '置顶消息失败',
|
||||
teamMemberText: '群成员',
|
||||
discussionMemberText: '讨论组成员',
|
||||
teamManagerText: '群管理',
|
||||
teamManagerSettingText: '管理管理员',
|
||||
updateTeamInfoText: '谁可以编辑群信息',
|
||||
updateTeamInviteText: '谁可以邀请新成员',
|
||||
updateTeamAtText: '谁可以@所有人',
|
||||
transformTeam: '转让群主',
|
||||
transformTeamContent: '确认转让群主给',
|
||||
doubt: '吗?',
|
||||
transformTeamSuccessText: '转让群主成功',
|
||||
transformTeamFailedText: '转让群主失败',
|
||||
transformTeamText: '选择转让成员',
|
||||
teamBannedText: '群禁言',
|
||||
saveText: '保存',
|
||||
setText: '设置',
|
||||
failText: '失败',
|
||||
saveSuccessText: '保存成功',
|
||||
saveFailedText: '保存失败',
|
||||
searchTeamPlaceholder: '请输入群名称',
|
||||
nickInTeamEmptyText: '请输入群昵称',
|
||||
teamTitle: '群名称',
|
||||
discussionTitle: '讨论组名称',
|
||||
teamInfoText: '群信息',
|
||||
discussionInfoText: '讨论组信息',
|
||||
teamIntro: '群介绍',
|
||||
teamAvatar: '群头像',
|
||||
discussionAvatarText: '讨论组头像',
|
||||
networkError: '当前网络错误',
|
||||
searchFailText: '查找失败',
|
||||
offlineText: '当前网络不可用,请检查你当前网络设置。',
|
||||
connectingText: '当前IM连接已断开,正在重连...',
|
||||
teamMenuText: '我的群组',
|
||||
teamChooseText: '我的群聊',
|
||||
chooseText: '选择',
|
||||
friendSelect: '请选择联系人',
|
||||
pleaseSelectMember: '请选择成员',
|
||||
teamMemebrSelect: '人员选择',
|
||||
getHistoryMsgFailedText: '获取失败',
|
||||
deleteText: '删除',
|
||||
recallText: '撤回',
|
||||
copyText: '复制',
|
||||
forwardText: '转发',
|
||||
forwardComment: '留言',
|
||||
forwardSuccessText: '转发成功',
|
||||
forwardFailedText: '转发失败',
|
||||
getMessageFailed: '获取消息失败',
|
||||
replyText: '回复',
|
||||
replyNotFindText: '该消息已撤回或删除',
|
||||
msgRecallTimeErrorText: '已超过时间无法撤回',
|
||||
enableV2CloudConversationText: '是否开启云端会话',
|
||||
SwitchToEnglish: '切换语言为英文',
|
||||
copySuccessText: '复制成功',
|
||||
|
||||
forwardToTeamText: '转发到群组',
|
||||
forwardToFriendText: '转发到个人',
|
||||
sessionRecordText: '的会话记录',
|
||||
chatInputPlaceHolder: '说点什么呢~',
|
||||
pinText: '标记',
|
||||
pinFailedText: '标记失败',
|
||||
unpinText: '取消标记',
|
||||
unpinFailedText: '取消标记失败',
|
||||
noPinListText: '暂无标记消息',
|
||||
collectionText: '收藏',
|
||||
deleteCollectionText: '删除收藏',
|
||||
addCollectionSuccessText: '收藏成功',
|
||||
addCollectionFailedText: '添加收藏失败',
|
||||
deleteCollectionFailedText: '删除收藏失败',
|
||||
noCollectionsText: '暂无收藏',
|
||||
you: '你',
|
||||
pinThisText: '标记了这条消息',
|
||||
delete: '删除此消息',
|
||||
recall: '撤回了一条消息',
|
||||
recall2: '此消息已撤回',
|
||||
recall3: '撤回此消息',
|
||||
recallMsgFailText: '撤回消息失败',
|
||||
reeditText: '重新编辑',
|
||||
loadingMoreText: '加载更多……',
|
||||
noMoreText: '没有更多了',
|
||||
deleteMsgSuccessText: '删除成功',
|
||||
deleteMsgFailText: '删除失败',
|
||||
conversationEmptyText: '暂无会话',
|
||||
teamOwner: '群主',
|
||||
teamManager: '群管理员',
|
||||
manager: '管理员',
|
||||
teamEmptyText: '暂无群聊',
|
||||
blacklistEmptyText: '暂无黑名单',
|
||||
blacklistSubTitle: '你不会收到列表中任何联系人的消息',
|
||||
validEmptyText: '暂无验证消息',
|
||||
dismissTeamText: '解散群组',
|
||||
dismissTeamConfirmText: '是否确认解散该群组',
|
||||
leaveTeamTitle: '退出群聊',
|
||||
leaveDiscussionTitle: '退出讨论组',
|
||||
leaveTeamConfirmText: '是否确认退出该群组',
|
||||
leaveDiscussionConfirmText: '是否确认退出该讨论组',
|
||||
leaveTeamSuccessText: '已成功退出此群',
|
||||
leaveDiscussionSuccessText: '已成功退出此讨论组',
|
||||
leaveTeamFailedText: '退出此群失败',
|
||||
leaveDiscussionFailedText: '退出此讨论组失败',
|
||||
dismissTeamSuccessText: '群解散成功',
|
||||
dismissTeamFailedText: '群解散失败',
|
||||
updateTeamSuccessText: '修改成功',
|
||||
updateTeamFailedText: '修改失败',
|
||||
teamTitleConfirmText: '群名称不能为空',
|
||||
teamIntroConfirmText: '群介绍不能为空',
|
||||
aliasConfirmText: '备注名不能为空',
|
||||
sendImageFailedText: '发送图片失败',
|
||||
sendVideoFailedText: '发送视频失败',
|
||||
sendAudioFailedText: '发送音频失败',
|
||||
sendFileFailedText: '发送文件失败',
|
||||
sendMsgFailedText: '发送消息失败',
|
||||
weekText: '周前',
|
||||
dayText: '天前',
|
||||
hourText: '小时前',
|
||||
minuteText: '分钟前',
|
||||
nowText: '刚刚',
|
||||
sendText: '发送',
|
||||
sendToText: '发送给',
|
||||
textMsgText: '文本消息',
|
||||
audioMsgText: '音频消息',
|
||||
videoMsgText: '视频消息',
|
||||
fileMsgText: '文件消息',
|
||||
callMsgText: '话单消息',
|
||||
geoMsgText: '地理位置消息',
|
||||
imgMsgText: '图片消息',
|
||||
notiMsgText: '通知消息',
|
||||
robotMsgText: '机器消息',
|
||||
tipMsgText: '提示消息',
|
||||
customMsgText: '自定义消息',
|
||||
unknowMsgText: '未知消息体',
|
||||
noFriendText: '暂无好友',
|
||||
onDismissTeamText: '该群聊已解散',
|
||||
onRemoveTeamText: '您已离开群组',
|
||||
selectSessionFailText: '选择会话失败',
|
||||
noExistUser: '该用户不存在',
|
||||
enterAccount: '请输入账号',
|
||||
validMsgText: '验证消息',
|
||||
blacklistText: '黑名单',
|
||||
applyTeamText: '申请入群',
|
||||
acceptResultText: '已同意',
|
||||
rejectResultText: '已拒绝',
|
||||
rejectTeamInviteText: '拒绝了群邀请',
|
||||
beRejectResultText: '拒绝了好友申请',
|
||||
passResultText: '通过了好友申请',
|
||||
acceptedText: '已同意该申请',
|
||||
acceptFailedText: '同意该申请失败',
|
||||
rejectedText: '已拒绝该申请',
|
||||
rejectFailedText: '拒绝该申请失败',
|
||||
passFriendAskText: '我已经同意了你的申请,现在开始聊天吧~',
|
||||
applyFriendText: '添加您为好友',
|
||||
rejectText: '拒绝',
|
||||
acceptText: '同意',
|
||||
inviteTeamText: '邀请入群',
|
||||
addBlacklist: '加入黑名单',
|
||||
removeBlacklist: '解除',
|
||||
teamAll: '所有人',
|
||||
sendFailWithInBlackText: '对方已将你拉黑,消息发送失败',
|
||||
sendFailWithDeleteText: '双方关系已解除,如需沟通,请申请',
|
||||
friendVerificationText: '好友验证',
|
||||
chooseMentionText: '选择提醒',
|
||||
someoneText: '有人',
|
||||
meText: '我',
|
||||
teamMutePlaceholder: '当前群主已经设置为群禁言',
|
||||
noPermission: '您暂无权限操作',
|
||||
muteAllTeamFailedText: '全员禁言失败',
|
||||
unmuteAllTeamFailedText: '解除全员禁言失败',
|
||||
|
||||
nickInTeam: '我在群里的昵称',
|
||||
viewMoreText: '查看更多',
|
||||
resendMsgFailText: '重发失败',
|
||||
audioBtnText: '按住说话',
|
||||
audioRemindText: '松开发送',
|
||||
audioErrorText: '录音失败,请检查是否开启权限',
|
||||
videoPlayText: '视频播放',
|
||||
updateTeamAvatar: '更新了群头像',
|
||||
updateTeamName: '更新群名称为',
|
||||
updateTeamIntro: '更新了群介绍',
|
||||
updateTeamInviteMode: '更新了群权限“邀请他人权限”为',
|
||||
updateTeamUpdateTeamMode: '更新了群权限“群资料修改权限”为',
|
||||
updateAllowAt: '更新了“@所有人权限”为',
|
||||
updateTeamMute: '更新了“群禁言”为',
|
||||
onlyTeamOwner: '仅群主',
|
||||
teamOwnerAndManagerText: '群主和管理员',
|
||||
closeText: '关闭',
|
||||
openText: '开启',
|
||||
joinTeamText: '加入群组',
|
||||
leaveTeamText: '离开了群组',
|
||||
beRemoveTeamText: '被移出群组',
|
||||
beAddTeamManagersText: '被任命为管理员',
|
||||
beRemoveTeamManagersText: '被移除管理员',
|
||||
newGroupOwnerText: '成为新群主',
|
||||
callDurationText: '通话时长',
|
||||
callCancelText: '已取消',
|
||||
callRejectedText: '已拒绝',
|
||||
callTimeoutText: '超时未接听',
|
||||
callBusyText: '忙线未接听',
|
||||
callFailedText: '呼叫失败',
|
||||
wxAppFileCopyText: '已自动复制网址,请在手机浏览器里粘贴该网址',
|
||||
shootText: '拍摄',
|
||||
albumText: '相册',
|
||||
voiceCallText: '语音通话',
|
||||
videoCallText: '视频通话',
|
||||
msgReadPageTitleText: '消息阅读状态',
|
||||
readText: '已读',
|
||||
unreadText: '未读',
|
||||
fileText: '文件',
|
||||
allUnReadText: '全部成员未读',
|
||||
allReadText: '全部成员已读',
|
||||
searchText: '请输入你要搜索的关键字',
|
||||
searchTitleText: '搜索',
|
||||
friendText: '好友',
|
||||
teamText: '群组',
|
||||
discussionText: '讨论组',
|
||||
searchResultNullText: '该用户不存在',
|
||||
conversationSendFailText: '[发送失败]',
|
||||
conversationNotificationText: '[通知消息]',
|
||||
getForwardMessageFailed: '该消息已撤回或删除',
|
||||
fileMsgTitleText: '[文件消息]',
|
||||
collectionFromText: '来自',
|
||||
openUrlText: '已复制链接,请在浏览器打开链接',
|
||||
SwitchToEnglishText: '是否切换为英文',
|
||||
securityTipText:
|
||||
'仅用于体验云信IM 产品功能,请勿轻信汇款、中奖等涉及钱款的信息,勿轻易拨打陌生电话,谨防上当受骗。',
|
||||
createDiscussionText: '创建讨论组',
|
||||
|
||||
// emoji 不能随便填,要用固定 key
|
||||
Laugh: '[大笑]',
|
||||
Happy: '[开心]',
|
||||
Sexy: '[色]',
|
||||
Cool: '[酷]',
|
||||
Mischievous: '[奸笑]',
|
||||
Kiss: '[亲]',
|
||||
Spit: '[伸舌头]',
|
||||
Squint: '[眯眼]',
|
||||
Cute: '[可爱]',
|
||||
Grimace: '[鬼脸]',
|
||||
Snicker: '[偷笑]',
|
||||
Joy: '[喜悦]',
|
||||
Ecstasy: '[狂喜]',
|
||||
Surprise: '[惊讶]',
|
||||
Tears: '[流泪]',
|
||||
Sweat: '[流汗]',
|
||||
Angle: '[天使]',
|
||||
Funny: '[笑哭]',
|
||||
Awkward: '[尴尬]',
|
||||
Thrill: '[惊恐]',
|
||||
Cry: '[大哭]',
|
||||
Fretting: '[烦躁]',
|
||||
Terrorist: '[恐怖]',
|
||||
Halo: '[两眼冒星]',
|
||||
Shame: '[害羞]',
|
||||
Sleep: '[睡着]',
|
||||
Tired: '[冒星]',
|
||||
Mask: '[口罩]',
|
||||
ok: '[OK]',
|
||||
AllRight: '[好吧]',
|
||||
Despise: '[鄙视]',
|
||||
Uncomfortable: '[难受]',
|
||||
Disdain: '[不屑]',
|
||||
ill: '[不舒服]',
|
||||
Mad: '[愤怒]',
|
||||
Ghost: '[鬼怪]',
|
||||
Angry: '[发怒]',
|
||||
Unhappy: '[不高兴]',
|
||||
Frown: '[皱眉]',
|
||||
Broken: '[心碎]',
|
||||
Beckoning: '[心动]',
|
||||
Ok: '[好的]',
|
||||
Low: '[低级]',
|
||||
Nice: '[赞]',
|
||||
Applause: '[鼓掌]',
|
||||
GoodJob: '[给力]',
|
||||
Hit: '[打你]',
|
||||
Please: '[阿弥陀佛]',
|
||||
Bye: '[拜拜]',
|
||||
First: '[第一]',
|
||||
Fist: '[拳头]',
|
||||
GiveMeFive: '[手掌]',
|
||||
Knife: '[剪刀]',
|
||||
Hi: '[招手]',
|
||||
No: '[不要]',
|
||||
Hold: '[举着]',
|
||||
Think: '[思考]',
|
||||
Pig: '[猪头]',
|
||||
NoListen: '[不听]',
|
||||
NoLook: '[不看]',
|
||||
NoWords: '[不说]',
|
||||
Monkey: '[猴子]',
|
||||
Bomb: '[炸弹]',
|
||||
Cloud: '[筋斗云]',
|
||||
Rocket: '[火箭]',
|
||||
Ambulance: '[救护车]',
|
||||
Poop: '[便便]',
|
||||
// 错误码及提示内容
|
||||
// 通用错误码
|
||||
190001: '内部错误',
|
||||
190002: '非法状态',
|
||||
191001: '使用姿势错误',
|
||||
191002: '操作取消',
|
||||
191003: '回调失败',
|
||||
191004: '参数错误',
|
||||
191005: '请求超时',
|
||||
191006: '资源不存在',
|
||||
191007: '资源已存在',
|
||||
// 连接相关
|
||||
192001: '连接错误',
|
||||
192002: '连接超时',
|
||||
192003: '连接断开',
|
||||
192004: '协议超时',
|
||||
192005: '协议发送失败',
|
||||
192006: '请求失败',
|
||||
// 消息相关
|
||||
107319: 'PIN 数量超限',
|
||||
107320: 'PIN 消息不存在',
|
||||
107322: '重复 PIN',
|
||||
107327: '未开启 PIN 功能',
|
||||
189301: '收藏数量超限',
|
||||
189302: '收藏不存在',
|
||||
189449: '并发操作收藏失败',
|
||||
// 群相关
|
||||
109432: '没有权限',
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
|
||||
// ES6模块化引入
|
||||
//import VConsole from 'vconsole'
|
||||
import App from './App'
|
||||
import uviewPlus, { setConfig } from 'uview-plus'
|
||||
//const vConsole = new VConsole();
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
|
||||
+141
-11
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name" : "uniapp",
|
||||
"appid" : "__UNI__34144D0",
|
||||
"name" : "肝胆相照专家版",
|
||||
"appid" : "__UNI__89F511F",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"versionName" : "4.2.7",
|
||||
"versionCode" : 427,
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
/* 5+App特有相关 */
|
||||
"runmode" : "liberate",
|
||||
"usingComponents" : true,
|
||||
"nvueCompiler" : "uni-app",
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
@@ -16,13 +17,28 @@
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
"compatible" : {
|
||||
"ignoreVersion" : true
|
||||
},
|
||||
"useragent" : {
|
||||
"value" : "gdxz",
|
||||
"concatenate" : true
|
||||
},
|
||||
"modules" : {
|
||||
"OAuth" : {}
|
||||
"Share" : {},
|
||||
"Webview-x5" : {},
|
||||
"UIWebview" : {},
|
||||
"VideoPlayer" : {},
|
||||
"OAuth" : {},
|
||||
"Barcode" : {},
|
||||
"Camera" : {},
|
||||
"Payment" : {}
|
||||
},
|
||||
/* 模块配置 */
|
||||
"distribute" : {
|
||||
/* 应用发布信息 */
|
||||
"android" : {
|
||||
"hasTaskAffinity" : true,
|
||||
/* android打包配置 */
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
@@ -41,34 +57,143 @@
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
|
||||
"minSdkVersion" : 21,
|
||||
"targetSdkVersion" : 30
|
||||
},
|
||||
"ios" : {
|
||||
"dSYMs" : false,
|
||||
"idfa" : false,
|
||||
"urlschemewhitelist" : [ "weixin", "wechat" ],
|
||||
"permission" : {
|
||||
"NSPhotoLibraryUsageDescription" : "需要访问相册以选择图片",
|
||||
"NSCameraUsageDescription" : "需要访问相机以拍照"
|
||||
}
|
||||
},
|
||||
"ios" : {},
|
||||
/* ios打包配置 */
|
||||
"sdkConfigs" : {
|
||||
"oauth" : {
|
||||
"univerify" : {},
|
||||
"weixin" : {
|
||||
"appid" : "wxbf3658f5e674667c",
|
||||
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
|
||||
},
|
||||
"univerify" : {},
|
||||
"sina" : {
|
||||
"appkey" : "2071931330",
|
||||
"redirect_uri" : "http://sns.whalecloud.com/sina2/callback",
|
||||
"UniversalLinks" : ""
|
||||
}
|
||||
},
|
||||
"payment" : {
|
||||
"weixin" : {
|
||||
"__platform__" : [ "ios", "android" ],
|
||||
"appid" : "wxbf3658f5e674667c",
|
||||
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
|
||||
}
|
||||
},
|
||||
"ad" : {},
|
||||
"share" : {
|
||||
"weixin" : {
|
||||
"appid" : "wxbf3658f5e674667c",
|
||||
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
|
||||
},
|
||||
"sina" : {
|
||||
"appkey" : "2071931330",
|
||||
"redirect_uri" : "http://sns.whalecloud.com/sina2/callback",
|
||||
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
|
||||
}
|
||||
},
|
||||
"speech" : {},
|
||||
"push" : {}
|
||||
},
|
||||
"splashscreen" : {
|
||||
"androidStyle" : "common",
|
||||
"useOriginalMsgbox" : false,
|
||||
"iosStyle" : "common"
|
||||
},
|
||||
"icons" : {
|
||||
"android" : {
|
||||
"hdpi" : "D:/uniapp/专家版图标/72.png",
|
||||
"xhdpi" : "D:/uniapp/专家版图标/96.png",
|
||||
"xxhdpi" : "D:/uniapp/专家版图标/144.png",
|
||||
"xxxhdpi" : "D:/uniapp/专家版图标/192.png"
|
||||
},
|
||||
"ios" : {
|
||||
"appstore" : "D:/uniapp/专家版图标/1024.png",
|
||||
"iphone" : {
|
||||
"app@2x" : "D:/uniapp/专家版图标/120.png",
|
||||
"app@3x" : "D:/uniapp/专家版图标/180.png",
|
||||
"spotlight@2x" : "D:/uniapp/专家版图标/80.png",
|
||||
"spotlight@3x" : "D:/uniapp/专家版图标/120.png",
|
||||
"settings@2x" : "D:/uniapp/专家版图标/58.png",
|
||||
"settings@3x" : "D:/uniapp/专家版图标/87.png",
|
||||
"notification@2x" : "D:/uniapp/专家版图标/40.png",
|
||||
"notification@3x" : "D:/uniapp/专家版图标/60.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativePlugins" : {
|
||||
"NIMUniPlugin" : {
|
||||
"PUSH_VIVO_APPID" : "100034326",
|
||||
"PUSH_VIVO_APPKEY" : "e789634c1f76c8f184b1def1101ce824",
|
||||
"PUSH_HONOR_APPID" : "900782424",
|
||||
"__plugin_info__" : {
|
||||
"name" : "NIMUniPlugin",
|
||||
"description" : "uni消息推送插件",
|
||||
"platforms" : "Android,iOS",
|
||||
"url" : "",
|
||||
"android_package_name" : "",
|
||||
"ios_bundle_id" : "",
|
||||
"isCloud" : false,
|
||||
"bought" : -1,
|
||||
"pid" : "",
|
||||
"parameters" : {
|
||||
"PUSH_VIVO_APPID" : {
|
||||
"des" : "厂商VIVO-app_id",
|
||||
"key" : "com.vivo.push.app_id",
|
||||
"value" : ""
|
||||
},
|
||||
"PUSH_VIVO_APPKEY" : {
|
||||
"des" : "厂商VIVO-app_key",
|
||||
"key" : "com.vivo.push.api_key",
|
||||
"value" : ""
|
||||
},
|
||||
"PUSH_HONOR_APPID" : {
|
||||
"des" : "厂商HONOR-app_id",
|
||||
"key" : "com.hihonor.push.app_id",
|
||||
"value" : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PLV-MediaPlayer" : {
|
||||
"__plugin_info__" : {
|
||||
"name" : "Polyv播放器插件",
|
||||
"description" : "该插件封装了保利威底层播放器,是云直播和云点播插件必须的附属插件",
|
||||
"platforms" : "Android,iOS",
|
||||
"url" : "https://ext.dcloud.net.cn/plugin?id=4798",
|
||||
"android_package_name" : "",
|
||||
"ios_bundle_id" : "com.domainname.apppolyvios",
|
||||
"android_package_name" : "cn.shangyu.gdxzExpert",
|
||||
"ios_bundle_id" : "cn.shangyu.gdxzExport",
|
||||
"isCloud" : true,
|
||||
"bought" : 1,
|
||||
"pid" : "4798",
|
||||
"parameters" : {}
|
||||
}
|
||||
},
|
||||
"PLV-VodUniPlugin" : {
|
||||
"__plugin_info__" : {
|
||||
"name" : "polyv保利威视频云点播插件",
|
||||
"description" : "polyv保利威视频云点播软件组件,支持聊天弹幕、视频加密、多码率、去广告等,还支持视频多维数据统计,适用于教学录播、产品介绍片、视频门户等",
|
||||
"platforms" : "Android,iOS",
|
||||
"url" : "https://ext.dcloud.net.cn/plugin?id=2802",
|
||||
"android_package_name" : "cn.shangyu.gdxzExpert",
|
||||
"ios_bundle_id" : "cn.shangyu.gdxzExport",
|
||||
"isCloud" : true,
|
||||
"bought" : 1,
|
||||
"pid" : "2802",
|
||||
"parameters" : {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -83,5 +208,10 @@
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
"vueVersion" : "3",
|
||||
"uniStatistics" : {
|
||||
"version" : "2"
|
||||
},
|
||||
"fallbackLocale" : "zh-Hans",
|
||||
"locale" : "zh-Hans"
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"890086000102014306","product_id":"9105163812218585677","client_id":"178572405214545024","client_secret":"B90774CD63B8A6AB5C2C587DC9D7C4C666EF53A19DBA70C4D549C6302ED6CFBE","project_id":"9105163812218585677","app_id":"10526652","api_key":"CV58Raxnpwbfazn6iLPkESUy7KoiUzZ5iTv8lFg/WuexwYZceAK6F9BjRf0HVmT+039dGKt5yecdkRdQ4FCHsmLZ1Zvh","package_name":"cn.shangyu.gdxzExpert"},"oauth_client":{"client_id":"10526652","client_type":1},"app_info":{"app_id":"10526652","package_name":"cn.shangyu.gdxzExpert"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"cn.shangyu.gdxzExpert","client":{"app_id":"10526652"},"app_info":{"package_name":"cn.shangyu.gdxzExpert","app_id":"10526652"},"oauth_client":{"client_type":1,"client_id":"10526652"}}]}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"developer_id":"109999967097",
|
||||
"app_id":"900782424",
|
||||
"package_name":"cn.shangyu.gdxzExpert",
|
||||
"version":"1.0"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>Info.plist</key>
|
||||
<data>
|
||||
MxazSmkOgOArGHG40O8L+mZ4ULU=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
<dict/>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^version.plist$</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>rules2</key>
|
||||
<dict>
|
||||
<key>.*\.dSYM($|/)</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>11</real>
|
||||
</dict>
|
||||
<key>^(.*/)?\.DS_Store$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>2000</real>
|
||||
</dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^Info\.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^PkgInfo$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^embedded\.provisionprofile$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^version\.plist$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "NIMUniPlugin",
|
||||
"id": "NIMUniPlugin",
|
||||
"version": "1.1.3",
|
||||
"git": "3ff8678",
|
||||
"buildAt": "7/21/2025, 10:17:00 AM",
|
||||
"description": "uni消息推送插件",
|
||||
"_dp_type": "nativeplugin",
|
||||
"_dp_nativeplugin": {
|
||||
"android": {
|
||||
"plugins": [
|
||||
{
|
||||
"type": "module",
|
||||
"name": "NIMUniPlugin-PluginModule",
|
||||
"class": "com.netease.nimlib.uniapp.push.PushModule"
|
||||
}
|
||||
],
|
||||
"hooksClass": "com.netease.nimlib.uniapp.push.PushModule",
|
||||
"integrateType": "aar",
|
||||
"dependencies": [
|
||||
"com.huawei.hms:push:6.9.0.300",
|
||||
"com.meizu.flyme.internet:push-internal:4.2.3",
|
||||
"com.google.android.gms:play-services-base:18.2.0",
|
||||
"com.google.firebase:firebase-messaging:23.1.2",
|
||||
"com.google.firebase:firebase-core:21.1.1",
|
||||
"com.google.code.gson:gson:2.6.2",
|
||||
"commons-codec:commons-codec:1.6",
|
||||
"com.netease.nimlib:basesdk:9.16.1",
|
||||
"com.netease.nimlib:push:9.16.1"
|
||||
],
|
||||
"compileOptions": {
|
||||
"sourceCompatibility": "1.8",
|
||||
"targetCompatibility": "1.8"
|
||||
},
|
||||
"abis": [
|
||||
"armeabi-v7a",
|
||||
"arm64-v8a",
|
||||
"x86"
|
||||
],
|
||||
"minSdkVersion": "21",
|
||||
"useAndroidX": true,
|
||||
"parameters": {
|
||||
"PUSH_VIVO_APPID": {
|
||||
"des": "厂商VIVO-app_id",
|
||||
"key": "com.vivo.push.app_id"
|
||||
},
|
||||
"PUSH_VIVO_APPKEY": {
|
||||
"des": "厂商VIVO-app_key",
|
||||
"key": "com.vivo.push.api_key"
|
||||
},
|
||||
"PUSH_HONOR_APPID": {
|
||||
"des": "厂商HONOR-app_id",
|
||||
"key": "com.hihonor.push.app_id"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ios": {
|
||||
"plugins": [
|
||||
{
|
||||
"type": "module",
|
||||
"name": "NIMUniPlugin-PluginModule",
|
||||
"class": "NIMPluginModule"
|
||||
}
|
||||
],
|
||||
"hooksClass": "NIMPluginProxy",
|
||||
"integrateType": "framework",
|
||||
"deploymentTarget": "9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user