This commit is contained in:
wangyu 2026-03-05 18:33:30 +08:00
commit 3936d39f7a
377 changed files with 17674 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test
/.appanalyzer

10
AppScope/app.json5 Normal file
View File

@ -0,0 +1,10 @@
{
"app": {
"bundleName": "com.ylqh.hm_cube",
"vendor": "devcon",
"versionCode": 210,
"versionName": "2.1.0",
"icon": "$media:layer_logo",
"label": "$string:app_name"
}
}

View File

@ -0,0 +1,28 @@
{
"string": [
{
"name": "app_name",
"value": "素材魔方"
},
{
"name": "oaid_reason",
"value": "为了更好的为您提供服务,请求授权广告跨应用关联访问权限"
},
{
"name": "read_pasteboard_reason",
"value": "为了方便识别您复制的链接并跳转相关页面,请求授权剪贴板读取权限"
},
{
"name": "privacy_content",
"value": "请你务必审慎阅读、充分理解服务协议和隐私政策各条款,包括但不限于:为了更好的向你提供服务,我们需要访问你的相册、位置信息等。你可阅读《隐私政策》了解详细信息。如果你同意,请点击下面同意按钮开始接受我们的服务。"
},
{
"name": "wx_video_course",
"value": "1、点击【添加助手】自动跳转进入微信创建快存助手\n2、进入微信视频号选择视频分享至-客服消息-素材助手;\n3、返回【素材魔方APP】微信视频号页面下拉刷新即可下载微信号视频"
},
{
"name": "wx_playback_course",
"value": "1、点击【添加直播回放助手】\n2、自动跳转进入微信小程序扫二维码加入群聊\n3、成功加入会自动绑定您的ID若显示绑定失败可回到我们APP点击复制我的ID后发送至群聊即可重新绑定\n4、绑定成功就可以去微信直播回放页面选择直播回放视频分享到群聊里返回我们APP刷新即可"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,6 @@
{
"layered-image": {
"foreground": "$media:ic_launcher_foreground",
"background": "$media:ic_launcher_background"
}
}

97
build-profile.json5 Normal file
View File

@ -0,0 +1,97 @@
{
"app": {
"signingConfigs": [
{
"name": "release",
"type": "HarmonyOS",
"material": {
"storeFile": "D:/android/签名/hmos/hmos.p12",
"storePassword": "000000188D653F4B8748579F0084CAC913B6D6AB6FF60084FB0F318CC52AC425A874B607DD8261F9",
"keyAlias": "__uni__1be0b2f",
"keyPassword": "00000018779445CEA4B7EA75CB8F722F9DA76CBDBC5CE7C083CE8B5E1973141BF17E654C7237F5EF",
"signAlg": "SHA256withECDSA",
"profile": "D:/android/签名/hmos/素材魔方/cube_releaseRelease.p7b",
"certpath": "D:/android/签名/hmos/素材魔方/cube_release.cer"
}
},
{
"name": "debug",
"type": "HarmonyOS",
"material": {
"storeFile": "D:/android/签名/hmos/hmos.p12",
"storePassword": "000000188D653F4B8748579F0084CAC913B6D6AB6FF60084FB0F318CC52AC425A874B607DD8261F9",
"keyAlias": "__uni__1be0b2f",
"keyPassword": "00000018779445CEA4B7EA75CB8F722F9DA76CBDBC5CE7C083CE8B5E1973141BF17E654C7237F5EF",
"signAlg": "SHA256withECDSA",
"profile": "D:/android/签名/hmos/素材魔方/cube_debugDebug.p7b",
"certpath": "D:/android/签名/hmos/素材魔方/cube_debug.cer"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "release",
"targetSdkVersion": "5.1.0(18)",
"compatibleSdkVersion": "5.0.4(16)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
},
{
"name": "debug",
"signingConfig": "debug",
"targetSdkVersion": "5.1.0(18)",
"compatibleSdkVersion": "5.0.4(16)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release",
"buildOption": {
"nativeLib": {
"filter": {
//根据正则表达式排除匹配到的.so文件匹配到的so文件将不会被打包
"excludes": [
"**/x86_64/*.so",
"**/armeabi-v7a/*.so"
//排除所有x86_64架构的so文件
],
//允许当.so重名冲突时使用高优先级的.so文件覆盖低优先级的.so文件
"enableOverride": true
}
}
}
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default",
"debug"
]
}
]
}
]
}

32
code-linter.json5 Normal file
View File

@ -0,0 +1,32 @@
{
"files": [
"**/*.ets"
],
"ignore": [
"**/src/ohosTest/**/*",
"**/src/test/**/*",
"**/src/mock/**/*",
"**/node_modules/**/*",
"**/oh_modules/**/*",
"**/build/**/*",
"**/.preview/**/*"
],
"ruleSet": [
"plugin:@performance/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@security/no-unsafe-aes": "error",
"@security/no-unsafe-hash": "error",
"@security/no-unsafe-mac": "warn",
"@security/no-unsafe-dh": "error",
"@security/no-unsafe-dsa": "error",
"@security/no-unsafe-ecdsa": "error",
"@security/no-unsafe-rsa-encrypt": "error",
"@security/no-unsafe-rsa-sign": "error",
"@security/no-unsafe-rsa-key": "error",
"@security/no-unsafe-dsa-key": "error",
"@security/no-unsafe-dh-key": "error",
"@security/no-unsafe-3des": "error"
}
}

6
entry/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

28
entry/build-profile.json5 Normal file
View File

@ -0,0 +1,28 @@
{
"apiType": "stageMode",
"buildOption": {
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

6
entry/hvigorfile.ts Normal file
View File

@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,28 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation
-keep-property-name
quickLoginAnonymousPhone
-keep
./oh_modules/@ohos/imageknifepro

View File

@ -0,0 +1,72 @@
{
"meta": {
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@getui/gtc@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har": "@getui/gtc@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har",
"@getui/gysdk@1.0.10": "@getui/gysdk@1.0.10",
"@ohos/axios@^2.2.6": "@ohos/axios@2.2.6",
"cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har": "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har",
"ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har": "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har",
"dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har": "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har",
"unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har"
},
"packages": {
"@getui/gtc@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har": {
"name": "@getui/gtc",
"version": "1.0.5",
"resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/GTC-HM-1.0.5-20241203.har",
"registryType": "local"
},
"@getui/gysdk@1.0.10": {
"name": "@getui/gysdk",
"version": "1.0.10",
"integrity": "sha512-wsPJMEtrDgVi3uWizD1Yg96hkoK9uhPUzXkR6daNGsxHaf0bRtjg79aRDkE7o6xTHNyOVkPI/1l/5cDiZqbIow==",
"resolved": "https://repo.harmonyos.com/ohpm/@getui/gysdk/-/gysdk-1.0.10.har",
"registryType": "ohpm",
"dependencies": {
"@getui/gtc": "file:./libs/GTC-HM-1.0.5-20241203.har",
"unicom_login_harmony": "file:./libs/unicom_login_harmony_v1.0.4AR001B0214.har",
"cmccssosdk": "file:./libs/quick_login_hm_1.0.2.har",
"ctaccount": "file:./libs/ctaccount_v1.1.2.har"
}
},
"@ohos/axios@2.2.6": {
"name": "@ohos/axios",
"version": "2.2.6",
"integrity": "sha512-A1JqGe6XaeqWyjQETitFW4EkubQS7Fv7h0YG5a/ry3/a/vOgVGzwC4y5KAhvMzVv1tYjfY0ntMtV2kJGlmOHcQ==",
"resolved": "https://repo.harmonyos.com/ohpm/@ohos/axios/-/axios-2.2.6.har",
"registryType": "ohpm"
},
"cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har": {
"name": "cmccssosdk",
"version": "1.0.0",
"resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har",
"registryType": "local"
},
"ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har": {
"name": "ctaccount",
"version": "1.1.2",
"resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har",
"registryType": "local",
"dependencies": {
"dljson": "file:library/dlJson.har"
}
},
"dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har": {
"name": "dljson",
"version": "0.0.5",
"resolved": "../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har",
"registryType": "local"
},
"unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": {
"name": "unicom_login_harmony",
"version": "1.0.4",
"resolved": "../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har",
"registryType": "local"
}
}
}

13
entry/oh-package.json5 Normal file
View File

@ -0,0 +1,13 @@
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"@ohos/axios": "^2.2.6",
"@getui/gysdk": "1.0.10"
}
}

View File

@ -0,0 +1,9 @@
import { AbilityStage } from '@kit.AbilityKit';
import { PrefUtils } from './utils/PrefUtils';
export default class MyAbilityStage extends AbilityStage {
onCreate(): void {
PrefUtils.init(this.context)
}
}

View File

@ -0,0 +1,5 @@
export declare class DialogCallback {
confirm?: () => void;
cancel?: () => void;
}

View File

@ -0,0 +1,19 @@
export class Constants {
static readonly BASE_URL = "https://material.u8t.cn" //正式地址
static readonly TEST_URL = "https://material.u8t.cn" //测试地址
static readonly WEB_URL = "" //网页版地址
static readonly APP_ID = "10040"
//
static readonly WX_APP_ID = "wx19e5013ad43754c8" //微信APPID
static readonly MINI_PROGRAM_APP_ID = "gh_b38277bd004a" //小程序
static readonly UMENG_APP_KEY = "6883311979267e0210c15cb9" //友盟
//
static readonly USER_AGREEMENT = "https://material.u8t.cn/static/policy/user.html" //用户协议
static readonly PRIVACY_POLICY = "https://material.u8t.cn/static/policy/provacy.html" //隐私政策
static readonly RENEW_AGREEMENT = "https://material.u8t.cn/static/policy/renew.html" //自动续费协议
//
static readonly ENCRYPT = "wE8x4EnIHgyGOyjnoluzI2vk60wz5eNI"
static readonly SIGNATURE = "hfLLOtXRjd0e1Ac7O6sAXrECH2E828S9"
}

View File

@ -0,0 +1,272 @@
export class EventConstants {
/*---------------------------------------------APP事件---------------------------------------------*/
static readonly LoginSuccessEvent = "LoginSuccessEvent"
static readonly LogoutSuccessEvent = "LogoutSuccessEvent"
static readonly HomeRefreshEvent = "HomeRefreshEvent"
static readonly RecordRefreshEvent = "RecordRefreshEvent"
static readonly MineRefreshEvent = "MineRefreshEvent"
static readonly MediaActionEvent = "MediaActionEvent"
static readonly JumpToRecordEvent = "JumpToRecordEvent"
static readonly DownloadHistoryRefreshEvent = "DownloadHistoryRefreshEvent"
/*-------------------------------------------客户端上报事件-------------------------------------------*/
static readonly APP_LAUNCH = "client.launch" //app启动
static readonly GUIDE_LAUNCH = "client.guide.launch" //引导页启动
static readonly GUIDE_OPPORTUNITY_SCROLL = "client.guide.content.scroll" //滑动切换引导页内容
static readonly GUIDE_SKIP = "client.guide.pay.skip" //跳过引导页支付
static readonly APP_ACTIVE = "client.home.active" //app激活
static readonly HOME_BOTTOM_TAB_CHECK = "client.main.bottom.tab.check" //底部tab切换
static readonly HOME_NOTICE_CHECK = "client.main.notice.check" //首页通知点击
static readonly GOODS_SELECT = "client.goods.select" //点击切换支付的会员类型
static readonly PAY_SELECT = "client.pay.select" //点击切换支付类型
static readonly PAY_PAY = "client.pay.pay" //支付按钮点击
static readonly CHALLENGE_TASK_PAY_PAY = "client.challenge.task.pay.pay" //0元挑战支付按钮点击
static readonly PAY_SUCCESS = "client.pay.success" //支付成功
static readonly PAY_CANCEL = "client.pay.cancel" //支付取消
static readonly ERROR_CLIENT_WXPAY_ERR = "client.wxpay.err" //微信支付失败
static readonly ERROR_CLIENT_ALIPAY_ERR = "client.alipay.err" //支付宝支付失败
static readonly ERROR_CLIENT_DOWNLOAD_IMG = "client.download.img.err" //图片下载失败
static readonly ERROR_CLIENT_DOWNLOAD_VIDEO = "client.download.video.err" //视频下载失败
static readonly ERROR_CLIENT_DOWNLOAD_AUDIO = "client.download.audio.err" //音频下载失败
static readonly CANCEL_DOWNLOAD_VIDEO = "client.download.video.cancel" //取消视频下载
static readonly PAUSE_DOWNLOAD_VIDEO = "client.download.video.pause" //暂停视频下载
static readonly CONTINUE_DOWNLOAD_VIDEO = "client.download.video.continue" //继续视频下载
static readonly RESTART_DOWNLOAD_VIDEO = "client.download.video.restart" //重新视频下载
static readonly SPEED_UP_DOWNLOAD_VIDEO = "client.download.video.speed.up" //加速视频下载
static readonly BACKGROUND_CLIENT_DOWNLOAD = "client.download.background" //后台下载
static readonly FLOAT_WINDOW_CLICK = "client.float.window.click" //点击悬浮窗
static readonly PKG_UPDATE = "client.pkg.update" //升级弹窗点击更新
static readonly PKG_CANCEL = "client.pkg.cancel" //升级弹窗点击取消
static readonly GET_MATERIAL = "client.get.material" //获取素材
static readonly GET_MATERIAL_CANCEL = "client.get.material.cancel" //取消获取素材
static readonly DIALOG_CONFIRM_SAVE_FILE = "client.dialog.confirm.save.file" //保存文件地址弹框确认
static readonly DIALOG_GO_TO_VIEW = "client.dialog.go.to.view" //前往保存文件的地址查看
static readonly JUMP_TO_ABOUT_US = "client.jump.to.about.us" //界面跳转
static readonly JUMP_TO_LINK_EXTRACT = "client.jump.to.link.extract" //跳转链接提取
static readonly JUMP_TO_WECHAT_VIDEO = "client.jump.to.wechat.video" //跳转视频号
static readonly JUMP_TO_WECHAT_PLAYBACK = "client.jump.to.wechat.video.playback" //跳转直播回放
static readonly JUMP_TO_COURSE_WX_VIDEO = "client.course.wechat.video" //视频号视频教程
static readonly JUMP_TO_COURSE_PLAYBACK = "client.course.playback" //直播回放视频教程
static readonly JUMP_TO_HOME_TOOL = "client.jump.to.home.tool" //跳转首页工具
static readonly MAIN_CENTER_ENABLE = "client.main.center.enable" //首页跳转个人中心
static readonly JUMP_TO_MEMBER_RECHARGE = "client.jump.to.member.recharge" //跳转到充值页
static readonly JUMP_TO_LOGIN = "client.jump.to.login" //跳转到登录页
static readonly JUMP_TO_SYSTEM_SETTING = "client.jump.to.system.setting" //跳转到系统设置
static readonly JUMP_TO_USER_SETTING = "client.jump.to.user.setting" //跳转到用户设置
static readonly JUMP_TO_FEEDBACK = "client.jump.to.feedback" //跳转到意见反馈
static readonly JUMP_TO_ACCOUNT_BIND = "client.jump.to.account.bind" //跳转到账号绑定
static readonly JUMP_TO_ACCOUNT_MANAGE = "client.jump.to.account.manage" //跳转到账号管理
static readonly JUMP_TO_DOWNLOAD_HISTORY = "client.jump.to.download.history" //跳转到下载记录
static readonly JUMP_TO_DOWNLOAD_TASK_LIST = "client.jump.to.download.task.list" //跳转到下载任务列表
static readonly JUMP_TO_RECHARGE_DIAMOND = "client.jump.to.recharge.diamond" //跳转到钻石充值
static readonly JUMP_TO_COUPON_LIST = "client.jump.to.coupon.list" //跳转优惠券列表
static readonly JUMP_TO_CHALLENGE_TASK = "client.jump.to.challenge.task" //跳转到0元挑战
static readonly JUMP_TO_SHARE_WX_VIDEO = "client.jump.to.wechat.share.video" //跳转到视频号分享
static readonly JUMP_TO_SHARE_WX_PLAYBACK = "client.jump.to.wechat.share.playback" //跳转到直播回放分享
static readonly JUMP_TO_COURSE = "client.jump.to.course" //跳转到指导教程
static readonly DOWNLOAD_FILE = "client.download.file" //下载文件
static readonly TRANSPOND_FILE = "client.transpond.file" //转发文件
static readonly MATERIAL_COPY_TEXT = "client.material.copy.text" //复制文字
static readonly MATERIAL_TYPE_CHECK = "client.material.type.check" //素材切换
static readonly MATERIAL_ALL_SELECT = "client.material.all.select" //全部选中素材
static readonly MATERIAL_SELECT = "client.material.select" //选择素材
static readonly TOOLS_VIDEO_EXTRACT_AUDIO = "client.tools.video.audio" //提取音频
static readonly MATERIAL_PLAY_VIDEO = "client.material.play.video" //播发视频
static readonly GET_CODE = "client.get.code" //获取验证码
static readonly LOGIN = "client.login" //登录
static readonly SWITCH_ACCOUNT = "client.switch.account" //切换账户
static readonly ACCOUNT_BIND = "client.account.bind" //绑定账号
static readonly ACCOUNT_BIND_CANCEL = "client.account.bind.cancel" //取消绑定账号
static readonly CHECK_AGREEMENT = "client.check.agreement" //切换协议状态
static readonly VIEW_AGREEMENT = "client.view.agreement" //查看用户协议
static readonly PRIVACY_POLICY_CLICK_OK = "client.privacy.policy.click.ok" //同意隐私协议
static readonly SHARE_APP = "client.share.app" //分享app
static readonly CLEAR_CACHE = "client.clear.cache" //清除缓存
static readonly CONTACT_SERVICE = "client.contact.service" //联系客服
static readonly EXIT_LOGIN = "client.exit.login" //退出登录
static readonly CANCEL_ACCOUNT = "client.cancel.account" //注销账户
static readonly MEMBER_FORCE_LOGIN = "client.member.force.login" //会员强制登录
static readonly GET_MATERIAL_TIMES_USE_UP = "client.times.use.up.get.material" //获取素材次数已用完
static readonly PICTURE_HANDLE_TIMES_USE_UP = "client.times.use.up.picture.handle" //图片处理次数已用完
static readonly CHECK_LOGIN_TYPE = "client.check.login.type" //切换登录方式
static readonly OPEN_SCREEN_AD_SHOW = "client.ad.open.screen.show" //开屏广告展示
static readonly OPEN_SCREEN_AD_SKIP = "client.ad.open.screen.skip" //开屏广告跳过
static readonly OPEN_SCREEN_AD_CLICK = "client.ad.open.screen.click" //开屏广告点击
static readonly BANNER_AD_SHOW = "client.ad.banner.show" //banner广告展示
static readonly BANNER_AD_CLOSE = "client.ad.banner.close" //banner广告关闭
static readonly BANNER_AD_CLICK = "client.ad.banner.click" //banner广告点击
static readonly INSERT_SCREEN_AD_SHOW = "client.ad.insert.screen.show" //插屏广告展示
static readonly INSERT_SCREEN_AD_CLOSE = "client.ad.insert.screen.close" //插屏广告关闭
static readonly INSERT_SCREEN_AD_CLICK = "client.ad.insert.screen.click" //插屏广告点击
static readonly INSERT_SCREEN_AD_SKIP_VIDEO = "client.ad.insert.screen.skip.video" //跳过插屏广告
static readonly INCENTIVE_AD_SHOW = "client.ad.incentive.show" //激励广告展示
static readonly INCENTIVE_AD_CLOSE = "client.ad.incentive.close" //激励广告关闭
static readonly INCENTIVE_AD_REWARD = "client.ad.incentive.reward" //激励广告已获取到奖励
static readonly INCENTIVE_AD_SKIP_VIDEO = "client.ad.incentive.skip.video" //跳过激励广告
static readonly ACCOUNT_UNBIND = "client.account.unbind" //解除绑定账号
static readonly HISTORY_RECORD_TYPE_CHECK = "client.history.record.type.check" //历史记录切换
static readonly CLOSE_FREE_TIME_USES_UP_DIALOG =
"client.free.time.uses.up.dialog.close" //关闭免费次数用完的提示框
static readonly CHECK_FREE_TIME_USES_UP_DIALOG =
"client.free.time.uses.up.dialog.check" //免费次数用完的提示框切换充值类型
static readonly CONFIRM_FREE_TIME_USES_UP_DIALOG =
"client.free.time.uses.up.dialog.confirm" //确认免费次数用完的提示框
static readonly MULTI_DELETE_FILE = "client.multi.delete.file" //批量删除文件
static readonly MULTI_TRANSMIT_FILE = "client.multi.transmit.file" //批量转发文件
static readonly PREVIEW_DELETE_FILE = "client.preview.delete.file" //预览时删除文件
static readonly PREVIEW_TRANSMIT_FILE = "client.preview.transmit.file" //预览时删除文件
static readonly PREVIEW_HANDLE_IMAGE = "client.preview.handle.image" //预览时进行图片处理
static readonly TOOLS_HANDLE_IMAGE_START = "client.tools.handle.image.start" //图片处理开始
static readonly TOOLS_HANDLE_SAVE_IMAGE = "client.tools.handle.save.image" //保存已处理过的图片至相册
static readonly AUTO_SWITCH_DOWNLOAD_URL = "client.auto.switch.download.url" //自动切换下载链接
static readonly HAND_SWITCH_DOWNLOAD_URL = "client.hand.switch.download.url" //手动切换下载链接立即加速
static readonly HOME_BANNER_CLICK = "client.home.banner.click"
static readonly COUPON_ANIMATION_PLAY = "client.coupon.animation.play" //播放优惠券动画
static readonly COUPON_ANIMATION_CLOSE = "client.coupon.animation.close" //关闭优惠券动画
static readonly COUPON_RECEIVE = "client.coupon.receive" //领取优惠券
static readonly COUPON_REDEEM_ENABLE = "client.coupon.redeem.enable" //优惠券兑换按钮点击
static readonly COUPON_REDEEM_INFO = "client.coupon.redeem.info" //优惠券兑换详情
static readonly COUPON_REDEEM = "client.coupon.redeem" //优惠券兑换
static readonly COUPON_REDEEM_SUCCESS = "client.coupon.redeem.success" //优惠券兑换成功
static readonly COUPON_REDEEM_SUCCESS_CONFIRM = "client.coupon.redeem.success.confirm" //优惠券兑换成功
static readonly COUPON_VIEW = "client.coupon.view" //查看优惠券
static readonly COUPON_DIALOG_CHECK = "client.coupon.dialog.check" //切换优惠券
static readonly COUPON_DIALOG_CLOSE = "client.coupon.dialog.close" //关闭优惠券
static readonly COUPON_DIALOG_CONFIRM = "client.coupon.dialog.confirm" //确认优惠券
static readonly COPY_USER_ID = "client.copy_user_id" //复制用户id
static readonly SHOW_PALYBACK_HINT_DIALOG = "client.show.playback.hint.dialog"
static readonly EXIT_APP = "client.exit.app" //退出APP
static readonly SHOW_DIALOG = "client.show.dialog" //弹出退出app的弹框
static readonly START_COUPON_ANIMATION = "client.start.coupon.animation"
static readonly CHALLENGE_TASK_SIGN_IN = "client.challenge.tasK.sign.in" //签到
static readonly CHALLENGE_TASK_SIGN_IN_SUCCESS = "client.challenge.tasK.sign.in.success" //签到成功
static readonly CHALLENGE_TASK_SIGN_IN_FAIL = "client.challenge.tasK.sign.in.fail" //签到失败
}

View File

@ -0,0 +1,146 @@
export class RouterUrls {
/**
* 通用web加载页
*/
static readonly WEB_PAGE = "pages/web/WebPage"
/**
* 引导页
*/
static readonly GUIDE_PAGE = "pages/guide/GuidePage"
/**
* 登录页
*/
static readonly LOGIN_PAGE = "pages/login/LoginPage"
/**
* 扫码登录页
*/
static readonly QRCODE_LOGIN_PAGE = "pages/login/qrcode/QrcodeLoginPage"
/**
* 主页
*/
static readonly MAIN_PAGE = "pages/main/MainPage"
/**
* 链接提取页
*/
static readonly TAKE_MATERIAL_PAGE = "pages/main/home/link/TakeMaterialPage"
/**
* 提取教程页
*/
static readonly COURSE_PAGE = "pages/main/home/course/CoursePage"
/**
* 视频号提取页
*/
static readonly WX_VIDEO_PAGE = "pages/main/home/wx/WxVideoPage"
/**
* 添加水印页
*/
static readonly ADD_WATER_MARKER_PAGE = "pages/main/home/tools/AddWaterMarkerPage"
/**
* MD5去重页
*/
static readonly MD5_RESET_PAGE = "pages/main/home/tools/MD5ResetPage"
/**
* 视频倒放页
*/
static readonly VIDEO_REVERSE_PAGE = "pages/main/home/tools/VideoReversePage"
/**
* 视频裁剪页
*/
static readonly VIDEO_MIRROR_PAGE = "pages/main/home/tools/VideoMirrorPage"
/**
* 视频裁剪页
*/
static readonly CLIP_VIDEO_PAGE = "pages/main/home/tools/ClipVideoPage"
/**
* 去除音乐页
*/
static readonly REMOVE_AUDIO_PAGE = "pages/main/home/tools/RemoveAudioPage"
/**
* 添加音乐页
*/
static readonly ADD_AUDIO_PAGE = "pages/main/home/tools/AddAudioPage"
/**
* 视频转音频页
*/
static readonly TAKE_AUDIO_PAGE = "pages/main/home/tools/TakeAudioPage"
/**
* 素材详情页
*/
static readonly MATERIAL_DETAIL_PAGE = "pages/main/home/material/MaterialDetailPage"
/**
* 用户设置页
*/
static readonly USER_SETTINGS_PAGE = "pages/main/mine/user/UserSettingsPage"
/**
* vip页
*/
static readonly VIP_PAGE = "pages/main/mine/vip/VipPage"
/**
* 下载记录页
*/
static readonly DOWNLOAD_HISTORY_PAGE = "pages/main/mine/history/DownloadHistoryPage"
/**
* 钻石充值页
*/
static readonly RECHARGE_DIAMOND_PAGE = "pages/main/mine/diamond/DiamondPage"
/**
* 设置页
*/
static readonly SETTING_PAGE = "pages/main/mine/setting/SettingsPage"
/**
* 意见反馈页
*/
static readonly FEEDBACK_PAGE = "pages/main/mine/setting/feedback/FeedbackPage"
/**
* 关于页
*/
static readonly ABOUT_PAGE = "pages/main/mine/setting/about/AboutPage"
/**
* 账号绑定页
*/
static readonly BIND_ACCOUNT_PAGE = "pages/main/mine/setting/account/BindAccountPage"
/**
* 账号管理页
*/
static readonly MANAGE_ACCOUNT_PAGE = "pages/main/mine/setting/account/ManageAccountPage"
/**
* 视频播放页
*/
static readonly VIDEO_PLAYER_PAGE = "pages/video/VideoPlayerPage"
/**
* 音频播放页
*/
static readonly AUDIO_PLAYER_PAGE = "pages/audio/AudioPlayerPage"
/**
* 图片查看页
*/
static readonly PHOTO_VIEW_PAGE = "pages/photo/PhotoViewPage"
}

View File

@ -0,0 +1,205 @@
import { StrUtil } from '@pura/harmony-utils';
import { plainToInstance } from 'class-transformer';
import { SendCodeEntity } from '../entity/SendCodeEntity';
import { apiService } from '../net/ApiService';
import { HttpResult } from '../net/HttpResult';
import { ToastUtils } from '../utils/ToastUtils';
import { LoadingDialog } from './LoadingDialog';
@CustomDialog
export struct BindPhoneDialog {
controller: CustomDialogController;
success: () => void = () => {}
phone: string = '';
code: string = '';
timestamp: string = '';
intervalId: number = -1;
@State countDownTime: number = 0;
build() {
RelativeContainer() {
Text('绑定手机号')
.width('auto')
.fontColor($r('app.color.color_90ffffff'))
.fontSize(16)
.fontWeight(FontWeight.Medium)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End },
})
.margin({ top: 18 })
.id('tv_title')
Image($r('app.media.ic_close'))
.width(20)
.height(20)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.margin({ top: 12, right: 12 })
.onClick(() => {
this.controller.close();
})
TextInput({ placeholder: '请输入手机号' })
.height(48)
.type(InputType.PhoneNumber)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.placeholderColor($r('app.color.color_30ffffff'))
.placeholderFont({ size: 15 })
.maxLength(11)
.backgroundColor($r('app.color.color_222222'))
.borderRadius(8)
.margin({ top: 20, left: 16, right: 16 })
.alignRules({
top: { anchor: 'tv_title', align: VerticalAlign.Bottom }
})
.id('et_phone')
.onChange((value: string) => {
this.phone = value;
})
Row() {
TextInput({ placeholder: '请输入验证码' })
.height(48)
.type(InputType.Number)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.placeholderColor($r('app.color.color_30ffffff'))
.placeholderFont({ size: 15 })
.maxLength(6)
.backgroundColor($r('app.color.color_222222'))
.borderRadius(8)
.margin({ left: 16, right: 12 })
.id('et_code')
.onChange((value: string) => {
this.code = value;
})
.layoutWeight(1)
Text(this.countDownTime === 0 && StrUtil.isEmpty(this.timestamp) ? '获取验证码' : this.countDownTime > 0 ? `${this.countDownTime}s` : '重新发送')
.width(110)
.height(48)
.textAlign(TextAlign.Center)
.fontColor($r("app.color.color_466afd"))
.fontSize(15)
.backgroundColor($r('app.color.color_222222'))
.borderRadius(8)
.margin({ right: 16 })
.onClick(() => {
this.sendCode(this.phone);
})
}
.alignRules({
top: { anchor: 'et_phone', align: VerticalAlign.Bottom }
})
.margin({ top: 14 })
.id('layout_code')
Stack() {
Button('确定', { type: ButtonType.Capsule, stateEffect: true })
.width('100%')
.height(46)
.fontColor(Color.White)
.fontSize(15)
.fontWeight(FontWeight.Medium)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.onClick(() => {
this.bindPhone();
})
}
.padding({ left: 16, right: 16 })
.margin({ top: 55 })
.alignRules({
top: { anchor: 'layout_code', align: VerticalAlign.Bottom }
})
}
.width('100%')
.height(320)
.borderRadius(20)
.backgroundColor($r('app.color.window_background'))
}
sendCode(phone: string) {
if (StrUtil.isEmpty(this.phone)) {
ToastUtils.show('请输入手机号');
return;
}
if (this.phone.length != 11) {
ToastUtils.show('请输入正确的手机号');
return;
}
LoadingDialog.show(this.getUIContext());
try {
apiService.sendCode(phone)
.then((result: HttpResult) => {
if (result.isSuccess()) {
ToastUtils.show('验证码已发送');
const codeEntity = plainToInstance(SendCodeEntity, result.data);
this.timestamp = codeEntity.timestamp;
this.countDownTime = 60;
this.intervalId = setInterval(() => {
if (this.countDownTime > 0) {
this.countDownTime--
} else {
if (this.intervalId !== 0) {
clearInterval(this.intervalId);
}
}
}, 1000)
} else {
ToastUtils.show(result.message, true);
}
})
.finally(() => {
LoadingDialog.dismiss();
})
} catch (e) {
LoadingDialog.dismiss();
console.log(e);
ToastUtils.show(e);
}
}
bindPhone() {
if (StrUtil.isEmpty(this.phone)) {
ToastUtils.show('请输入手机号');
return;
}
if (this.phone.length != 11) {
ToastUtils.show('请输入正确的手机号');
return;
}
if (StrUtil.isEmpty(this.code)) {
ToastUtils.show('请输入验证码');
return;
}
LoadingDialog.show(this.getUIContext());
try {
apiService.loginByPhone(this.phone, this.code, this.timestamp, true)
.then((result: HttpResult) => {
if (result.isSuccess()) {
ToastUtils.show('绑定成功');
this.controller.close();
this.success();
} else {
ToastUtils.show(result.message, true)
}
})
.finally(() => {
LoadingDialog.dismiss();
})
} catch (e) {
LoadingDialog.dismiss();
}
}
}

View File

@ -0,0 +1,59 @@
import { DiamondRuleEntity } from "../entity/DiamondRuleEntity";
import { DiamondRuleItemView } from "../view/DiamondRuleItemView";
@CustomDialog
export struct DiamondRuleDialog {
controller: CustomDialogController;
build() {
RelativeContainer() {
Image($r('app.media.ic_diamond_rule_top_bg')).width('100%').aspectRatio(1.811)
Image($r('app.media.ic_diamond_rule_indicator')).width(86).height(16)
.alignRules({
left: {anchor: 'tv_title', align: HorizontalAlign.Start},
right: {anchor: 'tv_title', align: HorizontalAlign.End},
bottom: {anchor: 'tv_title', align: VerticalAlign.Bottom}
})
.margin({left: -8, bottom: -4})
Text('规则说明').fontColor($r('app.color.color_1a1a1a')).fontSize(20).fontFamily('almmsht').id('tv_title')
.alignRules({
left: {anchor: '__container__', align: HorizontalAlign.Start},
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.width('auto')
.margin({top: 20})
Image($r('app.media.ic_close2')).width(24).height(24)
.alignRules({
top: {anchor: 'tv_title', align: VerticalAlign.Top},
right: {anchor: '__container__', align: HorizontalAlign.End},
bottom: {anchor: 'tv_title', align: VerticalAlign.Bottom}
})
.margin({right: 16})
.onClick(() => {
this.controller.close()
})
List({space: 20}) {
ForEach(DiamondRuleEntity.getRuleList(), (item: DiamondRuleEntity) => {
ListItem() {
DiamondRuleItemView({entity: item})
}
})
}
.width('auto')
.height('auto')
.margin({left: 16, top: 24, right: 16, bottom: 30})
.backgroundColor(Color.White)
.borderRadius(10)
.padding({left: 16, top: 20, right: 16, bottom: 20})
.alignRules({
top: {anchor: 'tv_title', align: VerticalAlign.Bottom}
})
}
.width('100%')
.height('auto')
.backgroundColor('#F6F6F6')
}
}

View File

@ -0,0 +1,220 @@
import { ComponentContent } from '@kit.ArkUI';
import { AppUtil, DisplayUtil, FileUtil, FormatUtil, NumberUtil, StrUtil, WindowUtil } from '@pura/harmony-utils';
import { DialogCallback } from '../callback/DialogCallback';
export enum DownloadStatus {
COMPLETED,
DOWNLOADING,
VIDEO_DOWNLOADING,
AUDIO_DOWNLOADING,
PROCESSING,
PROCESSING_PROGRESS,
TAKING
}
export declare class DownloadDialogOption {
status: DownloadStatus
totalSize: number
progress: number
totalCount: number
index: number
isAudio?: boolean
callback?: DialogCallback
}
@Builder
function defaultBuilder(option: DownloadDialogOption) {
Column() {
Text((option.status === DownloadStatus.DOWNLOADING ? '下载中' :
option.status === DownloadStatus.VIDEO_DOWNLOADING ? '视频下载中' :
option.status === DownloadStatus.AUDIO_DOWNLOADING ? '音频下载中' : '处理中') +
(option.totalCount > 1 ? ` ${option.index + 1}/${option.totalCount}` : ''))
.fontColor($r('app.color.color_90ffffff'))
.fontSize(16)
.visibility(option.status === DownloadStatus.COMPLETED ? Visibility.None : Visibility.Visible)
Image(option.status === DownloadStatus.COMPLETED ? $r('app.media.ic_completed') : $r('app.media.ic_downloading'))
.width(80)
.height(80)
.margin({ top: 20 })
Stack() {
Column() {
Stack({ alignContent: Alignment.Start }) {
Progress({ value: option.progress, total: option.totalSize, type: ProgressType.Linear })
.width('100%')
.style({ strokeWidth: 12, strokeRadius: 6 })
.color('#FC4F54')
.colorBlend($r('app.color.color_10ffffff'))
.borderRadius(6)
Text(FormatUtil.getFormatPercentage(option.progress / option.totalSize, 1))
.width(40)
.height(18)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(10)
.borderRadius(10)
.borderWidth(1)
.borderColor($r('app.color.color_80ffffff'))
.backgroundColor($r("app.color.color_466afd"))
.translate({ x: (AppUtil.getUIContext().px2vp(DisplayUtil.getWidth()) * 0.8 - 80) * option.progress / option.totalSize })
}
Text(`${FileUtil.getFormatFileSize(option.progress)}/${option.totalSize !== 0 ?
FileUtil.getFormatFileSize(option.totalSize) : '获取中'}`)
.fontColor($r('app.color.color_999999'))
.fontSize(12)
.margin({ top: 16 })
.visibility(option.status === DownloadStatus.PROCESSING ? Visibility.Hidden : Visibility.Visible)
Row() {
Button('取消下载', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (option.callback?.cancel) {
option.callback.cancel()
}
DownloadDialog.dismiss()
})
Button('后台下载', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 10 })
.onClick(() => {
if (option.callback?.confirm) {
option.callback.confirm()
}
DownloadDialog.dismiss()
})
.visibility(Visibility.None)
}
.margin({ top: 16 })
}
.margin({ top: 20 })
.visibility(option.status === DownloadStatus.COMPLETED ? Visibility.None : Visibility.Visible)
Column() {
Text(option.isAudio ? '已保存到本地' : '已保存到系统相册中').fontColor($r('app.color.color_90ffffff')).fontSize(16)
Text(option.isAudio ? '文件管理/我的手机/Download/素材魔方' : '文件管理/我的手机/Download/图库')
.fontColor($r('app.color.color_999999'))
.fontSize(12)
.margin({ top: 10 })
Row() {
Button('取消', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (option.callback?.cancel) {
option.callback.cancel()
}
DownloadDialog.dismiss()
})
Blank().width(10)
Button('前往查看', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.onClick(() => {
if (option.callback?.confirm) {
option.callback.confirm()
}
DownloadDialog.dismiss()
})
}
.margin({ top: 16 })
}
.margin({ top: 20 })
.visibility(option.status === DownloadStatus.COMPLETED ? Visibility.Visible : Visibility.None)
}
}
.width('80%')
.borderRadius(10)
.backgroundColor($r('app.color.color_222222'))
.padding(20)
}
export class DownloadDialog {
context: UIContext | null = null;
contentNode: ComponentContent<Object> | null = null;
confirm?: () => void;
cancel?: () => void;
private static instance: DownloadDialog | null = null;
static show(context: UIContext, option: DownloadDialogOption) {
if (DownloadDialog.instance === null) {
DownloadDialog.instance = new DownloadDialog(context, option);
}
DownloadDialog.instance?.openDialog();
}
static update(option: DownloadDialogOption) {
DownloadDialog.instance?.updateDialog(option);
}
static dismiss() {
if (DownloadDialog.instance !== null) {
DownloadDialog.instance.closeDialog();
DownloadDialog.instance = null;
}
}
constructor(context: UIContext, option: DownloadDialogOption) {
this.context = context;
this.confirm = option.callback?.confirm
this.cancel = option.callback?.cancel
this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option);
}
openDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
onWillDismiss: () => {
return false
}
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
}
}
closeDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().closeCustomDialog(this.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.');
})
}
}
updateDialog(option: DownloadDialogOption) {
option.callback = { confirm: this.confirm, cancel: this.cancel }
this.contentNode?.update(option);
}
}

