马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1、写在前面
过去的一百年里,在“编程”的这个行业诞生之初,人们接纳面向过程的方式举行开发,但是,伴随着步伐规模的日益增大,步伐的复杂度也随之增加,利用结构化编程方法来管理复杂的步伐逻辑变得越来越困难。因此,开发者们引入了“面向对象”的概念,接纳将数据和操纵封装在“对象”中的方式,令步伐设计更加条理。
而现如今,市场上的文本类软件越来越多,创作者的门槛越来越低,无数笔墨作品争相涌现,长篇小说的字数也不断突破记录,但是,对于一个经验不敷丰富的创作者而言,常常会发生文章内容跑偏,角色性格无法把握的问题,特别是在小说笔墨长度越来越长时,如许的问题便愈发严肃。而在如许的情况下,我决定仿照“面向对象”的方式,开发一款基于“角色”定义,举行剧本编写的软件,帮助笔墨创作者们能够基于“角色”来编写文章,进步创作的服从与质量。
在选择开发平台时,我们注意到鸿蒙平台上还没有基于对话式的文本类APP,这使得我在实现根本功能的同时,还可以在这方面添补市场的空缺。此外,我也希望能够加入鸿蒙生态建设,助力国产科技企业发展,成为openHarmony漫天星光中的一员。
综上,我们决定开发一个鸿蒙原生的,基于角色定义的对话式文本编写软件,以达到办理市场空缺,办理用户需求的目的。
本文就是分享《说书人》的一部分鸿蒙技术实践过程,接待各位阅读
2、路由展示
2.1、页面展示
《说书人》的界面非常轻便,主要的界面包括:角色/剧本列表,用户空间,动态空间,以及阅读界面等,如下图所示
2.2、路由展示
(1)Tabs组件简介
Tabs组件的页面组成包罗两个部分,分别是TabContent和TabBar。TabContent是内容页,TabBar是导航页签栏,页面结构如下图所示,根据不同的导航类型,布局会有区别,可以分为底部导航、顶部导航、侧边导航,其导航栏分别位于底部、顶部和侧边。
Tabs组件的实践
《说书人》的角色/书籍列表,动态空间,个人页面,主要分为UserSpacePage,scriptInterface和DynamicPage三个组件,团结Tabs组件,举行页面布局,如下列代码所示:
- import { frame, frameDataSet, ThisPagefontColor } from '../Data/frameData'
- import { userOwnData, userOwn, userTheyData, userTheyTemplate } from '../Data/userData'
- import UserSpacePage from './mainPage/userSpacePage'
- import scriptInterface from './mainPage/scriptPage'
- import DynamicPage from './mainPage/dynamicPage'
- import router from '@ohos.router'
- @Entry
- @Component
- struct UserSpace {
- aboutToAppear(): void {
- let params = router.getParams() as Record<string, number>
- this.pageIndex = params['goPageIndex']
- }
- @StorageProp('frame') frameData: frame = frameDataSet
- @StorageProp('fontColor') ThisColor: ResourceColor = ThisPagefontColor
- @StorageProp('userOwnData') userOwn: userOwnData = userOwn
- @StorageProp('userTheyData') userTheyData: userTheyTemplate = userTheyData
- @State pageIndex: number = 0
- build() {
- Flex(){
- Tabs({
- index: this.pageIndex == undefined ? 0 : this.pageIndex,
- }){
- TabContent(){
- scriptInterface()
- }.tabBar('推荐')
- TabContent(){
- DynamicPage()
- }.tabBar('动态')
- TabContent(){
- UserSpacePage()
- }.tabBar('空间')
- }
- .barPosition(BarPosition.End)
- }
- .backgroundColor(this.frameData.backGround)
- }
- }复制
复制代码 3、端云一体化的实践应用
在本项目中,接入了华为云提供的认证服务与数据库服务,将AGC的serverless云和HarmonyOS的端举行团结开发,以下是开发示例
1、登录AppGallery Connect
2、在全部服务中,选择“认证服务”,启动手机号码认证
3、我的项目中,添加项目,再选择添加应用,随后根据实际情况填写即可
4、在“项目设置”中,下载SDK配置,并放在自己项目的如图所示的目录中
5、修改oh-package.json5文件,添加以下内容
- "dependencies": { "@hw-agconnect/hmcore": "^1.0.1", "@hw-agconnect/cloud": "^1.0.1" }复制复制复制
复制代码 6、修改EntryAbility.ets文件,在onCreate中添加以下内容
- async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
- let input = await this.context.resourceManager.getRawFileContent('agconnect-services.json')
- let jsonString = util.TextDecoder.create('utf-8', {
- ignoreBOM: true
- }).decodeWithStream(input, {
- stream: false
- });
- initialize(this.context, JSON.parse(jsonString));
- }复制复制复制
复制代码 7、打开需要利用验证码的界面,导入代码
- // 导入
- import cloud from '@hw-agconnect/cloud';
- import { Auth, VerifyCodeAction } from '@hw-agconnect/cloud';
- import { promptAction } from '@kit.ArkUI';
- // 申请验证码
- function requestVerifyCode(phoneNumber: string){
- cloud.auth().requestVerifyCode({
- action: VerifyCodeAction.REGISTER_LOGIN,
- lang: 'zh_CN',
- sendInterval: 60,
- verifyCodeType: {
- phoneNumber: phoneNumber,
- countryCode: '86',
- kind: "phone"
- }
- }).then(verifyCodeResult => {
- //验证码申请成功
- promptAction.showToast({
- message: '申请成功'
- })
- }).catch(() => {
- //验证码申请失败
- promptAction.showToast({
- message: "申请失败 "
- })
- });
- }
- // 注册用户
- function createUser(phoneNumber: string, phoneCode: string){
- cloud.auth().createUser({
- kind: 'phone',
- countryCode: '86',
- phoneNumber: phoneNumber,
- password: '123456789',//可以给用户设置初始密码,后续可以用密码来登录
- verifyCode: phoneCode
- }).then(result => {
- // 创建用户成功
- promptAction.showToast({
- message: '创建成功'
- })
- }).catch(() => {
- // 创建用户失败
- promptAction.showToast({
- message: '创建失败'
- })
- })
- }复制
复制代码 以上是手机号部分的开发流程,数据库部分也与其相似,可以在AGC官网查看文档举行开发
4、角色/剧本创建部分
对于角色/剧本的创建,总是会遇见需要“多维数组”等云数据库默认无法实现的情况,这种情况下,就接纳分隔符来对数据举行处理,以字符串类型存储在云端,再在本地读取时举行反处理
角色创建部分如下:
- import { frame, frameDataSet,ThisPagefontColor, frameData } from '../../Data/frameData'
- import { userOwnData, userOwn } from '../../Data/userData'
- import { roleDataTemplate, roleData, roleCustom } from '../../Data/roleData'
- import router from '@ohos.router';
- import EntryPage from '../popUp/EntryPage'
- import CarePage from '../popUp/CarePage'
- import DelCarePage from '../popUp/DelCarePage'
- import closePage from '../popUp/closePage'
- import promptAction from '@ohos.promptAction';
- @Extend(Text) function textCare(size: number){
- .fontColor('#3c3f41')
- .backgroundColor('#e0dcda')
- .fontSize(size)
- .fontWeight(300)
- .padding({
- left: 8,
- right: 8,
- top: 5,
- bottom: 5
- })
- .borderRadius(12)
- .opacity(.8)
- }
- @Entry
- @Component
- struct UserCreate {
- entryPageOpen: CustomDialogController = new CustomDialogController({
- builder: EntryPage({
- onCancel: (Name: string, Value: string) => {
- this.roleCustom.push({
- name: Name,
- value: Value
- })
- }
- })
- })
- carePageOpen: CustomDialogController = new CustomDialogController({
- builder: CarePage({
- onCancel: (Value: string) => {
- this.roleLabel.push(Value)
- }
- })
- })
- delcarePageOpen: CustomDialogController = new CustomDialogController({
- builder: DelCarePage({
- onCancel: (Value: boolean) => {
- if (Value) {
- this.roleLabel.splice(this.delRoleIndex, 1)
- }
- },
- })
- })
- closeOpen: CustomDialogController = new CustomDialogController({
- builder: closePage({
- onCancel: () => {
- this.roleData = roleData
- this.newRoleData = this.roleData
- this.roleData.createUserID = this.userOwn.userID
- this.roleData.createUserName = this.userOwn.userName
- router.pushUrl({
- "url": "pages/Main/userSpace",
- "params": {
- "goPageIndex" : 0
- }
- })
- },
- })
- })
- @StorageProp('frame') frameData: frame = frameDataSet
- @StorageProp('fontColor') ThisColor: ResourceColor = ThisPagefontColor
- @StorageProp('userOwnData') userOwn: userOwnData = userOwn
- @StorageLink('roleData') roleData: roleDataTemplate = roleData
- @State newRoleData: roleDataTemplate = this.roleData
- @State openText: boolean = false
- @State roleCustom: roleCustom[] = []
- @State roleLabel: string[] = this.newRoleData.roleLabel
- @State delRoleIndex: number = 0
- loadRoleData(){
- this.roleData = this.newRoleData
- this.roleData.roleLabel = this.roleLabel
- this.roleData.createUserID = this.userOwn.userID
- this.roleData.createUserName = this.userOwn.userName
- for (let index = 0; index < this.roleCustom.length; index++) {
- this.roleData.roleCustomName.push(this.roleCustom[index].name)
- this.roleData.roleCustomValue.push(this.roleCustom[index].value)
- }
- router.pushUrl({
- "url": "pages/dynamicSystem/previewPage/userPreviewPage"
- })
- }
- build() {
- Flex({
- wrap: FlexWrap.Wrap
- }){
- Column({ space: 24 }){
- // 标题
- Row(){
- frameData()
- .width(50)
- Text('角色')
- .fontSize(this.frameData.typefaceSize + 8)
- .fontColor(this.ThisColor)
- Text('')
- .width(50)
- }.width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- Scroll(){
- Column({ space: 24 }){
- // 封面标题作者标签ID
- Row({space: 16}){
- Text(String(this.newRoleData.roleName == '' ? '角色名称' : this.newRoleData.roleName).slice(String(this.newRoleData.roleName == '' ? '角色名称' : this.newRoleData.roleName).length - 2, String(this.newRoleData.roleName == '' ? '角色名称' : this.newRoleData.roleName).length))
- .fontSize(18)
- .width(120)
- .height(120)
- .borderRadius(60)
- .backgroundColor(this.frameData.backGround)
- .border({
- width: 1
- })
- .textOverflow({overflow: TextOverflow.Ellipsis})
- .padding(2)
- .textAlign(TextAlign.Center)
- .letterSpacing(3)
- Column({ space: 9 }){
- // 角色昵称
- Row(){
- Text(this.newRoleData.roleName == '' ? '角色名称' : this.newRoleData.roleName)
- .fontSize(this.frameData.typefaceSize + 6)
- .fontColor(this.ThisColor)
- .fontWeight(600)
- }
- // ID
- Row(){
- Text(this.newRoleData.roleAge.toString())
- .fontSize(this.frameData.typefaceSize - 4)
- .fontColor(Color.Gray)
- .fontWeight(400)
- }
- // 作者
- Row({ space: 6 }){
- Text(String(this.userOwn.userName).slice(String(this.userOwn.userName).length - 2, String(this.userOwn.userName).length))
- .fontSize(8)
- .width(28)
- .height(28)
- .borderRadius(14)
- .backgroundColor(this.frameData.backGround)
- .border({
- width: 1
- })
- .textOverflow({overflow: TextOverflow.Ellipsis})
- .padding(2)
- .textAlign(TextAlign.Center)
- Text(this.userOwn.userName)
- .fontSize(this.frameData.typefaceSize - 2)
- .fontColor(Color.Gray)
- }
- // 标签
- Flex({
- wrap: FlexWrap.Wrap,
- justifyContent: FlexAlign.Start
- }){
- ForEach(this.roleLabel, (item: string) => {
- Text(item)
- .textCare(this.frameData.typefaceSize - 3)
- .margin({
- right: 2,
- bottom: 2
- })
- })
- }
- }.width('100%')
- .alignItems(HorizontalAlign.Start)
- .height(120)
- }
- // 简介
- Column({ space: 12 }){
- Text('简介')
- .fontSize(this.frameData.typefaceSize + 4)
- .fontColor(this.ThisColor)
- .fontWeight(600)
- Row(){
- Text(`${this.newRoleData.roleBrief}`)
- .maxLines(this.openText ? 999 : 4)
- .textOverflow({overflow: this.openText ? TextOverflow.None : TextOverflow.Ellipsis })
- .fontSize(this.frameData.typefaceSize)
- .fontColor(this.ThisColor)
- }.width('100%')
- Row(){
- Text(this.openText ? '收起' : '展开')
- .fontSize(this.frameData.typefaceSize - 2)
- .fontColor(this.ThisColor)
- .fontWeight(300)
- .onClick(() => {
- this.openText = !this.openText
- })
- }.width('100%')
- }.width('100%')
- .alignItems(HorizontalAlign.Start)
- // 详细档案
- Column(){
- Row(){
- Text('详细档案')
- .fontSize(this.frameData.typefaceSize + 4)
- .fontColor(this.ThisColor)
- .fontWeight(600)
- }.width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .border({
- width: {
- bottom: 2
- }
- })
- .padding({
- bottom: 6
- })
- // 详细档案中的档案
- Column(){
- // 词条
- Column(){
- // 姓名
- Row(){
- Text('姓名:')
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .fontWeight(500)
- TextInput({ placeholder: this.newRoleData.roleName == '' ? '请输入角色姓名' : this.newRoleData.roleName })
- .width('100%')
- .backgroundColor(this.frameData.backGround)
- .borderRadius(0)
- .border({
- width:{
- bottom: 1
- },
- color: this.ThisColor
- })
- .fontColor(this.ThisColor)
- .placeholderColor(this.ThisColor)
- .fontSize(this.frameData.typefaceSize + 2)
- .maxLength(16)
- .onChange(value => {
- this.newRoleData.roleName = value
- })
- }.width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- // 年龄
- Row(){
- Text('年龄:')
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .fontWeight(500)
- TextInput({ placeholder: this.newRoleData.roleAge == '0' ? '请输入角色年龄' : this.newRoleData.roleAge.toString()})
- .width('100%')
- .backgroundColor(this.frameData.backGround)
- .borderRadius(0)
- .border({
- width:{
- bottom: 1
- },
- color: this.ThisColor
- })
- .fontColor(this.ThisColor)
- .placeholderColor(this.ThisColor)
- .fontSize(this.frameData.typefaceSize + 2)
- .maxLength(8)
- .type(InputType.Number)
- .onChange(value => {
- this.newRoleData.roleAge = value
- })
- }.width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- // 性别
- Row(){
- Text('性别:')
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .fontWeight(500)
- TextInput({ placeholder: this.newRoleData.roleGender == '' ? '请输入角色性别' : this.newRoleData.roleGender })
- .width('100%')
- .backgroundColor(this.frameData.backGround)
- .borderRadius(0)
- .border({
- width:{
- bottom: 1
- },
- color: this.ThisColor
- })
- .fontColor(this.ThisColor)
- .placeholderColor(this.ThisColor)
- .fontSize(this.frameData.typefaceSize + 2)
- .maxLength(6)
- .onChange(value => {
- this.newRoleData.roleGender = value
- })
- }.width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- ForEach(this.roleCustom, (item: roleCustom, index: number) => {
- // 姓名
- Row(){
- Text(item.name + ':')
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .fontWeight(500)
- .textAlign(TextAlign.End)
- .width('17%')
- Row(){
- TextInput({ placeholder: '请输入角色姓名', text: item.value })
- .width('71%')
- .backgroundColor(this.frameData.backGround)
- .borderRadius(0)
- .border({
- width:{
- bottom: 1
- },
- color: this.ThisColor
- })
- .fontColor(this.ThisColor)
- .placeholderColor(this.ThisColor)
- .fontSize(this.frameData.typefaceSize + 2)
- .maxLength(16)
- Text('删除')
- .onClick(() => {
- this.roleCustom.splice(index, 1)
- })
- .textAlign(TextAlign.End)
- .width(50)
- }
- }.width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- })
- Row(){
- Button('添加自定义词条')
- .border({
- width: 1
- })
- .backgroundColor(this.frameData.backGround)
- .fontColor(this.ThisColor)
- .width('100%')
- .onClick(() => {
- this.entryPageOpen.open()
- })
- }.width('100%')
- .padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- }
- // 标签
- Flex({
- wrap: FlexWrap.Wrap,
- justifyContent: FlexAlign.SpaceAround
- }){
- ForEach(this.roleLabel, (item: string, index) => {
- Row(){
- Button(item)
- .border({
- width: 1
- })
- .backgroundColor(this.frameData.backGround)
- .fontColor(this.ThisColor)
- .width('45%')
- .onClick(() => {
- this.delRoleIndex = index
- this.delcarePageOpen.open()
- })
- }.padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- })
- Row(){
- Button('添加角色标签')
- .border({
- width: 1
- })
- .backgroundColor(this.frameData.backGround)
- .fontColor(this.ThisColor)
- .width('45%')
- .onClick(() => {
- this.carePageOpen.open()
- })
- }.padding({
- left: 12,
- right: 12,
- top: 12,
- bottom: 12
- })
- }.width('100%')
- // 剧本
- Column({ space: 6 }){
- Row(){
- Text('角色简介:')
- .fontSize(this.frameData.typefaceSize + 4)
- .fontColor(this.ThisColor)
- .fontWeight(600)
- }.width('100%')
- .padding({
- bottom: 6
- })
- // 简介
- Row(){
- TextArea({ placeholder: '请输入角色简介'})
- .width('100%')
- .height(120)
- .padding(16)
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .fontWeight(500)
- .onChange(value => {
- this.newRoleData.roleBrief = value
- })
- .maxLength(200)
- }.width('100%')
- }
- }
- }.width('100%')
- .alignItems(HorizontalAlign.Center)
- }
- .alignItems(HorizontalAlign.Start)
- }.edgeEffect(EdgeEffect.Spring)
- .scrollBar(BarState.Off)
- .height('85%')
- Row(){
- Button('预览')
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .backgroundColor(this.frameData.backGround)
- .width('50%')
- .padding({
- top: 16,
- bottom: 16
- })
- .onClick(() => {
- this.loadRoleData()
- })
- Button('取消')
- .fontSize(this.frameData.typefaceSize + 2)
- .fontColor(this.ThisColor)
- .backgroundColor(this.frameData.backGround)
- .width('50%')
- .padding({
- top: 16,
- bottom: 16
- })
- .onClick(() => {
- this.closeOpen.open()
- })
- }
- .width('100%')
- .justifyContent(FlexAlign.SpaceAround)
- .alignItems(VerticalAlign.Center)
- .border({
- width: {
- top: 2
- }
- })
- }
- .padding({
- top: 16,
- left: 24,
- right: 24,
- bottom: 16
- })
- .height('100%')
- }
- }
- }复制
复制代码 5、总结
说书人APP是一款基于鸿蒙平台开发的原生应用,旨在通过角色定义的方式进步笔墨创作的服从与质量。
不仅弥补了市场上对话式文本类APP的空缺,而且通过华为端云一体化开发,接入了短信认证服务和云数据库,确保了应用的高兼容性、稳固性、低功耗和高性能。
项目配景与目标:随着市场上文本创作软件的增多,创作者面临的挑战也随之增加,尤其是对于经验不足的创作者而言,怎样有效管理角色和剧情成为一大难题。说书人APP应运而生,目的是帮助这些创作者通过“面向对象”的方式编写剧本,从而进步创作服从和作品质量。
技术实现:应用接纳了华为的端云一体化开发模式,利用DevEco Studio举行编译运行,并支持API version 9版本SDK,确保了应用的技术先进性和安全性。
功能特色:包括注册登录系统、文章创建与编辑、章节管理、角色创建与自定义、以及用户个性化设置等,全面满意笔墨创作者的需求。特别是其基于角色的对话式编写功能,为创作者提供了全新的创作体验。
上风方面
技术创新:作为鸿蒙平台上的原生应用,说书人APP在技术上具有天然的上风,包括更好的系统兼容性和性能优化。 用户体验:通过角色定义的创作方式,简化了复杂的剧情管理,使得创作者可以更加专注于内容的创作。
生态贡献:加入鸿蒙生态建设,不仅丰富了应用市场的多样性,也为国产科技企业的发展贡献了一份气力。
需要改进的地方
用户引导:对于新用户来说,应用的功能大概较为复杂,需要更加直观的用户引导和教程来帮助用户快速上手。
社交互动:虽然应用提供了丰富的创作工具,但在社交互动方面似乎有所欠缺。增加创作者之间的交流和合作功能,大概会进一步提升用户的活泼度和粘性。
持续优化:任何应用都需要根据用户反馈举行持续优化。说书人APP也不例外,需要不断网络用户意见,对功能、界面和性能举行迭代升级。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |