HarmonyOS开发:自定义转场与转场工厂
【摘要】 HarmonyOS开发:自定义转场与转场工厂 核心要点自定义转场允许开发者突破内置转场的限制,实现独特的视觉效果转场工厂模式提供统一的转场管理,确保应用内转场效果的一致性通过转场代理(TransitionProxy)可实现转场过程的精细控制自定义转场需要平衡创意性和性能,避免过度复杂的动画 一、背景与动机 1.1 为什么需要自定义转场HarmonyOS内置的转场效果(滑动、淡入淡出、缩放)...
HarmonyOS开发:自定义转场与转场工厂
核心要点
- 自定义转场允许开发者突破内置转场的限制,实现独特的视觉效果
- 转场工厂模式提供统一的转场管理,确保应用内转场效果的一致性
- 通过转场代理(TransitionProxy)可实现转场过程的精细控制
- 自定义转场需要平衡创意性和性能,避免过度复杂的动画
一、背景与动机
1.1 为什么需要自定义转场
HarmonyOS内置的转场效果(滑动、淡入淡出、缩放)虽然覆盖了大部分场景,但在以下情况仍显不足:
- 品牌定制:应用需要独特的视觉标识,如卡片翻转、3D旋转等
- 复杂交互:需要根据用户操作动态调整转场效果
- 业务场景:特定业务需要特殊的转场逻辑,如游戏关卡切换
- 创意设计:设计师提出了创新的转场方案,需要技术实现
1.2 自定义转场的实现路径
HarmonyOS提供多种自定义转场的实现方式:
graph TB
A[自定义转场实现路径] --> B[转场属性组合]
A --> C[转场代理控制]
A --> D[自定义动画曲线]
A --> E[转场工厂模式]
B --> B1[组合多个TransitionEffect]
B --> B2[链式调用配置参数]
C --> C1[TransitionProxy回调]
C --> C2[动态修改转场状态]
D --> D1[实现ICurve接口]
D --> D2[贝塞尔曲线定制]
E --> E1[统一转场配置管理]
E --> E2[转场效果复用]
classDef primary fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff
classDef secondary fill:#7B68EE,stroke:#5A4FCF,stroke-width:2px,color:#fff
classDef tertiary fill:#50C878,stroke:#3A9D5C,stroke-width:2px,color:#fff
class A primary
class B,C,D,E secondary
class B1,B2,C1,C2,D1,D2,E1,E2 tertiary
1.3 转场工厂的设计理念
转场工厂是一种设计模式,用于统一管理和创建转场效果:
核心优势:
- 一致性:全应用使用统一的转场规范
- 可维护性:修改一处,全局生效
- 可扩展性:新增转场类型无需修改业务代码
- 可测试性:转场逻辑独立,便于单元测试
二、核心原理
2.1 TransitionEffect体系
TransitionEffect是HarmonyOS转场效果的基础类,支持多种效果的组合:
// TransitionEffect类型
enum TransitionEffect {
IDENTITY, // 无效果
OPACITY, // 透明度
SCALE, // 缩放
TRANSLATE, // 平移
ROTATE, // 旋转
ASYMMETRIC // 非对称效果
}
转场效果的组合规则:
- 多个效果可以链式组合
- 组合顺序影响最终效果
- 每个效果可独立配置参数
2.2 转场代理机制
TransitionProxy提供了转场过程的回调控制能力:
sequenceDiagram
participant Page
participant Transition
participant Proxy
participant Animation
Page->>Transition: 触发转场
Transition->>Proxy: 创建代理实例
Proxy->>Proxy: onAppear回调
Proxy->>Animation: 启动自定义动画
Animation->>Proxy: 动画更新
Proxy->>Proxy: onUpdate回调
Animation->>Proxy: 动画完成
Proxy->>Proxy: onDisappear回调
Proxy->>Transition: 转场完成
classDef primary fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff
classDef secondary fill:#7B68EE,stroke:#5A4FCF,stroke-width:2px,color:#fff
class Page,Animation primary
class Transition,Proxy secondary
2.3 自定义动画曲线
动画曲线决定了转场的节奏感,HarmonyOS支持自定义曲线:
// 自定义曲线接口
interface ICurve {
// 输入归一化时间[0,1],返回插值进度[0,1]
interpolate(time: number): number;
}
常用曲线类型:
- 线性曲线:匀速变化,缺乏自然感
- 缓动曲线:模拟物理运动,自然流畅
- 弹性曲线:带弹跳效果,活泼有趣
- 自定义贝塞尔:精确控制动画节奏
三、代码实战
3.1 基础自定义转场
实现一个卡片翻转效果的自定义转场:
// CardFlipTransition.ets
import { Router } from '@ohos.router';
@Entry
@Component
struct CardFlipTransition {
@State isFlipped: boolean = false;
// 自定义卡片翻转转场
private flipTransition() {
// 入场:从背面翻转到正面
PageTransitionEnter({ duration: 600, curve: Curve.FastOutSlowIn })
.rotate({ x: 0, y: 180, z: 0 }) // 初始角度:背面
.opacity(0.5, 1);
// 出场:从正面翻转到背面
PageTransitionExit({ duration: 600, curve: Curve.FastOutSlowIn })
.rotate({ x: 0, y: -180, z: 0 }) // 旋转到背面
.opacity(1, 0.5);
}
pageTransition() {
this.flipTransition();
}
build() {
Column() {
Text('卡片翻转转场')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 60, bottom: 30 })
// 卡片容器
Stack() {
// 正面
Column() {
Text('正面')
.fontSize(28)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.width(200)
.height(280)
.backgroundColor('#4A90E2')
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.rotate({ y: this.isFlipped ? 180 : 0 })
.visibility(this.isFlipped ? Visibility.Hidden : Visibility.Visible)
// 背面
Column() {
Text('背面')
.fontSize(28)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.width(200)
.height(280)
.backgroundColor('#7B68EE')
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.rotate({ y: this.isFlipped ? 0 : -180 })
.visibility(this.isFlipped ? Visibility.Visible : Visibility.Hidden)
}
.width(200)
.height(280)
Button('翻转')
.width(150)
.height(45)
.backgroundColor('#50C878')
.margin({ top: 40 })
.onClick(() => {
animateTo({ duration: 600, curve: Curve.FastOutSlowIn }, () => {
this.isFlipped = !this.isFlipped;
});
})
Button('跳转页面')
.width(150)
.height(45)
.backgroundColor('#FF6B6B')
.margin({ top: 15 })
.onClick(() => {
Router.pushUrl({ url: 'pages/FlipTargetPage' });
})
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.justifyContent(FlexAlign.Start)
}
}
3.2 转场工厂模式实现
创建统一的转场工厂,管理应用内所有转场效果:
// TransitionFactory.ets
/**
* 转场类型枚举
*/
export enum TransitionType {
SLIDE_RIGHT = 'slide_right', // 右滑
SLIDE_LEFT = 'slide_left', // 左滑
SLIDE_UP = 'slide_up', // 上滑
SLIDE_DOWN = 'slide_down', // 下滑
FADE = 'fade', // 淡入淡出
SCALE = 'scale', // 缩放
FLIP = 'flip', // 翻转
CARD_EXPAND = 'card_expand', // 卡片展开
DISSOLVE = 'dissolve' // 溶解
}
/**
* 转场配置接口
*/
export interface TransitionConfig {
duration: number; // 持续时间
curve: Curve | ICurve; // 动画曲线
delay?: number; // 延迟时间
}
/**
* 转场工厂类
*/
export class TransitionFactory {
// 默认配置
private static readonly DEFAULT_CONFIG: TransitionConfig = {
duration: 300,
curve: Curve.FastOutSlowIn
};
// 转场配置映射
private static transitionConfigs: Map<TransitionType, TransitionConfig> = new Map([
[TransitionType.SLIDE_RIGHT, { duration: 350, curve: Curve.FastOutSlowIn }],
[TransitionType.SLIDE_LEFT, { duration: 350, curve: Curve.FastOutSlowIn }],
[TransitionType.FADE, { duration: 250, curve: Curve.EaseInOut }],
[TransitionType.SCALE, { duration: 400, curve: Curve.SpringMotion }],
[TransitionType.FLIP, { duration: 600, curve: Curve.FastOutSlowIn }]
]);
/**
* 创建入场转场
*/
static createEnterTransition(type: TransitionType, customConfig?: Partial<TransitionConfig>): PageTransitionEnter {
const config = this.getConfig(type, customConfig);
switch (type) {
case TransitionType.SLIDE_RIGHT:
return PageTransitionEnter(config)
.slide(SlideEffect.Right);
case TransitionType.SLIDE_LEFT:
return PageTransitionEnter(config)
.slide(SlideEffect.Left);
case TransitionType.SLIDE_UP:
return PageTransitionEnter(config)
.translate({ y: 1000 });
case TransitionType.SLIDE_DOWN:
return PageTransitionEnter(config)
.translate({ y: -1000 });
case TransitionType.FADE:
return PageTransitionEnter(config)
.opacity(0, 1);
case TransitionType.SCALE:
return PageTransitionEnter(config)
.scale({ x: 0.8, y: 0.8 })
.opacity(0, 1);
case TransitionType.FLIP:
return PageTransitionEnter(config)
.rotate({ y: 90 })
.opacity(0.5, 1);
case TransitionType.CARD_EXPAND:
return PageTransitionEnter(config)
.scale({ x: 0.5, y: 0.5 })
.opacity(0.8, 1);
case TransitionType.DISSOLVE:
return PageTransitionEnter(config)
.opacity(0, 1)
.blur(10, 0);
default:
return PageTransitionEnter(config);
}
}
/**
* 创建出场转场
*/
static createExitTransition(type: TransitionType, customConfig?: Partial<TransitionConfig>): PageTransitionExit {
const config = this.getConfig(type, customConfig);
switch (type) {
case TransitionType.SLIDE_RIGHT:
return PageTransitionExit(config)
.slide(SlideEffect.Left);
case TransitionType.SLIDE_LEFT:
return PageTransitionExit(config)
.slide(SlideEffect.Right);
case TransitionType.SLIDE_UP:
return PageTransitionExit(config)
.translate({ y: -1000 });
case TransitionType.SLIDE_DOWN:
return PageTransitionExit(config)
.translate({ y: 1000 });
case TransitionType.FADE:
return PageTransitionExit(config)
.opacity(1, 0);
case TransitionType.SCALE:
return PageTransitionExit(config)
.scale({ x: 1.2, y: 1.2 })
.opacity(1, 0);
case TransitionType.FLIP:
return PageTransitionExit(config)
.rotate({ y: -90 })
.opacity(1, 0.5);
case TransitionType.CARD_EXPAND:
return PageTransitionExit(config)
.scale({ x: 1.5, y: 1.5 })
.opacity(1, 0.8);
case TransitionType.DISSOLVE:
return PageTransitionExit(config)
.opacity(1, 0)
.blur(0, 10);
default:
return PageTransitionExit(config);
}
}
/**
* 获取配置
*/
private static getConfig(type: TransitionType, customConfig?: Partial<TransitionConfig>): TransitionConfig {
const baseConfig = this.transitionConfigs.get(type) || this.DEFAULT_CONFIG;
return { ...baseConfig, ...customConfig };
}
/**
* 注册自定义转场类型
*/
static registerTransition(type: TransitionType, config: TransitionConfig): void {
this.transitionConfigs.set(type, config);
}
}
3.3 使用转场工厂
在页面中应用转场工厂创建的转场效果:
// TransitionFactoryDemo.ets
import { Router } from '@ohos.router';
import { TransitionFactory, TransitionType } from './TransitionFactory';
@Entry
@Component
struct TransitionFactoryDemo {
@State currentType: TransitionType = TransitionType.SLIDE_RIGHT;
@State transitionTypes: TransitionType[] = [
TransitionType.SLIDE_RIGHT,
TransitionType.FADE,
TransitionType.SCALE,
TransitionType.FLIP,
TransitionType.CARD_EXPAND
];
// 使用转场工厂配置页面转场
pageTransition() {
TransitionFactory.createEnterTransition(this.currentType);
TransitionFactory.createExitTransition(this.currentType);
}
build() {
Column() {
Text('转场工厂演示')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 30 })
Text('选择转场类型')
.fontSize(16)
.fontColor('#666666')
.margin({ bottom: 20 })
// 转场类型选择器
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
ForEach(this.transitionTypes, (type: TransitionType) => {
Button(type)
.height(36)
.fontSize(12)
.backgroundColor(this.currentType === type ? '#4A90E2' : '#E0E0E0')
.fontColor(this.currentType === type ? '#FFFFFF' : '#333333')
.margin({ left: 8, right: 8, bottom: 12 })
.onClick(() => {
this.currentType = type;
})
}, (type: TransitionType) => type)
}
.width('90%')
.margin({ bottom: 30 })
// 预览区域
Column() {
Text(`当前转场:${this.currentType}`)
.fontSize(18)
.fontColor('#4A90E2')
.fontWeight(FontWeight.Medium)
Text('点击下方按钮体验转场效果')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 10 })
}
.width('85%')
.height(200)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.shadow({ radius: 10, color: '#1A000000', offsetX: 0, offsetY: 5 })
// 操作按钮
Row() {
Button('前进')
.width(120)
.height(45)
.backgroundColor('#50C878')
.onClick(() => {
Router.pushUrl({
url: 'pages/TransitionTargetPage',
params: { transitionType: this.currentType }
});
})
Button('自定义配置')
.width(120)
.height(45)
.backgroundColor('#7B68EE')
.margin({ left: 20 })
.onClick(() => {
// 使用自定义配置
Router.pushUrl({
url: 'pages/TransitionTargetPage',
params: {
transitionType: this.currentType,
customConfig: { duration: 500, curve: Curve.SpringMotion }
}
});
})
}
.margin({ top: 40 })
}
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA')
}
}
3.4 自定义动画曲线
实现独特的动画曲线效果:
// CustomCurveDemo.ets
/**
* 弹性曲线实现
*/
class ElasticCurve implements ICurve {
private amplitude: number;
private period: number;
constructor(amplitude: number = 1, period: number = 0.3) {
this.amplitude = amplitude;
this.period = period;
}
interpolate(time: number): number {
if (time === 0 || time === 1) {
return time;
}
// 弹性公式
return this.amplitude * Math.pow(2, -10 * time) * Math.sin((time - this.period / 4) * (2 * Math.PI) / this.period) + 1;
}
}
/**
* 弹跳曲线实现
*/
class BounceCurve implements ICurve {
interpolate(time: number): number {
if (time < 1 / 2.75) {
return 7.5625 * time * time;
} else if (time < 2 / 2.75) {
const t = time - 1.5 / 2.75;
return 7.5625 * t * t + 0.75;
} else if (time < 2.5 / 2.75) {
const t = time - 2.25 / 2.75;
return 7.5625 * t * t + 0.9375;
} else {
const t = time - 2.625 / 2.75;
return 7.5625 * t * t + 0.984375;
}
}
}
/**
* 自定义贝塞尔曲线
*/
class BezierCurve implements ICurve {
private x1: number;
private y1: number;
private x2: number;
private y2: number;
constructor(x1: number, y1: number, x2: number, y2: number) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
interpolate(time: number): number {
// 简化的贝塞尔计算
const t = time;
const t2 = t * t;
const t3 = t2 * t;
const mt = 1 - t;
const mt2 = mt * mt;
const mt3 = mt2 * mt;
// 三次贝塞尔
return 3 * mt2 * t * this.y1 + 3 * mt * t2 * this.y2 + t3;
}
}
@Entry
@Component
struct CustomCurveDemo {
@State selectedCurve: string = 'elastic';
private elasticCurve: ElasticCurve = new ElasticCurve(1, 0.3);
private bounceCurve: BounceCurve = new BounceCurve();
private customBezier: BezierCurve = new BezierCurve(0.68, 0, 0.32, 1);
pageTransition() {
let curve: ICurve;
switch (this.selectedCurve) {
case 'elastic':
curve = this.elasticCurve;
break;
case 'bounce':
curve = this.bounceCurve;
break;
case 'bezier':
curve = this.customBezier;
break;
default:
curve = Curve.EaseInOut;
}
PageTransitionEnter({ duration: 800, curve: curve })
.slide(SlideEffect.Right)
.opacity(0.8, 1);
PageTransitionExit({ duration: 800, curve: curve })
.slide(SlideEffect.Left)
.opacity(1, 0.8);
}
build() {
Column() {
Text('自定义曲线演示')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 25 })
// 曲线选择
Row() {
ForEach(['elastic', 'bounce', 'bezier'], (curveName: string) => {
Button(curveName.toUpperCase())
.width(100)
.height(40)
.backgroundColor(this.selectedCurve === curveName ? '#4A90E2' : '#E0E0E0')
.fontColor(this.selectedCurve === curveName ? '#FFFFFF' : '#333333')
.onClick(() => {
this.selectedCurve = curveName;
})
}, (curveName: string) => curveName)
}
.space(15)
.margin({ bottom: 30 })
// 曲线可视化
Column() {
Text(`当前曲线:${this.selectedCurve}`)
.fontSize(18)
.fontColor('#7B68EE')
Text('不同的曲线产生不同的动画节奏')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 10 })
// 动画预览
this.CurvePreview()
}
.width('85%')
.height(300)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.padding(20)
.shadow({ radius: 8, color: '#1A000000', offsetX: 0, offsetY: 4 })
Button('体验转场')
.width('60%')
.height(50)
.backgroundColor('#50C878')
.margin({ top: 30 })
.onClick(() => {
// 触发页面转场
})
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
@Builder
CurvePreview() {
Column() {
Text('曲线效果预览')
.fontSize(14)
.fontColor('#666666')
.margin({ bottom: 15 })
// 简化的曲线可视化
Row() {
Text('时间')
.fontSize(12)
.fontColor('#999999')
Row()
.width(200)
.height(2)
.backgroundColor('#E0E0E0')
Text('进度')
.fontSize(12)
.fontColor('#999999')
}
.margin({ top: 20 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
3.5 转场代理高级应用
使用TransitionProxy实现复杂的转场控制:
// TransitionProxyDemo.ets
import { Router } from '@ohos.router';
@Entry
@Component
struct TransitionProxyDemo {
@State transitionProgress: number = 0;
@State isInteractive: boolean = false;
pageTransition() {
// 使用转场代理控制转场过程
PageTransitionEnter({ duration: 500 })
.onAppear((proxy: TransitionProxy) => {
console.log('转场开始');
this.isInteractive = true;
})
.onUpdate((proxy: TransitionProxy) => {
// 获取转场进度
this.transitionProgress = proxy.getProgress();
console.log(`转场进度: ${this.transitionProgress}`);
})
.onDisappear((proxy: TransitionProxy) => {
console.log('转场结束');
this.isInteractive = false;
this.transitionProgress = 1;
});
PageTransitionExit({ duration: 500 })
.onAppear((proxy: TransitionProxy) => {
console.log('出场转场开始');
});
}
build() {
Column() {
Text('转场代理演示')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 60, bottom: 30 })
// 进度显示
Column() {
Text('转场进度')
.fontSize(16)
.fontColor('#666666')
.margin({ bottom: 15 })
// 进度条
Stack() {
Row()
.width('100%')
.height(8)
.backgroundColor('#E0E0E0')
.borderRadius(4)
Row()
.width(`${this.transitionProgress * 100}%`)
.height(8)
.backgroundColor('#4A90E2')
.borderRadius(4)
}
.width('80%')
Text(`${(this.transitionProgress * 100).toFixed(0)}%`)
.fontSize(20)
.fontColor('#4A90E2')
.fontWeight(FontWeight.Bold)
.margin({ top: 15 })
}
.width('85%')
.height(180)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.shadow({ radius: 8, color: '#1A000000', offsetX: 0, offsetY: 4 })
// 状态指示
Row() {
Circle()
.width(12)
.height(12)
.fill(this.isInteractive ? '#50C878' : '#E0E0E0')
Text(this.isInteractive ? '转场中...' : '就绪')
.fontSize(14)
.fontColor('#666666')
.margin({ left: 8 })
}
.margin({ top: 30 })
// 操作按钮
Row() {
Button('跳转页面')
.width(140)
.height(45)
.backgroundColor('#7B68EE')
.onClick(() => {
Router.pushUrl({ url: 'pages/ProxyTargetPage' });
})
Button('返回')
.width(140)
.height(45)
.backgroundColor('#FF6B6B')
.margin({ left: 15 })
.onClick(() => {
Router.back();
})
}
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA')
}
}
四、踩坑与注意事项
4.1 自定义转场性能问题
问题:复杂自定义转场导致卡顿
// ❌ 错误:转场中执行复杂计算
PageTransitionEnter({ duration: 500 })
.onUpdate((proxy: TransitionProxy) => {
// 避免在onUpdate中执行耗时操作
this.complexCalculation(); // 性能杀手
});
// ✅ 正确:提前计算或使用简单插值
PageTransitionEnter({ duration: 500 })
.onUpdate((proxy: TransitionProxy) => {
// 只做简单的状态更新
this.progress = proxy.getProgress();
});
4.2 转场工厂扩展问题
问题:新增转场类型需要修改工厂类
// ✅ 解决方案:使用策略模式扩展
interface TransitionStrategy {
createEnter(config: TransitionConfig): PageTransitionEnter;
createExit(config: TransitionConfig): PageTransitionExit;
}
// 注册自定义策略
TransitionFactory.registerStrategy('custom_3d', new ThreeDTransitionStrategy());
4.3 曲线实现注意事项
// 曲线实现必须满足边界条件
class ValidCurve implements ICurve {
interpolate(time: number): number {
// 边界检查
if (time <= 0) return 0;
if (time >= 1) return 1;
// 确保输出在[0,1]范围内
const result = this.calculateInternal(time);
return Math.max(0, Math.min(1, result));
}
private calculateInternal(time: number): number {
// 实际计算逻辑
return time;
}
}
4.4 最佳实践总结
| 实践项 | 说明 |
|---|---|
| 转场工厂统一管理 | 所有转场效果通过工厂创建 |
| 曲线可复用 | 自定义曲线封装为独立类 |
| 性能优先 | 避免转场中的复杂计算 |
| 渐进增强 | 基础转场保证可用,高级转场锦上添花 |
五、总结
自定义转场与转场工厂为HarmonyOS应用提供了无限的创意空间。通过本文的深入讲解,我们掌握了:
- 自定义转场原理:理解TransitionEffect和TransitionProxy的工作机制
- 转场工厂模式:学会统一管理和创建转场效果
- 自定义动画曲线:掌握独特动画节奏的实现方法
- 转场代理应用:实现转场过程的精细控制
- 性能优化策略:平衡创意性和性能需求
在实际开发中,自定义转场应该服务于用户体验,而不是炫技。通过转场工厂模式,我们可以既保证创意性,又确保一致性和可维护性,打造专业级的HarmonyOS应用。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)