View File

@ -0,0 +1,128 @@
import { ComponentContent } from '@kit.ArkUI';
import { StrUtil } from '@pura/harmony-utils';
import { DialogCallback } from '../callback/DialogCallback';
import { ToastUtils } from '../utils/ToastUtils';
export declare class EditTextDialogOption {
title?: string;
content?: string;
hintText?: string;
confirm?: (content: string) => void;
}
@Builder
function defaultBuilder(option: EditTextDialogOption) {
Column() {
RelativeContainer() {
Text(option.title ?? '编辑')
.width('auto')
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontWeight(FontWeight.Medium)
.alignRules( {
left: {anchor: '__container__', align: HorizontalAlign.Start},
right: {anchor: '__container__', align: HorizontalAlign.End}
})
Image($r('app.media.ic_close')).width(20).height(20)
.alignRules( {
top: {anchor: '__container__', align: VerticalAlign.Top},
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.margin({ right: 16 })
.onClick(() => {
EditTextDialog.dismiss()
})
}
.width('100%')
.height(30)
TextInput({ placeholder: option.hintText ?? '请输入', text: option.content})
.width('90%')
.height(48)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.placeholderColor($r('app.color.color_30ffffff'))
.placeholderFont({ size: 15 })
.maxLength(12)
.backgroundColor($r('app.color.color_222222'))
.borderRadius(8)
.margin({ top: 16 })
.onChange((content) => {
option.content = content
})
Button('确定', { type: ButtonType.Capsule, stateEffect: true })
.fontColor(Color.White)
.fontSize(16)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.width('90%')
.height(46)
.margin({ top: 30 })
.onClick(() => {
if (StrUtil.isEmpty(option.content)) {
ToastUtils.show('内容不能为空')
} else {
if (option.confirm) {
option.confirm(option.content!!);
}
EditTextDialog.dismiss();
}
})
}
.padding({ top: 22, bottom: 22 })
.borderRadius(20)
.backgroundColor($r('app.color.window_background'))
.width('100%')
}
export class EditTextDialog {
context: UIContext | null = null;
contentNode: ComponentContent<Object> | null = null;
private static instance: EditTextDialog | null = null;
static show(context: UIContext, option: EditTextDialogOption) {
if (EditTextDialog.instance === null) {
EditTextDialog.instance = new EditTextDialog(context, option);
}
EditTextDialog.instance?.openDialog();
}
static dismiss() {
if (EditTextDialog.instance !== null) {
EditTextDialog.instance.closeDialog();
EditTextDialog.instance = null;
}
}
constructor(context: UIContext, option: EditTextDialogOption) {
this.context = context;
this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option);
}
openDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
maskColor: '#CC000000',
autoCancel: false,
alignment: DialogAlignment.Bottom
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
}
}
closeDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().closeCustomDialog(this.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.');
})
}
}
}

View File

@ -0,0 +1,179 @@
import { BusinessError, request } from '@kit.BasicServicesKit';
import { AppUtil, FileUtil, PasteboardUtil } from '@pura/harmony-utils';
import { ToastUtils } from '../utils/ToastUtils';
import { SaveUtils } from '../utils/SaveUtils';
import { LoginManager } from '../manager/LoginGlobalManager';
import { Want } from '@kit.AbilityKit';
import { WXApi } from '../utils/wechat/WXApiEventHandlerImpl';
@CustomDialog
export struct JoinWxGroupCourseDialog {
controller: CustomDialogController;
swiperController: SwiperController = new SwiperController()
isPlayback: boolean = true
images: Array<Resource> = [
$r('app.media.ic_wx_group_tip1'),
$r("app.media.ic_wx_group_tip2"),
$r("app.media.ic_wx_group_tip3"),
$r("app.media.ic_wx_group_tip4"),
$r('app.media.ic_wx_group_tip5')
];
steps: Array<string> = ['第一步', '第二步', '第三步', '第四步', '第五步']
qrCodePath = 'https://cdn.batiao8.com/kct/mp/kcsp_qrcode.png'
@State currentIndex: number = 0
downloadImage() {
try {
const cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + 'kcsp_wx_group_qrcode.jpg';
if (FileUtil.accessSync(cachePath)) {
FileUtil.unlink(cachePath)
}
request.downloadFile(AppUtil.getContext(), {
url: this.qrCodePath,
filePath: cachePath
}).then((downloadTask: request.DownloadTask) => {
downloadTask.on('complete', () => {
console.info('download complete');
SaveUtils.saveImageVideoToAlbumDialog([cachePath], false)
.then((saved) => {
if (saved) {
PasteboardUtil.setDataTextSync(LoginManager.getUserInfo()!!.user_id)
ToastUtils.show('ID复制成功')
this.jumpToWxScan()
this.controller.close()
} else {
ToastUtils.show('二维码保存失败')
}
})
.catch(() => {
ToastUtils.show('二维码保存失败')
})
})
}).catch((err: BusinessError) => {
ToastUtils.show('二维码下载失败')
});
} catch (error) {
ToastUtils.show('二维码下载失败')
}
}
async jumpToWxScan() {
let intent: Want = {
action: 'ohos.want.action.viewData',
uri: 'weixin://dl/scan' // 微信扫一扫的固定协议
};
try {
await AppUtil.getContext().startAbility(intent);
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(err.message)
}
}
build() {
RelativeContainer() {
Image($r('app.media.ic_wx_group_tip_bg')).width('100%').height(320)
Text(this.isPlayback ? '添加直播回放助手流程' : '添加视频助手流程')
.width('auto')
.fontColor(Color.White)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 16 })
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('tv_title')
List({space: 16}) {
ForEach(this.steps, (item: string, index) => {
ListItem() {
Text(item)
.fontColor(index == this.currentIndex ? Color.White : $r('app.color.color_bebebe'))
.fontSize(index == this.currentIndex ? 16 : 14)
.fontWeight(index == this.currentIndex ? FontWeight.Medium : FontWeight.Normal)
}
.onClick(() => {
this.swiperController.changeIndex(index, true)
})
})
}
.width('100%')
.height(24)
.listDirection(Axis.Horizontal)
.alignListItem(ListItemAlign.Center)
.alignRules({
top: {anchor: 'tv_title', align: VerticalAlign.Bottom}
})
.margin({top: 20})
.padding({left: 16, right: 16})
.id('li_step')
Swiper(this.swiperController) {
ForEach(this.images, (item: Resource) => {
Image(item).width('100%').aspectRatio(2.04).margin({ left: 16, right: 16 })
.borderRadius(20)
})
}
.indicator(false)
.loop(true)
.autoPlay(true)
.interval(2000)
.onChange((number) => {
this.currentIndex = number
})
.alignRules({
top: {anchor: 'li_step', align: VerticalAlign.Bottom}
})
.margin({top: 24})
.id('swiper')
Row() {
Button('取消', { type: ButtonType.Capsule, stateEffect: false })
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.backgroundColor($r('app.color.color_333333'))
.width(126)
.height(46)
.onClick(() => {
this.controller.close()
})
Blank().width(9)
Button('前往微信扫码加群', { type: ButtonType.Capsule, stateEffect: false })
.fontColor(Color.White)
.fontSize(15)
.backgroundColor(Color.Transparent)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.layoutWeight(1)
.height(46)
.onClick(() => {
if (!WXApi.isWXAppInstalled()) {
ToastUtils.show('未安装微信客户端,请先下载安装微信客户端');
return;
}
this.downloadImage()
})
}
.backgroundColor($r('app.color.color_222222'))
.alignRules({
top: {anchor: 'swiper', align: VerticalAlign.Bottom}
})
.margin({top: 28})
.padding({left: 16, top: 9, right: 16, bottom: 30})
}
.width('100%')
.height('auto')
.borderRadius(20)
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,81 @@
import { ComponentContent } from '@kit.ArkUI';
@Builder
function defaultBuilder() {
Column() {
LoadingProgress()
.color(Color.White)
.width(50)
.height(50)
Text('加载中')
.fontColor(Color.White)
.fontSize(12)
.margin({ top: 5 })
.width('auto')
.height('auto')
}
.justifyContent(FlexAlign.Center)
.borderRadius(6)
.backgroundColor('#CC000000')
.width(86)
.height(86)
}
export class LoadingDialog {
context: UIContext | null = null;
contentNode: ComponentContent<Object> | null = null;
private static instance: LoadingDialog | null = null;
static show(context: UIContext, cancelable: boolean = false) {
if (LoadingDialog.instance === null) {
LoadingDialog.instance = new LoadingDialog(context);
}
LoadingDialog.instance?.openDialog(cancelable);
}
static dismiss() {
if (LoadingDialog.instance !== null) {
LoadingDialog.instance.closeDialog();
LoadingDialog.instance = null;
}
}
constructor(context: UIContext) {
this.context = context;
this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder));
}
openDialog(cancelable: boolean) {
if (this.context !== null && this.contentNode !== null) {
if (!cancelable) {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
onWillDismiss: () => {
return false
},
autoCancel: false
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
} else {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
autoCancel: false
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
}
}
}
closeDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().closeCustomDialog(this.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.');
})
}
}
}

View File

@ -0,0 +1,80 @@
import { Constants } from '../common/Constants';
import { RouterUrls } from '../common/RouterUrls';
@CustomDialog
export struct LoginTipDialog {
controller: CustomDialogController;
cancel: () => void = () => {
}
confirm: () => void = () => {
}
build() {
RelativeContainer() {
Image($r('app.media.ic_tip_dialog_top_bg')).width('100%').aspectRatio(2.72)
Column() {
Text('温馨提示')
.fontColor($r('app.color.color_212226'))
.fontSize(24)
.fontFamily('ysbth')
.margin({ top: 20 })
Text() {
Span('登录之前需查看并同意')
Span('《用户协议》')
.fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: RouterUrls.WEB_PAGE, params: {
title: '用户协议',
url: Constants.USER_AGREEMENT
}
})
})
Span('和')
Span('《隐私政策》')
.fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: RouterUrls.WEB_PAGE, params: {
title: '隐私政策',
url: Constants.PRIVACY_POLICY
}
})
})
}
.fontColor($r('app.color.color_727686'))
.fontSize(14)
.textAlign(TextAlign.Center)
.margin({ top: 20, left: 20, right: 20 })
Button('同意', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(40)
.fontColor(Color.White)
.fontSize(16)
.backgroundColor($r("app.color.color_466afd"))
.margin({ top: 20, bottom: 20 })
.onClick(() => {
this.controller.close();
if (this.confirm) {
this.confirm();
}
})
}
Image($r('app.media.ic_close_dialog')).width(18).height(18)
.margin({top: 10, right: 10})
.alignRules({
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.onClick(() => {
this.controller.close()
})
}
.height('auto')
.borderRadius(20)
.backgroundColor(Color.White)
}
}

View File

@ -0,0 +1,84 @@
import { ComponentContent } from '@kit.ArkUI';
@Builder
function defaultBuilder(text: string) {
Column() {
LoadingProgress()
.color(Color.White)
.width(50)
.height(50)
Text(text)
.fontColor(Color.White)
.fontSize(12)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.ellipsisMode(EllipsisMode.END)
.margin({ left:10, top: 5, right: 10 })
.width('auto')
.height('auto')
}
.width(192)
.height(124)
.justifyContent(FlexAlign.Center)
.borderRadius(6)
.backgroundColor($r('app.color.color_222222'))
.padding({left: 10, right: 10})
}
export class MaterialLoadingDialog {
context: UIContext | null = null;
contentNode: ComponentContent<Object> | null = null;
private static instance: MaterialLoadingDialog | null = null;
static show(context: UIContext, text?: string) {
if (MaterialLoadingDialog.instance === null) {
MaterialLoadingDialog.instance = new MaterialLoadingDialog(context, text ? text : '');
}
MaterialLoadingDialog.instance?.openDialog();
}
static update(text: string) {
MaterialLoadingDialog.instance?.updateDialog(text);
}
static dismiss() {
if (MaterialLoadingDialog.instance !== null) {
MaterialLoadingDialog.instance.closeDialog();
MaterialLoadingDialog.instance = null;
}
}
constructor(context: UIContext, text: string) {
this.context = context;
this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), text);
}
openDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
onWillDismiss: () => {
return false
}
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
}
}
closeDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().closeCustomDialog(this.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.');
})
}
}
updateDialog(text: string) {
this.contentNode?.update(text);
}
}

View File

@ -0,0 +1,100 @@
import { Constants } from '../common/Constants';
import { RouterUrls } from '../common/RouterUrls';
import { LengthMetrics } from '@kit.ArkUI';
@CustomDialog
export struct PrivacyPolicyDialog {
controller: CustomDialogController;
cancel: () => void = () => {
}
confirm: () => void = () => {
}
build() {
Stack({alignContent: Alignment.Top}) {
Image($r('app.media.ic_tip_dialog_top_bg')).width('100%').aspectRatio(2.72)
Column() {
Text('欢迎使用')
.fontColor($r('app.color.color_212226'))
.fontSize(24)
.fontFamily('ysbth')
.margin({ top: 20 })
Text() {
Span('请你务必审慎阅读、充分理解')
Span('《服务协议》')
.fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: RouterUrls.WEB_PAGE, params: {
title: '用户协议',
url: Constants.USER_AGREEMENT
}
})
})
Span('和')
Span('《隐私政策》')
.fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: RouterUrls.WEB_PAGE, params: {
title: '隐私政策',
url: Constants.PRIVACY_POLICY
}
})
})
Span('各条款,包括但不限于:为了更好的向你提供服务,我们需要访问你的相册、位置信息等。你可阅读')
Span('《隐私政策》')
.fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: RouterUrls.WEB_PAGE, params: {
title: '隐私政策',
url: Constants.PRIVACY_POLICY
}
})
})
Span('了解详细信息。如果你同意,请点击下面同意按钮开始接受我们的服务。')
}
.fontColor($r('app.color.color_727686'))
.fontSize(14)
.lineSpacing(LengthMetrics.px(20))
.margin({ top: 10, left: 20, right: 20 })
Row(){
Button('拒绝', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(40)
.fontColor($r('app.color.color_80859B'))
.fontSize(16)
.backgroundColor($r("app.color.color_f1f2f6"))
.margin({left: 20})
.onClick(() => {
this.controller.close();
if (this.cancel) {
this.cancel();
}
})
Blank().layoutWeight(1)
Button('同意', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(40)
.fontColor(Color.White)
.fontSize(16)
.backgroundColor($r("app.color.color_466afd"))
.margin({right: 20})
.onClick(() => {
this.controller.close();
if (this.confirm) {
this.confirm();
}
})
}
.margin({ top: 20, bottom: 20 })
}
}
.borderRadius(20)
.backgroundColor(Color.White)
}
}

View File

@ -0,0 +1,111 @@
import { ComponentContent } from '@kit.ArkUI';
import { StrUtil } from '@pura/harmony-utils';
import { DialogCallback } from '../callback/DialogCallback';
export declare class TipDialogOption {
title?: string;
content: string;
buttonText?: string;
callback?: DialogCallback;
}
@Builder
function defaultBuilder(option: TipDialogOption) {
RelativeContainer() {
Image($r('app.media.ic_tip_dialog_top_bg')).width('100%').aspectRatio(2.72)
Column() {
Text(option.title)
.fontColor($r('app.color.color_212226'))
.fontSize(22)
.fontWeight(FontWeight.Medium)
.visibility(StrUtil.isEmpty(option.title) ? Visibility.Hidden : Visibility.Visible)
Text(option.content)
.textAlign(TextAlign.Center)
.fontColor(StrUtil.isNotEmpty(option.title) ? $r('app.color.color_727686') : $r('app.color.color_212226'))
.fontSize(StrUtil.isNotEmpty(option.title) ? 14 : 22)
.fontWeight(StrUtil.isNotEmpty(option.title) ? FontWeight.Normal : FontWeight.Medium)
.margin({ left: 27, top: 12, right: 27 })
.maxLines(5)
Button(StrUtil.isNotEmpty(option.buttonText) ? option.buttonText : '确定', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(40)
.fontColor(Color.White)
.fontSize(16)
.backgroundColor($r("app.color.color_466afd"))
.margin({ top: 20, bottom: 20 })
.onClick(() => {
SimpleTipDialog.dismiss();
if (option.callback?.confirm) {
option.callback?.confirm();
}
})
}
.padding({ top: 22, bottom: 22 })
Image($r('app.media.ic_close_dialog')).width(18).height(18)
.margin({top: 10, right: 10})
.alignRules({
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.onClick(() => {
SimpleTipDialog.dismiss();
})
}
.borderRadius(20)
.backgroundColor(Color.White)
.width('80%')
}
export class SimpleTipDialog {
context: UIContext | null = null;
contentNode: ComponentContent<Object> | null = null;
private static instance: SimpleTipDialog | null = null;
static show(context: UIContext, option: TipDialogOption, canCancel: boolean = true) {
if (SimpleTipDialog.instance === null) {
SimpleTipDialog.instance = new SimpleTipDialog(context, option);
}
SimpleTipDialog.instance?.openDialog(canCancel);
}
static dismiss() {
if (SimpleTipDialog.instance !== null) {
SimpleTipDialog.instance.closeDialog();
SimpleTipDialog.instance = null;
}
}
constructor(context: UIContext, option: TipDialogOption) {
this.context = context;
this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option);
}
openDialog(canCancel: boolean) {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
maskColor: '#CC000000',
onWillDismiss: () => {
return canCancel
}
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
}
}
closeDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().closeCustomDialog(this.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.');
})
}
}
updateDialog(options: TipDialogOption) {
this.contentNode?.update(options)
}
}

View File

@ -0,0 +1,121 @@
import { ComponentContent } from '@kit.ArkUI';
import { StrUtil } from '@pura/harmony-utils';
import { DialogCallback } from '../callback/DialogCallback';
export declare class TipDialogOption {
title?: string;
content: string;
leftText?: string;
rightText?: string;
callback?: DialogCallback;
}
@Builder
function defaultBuilder(option: TipDialogOption) {
Column() {
Text(option.title)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontWeight(FontWeight.Medium)
.visibility(StrUtil.isEmpty(option.title) ? Visibility.Hidden : Visibility.Visible)
Text(option.content)
.textAlign(TextAlign.Center)
.fontColor(StrUtil.isNotEmpty(option.title) ? $r('app.color.color_80ffffff') : $r('app.color.color_90ffffff'))
.fontSize(StrUtil.isNotEmpty(option.title) ? 14 : 18)
.fontWeight(StrUtil.isNotEmpty(option.title) ? FontWeight.Normal : FontWeight.Medium)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.ellipsisMode(EllipsisMode.END)
.margin({ left: 27, top: 12, right: 27 })
.maxLines(5)
Row() {
Button(StrUtil.isNotEmpty(option.leftText) ? option.leftText : '取消', { type: ButtonType.Capsule, stateEffect: false })
.fontColor($r('app.color.color_50ffffff'))
.fontSize(14)
.backgroundColor($r('app.color.color_333333'))
.layoutWeight(1)
.height(40)
.onClick(() => {
TipDialog.dismiss();
if (option.callback?.cancel) {
option.callback?.cancel();
}
})
Blank().width(12)
Button(StrUtil.isNotEmpty(option.rightText) ? option.rightText : '确定', { type: ButtonType.Capsule, stateEffect: false })
.fontColor(Color.White)
.fontSize(14)
.backgroundColor(Color.Transparent)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.layoutWeight(1)
.height(40)
.onClick(() => {
TipDialog.dismiss();
if (option.callback?.confirm) {
option.callback?.confirm();
}
})
}
.padding({ left: 14, right: 14 })
.margin({ top: 20 })
}
.padding({ top: 22, bottom: 22 })
.borderRadius(20)
.backgroundColor($r('app.color.color_222222'))
.width('80%')
}
export class TipDialog {
context: UIContext | null = null;
contentNode: ComponentContent<Object> | null = null;
private static instance: TipDialog | null = null;
static show(context: UIContext, option: TipDialogOption, canCancel: boolean = true) {
if (TipDialog.instance === null) {
TipDialog.instance = new TipDialog(context, option);
}
TipDialog.instance?.openDialog(canCancel);
}
static dismiss() {
if (TipDialog.instance !== null) {
TipDialog.instance.closeDialog();
TipDialog.instance = null;
}
}
constructor(context: UIContext, option: TipDialogOption) {
this.context = context;
this.contentNode = new ComponentContent(context, wrapBuilder(defaultBuilder), option);
}
openDialog(canCancel: boolean) {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().openCustomDialog(this.contentNode, {
maskColor: '#CC000000',
onWillDismiss: () => {
return canCancel;
}
})
.then(() => {
console.info('OpenCustomDialog complete.');
})
}
}
closeDialog() {
if (this.context !== null && this.contentNode !== null) {
this.context.getPromptAction().closeCustomDialog(this.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.');
})
}
}
updateDialog(options: TipDialogOption) {
this.contentNode?.update(options)
}
}

View File

@ -0,0 +1,13 @@
export class AccountEntity {
avater: string = '';
bind: Array<string> = new Array();
create_time: string = '';
name: string = '';
phone: string = '';
role: number = 0;
temp: boolean = true;
user_id: string = '';
vip_name: string = '';
vip_type: number = 1;
}

View File

@ -0,0 +1,5 @@
export class BannerEntity {
image: string = "";
page: string = "";
type: string = "";
}

View File

@ -0,0 +1,59 @@
import { Expose, Type } from 'class-transformer';
import { BannerEntity } from './BannerEntity';
import "reflect-metadata";
import { WxVideoConfigEntity } from './WxVideoConfig';
export class ConfigEntity {
@Expose({ name: 'client.guide.pay.enable' })
guidePayEnable: boolean = true; //引导页是否开启支付,默认可以
@Expose({ name: 'client.guide.enable' })
guideEnable: boolean = true; //是否开启引导页
@Expose({ name: 'client.start.function.hint' })
guideHint: string = ""; //引导页提示语
@Expose({ name: 'client.nologin.pay.enable' })
noLoginPayEnable: boolean = false; //是否开启未登录支付
@Expose({ name: 'client.pay.agreement' })
payAgreementEnable: boolean = true; //是否显示支付协议
@Expose({ name: 'client.login.type' })
loginType: Array<string> = new Array(); //支持的登录类型
@Expose({ name: 'client.banner.urls' })
@Type(() => BannerEntity)
homeBanners: Array<BannerEntity> = new Array(); //首页轮播图
@Expose({ name: 'client.wechat.video.share.enable' })
wxVideoEnable: boolean = true; //视频号开关
@Expose({ name: 'client.wechat.video.playback.share.enable' })
playbackEnable: boolean = true; //直播回放开关
@Expose({ name: 'client.course.wechat.video' })
wxVideoCourse: string = ""; //视频号教程
@Expose({ name: 'client.course.playback' })
wxPlaybackCourse: string = ""; //直播回放教程
@Expose({ name: 'client.playback.join.type' })
wxPlaybackJoinType: string = ""; //直播加群方式
@Type(() => WxVideoConfigEntity)
@Expose({ name: 'client.mp.share.config.kcsp' })
wxVideoConfig: WxVideoConfigEntity = new WxVideoConfigEntity() //直播回放小程序跳转配置
@Expose({ name: 'client.hmos.video.service.enable' }) //视频号助手是否可用
wxVideoServiceEnable: boolean = true
@Expose({ name: 'client.link.collection' })
domainMap: Record<string, string> = {}; //网站主机名
@Expose({ name: 'client.copy.contains' })
copyContainsList: Array<string> = []; //链接识别配置
@Expose({ name: 'client.challenge.enable' })
challengeEnable?: boolean = true; //0元挑战
}

View File

@ -0,0 +1,7 @@
export class DiamondDetailEntity {
buy_total: number = 0
buy_used: number = 0
month_total: number = 0
month_used: number = 0
remain: number = 0
}

View File

@ -0,0 +1,18 @@
export class DiamondRuleEntity {
title: string = ''
desc: string = ''
constructor(title: string, desc: string) {
this.title = title
this.desc = desc
}
static getRuleList(): Array<DiamondRuleEntity> {
const list = new Array<DiamondRuleEntity>()
list.push(new DiamondRuleEntity('一、固定钻石领取', '会员用户每月最后一天系统会固定发放500钻石到用户平台账户。'))
list.push(new DiamondRuleEntity('二、固定钻石刷新', '若会员用户在第一个月有没用完的钻石,那么会在本月最后一天重置不会留存到第二月。'))
list.push(new DiamondRuleEntity('三、兑换钻石', '会员兑换后的钻石统一叫【兑换钻石】,【兑换钻石】不同于【固定钻石】每月刷新钻石数量;用户兑换了多少就可以使用多少,用完截止,没有时间限制,也没有每月最后一天刷新。'))
list.push(new DiamondRuleEntity('四、重复兑换钻石', '兑换钻石可重复购买,用完截止,没有时间限制,也没有每月最后一天刷新。'))
return list
}
}

View File

@ -0,0 +1,102 @@
export class DownloadHistoryEntity {
id: string = ''
title: string = ''
status: string = ''
description: string = ''
create_time: string = ''
domain: string = ''
request: string = ''
save_cost: string = ''
save_end_time: string = ''
save_size: string = ''
save_start_time: string = ''
save_status: string = ''
getTypeBgColor(): string {
switch (this.domain) {
case 'b23.tv':
case 'm.bilibili.com':
case 'www.bilibili.com':
return '#F4306F'
case 'v.douyin.com':
case 'www.iesdouyin.com':
case 'vt.tiktok.com':
return '#80FFFFFF'
case 'm.toutiao.com':
return '#F91716'
case 'video.weibo.com':
case 'weibo.com':
case 'm.weibo.cn':
case 'shop.sc.weibo.com':
return '#DE860C'
case 'v.kuaishou.com':
return '#DE440D'
case 'wxapp.tc.qq.com':
case 'm.v.qq.com':
return '#0D95F3'
case 'twitter.com':
case 'x.com':
return '#139EEA'
case 'xhslink.com':
case 'www.xiaohongshu.com':
return '#EA3046'
case 'instagram.com':
case 'www.instagram.com':
return '#8D32F4'
case 'mr.baidu.com':
case 'm.baidu.com':
case 'mbd.baidu.com':
return '#0D95F3'
case 'youtu.be':
case 'youtube.com':
case 'm.youtube.com':
case 'www.youtube.com':
return '#F92C2C'
case 'mp.weixin.qq.com':
return '#1DDB50'
case 'e.tb.cn':
case 'm.tb.cn':
return '#F96E12'
case 'www.facebook.com':
return '#1B6EF6'
case 'v.ixigua.com':
case 'www.ixigua.com':
return '#F01566'
case 'mobile.yangkeduo.com':
return '#F40F18'
case 'm.youku.com':
return '#0CB3E2'
case '163cn.tv':
return '#F42C34'
case 'qishui.douyin.com':
return '#20C485'
case 'qr.1688.com':
case 'detail.m.1688.com':
return '#F65611'
case 'h5.pipix.com':
return '#EE3958'
case 'app.cctv.com':
return '#80FFFFFF'
case 'vk.com':
return '#1E82F3'
case 'www.finkapp.cn':
return '#F62C2C'
case 'novelquickapp.com':
return '#F18221'
case 'video.weishi.qq.com':
return '#2E5DF0'
case 'm.ctrip.com':
return '#177DF0'
}
return '#33FFFFFF'
}
getTypeTextColor(): string {
switch (this.domain) {
case 'v.douyin.com':
case 'app.cctv.com':
return '#CC000000'
}
return '#CCFFFFFF'
}
}

View File

@ -0,0 +1,23 @@
import { ArrayList } from "@kit.ArkTS";
export class HomeMenuEntity {
icon: Resource | null = null;
title: string = "";
alias: string = "";
constructor(icon: Resource, title: string, alias: string) {
this.icon = icon;
this.title = title;
this.alias = alias;
}
}
export function homeMenuList(): ArrayList<HomeMenuEntity> {
let list = new ArrayList<HomeMenuEntity>()
list.add(new HomeMenuEntity($r('app.media.ic_home_icon6'), "视频转音频", "videoToAudio"))
list.add(new HomeMenuEntity($r('app.media.ic_home_icon8'), "视频加水印", "addWatermark"))
list.add(new HomeMenuEntity($r('app.media.ic_home_icon5'), "视频转文字", "videoToText"))
list.add(new HomeMenuEntity($r('app.media.ic_home_icon9'), "长图拼接", "longImageMerge"))
list.add(new HomeMenuEntity($r('app.media.ic_home_icon10'), "更多功能", "moreTools"))
return list;
}

View File

@ -0,0 +1,7 @@
export class LoginEntity {
user_id: string = "";
name: string = "";
avater: string = "";
token: string = "";
}

View File

@ -0,0 +1,13 @@
import { UploadImgEntity } from './UploadImgEntity';
export class MaterialEntity {
id: string = '';
title: string = '';
pic?: UploadImgEntity = undefined;
pic_size: string = '';
tags: Array<string> = [];
user_id: string = '';
download: string = '';
update_time: string = '';
create_time: string = '';
}

View File

@ -0,0 +1,87 @@
import { Type } from 'class-transformer';
import "reflect-metadata";
import systemDateTime from '@ohos.systemDateTime';
import { RandomUtil } from '@pura/harmony-utils';
export class MaterialInfoEntity{
enc_user_id: string = '';
logid: string = '';
@Type(() => MaterialInfoEntity)
material?: MaterialDetailEntity;
offline: number = 0;
status: number = 0;
status_name: string = '';
timeout: number = 0;
user_id: string = '';
}
export class MaterialDetailEntity {
@Type(() => AudioMaterial)
audio?: Array<AudioMaterial>;
desc: string = '';
@Type(() => ImageMaterial)
image?: Array<ImageMaterial>;
merge: boolean = false;
proxyUrlList?: Array<string>;
threading: boolean = false;
title: string = '';
@Type(() => VideoMaterial)
video?: Array<VideoMaterial>;
}
@ObservedV2
export class MediaEntity {
name: string = ""
origin: string = ""
title: string = ""
url: string = ""
origin_url: string = ""
speed_up: number = 0 //1 加速
isThreading: boolean = false
isMerge: boolean = false
currentLen: number = 0
totalSize: number = 0
continueDownload: boolean = false
cacheName: string = ""
logid: string = ""
headers?: Map<string, string>
isM3u8: boolean = false
flag: number = 0
@Trace isChecked: boolean = false
initFileName(): string {
if (!this.name) {
if (this instanceof VideoMaterial) {
this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4`
} else if (this instanceof AudioMaterial) {
this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp3`
} else if (this instanceof ImageMaterial) {
this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg`
}
}
return this.name
}
}
export class VideoMaterial extends MediaEntity {
play: boolean = false
thumb?: string = ''
audio?: AudioMaterial
decodeKey?: Int8Array
sequenceNumber: number = 0
keyInfo?: Map<string, string> = new Map()
}
export class AudioMaterial extends MediaEntity {
}
export class ImageMaterial extends MediaEntity {
}
export class TextMaterial extends MediaEntity {
title: string = ''
desc: string = ''
}

View File

@ -0,0 +1,14 @@
import image from '@ohos.multimedia.image'
export class MediaRecordEntity {
uri?: string = ''
name?: string = ''
thumb?: image.PixelMap = undefined
duration: number = 0
createTime: number = 0
isChecked: boolean = false
constructor(uri: string) {
this.uri = uri
}
}

View File

@ -0,0 +1,6 @@
export class NoticeEntity {
loop: boolean = true;
notice?: Array<string> = [];
}

View File

@ -0,0 +1,14 @@
export class OrderEntity {
id: string = '';
create_time: string = '';
goods_name: string = '';
invoice_major: boolean = false;
invoice_status: string = '';
invoice_url: string = '';
out_trade_no: string = '';
pay_params: string = '';
pay_time: string = '';
pay_type: string = '';
status: string = '';
total_fee: string = '';
}

View File

@ -0,0 +1,16 @@
export class PayOrderEntity {
//支付宝
appId: string = '';
orderId: string = '';
outTradeNo: string = '';
payParam: string = '';
payType: string = '';
//微信
nonceStr: string = '';
package: string = '';
partnerId: string = '';
prepayId: string = '';
sign: string = '';
timeStamp: string = '';
}

View File

@ -0,0 +1,3 @@
export class SendCodeEntity {
timestamp: string = '';
}

View File

@ -0,0 +1,4 @@
export class UploadImgEntity {
id: string = ''
url: string = ''
}

View File

@ -0,0 +1,12 @@
import { Type } from 'class-transformer';
import { ConfigEntity } from './ConfigEntity';
import "reflect-metadata";
export class UserConfigEntity {
token: string = "";
temp: boolean = false;
name: string = "";
user_id: string = "";
@Type(() => ConfigEntity)
config: ConfigEntity = new ConfigEntity();
}

View File

@ -0,0 +1,30 @@
export class UserEntity {
appleid: string = '';
avater: string = '';
balance: string = '';
city: string = '';
client_cid: string = '';
country: string = '';
coupon_count: number = 0;
imei: string = '';
month_download_count: string = '0';
month_download_size: string = '0';
name: string = '';
oaid: string = '';
os_version: string = '';
phone: string = '';
province: string = '';
role: string = '';
sex: number = 0;
show_contact_menu: boolean = true;
show_masonry_menu: boolean = true;
temp: boolean = true;
unionid: string = '';
user_id: string = '';
vip: number = 1; // 1非会员 2会员 3终生会员
vip_expire: string = '';
vip_expire_time: string = '';
vip_name: string = '';
weixinAppOpenId: string = '';
ip_area: string = '';
}

View File

@ -0,0 +1,16 @@
@ObservedV2
export class VipMealEntity {
@Trace checked: boolean = false;
goods_id: string = '';
goods_name: string = '';
origin_price: string = '';
pay_type: string = '';
price: string = '';
single_pay_price: string = '';
tips: string = '';
sign_value = '';
value: string = '';
image: string = '';
description: string = '';
weixinMpOriId: string = '';
}

View File

@ -0,0 +1,13 @@
export class VipPermissionEntity {
auth: boolean = false
auth_ad: boolean = false
scene: string = ''
user_id: number = 0
vip: number = 0
vip_expire: string = ''
vip_expire_time: string = ''
vip_goods_type: string = ''
vip_message: string = ''
vip_name: string = ''
type: number = 0
}

View File

@ -0,0 +1,8 @@
import { Expose } from 'class-transformer';
import "reflect-metadata";
export class WxServiceEntity {
corpid: string = ""
@Expose({ name: 'kf.address' })
address: string = ""
}

View File

@ -0,0 +1,8 @@
export class WxVideoConfigEntity {
id: string = '';
title: string = '';
desc: string = '';
descTitle: string = '';
qrcode: string = '';
tips: string = '';
}

View File

@ -0,0 +1,6 @@
import { MaterialInfoEntity } from "./MaterialInfoEntity"
export class WxVideoEntity {
items?: Array<MaterialInfoEntity>
playback: boolean = false
}

View File

@ -0,0 +1,46 @@
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { AppUtil } from '@pura/harmony-utils';
import { WXApi, WXEventHandler } from '../utils/wechat/WXApiEventHandlerImpl';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
AppUtil.init(this.context);
this.handleWeChatCallIfNeed(want);
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleWeChatCallIfNeed(want);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
AppStorage.setOrCreate('windowStage', windowStage);
windowStage.loadContent('pages/splash/SplashPage', (err) => {
// this.registerFont();
if (err.code) {
return;
}
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
}
onDestroy(): void {
}
onForeground(): void {
// Ability has brought to foreground
}
onBackground(): void {
// Ability has back to background
}
private handleWeChatCallIfNeed(want: Want) { //放在与onCreate同级
WXApi.handleWant(want, WXEventHandler)
}
}

View File

@ -0,0 +1,16 @@
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
const DOMAIN = 0x0000;
export default class EntryBackupAbility extends BackupExtensionAbility {
async onBackup() {
hilog.info(DOMAIN, 'testTag', 'onBackup ok');
await Promise.resolve();
}
async onRestore(bundleVersion: BundleVersion) {
hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
await Promise.resolve();
}
}

View File

@ -0,0 +1,61 @@
import { audio } from "@kit.AudioKit";
import { BusinessError } from "@kit.BasicServicesKit";
class AVSessionManager {
private audioManager = audio.getAudioManager();
private audioSessionManager: audio.AudioSessionManager = this.audioManager.getSessionManager();
private strategy: audio.AudioSessionStrategy = {
concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
};
/**
* 激活音频会话
*/
async activate() {
if (!this.isActive()) {
await this.audioSessionManager.activateAudioSession(this.strategy).then(() => {
console.info('Succeeded in activating audio session.');
}).catch((err: BusinessError) => {
console.error(`Failed to activate audio session. Code: ${err.code}, message: ${err.message}`);
});
}
}
/**
* 停用音频会话
*/
async deactivate() {
if (this.isActive()) {
await this.audioSessionManager.deactivateAudioSession().then(() => {
console.info('Succeeded in deactivating audio session.');
}).catch((err: BusinessError) => {
console.error(`Failed to deactivate audio session. Code: ${err.code}, message: ${err.message}`);
});
}
}
/**
* 会话是否激活
* @returns
*/
isActive(): boolean {
return this.audioSessionManager.isAudioSessionActivated();
}
/**
* 添加会话停用监听
*/
setDeactivatedCallback(callback: Callback<audio.AudioSessionDeactivatedEvent>) {
this.audioSessionManager.on('audioSessionDeactivated', callback);
}
/**
* 移除会话停用监听
*/
removeDeactivatedCallback(callback?: Callback<audio.AudioSessionDeactivatedEvent>) {
this.audioSessionManager.off('audioSessionDeactivated', callback);
}
}
export const avSessionManager = new AVSessionManager()

View File

@ -0,0 +1,11 @@
import { apiService } from "../net/ApiService"
export class EventReportGlobalManager {
static async eventReport(key: string, value: string = '', extra: string = '') {
try {
apiService.eventReport(key, value, extra)
} catch (e) {
console.error(e)
}
}
}

View File

@ -0,0 +1,67 @@
import { PrefUtils } from '../utils/PrefUtils';
import { JSON } from '@kit.ArkTS';
/**
* 保存到相册的媒体列表
*/
export class LocalMediaManager {
static add(uri: string) {
let array = PrefUtils.getStringArray('local_record')
if (!array.includes(uri)) {
array.push(uri)
}
PrefUtils.put('local_record', JSON.stringify(array))
}
static delete(uri: string) {
let array = PrefUtils.getStringArray('local_record')
if (array.includes(uri)) {
array.splice(array.indexOf(uri), 1)
}
PrefUtils.put('local_record', JSON.stringify(array))
}
static getAllVideos(): Array<string> {
let array = PrefUtils.getStringArray('local_record')
return array.filter(item => item.endsWith('.mp4'))
}
static deleteAllVideos() {
let imageArray = LocalMediaManager.getAllImages()
let audioArray = LocalMediaManager.getAllAudios()
let newArray = imageArray.concat(audioArray)
PrefUtils.put('local_record', JSON.stringify(newArray))
}
static getAllAudios(): Array<string> {
let array = PrefUtils.getStringArray('local_record')
return array.filter(item => item.endsWith('.mp3'))
}
static deleteAllAudios() {
let videoArray = LocalMediaManager.getAllVideos()
let audioArray = LocalMediaManager.getAllImages()
let newArray = videoArray.concat(audioArray)
PrefUtils.put('local_record', JSON.stringify(newArray))
}
static getAllImages(): Array<string> {
let array = PrefUtils.getStringArray('local_record')
return array.filter(item => item.endsWith('.jpeg'))
}
static deleteAllImages() {
let videoArray = LocalMediaManager.getAllVideos()
let audioArray = LocalMediaManager.getAllAudios()
let newArray = videoArray.concat(audioArray)
PrefUtils.put('local_record', JSON.stringify(newArray))
}
static getAll(): Array<string> {
return PrefUtils.getStringArray('local_record')
}
static deleteAll() {
PrefUtils.remove('local_record')
}
}

View File

@ -0,0 +1,92 @@
import { StrUtil } from '@pura/harmony-utils';
import { UserEntity } from '../entity/UserEntity';
import { PrefUtils } from '../utils/PrefUtils';
import { ConfigManager } from './UserConfigManager';
import { JSON } from '@kit.ArkTS';
import { plainToInstance } from 'class-transformer';
class LoginGlobalManager {
private userInfo?: UserEntity;
/**
* 保存当前用户基本信息
* @param userEntity
*/
setUserInfo(userEntity: UserEntity) {
this.userInfo = userEntity;
}
/**
* 获取当前用户基本信息
* @returns
*/
getUserInfo(): UserEntity | undefined {
return this.userInfo;
}
/**
* 保存上一个用户基本信息
* @param userEntity
*/
saveLastUserInfo(userEntity: UserEntity) {
PrefUtils.put('last_userinfo', JSON.stringify(userEntity));
}
/**
* 获取上一个用户基本信息
* @returns
*/
getLastUserInfo(): UserEntity | null {
const str = PrefUtils.getString('last_userinfo');
if (StrUtil.isNotEmpty(str)) {
const userEntity = plainToInstance(UserEntity, JSON.parse(str));
return userEntity;
}
return null;
}
/**
* 保存上次登录方式
* @param userEntity
*/
saveLastLoginType(type: string) {
PrefUtils.put('last_login_type', type);
}
/**
* 获取上次登录方式
* @returns
*/
getLastLoginType(): string {
return PrefUtils.getString('last_login_type');
}
/**
* 用户token
* @param token
*/
saveToken(token: string) {
PrefUtils.put('x-token', token);
}
getToken(): string {
return PrefUtils.getString('x-token');
}
/**
* 是否已登录
* @returns
*/
isLogin(): boolean {
return !ConfigManager.isTemp();
}
logout() {
PrefUtils.remove('x-role');
PrefUtils.remove('x-token');
PrefUtils.remove('userConfig')
}
}
export const LoginManager = new LoginGlobalManager();

View File

@ -0,0 +1,119 @@
import { MediaRecordEntity } from '../entity/MediaRecordEntity';
import { LocalMediaManager } from './LocalMediaManager';
import { FileUtil, NumberUtil } from '@pura/harmony-utils';
import { fileIo as fs } from '@kit.CoreFileKit';
import { media } from '@kit.MediaKit';
export enum MediaAction {
ADD, DELETE, CLEAR
}
export enum MediaType {
VIDEO, IMAGE, AUDIO
}
export class MediaManager {
/**
* 获取相册中保存的视频
* @returns
*/
static async getVideoList(): Promise<Array<MediaRecordEntity>> {
try {
let mediaList = new Array<MediaRecordEntity>()
let videoUris = LocalMediaManager.getAllVideos()
for (let i = 0; i < videoUris.length; i++) {
try {
let uri = videoUris[i]
let record = new MediaRecordEntity(uri)
record.name = FileUtil.getFileName(uri)
let file = FileUtil.openSync(uri)
let videoSize: media.PixelMapParams = {}
let avMetaDataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor();
avMetaDataExtractor.fdSrc = file;
let metadata = await avMetaDataExtractor.fetchMetadata();
videoSize.width = parseInt(metadata.videoWidth as string);
videoSize.height = parseInt(metadata.videoHeight as string);
let avImageGenerator = await media.createAVImageGenerator();
if (avImageGenerator) {
avImageGenerator.fdSrc = file;
record.thumb =
await avImageGenerator.fetchFrameByTime(0, media.AVImageQueryOptions.AV_IMAGE_QUERY_CLOSEST_SYNC,
videoSize);
} else {
console.error('Create AVImageGenerator failed!');
}
mediaList.push(record)
} catch (e) {
console.error(e)
}
}
return Promise.resolve(mediaList)
} catch (err) {
console.error('video failed with err: ' + err);
return Promise.reject(err)
}
}
/**
* 获取相册中保存的图片
* @returns
*/
static async getImageList(): Promise<Array<MediaRecordEntity>> {
let mediaList = new Array<MediaRecordEntity>()
let imageUri = LocalMediaManager.getAllImages()
for (let i = 0; i < imageUri.length; i++) {
try {
let uri = imageUri[i]
let file = FileUtil.openSync(uri)
let record = new MediaRecordEntity(uri)
record.name = FileUtil.getFileName(uri)
mediaList.push(record)
} catch (e) {
console.error(e)
}
}
return Promise.resolve(mediaList)
}
/**
* 获取保存的音频
* @returns
*/
static async getAudioList(): Promise<Array<MediaRecordEntity>> {
try {
let mediaList = new Array<MediaRecordEntity>()
let audioUris = LocalMediaManager.getAllAudios()
for (let i = 0; i < audioUris.length; i++) {
let uri = audioUris[i]
let filePath = FileUtil.getFilePath(uri)
if (FileUtil.accessSync(filePath)) {
let record = new MediaRecordEntity(filePath)
record.name = FileUtil.getFileName(filePath)
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE)
let stat = fs.statSync(file.fd)
record.createTime = stat.ctime
let avMetadataExtractor = await media.createAVMetadataExtractor()
avMetadataExtractor.fdSrc = file
// 获取元数据promise模式
let result = await avMetadataExtractor.fetchMetadata()
record.duration = result.duration ? NumberUtil.toNumber(result.duration) : 0
avMetadataExtractor.release()
fs.close(file)
mediaList.push(record)
} else {
LocalMediaManager.delete(uri)
}
}
return Promise.resolve(mediaList)
} catch (err) {
console.error('audio failed with err: ' + err);
return Promise.reject(err)
}
}
}

View File

@ -0,0 +1,40 @@
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { AppUtil, FileUtil } from '@pura/harmony-utils';
export class ShareManager {
/**
* 分享文件
* @param path
*/
static shareFile(path: string) {
// 获取精准的utd类型
let ext = ''
let type = ''
if (path.endsWith('.mp4')) {
ext = '.mp4'
type = utd.UniformDataType.VIDEO
} else if (path.endsWith('.mp3')) {
ext = '.mp3'
type = utd.UniformDataType.AUDIO
} else if (path.endsWith('.jpeg')) {
ext = '.jpeg'
type = utd.UniformDataType.IMAGE
}
let utdTypeId = utd.getUniformDataTypeByFilenameExtension(ext, type);
let shareData: systemShare.SharedData = new systemShare.SharedData({
utd: utdTypeId,
uri: FileUtil.getUriFromPath(path)
});
let controller: systemShare.ShareController = new systemShare.ShareController(shareData);
controller.show(AppUtil.getContext(), {
selectionMode: systemShare.SelectionMode.SINGLE,
previewMode: systemShare.SharePreviewMode.DETAIL,
}).then(() => {
console.info('ShareController show success.');
}).catch((error: BusinessError) => {
console.error(`ShareController show error. code: ${error.code}, message: ${error.message}`);
});
}
}

View File

@ -0,0 +1,368 @@
import { ArrayUtil, PermissionUtil, StrUtil } from '@pura/harmony-utils';
import { apiService } from '../net/ApiService';
import { PrefUtils } from '../utils/PrefUtils';
import { JSON } from '@kit.ArkTS';
import { UserConfigEntity } from '../entity/UserConfigEntity';
import { instanceToPlain, plainToInstance } from 'class-transformer';
import { BannerEntity } from '../entity/BannerEntity';
import identifier from '@ohos.identifier.oaid';
import { LoginManager } from './LoginGlobalManager';
import { WxVideoConfigEntity } from '../entity/WxVideoConfig';
class UserConfigManager {
private INVALID_OAID = "00000000-0000-0000-0000-000000000000";
async getOaid(): Promise<string> {
try {
const isGranted = await PermissionUtil.checkRequestPermissions('ohos.permission.APP_TRACKING_CONSENT');
if (isGranted) {
const oaid = await identifier.getOAID();
if (StrUtil.isNotEmpty(oaid) && oaid !== this.INVALID_OAID) {
PrefUtils.put("oaid", oaid)
return Promise.resolve(oaid);
}
}
} catch (e) {
console.error(e);
}
return Promise.resolve('');
}
async getUserConfig(): Promise<void> {
if (StrUtil.isNotEmpty(PrefUtils.getString('userConfig'))) {
const str = PrefUtils.getString('userConfig');
const userConfig = plainToInstance(UserConfigEntity, JSON.parse(str));
this.saveUserConfig(userConfig);
return Promise.resolve()
} else {
return this.userConfig();
}
}
async userConfig(): Promise<void> {
try {
const oaid = PrefUtils.getString('oaid');
const result = await apiService.userConfig(oaid);
if (result.isSuccess()) {
const userConfig = plainToInstance(UserConfigEntity, result.data);
PrefUtils.put("userConfig", JSON.stringify(instanceToPlain(userConfig)))
this.saveUserConfig(userConfig);
return Promise.resolve();
} else {
return Promise.reject(result.message);
}
} catch (e) {
console.error(e);
return Promise.reject(e);
}
}
private saveUserConfig(config: UserConfigEntity) {
LoginManager.saveToken(config.token);
this.saveIsTemp(config.temp);
if (config.config !== null) {
this.saveGuideEnable(config.config.guideEnable);
this.saveGuideHint(config.config.guideHint);
this.saveGuidePayEnable(config.config.guidePayEnable);
this.saveNoLoginPayEnable(config.config.noLoginPayEnable);
this.savePayAgreementEnable(config.config.payAgreementEnable);
this.saveWxVideoEnable(config.config.wxVideoEnable);
this.savePlaybackEnable(config.config.playbackEnable);
this.saveLoginType(config.config.loginType);
this.saveWxVideoCourse(config.config.wxVideoCourse);
this.savePlaybackCourse(config.config.wxPlaybackCourse);
this.saveWxPlaybackJoinType(config.config.wxPlaybackJoinType);
this.saveWxVideoConfig(config.config.wxVideoConfig);
this.saveWxVideoServiceEnable(config.config.wxVideoServiceEnable);
this.saveDomainMap(config.config.domainMap);
this.saveCopyContainsList(config.config.copyContainsList);
this.saveHomeBanner(config.config.homeBanners);
}
}
/**
* 是否同意隐私政策
* @param agree
*/
saveIsAgreePrivacy(agree: boolean) {
PrefUtils.put('agree_privacy', agree);
}
isAgreePrivacy(): boolean {
return PrefUtils.getBoolean('agree_privacy');
}
/**
* 首次使用app
* @param isFirst
*/
saveFirstUse(isFirst: boolean) {
PrefUtils.put('is_first', isFirst);
}
isFirstUse(): boolean {
return PrefUtils.getBoolean('is_first', true);
}
/**
* 用户角色
* @param temp true临时用户 ,false登录用户
*/
saveIsTemp(temp: boolean) {
PrefUtils.put('x-role', temp);
}
isTemp(): boolean {
return PrefUtils.getBoolean('x-role', true);
}
/**
* 是否启用引导页
* @param enable
*/
saveGuideEnable(enable: boolean) {
PrefUtils.put('guide_enable', enable);
}
isGuideEnable(): boolean {
return PrefUtils.getBoolean('guide_enable', true);
}
/**
* 引导页提示语
* @param enable
*/
saveGuideHint(hint: string) {
PrefUtils.put('guide_hint', hint);
}
getGuideHint(): string {
return PrefUtils.getString('guide_hint');
}
/**
* 是否启用引导页支付
* @param enable
*/
saveGuidePayEnable(enable: boolean) {
PrefUtils.put('guide_pay_enable', enable);
}
isGuidePayEnable(): boolean {
return PrefUtils.getBoolean('guide_pay_enable', true);
}
/**
* 是否启用未登录支付
* @param enable
*/
saveNoLoginPayEnable(enable: boolean) {
PrefUtils.put('nologin_pay', enable);
}
isNoLoginPayEnable(): boolean {
return PrefUtils.getBoolean('nologin_pay', true);
}
/**
* 是否显示支付协议
* @param enable
*/
savePayAgreementEnable(enable: boolean) {
PrefUtils.put('pay_agreement', enable);
}
isPayAgreementEnable(): boolean {
return PrefUtils.getBoolean('pay_agreement', true);
}
/**
* 是否启用视频号功能
* @param enable
*/
saveWxVideoEnable(enable: boolean) {
PrefUtils.put('wx_video_enable', enable);
}
isWxVideoEnable(): boolean {
return PrefUtils.getBoolean('wx_video_enable', true);
}
/**
* 是否启用直播回放功能
* @param enable
*/
savePlaybackEnable(enable: boolean) {
PrefUtils.put('playback_enable', enable);
}
isPlaybackEnable(): boolean {
return PrefUtils.getBoolean('playback_enable', true);
}
/**
* 是否已绑定视频号助手
* @param isBind
*/
saveBindWxVideoHelper(isBind: boolean) {
PrefUtils.put('bind_wx_video', isBind);
}
isBindWxVideoHelper(): boolean {
return PrefUtils.getBoolean('bind_wx_video');
}
/**
* 是否已绑定直播回放助手
* @param isBind
*/
saveBindWxPlaybackHelper(isBind: boolean) {
PrefUtils.put('bind_wx_playback', isBind);
}
isBindWxPlaybackHelper(): boolean {
return PrefUtils.getBoolean('bind_wx_playback');
}
/**
* 支持的登录类型
* @param array
*/
saveLoginType(array?: Array<string>) {
if (ArrayUtil.isNotEmpty(array)) {
PrefUtils.put('login_type', JSON.stringify(array));
}
}
getLoginType(): Array<string> {
const str = PrefUtils.getString('login_type');
if (StrUtil.isNotEmpty(str)) {
return JSON.parse(str) as Array<string>;
}
return new Array('phone');
}
/**
* 视频号教程链接
* @param url
*/
saveWxVideoCourse(url?: string) {
if (StrUtil.isNotEmpty(url)) {
PrefUtils.put('wx_video_course', url!!);
}
}
getWxVideoCourse(): string {
return PrefUtils.getString('wx_video_course');
}
/**
* 直播回放教程链接
* @param url
*/
savePlaybackCourse(url?: string) {
if (StrUtil.isNotEmpty(url)) {
PrefUtils.put('playback_course', url!!);
}
}
getPlaybackCourse(): string {
return PrefUtils.getString('playback_course');
}
/**
* 直播回放加群方式
* @param type
*/
saveWxPlaybackJoinType(type?: string) {
if (StrUtil.isNotEmpty(type)) {
PrefUtils.put('playback_join_type', type!!);
}
}
getPlaybackJoinType(): string {
return PrefUtils.getString('playback_join_type');
}
/**
* 直播回放小程序跳转配置
* @param config
*/
saveWxVideoConfig(config: WxVideoConfigEntity) {
PrefUtils.put('wx_video_config', JSON.stringify(config))
}
getWxVideoConfig(): WxVideoConfigEntity | null {
const str = PrefUtils.getString('wx_video_config')
if (StrUtil.isNotEmpty(str)) {
return JSON.parse(str) as WxVideoConfigEntity
}
return null
}
/**
* 视频号助手是否可用
* @param enable
*/
saveWxVideoServiceEnable(enable: boolean) {
PrefUtils.put("wx_video_service_enable", enable)
}
isWxVideoServiceEnable(): boolean {
return PrefUtils.getBoolean('wx_video_service_enable', true)
}
/**
* 网站主机名
* @param domains
*/
saveDomainMap(domains: object) {
if (domains) {
PrefUtils.put('domain_map', JSON.stringify(domains));
}
}
getDomainMap(): object {
const str = PrefUtils.getString('domain_map');
if (StrUtil.isNotEmpty(str)) {
return JSON.parse(str)!!
}
return new Object();
}
/**
* 链接识别配置
* @param array
*/
saveCopyContainsList(array: Array<string>) {
PrefUtils.put('copy_contains_list', JSON.stringify(array))
}
getCopyContainsList(): Array<string> {
let array = PrefUtils.getStringArray('copy_contains_list')
if (array.length !== 0) {
return array
}
return ['http://', 'https://']
}
/**
* 首页banner
* @param array
*/
saveHomeBanner(array?: Array<BannerEntity>) {
if (ArrayUtil.isNotEmpty(array)) {
PrefUtils.put('home_banner', JSON.stringify(array));
}
}
getHomeBanner(): Array<BannerEntity> {
const str = PrefUtils.getString('home_banner');
if (StrUtil.isNotEmpty(str)) {
return JSON.parse(str) as Array<BannerEntity>;
}
return new Array();
}
}
export const ConfigManager = new UserConfigManager()

View File

@ -0,0 +1,116 @@
export class Api {
/**
* 获取用户配置信息
*/
static readonly USER_CONFIG = '/api/user/config';
/**
* 用户基本信息
*/
static readonly USERINFO = '/api/user';
/**
* 事件上报
*/
static readonly EVENT_REPORT = '/api/user/event'
/**
* 图片上传
*/
static readonly UPLOAD_IMAGE = '/api/user/upload'
/**
* 意见反馈
*/
static readonly FEEDBACK = '/api/user/feedback'
/**
* 发送验证码
*/
static readonly SEND_CODE = '/api/user/code';
/**
* 用户登录
*/
static readonly LOGIN = '/api/user/login';
/**
* 扫码登录
*/
static readonly QRCODE_LOGIN = '/api/app/code';
/**
* 用户注销
*/
static readonly USER_DESTROY = '/api/user/destroy';
/**
* 用户账号列表
*/
static readonly ACCOUNT_LIST = '/api/user/account';
/**
* 套餐列表
*/
static readonly GOODS_LIST = '/api/order/goods';
/**
* 创建订单
*/
static readonly CREATE_ORDER = '/api/order';
/**
* 微信客服
*/
static readonly WX_SERVICE = '/api/weixin/service'
/**
* 钻石信息
*/
static readonly USER_DIAMOND_INFO = '/api/diamond'
/**
* 权限验证
*/
static readonly USER_AUTH = '/api/user/auth';
/**
* 首页顶部通知
*/
static readonly NOTICE_LIST = '/api/user/notice';
/**
* 链接提取
*/
static readonly MATERIAL_INFO = '/api/material';
/**
* 视频号和直播回放提取
*/
static readonly WX_VIDEO = '/api/weixin/video/log';
/**
* 跳转至微信发送视频号给客服
*/
static readonly WX_VIDEO_SERVICE = '/api/weixin/video/service'
/**
* 绑定微信用户信息
*/
static readonly BIND_WX_USER_INFO = '/api/weixin/user/info'
/**
* 下载记录
*/
static readonly DOWNLOAD_HISTORY_LIST = '/api/material/log'
/**
* 素材列表
*/
static readonly MATERIAL_LIST = '/api/user/mat/search'
/**
* 素材分类列表
*/
static readonly MATERIAL_CATE_LIST = '/api/mat/cate'
}

View File

@ -0,0 +1,390 @@
import { Api } from './Api';
import { AxiosRequest } from './AxiosRequest';
import { HttpResult } from './HttpResult';
import { deviceInfo } from '@kit.BasicServicesKit';
import { DeviceUtil, FileUtil } from '@pura/harmony-utils';
import { LoginRequest } from './request/LoginRequest';
import { FormData } from '@ohos/axios';
import { fileIo } from '@kit.CoreFileKit';
import { JSON } from '@kit.ArkTS';
class ApiService {
/**
* 用户配置
* @param oaid
* @returns
*/
userConfig(oaid: string): Promise<HttpResult> {
const params: Record<string, string> = {
'oaid': oaid,
'os_version': deviceInfo.buildVersion.toString(),
'imei': DeviceUtil.getDeviceId(),
'cid': ''
}
return AxiosRequest.get<HttpResult>(Api.USER_CONFIG, params)
}
/**
* 用户信息
* @returns
*/
userinfo(): Promise<HttpResult> {
return AxiosRequest.get<HttpResult>(Api.USERINFO)
}
/**
* 更新用户信息
* @returns
*/
updateUserinfo(params: Record<string, string>): Promise<HttpResult> {
return AxiosRequest.put<HttpResult>(Api.USERINFO, params)
}
/**
* 事件上报
* @param key
* @param value
* @param extra
* @returns
*/
eventReport(key: string, value: string, extra: string): Promise<HttpResult> {
const params: Record<string, string> = {
'source': 'android',
'type': 'click',
'key': key,
'value': value,
'extra': extra
}
return AxiosRequest.post<HttpResult>(Api.EVENT_REPORT, params);
}
/**
* 图片上传
* @returns
*/
uploadImage(base64: string, scene: string): Promise<HttpResult> {
const data: Record<string, string> = {
'file': base64
}
const params: Record<string, string> = {
'type': 'base64',
'scene': scene
}
return AxiosRequest.request({
url: Api.UPLOAD_IMAGE,
method: 'post',
data: data,
params: params // query参数
})
}
feedback(type: string, content: string, contact: string, images: Array<string>): Promise<HttpResult> {
const params: Record<string, Object> = {
'type': type,
'content': content,
'contact': contact,
'images': images
}
return AxiosRequest.post(Api.FEEDBACK, params)
}
/**
* 发送验证码
* @returns
*/
sendCode(phone: string): Promise<HttpResult> {
const params: Record<string, string> = { 'phone': phone }
return AxiosRequest.post<HttpResult>(Api.SEND_CODE, params)
}
/**
* 手机号登录
* @returns
*/
loginByPhone(phone: string, code: string, timestamp: string, isBind: boolean = false): Promise<HttpResult> {
const params: LoginRequest = {
login_type: 'phone',
phone: {
'phone': phone,
'code': code,
'timestamp': timestamp
},
bind: isBind
}
return AxiosRequest.post<HttpResult>(Api.LOGIN, params)
}
/**
* 微信登录
* @returns
*/
loginByWX(code: string, isBind: boolean = false): Promise<HttpResult> {
const params: LoginRequest = {
login_type: 'weixin',
weixin: {
'code': code,
'code_type': ''
},
bind: isBind
}
return AxiosRequest.post<HttpResult>(Api.LOGIN, params)
}
/**
* 微信登录
* @returns
*/
loginByCode(code: string): Promise<HttpResult> {
const params: Record<string, string> = { 'key': code }
return AxiosRequest.put<HttpResult>(Api.QRCODE_LOGIN, params)
}
/**
* 注销账户
* @returns
*/
userDestroy(): Promise<HttpResult> {
return AxiosRequest.post<HttpResult>(Api.USER_DESTROY)
}
/**
* 解绑账号
* @returns
*/
unbind(loginType: string): Promise<HttpResult> {
const params: LoginRequest = {
login_type: loginType,
unbind: true
}
return AxiosRequest.post<HttpResult>(Api.LOGIN, params);
}
/**
* 账号列表
* @returns
*/
accountList(scene: string): Promise<HttpResult> {
const params: Record<string, string> = { 'scene': scene }
return AxiosRequest.get<HttpResult>(Api.ACCOUNT_LIST, params);
}
/**
* 切换账户
* @param userId
* @returns
*/
changeAccount(userId: string): Promise<HttpResult> {
const params: LoginRequest = {
login_type: 'device',
device: {
'user_id': userId
}
}
return AxiosRequest.post<HttpResult>(Api.LOGIN, params)
}
/**
* 套餐列表
* @returns
*/
goodsList(type: string): Promise<HttpResult> {
const params: Record<string, string> = { 'type': type }
return AxiosRequest.get<HttpResult>(Api.GOODS_LIST, params)
}
/**
* 创建订单
* @returns
*/
createOrder(goodsId: string, payType: string, source: string, coupon: string): Promise<HttpResult> {
const params: Record<string, string> = {
'goods_id': goodsId,
'pay_type': payType,
'source': source,
'pay_source': 'app',
'coupon': coupon
}
return AxiosRequest.post<HttpResult>(Api.CREATE_ORDER, params)
}
/**
* 查询订单
* @param orderId
* @returns
*/
getOrderInfo(orderId: string): Promise<HttpResult> {
const params: Record<string, string> = { 'order_id': orderId}
return AxiosRequest.get<HttpResult>(Api.CREATE_ORDER, params)
}
/**
* 微信客服
* @returns
*/
wxService(): Promise<HttpResult> {
return AxiosRequest.get<HttpResult>(Api.WX_SERVICE)
}
/**
* 钻石信息
*/
getDiamondInfo(): Promise<HttpResult> {
return AxiosRequest.get(Api.USER_DIAMOND_INFO)
}
/**
* 检查权限
* @param scene wechat download
* @returns
*/
checkPermission(scene: string): Promise<HttpResult> {
const params: Record<string, string> = { 'scene': scene }
return AxiosRequest.get<HttpResult>(Api.USER_AUTH, params)
}
/**
* 上报权限
* @param scene
* @param count
* @returns
*/
sendCheckPermission(scene: string, count: number): Promise<HttpResult> {
const params: Record<string, Object> = { 'scene': scene, 'count': count }
return AxiosRequest.post<HttpResult>(Api.USER_AUTH, params)
}
/**
* 首页顶部通知
* @returns
*/
noticeList(): Promise<HttpResult> {
return AxiosRequest.get<HttpResult>(Api.NOTICE_LIST)
}
/**
* 发送链接
* @returns
*/
getMaterialInfo(content: string): Promise<HttpResult> {
const params: Record<string, string> = { 'content': content }
return AxiosRequest.post<HttpResult>(Api.MATERIAL_INFO, params)
}
/**
* 获取提取状态信息
* @returns
*/
analysisMaterial(logId: string): Promise<HttpResult> {
const params: Record<string, string> = { 'logid': logId }
return AxiosRequest.get<HttpResult>(Api.MATERIAL_INFO, params)
}
/**
* 获取视频号和直播回放
* @returns
*/
wxVideoList(scene: string): Promise<HttpResult> {
const params: Record<string, string> = { 'v': 'v2', 'scene': scene }
return AxiosRequest.get<HttpResult>(Api.WX_VIDEO, params)
}
/**
* 删除视频号和直播回放
* @returns
*/
deleteWxVideo(logId: string): Promise<HttpResult> {
const params: Record<string, string> = { 'logId': logId }
return AxiosRequest.delete<HttpResult>(Api.WX_VIDEO, params)
}
/**
* 上报下载状态
*/
reportDownLoadStatus(logId: string, status: string, size: string, message: string): Promise<HttpResult> {
const params: Record<string, string> = {
'logid': logId,
'status': status,
'size': size,
'message': message
}
return AxiosRequest.put<HttpResult>(Api.MATERIAL_INFO, params)
}
/**
* 跳转至微信发送视频号给客服
*/
wxVideoService(): Promise<HttpResult> {
return AxiosRequest.get<HttpResult>(Api.WX_VIDEO_SERVICE)
}
/**
* 跳转至微信发送视频号给客服
*/
bindWxUserInfo(code: string): Promise<HttpResult> {
return AxiosRequest.request({
url: Api.BIND_WX_USER_INFO,
method: 'post',
params: { code: code } // query参数
})
}
/**
* 获取下载记录
* @returns
*/
getDownloadHistoryList(page: string, startTime: string, endTime: string): Promise<HttpResult> {
const params: Record<string, string> = {
'status': '2',
'is_deleted': '0',
'page': page,
'size': '20',
'start_time': startTime,
'end_time': endTime
}
return AxiosRequest.get<HttpResult>(Api.DOWNLOAD_HISTORY_LIST, params)
}
/**
* 删除下载记录
* @returns
*/
deleteDownloadHistory(startTime: string, endTime: string): Promise<HttpResult> {
const params: Record<string, string> = {
'status': '2',
'is_deleted': '0',
'page': '1',
'size': '9999',
'start_time': startTime,
'end_time': endTime
}
return AxiosRequest.delete<HttpResult>(Api.DOWNLOAD_HISTORY_LIST, params)
}
/**
* 获取素材列表
* @returns
*/
getMaterialList(page: string, cateId: string = '', keywords: string = ''): Promise<HttpResult> {
const params: Record<string, string> = {
'page': page,
'size': '20',
'cate_id': cateId,
'keywords': keywords
}
return AxiosRequest.get<HttpResult>(Api.MATERIAL_LIST, params)
}
/**
* 获取素材分类列表
* @returns
*/
getMaterialCateList(page: string): Promise<HttpResult> {
const params: Record<string, string> = {
'page': page,
'size': '50'
}
return AxiosRequest.get<HttpResult>(Api.MATERIAL_CATE_LIST, params)
}
}
export const apiService = new ApiService();

View File

@ -0,0 +1,168 @@
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse,
FormData,
InternalAxiosRequestConfig } from '@ohos/axios';
import BuildProfile from 'BuildProfile';
import { Constants } from '../common/Constants';
import deviceInfo from '@ohos.deviceInfo';
import { AppUtil, DeviceUtil, JSONUtil, MD5, RandomUtil, StrUtil } from '@pura/harmony-utils';
import systemDateTime from '@ohos.systemDateTime';
import { JSON } from '@kit.ArkTS';
import { AESpkcs7paddingUtil } from '../utils/AESpkcs7paddingUtil';
import { HttpResult } from './HttpResult';
import { plainToInstance } from 'class-transformer';
import { LoginManager } from '../manager/LoginGlobalManager';
import { router } from '@kit.ArkUI';
import { RouterUrls } from '../common/RouterUrls';
import { EventConstants } from '../common/EventConstants';
const instance = axios.create({
baseURL: BuildProfile.DEBUG ? Constants.TEST_URL : Constants.BASE_URL,
timeout: 20000
});
// 请求头拦截器
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.headers.set('x-token', LoginManager.getToken())
config.headers.set('x-app-id', Constants.APP_ID)
config.headers.set('x-device-id', DeviceUtil.getDeviceId())
config.headers.set('x-version', BuildProfile.VERSION_NAME)
config.headers.set('x-mobile-brand', deviceInfo.brand)
config.headers.set('x-mobile-model', deviceInfo.productModel)
config.headers.set('x-base-version', BuildProfile.VERSION_NAME)
config.headers.set('x-package', 'com.yuan.scmf')
config.headers.set('x-platform', 'android')
config.headers.set('x-channel', 'scmf_hmos')
return config;
}, (error: AxiosError) => {
return Promise.reject(error);
});
// 请求内容拦截器
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
let url = config.url;
if (!url?.includes("user/upload")) {
let method = config.method?.toLowerCase();
if (method === "post" || method === "put") {
config.headers.set("Content-Type", "application/json; charset=utf-8");
}
}
return config;
}, (error: AxiosError) => {
return Promise.reject(error);
});
// 请求内容加密拦截器
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
if (config.baseURL === Constants.BASE_URL) {
config.params = config.params || {};
config.params.nonce = RandomUtil.generateUUID36()
config.params.timestamp = Math.trunc(systemDateTime.getTime() / 1000)
let paramsMap = JSONUtil.jsonToMap(JSON.stringify(config.params));
let arrayMap = Array.from(paramsMap);
arrayMap.sort((a, b) => {
return a[0].localeCompare(b[0])
})
paramsMap = new Map(arrayMap)
let sortQueryString = "";
paramsMap.forEach((value, key) => {
sortQueryString += key + "=" + value + "&"
})
sortQueryString = sortQueryString.substring(0, sortQueryString.length - 1)
let signature = MD5.digestSync(sortQueryString + '&' + MD5.digestSync(Constants.SIGNATURE));
let method = config.method?.toLowerCase();
if (method === "post" || method === "put") {
if (config.data) {
let dataStr = JSON.stringify(config.data);
if (StrUtil.isNotEmpty(dataStr)) {
signature = MD5.digestSync(sortQueryString + '&' + dataStr + "&" + MD5.digestSync(Constants.SIGNATURE));
}
}
}
config.params.signature = signature;
}
return config;
}, (error: AxiosError) => {
return Promise.reject(error);
});
// 响应拦截器
instance.interceptors.response.use((response: AxiosResponse) => {
if (response.config.baseURL === (BuildProfile.DEBUG ? Constants.TEST_URL : Constants.BASE_URL)) {
try {
let contentType = response.headers['content-type'] as string;
let dataString: string = '';
if (StrUtil.isNotEmpty(contentType) && contentType?.includes("application/json")) {
if (response.data !== null) {
let isEncrypt = response.data['encrypt'] as boolean
dataString = response.data['data'] as string;
if (isEncrypt) {
dataString = AESpkcs7paddingUtil.decryptNormal(dataString, Constants.ENCRYPT);
let decData = JSON.parse(dataString) as Record<string, Object>;
let decCode = decData['code'] as number;
switch (decCode) {
case 11018: {
// 刷新首页
AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent);
AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent);
break;
}
case 19000: {
// 跳转Vip页面
router.push({url: RouterUrls.VIP_PAGE, params: { origin: response.config.url }})
break;
}
case 1001003: {
}
case 1001004: {
}
case 1001005: {
// 跳转登录
LoginManager.logout()
router.push({url: RouterUrls.LOGIN_PAGE})
break;
}
}
}
}
}
response.data = JSON.parse(dataString);
console.error('AxiosResponse' + JSON.stringify(response.data))
return plainToInstance(HttpResult, JSON.parse(dataString));
} catch (e) {
console.log(e);
return new HttpResult(-1, e);
}
}
return response.data;
}, (error: AxiosError) => {
// 响应失败
return Promise.reject(error);
});
export class AxiosRequest {
// 万能请求
static request<T>(config: AxiosRequestConfig): Promise<T> {
return instance.request<null, T>(config)
}
// get请求
static get<T>(url: string, params?: object): Promise<T> {
return instance.get<null, T>(url, { params });
}
// post请求
static post<T>(url: string, data?: object): Promise<T> {
return instance.post<null, T>(url, data);
}
// put请求
static put<T>(url: string, data?: object): Promise<T> {
return instance.put<null, T>(url, data);
}
// delete请求
static delete<T>(url: string, params?: object): Promise<T> {
return instance.delete<null, T>(url, { params });
}
}

View File

@ -0,0 +1,15 @@
export class HttpResult {
code: number = 0;
message: string = ""
data?: object = undefined;
constructor(code: number, message: string, data?: object) {
this.code = code;
this.message = message;
this.data = data;
}
isSuccess(): boolean {
return this.code === 0;
}
}

View File

@ -0,0 +1,426 @@
import { AppUtil, FileUtil, JSONUtil, StrUtil } from '@pura/harmony-utils';
import { MediaEntity, VideoMaterial } from '../entity/MaterialInfoEntity';
import url from '@ohos.url';
import { Constants } from '../common/Constants';
import axios, { AxiosHeaders, AxiosResponse, AxiosResponseHeaders } from '@ohos/axios';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { MP4Parser } from '@ohos/mp4parser';
import { JSON } from '@kit.ArkTS';
export interface DownloadCallback {
onGetTotal?:(total: number) => void;
onProgress?:(progress: number) => void;
onPause?:() => void;
onMerge?:(step: number) => void;
onSuccess?:(filePath: string) => void;
onCancel?:() => void;
onFailed?:(err: string) => void;
}
export class MediaDownloader {
static getInstance(): MediaDownloader {
return new MediaDownloader();
}
/**
* 缓存文件夹
*/
private cacheDir = FileUtil.getCacheDirPath() + FileUtil.separator;
/**
* 任务对象
*/
private task?: request.agent.Task | null
/**
* 下载回调
*/
private mCallback?: DownloadCallback;
/**
* 文件名
*/
private fileName = '';
/**
* 代理池
*/
private proxyList = new Array<string>();
callback(callback: DownloadCallback): MediaDownloader {
this.mCallback = callback;
return this;
}
createHeaders(map?: Map<string, Object>): Record<string, string> {
const headers: Record<string, string> = {};
map?.forEach((value, key) => {
headers[key] = value.toString();
})
return headers;
}
setProxyList(list?: Array<string>): MediaDownloader {
if (list && list.length > 0) {
this.proxyList = list
}
return this
}
async down(media: MediaEntity) {
this.fileName = media.name;
if (media.isM3u8) {
// this.doM3u8Down();
this.doException('暂不支持')
} else if (media.isThreading) {
// this.doThreadingDown();
this.doMediaDown(media, media.isMerge ? 1 : 0);
} else {
this.doMediaDown(media, media.isMerge ? 1 : 0);
}
}
async doMediaDown(media: MediaEntity, step: number) {
if (media instanceof VideoMaterial && step === 0) {
await this.decodeWxVideo(media)
}
let url = media.origin;
if (step === 2 && media instanceof VideoMaterial) {
url = media.speed_up === 1 || media.isThreading ? media.audio!!.url : media.audio!!.origin_url;
} else {
url = media.speed_up === 1 || media.isThreading ? media.url : media.origin_url;
}
let headers: Record<string, string> = {}
if (media.headers) {
headers = this.createHeaders(JSONUtil.jsonToMap(JSON.stringify(media.headers)));
}
headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0'
headers['Accept'] = '*/*'
let isCanContinue = false;
/*if (step === 0 || step === 1) {
isCanContinue = await this.isCanContinue(url, media.currentLen)
media.continueDownload = true;
} else if (step === 2 && media instanceof VideoMaterial && media.audio) {
isCanContinue = await this.isCanContinue(url, media.audio.currentLen)
media.continueDownload = true;
}*/
if (step === 0 && FileUtil.accessSync(this.cacheDir + this.fileName) && isCanContinue) {
headers['Range'] = `bytes=${media.currentLen}-`;
} else if (step == 1 && FileUtil.accessSync(this.cacheDir + media.cacheName) &&
(isCanContinue || media.totalSize !== 0 && media.currentLen === media.totalSize)) {
if (media instanceof VideoMaterial && media.audio && media.totalSize !== 0 && media.currentLen === media.totalSize) {
this.doMerge(4) // 音频提取中
this.doMediaDown(media, 2)
} else {
headers['Range'] = `bytes=${media.currentLen}-`;
}
} else if (step === 2 && media instanceof VideoMaterial && media.audio && FileUtil.accessSync(this.cacheDir + media.audio.cacheName) &&
(isCanContinue || media.audio.totalSize !== 0 && media.audio.currentLen === media.audio.totalSize)) {
if (media.audio.totalSize !== 0 && media.audio.currentLen == media.audio.totalSize) {
this.doMerge(5) // 音视频合并中
let videoPath = this.cacheDir + media.cacheName
let audioPath = this.cacheDir + media.audio.cacheName
if (FileUtil.accessSync(videoPath) && FileUtil.accessSync(audioPath)) {
this.mergeVideoAndAudio(videoPath, audioPath, url)
} else {
throw Error('file not exists')
}
} else {
headers['Range'] = `bytes=${media.audio.currentLen}-`;
}
} else {
if (step === 0 || step === 1) {
media.currentLen = 0
} else if (step === 2 && media instanceof VideoMaterial && media.audio) {
media.audio.currentLen = 0
}
}
if (this.canceled) return
try {
let filePath = ''
if (step == 1 && media instanceof VideoMaterial) {
filePath = this.cacheDir + media.cacheName
} else if (step == 2 && media instanceof VideoMaterial) {
filePath = this.cacheDir + media.audio!!.cacheName
} else {
filePath = this.cacheDir + this.fileName
}
let config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: url,
overwrite: true,
saveas: filePath,
headers: headers,
gauge: true,
priority:0
}
let total = 0;
request.agent.create(AppUtil.getApplicationContext(), config)
.then((task: request.agent.Task) => {
this.task = task
task.start((err: BusinessError) => {
if (err) {
console.error(err.message);
this.doException(err.message);
return;
}
});
task.on('progress', (progress) => {
if (total === 0) {
total = progress.sizes[0] as number;
if (step === 0 || step === 1) {
total += media.currentLen
media.totalSize = total
} else if (step === 2 && media instanceof VideoMaterial && media.audio) {
total += media.audio.currentLen
media.audio.totalSize = total
}
this.doGetTotal(total);
console.debug(`total: ${progress.sizes[0].toString()}`)
}
if (step === 0 || step === 1) {
media.currentLen = progress.processed
} else if (step === 2 && media instanceof VideoMaterial && media.audio) {
media.audio.currentLen = progress.processed
}
this.doProgress(progress.processed);
console.debug(`progress: ${progress.processed.toString()}`);
})
task.on('completed', () => {
console.debug('download completed');
if (step == 1 && media instanceof VideoMaterial && media.audio) {
this.doMerge(4) // 音频提取中
this.doMediaDown(media, 2)
} else if (step == 2 && media instanceof VideoMaterial) {
this.doMerge(5) // 音视频合并中
let videoPath = this.cacheDir + media.cacheName
let audioPath = this.cacheDir + media.audio!!.cacheName
if (FileUtil.accessSync(videoPath) && FileUtil.accessSync(audioPath)) {
this.mergeVideoAndAudio(videoPath, audioPath, url)
} else {
this.doException('文件不存在')
}
} else {
this.doSuccess(this.cacheDir + this.fileName);
}
request.agent.remove(task.tid);
})
task.on('failed', (progress) => {
console.log('download failed');
request.agent.show(task.tid).then((taskInfo: request.agent.TaskInfo) => {
request.agent.remove(task.tid);
if (this.proxyList.length > 0 && this.replaceUrlWithProxy(media)) {
this.doMediaDown(media, step)
} else {
this.doException(taskInfo.reason);
}
}).catch((err: BusinessError) => {
this.doException(err.message);
request.agent.remove(task.tid);
});
});
})
.catch((err: BusinessError) => {
console.error(err.message);
this.doException(err.message);
})
} catch (e) {
console.error(e);
this.doException(e);
}
}
async mergeVideoAndAudio(videoPath: string, audioPath: string, url: string) {
let outputPath = this.cacheDir + this.fileName
if (FileUtil.accessSync(outputPath)) {
FileUtil.unlinkSync(outputPath)
}
let cmd = `ffmpeg -i ${videoPath} -i ${audioPath} -c copy ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.doSuccess(outputPath)
} else {
this.doException('合并失败')
}
}
})
}
async fixM4s(filePath: string) {
const file = FileUtil.openSync(filePath, 0o2);
const stat = FileUtil.lstatSync(filePath);
const buffer = new ArrayBuffer(stat.size);
FileUtil.readSync(file.fd, buffer);
FileUtil.fsyncSync(file.fd);
FileUtil.closeSync(file.fd);
let bytes = new Uint8Array(buffer)
if (bytes.length > 9) {
bytes = bytes.subarray(9)
FileUtil.writeEasy(filePath, bytes.buffer, false)
}
}
async decodeWxVideo(video: VideoMaterial) {
if (!video.url.includes("wechatDecode=batiao")) {
return
}
try {
const parseUrl = url.URL.parseURL(video.url);
const realUrl = parseUrl.params.get('url') as string;
const decodeKey = parseUrl.params.get("decodeKey");
const keyUrl = Constants.BASE_URL + "/api/weixin/video/key?decodeKey=" + decodeKey;
const response: AxiosResponse = await axios({
url: keyUrl,
method: 'get',
responseType: 'array_buffer',
})
if (response.status === 200) {
video.url = realUrl;
video.origin_url = realUrl;
video.decodeKey = new Int8Array(response.data)
}
} catch (e) {
console.error(e);
}
}
async isCanContinue(url: string, start: number): Promise<boolean> {
try {
const headers = new AxiosHeaders();
headers.set('RANGE', `bytes=${start}-`);
headers.set('Connection', 'close');
const response: AxiosResponse = await axios({
url: url,
method: 'get',
headers: headers,
responseType: 'array_buffer'
})
if (response.status === 206) { //支持
return Promise.resolve(true);
} else {
const acceptRangesHeaderValue = (response.headers as AxiosResponseHeaders).get('Accept-Ranges') as string
if ("bytes" === acceptRangesHeaderValue?.toLowerCase()) { //支持
return Promise.resolve(true);
} else { //不支持
return Promise.resolve(false);
}
}
} catch (e) {
console.log(e);
return Promise.resolve(false);
}
}
replaceUrlWithProxy(media: MediaEntity): boolean {
try {
let vUrl = url.URL.parseURL(media.url)
let domainAndPort = vUrl?.host ? vUrl?.host : ''
for (let i = 0; i < this.proxyList.length; i++) {
const proxy = this.proxyList[i];
if (i < this.proxyList.length - 1) {
if (proxy.includes(domainAndPort)) {
domainAndPort = this.proxyList[i + 1]
break
}
} else return false
}
// 获取原始 URL 的路径和查询参数
media.url = domainAndPort + vUrl?.pathname + vUrl?.search
if (media instanceof VideoMaterial && media.audio) {
let aUrl = url.URL.parseURL(media.audio.url)
media.audio!!.url = domainAndPort + aUrl?.pathname + aUrl?.search
}
return true
} catch (e) {
console.error(e)
return false
}
}
getDomainFromUrl(urlString: string): string {
try {
let parseURL = url.URL.parseURL(urlString)
return parseURL.protocol + "//" + parseURL.host
} catch (e) {
return ''
}
}
getUrlFileExt(srcUrl: string): string {
try {
let url = srcUrl.substring(0, srcUrl.indexOf('?'))
if (url) {
return url.substring(url.lastIndexOf('.') + 1)
}
} catch (e) {
console.error(e)
}
return ''
}
private canceled = false
async cancel() {
this.canceled = true
if (this.task) {
this.task.stop()
.then(() => {
this.task = null
this.doCancel()
})
.catch((e: Error) => {
this.doException(e.message)
})
}
}
doGetTotal(total: number) {
if (this.mCallback?.onGetTotal) {
this.mCallback.onGetTotal(total);
}
}
doProgress(progress: number) {
if (this.mCallback?.onProgress) {
this.mCallback.onProgress(progress);
}
}
doPause() {
if (this.mCallback?.onPause) {
this.mCallback.onPause();
}
}
doMerge(step: number) {
if (this.mCallback?.onMerge) {
this.mCallback.onMerge(step);
}
}
doSuccess(filePath: string) {
if (this.mCallback?.onSuccess) {
this.mCallback.onSuccess(filePath)
}
}
doCancel() {
if (this.mCallback?.onCancel) {
this.mCallback.onCancel();
}
}
doException(err: string) {
if (this.mCallback?.onFailed) {
this.mCallback.onFailed(err);
}
}
}

