【HarmonyOS开发】案例-记账本开发

图片

OpenHarmony最近一段时间,简直火的一塌糊度,学习OpenHarmony相关的技术栈也有一段时间了,做个记账本小应用,将所学知识点融合记录一下。

1、记账本涉及知识点

  •     基础组件(Button、Select、Text、Span、Divider、Image)、容器(Row、Flex、List、Grid、Column)、定位(position)、路由(router)、Toast(promptAction)、Web组件;
  •     自定义弹窗(@CustomDialog);
  •     分布式键值数据库(@ohos.data.distributedKVStore);
  •     Web组件;
  •     OpenHarmony三方库;

2、效果预览

图片

  

图片

图片

  

图片

3、功能点实现简介

3.1 自定义弹窗


// 自定义弹窗定义
@CustomDialog
struct CustomDialogSetting {// 双向绑定传值@Link settingBudgetValue: string// 弹窗控制器,控制打开/关闭,必须传入,且名称必须为:controllercontroller: CustomDialogController// 弹窗中的按钮事件cancel: () => voidconfirm: () => void// 弹窗中的内容描述build() {Column() {Text($r('app.string.budget_setting')).fontSize(18).fontWeight(FontWeight.Bold).margin(12).textAlign(TextAlign.Center).width("100%")TextInput({placeholder: $r('app.string.estimated_amount_tips'),text: this.settingBudgetValue}).type(InputType.Number).height(60).width('90%').onChange((value: string) => {this.settingBudgetValue = value})Flex({ justifyContent: FlexAlign.SpaceAround }) {Button($r('app.string.cancel')).onClick(() => {this.settingBudgetValue = ''this.controller.close()this.cancel()}).backgroundColor(0xffffff).fontColor(Color.Black)Button($r('app.string.confirm')).onClick(() => {this.controller.close()this.confirm()}).backgroundColor(0xffffff).fontColor(AccountBookConstant.FONT_COLOR_BLUE)}.margin(15)}}
}
// 使用自定义弹窗
dialogController: CustomDialogController = new CustomDialogController({builder: CustomDialogSetting({cancel: this.onCancel.bind(this),confirm: this.onAccept.bind(this),settingBudgetValue: $settingBudgetValue}),cancel: this.onCancel,autoCancel: true,alignment: DialogAlignment.Center,gridCount: 4,customStyle: false
})// 开启弹窗
this.dialogController.open()

3.2 悬浮按钮

// 设置按钮, 通过position进行绝对定位
Button({  stateEffect: true }){Image($rawfile('setting.svg')).width(22).height(22)
}.width(42).height(42)
.borderRadius(90)
.shadow({ radius: 10, color: Color.Gray, offsetX: 5, offsetY:5 })
.position({ x: '98%', y: '98%' })
.markAnchor({ x: '98%', y: '98%'})
.margin(10).backgroundColor('#67C23A')
.onClick(() => {if (this.dialogController != undefined) {this.dialogController.open()}
})

3.3  数据存储

