feat: 增加视频水印和防盗录

This commit is contained in:
2026-06-09 17:22:21 +08:00
parent b78ad9a854
commit a83e021cd7
911 changed files with 1102 additions and 23532 deletions

View File

@@ -22,6 +22,11 @@ export default {
platform: null, // 系统 platform: null, // 系统
}; };
}, },
onShow: function() {
// #ifdef APP-PLUS
// screenshot.onAntiScreenshot()
// #endif
},
onLaunch: function (e) { onLaunch: function (e) {
// 检测自动更新 // 检测自动更新
// #ifdef APP-PLUS // #ifdef APP-PLUS
@@ -88,7 +93,7 @@ export default {
success(res) { success(res) {
Vue.prototype.winWidth = res.screenWidth; Vue.prototype.winWidth = res.screenWidth;
Vue.prototype.winHeight = res.screenHeight; Vue.prototype.winHeight = res.screenHeight;
Vue.prototype.statusBarHeight = res.statusBarHeight; Vue.prototype.statusBarHeight = res.statusBarHeight || 0;
}, },
}); });
@@ -246,7 +251,6 @@ export default {
}); });
} }
}, },
}, },
}; };
</script> </script>

View File

@@ -12,8 +12,8 @@
"src" : "图片路径" "src" : "图片路径"
} }
], ],
"versionName" : "2.0.51", "versionName" : "2.0.52",
"versionCode" : 2051, "versionCode" : 2052,
"sassImplementationName" : "node-sass", "sassImplementationName" : "node-sass",
"app-plus" : { "app-plus" : {
"nvueCompiler" : "uni-app", "nvueCompiler" : "uni-app",

20
package-lock.json generated
View File

@@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"e-peanut": "file:", "e-peanut": "file:",
"edu-core": "file:../edu-core", "edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14",
"element-plus": "^2.9.6", "element-plus": "^2.9.6",
"epubjs": "^0.3.93", "epubjs": "^0.3.93",
"jquery": "^2.2.4", "jquery": "^2.2.4",
@@ -23,11 +23,6 @@
" ../edu-core": { " ../edu-core": {
"extraneous": true "extraneous": true
}, },
"../edu-core": {
"version": "1.0.13",
"license": "ISC",
"devDependencies": {}
},
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -440,8 +435,9 @@
"link": true "link": true
}, },
"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/element-plus": { "node_modules/element-plus": {
"version": "2.11.5", "version": "2.11.5",
@@ -4177,7 +4173,7 @@
"requires": { "requires": {
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"e-peanut": "file:", "e-peanut": "file:",
"edu-core": "file:../edu-core", "edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14",
"element-plus": "^2.9.6", "element-plus": "^2.9.6",
"epubjs": "^0.3.93", "epubjs": "^0.3.93",
"jquery": "^2.2.4", "jquery": "^2.2.4",
@@ -4498,7 +4494,8 @@
} }
}, },
"edu-core": { "edu-core": {
"version": "file:../edu-core" "version": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#eb9eccdc3d281fe84127a93e019650512859ff0c",
"from": "edu-core@git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14"
}, },
"element-plus": { "element-plus": {
"version": "2.11.5", "version": "2.11.5",
@@ -7259,7 +7256,8 @@
} }
}, },
"edu-core": { "edu-core": {
"version": "file:../edu-core" "version": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#eb9eccdc3d281fe84127a93e019650512859ff0c",
"from": "edu-core@git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14"
}, },
"element-plus": { "element-plus": {
"version": "2.11.5", "version": "2.11.5",

View File

@@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"e-peanut": "file:", "e-peanut": "file:",
"edu-core": "file:../edu-core", "edu-core": "git+https://git.nuttyreading.com/chenghuan/edu-core.git#v1.0.14",
"element-plus": "^2.9.6", "element-plus": "^2.9.6",
"epubjs": "^0.3.93", "epubjs": "^0.3.93",
"jquery": "^2.2.4", "jquery": "^2.2.4",

View File

@@ -3,6 +3,12 @@
// "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" // "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
// }, // },
"pages": [ "pages": [
// {
// "path": "pages/test/test",
// "style": {
// "navigationBarTitleText": "测试页"
// }
// },
{ {
"path": "pages/peanut/home", "path": "pages/peanut/home",
"style": { "style": {
@@ -813,7 +819,6 @@
"navigationBarTitleText" : "数据迁移" "navigationBarTitleText" : "数据迁移"
} }
} }
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",

View File

@@ -20,8 +20,7 @@
:course="{courseTitle:options.navTitle, chapterTitle: curriculumData.title, catalogueId: curriculumData.catalogueId || '', courseId: curriculumData.courseId || options.courseId || ''}" :course="{courseTitle:options.navTitle, chapterTitle: curriculumData.title, catalogueId: curriculumData.catalogueId || '', courseId: curriculumData.courseId || options.courseId || ''}"
:cover="options.curriculumImgUrl || ''" :cover="options.curriculumImgUrl || ''"
:http="$http" :http="$http"
:user-id="watermarkUserId" :user-info="userInfo"
:mobile="watermarkMobile"
/> />
<view class="" style="border-top: 2px solid #2979ff;"> <view class="" style="border-top: 2px solid #2979ff;">
@@ -173,6 +172,7 @@
import { import {
mapState mapState
} from "vuex"; } from "vuex";
export default { export default {
components: { components: {
courseDescription, //课程说明 courseDescription, //课程说明
@@ -332,7 +332,6 @@
}, },
onHide() { onHide() {
console.log('回去了 .....................'); console.log('回去了 .....................');
}, },
onPullDownRefresh() { onPullDownRefresh() {
uni.stopPullDownRefresh(); uni.stopPullDownRefresh();
@@ -344,12 +343,6 @@
}, },
computed: { computed: {
...mapState(["userInfo"]), ...mapState(["userInfo"]),
watermarkUserId() {
return this.userInfo.id || this.userInfo.userId || this.userInfo.uid || ''
},
watermarkMobile() {
return this.userInfo.phone || this.userInfo.tel || this.userInfo.mobile || ''
},
}, },
methods: { methods: {
editorIput(e){ editorIput(e){

79
pages/test/test.vue Normal file
View File

@@ -0,0 +1,79 @@
<template>
<view class="content">
<view class="btn" @click="onAntiScreenshot"><text>开启防截屏</text></view>
<view class="btn" @click="onAntiScreenshot2"><text>开启防截屏</text></view>
<view class="btn" @click="offAntiScreenshot"><text>关闭防截屏</text></view>
<view class="btn" @click="offAntiScreenshot2"><text>关闭防截屏</text></view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
onAntiScreenshot() {
console.log('点击了开启按钮')
// screenshot.onAntiScreenshot()
// screenGuard.onAntiScreenshot()
uni.setUserCaptureScreen({
enable: false,
success() {
console.log('截屏录屏功能已禁用');
},
fail(err) {
console.error('禁用截屏录屏功能失败', err);
}
});
},
offAntiScreenshot() {
console.log('点击了关闭按钮')
// screenshot.offAntiScreenshot()
// screenGuard.offAntiScreenshot()
uni.setUserCaptureScreen({
enable: true,
success() {
console.log('截屏录屏功能已启用');
},
fail(err) {
console.error('启用截屏录屏功能失败', err);
}
})
},
onAntiScreenshot2() {
console.log('点击了开启按钮')
// screenshot.onAntiScreenshot()
screenGuard.onAntiScreenshot()
},
offAntiScreenshot2() {
console.log('点击了关闭按钮')
// screenshot.offAntiScreenshot()
screenGuard.offAntiScreenshot()
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.btn {
display: flex;
align-items: center;
justify-content: center;
background-color: antiquewhite;
border-radius: 8rpx;
width: 690rpx;
height: 80rpx;
margin-top: 40rpx;
}
</style>

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] ?? "";
}
}

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var __UniViewStartTime__ = Date.now();
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title>View</title>
<link rel="stylesheet" href="view.css" />
</head>
<body>
<div id="app"></div>
<script src="__uniappes6.js"></script>
<script src="view.umd.min.js"></script>
<script src="app-view.js"></script>
</body>
</html>

View File

@@ -1,3 +0,0 @@
{
"prompt" : "template"
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(function(e){function r(r){for(var n,l,i=r[0],p=r[1],a=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in p)Object.prototype.hasOwnProperty.call(p,n)&&(e[n]=p[n]);f&&f(r);while(s.length)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var p=t[i];0!==o[p]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={"app-config":0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e["default"]}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonp"]=this["webpackJsonp"]||[],p=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var f=p;t()})([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__48F690A","name":"E-peanut","version":{"name":"1.1.0","code":110},"description":"E-peanut-案例","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"Payment":{},"OAuth":{},"Messaging":{},"VideoPlayer":{},"Share":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"autoclose":false,"waiting":true},"popGesture":"close","launchwebview":{"id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#FFFFFF"},"compatible":{"ignoreVersion":true},"privacy":{"prompt":"template","template":{"title":"用户协议和隐私政策","message":"请你务必审慎阅读、充分理解“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href='https://www.twin-ui.com'>《用户协议》</a>和<a href='https://www.twin-ui.com'>《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。","buttonAccept":"同意","buttonRefuse":"暂不同意"}},"compilerVersion":3,"allowsInlineMediaPlayback":true,"safearea":{"background":"#ffffff","bottom":{"offset":"auto"}},"uni-app":{"compilerVersion":"3.5.3","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"fast"},"tabBar":{"color":"#444444","selectedColor":"#079307","borderStyle":"rgba(0,0,0,0.4)","backgroundColor":"#ffffff","list":[{"pagePath":"pages/peanut/home","iconPath":"static/icon/tab/icon1_n.png","selectedIconPath":"static/icon/tab/icon1_y.png","text":"首页"},{"pagePath":"pages/peanut/shopping","iconPath":"static/icon/tab/icon2_n.png","selectedIconPath":"static/icon/tab/icon2_y.png","text":"购物车"},{"pagePath":"pages/peanut/bookshelf","iconPath":"static/icon/tab/icon3_n.png","selectedIconPath":"static/icon/tab/icon3_y.png","text":"我的书架"},{"pagePath":"pages/peanut/mine","iconPath":"static/icon/tab/icon4_n.png","selectedIconPath":"static/icon/tab/icon4_y.png","text":"我的"}],"height":"50px","child":["lauchwebview"],"selected":0},"launch_path":"__uniappview.html"}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

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