View File

@ -0,0 +1,9 @@
export interface LoginRequest {
login_type: string;
bind?: boolean;
unbind?: boolean;
phone?: Record<string, Object>;
weixin?: Record<string, Object>;
device?: Record<string, Object>;
}

View File

@ -0,0 +1,237 @@
import { TipDialog } from '../../dialog/TipDialog'
import { ShareManager } from '../../manager/ShareManager'
import { TitleBar } from '../../view/TitleBar'
import { ToastUtils } from '../../utils/ToastUtils'
import { AppUtil } from '@pura/harmony-utils'
import { EventConstants } from '../../common/EventConstants'
import { MediaAction, MediaType } from '../../manager/MediaManager'
import { media } from '@kit.MediaKit'
import { fileIo } from '@kit.CoreFileKit'
import { LocalMediaManager } from '../../manager/LocalMediaManager'
import { avSessionManager } from '../../manager/AVSessionManager'
@Entry
@ComponentV2
struct AudioPlayerPage {
@Local title: string = ''
@Local uri: string = ''
@Local showActions: boolean = false
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
avPlayer?: media.AVPlayer;
aboutToAppear(): void {
this.initParams()
this.initPlayer()
}
onPageHide(): void {
if (this.avPlayer) {
this.avPlayer.pause()
}
}
aboutToDisappear(): void {
if (this.avPlayer) {
this.avPlayer.release()
}
}
initParams() {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params) {
this.title = params.title as string
this.uri = params.uri as string;
this.showActions = params.showActions as boolean
}
}
async initPlayer() {
this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
this.setAVPlayerCallback();
// 打开相应的资源文件地址获取fd
let file = await fileIo.open(this.uri);
this.avPlayer.url = 'fd://' + file.fd;
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
// 注册avplayer回调函数
setAVPlayerCallback() {
this.avPlayer!!.on('error', (err) => {
console.error(`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`);
// 调用reset重置资源触发idle状态
this.isPlaying = false
this.avPlayer!!.reset();
avSessionManager.deactivate()
})
// 状态机变化回调函数
this.avPlayer!!.on('stateChange', async (state, reason) => {
switch (state) {
case 'initialized':
console.info('资源初始化完成');
// 资源初始化完成,开始准备文件
this.avPlayer!!.prepare();
break;
case 'prepared':
console.info('资源准备完成');
// 资源准备完成,开始准备文件
this.durationTime = Math.trunc(this.avPlayer!!.duration / 1000)
this.currentTime = this.avPlayer!!.currentTime;
await avSessionManager.activate()
this.avPlayer!!.play();
break;
case 'completed':
console.info('播放完成');
this.isPlaying = false
this.avPlayer!!.off('bufferingUpdate')
AppStorage.setOrCreate('currentTime', this.durationTime);
avSessionManager.deactivate()
break;
case 'playing':
console.info('播放开始');
this.isPlaying = true
break;
case 'released':
case 'stopped':
case 'error':
case 'paused':
console.info('播放暂停');
this.isPlaying = false
avSessionManager.deactivate()
break;
}
})
// 时间上报监听函数
this.avPlayer!!.on('timeUpdate', (time: number) => {
this.currentTime = Math.trunc(time / 1000);
});
}
build() {
Column() {
TitleBar().width('100%')
RelativeContainer() {
Image($r('app.media.ic_audio_thumb'))
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.avPlayer!!.pause()
} else {
await avSessionManager.activate()
this.avPlayer!!.play()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.avPlayer!!.seek(value * 1000, 2); // 设置视频播放的进度跳转到value处
this.currentTime = value;
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.margin({ bottom: this.showActions ? 0 : 50})
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}.layoutWeight(1)
Row() {
Column() {
Image($r('app.media.ic_action_share')).width(50).height(50)
Text('转发').fontSize(12).fontColor($r('app.color.color_50ffffff')).margin({ top: 13 })
}
.id('btn_share')
.alignRules({
right: { anchor: '__container__', align: HorizontalAlign.Center }
})
.margin({ right: 40 })
.onClick(() => {
ShareManager.shareFile(this.uri)
})
Column() {
Image($r('app.media.ic_action_delete')).width(50).height(50)
Text('删除').fontSize(12).fontColor($r('app.color.color_50ffffff')).margin({ top: 13 })
}
.id('btn_delete')
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Center },
})
.margin({ left: 40 })
.onClick(() => {
this.avPlayer!!.pause()
TipDialog.show(this.getUIContext(), {
title: '提示', content: '确定删除该音频?', callback: {
confirm: () => {
fileIo.unlink(this.uri)
.then(() => {
ToastUtils.show('删除成功')
LocalMediaManager.delete(this.title)
AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.AUDIO, MediaAction.DELETE)
this.getUIContext().getRouter().back()
})
.catch(() => {
ToastUtils.show('删除失败, 请到文件管理中手动删除')
})
}
}
})
})
}
.height(200)
.margin({ top: 10 })
.visibility(this.showActions ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,90 @@
import { RouterUrls } from '../../common/RouterUrls';
import { ToastUtils } from '../../utils/ToastUtils';
import systemDateTime from '@ohos.systemDateTime';
import { router } from '@kit.ArkUI';
import { AppUtil } from '@pura/harmony-utils';
import { ConfigManager } from '../../manager/UserConfigManager';
import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager';
import { EventConstants } from '../../common/EventConstants';
@Entry
@ComponentV2
struct GuidePage {
child = [
$r('app.media.ic_guide_1'),
$r('app.media.ic_guide_2'),
$r('app.media.ic_guide_3'),
$r('app.media.ic_guide_4')
]
currentIndex: number = 0;
clickTime: number = 0;
@Local showHomePage: boolean = true
aboutToAppear(): void {
EventReportGlobalManager.eventReport(EventConstants.GUIDE_LAUNCH)
}
build() {
Stack() {
Image($r('app.media.ic_guide_bg')).width('100%').height('100%')
if (this.showHomePage) {
Column() {
Image($r('app.media.ic_guide_cover')).width('100%').aspectRatio(0.75)
Text('视频搬运工具').fontColor('#0B2449').fontSize(32).fontFamily('almmsht')
.margin({top: 27})
Text(ConfigManager.getGuideHint()).fontColor('#1E385D').fontSize(16).margin({top: 12, left: 54, right: 54})
Blank().layoutWeight(1)
Button('立即使用')
.width(180)
.height(40)
.fontColor(Color.White)
.fontSize(16)
.borderRadius(20)
.backgroundColor($r("app.color.color_466afd"))
.margin({bottom: 30})
.onClick(() => {
this.showHomePage = false
})
}
.width('100%')
.height('100%')
} else {
Swiper() {
ForEach(this.child, (item: Resource) => {
Image(item).width('100%').height('100%')
})
}
.indicator(false)
.onChange((index: number) => {
this.currentIndex = index;
EventReportGlobalManager.eventReport(EventConstants.GUIDE_OPPORTUNITY_SCROLL, `${index + 1}`)
})
.onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
if (index === this.child.length - 1 && extraInfo.currentOffset < -60) {
// 跳转VIP充值页面
if (ConfigManager.isGuidePayEnable()) {
this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.VIP_PAGE, params: { origin: 'bootpage'} });
} else {
this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single);
}
}
})
.loop(false)
.width('100%')
.height('100%')
}
}
}
onBackPress(): boolean | void {
if (systemDateTime.getTime() - this.clickTime < 1500) {
AppUtil.getContext().terminateSelf();
} else {
this.clickTime = systemDateTime.getTime();
ToastUtils.show('双击退出应用');
}
return true;
}
}

View File

@ -0,0 +1,355 @@
import { TitleBar } from '../../view/TitleBar';
import systemDateTime from '@ohos.systemDateTime';
import { ToastUtils } from '../../utils/ToastUtils';
import { common } from '@kit.AbilityKit';
import { RouterUrls } from '../../common/RouterUrls';
import { Constants } from '../../common/Constants';
import LoginViewModel from '../../viewModel/LoginViewModel';
import { AppUtil, RandomUtil, StrUtil } from '@pura/harmony-utils';
import { SendCodeEntity } from '../../entity/SendCodeEntity';
import { LoginEntity } from '../../entity/LoginEntity';
import { ConfigManager } from '../../manager/UserConfigManager';
import { LoginTipDialog } from '../../dialog/LoginTipDialog';
import { LevelMode, router } from '@kit.ArkUI';
import { LoginManager } from '../../manager/LoginGlobalManager';
import { OnWXResp, WXApi, WXEventHandler } from '../../utils/wechat/WXApiEventHandlerImpl';
import * as WxOpenSdk from '@tencent/wechat_open_sdk';
import { ErrCode, SendAuthResp } from '@tencent/wechat_open_sdk';
import BuildProfile from 'BuildProfile';
import { LoadingDialog } from '../../dialog/LoadingDialog';
import { EventConstants } from '../../common/EventConstants';
import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager';
@Entry
@ComponentV2
struct LoginPage {
@Local from: number = 0;
@Local isAgree: boolean = false;
@Local countDownTime: number = 0;
loginTipDialogController?: CustomDialogController | null;
viewModel: LoginViewModel = new LoginViewModel(this.getUIContext());
clickTime: number = 0;
phone: string = '';
code: string = '';
timestamp: string = '';
intervalId: number = -1;
//从微信返回的回调
onWXResp: OnWXResp = (resp) => {
//微信返回的数据
if (resp instanceof SendAuthResp && resp.state?.endsWith('phone')) {
const authResult = JSON.stringify(resp ?? {}, null , 2);
const errCode = JSON.parse(authResult).errCode as number;
if (errCode === ErrCode.ERR_OK) {
const authCode = JSON.parse(authResult).code as string;
this.viewModel.wxLogin(authCode);
} else {
ToastUtils.show(JSON.parse(authResult).errStr);
}
}
}
@Monitor('viewModel.codeEntity')
onCodeChange(monitor: IMonitor) {
const code = monitor.value()?.now as SendCodeEntity;
ToastUtils.show('验证码已发送');
this.timestamp = code.timestamp;
this.countDownTime = 60;
this.intervalId = setInterval(() => {
if (this.countDownTime > 0) {
this.countDownTime--
} else {
if (this.intervalId !== 0) {
clearInterval(this.intervalId);
}
}
}, 1000)
}
@Monitor('viewModel.phoneLoginEntity')
onPhoneLogin(monitor: IMonitor) {
const loginEntity = monitor.value()?.now as LoginEntity;
EventReportGlobalManager.eventReport(EventConstants.LOGIN, 'phone', this.phone)
LoginManager.saveToken(loginEntity.token);
LoginManager.saveLastLoginType('phone')
ConfigManager.userConfig()
.then(() => {
if (this.from === 0) {
this.toMainPage();
} else {
this.getUIContext().getRouter().back()
}
})
}
@Monitor('viewModel.wxLoginEntity')
onWxLogin(monitor: IMonitor) {
const loginEntity = monitor.value()?.now as LoginEntity;
EventReportGlobalManager.eventReport(EventConstants.LOGIN, 'weixin')
LoginManager.saveToken(loginEntity.token);
LoginManager.saveLastLoginType('weixin')
ConfigManager.userConfig()
.then(() => {
if (this.from === 0) {
this.toMainPage();
} else {
this.getUIContext().getRouter().back()
}
})
}
aboutToAppear() {
WXEventHandler.registerOnWXRespCallback(this.onWXResp)
this.initParams();
}
aboutToDisappear() {
WXEventHandler.unregisterOnWXRespCallback(this.onWXResp)
this.loginTipDialogController = null
}
initParams() {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params) {
this.from = params.from as number;
}
}
sendCode() {
if (StrUtil.isEmpty(this.phone)) {
ToastUtils.show('请输入手机号');
return;
}
if (this.phone.length != 11) {
ToastUtils.show('请输入正确的手机号');
return;
}
this.viewModel.sendCode(this.phone);
EventReportGlobalManager.eventReport(EventConstants.GET_CODE, "code_login", this.phone)
}
toMainPage() {
this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.MAIN_PAGE }, router.RouterMode.Single);
AppUtil.getContext().eventHub.emit(EventConstants.LoginSuccessEvent);
}
async wxAuth() {
if (!WXApi.isWXAppInstalled()) {
ToastUtils.show('未安装微信客户端,请先下载安装微信客户端');
return;
}
LoadingDialog.show(this.getUIContext());
let req = new WxOpenSdk.SendAuthReq;
req.isOption1 = false;
req.nonAutomatic = true;
req.scope = 'snsapi_userinfo';
req.state = BuildProfile.BUNDLE_NAME + RandomUtil.getRandomInt(0, 1000) + '_phone';
req.transaction ='';
await WXApi.sendReq(AppUtil.getContext(), req)
LoadingDialog.dismiss();
}
showLoginTipDialog(type: number) {
this.loginTipDialogController = new CustomDialogController({
builder: LoginTipDialog({
confirm: () => {
this.isAgree = true;
if (type === 0) {
this.viewModel.phoneLogin(this.phone, this.code, this.timestamp);
} else if (type === 1) {
this.wxAuth();
}
}
}),
cornerRadius: 20,
maskColor: '#CC000000',
levelMode: LevelMode.EMBEDDED,
backgroundBlurStyle: BlurStyle.NONE
})
this.loginTipDialogController.open();
}
onBackPress(): boolean | void {
if (this.from === 0) {
if (systemDateTime.getTime() - this.clickTime < 1500) {
(this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf();
} else {
EventReportGlobalManager.eventReport(EventConstants.EXIT_APP, 'login')
this.clickTime = systemDateTime.getTime();
ToastUtils.show('双击退出应用');
}
return true;
}
}
build() {
Stack({alignContent: Alignment.TopStart}) {
Image($r('app.media.ic_login_top_bg')).width('100%')
Column() {
TitleBar({ showBack: this.from !== 0 }).width('100%')
Image($r('app.media.ic_login_logo'))
.margin({ top: 10 })
.width(80)
.height(80)
Text($r('app.string.app_name'))
.fontColor($r('app.color.color_212226'))
.fontSize(18)
.fontFamily('almmsht')
.margin({ top: 12 })
.width('auto')
Row(){
Image($r('app.media.ic_login_phone')).width(22).height(22)
TextInput({ placeholder: '请输入您的手机号' })
.type(InputType.PhoneNumber)
.fontColor($r('app.color.color_1a1a1a'))
.fontSize(16)
.placeholderColor($r('app.color.color_bcbcbc'))
.placeholderFont({ size: 16 })
.maxLength(11)
.backgroundColor(Color.Transparent)
.onChange((value: string) => {
this.phone = value;
})
}
.height(50)
.margin({ top: 50, left: 38, right: 38 })
Divider().strokeWidth(1).color($r('app.color.color_dfdfdf')).margin({ left: 38, right: 38 })
RelativeContainer() {
Row() {
Image($r('app.media.ic_login_code')).width(22).height(22)
TextInput({ placeholder: '请输入验证码' })
.type(InputType.Number)
.fontColor($r('app.color.color_1a1a1a'))
.fontSize(16)
.placeholderColor($r('app.color.color_bcbcbc'))
.placeholderFont({ size: 16 })
.maxLength(6)
.backgroundColor(Color.Transparent)
.onChange((value: string) => {
this.code = value;
})
}
.id('row_code')
Text(this.countDownTime === 0 && StrUtil.isEmpty(this.timestamp) ? '获取验证码' :
this.countDownTime > 0 ? `${this.countDownTime}s` : '重新发送')
.fontColor(this.countDownTime === 0 ? $r("app.color.color_466afd") : $r('app.color.color_999999'))
.fontSize(14)
.alignRules({
top: { anchor: 'row_code', align: VerticalAlign.Top },
right: { anchor: 'row_code', align: HorizontalAlign.End },
bottom: { anchor: 'row_code', align: VerticalAlign.Bottom },
})
.margin({ right: 16 })
.width('auto')
.onClick(() => {
if (this.countDownTime === 0) {
this.sendCode();
}
})
}.margin({ top: 26, left: 38, right: 38 }).height(50)
Divider().strokeWidth(1).color($r('app.color.color_dfdfdf')).margin({ left: 38, right: 38 })
Stack() {
Button('登录', { type: ButtonType.Capsule, stateEffect: true })
.width('100%')
.height(50)
.fontColor(Color.White)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor($r("app.color.color_466afd"))
}.margin({ top: 40 }).padding({ left: 38, right: 38 })
.onClick(() => {
if (StrUtil.isEmpty(this.phone)) {
ToastUtils.show('请输入手机号');
return;
}
if (this.phone.length != 11) {
ToastUtils.show('请输入正确的手机号');
return;
}
if (StrUtil.isEmpty(this.code)) {
ToastUtils.show('请输入验证码');
return;
}
if (this.isAgree) {
this.viewModel.phoneLogin(this.phone, this.code, this.timestamp);
} else {
this.showLoginTipDialog(0);
}
})
Row() {
Image(this.isAgree ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(16).height(16)
Text() {
Span('我已阅读并同意')
Span('《用户协议》').fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext()
.getRouter()
.pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '用户协议', url: Constants.USER_AGREEMENT } })
})
Span('和')
Span('《隐私政策》').fontColor($r("app.color.color_466afd"))
.onClick(() => {
this.getUIContext()
.getRouter()
.pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '隐私政策', url: Constants.PRIVACY_POLICY } })
})
}
.fontColor($r('app.color.color_999999'))
.fontSize(12)
.margin({ left: 4, top: 2 })
}
.alignItems(VerticalAlign.Top)
.margin({ left: 38, top: 16, right: 38 })
.onClick(() => {
this.isAgree = !this.isAgree;
})
Blank().layoutWeight(1)
Text('其他登录方式')
.fontColor('#AAAAAA')
.fontSize(10)
.width('auto')
.visibility(ConfigManager.getLoginType().length > 1 ? Visibility.Visible : Visibility.None)
Row() {
Image($r('app.media.ic_wx_login'))
.margin({ left: 25, right: 25 })
.width(38)
.height(38)
.id('iv_wx_login')
.onClick(() => {
if (this.isAgree) {
this.wxAuth()
} else {
this.showLoginTipDialog(1);
}
})
.visibility(ConfigManager.getLoginType().includes('weixin') ? Visibility.Visible : Visibility.None)
Image($r('app.media.ic_onekey_login'))
.margin({ left: 25, right: 25 })
.width(38)
.height(38)
.id('iv_onekey_login')
.visibility(Visibility.None)
}
.margin({ top: 20, bottom: 50 })
}
.width('100%')
.height('100%')
}
.backgroundColor(Color.White)
}
}

View File

@ -0,0 +1,81 @@
import { TitleBar } from '../../../view/TitleBar'
import { QrcodeLoginViewModel } from '../../../viewModel/QrcodeLoginViewModel'
@Entry
@ComponentV2
struct QrcodeLoginPage {
viewModel: QrcodeLoginViewModel = new QrcodeLoginViewModel(this.getUIContext())
code: string = ''
@Monitor('viewModel.loginResult')
onLogin() {
this.getUIContext().getRouter().back()
}
aboutToAppear(): void {
this.initParams()
}
initParams() {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params) {
this.code = params.code as string
}
}
onBackPress(): boolean | void {
return true
}
build() {
Column() {
TitleBar({ title: '登录确认', isDark: false})
Column() {
Image($r('app.media.ic_qrcode_login_computer_black'))
.width(110)
.height(110)
.margin({ top: 100 })
Text() {
Span('登录 ')
Span($r('app.string.app_name')).fontColor($r("app.color.color_466afd"))
Span(' 网页端')
}
.fontSize(20)
.fontColor($r('app.color.color_1a1a1a'))
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
Button('登录')
.width(184)
.height(48)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.borderRadius(6)
.margin({ top: 90 })
.onClick(() => {
this.viewModel.login(this.code)
})
Text('取消登录').fontColor('#95979E')
.fontSize(14)
.margin({ top: 20 })
.onClick(() => {
this.getUIContext().getRouter().back()
})
}
.width('90%')
.layoutWeight(1)
.margin(16)
.borderRadius(16)
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.color_ededed'))
}
}

View File