// 定义键值对存储类import distributedKVStore from '@ohos.data.distributedKVStore';const BUNDLE_NAME = "baseInfo"let context = getContext(this)
// 数据库对象
let kvManager: distributedKVStore.KVManager | undefined = undefined;
// KVStore数据库
let kvStore: distributedKVStore.SingleKVStore | undefined = undefined;class DistributedUtil {constructor() {this.createKeyValueDB();}async getKvManager(bundleName?: string) {const kvStoreConfig: distributedKVStore.KVManagerConfig = {context: context,bundleName: bundleName || BUNDLE_NAME};try {kvManager = distributedKVStore.createKVManager(kvStoreConfig);}catch (err) {console.error(`error:${err}`)}}// 创建并得到指定类型的KVStore数据库async createKeyValueDB(op?: distributedKVStore.Options) {if (!kvManager) {await this.getKvManager();}try {const options: distributedKVStore.Options = {// 当数据库文件不存在时是否创建数据库,默认为truecreateIfMissing: true,// 设置数据库文件是否加密,默认为false,即不加密encrypt: false,// 设置数据库文件是否备份,默认为true,即备份backup: false,// 设置数据库文件是否自动同步。默认为false,即手动同步autoSync: true,// kvStoreType不填时,默认创建多设备协同数据库kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,// 多设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,securityLevel: distributedKVStore.SecurityLevel.S1};kvManager.getKVStore<distributedKVStore.SingleKVStore>('storeId', op || options, (err, store: distributedKVStore.SingleKVStore) => {if (err) {console.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);return;}console.info('Succeeded in getting KVStore.');kvStore = store;});} catch (e) {console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);}return kvStore;}// 删除指定键值的数据async deleteStoreData(key: string) {if (!kvStore) {return;}try {kvStore.delete(key, (err) => {if (err !== undefined) {console.error(`Failed to delete data. Code:${err.code},message:${err.message}`);return;}console.info('Succeeded in deleting data.');});} catch (e) {console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);}}// 向键值数据库中插入数据async putStoreData(key: string, value: any) {if (!key || !value) {return}if(!kvStore) {kvStore = await this.createKeyValueDB();}try {kvStore.put(key, value, (err) => {if (err !== undefined) {console.error(`Failed to put data. Code:${err.code},message:${err.message}`);return;}console.info('Succeeded in putting data.');});} catch (e) {console.error(`putStoreData===>An unexpected error occurred. Code:${e.code},message:${e.message}`);}}// 获取指定键的值async getStoreData(key: string) {if (!key) {return}if(!kvStore) {kvStore = await this.createKeyValueDB();}return new Promise((resolve, reject) => {try {kvStore.get(key, (err, data) => {if (err != undefined) {console.error(`Failed to get data. Code:${err.code},message:${err.message}`);reject(err)return;}resolve(data)});} catch (err) {reject(err)console.error('TAG-getStoreData', `Failed to get value, Cause: ${err}`)}});}
}export default new DistributedUtil();
// 使用键值对存储
import distributedUtil from '../../common/distributedStrong'
// 1、增加
distributedUtil.putStoreData('amountRecords', JSON.stringify(dataArray))
// 2、 获取
distributedUtil.getStoreData('amountRecords').then((res: string) => {if(res) {const result = JSON.parse(res)// 处理存储的图片资源,会自动转换为id的形式,无法直接获取展示result.map(item => {item.icon = $rawfile(item.icon.params[0])return item})this.recordsArray = result}
})

3.4 统计图表

3.4.1 定义本地html文件

在resources下创建rawfile文件夹,增加chart.html文件

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>柱状图示例</title><script src="./js/echarts5.4.0.js"></script>
</head>
<body>
<h1>本月支出</h1>
<div id="chartBarContainer" style="width: 400px; height: 300px;"></div><div id="chartPieContainer" style="width: 400px; height: 300px;"></div></body>
<script>function initBarChart(chartData) {const data = JSON.parse(chartData);var chartContainer = document.getElementById('chartBarContainer');var chart = echarts.init(chartContainer);var option = {tooltip: {trigger: 'axis',axisPointer: {type: 'shadow'}},xAxis: {type: 'category',data: data.xAxisData},yAxis: {type: 'value'},series: [{data: data.seriesData,type: 'bar',showBackground: true,stack: 'Total',label: {show: true,position: 'top'},emphasis: {itemStyle: {color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#2378f7' },{ offset: 0.7, color: '#2378f7' },{ offset: 1, color: '#83bff6' }])}},itemStyle: {borderRadius: [25, 25, 0, 0],color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#83bff6' },{ offset: 0.5, color: '#188df0' },{ offset: 1, color: '#188df0' }])}}]};chart.setOption(option);}function initPieChart(chartData) {const data = JSON.parse(chartData);var chartContainer = document.getElementById('chartPieContainer');var chart = echarts.init(chartContainer);var option = {tooltip: {trigger: 'item'},legend: {show: false,top: '5%',left: 'center'},series: [{data: data.seriesData,type: 'pie',radius: ['40%', '70%'],avoidLabelOverlap: false,itemStyle: {borderRadius: 10,borderColor: '#fff',borderWidth: 2},startAngle: 180,labelLine: {show: true,length: 20, // 标签线的长度length2: 50 // 标签线的第二段长度},emphasis: {label: {show: true,fontSize: 40,fontWeight: 'bold'}},labelLine: {show: false}}]};chart.setOption(option);}
</script>
</html>

3.4.2 Web组件使用本地文件

import web_webview from '@ohos.web.webview'@Entry
@Component
struct Chart {controllerWeb: web_webview.WebviewController = new web_webview.WebviewController()build() {Web({ src: $rawfile('barChart.html'), controller: this.controllerWeb })}
}

3.4.3 Web组件向H5页面传值,调用H5中的方法

// 初始化柱状图
const codeJS = `initBarChart('${JSON.stringify(this.chartBarData)}')
`
this.controllerWeb.runJavaScript(codeJS)

3.4.4 完整调用代码

import web_webview from '@ohos.web.webview'
import router from '@ohos.router';interface ChartDataType {xAxisData?: Array<string | number>;seriesData?: Array<string | number | any>;
}@Entry
@Component
struct BarChart {controllerWeb: web_webview.WebviewController = new web_webview.WebviewController()private chartBarData: ChartDataType = {xAxisData: ['餐饮', '购物', '教育', '生活', '宠物', '运动', '娱乐', '其他'],seriesData: [10, 20, 15, 30, 10, 20, 15, 30],}private chartPieData: ChartDataType = {seriesData: [{ value: 10, name: '餐饮' },{ value: 20, name: '购物' },{ value: 15, name: '教育' },{ value: 30, name: '生活' },{ value: 10, name: '宠物' },{ value: 20, name: '运动' },{ value: 15, name: '娱乐' },{ value: 30, name: '其他' },],}build() {Column() {Row() {Button() {Image($rawfile('icon_back.png')).width(18)}.backgroundColor(Color.Transparent).padding(10).onClick(() => router.back())Text('图表分析').fontSize(20).fontWeight(FontWeight.Bold)}.padding(10).justifyContent(FlexAlign.SpaceBetween).width('100%')Web({ src: $rawfile('barChart.html'), controller: this.controllerWeb }).verticalScrollBarAccess(true).javaScriptAccess(true).onPageEnd(() => {// 初始化柱状图const codeJS = `initBarChart('${JSON.stringify(this.chartBarData)}')`this.controllerWeb.runJavaScript(codeJS)// 初始化饼图const codeJSPie = `initPieChart('${JSON.stringify(this.chartPieData)}')`this.controllerWeb.runJavaScript(codeJSPie)})}.width('100%').height('100%')}
}

3.4.5 传值注意点总结

  • 传递数据需要通过 JSON.stringify() 转换为字符串;
  • 传递的参数必须使用引号包裹,否则无法调用到H5中的方法;
  • H5中使用传过来的数据,同理,需要使用 JSON.parse() 进行转换;

3.5  自定义键盘

使用Grid布局,通过rowStart、rowEnd、columnStart、columnEnd进行单元格合并。或者使用column和row布局,循环即可。

参考:https://gitee.com/harmonyos/codelabs/tree/master/SimpleCalculator

Grid() {GridItem() {this.GridItemButtonBuilder('7')}.gridItemStyle().onClick(() => { this.clickBtn(7) })GridItem() {this.GridItemButtonBuilder('8')}.gridItemStyle().onClick(() => { this.clickBtn(8) })GridItem() {this.GridItemButtonBuilder('9')}.gridItemStyle().onClick(() => { this.clickBtn(9) })GridItem() {Text(this.time).backgroundColor(Color.White).width('100%').height('100%').textAlign(TextAlign.Center)}.gridItemStyle()GridItem() {this.GridItemButtonBuilder('4')}.gridItemStyle().onClick(() => { this.clickBtn(4) })GridItem() {this.GridItemButtonBuilder('5')}.gridItemStyle().onClick(() => { this.clickBtn(5) })GridItem() {this.GridItemButtonBuilder('6')}.gridItemStyle().onClick(() => { this.clickBtn(6) })GridItem() {this.GridItemButtonBuilder('remove')}.gridItemStyle().onClick(() => { this.clickBtn('remove') })GridItem() {this.GridItemButtonBuilder('1')}.gridItemStyle().onClick(() => { this.clickBtn('1') })GridItem() {this.GridItemButtonBuilder('2')}.gridItemStyle().onClick(() => { this.clickBtn('2') })GridItem() {this.GridItemButtonBuilder('3')}.gridItemStyle().onClick(() => { this.clickBtn('3') })GridItem() {this.GridItemButtonBuilder('保存', '#409EFF')}.rowStart(2).rowEnd(3).columnStart(3).columnEnd(4).onClick(() => { this.clickBtn('save') })GridItem() {this.GridItemButtonBuilder('清空')}.gridItemStyle().onClick(() => { this.clickBtn('clear') })GridItem() {this.GridItemButtonBuilder('0')}.gridItemStyle().onClick(() => { this.clickBtn('0') })GridItem() {this.GridItemButtonBuilder('.')}.gridItemStyle().onClick(() => { this.clickBtn('.') })
}
.height(220)
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.rowsGap(0)
.margin({ top: 0 })

4、扩展(如何使用基础组件自定义柱状图)

  • 使用Stack容器进行堆叠
  • 通过Rect绘制柱子
  • 通过Divider绘制分割线

    一个简单的柱状图就完成了,具体可以参考健康生活,希望ArkUI可以早日集成Echarts类似的图表组件,JS版本的有Chart组件,ArkTs的还未集成,期待官方❤️❤️❤️

@Builder
content(item: OneMealStatisticsInfo) {Column() {if (item.totalFat > 0) {Rect({ width: 14, height: item.totalFat / 200 + 14, radius: 7 }).fill('#FD9A42').padding({ top: 14 }).margin({ bottom: -28 })}if (item.totalProtein > 0) {Rect({ width: 14, height: item.totalProtein / 200 + 14, radius: 7 }).fill('#FBD44E').padding({ top: 14 }).margin({ bottom: -21 })}if (item.totalCarbohydrates > 0) {Rect({ width: 14, height: item.totalCarbohydrates / 200 + 14, radius: 7 }).fill('#73CD57').padding({ top: 7 }).margin({ bottom: -7 })}}.clip(true)}@Builder
legendComponent(item: HistogramLegend) {Text(item.value).fontSize(12).fontColor('#18181A').fontFamily('HarmonyHeTi')
}@Component
struct Histogram {@Consume("dietData") dietData: Array<OneMealStatisticsInfo>@BuilderParam content?: (item: OneMealStatisticsInfo) => void@BuilderParam legendComponent?: (item: HistogramLegend) => voidprivate title: string | Resource = ''private legend: HistogramLegend[] = []build() {Column() {Text(this.title).textAlign(TextAlign.Start).fontSize(24).fontColor('#000000').fontFamily('HarmonyHeTi-Medium').width('100%').height(46)Stack({ alignContent: Alignment.Bottom }) {Column() {ForEach([0, 0, 0, 0, 0, 0], () => {Divider().strokeWidth(1).color('#D8D8D8')})}.height('100%').margin({ top: 20 }).justifyContent(FlexAlign.SpaceBetween)Column() {Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly, alignItems: ItemAlign.Start }) {ForEach(this.dietData, (item: OneMealStatisticsInfo) => {if (item.mealFoods.length > 1) {Column() {if (this.content !== undefined) {this.content(item)}Text(item.mealTime.name).fontSize(14).fontColor('#7E7E7E').fontFamily('HarmonyHeTi').margin({ top: 10 })}.justifyContent(FlexAlign.End).height('100%')}})}}.height(236)}.height(190)Row() {ForEach(this.legend, (item: HistogramLegend) => {Row() {Rect({ width: 9, height: 9, radius: 9 }).fill(item.color).margin({ right: 18 })if (this.legendComponent !== undefined) {this.legendComponent(item)}}})}.justifyContent(FlexAlign.SpaceEvenly).width('100%').margin({ top: 70 })}.height('100%').padding({ left: 32, right: 32 }).borderRadius(12).backgroundColor('#FFFFFF')}
}

后面计划基于canvas基础组件实现一个柱状图,不断学习,期望鸿蒙不断强大完善。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/228700.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud(H版alibaba)框架开发教程,使用eureka,zookeeper,consul,nacos做注册中心——附源码(1)

源码地址&#xff1a;https://gitee.com/jackXUYY/springboot-example 创建订单服务&#xff0c;支付服务&#xff0c;公共api服务&#xff08;共用的实体&#xff09;&#xff0c;eureka服务 1.cloud-consumer-order80 2.cloud-provider-payment8001 3.cloud-api-commons 4.…

数据结构:队列(链表和数组模拟实现)

目录 1.何为队列 2.链表模拟实现 2.1 节点和队列创建 2.2 初始化队列 2.3 入队操作 2.4 出队操作 2.5 遍历队列 2.6 获取队首和队尾元素 2.7 判断队列是否为空 2.8 完整实现 3. 数组模拟实现 3.1 创建队列 3.2 入队和出队操作 3.3 遍历队列 3.4 获取队首和队尾元…

构建安全的SSH服务体系

1、配置OpenSSH服务端 在CentOS7.3系统中&#xff0c;OpenSSH服务由openssh、openssh-server等软件包提供&#xff08;默认已安装&#xff09;&#xff0c;并已将sshd添加为标准的系统服务。执行"systemctl start sshd"命令即可启动sshd服务。ssh服务的配置文件默认位…

初识SpringBoot(2023最后一篇文章)

初识SpringBoot 1、SpringBoot概述 Spring是什么&#xff1f; Spring是一个于2003 年兴起的一个轻量级开源Java开发框架&#xff0c;由Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》。Spring是为了解决企业级应用开发的复杂性而创建的&#xff0c;使…

探索工业智能检测,基于轻量级YOLOv8开发构建焊接缺陷检测识别系统

焊接缺陷相关的开发实践在前面的博文中已经有所涉及了&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a;《探索工业智能检测&#xff0c;基于轻量级YOLOv5s开发构建焊接缺陷检测识别系统》 将智能模型应用和工业等领域结合起来是有不错市场前景的&#xff0c;比如&…

001、安装 Rust

目录 1. 安装 Rust 2. 安装编译器 Visual Studio Code 3. 更新、卸载、文档命令 4. 结语 1. 安装 Rust 安装 Rust 非常简单&#xff0c;首先进入 Rust官网 &#xff0c;然后点击右上角的 Install 。 进入 Install 界面&#xff0c; 它会自动识别你当前的操作系统并给你推荐…

SASS循环

<template><div><button class"btn type-1">默认按钮</button><button class"type-2">主要按钮</button><button class"type-3">成功按钮</button><button class"type-4">信息…

Linux:apache优化(6)—— apache的ab压力测试

要对所购买的物理机(二手)进行烧机在产品上线之前&#xff0c;对应用的一个压力测试对产品本身的压力测试 作用&#xff1a;Apache 附带了压力测试工具 ab&#xff0c;非常容易使用&#xff0c;并且完全可以模拟各种条件对 Web 服务器发起测试请求。在进行性能调整优化过程中&a…

docker学习笔记01-安装docker

1.Docker的概述 用Go语言实现的开源应用项目&#xff08;container&#xff09;&#xff1b;克服操作系统的笨重&#xff1b;快速部署&#xff1b;只隔离应用程序的运行时环境但容器之间可以共享同一个操作系统&#xff1b;Docker通过隔离机制&#xff0c;每个容器间是互相隔离…

Linux:apache优化(7)—— 访问控制

作用&#xff1a;为apache服务提供的页面设置客户端访问权限&#xff0c;为某个组或者某个用户加密访问&#xff1b; /usr/local/httpd/bin/htpasswd -c /usr/local/httpd/conf/htpasswd tarro1 #添加admin用户&#xff0c;可以在两个路径中间添加-c是新建文件删除原文件&#…

MS2358:96KHz、24bit 音频 ADC

产品简述 MS2358 是带有采样速率 8kHz-96kHz 的立体声音频模数 转换器&#xff0c;适合于面向消费者的专业音频系统。 MS2358 通过使用增强型双位 Δ - ∑ 技术来实现其高精度 的特点。 MS2358 支持单端的模拟输入&#xff0c;所以不需要外部器 件&#xff0c;非常适…

RabbitMQ 和 Kafka 对比

本文对RabbitMQ 和 Kafka 进行下比较 文章目录 前言RabbitMQ架构队列消费队列生产 Kafka本文小结 前言 开源社区有好多优秀的队列中间件&#xff0c;比如RabbitMQ和Kafka&#xff0c;每个队列都貌似有其特性&#xff0c;在进行工程选择时&#xff0c;往往眼花缭乱&#xff0c;不…

2023第三届中国高校大数据挑战赛B题代码

任务已完成&#xff0c;聚类效果很好&#xff08;主要在于数据的处理以及特征工程&#xff09;, 需代码si&#xff0c;yuer有限先到先得。

LeetCode每日一题.05(N皇后)

按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种…

Ubuntu 18.04搭建RISCV和QEMU环境

前言 因为公司项目代码需要在RISCV环境下测试&#xff0c;因为没有硬件实体&#xff0c;所以在Ubuntu 18.04上搭建了riscv-gnu-toolchain QEMU模拟器环境。 安装riscv-gnu-toolchain riscv-gnu-toolchain可以从GitHub上下载源码编译&#xff0c;地址为&#xff1a;https://…

pycharm找回误删的文件和目录

昨天不知道做了什么鬼操作&#xff0c;可能是运行了几个git命令&#xff0c;将项目里面的几个文件删除了&#xff0c;有点懵。 我知道pycharm可以找回文件的历史修改记录&#xff0c;但是对于删除的文件能否恢复&#xff0c;一直没试过。 找到删除文件的目录&#xff0c;点击右…

3D换肤在服装行业的应用

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 通过采用高质量的 3D 模型&#xff0c;企业可以提供更加身临其境的体…

基于uniapp+vue3多端「h5+小程序+App」仿微信/抖音直播商城|uni-app+vue3小视频

uniapp-vue3-welive一款uniappvue3pinia跨端仿抖音直播商城实例。 全新基于uniappvue3vite4pinia等技术研发的一款跨平台仿制微信/抖音直播带货商城uniappvue3短视频实例项目&#xff0c;支持编译到h5小程序App端。 技术框架 编辑器&#xff1a;HbuilderX 3.98框架技术&#x…

c++写入数据到文件中

假设你想编写一个C程序&#xff1a;当你在调试控制台输入一些数据时&#xff0c;系统会自动存入到指定的文件中&#xff0c;该如何操作呢&#xff1f; 具体操作代码如下&#xff1a; #include<iostream> #include<string> #include<fstream> using namespa…

性能优化(CPU优化技术)-ARM Neon详细介绍

本文主要介绍ARM Neon技术&#xff0c;包括SIMD技术、SIMT、ARM Neon的指令、寄存器、意图为读者提供对ARM Neon的一个整体理解。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09…