更新云信和公益咨询相关代码
This commit is contained in:
@@ -52,9 +52,9 @@ export class NimRepository {
|
||||
console.debug(`Performance Test im start loginSuccess`)
|
||||
await this.nim.loginService.login(accountId, token);
|
||||
console.error('----------- 登录成功 -----------')
|
||||
router.pushUrl({
|
||||
url: 'pages/Netease/imTabPage'
|
||||
});
|
||||
// router.pushUrl({
|
||||
// url: 'pages/Netease/imTabPage'
|
||||
// });
|
||||
console.debug(`Performance Test im loginSuccess`)
|
||||
|
||||
ChatKitClient.init(this.nim, appKey)
|
||||
@@ -62,6 +62,7 @@ export class NimRepository {
|
||||
|
||||
} catch (error) {
|
||||
console.error('----------- 登录失败 -----------', error)
|
||||
ChatKitClient.init(this.nim, appKey)
|
||||
throw error as Error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
import webview from '@ohos.web.webview';
|
||||
import { HdNav } from '@itcast/basic';
|
||||
import router from '@ohos.router';
|
||||
import { image } from '@kit.ImageKit';
|
||||
import { photoAccessHelper } from '@kit.MediaLibraryKit';
|
||||
import { promptAction } from '@kit.ArkUI';
|
||||
import { componentSnapshot } from '@kit.ArkUI';
|
||||
import { fileIo, fileUri } from '@kit.CoreFileKit';
|
||||
import { display} from '@kit.ArkUI';
|
||||
|
||||
|
||||
const TAG = 'WebViewSaveImage';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct WebPageSnapshot {
|
||||
private controller: webview.WebviewController = new webview.WebviewController();
|
||||
@State params:RouteParams = router.getParams() as RouteParams;
|
||||
|
||||
@State contentWidth: number = 0;
|
||||
@State contentHeight: number = 0;
|
||||
@State url: string = this.params.url;
|
||||
@State title: string = this.params.title;
|
||||
customUserAgent: string = 'gdxz-expert';
|
||||
// 网页尺寸
|
||||
@State h5Width: number = 0;
|
||||
@State h5Height: number = 0;
|
||||
// Web组件尺寸
|
||||
private webWidth: number = 0;
|
||||
private webHeight: number = 0;
|
||||
// 当前网页位置
|
||||
private curXOffset: number = 0;
|
||||
private curYOffset: number = 0;
|
||||
// 备份当前网页位置
|
||||
private xOffsetBefore: number = 0;
|
||||
private yOffsetBefore: number = 0;
|
||||
// 截图过程的Web组件覆盖
|
||||
@State webMaskImage: PixelMap | undefined = undefined;
|
||||
private webMaskImageZIndex: number = -1;
|
||||
// 合并后的图片
|
||||
@State mergedImage: PixelMap | undefined = undefined;
|
||||
|
||||
@State snapPopupPosition: Position = { x: 0, y: 0 };
|
||||
//Web是否已经滚动到底部
|
||||
@State WebTouchBottom: boolean = false;
|
||||
// 屏幕尺寸
|
||||
private displayWidth: number = 0;
|
||||
private displayHeight: number = 0;
|
||||
onBackPress(): boolean | void {
|
||||
if (this.controller.accessStep(-1)) {
|
||||
this.controller.backward();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
HdNav({ title: this.title, showRightIcon: false, hasBorder: true ,isLeftAction:true,leftItemAction:()=>{
|
||||
if (this.controller.accessBackward()) {
|
||||
this.controller.backward();
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}})
|
||||
|
||||
Web({
|
||||
src: this.url,
|
||||
controller: this.controller
|
||||
})
|
||||
.id('webViewShot')
|
||||
.mixedMode(MixedMode.All)
|
||||
.overScrollMode(OverScrollMode.ALWAYS)
|
||||
.domStorageAccess(true)
|
||||
.onAreaChange((oldValue, newValue) => {
|
||||
// TODO: 高性能知识点: onAreaChange为高频回调,组件变动时每帧都会调用,避免冗余和耗时操作。
|
||||
this.webWidth = newValue.width as number;
|
||||
this.webHeight = newValue.height as number;
|
||||
|
||||
})
|
||||
.onControllerAttached(() => {
|
||||
let userAgent = this.controller.getUserAgent() + this.customUserAgent;
|
||||
this.controller.setCustomUserAgent(userAgent);
|
||||
})
|
||||
.onOverScroll((event) => {
|
||||
if (event?.yOffset > 0) {
|
||||
this.WebTouchBottom = true
|
||||
} else if (event?.yOffset === 0 && this.WebTouchBottom) {
|
||||
this.WebTouchBottom = false
|
||||
}
|
||||
})
|
||||
.onScroll((event) => {
|
||||
this.curXOffset = event.xOffset;
|
||||
this.curYOffset = event.yOffset;
|
||||
|
||||
})
|
||||
.width('100%')
|
||||
.layoutWeight(1)// 占据剩余空间
|
||||
.height('100%')
|
||||
|
||||
if (this.title == '随访二维码') {
|
||||
Row(){
|
||||
SaveButton({text:SaveDescription.SAVE_IMAGE,buttonType:ButtonType.Normal})
|
||||
.fontSize(16)
|
||||
.fontColor(Color.White)
|
||||
.backgroundColor('rgb(63,199,193)')
|
||||
.width('100%').height(50)
|
||||
.onClick( () => {
|
||||
|
||||
this.snapShot();
|
||||
|
||||
})
|
||||
}.width('100%').height(56).backgroundColor(Color.White).alignItems(VerticalAlign.Top)
|
||||
}
|
||||
}
|
||||
.height('100%')// 关键:约束父容器高度
|
||||
}
|
||||
aboutToAppear(): void {
|
||||
let a=this.url
|
||||
// 获取屏幕尺寸
|
||||
const displayData = display.getDefaultDisplaySync();
|
||||
this.displayWidth = px2vp(displayData.width);
|
||||
this.displayHeight = px2vp(displayData.height);
|
||||
|
||||
}
|
||||
async saveWebViewImage() {
|
||||
try {
|
||||
// 1. 获取整个网页的长截图
|
||||
this.controller.webPageSnapshot({
|
||||
id: "webView",
|
||||
size: { width: this.contentWidth, height:this.contentHeight } // 确保捕获全尺寸
|
||||
}, async (error, result) => {
|
||||
if (error) {
|
||||
promptAction.showToast({ message: `截图失败: ${error.message}` });
|
||||
console.error('webPageSnapshot error:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result?.imagePixelMap) {
|
||||
promptAction.showToast({ message: '获取截图数据失败' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 转为JPEG并写入沙箱
|
||||
const ctx = getContext(this);
|
||||
const imagePacker = image.createImagePacker();
|
||||
const arrayBuffer = await imagePacker.packToData(
|
||||
result.imagePixelMap,
|
||||
{ format: 'image/jpeg', quality: 100 } // 平衡质量与大小
|
||||
);
|
||||
|
||||
const sandboxPath = ctx.cacheDir + `/web_fullpage_${Date.now()}.jpg`;
|
||||
const file = fileIo.openSync(sandboxPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
|
||||
fileIo.writeSync(file.fd, arrayBuffer);
|
||||
fileIo.closeSync(file.fd);
|
||||
|
||||
// 3. 保存到相册
|
||||
const helper = photoAccessHelper.getPhotoAccessHelper(ctx);
|
||||
const sandboxUri = fileUri.getUriFromPath(sandboxPath);
|
||||
const request = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, sandboxUri);
|
||||
await helper.applyChanges(request);
|
||||
|
||||
promptAction.showToast({ message: '网页已保存至相册' });
|
||||
});
|
||||
} catch (e) {
|
||||
promptAction.showToast({ message: '保存失败: ' + e.message });
|
||||
console.error('saveWebViewImage error:', e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 截图函数。
|
||||
*/
|
||||
async snapShot() {
|
||||
// 获取Web页面尺寸
|
||||
this.getWebSize();
|
||||
// 截图前的状态初始化
|
||||
await this.beforeSnapshot();
|
||||
// TODO: 性能知识点: 使用Canvas离屏绘制在缓冲区拼接截图
|
||||
const canvasSetting: RenderingContextSettings = new RenderingContextSettings(true);
|
||||
const offCanvasCtx: OffscreenCanvasRenderingContext2D =
|
||||
new OffscreenCanvasRenderingContext2D(this.h5Width, this.h5Height, canvasSetting);
|
||||
// 前置常量
|
||||
const snipTimes = Math.ceil(this.h5Height / this.webHeight);
|
||||
|
||||
const lastTime = snipTimes - 1;
|
||||
const leftoverHeight = this.h5Height % this.webHeight;
|
||||
let cropLeftover: image.Region = { x: 0, y: 0, size: { height: 0, width: 0 } }
|
||||
if (this.WebTouchBottom) {
|
||||
// 这里要分两种情况,1.滚动到底部时,裁剪应该取最后一张除去重复部分以外的底部
|
||||
cropLeftover = {
|
||||
x: 0,
|
||||
y: vp2px(this.webHeight - leftoverHeight),
|
||||
size: {
|
||||
height: vp2px(leftoverHeight),
|
||||
width: vp2px(this.webWidth)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// 2.未滚动到底部时,裁剪应该取最后一张leftoverHeight的上部分
|
||||
cropLeftover = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
size: {
|
||||
height: vp2px(leftoverHeight),
|
||||
width: vp2px(this.webWidth)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 开始截图
|
||||
for (let i = 0; i < snipTimes; i++) {
|
||||
const curSnip = await componentSnapshot.get('webViewShot');
|
||||
// 最后一次截图需要特殊处理,去除重复部分
|
||||
if (i === lastTime) {
|
||||
|
||||
await curSnip.crop(cropLeftover);
|
||||
offCanvasCtx.drawImage(curSnip, 0, this.webHeight * i, this.webWidth, leftoverHeight);
|
||||
} else {
|
||||
offCanvasCtx.drawImage(curSnip, 0, this.webHeight * i, this.webWidth, this.webHeight);
|
||||
}
|
||||
|
||||
// 继续滚动
|
||||
this.controller.scrollBy(0, this.webHeight);
|
||||
// 延时保证滚动完成
|
||||
await sleep(200);
|
||||
}
|
||||
// 截图后的操作
|
||||
await this.afterSnapshot();
|
||||
// 获取pixelMap
|
||||
this.mergedImage = offCanvasCtx.getPixelMap(0, 0, this.h5Width, this.h5Height);
|
||||
try {
|
||||
// 2. 转为JPEG并写入沙箱
|
||||
const ctx = getContext(this);
|
||||
const imagePacker = image.createImagePacker();
|
||||
const arrayBuffer = await imagePacker.packToData(
|
||||
this.mergedImage,
|
||||
{ format: 'image/jpeg', quality: 100 } // 平衡质量与大小
|
||||
);
|
||||
|
||||
const sandboxPath = ctx.cacheDir + `/web_fullpage_${Date.now()}.jpg`;
|
||||
const file = fileIo.openSync(sandboxPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
|
||||
fileIo.writeSync(file.fd, arrayBuffer);
|
||||
fileIo.closeSync(file.fd);
|
||||
|
||||
// 3. 保存到相册
|
||||
const helper = photoAccessHelper.getPhotoAccessHelper(ctx);
|
||||
const sandboxUri = fileUri.getUriFromPath(sandboxPath);
|
||||
const request = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, sandboxUri);
|
||||
await helper.applyChanges(request);
|
||||
|
||||
promptAction.showToast({ message: '网页已保存至相册' });
|
||||
// 拼接之后修改可动画变量
|
||||
// this.afterGeneratorImage();
|
||||
} catch (e) {
|
||||
promptAction.showToast({ message: '保存失败: ' + e.message });
|
||||
console.error('saveWebViewImage error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截图前获取尺寸
|
||||
*/
|
||||
getWebSize() {
|
||||
const SCRIPT = '[document.documentElement.scrollWidth, document.documentElement.scrollHeight]';
|
||||
this.controller.runJavaScriptExt(SCRIPT).then((result) => {
|
||||
try {
|
||||
switch (result.getType()) {
|
||||
case webview.JsMessageType.ARRAY:
|
||||
this.h5Width = (result.getArray() as number[])[0]; // 单位是vp
|
||||
this.h5Height = (result.getArray() as number[])[1];
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 截图开始前的操作。
|
||||
* - 保存网页当前位置,用于恢复状态
|
||||
* - 截图当前页面作为遮罩层,避免用户察觉组件的滚动,提高用户体验
|
||||
* - Web页面滚动到顶部,准备开始截图
|
||||
* - 设置截图后小弹窗的位置,提示用户暂时不要操作,等待截图
|
||||
* - 开启提示小弹窗
|
||||
*/
|
||||
async beforeSnapshot() {
|
||||
// 保存网页当前位置,用于恢复
|
||||
this.xOffsetBefore = this.curXOffset;
|
||||
this.yOffsetBefore = this.curYOffset;
|
||||
this.h5Height = this.curYOffset + Math.ceil(this.webHeight);
|
||||
// TODO: 知识点: 使用componentSnapshot.get接口直接获取组件的渲染结果,而不需要将屏幕截图
|
||||
this.webMaskImage = await componentSnapshot.get('webViewShot');
|
||||
this.webMaskImageZIndex =2;
|
||||
this.controller.scrollTo(0, 0);
|
||||
promptAction.showToast({
|
||||
message: '正在截图,请勿操作...',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 延时确保已经滚动到了顶部
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截图之后的操作。
|
||||
* - 恢复web页面到截图之前的位置
|
||||
* - 取消遮罩层
|
||||
*/
|
||||
async afterSnapshot() {
|
||||
this.controller.scrollTo(this.xOffsetBefore, this.yOffsetBefore);
|
||||
await sleep(200);
|
||||
this.webMaskImageZIndex = -1;
|
||||
this.webMaskImage = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成拼接后图片的操作。用于窗口形成移动的动画。
|
||||
*/
|
||||
async afterGeneratorImage() {
|
||||
// 小窗在屏幕中间短暂停留,避免位置突变,无法形成动画
|
||||
await sleep(200);
|
||||
// 修改弹窗位置,形成移动动画
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface RouteParams {
|
||||
url:string;
|
||||
title:string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步延时函数。
|
||||
* @param ms 延时时长,单位 ms。
|
||||
* @returns Promise 对象,执行回调。
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
"pages/Netease/InterrogationDetailCompPage",
|
||||
"pages/Netease/PatientSimplyPage",
|
||||
"pages/Netease/MyOpinionPage",
|
||||
"pages/PatientsPage/GroupManagementPage"
|
||||
"pages/PatientsPage/GroupManagementPage",
|
||||
"pages/WebView/WebPageSnapshot"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user