@ -0,0 +1,177 @@
import { router, window } from '@kit.ArkUI';
import { HomePage } from './home/HomePage';
import { MinePage } from './mine/MinePage';
import { RecordPage } from './record/RecordPage';
import systemDateTime from '@ohos.systemDateTime';
import { ToastUtils } from '../../utils/ToastUtils';
import { AppUtil, PasteboardUtil, StrUtil } from '@pura/harmony-utils';
import { EventConstants } from '../../common/EventConstants';
import { MainViewModel } from '../../viewModel/MainViewModel';
import { UserEntity } from '../../entity/UserEntity';
import { SimpleTipDialog } from '../../dialog/SimpleTipDialog';
import { RouterUrls } from '../../common/RouterUrls';
import { TipDialog } from '../../dialog/TipDialog';
import { ConfigManager } from '../../manager/UserConfigManager';
import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager';
import { PasteboardUtils } from '../../utils/PasteboardUtils';
@Entry
@ComponentV2
struct MainPage {
windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage;
viewModel: MainViewModel = new MainViewModel(this.getUIContext());
tabController: TabsController = new TabsController()
userinfo?: UserEntity;
titles = ['首页', '素材库', '工具', '我的'];
clickTime: number = 0;
@Local currentIndex: number = 0;
@Provider() recordIndex: number = 0
@Monitor('viewModel.userEntity')
onUserinfoChange(monitor: IMonitor) {
this.userinfo = monitor.value()?.now as UserEntity;
this.showLoginTip();
}
aboutToAppear(): void {
this.windowStage.getMainWindowSync().setWindowSystemBarProperties({
statusBarColor: '#00000000',
statusBarContentColor: '#000000'
});
this.initObserver();
ConfigManager.userConfig()
}
aboutToDisappear(): void {
AppUtil.getContext().eventHub.off(EventConstants.LoginSuccessEvent);
AppUtil.getContext().eventHub.off(EventConstants.JumpToRecordEvent);
}
onPageShow(): void {
this.viewModel.userinfo();
if (this.currentIndex === 1) {
AppUtil.getContext().eventHub.emit(EventConstants.RecordRefreshEvent);
} else if (this.currentIndex === 2) {
AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent);
}
this.checkPasteboard()
}
initObserver() {
AppUtil.getContext().eventHub.on(EventConstants.LoginSuccessEvent, () => {
AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent);
AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent);
})
AppUtil.getContext().eventHub.on(EventConstants.JumpToRecordEvent, (index: number) => {
this.tabController.changeIndex(1)
this.currentIndex = 1
})
}
checkPasteboard() {
PasteboardUtils.isNeedGetPermissionFromUser()
.then((isNeed) => {
if (isNeed) {
PasteboardUtil.requestPermissions()
.then((isGranted) => {
if (isGranted) {
let content = PasteboardUtil.getDataTextSync()
if (StrUtil.isNotEmpty(content) && content !== PasteboardUtils.clipText && PasteboardUtils.isValidUrl(content)) {
TipDialog.show(this.getUIContext(), {title: '您已复制链接,是否粘贴', content: content, leftText: '取消', rightText: '粘贴', callback: {
confirm: () => {
PasteboardUtils.clipText = content
this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_MATERIAL_PAGE, params:{url: content}})
},
cancel: () => {
PasteboardUtils.clipText = content
}
}})
}
}
})
.catch(() => {})
}
})
}
build() {
Tabs({ controller: this.tabController, barPosition: BarPosition.End }) {
TabContent() {
HomePage()
}
.tabBar(this.tabBuilder(this.titles[0], 0, $r('app.media.ic_home_select'), $r('app.media.ic_home_default')))
TabContent() {
}
.tabBar(this.tabBuilder(this.titles[1], 1, $r('app.media.ic_material_select'), $r('app.media.ic_material_default')))
TabContent() {
}
.tabBar(this.tabBuilder(this.titles[2], 2, $r('app.media.ic_tool_select'), $r('app.media.ic_tool_default')))
TabContent() {
MinePage()
}
.tabBar(this.tabBuilder(this.titles[3], 3, $r('app.media.ic_mine_select'), $r('app.media.ic_mine_default')))
}
.scrollable(false)
.onSelected((index: number) => {
this.currentIndex = index;
if (index === 3) {
AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent);
}
this.showLoginTip();
EventReportGlobalManager.eventReport(EventConstants.HOME_BOTTOM_TAB_CHECK, this.titles[index], '')
})
.padding({ bottom: 20 })
.backgroundColor(Color.White)
}
@Builder
tabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
.size({ width: 24, height: 24 })
Text(title)
.fontColor(this.currentIndex === targetIndex ? $r("app.color.color_466afd") : '#676E87')
.fontSize(10)
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
}
showLoginTip() {
if (this.userinfo && this.userinfo.temp && (this.userinfo.vip === 2 || this.userinfo.vip == 3)) {
SimpleTipDialog.show(this.getUIContext(), {
title: '郑重提示',
content: '系统检测到您目前尚未登录,但您已成为我们尊贵的会员,为了防止您的会员账号丢失,建议您立即前往登录',
buttonText: '立即登录',
callback: {
confirm: () => {
this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.LOGIN_PAGE }, router.RouterMode.Single)
}
}
}, false)
}
}
onBackPress(): boolean | void {
if (systemDateTime.getTime() - this.clickTime < 1500) {
AppUtil.getContext().terminateSelf();
} else {
EventReportGlobalManager.eventReport(EventConstants.EXIT_APP, 'main')
this.clickTime = systemDateTime.getTime();
ToastUtils.show('双击退出应用');
}
return true;
}
}

View File

@ -0,0 +1,457 @@
import { borderRadiuses } from '@kit.ArkUI';
import { HomeMenuEntity, homeMenuList } from '../../../entity/HomeMenuEntity';
import { NoticeEntity } from '../../../entity/NoticeEntity';
import { HomeViewModel } from '../../../viewModel/HomeViewModel';
import { AppUtil, WantUtil } from '@pura/harmony-utils';
import { EventConstants } from '../../../common/EventConstants';
import { BannerEntity } from '../../../entity/BannerEntity';
import { ConfigManager } from '../../../manager/UserConfigManager';
import { LoginManager } from '../../../manager/LoginGlobalManager';
import { RouterUrls } from '../../../common/RouterUrls';
import { TipDialog } from '../../../dialog/TipDialog';
import { EventReportGlobalManager } from '../../../manager/EventReportGlobalManager';
import { Constants } from '../../../common/Constants';
import { MaterialEntity } from '../../../entity/MaterialEntity';
import { DownSamplingStrategy, ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknifepro';
@ComponentV2
export struct HomePage {
@Local notices?: Array<string> = [];
@Local banners: Array<BannerEntity> = [];
@Local materialList: Array<MaterialEntity> = []
@Local isRefreshing: boolean = false
@Local isLoading: boolean = false
@Local canLoadMore: boolean = false
viewModel: HomeViewModel = new HomeViewModel(this.getUIContext());
noticeScroller: Scroller = new Scroller();
noticeTaskId: number = 0;
bannerTaskId: number = 0;
private page: number = 1;
@Monitor('viewModel.noticeEntity')
onNoticeChange(monitor: IMonitor) {
const noticeEntity = monitor.value()?.now as NoticeEntity;
this.notices = noticeEntity.notice;
if (this.notices && this.notices.length >= 1) {
this.startNoticeTask();
} else {
this.stopNoticeTask();
}
}
@Monitor('viewModel.materialList')
onMaterialListChange(monitor: IMonitor) {
const list = monitor.value()?.now as Array<MaterialEntity>
if (this.page === 1) {
this.materialList = list
this.isRefreshing = false
} else {
this.materialList.push(...list)
this.isLoading = false
}
this.canLoadMore = list.length === 20
}
aboutToAppear(): void {
this.initObserver();
this.initBanner();
this.viewModel.noticeList();
this.viewModel.getMaterialList(this.page.toString())
}
aboutToDisappear(): void {
this.stopNoticeTask();
}
initObserver() {
AppUtil.getContext().eventHub.on(EventConstants.HomeRefreshEvent, () => {
this.viewModel.noticeList();
})
}
initBanner() {
if (this.banners.length > 0) {
this.banners.splice(0, this.banners.length)
}
const list = ConfigManager.getHomeBanner();
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item.page === 'check_Task') {
continue
} else if (item.page === 'recharge' && LoginManager.getUserInfo()?.vip === 3) {
continue
}
this.banners.push(item);
}
}
startNoticeTask() {
if (this.noticeTaskId === 0 && this.notices && this.notices.length >= 1) {
this.noticeTaskId = setInterval(() => {
this.noticeScroller.scrollBy(3, 0)
if (this.noticeScroller.isAtEnd()) {
this.notices = this.notices?.concat(this.notices);
}
}, 25)
}
}
stopNoticeTask() {
if (this.noticeTaskId !== 0) {
clearInterval(this.noticeTaskId);
this.noticeTaskId = 0;
}
}
createImageOption(item: MaterialEntity): ImageKnifeOption {
const option:ImageKnifeOption = {
loadSrc: item.pic ? item.pic.url : '',
placeholderSrc: $r('app.media.ic_placeholder'),
thumbnailSrc: $r('app.media.ic_placeholder'),
errorSrc: $r('app.media.ic_placeholder'),
objectFit: ImageFit.Cover,
border: {radius: 6},
downSampling: DownSamplingStrategy.FIT_CENTER_QUALITY,
onLoadListener:{
onLoadSuccess: (imageInfo) => {
item.pic_size = `${imageInfo.imageWidth}:${imageInfo.imageHeight}`
}
}
}
return option
}
build() {
Refresh({refreshing: this.isRefreshing}) {
Scroll() {
RelativeContainer() {
Image($r('app.media.ic_home_top_bg')).width('100%').aspectRatio(1.973)
Swiper() {
ForEach(this.banners, (item: BannerEntity) => {
Image(item.image).width('100%').aspectRatio(2.5).margin({left: 12, right: 12})
.onClick(() => {
switch (item.page) {
case 'vip': {
if (LoginManager.getUserInfo()?.vip !== 3) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'banner'}})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'banner')
}
break;
}
case 'check_Task': {
}
case 'course': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.COURSE_PAGE})
}
case 'web_link': {
WantUtil.toWebBrowser(Constants.WEB_URL)
}
}
})
.borderRadius(10)
})
}
.indicator(
Indicator.dot()
.itemWidth(4)
.itemHeight(4)
.selectedItemWidth(12)
.selectedItemHeight(4)
.color('#D6D6D6')
.selectedColor(Color.White)
)
.loop(this.banners && this.banners.length > 1)
.autoPlay(true)
.interval(2000)
.margin({ top: 70 })
.visibility(this.banners && this.banners.length > 0 ? Visibility.Visible : Visibility.None)
.id('swiper_banner')
RelativeContainer() {
List({space: 100, scroller: this.noticeScroller}) {
if (this.notices && this.notices.length >= 1) {
ForEach(this.notices, (item: string) => {
ListItem() {
Text(item).height('100%').textAlign(TextAlign.Center).fontColor('#1876E2').fontSize(13)
}
})
}
}
.listDirection(Axis.Horizontal)
.backgroundColor('#E4EDFD')
.borderRadius(borderRadiuses(6))
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.scrollBar(BarState.Off)
.enableScrollInteraction(false)
.margin({ left: 12, right: 12 })
.padding({left: 45})
.height(36)
.id('li_notice')
Image($r('app.media.ic_notice'))
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.margin({ left: 5, bottom: 2 })
.width(54)
.height(54)
}
.alignRules({
top: { anchor: 'swiper_banner', align: VerticalAlign.Bottom }
})
.margin({top: 11})
.width('100%')
.height(54)
.visibility(this.notices && this.notices?.length > 0 ? Visibility.Visible : Visibility.None)
.id('layout_notice')
Row() {
Column() {
Row() {
Text('链接提取').fontColor('#15005D').fontSize(17).fontFamily('almmsht')
Image($r('app.media.ic_home_top_menu_arrow1')).width(12).height(12).margin({left: 2})
}
Text('涵盖大多数平台').fontColor($r('app.color.color_666666')).fontSize(11).margin({top: 4})
Blank().layoutWeight(1)
Text('1000+国内外视频平台').fontColor($r('app.color.color_999999')).fontSize(10)
Row({space: 6}){
Image($r('app.media.ic_home_link_support_icon1')).width(30).width(30)
Image($r('app.media.ic_home_link_support_icon2')).width(30).width(30)
Image($r('app.media.ic_home_link_support_icon3')).width(30).width(30)
Text('更多').fontColor(Color.White).fontSize(10).width(30).height(30).textAlign(TextAlign.Center).backgroundColor('#825EFF').borderRadius(6)
}
.margin({top: 8})
Row() {
Image($r('app.media.yq_0')).width(14).height(14).borderRadius(7)
Image($r('app.media.yq_1')).width(14).height(14).borderRadius(7).margin({left: -4})
Image($r('app.media.yq_2')).width(14).height(14).borderRadius(7).margin({left: -4})
Text('1000+用户体验中').fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 6})
}
.margin({top: 12})
}
.width('52.7%')
.aspectRatio(1.12)
.backgroundImage($r('app.media.ic_home_top_menu_bg1'))
.backgroundImageSize({width: '100%', height: '100%'})
.padding({left: 10, top: 25, right: 10, bottom: 8})
.alignItems(HorizontalAlign.Start)
.id('layout_link')
.onClick(() => {
// let info = AppUtil.getSignatureInfoSync()
this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_MATERIAL_PAGE})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_LINK_EXTRACT)
})
Column() {
Column() {
Row() {
Text('视频号').fontColor('#113100').fontSize(16).fontFamily('almmsht')
Image($r('app.media.ic_home_top_menu_arrow2')).width(12).height(12).margin({left: 2})
}
Text('支持微信视频号').fontColor($r('app.color.color_666666')).fontSize(11)
Blank().layoutWeight(1)
Row() {
Image($r('app.media.yq_3')).width(14).height(14).borderRadius(7)
Image($r('app.media.yq_4')).width(14).height(14).borderRadius(7).margin({left: -4})
Image($r('app.media.yq_5')).width(14).height(14).borderRadius(7).margin({left: -4})
Text('1200+用户体验中').fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 6})
}
}
.width('48.7%')
.aspectRatio(1.965)
.backgroundImage($r('app.media.ic_home_top_menu_bg2'))
.backgroundImageSize({width: '100%', height: '100%'})
.padding({left: 10, top: 20, right: 10, bottom: 8})
.margin({top: -5})
.alignItems(HorizontalAlign.Start)
.id('layout_wx_video')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.WX_VIDEO_PAGE, params: { isPlayback: false }})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_WECHAT_VIDEO)
})
Column() {
Row() {
Text('直播回放').fontColor('#4A0006').fontSize(16).fontFamily('almmsht')
Image($r('app.media.ic_home_top_menu_arrow3')).width(12).height(12).margin({left: 2})
}
Text('支持微信直播回放').fontColor($r('app.color.color_666666')).fontSize(11)
Blank().layoutWeight(1)
Row() {
Image($r('app.media.yq_6')).width(14).height(14).borderRadius(7)
Image($r('app.media.yq_7')).width(14).height(14).borderRadius(7).margin({left: -4})
Image($r('app.media.yq_8')).width(14).height(14).borderRadius(7).margin({left: -4})
Text('2000+用户体验中').fontColor($r('app.color.color_999999')).fontSize(10).margin({left: 6})
}
}
.width('48.7%')
.aspectRatio(2.28)
.backgroundImage($r('app.media.ic_home_top_menu_bg3'))
.backgroundImageSize({width: '100%', height: '100%'})
.padding({left: 10, top: 9, right: 10, bottom: 8})
.margin({top: 9})
.alignItems(HorizontalAlign.Start)
.id('layout_wx_playback')
.onClick(() => {
TipDialog.show(this.getUIContext(), {title: '提示', content: '仅限微信直播回放视频提取,是否前往?', callback: {
confirm: () => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.WX_VIDEO_PAGE, params: { isPlayback: true }})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_WECHAT_PLAYBACK)
}
}})
})
}
}
.alignRules({
top: { anchor: 'layout_notice', align: VerticalAlign.Bottom }
})
.padding({left: 12, right: 12})
.margin({ top: 10 })
.height('auto')
.id('layout_top_menu')
Grid() {
ForEach(homeMenuList().convertToArray(), (item: HomeMenuEntity) => {
GridItem() {
Column() {
Image(item.icon)
.width(44)
.height(44)
Text(item.title)
.fontColor($r('app.color.color_212226'))
.fontSize(12)
.margin({ top: 6 })
}
.alignItems(HorizontalAlign.Center)
}
.width('20%')
.onClick(() => {
switch (item.alias) {
case 'videoToAudio': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_AUDIO_PAGE})
break
}
case 'addWatermark': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.ADD_WATER_MARKER_PAGE})
break
}
case 'videoToText': {
break
}
case 'longImageMerge': {
break
}
case 'moreTools': {
break
}
}
})
})
}
.maxCount(5)
.layoutDirection(GridDirection.Row)
.alignRules({
top: { anchor: 'layout_top_menu', align: VerticalAlign.Bottom }
})
.margin({
top: 20,
left: 12,
right: 12
})
.id('li_menu')
Column() {
Stack() {
Image($r('app.media.ic_tab_indicator')).width(27).height(11).margin({top: 13})
Text('高清推荐素材').fontColor($r('app.color.color_212226')).fontSize(16).fontWeight(FontWeight.Medium)
}
.alignSelf(ItemAlign.Start)
.margin({left: 12, top: 27})
WaterFlow({footer: this.itemFoot()}) {
ForEach(this.materialList, (item: MaterialEntity, index: number) => {
FlowItem() {
ImageKnifeComponent({
imageKnifeOption:this.createImageOption(item)
})
.width('100%')
.height('100%')
.borderRadius(6)
.backgroundColor($r('app.color.color_ededed'))
.onClick(() => {
if (item.pic?.url) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.MATERIAL_DETAIL_PAGE, params: {material: item}})
}
})
}
.width('100%')
.height(500 / (index % 2 === 0 ? 2 : 3))
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(7)
.rowsGap(7)
.width('100%')
.height('100%')
.padding({left: 12, right: 12})
.margin({top: 14})
.onReachEnd(() => {
this.page++
this.isLoading = true
this.viewModel.getMaterialList(this.page.toString())
})
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
.alignRules({
top: {anchor: 'li_menu', align: VerticalAlign.Bottom}
})
.height('100%')
}
.height('auto')
}
.scrollBar(BarState.Off)
}
.backgroundColor(Color.White)
.onRefreshing(() => {
this.page = 1
this.isRefreshing = true
this.viewModel.getMaterialList(this.page.toString())
})
}
@Builder
itemFoot() {
// 注意不要直接用IfElse节点作为footer的根节点
// 必须在外面使用(Column/Row/Stack等)容器包裹,确保布局正确
Column() {
if (!this.canLoadMore) {
Text('到底啦')
.width('100%')
.height(50)
.textAlign(TextAlign.Center)
} else if (this.isLoading) {
Row() {
LoadingProgress().height(32).width(48)
Text("加载中")
}.width("100%")
.height(50)
.justifyContent(FlexAlign.Center)
}
}
}
}

View File

@ -0,0 +1,22 @@
import { TitleBar } from '../../../../view/TitleBar'
@Entry
@ComponentV2
struct CoursePage {
build() {
Column() {
TitleBar({title: '指导教程'}).width('100%')
Scroll() {
Column() {
Image($r('app.media.ic_course1'))
Image($r('app.media.ic_course2'))
Image($r('app.media.ic_course3'))
}
.height('auto')
}
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,600 @@
import { AppUtil, FileUtil, KeyboardUtil, ObjectUtil, PasteboardUtil, StrUtil } from '@pura/harmony-utils';
import { RouterUrls } from '../../../../common/RouterUrls';
import { MaterialLoadingDialog } from '../../../../dialog/MaterialLoadingDialog';
import {
AudioMaterial,
ImageMaterial,
MaterialDetailEntity,
MaterialInfoEntity,
MediaEntity,
TextMaterial,
VideoMaterial
} from '../../../../entity/MaterialInfoEntity';
import { VipPermissionEntity } from '../../../../entity/VipPermissionEntity';
import { LoginManager } from '../../../../manager/LoginGlobalManager';
import { ToastUtils } from '../../../../utils/ToastUtils';
import { LinkRecognizeViewModel } from '../../../../viewModel/LinkRecognizeViewModel';
import { AudioMaterialPage } from './material/AudioMaterialPage';
import { ImageMaterialPage } from './material/ImageMaterialPage';
import { TextMaterialPage } from './material/TextMaterialPage';
import { VideoMaterialPage } from './material/VideoMaterialPage';
import { router } from '@kit.ArkUI';
import { MediaDownloader } from '../../../../net/MediaDownloader';
import { ShareManager } from '../../../../manager/ShareManager';
import { systemDateTime } from '@kit.BasicServicesKit';
import { SaveUtils } from '../../../../utils/SaveUtils';
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog';
import { EventConstants } from '../../../../common/EventConstants';
import { SimpleTipDialog } from '../../../../dialog/SimpleTipDialog';
import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager';
import { PasteboardUtils } from '../../../../utils/PasteboardUtils';
import { TipDialog } from '../../../../dialog/TipDialog';
import { PrefUtils } from '../../../../utils/PrefUtils';
@Entry
@ComponentV2
struct TakeMaterialPage {
@Local inputText: string = '';
@Local currentIndex: number = 0;
@Local materialInfo?: MaterialInfoEntity;
@Local videoList: Array<VideoMaterial> = [];
@Local imageList: Array<ImageMaterial> = [];
@Local audioList: Array<AudioMaterial> = [];
@Local textList: Array<TextMaterial> = [];
@Local videoRowCount: number = 1;
@Local imageRowCount: number = 1;
viewModel: LinkRecognizeViewModel = new LinkRecognizeViewModel(this.getUIContext());
tabController: TabsController = new TabsController();
titles: Array<string> = ['视频', '图片', '音频', '文本'];
logId?: string;
type: number = 0
originText: string = ''
mediaDownloader?: MediaDownloader | null
selectedList: Array<MediaEntity> = []
cacheFileUris: Array<string> = []
downloadIndex: number = 0
totalSize = 0
downloadStatus = DownloadStatus.DOWNLOADING
@Monitor('viewModel.materialInfo')
onMaterialInfoChange(monitor: IMonitor) {
const info = monitor.value()?.now as MaterialInfoEntity
this.logId = info.logid;
MaterialLoadingDialog.show(this.getUIContext(), info.status_name)
this.viewModel.analysisMaterial(info.logid, info.timeout);
}
@Monitor('viewModel.analysisInfo')
onAnalysisInfoInfoChange(monitor: IMonitor) {
const info = monitor.value()?.now as MaterialInfoEntity
if (info.material && info.material !== null) {
this.materialInfo = info;
this.originText = this.inputText
this.materialInfo.logid = this.logId!!
this.createVideoList(info.material);
this.createImageList(info.material);
this.createAudioList(info.material);
this.createTextList(info.material);
if (this.videoList.length > 0) {
this.tabController.changeIndex(0);
} else if (this.imageList.length > 0) {
this.tabController.changeIndex(1);
}
MaterialLoadingDialog.dismiss();
} else {
if (info.status !== 1) {
ToastUtils.show(info.status_name)
MaterialLoadingDialog.dismiss()
} else {
MaterialLoadingDialog.update(info.status_name)
}
}
}
@Monitor('viewModel.permissionInfo')
onPermissionInfoChange(monitor: IMonitor) {
const info = monitor.value()?.now as VipPermissionEntity;
if (info.auth) {
if (!LoginManager.isLogin()) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.LOGIN_PAGE, params: {from: 1}}, router.RouterMode.Single)
return;
}
this.shareOrDownload()
} else {
if (!info.auth_ad) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'download_material'}})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'download_material')
return;
}
}
}
@Monitor('viewModel.errorCode')
onErrorCodeChange(monitor: IMonitor) {
const errorCode = monitor.value()?.now as number;
if (errorCode === 12002 || errorCode === 12003 || errorCode === 12004) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'download_material')
ToastUtils.show('钻石已用完')
}
}
aboutToAppear(): void {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>
if (params && params.url) {
this.inputText = params.url as string
this.parseUrl(this.inputText)
}
}
aboutToDisappear(): void {
this.viewModel.cancelInterval()
}
onPageShow(): void {
this.checkPasteboard()
}
checkPasteboard() {
PasteboardUtils.isNeedGetPermissionFromUser()
.then((isNeed) => {
if (isNeed) {
PasteboardUtil.requestPermissions()
.then((isGranted) => {
if (isGranted) {
let content = PasteboardUtil.getDataTextSync()
if (StrUtil.isNotEmpty(content) && content !== PasteboardUtils.clipText && PasteboardUtils.isValidUrl(content)) {
TipDialog.show(this.getUIContext(), {title: '您已复制链接,是否粘贴', content: content, leftText: '取消', rightText: '粘贴', callback: {
confirm: () => {
PasteboardUtils.clipText = content
this.inputText = content
this.parseUrl(content)
},
cancel: () => {
PasteboardUtils.clipText = content
}
}})
}
}
})
.catch(() => {})
}
})
}
showSaveTip(isAudio: boolean = false) {
if (!isAudio) {
if (PrefUtils.getBoolean('show_save_tip', true)) {
SimpleTipDialog.show(this.getUIContext(), {title: '重要提示', content: '下载完成后需要您点击弹窗允许保存之后才能保存文件到相册', callback: {
confirm: () => {
this.viewModel.checkVip();
PrefUtils.put('show_save_tip', false)
}
}})
} else {
this.viewModel.checkVip();
}
} else {
this.viewModel.checkVip();
}
}
shareOrDownload() {
this.cacheFileUris.length = 0
if (this.type == 0) {
if (this.selectedList.length > 0) {
this.shareMedia(this.selectedList[0])
}
} else {
if (this.selectedList.length > 0) {
this.downloadIndex = 0
this.downloadMedia(this.selectedList[this.downloadIndex])
}
}
}
shareMedia(media: MediaEntity) {
let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName()
if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) {
this.shareFile(media)
} else {
this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING)
if (media.isMerge) {
this.beforeDoMerge(media)
}
this.download(media, true)
}
}
async downloadMedia(media: MediaEntity) {
let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName()
if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) {
this.cacheFileUris.push(FileUtil.getUriFromPath(filePath))
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
let saved = await this.saveFile(media)
if (saved) {
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize, media instanceof AudioMaterial)
}
} else {
ToastUtils.show('保存失败')
this.dismissDownloadDialog()
}
}
} else {
this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING)
if (media.isMerge) {
this.beforeDoMerge(media)
}
this.download(media)
}
}
beforeDoMerge(media: MediaEntity) {
let video = media as VideoMaterial
let audio = video.audio
let name = systemDateTime.getTime()
if (StrUtil.isEmpty(video.cacheName)) video.cacheName = `merge_${name}.mp4`
if (audio && StrUtil.isEmpty(audio?.cacheName)) audio.cacheName = `merge_${name}.mp3`
}
async download(media: MediaEntity, isShare: boolean = false) {
this.viewModel.reportStatus(this.materialInfo!!.logid, '1')
media.initFileName()
media.origin = this.originText
this.mediaDownloader = MediaDownloader.getInstance()
this.mediaDownloader.setProxyList(this.materialInfo?.material?.proxyUrlList)
.callback({
onSuccess: async (path) => {
if (isShare) {
this.shareFile(media)
} else {
this.cacheFileUris.push(FileUtil.getUriFromPath(path))
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
let saved = await this.saveFile(media)
if (saved) {
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize, media instanceof AudioMaterial)
}
} else {
ToastUtils.show('保存失败')
this.dismissDownloadDialog()
}
}
}
},
onGetTotal: (total) => {
this.totalSize = total
this.updateDownloadDialog(this.downloadStatus, total)
},
onProgress: (progress) => {
this.updateDownloadDialog(this.downloadStatus, this.totalSize, progress)
},
onMerge: (step) => {
if (step === 4) {
this.updateDownloadDialog(DownloadStatus.AUDIO_DOWNLOADING)
} else {
this.updateDownloadDialog(DownloadStatus.PROCESSING)
}
},
onPause:() => {
this.dismissDownloadDialog()
},
onCancel: () => {
this.dismissDownloadDialog()
},
onFailed: (msg) => {
ToastUtils.show('下载失败:' + msg)
this.dismissDownloadDialog()
this.viewModel.reportStatus(this.materialInfo!!.logid, '-1', `${media.totalSize}`, msg)
this.reportErrorEvent(media, msg)
}
})
.down(media);
}
shareFile(media: MediaEntity) {
ShareManager.shareFile(FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName())
this.viewModel.reportStatus(this.materialInfo!!.logid, '2', `${media.totalSize}`)
this.dismissDownloadDialog()
}
async saveFile(media: MediaEntity): Promise<boolean> {
let saved = false
if (media instanceof VideoMaterial) {
saved = await SaveUtils.saveImageVideoToAlbumDialog(this.cacheFileUris)
} else if (media instanceof ImageMaterial) {
saved = await SaveUtils.saveImageVideoToAlbumDialog(this.cacheFileUris)
} else if (media instanceof AudioMaterial) {
saved = await SaveUtils.saveAudioToMusic(this.cacheFileUris)
}
if (saved) {
let totalSize = 0
this.cacheFileUris.forEach((uri) => {
let stat = FileUtil.statSync(FileUtil.getFilePath(uri))
totalSize += stat.size
})
this.viewModel.reportStatus(this.materialInfo!!.logid, '2', `${totalSize}`)
} else {
this.viewModel.reportStatus(this.materialInfo!!.logid, '-1', `保存失败`)
}
return Promise.resolve(saved)
}
showDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING) {
this.downloadStatus = status
DownloadDialog.show(this.getUIContext(), { status: status, totalSize: 0, progress: 0, totalCount: this.selectedList.length, index: this.downloadIndex, callback: {
confirm: () => {
if (this.downloadStatus === DownloadStatus.COMPLETED) {
EventReportGlobalManager.eventReport(EventConstants.DIALOG_GO_TO_VIEW, this.titles[this.currentIndex])
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, this.currentIndex)
this.getUIContext().getRouter().back()
} else {
//todo 后台下载
}
},
cancel: () => {
if (this.downloadStatus !== DownloadStatus.COMPLETED) {
if (this.mediaDownloader) {
this.mediaDownloader.cancel()
EventReportGlobalManager.eventReport(EventConstants.CANCEL_DOWNLOAD_VIDEO, this.selectedList[this.downloadIndex].url)
}
} else {
EventReportGlobalManager.eventReport(EventConstants.DIALOG_CONFIRM_SAVE_FILE, this.titles[this.currentIndex])
}
}
} })
}
updateDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING, totalSize: number = 0, progress: number = 0, isAudio: boolean = false) {
this.downloadStatus = status
DownloadDialog.update({ status: status, totalSize: totalSize, progress: progress, totalCount: this.selectedList.length, index: this.downloadIndex, isAudio: isAudio })
}
dismissDownloadDialog() {
this.downloadStatus = DownloadStatus.DOWNLOADING
this.totalSize = 0
this.downloadIndex = 0
this.mediaDownloader = null
DownloadDialog.dismiss()
}
parseUrl(url: string) {
if (StrUtil.isNotEmpty(this.inputText)) {
this.viewModel.getMaterialInfo(url);
}
}
createVideoList(material?: MaterialDetailEntity) {
const list = new Array<VideoMaterial>();
material?.video?.forEach((item, index) => {
item.isThreading = material?.threading!!;
if (material?.merge && material.audio && material.audio.length > index) {
item.audio = material.audio[index];
item.isMerge = true;
}
if (material?.image && material.image.length > index) {
item.thumb = material.image[index].url;
}
list.push(item);
})
this.videoList = list.map(item => ObjectUtil.assign(new VideoMaterial(), item) as VideoMaterial);
if (this.videoList.length > 0) {
this.videoList[0].isChecked = true
}
this.videoRowCount = this.computeRowCount(this.videoList);
}
createImageList(material: MaterialDetailEntity) {
if (material.image) {
this.imageList = material.image.map(item => ObjectUtil.assign(new ImageMaterial(), item) as ImageMaterial);
if (this.imageList.length > 0) {
this.imageList[0].isChecked = true
}
this.imageRowCount = this.computeRowCount(this.imageList);
}
}
createAudioList(material: MaterialDetailEntity) {
if (material.audio) {
this.audioList = material.audio.map(item => ObjectUtil.assign(new AudioMaterial(), item) as AudioMaterial);
if (this.audioList.length > 0) {
this.audioList[0].isChecked = true
}
}
}
createTextList(material: MaterialDetailEntity) {
const list = new Array<TextMaterial>();
if (material.desc) {
const text = new TextMaterial()
text.title = material.title;
text.desc = material.desc;
list.push(text);
}
this.textList = list;
if (this.textList.length > 0) {
this.textList[0].isChecked = true
}
}
computeRowCount(list: Array<MediaEntity>): number {
if (list.length > 8) {
return 3;
} else if (list.length >= 3 && list.length <= 8) {
return 2;
} else {
return 1;
}
}
reportErrorEvent(media: MediaEntity, message: string) {
if (media instanceof VideoMaterial) {
EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_VIDEO, media.url, message)
} else if (media instanceof ImageMaterial) {
EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_IMG, media.url, message)
} else if (media instanceof AudioMaterial) {
EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_AUDIO, media.url, message)
}
}
build() {
Column() {
Row() {
Button({ type: ButtonType.Circle, stateEffect: true }) {
Image($r('app.media.ic_back')).width(24).height(24)
}
.width(40)
.height(40)
.margin({ left: 10 })
.onClick(() => {
this.getUIContext().getRouter().back()
})
.backgroundColor(Color.Transparent)
Row() {
Image($r('app.media.ic_link')).width(18).height(18)
TextInput({ placeholder: '请输入链接地址', text: this.inputText })
.layoutWeight(1)
.fontColor('#D6D6D6')
.fontSize(14)
.placeholderColor($r('app.color.color_30ffffff'))
.placeholderFont({ size: 14 })
.onChange((value: string) => {
this.inputText = value;
})
Image($r('app.media.ic_clear_text'))
.width(18)
.height(18)
.padding(2)
.visibility(StrUtil.isNotEmpty(this.inputText) ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.inputText = '';
})
Divider()
.vertical(true)
.strokeWidth(1)
.height(14)
.color($r('app.color.color_10ffffff'))
.margin({ left: 12, right: 12 })
Text('获取').fontColor($r('app.color.color_80ffffff')).fontSize(14)
.onClick(() => {
if (StrUtil.isNotEmpty(this.inputText)) {
KeyboardUtil.hide()
this.parseUrl(this.inputText)
EventReportGlobalManager.eventReport(EventConstants.GET_MATERIAL, "material-button", this.inputText)
} else {
ToastUtils.show('请输入链接地址')
}
})
}
.layoutWeight(1)
.height(34)
.borderRadius(20)
.backgroundColor($r('app.color.color_333333'))
.margin({ left: 20, right: 16 })
.padding({ left: 10, right: 10 })
}.height(100).padding({ top: 50 })
Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) {
TabContent() {
VideoMaterialPage({
mediaList: this.videoList, rowCount: this.videoRowCount,
onShare:(video) => {
this.type = 0
this.selectedList.length = 0
this.selectedList.push(video)
this.viewModel.checkVip();
},
onSave: (list) => {
this.type = 1
this.selectedList = list
this.showSaveTip();
} })
}
.tabBar(this.tabBuilder(this.titles[0], 0))
TabContent() {
ImageMaterialPage({
mediaList: this.imageList, rowCount: this.imageRowCount,
onShare:(image) => {
this.type = 0
this.selectedList.length = 0
this.selectedList.push(image)
this.viewModel.checkVip();
},
onSave: (list) => {
this.type = 1
this.selectedList = list
this.showSaveTip();
} })
}
.tabBar(this.tabBuilder(this.titles[1], 1))
TabContent() {
AudioMaterialPage({
mediaList: this.audioList,
onShare:(audio) => {
this.type = 0
this.selectedList.length = 0
this.selectedList.push(audio)
this.viewModel.checkVip();
},
onSave: (list) => {
this.type = 1
this.selectedList = list
this.showSaveTip(true);
} })
}
.tabBar(this.tabBuilder(this.titles[2], 2))
TabContent() {
TextMaterialPage({ mediaList: this.textList })
}
.tabBar(this.tabBuilder(this.titles[3], 3))
}
.scrollable(false)
.onTabBarClick((index) => {
this.currentIndex = index;
})
/*.onSelected((index: number) => {
this.currentIndex = index;
})*/
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
@Builder
tabBuilder(title: string, targetIndex: number) {
Column() {
Text(title)
.fontColor(this.currentIndex === targetIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff'))
.fontSize(this.currentIndex === targetIndex ? 17 : 14)
.fontWeight(this.currentIndex === targetIndex ? FontWeight.Medium : FontWeight.Regular)
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
}
}

View File

@ -0,0 +1,114 @@
import { AudioMaterial } from '../../../../../entity/MaterialInfoEntity';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { EmptyView, PageStatus } from '../../../../../view/EmptyView';
import { AudioMaterialItemView } from '../../../../../view/MaterialItemView';
@ComponentV2
export struct AudioMaterialPage {
@Param mediaList: Array<AudioMaterial> = [];
@Param onShare?: (audio: AudioMaterial) => void = undefined
@Param onSave?: (list: Array<AudioMaterial>) => void = undefined
@Local isCheckAll: boolean = false
selectedItems(): Array<AudioMaterial> {
const list = new Array<AudioMaterial>();
this.mediaList.forEach((item) => {
if (item.isChecked) {
list.push(item);
}
})
return list;
}
build() {
Stack() {
Column() {
List({ space: 10 }) {
ForEach(this.mediaList, (item: AudioMaterial, index) => {
ListItem() {
AudioMaterialItemView({ media: item })
}
.onClick(() => {
item.isChecked = !item.isChecked;
this.isCheckAll = this.mediaList.every(item => item.isChecked)
})
})
}
.width('auto')
.layoutWeight(1)
.scrollBar(BarState.Off)
.margin({ left: 16, right: 16, bottom: 15 })
Row() {
Row() {
Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18)
Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 })
}
.onClick(() => {
this.isCheckAll = !this.isCheckAll;
this.mediaList.forEach((item) => {
item.isChecked = this.isCheckAll;
})
})
Blank().layoutWeight(1)
Button('转发', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (this.onShare) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要转发的音频');
} else if (list.length > 1) {
ToastUtils.show('一次只能转发一个音频');
} else {
this.onShare(list[0]);
}
}
})
Button('保存', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 12 })
.onClick(() => {
if (this.onSave) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要保存的音频');
} else {
this.onSave(list);
}
}
})
}
.backgroundColor($r('app.color.color_222222'))
.padding({
left: 16,
top: 10,
right: 16,
bottom: 30
})
.visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None)
}
EmptyView({
status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA,
noDataImage: $r('app.media.ic_empty_audio'),
noDataText: '暂无音频'
})
}
}
}

