Compare commits

...

10 Commits

Author SHA1 Message Date
bad93a3a89 feat: 增加视频水印和防盗录 2026-06-09 16:33:15 +08:00
aaca2f938c chore: 增加妇幼生殖vip相关修改
- 将应用版本号更新至1.0.60
- 修改开发环境baseUrl配置,切换至本地测试地址
- 在多个页面中新增"妇幼生殖"角色及相关VIP权限说明
- 优化VIP权限文案,明确不包含论坛权益
2026-05-20 13:28:10 +08:00
de92a851b7 feat: 新增退款相关功能及状态显示
- 在订单详情页新增退款状态显示,包括"已退款"和"退款中"
- 新增退款目的地页面,用户可查看退款信息
- 更新应用版本号至1.0.58
- 更新依赖edu-core至v1.0.13
- 修改部分组件样式和逻辑,优化用户体验
2026-05-09 11:13:51 +08:00
69aa5cda40 chore: 视频播放下一集自动保持倍速设置;增加2.5倍速 2026-04-14 10:24:58 +08:00
f19d2b5eca chore: 更新课程购买页面的权限说明文案
- 将应用版本号从1.0.53提升至1.0.54
- 开发环境API切换至线上正式地址,便于测试
- 更新课程购买页面的权限说明文案,使其更清晰
2026-03-31 10:16:11 +08:00
78cc9cbccf feat: 更新心理论坛页面,优化分类和搜索功能
- 将应用版本号更新至1.0.53
- 修改开发环境baseUrl配置,确保使用本地测试地址
- 在心理论坛页面新增分类选择和搜索功能
- 优化滚动条样式和页面布局
2026-03-30 16:33:42 +08:00
b0c63d3faa chore: 更新 edu-core 依赖至 v1.0.11,修改个人资产获取相册权限兼容性。
- 将 edu-core 依赖版本更新至 v1.0.11
- 修改开发环境 baseUrl 配置,确保使用线上正式环境
2026-03-27 09:44:14 +08:00
e2a546218a feat: 新增预售书重复购买提示
- 更新应用版本号至1.0.52
- 在订单提交页面新增预售备注确认弹窗,用户可在购买前查看并确认备注内容
- 添加相关方法以处理预售备注的显示和确认逻辑
- 更新请求配置,增加预售备注接口
2026-03-25 11:40:20 +08:00
d635ab5081 feat: 修改学术传承分类和我的vip自动换行
调整VIP购买提示明确不包含论坛权益
优化用户信息页面布局样式
切换开发环境API至本地测试地址
2026-03-24 18:48:03 +08:00
1efcf2d675 feat: 添加记录观看时长;个人资料增加身份项;
- 将应用版本号更新至1.0.50
- 修改edu-core依赖为git引用,版本更新至1.0.9
- 优化用户资料页面,增加用户信息展示和编辑功能
- 更新请求配置,改善加载动画逻辑
- 添加新权限请求以支持外部存储访问
2026-03-24 14:25:45 +08:00
36 changed files with 1362 additions and 2865 deletions

View File

@@ -4,6 +4,7 @@ if (process.env.NODE_ENV === 'development') {
// 开发环境 // 开发环境
// baseUrl = "http://192.168.110.100:9200/pb/"; // 张川川 // baseUrl = "http://192.168.110.100:9200/pb/"; // 张川川
baseUrl = "https://api.nuttyreading.com/"; //线上正式 baseUrl = "https://api.nuttyreading.com/"; //线上正式
// baseUrl = "http://192.168.110.131:9200/pb/"; // 王亚男
} else if (process.env.NODE_ENV === 'production') { } else if (process.env.NODE_ENV === 'production') {
// baseUrl = "http://192.168.110.100:9200/pb/"; // 张川川 // baseUrl = "http://192.168.110.100:9200/pb/"; // 张川川
baseUrl = "https://api.nuttyreading.com/"; //线上正式 baseUrl = "https://api.nuttyreading.com/"; //线上正式

View File

@@ -82,11 +82,12 @@ $http.getAliToken = function (callback) {
//请求开始拦截器 //请求开始拦截器
$http.requestStart = function (options) { $http.requestStart = function (options) {
// console.log("请求开始", options); // console.log("请求开始", options);
if (options.load && options.data.loadAnimate != 'none') { const resData = options.data || {}
if (options.load && resData.loadAnimate != 'none') {
//打开加载动画 //打开加载动画
store.commit("setLoadingShow", true); store.commit("setLoadingShow", true);
} }
if (options.data.loadAnimate == 'none') { if (resData.loadAnimate == 'none') {
delete options.data.loadAnimate delete options.data.loadAnimate
} }
// 图片、视频上传大小限制 // 图片、视频上传大小限制
@@ -264,7 +265,8 @@ $http.dataFactory = async function (res) {
// 返回错误的结果(catch接受数据) // 返回错误的结果(catch接受数据)
return Promise.reject({ return Promise.reject({
statusCode: 0, statusCode: 0,
errMsg: "【request】" + (httpData.info || httpData.msg), // errMsg: "【request】" + (httpData.info || httpData.msg),
errMsg: (httpData.info || httpData.msg),
data: res.data data: res.data
}); });
} }

View File

@@ -61,6 +61,9 @@ Vue.component('common-video', commonVideo);
import CommonCourseVideo from 'edu-core/components/course-video' import CommonCourseVideo from 'edu-core/components/course-video'
Vue.component('CommonCourseVideo', CommonCourseVideo); Vue.component('CommonCourseVideo', CommonCourseVideo);
import CommonRefundDestination from 'edu-core/components/order/refund-destination.vue'
Vue.component('common-refund-destination', CommonRefundDestination);
import commonGoodsList from '@/pages/component/commonComponents/goodsList.vue' import commonGoodsList from '@/pages/component/commonComponents/goodsList.vue'
Vue.component('common-goods-list', commonGoodsList); Vue.component('common-goods-list', commonGoodsList);
import commonCurriculumList from '@/pages/component/commonComponents/curriculum.vue' import commonCurriculumList from '@/pages/component/commonComponents/curriculum.vue'

View File

@@ -2,9 +2,9 @@
"name" : "心灵空间", "name" : "心灵空间",
"appid" : "__UNI__BBBDFD2", "appid" : "__UNI__BBBDFD2",
"description" : "心灵空间", "description" : "心灵空间",
"versionName" : "1.0.49", "versionName" : "1.0.61",
"sassImplementationName" : "node-sass", "sassImplementationName" : "node-sass",
"versionCode" : 1049, "versionCode" : 1061,
"transformPx" : false, "transformPx" : false,
/* 5+App */ /* 5+App */
"app-plus" : { "app-plus" : {
@@ -61,12 +61,16 @@
"<uses-permission android:name=\"android.permission.CAMERA\"/>", "<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>", "<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>", "<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\" />" "<uses-permission android:name=\"android.permission.WAKE_LOCK\" />",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\"/>"
], ],
"minSdkVersion" : 23, "minSdkVersion" : 23,
"targetSdkVersion" : 35, "targetSdkVersion" : 35,
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ], "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
"schemes" : "soulspace" "schemes" : "soulspace",
"excludePermissions" : []
}, },
/* ios */ /* ios */
"ios" : { "ios" : {

12
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"edu-core": "file:../edu-core", "edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"tcplayer.js": "^5.1.0" "tcplayer.js": "^5.1.0"
}, },
@@ -17,11 +17,6 @@
"postcss-px-to-viewport": "^1.1.1" "postcss-px-to-viewport": "^1.1.1"
} }
}, },
"../edu-core": {
"version": "1.0.6",
"license": "ISC",
"devDependencies": {}
},
"node_modules/babel-runtime": { "node_modules/babel-runtime": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz", "resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -73,8 +68,9 @@
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
}, },
"node_modules/edu-core": { "node_modules/edu-core": {
"resolved": "../edu-core", "version": "1.0.14",
"link": true "resolved": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#eb9eccdc3d281fe84127a93e019650512859ff0c",
"license": "ISC"
}, },
"node_modules/es5-shim": { "node_modules/es5-shim": {
"version": "4.6.7", "version": "4.6.7",

View File

@@ -14,7 +14,7 @@
}, },
"homepage": "https://github.com/dcloudio/hello-uniapp#readme", "homepage": "https://github.com/dcloudio/hello-uniapp#readme",
"dependencies": { "dependencies": {
"edu-core": "file:../edu-core", "edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"tcplayer.js": "^5.1.0" "tcplayer.js": "^5.1.0"
}, },