View File

@ -0,0 +1,117 @@
import { ImageMaterial } from '../../../../../entity/MaterialInfoEntity';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { EmptyView, PageStatus } from '../../../../../view/EmptyView';
import { ImageMaterialItemView } from '../../../../../view/MaterialItemView';
@ComponentV2
export struct ImageMaterialPage {
@Param mediaList: Array<ImageMaterial> = [];
@Param rowCount: number = 1;
@Param onShare?: (image: ImageMaterial) => void = undefined
@Param onSave?: (list: Array<ImageMaterial>) => void = undefined
@Local isCheckAll: boolean = false
selectedItems(): Array<ImageMaterial> {
const list = new Array<ImageMaterial>();
this.mediaList.forEach((item) => {
if (item.isChecked) {
list.push(item);
}
})
return list;
}
build() {
Stack() {
Column() {
Grid() {
ForEach(this.mediaList, (item: ImageMaterial, index) => {
GridItem() {
ImageMaterialItemView({ media: item, rowCount: this.rowCount })
}
.onClick(() => {
item.isChecked = !item.isChecked;
this.isCheckAll = this.mediaList.every(item => item.isChecked)
})
})
}
.scrollBar(BarState.Off)
.columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)
.margin({ left: 16, right: 16, bottom: 15 })
.layoutWeight(1)
Row() {
Row() {
Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18)
Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 })
}
.onClick(() => {
this.isCheckAll = !this.isCheckAll;
this.mediaList.forEach((item) => {
item.isChecked = this.isCheckAll;
})
})
Blank().layoutWeight(1)
Button('转发', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (this.onShare) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要转发的图片');
} else if (list.length > 1) {
ToastUtils.show('一次只能转发一张图片');
} else {
this.onShare(list[0]);
}
}
})
Button('保存', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 12 })
.onClick(() => {
if (this.onSave) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要保存的图片');
} else {
this.onSave(list);
}
}
})
}
.backgroundColor($r('app.color.color_222222'))
.padding({
left: 16,
top: 10,
right: 16,
bottom: 30
})
.visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None)
}
EmptyView({
status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA,
noDataImage: $r('app.media.ic_empty_image'),
noDataText: '暂无图片'
})
}
}
}

View File

@ -0,0 +1,93 @@
import { PasteboardUtil } from '@pura/harmony-utils';
import { TextMaterial } from '../../../../../entity/MaterialInfoEntity';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { EmptyView, PageStatus } from '../../../../../view/EmptyView';
import { TextMaterialItemView } from '../../../../../view/MaterialItemView';
@ComponentV2
export struct TextMaterialPage {
@Param mediaList: Array<TextMaterial> = [];
@Local isCheckAll: boolean = false
selectedItems(): Array<TextMaterial> {
const list = new Array<TextMaterial>();
this.mediaList.forEach((item) => {
if (item.isChecked) {
list.push(item);
}
})
return list;
}
build() {
Stack() {
Column() {
List({ space: 10 }) {
ForEach(this.mediaList, (item: TextMaterial, index) => {
ListItem() {
TextMaterialItemView({ media: item })
}
.onClick(() => {
item.isChecked = !item.isChecked;
this.isCheckAll = this.mediaList.every(item => item.isChecked)
})
})
}
.width('auto')
.layoutWeight(1)
.scrollBar(BarState.Off)
.margin({ left: 16, right: 16, bottom: 15 })
Row() {
Row() {
Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18)
Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 })
}
.onClick(() => {
this.isCheckAll = !this.isCheckAll;
this.mediaList.forEach((item) => {
item.isChecked = this.isCheckAll;
})
})
Button('复制文本', { type: ButtonType.Capsule, stateEffect: true })
.layoutWeight(1)
.height(40)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 27 })
.onClick(() => {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要复制的文本');
} else {
list.forEach((item) => {
PasteboardUtil.setDataTextSync(item.title);
})
ToastUtils.show('已复制');
}
})
}
.backgroundColor($r('app.color.color_222222'))
.padding({
left: 16,
top: 10,
right: 16,
bottom: 30
})
.visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None)
}
EmptyView({
status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA,
noDataImage: $r('app.media.ic_empty_text'),
noDataText: '暂无文本'
})
}
}
}

View File

@ -0,0 +1,117 @@
import { VideoMaterial } from '../../../../../entity/MaterialInfoEntity';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { EmptyView, PageStatus } from '../../../../../view/EmptyView';
import { VideoMaterialItemView } from '../../../../../view/MaterialItemView';
@ComponentV2
export struct VideoMaterialPage {
@Param mediaList: Array<VideoMaterial> = [];
@Param rowCount: number = 1;
@Param onShare?: (video: VideoMaterial) => void = undefined
@Param onSave?: (list: Array<VideoMaterial>) => void = undefined
@Local isCheckAll: boolean = false
selectedItems(): Array<VideoMaterial> {
const list = new Array<VideoMaterial>();
this.mediaList.forEach((item) => {
if (item.isChecked) {
list.push(item);
}
})
return list;
}
build() {
Stack() {
Column() {
Grid() {
ForEach(this.mediaList, (item: VideoMaterial, index) => {
GridItem() {
VideoMaterialItemView({ media: item, rowCount: this.rowCount })
}
.onClick(() => {
item.isChecked = !item.isChecked;
this.isCheckAll = this.mediaList.every(item => item.isChecked)
})
})
}
.scrollBar(BarState.Off)
.columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)
.margin({ left: 16, right: 16, bottom: 15 })
.layoutWeight(1)
Row() {
Row() {
Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') : $r('app.media.ic_check_false')).width(18).height(18)
Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 })
}
.onClick(() => {
this.isCheckAll = !this.isCheckAll;
this.mediaList.forEach((item) => {
item.isChecked = this.isCheckAll;
})
})
Blank().layoutWeight(1)
Button('转发', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (this.onShare) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要转发的视频');
} else if (list.length > 1) {
ToastUtils.show('一次只能转发一个视频');
} else {
this.onShare(list[0]);
}
}
})
Button('保存', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 12 })
.onClick(() => {
if (this.onSave) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要保存的视频');
} else {
this.onSave(list);
}
}
})
}
.backgroundColor($r('app.color.color_222222'))
.padding({
left: 16,
top: 10,
right: 16,
bottom: 30
})
.visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None)
}
EmptyView({
status: this.mediaList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA,
noDataImage: $r('app.media.ic_empty_video'),
noDataText: '暂无视频'
})
}
}
}

View File

@ -0,0 +1,228 @@
import { MaterialEntity } from '../../../../entity/MaterialEntity';
import { TitleBar } from '../../../../view/TitleBar';
import { router, window } from '@kit.ArkUI';
import { ImagePreview } from '@rv/image-preview';
import { AppUtil, DisplayUtil, FileUtil, NumberUtil } from '@pura/harmony-utils';
import { media } from '@kit.MediaKit';
import { BusinessError, request, systemDateTime } from '@kit.BasicServicesKit';
import { ToastUtils } from '../../../../utils/ToastUtils';
import { SaveUtils } from '../../../../utils/SaveUtils';
import { AuthViewModel } from '../../../../viewModel/AuthViewModel';
import { VipPermissionEntity } from '../../../../entity/VipPermissionEntity';
import { LoginManager } from '../../../../manager/LoginGlobalManager';
import { RouterUrls } from '../../../../common/RouterUrls';
import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager';
import { EventConstants } from '../../../../common/EventConstants';
import { LoadingDialog } from '../../../../dialog/LoadingDialog';
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog';
@Entry
@ComponentV2
struct MaterialDetailPage {
windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage;
@Local material?: MaterialEntity;
@Local tagArray: Array<string> = []
private viewModel: AuthViewModel = new AuthViewModel(this.getUIContext())
private imageSize: media.PixelMapParams = {}
@Monitor('viewModel.permissionInfo')
onPermissionInfoChange(monitor: IMonitor) {
const info = monitor.value()?.now as VipPermissionEntity;
if (info.auth) {
if (!LoginManager.isLogin()) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.LOGIN_PAGE, params: {from: 1}}, router.RouterMode.Single)
return;
}
this.download()
} else {
if (!info.auth_ad) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'download_material'}})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'download_material')
return;
}
}
}
@Monitor('viewModel.errorCode')
onErrorCodeChange(monitor: IMonitor) {
const errorCode = monitor.value()?.now as number;
if (errorCode === 12002 || errorCode === 12003 || errorCode === 12004) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'download_material')
ToastUtils.show('M币已用完')
}
}
aboutToAppear(): void {
this.windowStage.getMainWindowSync().setWindowSystemBarProperties({
statusBarColor: '#00000000',
statusBarContentColor: '#FFFFFF'
});
this.initParams()
}
aboutToDisappear(): void {
this.windowStage.getMainWindowSync().setWindowSystemBarProperties({
statusBarColor: '#00000000',
statusBarContentColor: '#000000'
});
}
initParams() {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params) {
this.material = params.material as MaterialEntity
if (this.material.pic_size) {
const size = this.material.pic_size.split(':')
if (size.length === 2) {
this.imageSize.width = NumberUtil.toNumber(size[0])
this.imageSize.height = NumberUtil.toNumber(size[1])
}
}
this.tagArray = this.material.tags
}
}
download() {
LoadingDialog.show(this.getUIContext())
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.jpeg`
let config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: this.material!!.pic!!.url,
overwrite: true,
saveas: outputPath,
gauge: true,
priority:0
}
request.agent.create(AppUtil.getApplicationContext(), config)
.then((task: request.agent.Task) => {
task.start((err: BusinessError) => {
if (err) {
console.error(`下载失败: ${err.message}`);
ToastUtils.show(err.message);
return;
}
LoadingDialog.dismiss()
});
task.on('completed',() => {
LoadingDialog.dismiss()
if (FileUtil.accessSync(outputPath)) {
SaveUtils.saveImageVideoToAlbumDialog([outputPath])
.then((saved) => {
if (saved) {
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch(() => {
ToastUtils.show('保存失败')
})
} else {
ToastUtils.show('下载失败: 文件不存在');
}
request.agent.remove(task.tid);
})
task.on('failed', () => {
LoadingDialog.dismiss()
ToastUtils.show('下载失败,请检查网络后重试');
request.agent.remove(task.tid);
});
})
.catch((err: BusinessError) => {
if (err) {
console.error(`下载失败: ${err.message}`);
ToastUtils.show(err.message);
}
LoadingDialog.dismiss()
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), {
status: DownloadStatus.COMPLETED,
totalSize: 0,
progress: 0,
totalCount: 1,
index: 0,
callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
}
})
}
build() {
Column() {
TitleBar({title: this.material?.title, isDark: true})
Stack() {
ImagePreview() {
RelativeContainer() {
Image(this.material?.pic?.url)
.width("100%")
.height(px2vp(Math.round(this.imageSize.height!! * DisplayUtil.getWidth() / this.imageSize.width!!)))
.sourceSize({
width: px2vp(DisplayUtil.getWidth()),
height: px2vp(Math.round(this.imageSize.height!! * DisplayUtil.getWidth() / this.imageSize.width!!))
})
.alignRules({
top: {anchor: '__container__', align: VerticalAlign.Top},
bottom: {anchor: '__container__', align: VerticalAlign.Bottom}
})
.draggable(false)
.id('image')
List({space: 4}){
ForEach(this.tagArray, (item: string) => {
ListItem() {
Text(item).fontColor(Color.White).fontSize(10)
.borderRadius(2)
.backgroundColor('#80000000')
.padding({left: 4, top: 1, right: 4, bottom: 1})
}
})
}
.listDirection(Axis.Horizontal)
.alignRules({
top: {anchor: 'image', align: VerticalAlign.Top},
right: {anchor: 'image', align: HorizontalAlign.End}
})
.margin({top: 12, right: 12})
}
.height('auto')
}
}
.width('100%')
.layoutWeight(1)
Stack() {
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Row() {
Image($r('app.media.ic_download1')).width(22).height(22)
Text('下载').fontColor(Color.White).fontSize(15).fontWeight(FontWeight.Medium)
}
}
.width('100%')
.height(46)
.backgroundColor($r('app.color.color_466afd'))
.onClick(() => {
if (this.material?.pic?.url) {
this.viewModel.checkVip()
} else {
ToastUtils.show('链接不存在')
}
})
}
.backgroundColor($r('app.color.color_1b1b1b'))
.padding({left: 16, top: 9, right: 16, bottom: 30})
}
.width('100%')
.height('100%')
.backgroundColor('#0F0E13')
}
}

View File

@ -0,0 +1,329 @@
import { PhotoHelper, PickerUtil } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo, picker } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { MP4Parser } from '@ohos/mp4parser'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct AddAudioPage {
private controller: VideoController = new VideoController()
@Local videoUri?: string
@Local audioUri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
mirrorVideo() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
let videoFile = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY)
// 复制视频文件到缓存目录下
FileUtil.copyFileSync(videoFile.fd, cacheVideoPath)
let cacheAudioPath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp3`
if (FileUtil.accessSync(cacheAudioPath)) {
FileUtil.unlinkSync(cacheAudioPath)
}
let audioFile = FileUtil.openSync(this.audioUri!!, fileIo.OpenMode.READ_ONLY)
// 复制音频文件到缓存目录下
FileUtil.copyFileSync(audioFile.fd, cacheAudioPath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -stream_loop -1 -i ${cacheAudioPath} -c:v copy -c:a aac -shortest -map 0:v -map 1:a ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.videoUri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.videoUri = uris[0]
}
})
}
selectAudio() {
/*PickerUtil.selectAudio({maxSelectNumber: 1})
.then((uris) => {
if (uris.length != 0) {
this.audioUri = uris[0]
}
})*/
PickerUtil.selectDocument({maxSelectNumber: 1, fileSuffixFilters: ['.mp3']})
.then((uris) => {
if (uris.length != 0) {
this.audioUri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '加音乐' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.videoUri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.videoUri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.layoutWeight(1)
Image($r('app.media.ic_add_audio')).width(50).height(50).margin({top: 20})
.onClick(() => {
this.selectAudio()
})
Text('音频').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({top: 10})
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.videoUri!!])
.then((saved) => {
if (saved) {
this.videoUri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
if (this.audioUri) {
this.mirrorVideo()
} else {
ToastUtils.show('请上传音频')
}
}
})
}
.margin({ top: 50, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.videoUri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,425 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, DisplayUtil, FileUtil, ImageUtil, StrUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { RectPosition } from '../../../../view/RectCropView'
import { media } from '@kit.MediaKit'
import { MediaUtils } from '../../../../utils/MediaUtils'
import { MP4Parser } from '@ohos/mp4parser'
import { TipDialog } from '../../../../dialog/TipDialog'
import { WaterMarkerView } from '../../../../view/WaterMarkerView'
import { EditTextDialog } from '../../../../dialog/EditTextDialog'
import { image } from '@kit.ImageKit'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct AddWaterMarkerPage {
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
@Local playerSize: media.PixelMapParams = { width: 0, height: 0 }
@Local showWaterMaker: boolean = false
@Local textContent: string = ''
@Local imagePath: string = ''
private controller: VideoController = new VideoController()
private videoSize: media.PixelMapParams = { width: 0, height: 0 }
private rect: RectPosition = { x: 0, y: 0, width: 0, height: 0 }
addWaterMarker() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
let imageX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!!
let imageY = (vp2px(this.rect.y) * this.videoSize.height!!) / this.playerSize.height!!
let imageWidth = (vp2px(this.rect.width * this.videoSize.width!!) / this.playerSize.width!!)
let imageHeight = (vp2px(this.rect.height * this.videoSize.height!!) / this.playerSize.height!!)
this.getUIContext().getComponentSnapshot().get(StrUtil.isNotEmpty(this.textContent) ? 'textWaterMarker' : 'imageWaterMarker')
.then(async (image: image.PixelMap) => {
let imagePath = await ImageUtil.savePixelMap(image, FileUtil.getCacheDirPath(), `cache_${systemDateTime.getTime()}.png`)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -i ${imagePath} -filter_complex [1:v]scale=${Math.round(imageWidth)}:${Math.round(imageHeight)}[wm];[0:v][wm]overlay=${imageX}:${imageY} -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.uri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
})
.catch((error: BusinessError) => {
ToastUtils.show('处理失败:' + error.message)
LoadingDialog.dismiss()
})
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
this.showWaterMaker = false
this.textContent = ''
this.imagePath = ''
MediaUtils.getVideoSize(this.uri)
.then((size) => {
this.videoSize = size
if (size.width && size.height) {
const ratio = (DisplayUtil.getWidth() - 180) / size.width
this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)}
}
})
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), {
status: DownloadStatus.COMPLETED,
totalSize: 0,
progress: 0,
totalCount: 1,
index: 0,
callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
}
})
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '加水印' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.id('video')
.width(this.playerSize ? px2vp(this.playerSize.width) : '100%')
.height(this.playerSize ? px2vp(this.playerSize.height) : '100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Cover) // 设置视频填充模式
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
top: { anchor: 'video', align: VerticalAlign.Top },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width(this.playerSize ? px2vp(this.playerSize.width) : "100%")
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
if (this.showWaterMaker && this.uri && !this.isSuccess) {
WaterMarkerView({
content: this.textContent,
imagePath: this.imagePath,
onRectChange: (rect) => {
this.rect = rect
},
onClose: () => {
this.showWaterMaker = false
this.textContent = ''
this.imagePath = ''
}
})
.width(this.playerSize ? px2vp(this.playerSize.width) : '100%')
.height(this.playerSize ? px2vp(this.playerSize.height) : '100%')
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
top: { anchor: 'video', align: VerticalAlign.Top },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
}
}
.layoutWeight(1)
Row() {
Column(){
Image($r('app.media.ic_text_water_marker')).width(50).height(50)
Text('文字').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 })
}
.onClick(() => {
if (!this.showWaterMaker) {
this.controller.stop()
EditTextDialog.show(this.getUIContext(), {title: '添加水印', hintText: '请输入文字', confirm: (text) => {
this.textContent = text
this.showWaterMaker = true
}})
}
})
Column(){
Image($r('app.media.ic_image_water_marker')).width(50).height(50)
Text('图片').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 })
}
.margin({ left: 50 })
.onClick(() => {
if (!this.showWaterMaker) {
this.controller.stop()
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.imagePath = uris[0]
this.showWaterMaker = true
}
})
}
})
}
.margin({ top: 20 })
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff'))
.fontSize(17)
.margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定')
.fontColor($r("app.color.color_466afd"))
.fontSize(17)
.margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
if (this.showWaterMaker) {
this.addWaterMarker()
} else {
ToastUtils.show('请添加水印')
}
}
})
}
.margin({ top: 20, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,467 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, DisplayUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { RectCropView, RectPosition } from '../../../../view/RectCropView'
import { media } from '@kit.MediaKit'
import { MediaUtils } from '../../../../utils/MediaUtils'
import { MP4Parser } from '@ohos/mp4parser'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct ClipVideoPage {
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
@Local playerSize: media.PixelMapParams = { width: 0, height: 0 }
@Local currentIndex: number = 0
private controller: VideoController = new VideoController()
private rectArray : Array<string> = ['自由', '1:1', '4:3', '3:4', '16:9', '9:16']
private videoSize: media.PixelMapParams = { width: 0, height: 0 }
private rect: RectPosition = { x: 0, y: 0, width: 0, height: 0 }
clipVideo() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
let clipWidth: number = 0
let clipHeight: number = 0
if (this.currentIndex === 0) {
clipWidth = (vp2px(this.rect.width * this.videoSize.width!!) / this.playerSize.width!!)
clipHeight = (vp2px(this.rect.height * this.videoSize.height!!) / this.playerSize.height!!)
} else {
const ratio = (DisplayUtil.getWidth() - 180) / this.videoSize.width!!
let originPlayerSize: media.PixelMapParams = {width: Math.ceil(this.videoSize.width!! * ratio), height: Math.ceil(this.videoSize.height!! * ratio)}
clipWidth = (this.playerSize.width!! * this.videoSize.width!!) / originPlayerSize.width!!
clipHeight = (this.playerSize.height!! * this.videoSize.height!!) / originPlayerSize.height!!
}
let clipX: number = 0
let clipY: number = 0
if (this.currentIndex === 0) {
clipX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!!
clipY = (vp2px(this.rect.y) * this.videoSize.width!!) / this.playerSize.height!!
} else {
const ratio = (DisplayUtil.getWidth() - 180) / this.videoSize.width!!
let originPlayerSize: media.PixelMapParams = {width: Math.ceil(this.videoSize.width!! * ratio), height: Math.ceil(this.videoSize.height!! * ratio)}
clipX = clipWidth === originPlayerSize.width ? 0 : (originPlayerSize.width!! - this.playerSize.width!!) / 2 * (this.videoSize.width!!) / originPlayerSize.width!!
clipY = clipHeight === originPlayerSize.height ? 0 : (originPlayerSize.height!! - this.playerSize.height!!) / 2 * (this.videoSize.height!!) / originPlayerSize.height!!
}
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -vf \"crop=${Math.ceil(clipWidth)}:${Math.ceil(clipHeight)}:${Math.ceil(clipX)}:${Math.ceil(clipY)}\" -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.uri = FileUtil.getUriFromPath(outputPath)
MediaUtils.getVideoSize(this.uri)
.then((size) => {
this.videoSize = size
if (size.width && size.height) {
const ratio = (DisplayUtil.getWidth() - 180) / size.width
this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)}
}
})
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
setVideoRatio() {
if (this.videoSize.width && this.videoSize.height) {
const ratio = (DisplayUtil.getWidth() - 180) / this.videoSize.width
let originPlayerSize: media.PixelMapParams = {width: Math.ceil(this.videoSize.width * ratio), height: Math.ceil(this.videoSize.height * ratio)}
if (originPlayerSize.width && originPlayerSize.height) {
switch (this.currentIndex) {
case 0: {
this.playerSize = originPlayerSize
break
}
case 1: {
if (originPlayerSize.width >= originPlayerSize.height) {
this.playerSize = {width: originPlayerSize.height, height: originPlayerSize.height}
} else {
this.playerSize = {width: originPlayerSize.width, height: originPlayerSize.width}
}
break
}
case 2: {
if (originPlayerSize.width / originPlayerSize.height >= 4 / 3) {
this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 3 * 4), height: originPlayerSize.height}
} else {
this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 4 * 3)}
}
break
}
case 3: {
if (originPlayerSize.width / originPlayerSize.height <= 3 / 4) {
this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 3 * 4)}
} else {
this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 4 * 3), height: originPlayerSize.height}
}
break
}
case 4: {
if (originPlayerSize.width / originPlayerSize.height >= 16 / 9) {
this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 9 * 16), height: originPlayerSize.height}
} else {
this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 16 * 9)}
}
break
}
case 5: {
if (originPlayerSize.width / originPlayerSize.height <= 9 / 16) {
this.playerSize = {width: originPlayerSize.width, height: Math.ceil((originPlayerSize.width!!) / 9 * 16)}
} else {
this.playerSize = {width: Math.ceil((originPlayerSize.height!!) / 16 * 9), height: originPlayerSize.height}
}
break
}
}
}
}
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
this.currentIndex = 0
MediaUtils.getVideoSize(this.uri)
.then((size) => {
this.videoSize = size
if (size.width && size.height) {
const ratio = (DisplayUtil.getWidth() - 180) / size.width
this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)}
}
})
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), {
status: DownloadStatus.COMPLETED,
totalSize: 0,
progress: 0,
totalCount: 1,
index: 0,
callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
}
})
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '视频裁剪' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.id('video')
.width(this.playerSize ? px2vp(this.playerSize.width) : '100%')
.height(this.playerSize ? px2vp(this.playerSize.height) : '100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Cover) // 设置视频填充模式
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
top: { anchor: 'video', align: VerticalAlign.Top },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width(this.playerSize ? px2vp(this.playerSize.width) : "100%")
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
RectCropView({
onRectChange: (rect) => {
this.rect = rect
}
})
.width(this.playerSize ? px2vp(this.playerSize.width) : '100%')
.height(this.playerSize ? px2vp(this.playerSize.height) : '100%')
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
top: { anchor: 'video', align: VerticalAlign.Top },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
.visibility(this.uri && this.currentIndex === 0 && !this.isSuccess ? Visibility.Visible : Visibility.None)
}
.layoutWeight(1)
Grid() {
ForEach(this.rectArray, (item: string, index) => {
GridItem() {
Text(item).width(50).height(50)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.borderRadius(4)
.borderWidth(1.5)
.borderColor(this.currentIndex === index ? '#D33952' : Color.Transparent)
.backgroundColor('#282828')
.onClick(() => {
this.currentIndex = index
this.controller.stop()
this.setVideoRatio()
})
}
.width('100%')
})
}
.height(50)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
.layoutDirection(GridDirection.Row)
.visibility(this.uri && !this.isSuccess ? Visibility.Visible : Visibility.None)
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff'))
.fontSize(17)
.margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定')
.fontColor($r("app.color.color_466afd"))
.fontSize(17)
.margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
this.clipVideo()
}
})
}
.margin({ top: 20, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,289 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct MD5ResetPage {
private controller: VideoController = new VideoController()
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
modifyMD5() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(outputPath)) {
FileUtil.unlinkSync(outputPath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, outputPath)
if (FileUtil.accessSync(outputPath)) {
this.uri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: 'MD5去重' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.layoutWeight(1)
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
this.modifyMD5()
}
})
}
.margin({ top: 140, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,295 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { MP4Parser } from '@ohos/mp4parser'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct RemoveAudioPage {
private controller: VideoController = new VideoController()
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
mirrorVideo() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cachePath)) {
FileUtil.unlinkSync(cachePath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cachePath} -an -c:v copy ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.uri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '去音乐' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.layoutWeight(1)
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
this.mirrorVideo()
}
})
}
.margin({ top: 140, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,287 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { MP4Parser } from '@ohos/mp4parser'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct TakeAudioPage {
private controller: VideoController = new VideoController()
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
takeAudio() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cachePath)) {
FileUtil.unlinkSync(cachePath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp3`
let cmd = `ffmpeg -i ${cachePath} -vn -c:a mp3 ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
SaveUtils.saveAudioToMusic([outputPath])
.then(() => {
this.uri = undefined
this.isSuccess = true
this.isPlaying = false
this.showDownloadDialog()
})
.catch((e: BusinessError) => {
ToastUtils.show('提取失败:' + e.message)
})
} else {
ToastUtils.show('提取失败')
}
LoadingDialog.dismiss()
}
})
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, isAudio: true, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 2)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
build() {
Column() {
TitleBar({ title: '视频转音频' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.margin({top: 40})
.onClick(() => {
this.selectVideo()
})
.visibility(this.uri ? Visibility.None : Visibility.Visible)
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.borderRadius(12)
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 20, right: 20 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.height(350)
.margin({top: 50})
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
end: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('layout_content')
Image($r('app.media.ic_reupload_video')).width(20).height(20)
.alignRules({
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.margin({top: 20})
.onClick(() => {
this.controller.stop()
this.selectVideo()
})
.visibility(this.uri ? Visibility.Visible : Visibility.None)
Button('确认提取', {type: ButtonType.Capsule ,stateEffect:true})
.width('100%')
.height(42)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(16)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.alignRules({
top: {anchor: 'layout_content', align: VerticalAlign.Bottom}
})
.margin({top: 40})
.onClick(() => {
if (this.uri) {
this.takeAudio()
} else {
ToastUtils.show('请先上传视频')
}
})
}
.height('auto')
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
.padding({left: 20, right: 20, bottom: 30})
}.margin({ left: 16, top: 16, right: 16 })
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,321 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { MP4Parser } from '@ohos/mp4parser'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct VideoMirrorPage {
private controller: VideoController = new VideoController()
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
private orientation: number = 0
mirrorVideo() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -vf ${this.orientation === 1 ? "hflip" : "vflip"} -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.uri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '视频镜像' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.layoutWeight(1)
Row() {
Column(){
Image($r('app.media.ic_mirror_h')).width(50).height(50)
Text('水平').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 })
}
.onClick(() => {
this.orientation = 1
this.controller.stop()
this.mirrorVideo()
})
Column(){
Image($r('app.media.ic_mirror_v')).width(50).height(50)
Text('垂直').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 })
}
.margin({ left: 50 })
.onClick(() => {
this.orientation = 2
this.controller.stop()
this.mirrorVideo()
})
.visibility(Visibility.None)
}
.margin({ top: 20 })
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text('保存').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.orientation !== 0) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
ToastUtils.show('请选择镜像方向')
}
})
}
.margin({ top: 20, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,296 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { MP4Parser } from '@ohos/mp4parser'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct VideoReversePage {
private controller: VideoController = new VideoController()
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
videoReverse() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -vf reverse -af areverse -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.uri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.uri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'视频尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '视频倒放' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.layoutWeight(1)
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
this.videoReverse()
}
})
}
.margin({ top: 140, bottom: 30 })
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,633 @@
import { AppUtil, FileUtil, ObjectUtil,
PasteboardUtil,
PermissionUtil, RandomUtil, StrUtil } from '@pura/harmony-utils';
import { EventConstants } from '../../../../common/EventConstants';
import { RouterUrls } from '../../../../common/RouterUrls';
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog';
import { ImageMaterial, MaterialInfoEntity, MediaEntity, VideoMaterial } from '../../../../entity/MaterialInfoEntity';
import { VipPermissionEntity } from '../../../../entity/VipPermissionEntity';
import { LoginManager } from '../../../../manager/LoginGlobalManager';
import { ShareManager } from '../../../../manager/ShareManager';
import { ConfigManager } from '../../../../manager/UserConfigManager';
import { MediaDownloader } from '../../../../net/MediaDownloader';
import { SaveUtils } from '../../../../utils/SaveUtils';
import { ToastUtils } from '../../../../utils/ToastUtils';
import { TitleBar } from '../../../../view/TitleBar';
import { WxVideoViewModel } from '../../../../viewModel/WxVideoViewModel';
import { WxVideoMaterialPage } from './material/WxVideoMaterialPage';
import { LevelMode, router } from '@kit.ArkUI';
import { WxVideoEntity } from '../../../../entity/WxVideoEntity';
import { WxImageMaterialPage } from './material/WxImageMaterialPage';
import { OnWXResp, WXApi, WXEventHandler } from '../../../../utils/wechat/WXApiEventHandlerImpl';
import * as WxOpenSdk from '@tencent/wechat_open_sdk';
import { ErrCode, SendAuthResp } from '@tencent/wechat_open_sdk';
import { LoadingDialog } from '../../../../dialog/LoadingDialog';
import BuildProfile from 'BuildProfile';
import { WxServiceEntity } from '../../../../entity/WxServiceEntity';
import { Constants } from '../../../../common/Constants';
import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager';
import { TipDialog } from '../../../../dialog/TipDialog';
import { SimpleTipDialog } from '../../../../dialog/SimpleTipDialog';
import { JoinWxGroupCourseDialog } from '../../../../dialog/JoinWxGroupCourseDialog';
import { PrefUtils } from '../../../../utils/PrefUtils';
@Entry
@ComponentV2
struct WxVideoPage {
@Local isPlayback: boolean = false
@Local currentIndex: number = 0;
@Local isRefreshing: boolean = false;
@Local videoList: Array<VideoMaterial> = [];
@Local imageList: Array<ImageMaterial> = [];
@Local videoRowCount: number = 1;
@Local imageRowCount: number = 1;
viewModel: WxVideoViewModel = new WxVideoViewModel(this.getUIContext());
tabController: TabsController = new TabsController();
titles: Array<string> = ['视频', '图片'];
type: number = 0
mediaDownloader?: MediaDownloader | null
selectedList: Array<MediaEntity> = []
cacheFileUris: Array<string> = []
downloadIndex: number = 0
totalSize = 0
downloadStatus = DownloadStatus.DOWNLOADING
joinGroupDialogController?: CustomDialogController | null;
//从微信返回的回调
onWXResp: OnWXResp = (resp) => {
//微信返回的数据
if (resp instanceof SendAuthResp && resp.state?.endsWith('video')) {
const authResult = JSON.stringify(resp ?? {}, null , 2);
const errCode = JSON.parse(authResult).errCode as number;
if (errCode === ErrCode.ERR_OK) {
const authCode = JSON.parse(authResult).code as string;
if (ConfigManager.getPlaybackJoinType() === "img") {
this.showJoinGroupCourseDialog()
} else {
this.viewModel.bindWxUserinfo(authCode)
}
} else {
ToastUtils.show(JSON.parse(authResult).errStr);
}
}
}
@Monitor('viewModel.wxVideo')
onMaterialInfoChange(monitor: IMonitor) {
const entity = monitor.value()?.now as WxVideoEntity
this.createVideoList(entity.items);
this.createImageList(entity.items);
if (this.videoList.length > 0) {
this.tabController.changeIndex(0);
this.currentIndex = 0
} else if (this.imageList.length > 0) {
this.tabController.changeIndex(1);
this.currentIndex = 1
}
this.isRefreshing = false
}
@Monitor('viewModel.permissionInfo')
onPermissionInfoChange(monitor: IMonitor) {
const info = monitor.value()?.now as VipPermissionEntity;
if (info.auth) {
if (!LoginManager.isLogin()) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.LOGIN_PAGE, params: {from: 1}}, router.RouterMode.Single)
return;
}
this.shareOrDownload()
} else {
if (!info.auth_ad) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'download_wechat_video'}})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'download_wechat_video')
return;
}
}
}
@Monitor('viewModel.errorCode')
onErrorCodeChange(monitor: IMonitor) {
const errorCode = monitor.value()?.now as number;
if (errorCode === 12002 || errorCode === 12003 || errorCode === 12004) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'download_wechat_video')
ToastUtils.show('钻石已用完')
}
}
@Monitor('viewModel.deleteVideo')
onDeleteVideo(monitor: IMonitor) {
this.isRefreshing = true
ToastUtils.show('删除成功')
}
@Monitor('viewModel.wxService')
onWxServiceChange(monitor: IMonitor) {
const info = monitor.value()?.now as WxServiceEntity;
if (StrUtil.isEmpty(info.corpid) || StrUtil.isEmpty(info.address)) {
ToastUtils.show('获取客服信息错误')
} else {
ConfigManager.saveBindWxVideoHelper(true)
this.contactWxService(info)
}
}
@Monitor('viewModel.wxUserinfo')
onWxUserinfoChange(monitor: IMonitor) {
if (this.isPlayback) {
ConfigManager.saveBindWxPlaybackHelper(true)
} else {
ConfigManager.saveBindWxVideoHelper(true)
}
this.jumpToMiniProgram()
}
aboutToAppear(): void {
WXEventHandler.registerOnWXRespCallback(this.onWXResp)
this.initParams()
this.checkBindStatus()
}
aboutToDisappear() {
WXEventHandler.unregisterOnWXRespCallback(this.onWXResp)
}
onPageShow(): void {
this.isRefreshing = true
}
initParams() {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params) {
this.isPlayback = params.isPlayback as boolean
}
}
showSaveTip() {
if (PrefUtils.getBoolean('show_save_tip', true)) {
SimpleTipDialog.show(this.getUIContext(), {title: '重要提示', content: '下载完成后需要您点击弹窗允许保存之后才能保存文件到相册', callback: {
confirm: () => {
this.viewModel.checkVip();
PrefUtils.put('show_save_tip', false)
}
}})
} else {
this.viewModel.checkVip()
}
}
shareOrDownload() {
this.cacheFileUris.length = 0
if (this.type == 0) {
if (this.selectedList.length > 0) {
this.shareMedia(this.selectedList[0])
}
} else {
if (this.selectedList.length > 0) {
this.downloadIndex = 0
this.downloadMedia(this.selectedList[this.downloadIndex])
}
}
}
shareMedia(media: MediaEntity) {
let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName()
if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) {
this.shareFile(media)
} else {
this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING)
this.download(media, true)
}
}
async downloadMedia(media: MediaEntity) {
let filePath = FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName()
if (FileUtil.accessSync(filePath) && FileUtil.isFile(filePath) && !(media.totalSize === 0 || media.currentLen !== media.totalSize)) {
this.cacheFileUris.push(FileUtil.getUriFromPath(filePath))
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
let saved = await this.saveFile()
if (saved) {
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize)
}
} else {
ToastUtils.show('保存失败')
this.dismissDownloadDialog()
}
this.isRefreshing = true
}
} else {
this.showDownloadDialog(media instanceof VideoMaterial && media.isMerge ? DownloadStatus.VIDEO_DOWNLOADING : DownloadStatus.DOWNLOADING)
this.download(media)
}
}
async download(media: MediaEntity, isShare: boolean = false) {
this.viewModel.reportStatus(media.logid, '1')
media.initFileName()
this.mediaDownloader = MediaDownloader.getInstance()
this.mediaDownloader
.callback({
onSuccess: async (path) => {
if (isShare) {
this.shareFile(media)
} else {
this.cacheFileUris.push(FileUtil.getUriFromPath(path))
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
let saved = await this.saveFile()
if (saved) {
if (this.downloadIndex < this.selectedList.length - 1) {
this.downloadIndex++
this.downloadMedia(this.selectedList[this.downloadIndex])
} else {
this.updateDownloadDialog(DownloadStatus.COMPLETED, this.totalSize, this.totalSize)
}
} else {
ToastUtils.show('保存失败')
this.dismissDownloadDialog()
}
this.isRefreshing = true
}
}
},
onGetTotal: (total) => {
this.totalSize = total
this.updateDownloadDialog(this.downloadStatus, total)
},
onProgress: (progress) => {
this.updateDownloadDialog(this.downloadStatus, this.totalSize, progress)
},
onMerge: (step) => {
if (step === 4) {
this.updateDownloadDialog(DownloadStatus.AUDIO_DOWNLOADING)
} else {
this.updateDownloadDialog(DownloadStatus.PROCESSING)
}
},
onPause:() => {
this.dismissDownloadDialog()
},
onCancel: () => {
this.dismissDownloadDialog()
},
onFailed: (msg) => {
ToastUtils.show('下载失败:' + msg)
this.dismissDownloadDialog()
this.viewModel.reportStatus(media.logid, '-1', `${media.totalSize}`, msg)
this.reportErrorEvent(media, msg)
}
})
.down(media);
}
shareFile(media: MediaEntity) {
ShareManager.shareFile(FileUtil.getCacheDirPath() + FileUtil.separator + media.initFileName())
this.viewModel.reportStatus(media.logid, '2', `${media.totalSize}`)
this.dismissDownloadDialog()
}
async saveFile(): Promise<boolean> {
let saved = await SaveUtils.saveImageVideoToAlbumDialog(this.cacheFileUris)
if (saved) {
this.cacheFileUris.forEach((uri, index) => {
let stat = FileUtil.statSync(FileUtil.getFilePath(uri))
this.viewModel.reportStatus(this.selectedList[index].logid, '2', `${stat.size}`)
})
}
return Promise.resolve(saved)
}
showDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING) {
this.downloadStatus = status
DownloadDialog.show(this.getUIContext(), { status: status, totalSize: 0, progress: 0, totalCount: this.selectedList.length, index: this.downloadIndex, callback: {
confirm: () => {
if (this.downloadStatus === DownloadStatus.COMPLETED) {
EventReportGlobalManager.eventReport(EventConstants.DIALOG_GO_TO_VIEW, this.titles[this.currentIndex])
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, this.currentIndex)
this.getUIContext().getRouter().back()
} else {
//todo 后台下载
}
},
cancel: () => {
if (this.downloadStatus !== DownloadStatus.COMPLETED) {
if (this.mediaDownloader) {
this.mediaDownloader.cancel()
EventReportGlobalManager.eventReport(EventConstants.CANCEL_DOWNLOAD_VIDEO, this.selectedList[this.downloadIndex].url)
}
} else {
EventReportGlobalManager.eventReport(EventConstants.DIALOG_CONFIRM_SAVE_FILE, this.titles[this.currentIndex])
}
}
} })
}
updateDownloadDialog(status: DownloadStatus = DownloadStatus.DOWNLOADING, totalSize: number = 0, progress: number = 0, isAudio: boolean = false) {
this.downloadStatus = status
DownloadDialog.update({ status: status, totalSize: totalSize, progress: progress, totalCount: this.selectedList.length, index: this.downloadIndex, isAudio: isAudio })
}
dismissDownloadDialog() {
this.downloadStatus = DownloadStatus.DOWNLOADING
this.totalSize = 0
this.downloadIndex = 0
this.mediaDownloader = null
DownloadDialog.dismiss()
}
createVideoList(materials?: Array<MaterialInfoEntity>) {
const list = new Array<VideoMaterial>();
materials?.forEach((info) => {
info.material?.video?.forEach((video, index) => {
video.logid = info.logid
video.isThreading = info.material!!.threading
if (info.material?.image && info.material.image.length > index) {
video.thumb = info.material.image[index].url
}
video.flag = RandomUtil.getRandomNumber(0, 1000)
list.push(video)
})
})
this.videoList = list.map(item => ObjectUtil.assign(new VideoMaterial(), item) as VideoMaterial);
if (this.videoList.length > 0) {
this.videoList[0].isChecked = true
}
this.videoRowCount = this.computeRowCount(this.videoList);
}
createImageList(materials?: Array<MaterialInfoEntity>) {
const list = new Array<ImageMaterial>();
materials?.forEach((info) => {
info.material?.image?.forEach((image) => {
image.logid = info.logid
image.flag = RandomUtil.getRandomNumber(0, 1000)
list.push(image)
})
})
this.imageList = list.map(item => ObjectUtil.assign(new ImageMaterial(), item) as ImageMaterial);
if (this.imageList.length > 0) {
this.imageList[0].isChecked = true
}
this.imageRowCount = this.computeRowCount(this.imageList);
}
computeRowCount(list: Array<MediaEntity>): number {
if (list.length > 8) {
return 3;
} else if (list.length >= 3 && list.length <= 8) {
return 2;
} else {
return 1;
}
}
checkBindStatus() {
if (this.isPlayback && !ConfigManager.isBindWxPlaybackHelper() || !this.isPlayback && !ConfigManager.isBindWxVideoHelper()) {
if (LoginManager.getLastUserInfo()?.user_id !== LoginManager.getUserInfo()?.user_id) {
TipDialog.show(this.getUIContext(), {title: '提示', content: '系统检测到您更换了账号,请重新添加助手', callback: {
confirm: () => {
if (this.isPlayback || !ConfigManager.isWxVideoServiceEnable()) {
if (ConfigManager.getPlaybackJoinType() === "img") {
this.showJoinGroupCourseDialog()
} else {
this.wxAuth()
}
} else {
this.getWxServiceInfo()
}
}
}})
}
}
}
showJoinGroupCourseDialog() {
this.joinGroupDialogController = new CustomDialogController({
builder: JoinWxGroupCourseDialog({ isPlayback: this.isPlayback }),
width: '100%',
cornerRadius: 20,
autoCancel: true,
maskColor: '#CC000000',
levelMode: LevelMode.EMBEDDED,
backgroundBlurStyle: BlurStyle.NONE,
alignment: DialogAlignment.Bottom
})
this.joinGroupDialogController.open();
}
async wxAuth() {
if (!WXApi.isWXAppInstalled()) {
ToastUtils.show('未安装微信客户端,请先下载安装微信客户端');
return;
}
LoadingDialog.show(this.getUIContext());
let req = new WxOpenSdk.SendAuthReq;
req.isOption1 = false;
req.nonAutomatic = true;
req.scope = 'snsapi_userinfo';
req.state = BuildProfile.BUNDLE_NAME + RandomUtil.getRandomInt(0, 1000) + '_video';
req.transaction ='';
await WXApi.sendReq(AppUtil.getContext(), req)
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_SHARE_WX_PLAYBACK, '前往微信分享直播回放')
LoadingDialog.dismiss();
}
async jumpToMiniProgram() {
//保存成功跳转直播回放加群小程序
try {
let wxVideoConfig = ConfigManager.getWxVideoConfig()
if (wxVideoConfig != null && LoginManager.getUserInfo()) {
PasteboardUtil.setDataText(LoginManager.getUserInfo()!!.user_id)
wxVideoConfig.id = LoginManager.getUserInfo()!!.user_id
let params = encodeURI(JSON.stringify(wxVideoConfig)).replace('+', '%20')
let launchMiniProgramReq = new WxOpenSdk.LaunchMiniProgramReq()
launchMiniProgramReq.userName = Constants.MINI_PROGRAM_APP_ID //拉起的小程序的原始id
launchMiniProgramReq.path = `pages/index/index?path=share&param=${params}`
launchMiniProgramReq.miniprogramType = 0 //拉起小程序的类型 0-正式版 1-开发版 2-体验版
await WXApi.sendReq(AppUtil.getContext(), launchMiniProgramReq)
} else {
ToastUtils.show('获取配置失败,请联系客服')
}
} catch (e) {
ToastUtils.show('跳转失败,请联系客服')
}
}
getWxServiceInfo() {
if (!WXApi.isWXAppInstalled()) {
ToastUtils.show('未安装微信客户端,请先下载安装微信客户端');
return;
}
this.viewModel.wxServiceInfo()
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_SHARE_WX_VIDEO, "前往微信分享视频号", '')
}
/**
* 跳转客服
* @param service
*/
contactWxService(service: WxServiceEntity) {
let req = new WxOpenSdk.OpenCustomerServiceChatReq()
req.corpId = service.corpid; // 企业ID
req.url = service.address; // 客服URL
WXApi.sendReq(AppUtil.getContext(), req)
}
reportErrorEvent(media: MediaEntity, message: string) {
if (media instanceof VideoMaterial) {
EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_VIDEO, media.url, message)
} else if (media instanceof ImageMaterial) {
EventReportGlobalManager.eventReport(EventConstants.ERROR_CLIENT_DOWNLOAD_IMG, media.url, message)
}
}
build() {
RelativeContainer() {
Column() {
TitleBar({ title: this.isPlayback ? '直播回放' : '视频号' })
Stack({ alignContent: Alignment.TopStart }) {
Refresh({refreshing: this.isRefreshing}) {
Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) {
TabContent() {
WxVideoMaterialPage({mediaList: this.videoList, rowCount: this.videoRowCount, isPlayback: this.isPlayback,
onShare:(video) => {
this.type = 0
this.selectedList.length = 0
this.selectedList.push(video)
this.viewModel.checkVip();
},
onSave: (list) => {
this.type = 1
this.selectedList = list
this.showSaveTip();
},
onItemDelete: (video) => {
TipDialog.show(this.getUIContext(), {title: '提示', content: this.isPlayback ? '确定删除该直播回放?' : '确定删除该视频号?', callback: {
confirm: () => {
this.viewModel.deleteWxVideo(video.logid)
}
}})
}
})
}
TabContent() {
WxImageMaterialPage({mediaList: this.imageList, rowCount: this.imageRowCount, isPlayback: this.isPlayback,
onShare:(image) => {
this.type = 0
this.selectedList.length = 0
this.selectedList.push(image)
this.viewModel.checkVip();
},
onSave: (list) => {
this.type = 1
this.selectedList = list
this.showSaveTip();
},
onItemDelete: (image) => {
TipDialog.show(this.getUIContext(), {title: '提示', content: this.isPlayback ? '确定删除该直播回放?' : '确定删除该视频号?', callback: {
confirm: () => {
this.viewModel.deleteWxVideo(image.logid)
}
}})
}
})
}
}
.scrollable(false)
/*.onSelected((index: number) => {
this.currentIndex = index;
})*/
}
.onRefreshing(() => {
this.isRefreshing = true
this.viewModel.videoList(this.isPlayback ? 'playback' : 'wechatvideo')
})
.refreshOffset(50)
.pullToRefresh(true)
Row({ space: 40 }) {
ForEach(this.titles, (title: string, index) => {
this.tab(title, index);
})
Blank().layoutWeight(1)
Row() {
Image($r('app.media.ic_wx_video_course')).width(16).height(16)
Text('视频教程').fontColor($r('app.color.color_80ffffff')).fontSize(12).margin({ left: 4 })
}
.onClick(() => {
if (this.isPlayback) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getPlaybackCourse(), title: '视频教程'}})
} else {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getWxVideoCourse(), title: '视频教程'}})
}
})
}.padding({ left: 16, right: 16 })
.margin({ top: 10 })
}.layoutWeight(1)
}
Row() {
Image(this.isPlayback ? $r('app.media.ic_playback_helper') : $r('app.media.ic_video_helper')).width(26).height(26)
Text(this.isPlayback ? '添加直播\n回放助手' : '添加助手').margin({left: 6}).fontColor(Color.White).fontSize(12)
Image($r('app.media.ic_arrow_dp16')).width(16).height(16).margin({left: 2})
}
.padding(8)
.backgroundColor($r('app.color.color_333333'))
.borderRadius({topLeft: 10, bottomLeft: 10})
.alignRules({
right: {anchor:'__container__', align: HorizontalAlign.End},
bottom: {anchor:'__container__', align: VerticalAlign.Bottom},
})
.margin({bottom: 100})
.onClick(() => {
if (this.isPlayback || !ConfigManager.isWxVideoServiceEnable()) {
if (ConfigManager.getPlaybackJoinType() === "img") {
this.showJoinGroupCourseDialog()
} else {
this.wxAuth()
}
} else {
this.getWxServiceInfo()
}
})
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
@Builder
tab(tabName: string, tabIndex: number) {
Row() {
Text(tabName)
.fontSize(this.currentIndex === tabIndex ? 17 : 14)
.fontWeight(this.currentIndex === tabIndex ? FontWeight.Medium : FontWeight.Regular)
.lineHeight(24)
.fontColor(tabIndex === this.currentIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff'))
}
.width('auto')
.height('auto')
.onClick(() => {
this.tabController.changeIndex(tabIndex);
this.currentIndex = tabIndex;
})
}
}

View File

@ -0,0 +1,146 @@
import { RouterUrls } from '../../../../../common/RouterUrls';
import { ImageMaterial } from '../../../../../entity/MaterialInfoEntity';
import { ConfigManager } from '../../../../../manager/UserConfigManager';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { ImageMaterialItemView } from '../../../../../view/MaterialItemView';
@ComponentV2
export struct WxImageMaterialPage {
@Param isPlayback: boolean = false
@Param mediaList: Array<ImageMaterial> = [];
@Param rowCount: number = 1;
@Param onShare?: (video: ImageMaterial) => void = undefined
@Param onSave?: (list: Array<ImageMaterial>) => void = undefined
@Param onItemDelete?: (video: ImageMaterial) => void = undefined
@Local isCheckAll: boolean = false
selectedItems(): Array<ImageMaterial> {
const list = new Array<ImageMaterial>();
this.mediaList.forEach((item) => {
if (item.isChecked) {
list.push(item);
}
})
return list;
}
build() {
Stack() {
Column() {
Grid() {
ForEach(this.mediaList, (item: ImageMaterial, index) => {
GridItem() {
ImageMaterialItemView({ media: item, rowCount: this.rowCount, isWxVideo: true,
onDelete: (image) => {
if (this.onItemDelete) {
this.onItemDelete(image)
}
}
})
}
.onClick(() => {
item.isChecked = !item.isChecked;
this.isCheckAll = this.mediaList.every(item => item.isChecked)
})
})
}
.scrollBar(BarState.Off)
.columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)
.margin({ left: 16, right: 16, bottom: 15 })
.layoutWeight(1)
Row() {
Row() {
Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') :
$r('app.media.ic_check_false')).width(18).height(18)
Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 })
}
.onClick(() => {
this.isCheckAll = !this.isCheckAll;
this.mediaList.forEach((item) => {
item.isChecked = this.isCheckAll;
})
})
Blank().layoutWeight(1)
Button('转发', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (this.onShare) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要转发的图片');
} else if (list.length > 1) {
ToastUtils.show('一次只能转发一张图片');
} else {
this.onShare(list[0]);
}
}
})
Button('保存', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 12 })
.onClick(() => {
if (this.onSave) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要保存的图片');
} else {
this.onSave(list);
}
}
})
}
.backgroundColor($r('app.color.color_222222'))
.padding({
left: 16,
top: 10,
right: 16,
bottom: 30
})
.visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None)
}
Column() {
Stack() {
Image($r('app.media.ic_course_thumb')).width(240).height(240)
Image($r('app.media.ic_play_video')).width(50).height(50)
}.width('100%').height(290).backgroundColor(Color.Black)
.onClick(() => {
if (this.isPlayback) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getPlaybackCourse(), title: '视频教程'}})
} else {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getWxVideoCourse(), title: '视频教程'}})
}
})
Column() {
Text('操作步骤:').fontColor($r('app.color.color_90ffffff')).fontSize(14)
Text(this.isPlayback ? $r('app.string.wx_playback_course') : $r('app.string.wx_video_course'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(12)
.lineHeight(20)
.margin({ top: 9 })
}.alignItems(HorizontalAlign.Start).width(245).layoutWeight(1).margin({ top: 37 })
}
.visibility(this.mediaList.length > 0 ? Visibility.None : Visibility.Visible)
}
.width('100%')
.height('100%')
}
}

View File

@ -0,0 +1,145 @@
import { RouterUrls } from '../../../../../common/RouterUrls';
import { VideoMaterial } from '../../../../../entity/MaterialInfoEntity';
import { ConfigManager } from '../../../../../manager/UserConfigManager';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { VideoMaterialItemView } from '../../../../../view/MaterialItemView';
@ComponentV2
export struct WxVideoMaterialPage {
@Param isPlayback: boolean = false
@Param mediaList: Array<VideoMaterial> = [];
@Param rowCount: number = 1;
@Param onShare?: (video: VideoMaterial) => void = undefined
@Param onSave?: (list: Array<VideoMaterial>) => void = undefined
@Param onItemDelete?: (video: VideoMaterial) => void = undefined
@Local isCheckAll: boolean = false
selectedItems(): Array<VideoMaterial> {
const list = new Array<VideoMaterial>();
this.mediaList.forEach((item) => {
if (item.isChecked) {
list.push(item);
}
})
return list;
}
build() {
Stack() {
Column() {
Grid() {
ForEach(this.mediaList, (item: VideoMaterial, index) => {
GridItem() {
VideoMaterialItemView({ media: item, rowCount: this.rowCount, isWxVideo: true, onDelete:(video) => {
if (this.onItemDelete) {
this.onItemDelete(video)
}
}
})
}
.onClick(() => {
item.isChecked = !item.isChecked;
this.isCheckAll = this.mediaList.every(item => item.isChecked)
})
})
}
.scrollBar(BarState.Off)
.columnsTemplate(this.rowCount === 1 ? '1fr' : this.rowCount === 2 ? '1fr 1fr' : '1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)
.margin({ left: 16, right: 16, bottom: 15 })
.layoutWeight(1)
Row() {
Row() {
Image(this.mediaList.every(item => item.isChecked) ? $r('app.media.ic_check_true') :
$r('app.media.ic_check_false')).width(18).height(18)
Text('全选').fontColor($r('app.color.color_90ffffff')).fontSize(16).margin({ left: 7 })
}
.onClick(() => {
this.isCheckAll = !this.isCheckAll;
this.mediaList.forEach((item) => {
item.isChecked = this.isCheckAll;
})
})
Blank().layoutWeight(1)
Button('转发', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(15)
.onClick(() => {
if (this.onShare) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要转发的视频');
} else if (list.length > 1) {
ToastUtils.show('一次只能转发一个视频');
} else {
this.onShare(list[0]);
}
}
})
Button('保存', { type: ButtonType.Capsule, stateEffect: true })
.width(122)
.height(40)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.margin({ left: 12 })
.onClick(() => {
if (this.onSave) {
const list = this.selectedItems();
if (list.length === 0) {
ToastUtils.show('请选择要保存的视频');
} else {
this.onSave(list);
}
}
})
}
.backgroundColor($r('app.color.color_222222'))
.padding({
left: 16,
top: 10,
right: 16,
bottom: 30
})
.visibility(this.mediaList.length > 0 ? Visibility.Visible : Visibility.None)
}
Column() {
Stack() {
Image($r('app.media.ic_course_thumb')).width(240).height(240)
Image($r('app.media.ic_play_video')).width(50).height(50)
}.width('100%').height(290).backgroundColor(Color.Black)
.onClick(() => {
if (this.isPlayback) {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getPlaybackCourse(), title: '视频教程'}})
} else {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_PLAYER_PAGE, params: {uri: ConfigManager.getWxVideoCourse(), title: '视频教程'}})
}
})
Column() {
Text('操作步骤:').fontColor($r('app.color.color_90ffffff')).fontSize(14)
Text(this.isPlayback ? $r('app.string.wx_playback_course') : $r('app.string.wx_video_course'))
.fontColor($r('app.color.color_50ffffff'))
.fontSize(12)
.lineHeight(20)
.margin({ top: 9 })
}.alignItems(HorizontalAlign.Start).width(245).layoutWeight(1).margin({ top: 37 })
}
.visibility(this.mediaList.length > 0 ? Visibility.None : Visibility.Visible)
}
.width('100%')
.height('100%')
}
}

View File

@ -0,0 +1,436 @@
import { AppUtil, FileUtil, FormatUtil, NumberUtil, PasteboardUtil, StrUtil } from '@pura/harmony-utils';
import { EventConstants } from '../../../common/EventConstants';
import { RouterUrls } from '../../../common/RouterUrls';
import { UserEntity } from '../../../entity/UserEntity';
import { LoginManager } from '../../../manager/LoginGlobalManager';
import { ToastUtils } from '../../../utils/ToastUtils';
import { TextItemView } from '../../../view/TextItemView';
import MineViewModel from '../../../viewModel/MineViewModel';
import { router } from '@kit.ArkUI';
import { TipDialog } from '../../../dialog/TipDialog';
import { fileIo, storageStatistics } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { LoadingDialog } from '../../../dialog/LoadingDialog';
import { DiamondDetailEntity } from '../../../entity/DiamondDetailEntity';
import { EventReportGlobalManager } from '../../../manager/EventReportGlobalManager';
import { WxServiceEntity } from '../../../entity/WxServiceEntity';
import { WXApi } from '../../../utils/wechat/WXApiEventHandlerImpl';
import * as WxOpenSdk from '@tencent/wechat_open_sdk';
import { ScanUtil } from '@pura/picker_utils';
import { scanCore } from '@kit.ScanKit';
@ComponentV2
export struct MinePage {
@Local showChallenge: boolean = false;
@Local showShare: boolean = false;
@Local isLogin: boolean = LoginManager.isLogin();
@Local userinfo?: UserEntity;
@Local diamondInfo?: DiamondDetailEntity
@Local cacheSize: number = 0
scroller: Scroller = new Scroller();
viewModel: MineViewModel = new MineViewModel(this.getUIContext());
@Monitor('viewModel.userEntity')
onUserinfoChange(monitor: IMonitor) {
this.userinfo = monitor.value()?.now as UserEntity;
}
@Monitor('viewModel.wxService')
onWxServiceChange(monitor: IMonitor) {
const info = monitor.value()?.now as WxServiceEntity
if (StrUtil.isEmpty(info.corpid) || StrUtil.isEmpty(info.address)) {
ToastUtils.show('获取客服信息错误')
} else {
this.contactWxService(info)
}
}
@Monitor('viewModel.diamondInfo')
onDiamondInfoChange(monitor: IMonitor) {
this.diamondInfo = monitor.value()?.now as DiamondDetailEntity;
}
aboutToAppear(): void {
this.initObserver();
this.getCache()
this.viewModel.userinfo();
this.viewModel.getDiamondInfo()
}
initObserver() {
AppUtil.getContext().eventHub.on(EventConstants.MineRefreshEvent, () => {
this.isLogin = LoginManager.isLogin();
this.viewModel.userinfo();
this.getCache();
});
}
getCache() {
storageStatistics.getCurrentBundleStats((error: BusinessError, bundleStats: storageStatistics.BundleStats) => {
if (error) {
console.error('getCurrentBundleStats failed with error:' + JSON.stringify(error));
} else {
console.info('getCurrentBundleStats successfully:' + JSON.stringify(bundleStats));
this.cacheSize = bundleStats.cacheSize
}
});
}
// Clear cache
clearCache() {
LoadingDialog.show(this.getUIContext())
let cacheDir = AppUtil.getContext().cacheDir;
fileIo.listFile(cacheDir).then((filenames) => {
for (let i = 0; i < filenames.length; i++) {
let dirPath = cacheDir + '/' + filenames[i];
let isDirectory: boolean = false;
try {
isDirectory = fileIo.statSync(dirPath).isDirectory();
} catch (e) {
console.error(JSON.stringify(e));
}
if (isDirectory) {
fileIo.rmdirSync(dirPath);
} else {
fileIo.unlinkSync(dirPath);
}
}
})
setTimeout(() => { this.getCache() }, 200)
LoadingDialog.dismiss()
}
/**
* 跳转客服
* @param service
*/
contactWxService(service: WxServiceEntity) {
let req = new WxOpenSdk.OpenCustomerServiceChatReq()
req.corpId = service.corpid; // 企业ID
req.url = service.address; // 客服URL
WXApi.sendReq(AppUtil.getContext(), req)
}
build() {
Scroll(this.scroller) {
Stack() {
Image($r('app.media.ic_mine_top_bg'))
.width('100%')
.aspectRatio(0.8)
.id('iv_top_bg')
Image(this.userinfo?.vip === 3 ? $r('app.media.ic_vip_bg2') : $r('app.media.ic_vip_bg1'))
.width('100%')
.aspectRatio(1.4)
.visibility(this.userinfo?.vip !== 1 ? Visibility.Visible : Visibility.Hidden)
.id('iv_vip_bg')
Column() {
RelativeContainer() {
Image(StrUtil.isNotEmpty(this.userinfo?.avater) ? this.userinfo?.avater : $r('app.media.ic_default_avatar'))
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.margin({ top: 20 })
.padding(1)
.borderRadius(45)
.width(90)
.height(90)
.backgroundColor(Color.White)
.id('iv_avatar')
.onClick(() => {
if (!LoginManager.isLogin()) {
this.getUIContext().getRouter().pushUrl({ url: RouterUrls.LOGIN_PAGE, params: { from : 1 }}, router.RouterMode.Single)
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_LOGIN, 'center')
} else {
this.getUIContext().getRouter().pushUrl({ url: RouterUrls.USER_SETTINGS_PAGE })
}
})
Text('Hi! 快登录')
.fontColor(Color.White)
.fontSize(12)
.textAlign(TextAlign.Center)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: 'iv_avatar', align: HorizontalAlign.End }
})
.padding({
left: 6,
right: 6,
})
.margin({ left: -25 })
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.borderRadius({ topLeft: 6, topRight: 6, bottomRight: 6 })
.width('auto')
.height(24)
.visibility(this.isLogin ? Visibility.None : Visibility.Visible)
.id('tv_nologin_tip')
Text(this.userinfo?.ip_area)
.fontColor(Color.White)
.fontSize(12)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: 'iv_avatar', align: HorizontalAlign.End }
})
.padding({
left: 6,
right: 6,
bottom: 4
})
.margin({ left: -25 })
.width('auto')
.height(21)
.backgroundImage($r('app.media.ic_area_bg'))
.backgroundImageSize({width: 'auto', height:21})
.visibility(StrUtil.isNotEmpty(this.userinfo?.ip_area) && this.userinfo?.ip_area !== 'CN' && LoginManager.isLogin() ? Visibility.Visible : Visibility.None)
.id('tv_area')
Text(StrUtil.isNotEmpty(this.userinfo?.name) ? this.userinfo?.name : '游客')
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ top: 12 })
.alignRules({
top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom },
left: { anchor: 'iv_avatar', align: HorizontalAlign.Start },
right: { anchor: 'iv_avatar', align: HorizontalAlign.End }
})
.width('auto')
.height('auto')
.id('tv_username')
Row() {
Text('ID:' + this.userinfo?.user_id)
.fontColor($r('app.color.color_999999'))
.fontSize(14)
.id('tv_user_id')
Image($r('app.media.ic_copy_id'))
.margin({ left: 4 })
.width(14)
.height(14)
}
.alignRules({
top: { anchor: 'tv_username', align: VerticalAlign.Bottom },
left: { anchor: 'tv_username', align: HorizontalAlign.Start },
right: { anchor: 'tv_username', align: HorizontalAlign.End }
})
.margin({ top: 4 })
.width('auto')
.id('layout_copy_id')
.onClick(() => {
if (StrUtil.isNotEmpty(this.userinfo?.user_id)) {
PasteboardUtil.setDataTextSync(this.userinfo?.user_id!!)
ToastUtils.show('复制成功')
}
})
Image($r('app.media.ic_scan')).width(24).height(24)
.alignRules({
top: {anchor: '__container__', align: VerticalAlign.Top},
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.margin({ right: 20 })
.onClick(() => {
try {
ScanUtil.startScanForResult({ scanTypes: [scanCore.ScanType.QR_CODE], enableMultiMode: true, enableAlbum: true})
.then((result) => {
const code = result.originalValue
const array = code.split('-')
if (code.length === 36 && array.length === 5) {
this.getUIContext().getRouter().pushUrl({ url: RouterUrls.QRCODE_LOGIN_PAGE, params: { code: code }})
} else {
ToastUtils.show('无效二维码')
}
})
.catch(() => {
ToastUtils.show('此设备不支持二维码扫描')
})
} catch (e) {
ToastUtils.show('此设备不支持二维码扫描')
}
})
}
.margin({ top: 70 })
.width('100%')
.height(180)
.id('layout_userinfo')
Row() {
Column() {
Text(FormatUtil.getFormatFileSize(NumberUtil.toNumber(this.userinfo?.month_download_size!!)))
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontFamily('ddp500m')
.id('tv_month_size')
Text('本月流量')
.fontColor($r("app.color.color_50ffffff"))
.fontSize(12)
.margin({ top: 4 })
}
.layoutWeight(1)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.DOWNLOAD_HISTORY_PAGE})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_DOWNLOAD_HISTORY)
})
Divider().vertical(true).color('#1affffff').strokeWidth(1).height(26)
Column() {
Text(this.userinfo?.month_download_count)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontFamily('ddp500m')
.id('tv_month_count')
Text('本月下载')
.fontColor($r("app.color.color_50ffffff"))
.fontSize(12)
.margin({ top: 4 })
}
.layoutWeight(1)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.DOWNLOAD_HISTORY_PAGE})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_DOWNLOAD_HISTORY)
})
Divider().vertical(true).color('#1affffff').strokeWidth(1).height(26)
Column() {
Text('0')
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontFamily('ddp500m')
.id('tv_task_count')
Text('下载任务')
.fontColor($r("app.color.color_50ffffff"))
.fontSize(12)
.margin({ top: 4 })
}
.layoutWeight(1)
.visibility(Visibility.None)
}
.padding({ top: 9, bottom: 9 })
.width('100%')
.height('auto')
.id('layout_download_info')
RelativeContainer() {
Text(this.userinfo?.vip === 1 ? '素材魔方·会员' : this.userinfo?.vip_name)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontFamily('almmsht')
.id('tv_vip_name')
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Center }
})
Text(this.userinfo?.vip === 1 ? '点击成为会员解锁所有功能' : this.userinfo?.vip === 2 ? this.userinfo.vip_expire + ' 会员到期' : '享受永久会员专属权益')
.fontColor($r('app.color.color_60ffffff'))
.fontSize(14)
.margin({ top: 8 })
.id('tv_vip_expire')
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Center }
})
Button(this.userinfo?.vip === 1 ? '立即开通' : this.userinfo?.vip === 2 ? '立即续费' : '永久会员' , { type: ButtonType.Capsule, stateEffect: true })
.fontColor('#291966')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.linearGradient({
colors: [['#D4C4F9', 0.0], ['#E9E6F9', 1.0]],
direction: GradientDirection.Top
})
.width(88)
.height(32)
.visibility(this.userinfo?.vip === 3 ? Visibility.None : Visibility.Visible)
.id('btn_to_vip')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIP_PAGE, params: {origin: 'center'}})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_MEMBER_RECHARGE, 'center')
})
}
.margin({ left: 16, top: 10, right: 16 })
.padding({ left: 16, right: 16 })
.backgroundImage($r('app.media.ic_mine_vip_bg'))
.backgroundImageSize({ width: '100%' })
.aspectRatio(4.28)
.id('layout_vip_info')
Column() {
TextItemView({ image: $r("app.media.ic_mine_icon1"), leftText: '免单挑战' })
.height(60)
.id('item_challenge')
.visibility(/*this.showChallenge ? Visibility.Visible : Visibility.None*/Visibility.None)
TextItemView({ image: $r("app.media.ic_mine_icon2"), leftText: '兑换钻石' })
.height(60)
.id('item_diamond')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.RECHARGE_DIAMOND_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_RECHARGE_DIAMOND, 'center')
})
.visibility(this.diamondInfo && this.diamondInfo?.buy_total > 0 ? Visibility.Visible : Visibility.None)
TextItemView({ image: $r("app.media.ic_mine_icon3"), leftText: '优惠券' }).height(60).id('item_coupon')
.visibility(Visibility.None)
TextItemView({ image: $r("app.media.ic_mine_icon4"), leftText: '分享APP' })
.height(60)
.id('item_share')
.visibility(/*this.showShare ? Visibility.Visible : Visibility.None*/Visibility.None)
TextItemView({ image: $r("app.media.ic_mine_icon5"), leftText: '联系客服' }).height(60).id('item_service')
.onClick(() => {
if (!WXApi.isWXAppInstalled()) {
ToastUtils.show('未安装微信客户端,请先下载安装微信客户端');
return;
}
this.viewModel.getWxService()
})
TextItemView({ image: $r("app.media.ic_mine_icon6"), leftText: '清除缓存', rightText: FileUtil.getFormatFileSize(this.cacheSize) }).height(60).id('item_cache')
.onClick(() => {
if (this.cacheSize > 0) {
TipDialog.show(this.getUIContext(), {title: '提示', content: '确定清除缓存?', callback: {
confirm: () => {
this.clearCache()
}
}})
}
})
TextItemView({ image: $r("app.media.ic_mine_icon7"), leftText: '设置' }).height(60).id('item_setting')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.SETTING_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_SYSTEM_SETTING)
})
}
.margin({ top: 16, left: 16, right: 16 })
.backgroundColor('#1E1D24')
.borderRadius(8)
.id('layout_menu')
Blank().layoutWeight(1)
}
}
.alignContent(Alignment.Top)
}
.padding({ bottom: 20 })
.scrollBar(BarState.Off)
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,436 @@
import { RouterUrls } from '../../../../common/RouterUrls';
import { VipMealEntity } from '../../../../entity/VipMealEntity'
import { LoginManager } from '../../../../manager/LoginGlobalManager';
import { ConfigManager } from '../../../../manager/UserConfigManager';
import { ToastUtils } from '../../../../utils/ToastUtils';
import { TitleBar } from '../../../../view/TitleBar'
import { LevelMode, router } from '@kit.ArkUI';
import { OnWXResp, WXApi, WXEventHandler } from '../../../../utils/wechat/WXApiEventHandlerImpl';
import { PayOrderEntity } from '../../../../entity/OrderPayEntity';
import { PayUtils } from '../../../../utils/PayUtils';
import { BusinessError } from '@kit.BasicServicesKit';
import { NumberUtil, ObjectUtil, StrUtil } from '@pura/harmony-utils';
import { ErrCode, PayResp } from '@tencent/wechat_open_sdk';
import { DiamondViewModel } from '../../../../viewModel/DiamondViewModel';
import { LoadingDialog } from '../../../../dialog/LoadingDialog';
import { DiamondDetailEntity } from '../../../../entity/DiamondDetailEntity';
import { DiamondItemView } from '../../../../view/DiamondItemView';
import { DiamondRuleDialog } from '../../../../dialog/DiamondRuleDialog';
import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager';
import { EventConstants } from '../../../../common/EventConstants';
import { OrderEntity } from '../../../../entity/OrderEntity';
import { TipDialog } from '../../../../dialog/TipDialog';
@Entry
@ComponentV2
struct DiamondPage {
@Local origin: string = 'center';
@Local diamondInfo?: DiamondDetailEntity
@Local diamondList: Array<VipMealEntity> = [];
@Local vipMeal?: VipMealEntity;
@Local isAgree: boolean = false;
@Local payType: number = 0; //0微信支付 1支付宝支付
@Local totalPrice: number = 0;
showQueryTip: boolean = false //是否显示支付状态查询提示
diamondRuleDialogController?: CustomDialogController | null;
viewModel: DiamondViewModel = new DiamondViewModel(this.getUIContext());
orderEntity?: PayOrderEntity;
//从微信返回的回调
onWXResp: OnWXResp = (resp) => {
//微信返回的数据
if (resp instanceof PayResp) {
const payResult = JSON.stringify(resp ?? {}, null, 2);
const errCode = JSON.parse(payResult).errCode as number;
if (errCode === ErrCode.ERR_OK) {
this.viewModel.getOrderInfo(this.orderEntity!!.orderId)
} else {
ToastUtils.show(JSON.parse(payResult).errStr);
EventReportGlobalManager.eventReport(
EventConstants.PAY_CANCEL,
`weixin:${this.origin}`, `{type:\"diamond\", orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}`
)
}
}
LoadingDialog.dismiss();
}
@Monitor('viewModel.diamondInfo')
onDiamondInfoChange(monitor: IMonitor) {
this.diamondInfo = monitor.value()?.now as DiamondDetailEntity;
}
@Monitor('viewModel.goodsList')
onGoodsListChange(monitor: IMonitor) {
const list = monitor.value()?.now as Array<VipMealEntity>;
this.diamondList = list.map(item => ObjectUtil.assign(new VipMealEntity(), item) as VipMealEntity);
if (this.diamondList.length > 0) {
this.vipMeal = this.diamondList.find(item => item.checked === true);
if (!this.vipMeal) {
this.vipMeal = this.diamondList[0];
}
if (this.vipMeal) {
this.totalPrice = NumberUtil.toNumber(this.vipMeal.price);
this.releasePayType();
}
}
}
@Monitor('viewModel.payOrderEntity')
onPayOrderChange(monitor: IMonitor) {
const payOrderEntity = monitor.value()?.now as PayOrderEntity;
this.orderEntity = payOrderEntity;
if (this.payType === 0) {
if (StrUtil.isNotEmpty(this.vipMeal?.weixinMpOriId)) {
PayUtils.toWXMPPay(payOrderEntity.outTradeNo, this.vipMeal!!.weixinMpOriId)
this.showQueryTip = true
} else {
PayUtils.toWXPay(payOrderEntity);
}
} else {
PayUtils.toAliPay(payOrderEntity.payParam)
.then((result) => {
this.parseAlipayResult(result);
})
.catch((error: BusinessError) => {
console.log(error.message);
ToastUtils.show('支付失败');
});
}
}
@Monitor('viewModel.orderInfoEntity')
onOrderInfoChange(monitor: IMonitor) {
const orderEntity = monitor.value()?.now as OrderEntity;
if (orderEntity.status == "2") {
ToastUtils.show('支付成功');
EventReportGlobalManager.eventReport(
EventConstants.PAY_SUCCESS,
`alipay:${this.origin}`, `{type:\"diamond\", orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}`
)
this.viewModel.getDiamondInfo()
}
}
aboutToAppear() {
WXEventHandler.registerOnWXRespCallback(this.onWXResp)
this.initParams();
this.viewModel.getDiamondInfo()
this.viewModel.mealList();
}
aboutToDisappear() {
WXEventHandler.unregisterOnWXRespCallback(this.onWXResp)
this.diamondRuleDialogController = null
this.viewModel.cancelInterval()
}
onPageShow(): void {
if (this.showQueryTip) {
TipDialog.show(this.getUIContext(), { title: '温馨提示', content: '是否已完成支付', leftText: '未支付', rightText: '已支付', callback: {
confirm: () => {
this.viewModel.getOrderInfo(this.orderEntity!!.orderId)
}
} }, false)
this.showQueryTip = false
}
}
initParams() {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params) {
this.origin = params.origin as string;
}
}
createOrder() {
if (!ConfigManager.isNoLoginPayEnable() && !LoginManager.isLogin()) {
ToastUtils.show('请登录后支付');
this.getUIContext().getRouter().pushUrl({ url: RouterUrls.LOGIN_PAGE, params: { from: 1 } }, router.RouterMode.Single);
return;
}
if (this.payType === 0 && !WXApi.isWXAppInstalled()) {
ToastUtils.show('请先下载安装微信客户端');
return;
}
if (this.payType === 0) {
if (StrUtil.isNotEmpty(this.vipMeal!!.weixinMpOriId)) {
this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'combo', this.origin, '');
} else {
this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'weixin', this.origin, '');
}
} else {
this.viewModel.createOrder(this.vipMeal?.goods_id!!, 'alipay', this.origin, '');
}
}
parseAlipayResult(result: Map<string, string>) {
const resultStatus = result.get('resultStatus');
if (resultStatus === '9000') {
this.viewModel.getOrderInfo(this.orderEntity!!.orderId)
} else if (resultStatus === '6001') {
ToastUtils.show('支付取消');
EventReportGlobalManager.eventReport(
EventConstants.PAY_CANCEL,
`alipay:${this.origin}`, `{type:\"diamond\", orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}`
)
} else {
ToastUtils.show('支付失败');
EventReportGlobalManager.eventReport(
EventConstants.ERROR_CLIENT_ALIPAY_ERR,
`{orderId:${this.orderEntity?.orderId}, meal:${JSON.stringify(this.diamondInfo)}}`, resultStatus
)
}
}
releasePayType() {
if (this.vipMeal?.pay_type.startsWith('alipay')) {
this.payType = 1;
} else if (this.vipMeal?.pay_type.startsWith('weixin')) {
this.payType = 0;
}
}
showRuleDialog() {
this.diamondRuleDialogController = new CustomDialogController({
builder: DiamondRuleDialog(),
width: '100%',
cornerRadius: 20,
maskColor: '#CC000000',
levelMode: LevelMode.EMBEDDED,
backgroundBlurStyle: BlurStyle.NONE,
alignment: DialogAlignment.Bottom
})
this.diamondRuleDialogController.open();
}
build() {
RelativeContainer() {
Image($r('app.media.ic_diamond_top_bg')).width('100%').aspectRatio(1.524)
Column() {
Scroll() {
Column() {
RelativeContainer() {
Image(LoginManager.getUserInfo()?.avater)
.width(46)
.height(46)
.borderRadius(25)
.margin({ left: 7 })
.id('iv_avatar')
Text(LoginManager.getUserInfo()?.vip === 1 ? '非会员' : LoginManager.getUserInfo()?.vip === 3 ? '终身会员' : LoginManager.getUserInfo()?.vip_name)
.fontColor('#4F59FF')
.fontSize(12)
.width('auto')
.height(22)
.padding({ left: 6, right: 6 })
.borderRadius(25)
.linearGradient({
colors: [['#EFF0FB', 0.0], ['#ABC3FF', 1.0]],
direction: GradientDirection.Right
})
.alignRules({
top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom },
left: { anchor: 'iv_avatar', align: HorizontalAlign.Start },
right: { anchor: 'iv_avatar', align: HorizontalAlign.End }
})
.margin({ top: -9 })
Text(LoginManager.getUserInfo()?.name)
.fontColor('#FFF4D0')
.fontSize(15)
.alignRules({
top: { anchor: 'iv_avatar', align: VerticalAlign.Top },
bottom: { anchor: 'iv_avatar', align: VerticalAlign.Center },
left: { anchor: 'iv_avatar', align: HorizontalAlign.End }
})
.margin({ left: 16 })
.id('tv_username')
Text('ID:' + LoginManager.getUserInfo()?.user_id).fontColor('#FFF4D0').fontSize(12)
.alignRules({
left: { anchor: 'tv_username', align: HorizontalAlign.Start },
top: { anchor: 'iv_avatar', align: VerticalAlign.Center },
bottom: { anchor: 'iv_avatar', align: VerticalAlign.Bottom }
})
Text() {
Span('剩余总次数 ')
Span(`${this.diamondInfo?.remain}`).fontSize(22).fontFamily('ddp500m')
Span(' 次')
}.fontColor('#FFF4D0').fontSize(12)
.alignRules({
top: { anchor: 'iv_avatar', align: VerticalAlign.Bottom },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
}
.aspectRatio(2.584)
.margin({ top: 120, left: 26, right: 26 })
.padding({ top: 16, left: 12, right: 12 })
.backgroundImage($r('app.media.ic_diamond_vip_bg1'))
.backgroundImageSize({ width: '100%' })
Image($r('app.media.ic_diamond_vip_bg2')).width('100%').aspectRatio(4.866)
.margin({ top: -30 })
Column() {
Text('钻石消耗数量').fontColor('#8F4A2A').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 4 })
Column() {
Row() {
Text('本月固定钻石数量').fontColor($r('app.color.color_1a1a1a')).fontSize(14)
Text('每月重置')
.textAlign(TextAlign.Center)
.fontColor('#FFA61E')
.fontSize(12)
.height(18)
.padding({ left: 4, right: 4 })
.borderWidth(1)
.borderRadius(4)
.borderColor('#FFA61E')
.margin({left: 8})
Blank().layoutWeight(1)
Text() {
Span(`${this.diamondInfo?.month_used}`).fontColor('#FFA61E').fontSize(20).fontFamily('ddp500m')
Span(`/${this.diamondInfo?.month_total}`).fontColor($r('app.color.color_1a1a1a')).fontSize(14)
}
}
Progress({ value: this.diamondInfo?.month_used, total: this.diamondInfo?.month_total, type: ProgressType.Linear })
.width('100%')
.height(10)
.style({ strokeWidth: 10, strokeRadius: 5 })
.color('#FF9026')
.borderRadius(5)
.margin({top: 16})
Row() {
Text('兑换钻石数量').fontColor($r('app.color.color_1a1a1a')).fontSize(14)
Text('用完即止')
.textAlign(TextAlign.Center)
.fontColor('#FFA61E')
.fontSize(12)
.height(18)
.padding({ left: 4, right: 4 })
.borderWidth(1)
.borderRadius(4)
.borderColor('#FFA61E')
.margin({left: 8})
Blank().layoutWeight(1)
Text() {
Span(`${this.diamondInfo?.buy_used}`).fontColor('#FFA61E').fontSize(20).fontFamily('ddp500m')
Span(`/${this.diamondInfo?.buy_total}`).fontColor($r('app.color.color_1a1a1a')).fontSize(14)
}
}.margin({top: 24})
Progress({ value: this.diamondInfo?.buy_used, total: this.diamondInfo?.buy_total, type: ProgressType.Linear })
.width('100%')
.height(10)
.style({ strokeWidth: 10, strokeRadius: 5 })
.color('#FF9026')
.borderRadius(5)
.margin({top: 16})
}
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ top: 10 })
.padding({
left: 12,
top: 16,
right: 12,
bottom: 16
})
}
.alignItems(HorizontalAlign.Start)
.height('auto')
.padding(10)
.margin({ top: -45, left: 16, right: 16 })
.backgroundImage($r('app.media.ic_diamond_count_bg'))
.backgroundImageSize({ width: '100%' })
.borderRadius(10)
.clip(true)
Text('钻石套餐').fontColor($r('app.color.color_1a1a1a')).fontSize(16).fontWeight(FontWeight.Medium).margin({left: 16, top: 26})
.alignSelf(ItemAlign.Start)
List({space: 12}) {
ForEach(this.diamondList, (item: VipMealEntity) => {
ListItem() {
DiamondItemView({ goodInfo: item })
}
.onClick(() => {
let goodsInfo = this.diamondList.find(item => item.checked) as VipMealEntity
goodsInfo.checked = false
item.checked = true
this.vipMeal = item
this.releasePayType()
})
})
}
.width('auto')
.height('auto')
.margin({left: 16, top: 14, right: 16, bottom: 15 })
}
}
.layoutWeight(1)
.scrollBar(BarState.Off)
Column() {
Row() {
Row() {
Image($r('app.media.ic_wx_pay3')).width(26).height(26)
Text('微信').fontColor($r('app.color.color_1a1a1a')).fontSize(15).margin({left: 10})
Blank().layoutWeight(1)
Image(this.payType === 0 ? $r('app.media.ic_pay_true2') : $r('app.media.ic_pay_false2')).width(18).height(18)
}.layoutWeight(1)
.visibility(this.vipMeal?.pay_type.includes('weixin') ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.payType = 0;
})
Divider().width(1).strokeWidth(1).height(26).color('#EEEEEE').margin({left: 20, right: 20})
Row() {
Image($r('app.media.ic_ali_pay3')).width(26).height(26)
Text('支付宝').fontColor($r('app.color.color_1a1a1a')).fontSize(15).margin({left: 10})
Blank().layoutWeight(1)
Image(this.payType === 1 ? $r('app.media.ic_pay_true2') : $r('app.media.ic_pay_false2')).width(18).height(18)
}.layoutWeight(1)
.visibility(this.vipMeal?.pay_type.includes('alipay') ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.payType = 1;
})
}
Button('立即支付', {type: ButtonType.Capsule, stateEffect: true})
.width('100%')
.height(46)
.fontColor(Color.White)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FF8D1B')
.margin({top: 17})
.onClick(() => {
if (this.vipMeal) {
this.createOrder()
}
})
}.backgroundColor(Color.White)
.padding({left: 16, top: 8, right: 16, bottom: 30})
.shadow({radius: 6, offsetY: -2, color: '#1d000000'})
}
TitleBar({
title: '素材加油站',
isDark: false,
rightText: '规则说明',
rightColor: '#6F3A21',
onRightClick: () => {
this.showRuleDialog()
}
}).id('titleBar')
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
}
}