View File

@@ -194,6 +194,18 @@
} }
} }
}, },
{
"path": "pages/detail/refundDestination",
"style": {
"navigationBarTitleText": "钱款去向",
"enablePullDownRefresh": false,
"app-plus": {
"bounce": "none",
"titleNView": false,
"popGesture": "none"
}
}
},
{ {
"path": "pages/my/index", "path": "pages/my/index",
"style": { "style": {

View File

@@ -510,6 +510,17 @@
</view> </view>
</view> </view>
</u-popup> </u-popup>
<u-modal
:show="presaleRemarkModalShow"
title="提示"
:content="presaleRemarkModalContent"
:showCancelButton="true"
confirmText="继续购买"
cancelText="取消"
@confirm="handlePresaleRemarkConfirm"
@cancel="handlePresaleRemarkCancel"
@close="handlePresaleRemarkCancel"
></u-modal>
</view> </view>
</template> </template>
@@ -607,6 +618,9 @@
orderModalShowInfo: {}, orderModalShowInfo: {},
orderModalShow: false, orderModalShow: false,
presaleRemarkModalShow: false,
presaleRemarkModalContent: "",
presaleRemarkModalResolve: null,
payType: 1, payType: 1,
freightNum: 0, freightNum: 0,
addressData: { addressData: {
@@ -1339,6 +1353,62 @@
this.content = this.remark; this.content = this.remark;
} }
}, },
openPresaleRemarkModal(content) {
this.presaleRemarkModalContent = content;
this.presaleRemarkModalShow = true;
return new Promise((resolve) => {
this.presaleRemarkModalResolve = resolve;
});
},
handlePresaleRemarkConfirm() {
this.presaleRemarkModalShow = false;
if (this.presaleRemarkModalResolve) {
const resolve = this.presaleRemarkModalResolve;
this.presaleRemarkModalResolve = null;
resolve(true);
}
},
handlePresaleRemarkCancel() {
this.presaleRemarkModalShow = false;
if (this.presaleRemarkModalResolve) {
const resolve = this.presaleRemarkModalResolve;
this.presaleRemarkModalResolve = null;
resolve(false);
}
},
async checkPresaleRemark() {
if (this.pageType != "goods") {
return true;
}
const productIds = this.goodsDataList
.map((e) => e.productId)
.filter((id) => id)
.join(",");
if (!productIds || !this.urlList.presaleRemark) {
return true;
}
try {
const res = await this.$http.request({
url: `${this.urlList.presaleRemark}`,
method: "POST",
data: {
productIds,
},
header: {
"Content-Type": "application/json",
},
});
const remark =
res && typeof res.remark == "string" ? res.remark.trim() : "";
if (!remark) {
return true;
}
return await this.openPresaleRemarkModal(remark);
} catch (error) {
console.log("presaleRemark error:", error);
return true;
}
},
async goBuyJie() { async goBuyJie() {
if (this.pageType == "vip") { if (this.pageType == "vip") {
if (this.radioValue != "1") { if (this.radioValue != "1") {
@@ -1446,6 +1516,11 @@
}); });
thisproduct = thisproduct.join(","); thisproduct = thisproduct.join(",");
} }
const passPresaleRemark = await this.checkPresaleRemark();
if (!passPresaleRemark) {
this.buyingFlag = false;
return;
}
if ( if (
this.historyOrderInfo && this.historyOrderInfo &&
thisproduct == this.historyOrderInfo.product && thisproduct == this.historyOrderInfo.product &&

View File

@@ -180,9 +180,9 @@ export default {
gotoDetail(v) { gotoDetail(v) {
this.$emit("hancleClick", v); this.$emit("hancleClick", v);
}, },
onHandleClickBuy() { onHandleClickBuy(e) {
this.$emit("selectGoodsData", this.selectGoodsData); this.$emit("selectGoodsData", this.selectGoodsData);
this.$emit("onHandleClickBuy"); this.$emit("onHandleClickBuy", e);
}, },
}, },
onBackPress() { onBackPress() {
@@ -362,4 +362,8 @@ export default {
color: #ff1f00; color: #ff1f00;
font-weight: bold; font-weight: bold;
} }
.title_list {
margin-bottom: 20rpx;
}
</style> </style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<view style="background-color: #fff; width: 100%"> <view style="background-color: #fff; width: 100%; padding: 0 20rpx;">
<u-tabs <u-tabs
lineWidth="30" lineWidth="30"
lineColor="#294a97" lineColor="#294a97"

View File

@@ -12,9 +12,10 @@
<CommonCourseVideo <CommonCourseVideo
:video-list="videoArray" :video-list="videoArray"
:current-index="currentVideoIndex !== null ? currentVideoIndex : 0" :current-index="currentVideoIndex !== null ? currentVideoIndex : 0"
:course="{courseTitle:options.navTitle, chapterTitle: curriculumData.title}" :course="{courseTitle:options.navTitle, chapterTitle: curriculumData.title, catalogueId: curriculumData.catalogueId || '', courseId: curriculumData.courseId || ''}"
:cover="options.curriculumImgUrl" :cover="options.curriculumImgUrl"
:http="$http" :http="$http"
:user-info="userInfo"
/> />
<view <view

File diff suppressed because it is too large Load Diff

View File

@@ -363,7 +363,8 @@
<view class="title">咨询课程</view> <view class="title">咨询课程</view>
<view class="content"> <view class="content">
<view class="center"> <view class="center">
<text style="color: #f42c32 ;">课程仅限为做过心理人格测试,结果显示均为正常的用户开放购买权限。</text> <view style="color: #f42c32 ;">课程为进阶课程,原则上需完成「心理动力训练」课程后,方可开通学习权限。</view>
<view style="color: #f42c32 ;">如尚未学习「心理动力训练」课程,但人格测试结果全部为阴性,可联系客服老师申请开通权限。</view>
<br /><text style="color: #838588;font-size: 13px;">点击图片后长按图片保存到手机,或使用微信扫描二维码添加客服企业微信。</text> <br /><text style="color: #838588;font-size: 13px;">点击图片后长按图片保存到手机,或使用微信扫描二维码添加客服企业微信。</text>
@@ -630,6 +631,8 @@ export default {
role = '心理学'; role = '心理学';
} else if (type == 9) { } else if (type == 9) {
role = '中西汇通学'; role = '中西汇通学';
} else if (type == 10) {
role = '妇幼生殖';
} }
if (res.userVip.endTime) { if (res.userVip.endTime) {
var time = res.userVip.endTime ? res.userVip.endTime.split(' ')[0] : '' var time = res.userVip.endTime ? res.userVip.endTime.split(' ')[0] : ''
@@ -637,7 +640,7 @@ export default {
var time = '' var time = ''
} }
this.goBuyTitle = '尊贵的' + role + 'VIP您的有效期到' + time; this.goBuyTitle = '尊贵的' + role + 'VIP您的有效期到' + time + '(不包含论坛)';
this.goBuyType = 1; this.goBuyType = 1;
} else { //否则没有开通vip } else { //否则没有开通vip
this.goBuyTitle = ''; this.goBuyTitle = '';
@@ -676,12 +679,14 @@ export default {
text = '心理学'; text = '心理学';
} else if (item == '9') { } else if (item == '9') {
text = '中西汇通学'; text = '中西汇通学';
} else if (item == '10') {
text = '妇幼生殖';
} }
this.textList.push(text); this.textList.push(text);
}); });
const joinedText = this.textList.join('/'); const joinedText = this.textList.join('/');
this.goBuyTitle = '购买' + joinedText + 'VIP即可畅享更多专属权益'; this.goBuyTitle = '购买' + joinedText + 'VIP即可畅享更多专属权益(不包含论坛)';
} }
}) })
}, },

View File

@@ -69,6 +69,12 @@
<text <text
class="orderState orderState5" class="orderState orderState5"
v-if="orderContet.orderStatus == 5">已超时</text> v-if="orderContet.orderStatus == 5">已超时</text>
<text
class="orderState orderState6"
v-if="orderContet.orderStatus == 6">已退款</text>
<text
class="orderState orderState7"
v-if="orderContet.orderStatus == 7">退款中</text>
</view> </view>
<view class="order_block"> <view class="order_block">
@@ -857,14 +863,15 @@ export default {
text: "继续付款", text: "继续付款",
}); });
} }
if (this.orderContet.orderStatus == 0) { // soulspace注释取消订单按钮
this.customButton.push({ // if (this.orderContet.orderStatus == 0) {
width: "160rpx", // this.customButton.push({
text: "取消订单", // width: "160rpx",
color: "#333", // text: "取消订单",
backgroundColor: "#f0f0f0", // color: "#333",
}); // backgroundColor: "#f0f0f0",
} // });
// }
if (this.orderContet.orderStatus == 0) { if (this.orderContet.orderStatus == 0) {
this.titleStat = "待支付"; this.titleStat = "待支付";
} else if (this.orderContet.orderStatus == 1) { } else if (this.orderContet.orderStatus == 1) {
@@ -873,6 +880,10 @@ export default {
this.titleStat = "待收到"; this.titleStat = "待收到";
} else if (this.orderContet.orderStatus == 3) { } else if (this.orderContet.orderStatus == 3) {
this.titleStat = "已完成"; this.titleStat = "已完成";
} else if (this.orderContet.orderStatus == 6) {
this.titleStat = "已退款";
} else if (this.orderContet.orderStatus == 7) {
this.titleStat = "退款中";
} }
if ( if (
this.orderContet.orderStatus >= 2 && this.orderContet.orderStatus >= 2 &&
@@ -915,9 +926,27 @@ export default {
}, },
}); });
}, },
checkPayTimeout(payItem) {
const createTime = payItem && payItem.createTime;
if (!createTime) return true;
const createdAt = new Date(String(createTime).replace(/-/g, "/")).getTime();
if (!createdAt) return true;
const expired = Date.now() - createdAt > 10 * 60 * 1000;
if (expired) {
uni.showModal({
title: "提示",
content: "订单已超时,不能继续支付,请重新下单",
confirmText: "知道了",
showCancel: false,
});
return false;
}
return true;
},
// 支付 // 支付
goPay(payItem) { goPay(payItem) {
if (!this.checkPayTimeout(payItem)) return;
if (payItem.paymentMethod == 2) { if (payItem.paymentMethod == 2) {
console.log("阿里支付"); console.log("阿里支付");
setPay( setPay(
@@ -1054,6 +1083,12 @@ view,uni-view {
.orderState5 { .orderState5 {
background-color: #787878; background-color: #787878;
} }
.orderState6 {
background-color: #f56c6c;
}
.orderState7 {
background-color: #f56c6c;
}
.guoqi { .guoqi {
font-size: 28rpx; font-size: 28rpx;
align-items: center; align-items: center;

View File

@@ -0,0 +1,27 @@
<template>
<view class="page-wrap">
<public-module></public-module>
<common-refund-destination :order-id="orderId" :http="$http" />
</view>
</template>
<script>
export default {
data() {
return {
orderId: "",
};
},
onLoad(options) {
if (options && options.orderId != null) {
this.orderId = options.orderId;
}
},
};
</script>
<style scoped>
.page-wrap {
min-height: 100vh;
}
</style>

View File

@@ -1,10 +1,12 @@
<template> <template>
<view class="commonPageBox"> <view class="commonPageBox">
<z-paging ref="paging" v-model="dataList" auto-show-back-to-top @query="getDataList"> <z-paging ref="paging" v-model="dataList" auto-show-back-to-top :auto="false" @query="getDataList">
<template #top> <template #top>
<z-nav-bar title="心理论坛"></z-nav-bar> <z-nav-bar title="心理论坛"></z-nav-bar>
<view class="search_box"> <view class="search_box">
<u-search placeholder="请输入文章标题" v-model="query.title" @search="handleSearch" @custom="handleSearch"></u-search> <u-search placeholder="请输入文章标题" v-model="query.title" @search="handleSearch" @custom="handleSearch"></u-search>
<u-tabs :list="classification" keyName="dictValue" lineWidth="30" class="classification_tabs" @click="handleClassification"></u-tabs>
</view> </view>
</template> </template>
@@ -39,15 +41,38 @@ export default {
}, },
dataList: [], //列表数据 dataList: [], //列表数据
statusNull: null, //暂无数据显示 statusNull: null, //暂无数据显示
classification: []
} }
}, },
mounted(){ mounted(){
}, },
onLoad() { onLoad() {
// this.getDataList(this.query.page, this.query.limit) this.getClassification();
}, },
methods: { methods: {
//免费课程数据 // 获取分类
getClassification(){
this.$http.request({
url: "book/sysdictdata/selectByType/psycheForumLabel",
method: "GET",
header: { "Content-Type": "application/json" },
})
.then((res) => {
if (res.code == 0 && res.dataList) {
this.classification = res.dataList;
this.query.type = res.dataList[0].dictType;
this.$refs.paging.reload();
}
})
.catch((e) => {
console.log("获取分类失败", e);
});
},
handleClassification(e){
this.query.type = e.dictType;
this.$refs.paging.reload();
},
// 获取列表
getDataList(pageNo, pageSize){ getDataList(pageNo, pageSize){
this.$http.request({ this.$http.request({
url: 'common/wxPublicAccount/getWxPublicAccountArticleList', url: 'common/wxPublicAccount/getWxPublicAccountArticleList',
@@ -63,7 +88,7 @@ export default {
}) })
.then(res=> { .then(res=> {
if (res.code == 0) { if (res.code == 0) {
if(res.page.records && res.page.records.length>0){ if(res.page.records){
this.$refs.paging.complete(res.page.records); this.$refs.paging.complete(res.page.records);
}else{ }else{
this.$refs.paging.complete(false); this.$refs.paging.complete(false);
@@ -85,7 +110,7 @@ export default {
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.commonPageBox{ .commonPageBox{
background: #eff5f8; background: #eff5f8;
height: 100vh; height: 100vh;
@@ -94,6 +119,11 @@ export default {
background: #fff; background: #fff;
padding: 0 20rpx 20rpx; padding: 0 20rpx 20rpx;
} }
.classification_tabs {
::v-deep .u-tabs__wrapper__nav__item {
padding: 0 20rpx;
}
}
.cateList { .cateList {
width: 100%; width: 100%;
} }
@@ -189,25 +219,24 @@ export default {
color: #999; color: #999;
font-size: 22rpx; font-size: 22rpx;
} }
::v-deep .zp-scroll-view .uni-scroll-view::-webkit-scrollbar {
::v-deep ::-webkit-scrollbar {
/*滚动条整体样式*/ /*滚动条整体样式*/
width: 4px !important; width: 4px !important;
height: 1px !important; height: 4px !important;
overflow: auto !important; overflow: auto !important;
background: #ccc !important; background: #f5f5f5 !important;
-webkit-appearance: auto !important; -webkit-appearance: auto !important;
display: block; display: block;
} }
::v-deep ::-webkit-scrollbar-thumb { ::v-deep .zp-scroll-view .uni-scroll-view::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/ /*滚动条里面小方块*/
border-radius: 10px !important; border-radius: 10px !important;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2) !important; box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2) !important;
background: #7b7979 !important; background: #eee !important;
} }
::v-deep ::-webkit-scrollbar-track { ::v-deep .zp-scroll-view .uni-scroll-view::-webkit-scrollbar-track {
/*滚动条里面轨道*/ /*滚动条里面轨道*/
// box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2) !important; // box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2) !important;
// border-radius: 10px !important; // border-radius: 10px !important;

View File

@@ -455,7 +455,12 @@
}, },
//点击下单按钮 //点击下单按钮
onHandleClickBuy(e) { onHandleClickBuy(e) {
if(this.buttonType==0){ //如果是加入购物车 if (e && e.content && e.content.text === "购物车") {
this.onHandleClick();
return;
}
const clickType = typeof (e && e.index) === "number" ? e.index : this.buttonType;
if(clickType==0){ //如果是加入购物车
console.log('剩余', this.selectGoodsData.productStock) console.log('剩余', this.selectGoodsData.productStock)
if(this.selectGoodsData.productStock==0){ if(this.selectGoodsData.productStock==0){
uni.showToast({ uni.showToast({

View File

@@ -107,6 +107,7 @@ export default {
list: "app/phone.do?getCourseDetail_new", list: "app/phone.do?getCourseDetail_new",
initPrepareOrder: "common/buyOrder/initPrepareOrder", initPrepareOrder: "common/buyOrder/initPrepareOrder",
buyOrder: "book/buyOrder/placeOrder", buyOrder: "book/buyOrder/placeOrder",
presaleRemark: "book/buyOrder/presaleRemark",
curriculumInfo: "app/phone.do?getCourseInfo", curriculumInfo: "app/phone.do?getCourseInfo",
detailInfo: "app/phoneDoctor.do?getTaiHuClassInfo_new", detailInfo: "app/phoneDoctor.do?getTaiHuClassInfo_new",
userInfo: "common/user/getUserInfo", userInfo: "common/user/getUserInfo",

View File

@@ -22,13 +22,20 @@
</view> </view>
</view> </view>
<view class="userInfoBox"> <view class="userInfoBox">
<view class="name">{{ userMes.nickname ? userMes.nickname : "未设置" }}</view> <view class="name">
{{ userMes.nickname ? userMes.nickname : "未设置" }}
<text v-if="userMes.profile" class="user-profile">({{userMes.profile}})</text>
</view>
<view class="phone" v-if="userMes.tel">手机号({{ userMes.tel }})</view> <view class="phone" v-if="userMes.tel">手机号({{ userMes.tel }})</view>
<view class="vip_type" v-if="textList.length>0"> <view class="vip_type" v-if="textList.length>0">
<view class="vip_type_item" v-for="(item,index) in textList" :key="index"> <view class="vip_type_item" v-for="(item,index) in textList" :key="index">
{{item}}<image src="@/static/icon/chao_vip.png"></image> {{item}}<image src="@/static/icon/chao_vip.png"></image>
</view> </view>
</view> </view>
<view>
<u-tag v-if="userMes.todayWatch" :text="userMes.todayWatch" size="mini" plain plainFill type="success" class="watch-time"></u-tag>
<u-tag v-if="userMes.totalWatch" :text="userMes.totalWatch" size="mini" plain plainFill class="watch-time"></u-tag>
</view>
</view> </view>
<br clear="both" /> <br clear="both" />
</view> </view>
@@ -318,6 +325,9 @@ export default {
if (this.userInfo.id != undefined) { if (this.userInfo.id != undefined) {
this.$http.post("common/user/getUserInfo").then((res) => { this.$http.post("common/user/getUserInfo").then((res) => {
this.userMes = res.result; this.userMes = res.result;
this.userMes.profile = res.des || '';
this.userMes.todayWatch = res.todayWatch || '';
this.userMes.totalWatch = res.totalWatch || '';
}); });
} }
}, },
@@ -383,6 +393,9 @@ export default {
}else if(item.type=='9'){ }else if(item.type=='9'){
item.text = '中西汇通学'; item.text = '中西汇通学';
this.textList.push(item.text); this.textList.push(item.text);
}else if(item.type=='10'){
item.text = '妇幼生殖';
this.textList.push(item.text);
}else if(item.type=='1'){ }else if(item.type=='1'){
item.text = '医学S'; item.text = '医学S';
}else if(item.type=='2'){ }else if(item.type=='2'){
@@ -427,23 +440,31 @@ export default {
} }
.userInfoBox{ .userInfoBox{
padding-left: 20rpx; padding-left: 20rpx;
flex: 1;
} }
view { view {
.name { .name {
font-weight: bold;
width: 100%; width: 100%;
font-size: 30rpx; font-weight: bold;
line-height: 40rpx; font-size: 32rpx;
color: #6990c7 !important; color: #6990c7 !important;
line-height: 1.2;
}
.user-profile{
font-size: 28rpx;
font-weight: normal;
display: inline-block;
} }
.phone { .phone {
font-size: 26rpx; font-size: 26rpx;
line-height: 40rpx; color: #6990c7 !important;
color: #6990c7; margin: 6rpx 0;
} }
.per_user_img { .per_user_img {
display: inline-block; display: inline-block;
width: 40upx; width: 40upx;
@@ -451,6 +472,12 @@ export default {
margin-left: 10rpx; margin-left: 10rpx;
vertical-align: super; vertical-align: super;
} }
.watch-time {
margin-top: 10rpx;
display: inline-block;
margin-right: 10rpx;
}
} }
} }
@@ -732,7 +759,7 @@ export default {
} }
.modal_vip{ .modal_vip{
margin-top: 50rpx; margin-top: 25rpx;
padding: 20rpx 20rpx 0; padding: 20rpx 20rpx 0;
height: auto; height: auto;
display: flex; display: flex;
@@ -1022,6 +1049,7 @@ export default {
margin-top: 10rpx; margin-top: 10rpx;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
} }
.vip_type_item{ .vip_type_item{
display: flex; display: flex;
@@ -1035,7 +1063,8 @@ export default {
height: 40rpx; height: 40rpx;
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
margin-right: 10rpx; margin-right: 8rpx;
margin-bottom: 4rpx;
} }
.vip_type_item:last-child{ .vip_type_item:last-child{
margin-right: 0; margin-right: 0;
@@ -1044,9 +1073,9 @@ export default {
width: 34rpx; width: 34rpx;
height: 24rpx; height: 24rpx;
} }
.vip_infor{ // .vip_infor{
} // }
.vip_infor_item{ .vip_infor_item{
display: block; display: block;
color: #fff; color: #fff;
@@ -1058,9 +1087,9 @@ export default {
.vip_infor_item text{ .vip_infor_item text{
color: #fff; color: #fff;
} }
.vip_btn{ // .vip_btn{
} // }
.vip_btn button{ .vip_btn button{
background: none; background: none;
border: 2rpx solid #294a97; border: 2rpx solid #294a97;

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
<view class="commonPageBox commonDetailPage"> <view class="commonPageBox commonDetailPage">
<z-nav-bar title="我的订单" :backState="2000"></z-nav-bar> <z-nav-bar title="我的订单" :backState="2000"></z-nav-bar>
<view class="cateList flexbox"> <view class="cateList flexbox">
<common-sticky itemStyle="width:20%; height: 68rpx;font-size:24rpx;" :list="ordersTabs" label="name" <common-sticky itemStyle="height: 68rpx;font-size:24rpx; padding: 0 20rpx;" :list="ordersTabs" label="name"
:currentCateIndex="currentCateIndex" @handleselectCate="ordersTabCLi"></common-sticky> :currentCateIndex="currentCateIndex" @handleselectCate="ordersTabCLi"></common-sticky>
</view> </view>
@@ -42,6 +42,8 @@
<text class="orderstatus" v-show="slotProps.row.orderStatus == 3">交易成功</text> <text class="orderstatus" v-show="slotProps.row.orderStatus == 3">交易成功</text>
<text class="orderstatus" v-show="slotProps.row.orderStatus == 4">交易失败</text> <text class="orderstatus" v-show="slotProps.row.orderStatus == 4">交易失败</text>
<text class="orderstatus" v-show="slotProps.row.orderStatus == 5">已过期</text> <text class="orderstatus" v-show="slotProps.row.orderStatus == 5">已过期</text>
<text class="orderstatus" v-show="slotProps.row.orderStatus == 6">已退款</text>
<text class="orderstatus" v-show="slotProps.row.orderStatus == 7">退款中</text>
</view> </view>
</view> </view>
@@ -248,8 +250,9 @@
</view> </view>
<view class="operation_box boxShadow" v-if="slotProps.row.isShowMore == true"> <view class="operation_box boxShadow" v-if="slotProps.row.isShowMore == true">
<view v-if="slotProps.row.orderStatus == 0" <!-- soulspace注释取消订单入口 -->
@click.stop="canceOrder(slotProps.row)">取消订单</view> <!-- <view v-if="slotProps.row.orderStatus == 0"
@click.stop="canceOrder(slotProps.row)">取消订单</view> -->
</view> </view>
<!-- @click.stop="openMore(slotProps.row, slotProps.rowIndex)" 更多 --> <!-- @click.stop="openMore(slotProps.row, slotProps.rowIndex)" 更多 -->
<view class="btns flexbox" style="margin-top: 10rpx"> <view class="btns flexbox" style="margin-top: 10rpx">
@@ -264,7 +267,10 @@
@click.stop="seeExpressDetail(slotProps.row)">查看物流</view> @click.stop="seeExpressDetail(slotProps.row)">查看物流</view>
<view class="orderstatusbtn" v-if="slotProps.row.orderStatus == 2" <view class="orderstatusbtn" v-if="slotProps.row.orderStatus == 2"
@click.stop="OverOrder(slotProps.row)">确认收到</view> @click.stop="OverOrder(slotProps.row)">确认收到</view>
<view class="orderstatusbtn" v-if="slotProps.row.orderStatus == 3">申请售后</view> <view class="orderstatusbtn" v-if="slotProps.row.orderStatus == 6 || slotProps.row.orderStatus == 7"
@click.stop="goRefundDestination(slotProps.row)">钱款去向</view>
<view class="orderstatusbtn" v-if="slotProps.row.refundableStatus === true"
@click.stop="confirmApplyRefund(slotProps.row)">申请退款</view>
</view> </view>
</view> </view>
<view style="border-bottom: 2rpx solid #e9e9e9; height: 50rpx" <view style="border-bottom: 2rpx solid #e9e9e9; height: 50rpx"
@@ -300,10 +306,12 @@
come: "3", come: "3",
isShowTab: false, isShowTab: false,
isLoadingHide: false, isLoadingHide: false,
moreList: [{ // soulspace注释取消订单菜单
name: "取消订单", // moreList: [{
key: "false", // name: "取消订单",
}, ], // key: "false",
// }, ],
moreList: [],
currentCateIndex: 0, currentCateIndex: 0,
pagination: { pagination: {
page: 1, //页码 page: 1, //页码
@@ -347,6 +355,16 @@
value: 3, value: 3,
badge: {}, badge: {},
}, },
{
name: "已退款",
value: 6,
badge: {},
},
{
name: "退款中",
value: 7,
badge: {},
},
], ],
selectOrderInfo: {}, selectOrderInfo: {},
ordersListTab: 1, ordersListTab: 1,
@@ -437,6 +455,48 @@
val.orderSn, val.orderSn,
}); });
}, },
goRefundDestination(row) {
uni.navigateTo({
url: `/pages/detail/refundDestination?orderId=${row.orderId}`,
});
},
confirmApplyRefund(row) {
uni.showModal({
title: "申请退款",
content: "请确认是否提交退款申请?",
confirmText: "确认提交",
cancelText: "取消",
success: (res) => {
if (res.confirm) {
this.submitOrderRefund(row);
}
},
});
},
submitOrderRefund(orderRow) {
this.$http.request({
url: "book/buyOrder/refundOrder",
method: "POST",
data: {
orderId: orderRow.orderId,
},
header: {
"Content-Type": "application/json",
},
}).then((res) => {
if (res.code === 0) {
this.$commonJS.showToast("申请退款成功");
this.newestpage = 1;
this.pagination.page = 1;
this.newList = [];
this.getBookList(this.ordersListTab, false);
return;
}
this.$commonJS.showToast(res.errMsg || "申请退款失败");
}).catch(() => {
this.$commonJS.showToast("申请退款失败");
});
},
//初始化获取数据 //初始化获取数据
getBookList(flag, refreshflag) { getBookList(flag, refreshflag) {
this.isLoadingHide = false; this.isLoadingHide = false;
@@ -461,6 +521,8 @@
// * 3已完成 // * 3已完成
// * 4: 交易失败 // * 4: 交易失败
// * 5: 已过期 // * 5: 已过期
// * 6: 已退款
// * 7: 退款中
that.map = res.data; that.map = res.data;
that.ordersTabs.map((e) => { that.ordersTabs.map((e) => {
@@ -489,7 +551,7 @@
var params = { var params = {
userId: this.userInfo.id, userId: this.userInfo.id,
come: this.come, come: this.come,
orderStatus: flag == -1 ? "" : flag, //传null为全部订单状态 0-未付款 1-待发出 2-待收到 3-交易成功 4-交易失败 5-过期 orderStatus: flag == -1 ? "" : flag, //传null为全部订单状态 0-未付款 1-待发出 2-待收到 3-交易成功 4-交易失败 5-过期 6-已退款 7-退款中
...this.pagination ...this.pagination
} }
this.$http.request({ this.$http.request({
@@ -522,8 +584,26 @@
}); });
this.axiosStatus = 1 this.axiosStatus = 1
}, },
checkPayTimeout(payItem) {
const createTime = payItem && payItem.createTime;
if (!createTime) return true;
const createdAt = new Date(String(createTime).replace(/-/g, "/")).getTime();
if (!createdAt) return true;
const expired = Date.now() - createdAt > 10 * 60 * 1000;
if (expired) {
uni.showModal({
title: "提示",
content: "订单已超时,不能继续支付,请重新下单",
confirmText: "知道了",
showCancel: false,
});
return false;
}
return true;
},
// 支付 // 支付
goPay(payItem) { goPay(payItem) {
if (!this.checkPayTimeout(payItem)) return;
if (payItem.paymentMethod == 2) { if (payItem.paymentMethod == 2) {
console.log("阿里支付"); console.log("阿里支付");
setPay({ setPay({
@@ -982,9 +1062,9 @@
width: 100%; width: 100%;
} }
/deep/.u-tabs__wrapper__nav__item { // /deep/.u-tabs__wrapper__nav__item {
padding: 0 !important; // padding: 0 !important;
} // }
.order_box { .order_box {
padding: 20rpx; padding: 20rpx;
} }

View File

@@ -828,8 +828,6 @@ export default {
color: $themeColor; color: $themeColor;
} }
.emaPho {}
.emaPho>view { .emaPho>view {
display: inline-block; display: inline-block;
padding: 10rpx 0; padding: 10rpx 0;
@@ -904,7 +902,8 @@ export default {
border-radius: 50rpx; border-radius: 50rpx;
&.active { &.active {
@include theme("btn_bg") color: #fff; @include theme("btn_bg");
color: #fff;
} }
} }
} }

View File

@@ -39,9 +39,9 @@
</view> </view>
<view class="vip_qx"> <view class="vip_qx">
<text class="font_bold" style=" display: block;">VIP权限</text> <text class="font_bold" style=" display: block;">VIP权限</text>
<view class="vip_qx_v" v-if="item.type==2">无限制观看众妙之门APP与心灵空间APP任意课程</view> <view class="vip_qx_v" v-if="item.type==2">无限制观看众妙之门APP与心灵空间APP任意课程不包含论坛</view>
<view class="vip_qx_v" v-else-if="item.type==7">无限制观看众妙之门APP任意课程</view> <view class="vip_qx_v" v-else-if="item.type==7">无限制观看众妙之门APP任意课程不包含论坛</view>
<view class="vip_qx_v" v-else>无限制观看心灵空间APP任意课程</view> <view class="vip_qx_v" v-else>无限制观看心灵空间APP任意课程不包含论坛</view>
<view class="vip_qx_v"> <view class="vip_qx_v">
<text v-if="item.type==1||item.type==2">{{item.title.replace(/超级VIP/g, '')}}</text> <text v-if="item.type==1||item.type==2">{{item.title.replace(/超级VIP/g, '')}}</text>

View File

@@ -37,8 +37,7 @@
<view class="label_content AC_List"> <view class="label_content AC_List">
<view style=" display: flex; align-items: center; justify-content: space-between;"> <view style=" display: flex; align-items: center; justify-content: space-between;">
<view class="left"> <view class="left">
<view class="title" v-if="slotProps.row.orderType=='购买商品'&&slotProps.row.productName">{{ slotProps.row.orderType }} <br/> {{ slotProps.row.productName }}</view> <view class="title">{{ slotProps.row.productName || slotProps.row.orderType}}</view>
<view class="title" v-else>{{ slotProps.row.orderType }}</view>
</view> </view>
<view class="right Hot"> <view class="right Hot">
<text v-if="slotProps.row.changeAmount > 0">+</text> <text v-if="slotProps.row.changeAmount > 0">+</text>
@@ -211,6 +210,12 @@ export default {
.AC_List { .AC_List {
overflow: hidden; overflow: hidden;
.title,
.AC_note,
.AC_mark {
word-break: break-word;
overflow-wrap: break-word;
}
.left { .left {
width: calc(100% - 140rpx) !important; width: calc(100% - 140rpx) !important;
font-weight: 700; font-weight: 700;
@@ -230,6 +235,19 @@ export default {
font-weight: 700; font-weight: 700;
color: #333; color: #333;
} }
> view:first-child {
.left {
flex: 1;
min-width: 0;
width: auto !important;
float: none;
}
.right {
flex-shrink: 0;
float: none;
width: auto !important;
}
}
.AC_title { .AC_title {
font-size: 32rpx; font-size: 32rpx;

View File

@@ -363,3 +363,7 @@ button::after {
} }
} }
} }
uni-text {
white-space: normal;
}

3
style/mixin.scss Normal file
View File

@@ -0,0 +1,3 @@
@charset "utf-8";
//主题色
$themeColor: #7dc1f0;

View File

@@ -0,0 +1,14 @@
## 1.0.62024-11-22
- 修复 HarmonyOS Next 上调用 setUserCaptureScreen 报错的 Bug
## 1.0.52024-10-14
- 新增 支持 HarmonyOS Next 调用
## 1.0.42023-03-24
新增开启/关闭防截屏功能
## 1.0.32023-03-17
修复android平台 部分场景下js可能报错的问题
## 1.0.22023-03-16
修复Android平台在小米设备无法监听的问题 修复Android平台调用uni.onUserCaptureScreen必然会触发回调的问题
## 1.0.12022-10-27
修改插件描述
## 1.0.02022-10-26
支持安卓、iOS、微信小程序平台

View File

@@ -0,0 +1,157 @@
declare namespace UniNamespace {
/**
* uni.onUserCaptureScreen/uni.offUserCaptureScreen回调参数
*/
type OnUserCaptureScreenCallbackResult = {
/**
* 截屏文件路径仅Android返回
*/
path ?: string
}
/**
* uni.onUserCaptureScreen/uni.offUserCaptureScreen回调函数定义
*/
type UserCaptureScreenCallback = (res : OnUserCaptureScreenCallbackResult) => void
type OnUserCaptureScreen = (callback : UserCaptureScreenCallback | null) => void
type OffUserCaptureScreen = (callback : UserCaptureScreenCallback | null) => void
/**
* uni.setUserCaptureScreen成功回调参数
*/
type SetUserCaptureScreenSuccess = {
}
/**
* uni.setUserCaptureScreen成功回调函数定义
*/
type SetUserCaptureScreenSuccessCallback = (res : SetUserCaptureScreenSuccess) => void
/**
* 错误码
* - 12001 "setUserCaptureScreen:system not support"
* - 12010 "setUserCaptureScreen:system internal error"
*/
type SetUserCaptureScreenErrorCode = 12001 | 12010;
/**
* SetUserCaptureScreen 的错误回调参数
*/
interface SetUserCaptureScreenFail {
errCode : SetUserCaptureScreenErrorCode
}
/**
* uni.setUserCaptureScreen失败回调函数定义
*/
type SetUserCaptureScreenFailCallback = (res : SetUserCaptureScreenFail) => void
/**
* uni.setUserCaptureScreen完成回调函数定义
*/
type SetUserCaptureScreenCompleteCallback = (res : any) => void
/**
* uni.setUserCaptureScreen参数
*/
type SetUserCaptureScreenOptions = {
/**
* true: 允许用户截屏 false: 不允许用户截屏,防止用户截屏到应用页面内容
*/
enable : boolean;
/**
* 接口调用成功的回调函数
*/
// success : SetUserCaptureScreenSuccessCallback | null,
success ?: SetUserCaptureScreenSuccessCallback,
/**
* 接口调用失败的回调函数
*/
// fail : SetUserCaptureScreenFailCallback | null,
fail ?: SetUserCaptureScreenFailCallback,
/**
* 接口调用结束的回调函数(调用成功、失败都会执行)
*/
// complete : SetUserCaptureScreenSuccessCallback | SetUserCaptureScreenFailCallback | null
complete ?: SetUserCaptureScreenCompleteCallback
}
type SetUserCaptureScreen = (options : SetUserCaptureScreenOptions) => void
}
declare interface Uni {
/**
* 开启截屏监听
*
* @param {UserCaptureScreenCallback} callback
* @tutorial https://uniapp.dcloud.net.cn/api/system/capture-screen.html#onusercapturescreen
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4.4",
* "uniVer": "3.7.7",
* "unixVer": "3.9.0"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "3.7.7",
* "unixVer": "x"
* }
* }
* }
* @uniVersion 3.7.7
* @uniVueVersion 2,3 //支持的vue版本
* @autotest { expectCallback: true }
*/
onUserCaptureScreen(callback : UniNamespace.UserCaptureScreenCallback | null) : void,
/**
* 关闭截屏监听
*
* @param {UserCaptureScreenCallback} callback
* @tutorial https://uniapp.dcloud.net.cn/api/system/capture-screen.html#offusercapturescreen
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4.4",
* "uniVer": "3.7.7",
* "unixVer": "3.9.0"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "3.7.7",
* "unixVer": "x"
* }
* }
* }
* @uniVersion 3.7.7
* @uniVueVersion 2,3 //支持的vue版本
* @autotest { expectCallback: true }
*/
offUserCaptureScreen(callback : UniNamespace.UserCaptureScreenCallback | null) : void,
/**
* 设置防截屏
*
* @param {SetUserCaptureScreenOptions} options
* @tutorial https://uniapp.dcloud.net.cn/api/system/capture-screen.html#setusercapturescreen
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4.4",
* "uniVer": "3.7.7",
* "unixVer": "3.9.0"
* },
* "ios": {
* "osVer": "13.0",
* "uniVer": "3.7.7",
* "unixVer": "x"
* }
* }
* }
* @uniVersion 3.7.7
* @uniVueVersion 2,3 //支持的vue版本
*/
setUserCaptureScreen(options : UniNamespace.SetUserCaptureScreenOptions) : void
}

View File

@@ -0,0 +1,97 @@
{
"id": "uni-usercapturescreen",
"displayName": "uni-usercapturescreen",
"version": "1.0.6",
"description": "用户主动截屏事件监听",
"keywords": [
"截屏"
],
"repository": "",
"engines": {
"HBuilderX": "^3.7.7"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"uni-ext-api":{
"uni": {
"onUserCaptureScreen": {
"web": false
},
"offUserCaptureScreen": {
"web": false
},
"setUserCaptureScreen": {
"web": false,
"mp-weixin": false
}
}
},
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-android": "y",
"app-ios": "y",
"app-harmony": "y"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "y",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
# uni-usercapturescreen
用户主动截屏事件监听
### uni.onUserCaptureScreen
监听用户主动截屏事件,用户使用系统截屏按键截屏时触发此事件。
> 使用文档:[https://uniapp.dcloud.net.cn/api/system/capture-screen.html#onusercapturescreen](https://uniapp.dcloud.net.cn/api/system/capture-screen.html#onusercapturescreen)
### uni.offUserCaptureScreen
用户主动截屏事件。取消事件监听。
> 使用文档:[https://uniapp.dcloud.net.cn/api/system/capture-screen.html#offusercapturescreen](https://uniapp.dcloud.net.cn/api/system/capture-screen.html#offusercapturescreen)
### uni.setUserCaptureScreen
开启/关闭防截屏。
> 使用文档:[https://uniapp.dcloud.net.cn/api/system/capture-screen.html#setusercapturescreen](https://uniapp.dcloud.net.cn/api/system/capture-screen.html#setusercapturescreen)

View File

@@ -0,0 +1,139 @@
import { UTSAndroid } from "io.dcloud.uts";
import ActivityCompat from "androidx.core.app.ActivityCompat";
import Manifest from "android.Manifest";
import PackageManager from "android.content.pm.PackageManager";
import Build from "android.os.Build";
import FileObserver from "android.os.FileObserver";
import File from "java.io.File";
import Environment from "android.os.Environment";
import System from 'java.lang.System';
import WindowManager from 'android.view.WindowManager';
import { OnUserCaptureScreenCallbackResult, UserCaptureScreenCallback, OnUserCaptureScreen, OffUserCaptureScreen, SetUserCaptureScreenSuccess, SetUserCaptureScreenOptions, SetUserCaptureScreen } from "../interface.uts";
import string from 'android.R.string';
/**
* 文件监听器
*/
let observer : ScreenFileObserver | null = null;
/**
* 记录文件监听器上次监听的时间戳,避免重复监听
*/
let lastObserverTime : number = 0;
/**
* 截屏回调
*/
let listener : UserCaptureScreenCallback | null = null;
/**
* android 文件监听实现
*/
class ScreenFileObserver extends FileObserver {
/**
* 截屏文件目录
*/
private screenFile : File;
constructor(screenFileStr : string) {
super(screenFileStr);
this.screenFile = new File(screenFileStr);
}
override onEvent(event : Int, path : string | null) : void {
// 只监听文件新增事件
if (event == FileObserver.CREATE) {
if (path != null) {
const currentTime = System.currentTimeMillis();
if ((currentTime - lastObserverTime) < 1000) {
// 本地截屏行为比上一次超过1000ms, 才认为是一个有效的时间
return;
}
lastObserverTime = currentTime;
const screenShotPath = new File(this.screenFile, path).getPath();
const res : OnUserCaptureScreenCallbackResult = {
path: screenShotPath
}
listener?.(res);
}
}
}
}
/**
* 开启截图监听
*/
export const onUserCaptureScreen : OnUserCaptureScreen = function (callback : UserCaptureScreenCallback | null) {
// 检查相关权限是否已授予
if (ActivityCompat.checkSelfPermission(UTSAndroid.getAppContext()!, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 无权限,申请权限
ActivityCompat.requestPermissions(UTSAndroid.getUniActivity()!, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1001);
return;
}
// 更新监听
listener = callback;
let directory_screenshot : File;
if (Build.MANUFACTURER.toLowerCase() == "xiaomi") {
// @Suppress("DEPRECATION")
directory_screenshot = new File(new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DCIM), "Screenshots");
} else {
// @Suppress("DEPRECATION")
directory_screenshot = new File(new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_PICTURES), "Screenshots");
}
// 先结束监听 再开启监听
observer?.stopWatching();
observer = new ScreenFileObserver(directory_screenshot.getPath());
observer?.startWatching();
UTSAndroid.onAppActivityDestroy(function(){
observer?.stopWatching()
observer = null
})
}
/**
* 关闭截屏监听
*/
export const offUserCaptureScreen : OffUserCaptureScreen = function (_ : UserCaptureScreenCallback | null) {
// android10以上关闭监听通过移除文件监听器实现
observer?.stopWatching();
observer = null;
lastObserverTime = 0;
}
/**
* 设置是否禁止截屏
*/
export const setUserCaptureScreen : SetUserCaptureScreen = function (option : SetUserCaptureScreenOptions) {
// 切换到UI线程
UTSAndroid.getUniActivity()?.runOnUiThread(new SetUserCaptureScreenRunnable(option.enable));
const res : SetUserCaptureScreenSuccess = {}
option.success?.(res);
option.complete?.(res);
}
class SetUserCaptureScreenRunnable extends Runnable {
/**
* ture: 允许用户截屏
* false: 不允许用户截屏,防止用户截屏到应用页面内容
*/
private enable : boolean;
constructor(enable : boolean) {
super();
this.enable = enable;
}
override run() : void {
if (this.enable) {
UTSAndroid.getUniActivity()?.getWindow()?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
UTSAndroid.getUniActivity()?.getWindow()?.addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
}

View File

@@ -0,0 +1,87 @@
import { display } from '@kit.ArkUI';
import { window } from '@kit.ArkUI';
import { Callback, BusinessError } from '@kit.BasicServicesKit';
import { getAbilityContext } from '@dcloudio/uni-runtime';
import {
OnUserCaptureScreen, UserCaptureScreenCallback,
OffUserCaptureScreen,
SetUserCaptureScreen, SetUserCaptureScreenOptions, SetUserCaptureScreenSuccess
} from '../interface.uts';
const onUserCaptureScreenCallbacks: Function[] = []
const harmonyCaptureStatusChange: Callback<boolean> = (captureStatus: boolean) => {
if (captureStatus) {
onUserCaptureScreenCallbacks.forEach(cb => {
typeof cb === 'function' && cb()
})
}
}
display.on('captureStatusChange', harmonyCaptureStatusChange)
export const onUserCaptureScreen: OnUserCaptureScreen = function (callback: UserCaptureScreenCallback | null) {
if (callback) {
onUserCaptureScreenCallbacks.push(callback)
}
}
export const offUserCaptureScreen: OffUserCaptureScreen = function (callback: UserCaptureScreenCallback | null) {
if (callback) {
const index = onUserCaptureScreenCallbacks.indexOf(callback)
if (index > -1) {
onUserCaptureScreenCallbacks.splice(index, 1)
}
}
}
export const setUserCaptureScreen: SetUserCaptureScreen = function (options: SetUserCaptureScreenOptions) {
const errSubject = 'uni-usercapturescreen'
const setUserCaptureScreenSuccess: SetUserCaptureScreenSuccess = {}
window.getLastWindow(getAbilityContext()!, (err, window) => {
const errCode: number = err.code;
if (errCode) {
options.fail?.({
errCode: (err as BusinessError).code,
errSubject,
errMsg: `setUserCaptureScreen:fail ${(err as BusinessError).message}`
} as IUniError)
options.complete?.(setUserCaptureScreenSuccess);
return;
} else {
try {
UTSHarmony.requestSystemPermission(['ohos.permission.PRIVACY_WINDOW'], (allRight: boolean, _grantedList: string[]) => {
if (allRight) {
window.setWindowPrivacyMode(!options.enable, (err: BusinessError) => {
const errCode: number = err.code;
if (errCode) {
options.fail?.({
errCode: err.code,
errSubject,
errMsg: `setUserCaptureScreen:fail ${err.message}`
} as IUniError)
options.complete?.(setUserCaptureScreenSuccess);
return;
}
options.success?.(setUserCaptureScreenSuccess);
options.complete?.(setUserCaptureScreenSuccess);
});
} else {
throw new Error('permission denied')
}
}, (_doNotAskAgain: boolean, _grantedList: string[]) => {
throw new Error('permission denied');
})
} catch (err) {
options.fail?.({
errCode: (err as BusinessError).code,
errSubject,
errMsg: `setUserCaptureScreen:fail ${(err as BusinessError).message}`
} as IUniError)
options.complete?.(setUserCaptureScreenSuccess);
}
}
})
}

View File

@@ -0,0 +1,141 @@
import { NotificationCenter } from 'Foundation';
import { CGRect } from "CoreFoundation";
import { UIApplication, UIView, UITextField, UIScreen, UIDevice } from "UIKit"
import { UTSiOS } from "DCloudUTSFoundation"
import { DispatchQueue } from 'Dispatch';
import { SetUserCaptureScreenOptions, OnUserCaptureScreenCallbackResult, OnUserCaptureScreen, OffUserCaptureScreen, SetUserCaptureScreen, UserCaptureScreenCallback, SetUserCaptureScreenSuccess } from "../interface.uts"
import { SetUserCaptureScreenFailImpl } from "../unierror.uts"
/**
* 定义监听截屏事件工具类
*/
class CaptureScreenTool {
static listener : UserCaptureScreenCallback | null;
static secureView : UIView | null;
// 监听截屏
static listenCaptureScreen(callback : UserCaptureScreenCallback | null) {
this.listener = callback
// 注册监听截屏事件及回调方法
// target-action 回调方法需要通过 Selector("方法名") 构建
const method = Selector("userDidTakeScreenshot")
NotificationCenter.default.addObserver(this, selector = method, name = UIApplication.userDidTakeScreenshotNotification, object = null)
}
// 捕获截屏回调的方法
// target-action 的方法前需要添加 @objc 前缀
@objc static userDidTakeScreenshot() {
// 回调
const res: OnUserCaptureScreenCallbackResult = {
}
this.listener?.(res)
}
// 移除监听事件
static removeListen(callback : UserCaptureScreenCallback | null) {
this.listener = null
NotificationCenter.default.removeObserver(this)
}
static createSecureView() : UIView | null {
let field = new UITextField(frame = CGRect.zero)
field.isSecureTextEntry = true
if (field.subviews.length > 0 && UIDevice.current.systemVersion != '15.1') {
let view = field.subviews[0]
view.subviews.forEach((item) => {
item.removeFromSuperview()
})
view.isUserInteractionEnabled = true
return view
}
return null
}
// 开启防截屏
static onAntiScreenshot(option : SetUserCaptureScreenOptions) {
// uts方法默认会在子线程中执行涉及 UI 操作必须在主线程中运行,通过 DispatchQueue.main.async 方法可将代码在主线程中运行
DispatchQueue.main.async(execute = () : void => {
let secureView = this.createSecureView()
let window = UTSiOS.getKeyWindow()
let rootView = window.rootViewController == null ? null : window.rootViewController!.view
if (secureView != null && rootView != null) {
let rootSuperview = rootView!.superview
if (rootSuperview != null) {
this.secureView = secureView
rootSuperview!.addSubview(secureView!)
rootView!.removeFromSuperview()
secureView!.addSubview(rootView!)
// secureView 充满父视图并随父视图宽高自适应(横竖屏、全屏切换时自动跟随)
secureView!.frame = rootSuperview!.bounds
secureView!.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
// rootView 充满 secureView 并自适应,避免全屏时被固定在竖屏尺寸而缩到角落
rootView!.frame = secureView!.bounds
rootView!.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
}
}
let res: SetUserCaptureScreenSuccess = {
}
option.success?.(res)
option.complete?.(res)
})
}
// 关闭防截屏
static offAntiScreenshot(option : SetUserCaptureScreenOptions) {
DispatchQueue.main.async(execute = () : void => {
if (this.secureView != null) {
let window = UTSiOS.getKeyWindow()
let rootView = window.rootViewController == null ? null : window.rootViewController!.view
if (rootView != null && this.secureView!.superview != null) {
let rootSuperview = this.secureView!.superview
if (rootSuperview != null) {
rootSuperview!.addSubview(rootView!)
this.secureView!.removeFromSuperview()
}
}
this.secureView = null
}
let res: SetUserCaptureScreenSuccess = {
}
option.success?.(res)
option.complete?.(res)
})
}
}
/**
* 开启截图监听
*/
export const onUserCaptureScreen : OnUserCaptureScreen = function (callback : UserCaptureScreenCallback | null) {
CaptureScreenTool.listenCaptureScreen(callback)
}
/**
* 关闭截屏监听
*/
export const offUserCaptureScreen : OffUserCaptureScreen = function (callback : UserCaptureScreenCallback | null) {
CaptureScreenTool.removeListen(callback)
}
/**
* 开启/关闭防截屏
*/
export const setUserCaptureScreen : SetUserCaptureScreen = function (options : SetUserCaptureScreenOptions) {
if (UIDevice.current.systemVersion < "13.0") {
let res = new SetUserCaptureScreenFailImpl(12001)
options.fail?.(res);
options.complete?.(res);
} else if (UIDevice.current.systemVersion == "15.1") {
let res = new SetUserCaptureScreenFailImpl(12010)
options.fail?.(res);
options.complete?.(res);
} else {
if (options.enable == true) {
CaptureScreenTool.offAntiScreenshot(options)
} else {
CaptureScreenTool.onAntiScreenshot(options)
}
}
}

View File

@@ -0,0 +1,170 @@
/**
* uni.onUserCaptureScreen/uni.offUserCaptureScreen回调参数
*/
export type OnUserCaptureScreenCallbackResult = {
/**
* 截屏文件路径仅Android返回
*/
path ?: string
}
/**
* uni.onUserCaptureScreen/uni.offUserCaptureScreen回调函数定义
*/
export type UserCaptureScreenCallback = (res : OnUserCaptureScreenCallbackResult) => void
export type OnUserCaptureScreen = (callback : UserCaptureScreenCallback | null) => void
export type OffUserCaptureScreen = (callback : UserCaptureScreenCallback | null) => void
/**
* uni.setUserCaptureScreen成功回调参数
*/
export type SetUserCaptureScreenSuccess = {
}
/**
* uni.setUserCaptureScreen成功回调函数定义
*/
export type SetUserCaptureScreenSuccessCallback = (res : SetUserCaptureScreenSuccess) => void
/**
* uni.setUserCaptureScreen失败回调函数定义
*/
export type SetUserCaptureScreenFailCallback = (res : IUniError) => void
/**
* uni.setUserCaptureScreen完成回调函数定义
*/
export type SetUserCaptureScreenCompleteCallback = (res : any) => void
/**
* uni.setUserCaptureScreen参数
*/
export type SetUserCaptureScreenOptions = {
/**
* true: 允许用户截屏 false: 不允许用户截屏,防止用户截屏到应用页面内容
*/
enable : boolean;
/**
* 接口调用成功的回调函数
*/
// success : SetUserCaptureScreenSuccessCallback | null,
success ?: SetUserCaptureScreenSuccessCallback,
/**
* 接口调用失败的回调函数
*/
// fail : SetUserCaptureScreenFailCallback | null,
fail ?: SetUserCaptureScreenFailCallback,
/**
* 接口调用结束的回调函数(调用成功、失败都会执行)
*/
// complete : SetUserCaptureScreenSuccessCallback | SetUserCaptureScreenFailCallback | null
complete ?: SetUserCaptureScreenCompleteCallback
}
/**
* 错误码
* - 12001 "setUserCaptureScreen:system not support"
* - 12010 "setUserCaptureScreen:system internal error"
*/
export type SetUserCaptureScreenErrorCode = 12001 | 12010;
/**
* SetUserCaptureScreen 的错误回调参数
*/
export interface SetUserCaptureScreenFail extends IUniError {
errCode : SetUserCaptureScreenErrorCode
};
export type SetUserCaptureScreen = (options : SetUserCaptureScreenOptions) => void
export interface Uni {
/**
* 开启截屏监听
*
* @param {UserCaptureScreenCallback} callback
* @tutorial https://uniapp.dcloud.net.cn/api/system/capture-screen.html#onusercapturescreen
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4.4",
* "uniVer": "3.7.7",
* "unixVer": "3.9.0"
* },
* "ios": {
* "osVer": "12.0",
* "uniVer": "3.7.7",
* "unixVer": "4.11"
* },
* "harmony": {
* "osVer": "3.0",
* "uniVer": "4.25",
* "unixVer": "x"
* }
* }
* }
* @uniVersion 3.7.7
* @uniVueVersion 2,3 //支持的vue版本
* @autotest { expectCallback: true }
*/
onUserCaptureScreen(callback : UserCaptureScreenCallback | null) : void,
/**
* 关闭截屏监听
*
* @param {UserCaptureScreenCallback} callback
* @tutorial https://uniapp.dcloud.net.cn/api/system/capture-screen.html#offusercapturescreen
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4.4",
* "uniVer": "3.7.7",
* "unixVer": "3.9.0"
* },
* "ios": {
* "osVer": "12.0",
* "uniVer": "3.7.7",
* "unixVer": "4.11"
* },
* "harmony": {
* "osVer": "3.0",
* "uniVer": "4.25",,
* "unixVer": "x"
* }
* }
* }
* @uniVersion 3.7.7
* @uniVueVersion 2,3 //支持的vue版本
* @autotest { expectCallback: true }
*/
offUserCaptureScreen(callback : UserCaptureScreenCallback | null) : void,
/**
* 设置防截屏
*
* @param {SetUserCaptureScreenOptions} options
* @tutorial https://uniapp.dcloud.net.cn/api/system/capture-screen.html#setusercapturescreen
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4.4",
* "uniVer": "3.7.7",
* "unixVer": "3.9.0"
* },
* "ios": {
* "osVer": "13.0",
* "uniVer": "3.7.7",
* "unixVer": "4.11"
* },
* "harmony": {
* "osVer": "3.0",
* "uniVer": "4.25",
* "unixVer": "x"
* }
* }
* }
* @uniVersion 3.7.7
* @uniVueVersion 2,3 //支持的vue版本
*/
setUserCaptureScreen(options : SetUserCaptureScreenOptions) : void
}

View File

@@ -0,0 +1,7 @@
export function onUserCaptureScreen (callback) {
return wx.onUserCaptureScreen(callback)
}
export function offUserCaptureScreen (callback) {
return wx.offUserCaptureScreen(callback)
}

View File

@@ -0,0 +1,35 @@
import { SetUserCaptureScreenErrorCode, SetUserCaptureScreenFail } from "./interface.uts"
/**
* 错误主题
*/
export const UniErrorSubject = 'uni-usercapturescreen';
/**
* 错误信息
* @UniError
*/
export const UniErrors : Map<SetUserCaptureScreenErrorCode, string> = new Map([
/**
* 错误码及对应的错误信息
*/
[12001, 'setUserCaptureScreen:system not support'],
[12010, 'setUserCaptureScreen:system internal error'],
]);
/**
* 错误对象实现
*/
export class SetUserCaptureScreenFailImpl extends UniError implements SetUserCaptureScreenFail {
/**
* 错误对象构造函数
*/
constructor(errCode : SetUserCaptureScreenErrorCode) {
super();
this.errSubject = UniErrorSubject;
this.errCode = errCode;
this.errMsg = UniErrors[errCode] ?? "";
}
}