View File

@ -0,0 +1,143 @@
import { AppUtil, DateUtil } from "@pura/harmony-utils";
import { EventConstants } from "../../../../common/EventConstants";
import { DownloadHistoryEntity } from "../../../../entity/DownloadHistoryEntity";
import { DownloadHistoryItemView } from "../../../../view/DownloadHistoryItemView";
import { EmptyView, PageStatus } from "../../../../view/EmptyView";
import { DownloadHistoryViewModel } from "../../../../viewModel/DownloadHistoryViewModel";
@ComponentV2
export struct DownloadHistoryItemPage {
@Local isRefreshing: boolean = false;
@Local isLoading: boolean = false;
@Local refreshOffset: number = 0;
@Local refreshState: RefreshStatus = RefreshStatus.Inactive;
@Local historyList: Array<DownloadHistoryEntity> = []
@Param type: number = 0
viewModel: DownloadHistoryViewModel = new DownloadHistoryViewModel(this.getUIContext())
page: number = 1
canLoadMore: boolean = false
@Monitor('viewModel.getHistory')
onGetDownloadHistory(monitor: IMonitor) {
const list = monitor.value()?.now as Array<DownloadHistoryEntity>
if (this.page === 1) {
this.historyList = list
this.isRefreshing = false
} else {
this.historyList = this.historyList.concat(list)
this.isLoading = false
}
this.canLoadMore = list.length === 20
}
aboutToAppear(): void {
this.isRefreshing = true
this.initObserver()
}
aboutToDisappear(): void {
AppUtil.getContext().eventHub.off(EventConstants.DownloadHistoryRefreshEvent)
}
initObserver() {
AppUtil.getContext().eventHub.on(EventConstants.DownloadHistoryRefreshEvent, (type: number) => {
if (type === this.type) {
this.isRefreshing = true
}
})
}
getStartTime(): number {
let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1)
let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 2, 1)
if (this.type === 0) {
return Math.trunc(nowDate.getTime() / 1000)
} else {
return Math.trunc(lastDate.getTime() / 1000)
}
}
getEndTime(): number {
let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth(), 1)
let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1)
if (this.type === 0) {
return Math.trunc(nowDate.getTime() / 1000)
} else {
return Math.trunc(lastDate.getTime() / 1000)
}
}
build() {
Stack() {
Refresh({refreshing: this.isRefreshing, builder: this.refreshBuilder()}) {
List({space: 12}) {
ForEach(this.historyList, (item: DownloadHistoryEntity) => {
ListItem() {
DownloadHistoryItemView({ historyEntity: item })
}
})
ListItem() {
this.footer();
}
}
.width('100%')
.height('100%')
.onScrollIndex((start: number, end: number) => {
// 当达到列表末尾时,触发新数据加载。
if (this.canLoadMore && end >= this.historyList.length - 1) {
this.page++
this.isLoading = true;
this.viewModel.getHistoryList(`${this.page}`, `${this.getStartTime()}`, `${this.getEndTime()}`)
}
})
}
.onRefreshing(() => {
this.page = 1
this.isRefreshing = true
this.viewModel.getHistoryList(`${this.page}`, `${this.getStartTime()}`, `${this.getEndTime()}`)
})
.refreshOffset(50)
.pullToRefresh(true)
.visibility(this.historyList.length > 0 ? Visibility.Visible : Visibility.None)
EmptyView({
status: this.historyList.length > 0 ? PageStatus.GONE : PageStatus.NO_DATA,
noDataImage: $r('app.media.ic_empty_data'),
noDataText: '暂无数据'
})
}
.width('100%')
.height('100%')
}
@Builder
refreshBuilder() {
Stack({ alignContent: Alignment.Bottom }) {
// 可以通过刷新状态控制是否存在Progress组件。
// 当刷新状态处于下拉中或刷新中状态时Progress组件才存在。
if (this.refreshState != RefreshStatus.Inactive && this.refreshState != RefreshStatus.Done) {
Progress({ value: this.refreshOffset, total: 50, type: ProgressType.Ring })
.width(32).height(32)
.style({ status: this.isRefreshing ? ProgressStatus.LOADING : ProgressStatus.PROGRESSING })
.margin(10)
}
}
.clip(true)
.height("100%")
.width("100%")
}
@Builder
footer() {
Row() {
LoadingProgress().height(32).width(48)
Text("加载中")
}.width("100%")
.height(50)
.justifyContent(FlexAlign.Center)
// 当不处于加载中状态时隐藏组件。
.visibility(this.isLoading ? Visibility.Visible : Visibility.Hidden)
}
}

View File

@ -0,0 +1,102 @@
import { AppUtil, DateUtil } from '@pura/harmony-utils';
import { EventConstants } from '../../../../common/EventConstants';
import { TipDialog } from '../../../../dialog/TipDialog';
import { TitleBar } from '../../../../view/TitleBar';
import { DownloadHistoryViewModel } from '../../../../viewModel/DownloadHistoryViewModel';
import { DownloadHistoryItemPage } from './DownloadHistoryItemPage';
@Entry
@ComponentV2
struct DownloadHistory {
@Local currentIndex: number = 0;
tabController: TabsController = new TabsController();
titles: Array<string> = ['本月', '上月'];
viewModel: DownloadHistoryViewModel = new DownloadHistoryViewModel(this.getUIContext())
@Monitor('viewModel.deleteHistory')
onDeleteDownloadHistory(monitor: IMonitor) {
AppUtil.getContext().eventHub.emit(EventConstants.DownloadHistoryRefreshEvent, this.currentIndex)
}
getStartTime(): number {
let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1)
let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 2, 1)
if (this.currentIndex === 0) {
return Math.trunc(nowDate.getTime() / 1000)
} else {
return Math.trunc(lastDate.getTime() / 1000)
}
}
getEndTime(): number {
let nowDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth(), 1)
let lastDate = new Date(DateUtil.getNowYear(), DateUtil.getNowMonth() - 1, 1)
if (this.currentIndex === 0) {
return Math.trunc(nowDate.getTime() / 1000)
} else {
return Math.trunc(lastDate.getTime() / 1000)
}
}
build() {
Column() {
TitleBar({ title: '本月下载' })
Stack({ alignContent: Alignment.TopStart }) {
Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) {
TabContent() {
DownloadHistoryItemPage({type: 0})
}
TabContent() {
DownloadHistoryItemPage({type: 1})
}
}
.scrollable(false)
/*.onSelected((index: number) => {
this.currentIndex = index;
})*/
Row({ space: 40 }) {
ForEach(this.titles, (title: string, index) => {
this.tab(title, index);
})
Blank().layoutWeight(1)
Row() {
Image($r('app.media.ic_clear_record')).width(16).height(16)
Text('全部清空').fontColor($r('app.color.color_80ffffff')).fontSize(12).margin({ left: 4 })
}
.onClick(() => {
TipDialog.show(this.getUIContext(), {title: '提示', content: '确定清空记录?', callback: {
confirm: () => {
this.viewModel.deleteHistoryList(`${this.getStartTime()}`, `${this.getEndTime()}`)
}
}})
})
}.padding({ left: 16, right: 16 })
.margin({ top: 10 })
}.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
@Builder
tab(tabName: string, tabIndex: number) {
Row() {
Text(tabName)
.fontSize(this.currentIndex === tabIndex ? 17 : 14)
.fontWeight(this.currentIndex === tabIndex ? FontWeight.Medium : FontWeight.Regular)
.lineHeight(24)
.fontColor(tabIndex === this.currentIndex ? $r("app.color.color_466afd") : $r('app.color.color_50ffffff'))
}
.width('auto')
.height('auto')
.onClick(() => {
this.tabController.changeIndex(tabIndex);
this.currentIndex = tabIndex;
})
}
}

View File

@ -0,0 +1,103 @@
import { AppUtil } from '@pura/harmony-utils'
import { EventConstants } from '../../../../common/EventConstants'
import { RouterUrls } from '../../../../common/RouterUrls'
import { TipDialog } from '../../../../dialog/TipDialog'
import { LoginManager } from '../../../../manager/LoginGlobalManager'
import { ConfigManager } from '../../../../manager/UserConfigManager'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { TextItemChildView } from '../../../../view/TextItemChildView'
import { TitleBar } from '../../../../view/TitleBar'
import { SettingsViewModel } from '../../../../viewModel/SettingsViewModel'
import { router } from '@kit.ArkUI'
import { EventReportGlobalManager } from '../../../../manager/EventReportGlobalManager'
@Entry
@ComponentV2
struct SettingsPage {
viewModel: SettingsViewModel = new SettingsViewModel(this.getUIContext());
@Monitor('viewModel.destroy')
onDestroy(monitor: IMonitor) {
EventReportGlobalManager.eventReport(EventConstants.CANCEL_ACCOUNT)
this.logout();
ToastUtils.show('账户已注销');
}
logout() {
LoginManager.saveLastUserInfo(LoginManager.getUserInfo()!!);
ConfigManager.saveBindWxPlaybackHelper(false);
ConfigManager.saveBindWxVideoHelper(false);
LoginManager.logout();
AppUtil.getContext().eventHub.emit(EventConstants.LogoutSuccessEvent);
this.getUIContext().getRouter().replaceUrl({ url: RouterUrls.LOGIN_PAGE }, router.RouterMode.Single);
}
build() {
Column() {
TitleBar({ title: '设置' }).width('100%')
Scroll() {
Column() {
TextItemChildView({ text: '意见反馈' }).height(60).margin({ left: 16, right: 16 })
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.FEEDBACK_PAGE})
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_FEEDBACK, 'center')
})
TextItemChildView({ text: '账号绑定' }).height(60).margin({ left: 16, right: 16 })
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.BIND_ACCOUNT_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_ACCOUNT_BIND)
})
TextItemChildView({ text: '账号管理' }).height(60).margin({ left: 16, right: 16 })
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.MANAGE_ACCOUNT_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_ACCOUNT_MANAGE)
})
TextItemChildView({ text: '关于我们' }).height(60).margin({ left: 16, right: 16 })
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.ABOUT_PAGE});
EventReportGlobalManager.eventReport(EventConstants.JUMP_TO_ABOUT_US)
})
TextItemChildView({ text: '注销账号' }).height(60).margin({ left: 16, right: 16 })
.onClick(() => {
TipDialog.show(this.getUIContext(), {title: '提示', content: '为了您的账户安全,注销账户后将会永久清除与该账户相关的所有信息,服务器不再保存', callback: {
confirm: () => {
this.viewModel.userDestroy();
}
}})
})
.visibility(LoginManager.isLogin() ? Visibility.Visible : Visibility.None)
}
.justifyContent(FlexAlign.Start)
.height('100%')
}
.layoutWeight(1)
Stack() {
Button('退出登录', { type: ButtonType.Capsule, stateEffect: true })
.fontColor(Color.White)
.fontSize(15)
.fontWeight(FontWeight.Medium)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.width('100%')
.height(46)
.onClick(() => {
TipDialog.show(this.getUIContext(), {title: '温馨提示', content: '确定退出登录?', callback: {
confirm: () => {
EventReportGlobalManager.eventReport(EventConstants.EXIT_LOGIN)
this.logout();
}
}})
})
}
.padding({ left: 16, right: 16 })
.margin({ bottom: 30 })
.visibility(LoginManager.isLogin() ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -0,0 +1,43 @@
import { AppUtil } from '@pura/harmony-utils'
import { Constants } from '../../../../../common/Constants'
import { RouterUrls } from '../../../../../common/RouterUrls'
import { TextItemChildView } from '../../../../../view/TextItemChildView'
import { TitleBar } from '../../../../../view/TitleBar'
@Entry
@ComponentV2
struct AboutPage {
build() {
Column() {
TitleBar({ title: '关于' }).width('100%')
Image($r('app.media.ic_login_logo')).width(100).height(100).margin({ top: 100 })
Text($r('app.string.app_name'))
.fontColor(Color.White)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 16 })
.width('auto')
Text('版本号v' + AppUtil.getVersionName())
.fontColor($r('app.color.color_999999'))
.fontSize(14)
.margin({ top: 6 })
.width('auto')
TextItemChildView({ text: '用户协议' }).height(60).margin({ left: 16, top: 52, right: 16 })
.onClick(() => {
this.getUIContext()
.getRouter()
.pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '用户协议', url: Constants.USER_AGREEMENT } })
})
TextItemChildView({ text: '隐私政策', divider: false }).height(60).margin({ left: 16, right: 16 })
.onClick(() => {
this.getUIContext()
.getRouter()
.pushUrl({ url: RouterUrls.WEB_PAGE, params: { title: '隐私政策', url: Constants.PRIVACY_POLICY } })
})
}
.justifyContent(FlexAlign.Start)
.backgroundColor($r('app.color.window_background'))
.width('100%')
.height('100%')
}
}

View File

@ -0,0 +1,186 @@
import { AppUtil, RandomUtil, StrUtil } from '@pura/harmony-utils';
import { TipDialog } from '../../../../../dialog/TipDialog';
import { UserEntity } from '../../../../../entity/UserEntity';
import { ConfigManager } from '../../../../../manager/UserConfigManager';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { TitleBar } from '../../../../../view/TitleBar';
import BindAccountViewModel from '../../../../../viewModel/BindAccountViewModel';
import * as WxOpenSdk from '@tencent/wechat_open_sdk';
import { ErrCode, SendAuthResp } from '@tencent/wechat_open_sdk';
import { OnWXResp, WXApi, WXEventHandler } from '../../../../../utils/wechat/WXApiEventHandlerImpl';
import BuildProfile from 'BuildProfile';
import { BindPhoneDialog } from '../../../../../dialog/BindPhoneDialog';
import { LevelMode } from '@kit.ArkUI';
import { EventConstants } from '../../../../../common/EventConstants';
import { LoadingDialog } from '../../../../../dialog/LoadingDialog';
import { EventReportGlobalManager } from '../../../../../manager/EventReportGlobalManager';
@Entry
@ComponentV2
struct BindAccountPage {
bindPhoneDialogController?: CustomDialogController | null;
viewModel: BindAccountViewModel = new BindAccountViewModel(this.getUIContext());
@Local userEntity?: UserEntity;
//从微信返回的回调
onWXResp: OnWXResp = (resp) => {
//微信返回的数据
if (resp instanceof SendAuthResp && resp.state?.endsWith('bind')) {
const authResult = JSON.stringify(resp ?? {}, null , 2);
const errCode = JSON.parse(authResult).errCode as number;
if (errCode === ErrCode.ERR_OK) {
const authCode = JSON.parse(authResult).code as string;
this.viewModel.bindWx(authCode);
} else {
ToastUtils.show(JSON.parse(authResult).errStr);
}
}
}
@Monitor('viewModel.userEntity')
onUserinfoChange(monitor: IMonitor) {
this.userEntity = monitor.value()?.now as UserEntity;
}
@Monitor('viewModel.bindInfo')
onBindInfoChange(monitor: IMonitor) {
ToastUtils.show('绑定成功');
this.refreshData();
EventReportGlobalManager.eventReport(EventConstants.ACCOUNT_BIND)
}
@Monitor('viewModel.unbindInfo')
onUnbindInfoChange(monitor: IMonitor) {
ToastUtils.show('解绑成功');
this.refreshData();
}
async aboutToAppear() {
WXEventHandler.registerOnWXRespCallback(this.onWXResp)
this.viewModel.userinfo();
}
aboutToDisappear() {
WXEventHandler.unregisterOnWXRespCallback(this.onWXResp)
this.bindPhoneDialogController = null
}
showBindPhoneDialog() {
this.bindPhoneDialogController = new CustomDialogController({
builder: BindPhoneDialog({
success: () => {
this.refreshData();
}
}),
width: '100%',
cornerRadius: 20,
autoCancel: false,
maskColor: '#CC000000',
levelMode: LevelMode.EMBEDDED,
backgroundBlurStyle: BlurStyle.NONE,
alignment: DialogAlignment.Bottom
})
this.bindPhoneDialogController.open();
}
async wxAuth() {
if (!WXApi.isWXAppInstalled()) {
ToastUtils.show('请先下载安装微信客户端');
return;
}
LoadingDialog.show(this.getUIContext());
let req = new WxOpenSdk.SendAuthReq;
req.isOption1 = false;
req.nonAutomatic = true;
req.scope = 'snsapi_userinfo';
req.state = BuildProfile.BUNDLE_NAME + RandomUtil.getRandomInt(0, 1000) + '_bind';
req.transaction ='';
await WXApi.sendReq(AppUtil.getContext(), req)
LoadingDialog.dismiss();
}
refreshData() {
ConfigManager.userConfig()
.then(() => {
this.viewModel.userinfo();
AppUtil.getContext().eventHub.emit(EventConstants.HomeRefreshEvent);
AppUtil.getContext().eventHub.emit(EventConstants.MineRefreshEvent);
})
}
build() {
Column() {
TitleBar({ title: '账号绑定' }).width('100%')
Row() {
Image($r('app.media.ic_bind_wx')).width(30).height(30)
Text('微信绑定').fontColor(Color.White).fontSize(15).layoutWeight(1).margin({left: 14})
Text(StrUtil.isEmpty(this.userEntity?.unionid) ? '立即绑定' : '解除绑定')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(12)
.borderColor(Color.White)
.borderWidth(1)
.borderRadius(4)
.width(64)
.height(24)
.onClick(() => {
if (StrUtil.isEmpty(this.userEntity?.unionid)) {
this.wxAuth();
} else {
TipDialog.show(this.getUIContext(), {title: '解除微信绑定', content: '解绑后将无法使用该微信号登录此账号,请谨慎操作!', callback: {
confirm: () => {
if (StrUtil.isNotEmpty(this.userEntity?.phone)) {
this.viewModel.unbind('weixin');
} else {
TipDialog.show(this.getUIContext(), {title: '预警提示', content: '您若继续解除绑定,您的账号将没有绑定账号,软件在使用过程中可能会造成账号丢失!是否继续解除绑定!', callback: {
confirm: () => {
this.viewModel.unbind('weixin');
}
}})
}
}
}})
}
})
}.width('100%').height(60).padding({ left: 16, right: 16 })
Divider().color($r('app.color.color_10ffffff')).strokeWidth(1).margin({left: 16, right: 16})
Row() {
Image($r('app.media.ic_bind_phone')).width(30).height(30)
Text('手机绑定').fontColor(Color.White).fontSize(15).layoutWeight(1).margin({left: 14})
Text(StrUtil.isEmpty(this.userEntity?.phone) ? '立即绑定' : '解除绑定')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(12)
.borderColor(Color.White)
.borderWidth(1)
.borderRadius(4)
.width(64)
.height(24)
.onClick(() => {
if (StrUtil.isEmpty(this.userEntity?.phone)) {
this.showBindPhoneDialog();
} else {
TipDialog.show(this.getUIContext(), {title: '解除手机绑定', content: '解绑后将无法使用该手机号登录此账号,请谨慎操作!', callback: {
confirm: () => {
if (StrUtil.isNotEmpty(this.userEntity?.unionid)) {
this.viewModel.unbind('phone');
} else {
TipDialog.show(this.getUIContext(), {title: '预警提示', content: '您若继续解除绑定,您的账号将没有绑定账号,软件在使用过程中可能会造成账号丢失!是否继续解除绑定!', callback: {
confirm: () => {
this.viewModel.unbind('phone');
}
}})
}
}
}})
}
})
}.width('100%').height(60).padding({ left: 16, right: 16 })
}
.backgroundColor($r('app.color.window_background'))
.height('100%')
}
}

View File

@ -0,0 +1,130 @@
import { AppUtil, StrUtil } from '@pura/harmony-utils';
import { EventConstants } from '../../../../../common/EventConstants';
import { LoadingDialog } from '../../../../../dialog/LoadingDialog';
import { TipDialog } from '../../../../../dialog/TipDialog';
import { AccountEntity } from '../../../../../entity/AccountEntity';
import { LoginEntity } from '../../../../../entity/LoginEntity';
import { EventReportGlobalManager } from '../../../../../manager/EventReportGlobalManager';
import { LoginManager } from '../../../../../manager/LoginGlobalManager';
import { ConfigManager } from '../../../../../manager/UserConfigManager';
import { ToastUtils } from '../../../../../utils/ToastUtils';
import { AccountItemView } from '../../../../../view/AccountItemView';
import { TitleBar } from '../../../../../view/TitleBar';
import ManageAccountViewModel from '../../../../../viewModel/ManageAccountViewModel';
@Entry
@ComponentV2
struct ManageAccountPage {
viewModel: ManageAccountViewModel = new ManageAccountViewModel(this.getUIContext());
@Local accountList: AccountEntity[] = [];
@Local currentAccount?: AccountEntity;
@Monitor('viewModel.accounts')
onAccountsChange(monitor: IMonitor) {
const list = monitor.value()?.now as Array<AccountEntity>;
list.forEach((item) => {
if (item.user_id === LoginManager.getUserInfo()?.user_id) {
this.currentAccount = item;
} else {
this.accountList.push(item);
}
})
if (this.currentAccount === null && LoginManager.getUserInfo()) {
let userinfo = LoginManager.getUserInfo()!!
let account = new AccountEntity()
let bind: string[] = []
if (StrUtil.isNotEmpty(userinfo.unionid)) {
bind.push("weixin")
}
if (StrUtil.isNotEmpty(userinfo.phone)) {
bind.push('phone')
}
account.user_id = userinfo.user_id
account.name = userinfo.name
account.avater = userinfo.avater
account.phone = userinfo.phone
account.vip_name = userinfo.vip_name
account.vip_type = userinfo.vip
account.bind = bind
this.currentAccount = account
}
}
@Monitor('viewModel.loginEntity')
onLoginChange(monitor: IMonitor) {
const loginEntity = monitor.value()?.now as LoginEntity;
LoginManager.saveToken(loginEntity.token);
LoginManager.saveLastUserInfo(LoginManager.getUserInfo()!!);
ConfigManager.saveBindWxPlaybackHelper(false);
ConfigManager.saveBindWxVideoHelper(false);
ConfigManager.userConfig()
.then(() => {
ToastUtils.show('切换成功');
AppUtil.getContext().eventHub.emit(EventConstants.LoginSuccessEvent);
this.getUIContext().getRouter().back();
})
.finally(() => {
LoadingDialog.dismiss();
})
}
aboutToAppear(): void {
this.viewModel.accountList();
}
build() {
Column() {
TitleBar({ title: '名下账户管理' }).width('100%')
Scroll() {
Column() {
Text('当前登录的账户')
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.fontWeight(FontWeight.Medium)
.margin({ left: 20, top: 24, right: 20 })
.alignSelf(ItemAlign.Start)
AccountItemView({ account: this.currentAccount }).margin({ left: 20, top: 14, right: 20 })
Text('你名下其他账户')
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.fontWeight(FontWeight.Medium)
.margin({ left: 20, top: 30, right: 20 })
.alignSelf(ItemAlign.Start)
.visibility(this.accountList.length > 0 ? Visibility.Visible : Visibility.Hidden)
List() {
ForEach(this.accountList, (item: AccountEntity) => {
ListItem() {
AccountItemView({account: item})
}
.margin({ left: 20, top: 14, right: 20 })
.onClick(() => {
TipDialog.show(this.getUIContext(), {content: '您确定要切换账户吗?', callback: {
confirm: () => {
LoginManager.saveToken('');
this.viewModel.changeAccount(item.user_id);
EventReportGlobalManager.eventReport(EventConstants.SWITCH_ACCOUNT,item.user_id)
}
}})
})
})
}
.width('100%')
.height('auto')
.layoutWeight(1)
.margin({ bottom: 30 })
.visibility(this.accountList.length > 0 ? Visibility.Visible : Visibility.Hidden)
}
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

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