【搜索页】- 功能流程
【搜索页】- 功能流程
【搜索组件】- 改造搜索组件HdSearch
src/main/ets/common/components/HdSearch.ets
课程目标
- 直接将搜索关键字写死在keywords数组中:keywords:string[]=['html','css','js','vue','react']
- 使用setInterval实现每隔3秒完成题目分类数据的切换
- 使用router传参的方式将题目分类名称传给搜索页SearchPage.ets
步骤:
- 改造HdSearch.ets组件
import { router } from '@kit.ArkUI'
@Component
export struct HdSearch {
@State
readonlyMode: boolean = true
@State
ph: string = 'html'
bg: string = ''
color: string = ''
keywords: string[] = ['html', 'css3', 'javascript', 'vue', 'react'] // 用来给轮播使用的数据
index: number = 0 //表示取得数组的索引号
aboutToAppear(): void {
// 页面加载完毕就开启定时器
setInterval(() => {
// 索引先+
this.index++
// 判断等于数组长度就重置索引
if (this.index === this.keywords.length) {
this.index = 0
}
// 从数组中获取当前索引的关键字
this.ph = this.keywords[this.index]
}, 3000)
}
build() {
Row() {
Row({ space: 4 }) {
Image($r("app.media.ic_common_search"))
.width($r('app.float.hd_search_icon_size'))
.aspectRatio(1)
.fillColor(this.color || $r('app.color.common_gray_02'))
Text(this.ph || $r('app.string.hd_search_placeholder'))
.fontColor(this.color || $r('app.color.common_gray_02'))
.fontSize($r('app.float.common_font14'))
}
.layoutWeight(1)
.height($r('app.float.hd_search_height'))
.backgroundColor(this.bg || $r('app.color.common_gray_border'))
.borderRadius($r('app.float.hd_search_radius'))
.justifyContent(FlexAlign.Center)
}
.onClick(() => {
router.pushUrl({ url: 'pages/SearchPage',params:{keywordText:this.ph} })
})
}
}
- 新建SearchPage.ets 接受路由传参
import { router } from '@kit.ArkUI';
interface iRouterParams {
keywordText: string
}
@Entry
@Component
struct SearchPage {
@State message: string = 'Hello World';
aboutToAppear(): void {
let params = router.getParams() as iRouterParams
this.message = params.keywordText
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
【搜索页】- 静态结构(SearchPage.ets)
课程目标
- 首选项综合案例上代码作为静态模版
import { promptAction } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'
@Entry
@Component
struct PreDemo {
@State keyword: string = ''
@StorageProp('topHeight') topHeight: number = 0
@State keywords: string[] = []
aboutToAppear() {
this.getData()
}
@Builder
itemBuilder(text: string) {
Row({ space: 20 }) {
Text(text)
Text('x')
.height(30)
.onClick(async () => {
// 删除指定关键字
await this.delData(text)
await this.getData()
})
}
.margin({ right: 10, top: 10 })
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.backgroundColor('rgba(0,0,0,0.05)')
.borderRadius(20)
}
// 1.0 新增方法
async saveData(text: string) {
// 非空验证
if (!text) {
return
}
// 1.0 获取首选项对象实例
const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
// 2.0 调用putsync方法保存数组
// 2.0.1 先从首选项中获取老数据
let dataArr = pre.getSync('keyword', []) as string[]
// 判断如果首选项中已经有了该关键词,不再保存
if (dataArr.includes(text)) {
// 该关键字已经存在了,不保存
return
}
dataArr.push(text)
// 2.0.2 存数据
pre.putSync('keyword', dataArr)
// 3.0 调用flush写入到磁盘
await pre.flush()
}
// 2.0 获取首选项数据
async getData() {
const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
this.keywords = pre.getSync('keyword', []) as string[]
}
// 3.0 删除指定关键字和全部关键字
async delData(text?: string) {
const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
// 1.0 删除全部关键
if (!text) {
pre.deleteSync('keyword')
} else {
// 2.0 删除指定关键字 获取 -> 删除内存数组的关键字 -> 写回
let datas = pre.getSync('keyword', []) as string[]
let cindex = datas.findIndex(item => item === text)
// 当关键字索引为-1的时候,表示没有找到
if (cindex < 0) {
return
}
// 如果有删除
datas.splice(cindex, 1) // 返回值表示删除的元素
// 保存回去
pre.putSync('keyword', datas)
}
// 写回磁盘
pre.flush()
}
build() {
Navigation() {
Column({ space: 15 }) {
// 1. 搜索关键字
TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
.onSubmit(async () => {
// AlertDialog.show({ message: this.keyword })
// 将关键词数据保存到首选项中
await this.saveData(this.keyword)
await this.getData()
})
// 2. 关键字列表
Row() {
Text('搜索记录').fontSize(20).fontWeight(800)
Row() {
Text('全部删除')
.onClick(async () => {
// 删除全局数据
await this.delData()
await this.getData()
})
Text(' | ')
Text('取消删除')
Image($r('app.media.ic_common_delete'))
.height(28)
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 3. 关键字列表
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.keywords, (item: string) => {
this.itemBuilder(item)
})
}
}
.padding(15)
}
.padding({ top: this.topHeight })
.titleMode(NavigationTitleMode.Mini)
.title('搜索页面')
}
}
【搜索页】- 向首选项中存储路由传入的关键字
课程目标
- HdSearch.ets组件通过路由给SearchPage传入关键字
- SearchPage接收路由参数关键字,将数据保存到首选项
注意:不能将@State修饰的状态变量数组存储到首选项,会报错
import { router } from '@kit.ArkUI'
@Component
export struct HdSearch {
@State
readonlyMode: boolean = true
@State
ph: string = 'html'
bg: string = ''
color: string = ''
keywords: string[] = ['html', 'css3', 'javascript', 'vue', 'react'] // 用来给轮播使用的数据
index: number = 0 //表示取得数组的索引号
aboutToAppear(): void {
// 页面加载完毕就开启定时器
setInterval(() => {
// 索引先+
this.index++
// 判断等于数组长度就重置索引
if (this.index === this.keywords.length) {
this.index = 0
}
// 从数组中获取当前索引的关键字
this.ph = this.keywords[this.index]
}, 3000)
}
build() {
Row() {
Row({ space: 4 }) {
Image($r("app.media.ic_common_search"))
.width($r('app.float.hd_search_icon_size'))
.aspectRatio(1)
.fillColor(this.color || $r('app.color.common_gray_02'))
Text(this.ph || $r('app.string.hd_search_placeholder'))
.fontColor(this.color || $r('app.color.common_gray_02'))
.fontSize($r('app.float.common_font14'))
}
.layoutWeight(1)
.height($r('app.float.hd_search_height'))
.backgroundColor(this.bg || $r('app.color.common_gray_border'))
.borderRadius($r('app.float.hd_search_radius'))
.justifyContent(FlexAlign.Center)
}
.onClick(() => {
router.pushUrl({ url: 'pages/SearchPage',params:{keywordText:this.ph} })
})
}
}
import { promptAction } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'
import { router } from '@kit.ArkUI';
interface iRouterParams {
keywordText: string
}
@Entry
@Component
struct SearchPage {
@State keyword: string = ''
@StorageProp('topHeight') topHeight: number = 0
@State keywords: string[] = []
@State isdel: boolean = false
async aboutToAppear() {
let params = router.getParams() as iRouterParams
this.keyword = params.keywordText // 接收路由参数
// 将路由传入的参数关键字保存到首选项
await this.saveData(params.keywordText)
await this.getData()
}
@Builder
itemBuilder(text: string) {
Row({ space: 20 }) {
Text(text)
if (this.isdel) {
Text('x')
.height(30)
.onClick(async () => {
// 删除指定关键字
await this.delData(text)
await this.getData()
})
}
}
.margin({ right: 10, top: 10 })
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.backgroundColor('rgba(0,0,0,0.05)')
.borderRadius(20)
}
// 1.0 新增方法
async saveData(text: string) {
// 非空验证
if (!text) {
return
}
// 1.0 获取首选项对象实例
const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
// 2.0 调用putsync方法保存数组
// 2.0.1 先从首选项中获取老数据
let dataArr = pre.getSync('keyword', []) as string[]
// 判断如果首选项中已经有了该关键词,不再保存
if (dataArr.includes(text)) {
// 该关键字已经存在了,不保存
return
}
dataArr.push(text)
// 2.0.2 存数据
pre.putSync('keyword', dataArr)
// 3.0 调用flush写入到磁盘
await pre.flush()
}
// 2.0 获取首选项数据
async getData() {
const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
this.keywords = pre.getSync('keyword', []) as string[]
}
// 3.0 删除指定关键字和全部关键字
async delData(text?: string) {
const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
// 1.0 删除全部关键
if (!text) {
pre.deleteSync('keyword')
} else {
// 2.0 删除指定关键字 获取 -> 删除内存数组的关键字 -> 写回
let datas = pre.getSync('keyword', []) as string[]
let cindex = datas.findIndex(item => item === text)
// 当关键字索引为-1的时候,表示没有找到
if (cindex < 0) {
return
}
// 如果有删除
datas.splice(cindex, 1) // 返回值表示删除的元素
// 保存回去
pre.putSync('keyword', datas)
}
// 写回磁盘
pre.flush()
}
build() {
Navigation() {
Column({ space: 15 }) {
// 1. 搜索关键字
TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
.onSubmit(async () => {
// AlertDialog.show({ message: this.keyword })
// 将关键词数据保存到首选项中
await this.saveData(this.keyword)
await this.getData()
})
// 2. 关键字列表
Row() {
Text('搜索记录').fontSize(20).fontWeight(800)
Row() {
if (this.isdel) {
Text('全部删除')
.onClick(async () => {
// 删除全局数据
await this.delData()
await this.getData()
})
Text(' | ')
Text('取消删除')
.onClick(() => {
this.isdel = false
})
}
Image($r('app.media.ic_common_delete'))
.height(28)
.onClick(() => {
this.isdel = true
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 3. 关键字列表
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.keywords, (item: string) => {
this.itemBuilder(item)
})
}
}
.padding(15)
}
.padding({ top: this.topHeight })
.titleMode(NavigationTitleMode.Mini)
.title('搜索页面')
}
}
【搜索页】- 根据关键字搜索数据
课程目标
- 复用src/main/ets/views/home/QuestionListComp.ets页面,在QuestionListComp.ets里面的请求url上增加keyword关键字参数
import { preferences } from '@kit.ArkData'
import promptAction from '@ohos.promptAction';
import { router } from '@kit.ArkUI';
import { QuestionListComp } from '../views/home/QuestionListComp';
interface iRouterParams {
keywrod: string
}
@Entry
@Component
struct SearchPage {
@State keyword: string = '' // 搜索关键字
@State kdList: string[] = []
@StorageProp('topHeight') topHeight: number = 0
@State isdel: boolean = false
@State isSearch: boolean = false
async aboutToAppear() {
// 获取路由传入的关键字
let routerObj = router.getParams() as iRouterParams
this.keyword = routerObj.keywrod
this.isSearch = true //打开搜索页面
// 将关键字保存到用户首选项中
await this.saveKeyWord(routerObj.keywrod)
// 页面进入即获取关键字数组显示
this.kdList = await this.getKeyWords()
}
// 1. 保存用户首选项中的关键
async saveKeyWord(keyword: string) {
// 1. 去用户首选项中获取key为keyword的数据,字符串数组
let pre = preferences.getPreferencesSync(getContext(), { name: 'store' })
// 2. 向首选项中获取数据
let kdArr = pre.getSync('keyword', []) as string[]
// 判断kdArr数组中如果已经存在了keyword变量的值,则不再追加
// ['html5','css3'] -> css3 --> 数组的 find,findindex,indexOf, includes
if (kdArr.includes(keyword)) {
return //不再追加
}
// 3. 利用push方法将用户输入的关键字追加到kdArr中
kdArr.push(keyword)
// 4. 利用putSync+flush方法完成写入
pre.putSync('keyword', kdArr)
await pre.flush()
}
// 2. 获取用户首选项中的关键字数组
async getKeyWords() {
let pre = preferences.getPreferencesSync(getContext(), { name: 'store' })
return pre.getSync('keyword', []) as string[]
}
// 3. 删除(① 删除指定关键字 ② 全部删除)
async delKeyWord(keywrod?: string) {
let pre = preferences.getPreferencesSync(getContext(), { name: 'store' })
if (keywrod) {
// 删除指定的关键字
// 1. 获取首选项的所有数据到内存中(字符串数组)
let kdArr = pre.getSync('keyword', []) as string[]
// 2. 根据已知的关键字keywrod 查找它在这个数组中的索引,同时调用splice删除之
let index = kdArr.findIndex(item => item == keywrod)
// ✨注意:如果 index < 0 则不删除
if (index < 0) {
promptAction.showToast({ message: '当前要删除掉关键字不存在' })
return
}
kdArr.splice(index, 1)
// 3. 调用 putSync将最新的数组写回文件
pre.putSync('keyword', kdArr)
} else {
// 删除全部
pre.deleteSync('keyword')
}
await pre.flush()
}
@Builder
itemBuilder(text: string) {
Row({ space: 20 }) {
Text(text)
if (this.isdel) {
Text('x')
.height(30)
.onClick(async () => {
// 删除指定关键字
await this.delKeyWord(text)
// 重新筛选页面
this.kdList = await this.getKeyWords()
})
}
}
.margin({ right: 10, top: 10 })
.padding({
left: 15,
right: 15,
top: 5,
bottom: 5
})
.backgroundColor('rgba(0,0,0,0.05)')
.borderRadius(20)
}
build() {
Navigation() {
Column({ space: 15 }) {
// 1. 搜索关键字
TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
.onSubmit(async () => {
// 关键字保存到首选项中
await this.saveKeyWord(this.keyword)
this.isSearch = true
// 获取关键字数组显示
this.kdList = await this.getKeyWords()
})
.onClick(() => {
// 点击文本框关闭搜索组件
this.isSearch = false
})
if (this.isSearch) {
// 显示搜索记录组件(List)
Row() {
QuestionListComp({ keyword: this.keyword })
}
} else {
// 2. 搜索记录
Row() {
Text('搜索记录').fontSize(20).fontWeight(800)
Row() {
if (this.isdel) {
Text('全部删除')
.onClick(async () => {
// 全部删除所以无需传参数
await this.delKeyWord()
// 刷新页面
this.kdList = await this.getKeyWords()
})
Text(' | ')
Text('取消删除')
.onClick(() => {
this.isdel = false
})
} else {
Image($r('app.media.ic_common_delete'))
.height(28)
.onClick(() => {
this.isdel = true
})
}
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 3. 关键字列表
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.kdList, (item: string) => {
this.itemBuilder(item)
})
}
}
}
.padding(15)
}
.padding({ top: this.topHeight })
.titleMode(NavigationTitleMode.Mini)
.title('搜索页面')
}
}
import { HdLoadingDialog } from '../../components/HdLoadingDialog'
import { HdHttp } from '../../utils/request'
import { QuestionItemComp, QuestionItem, iQuestion } from '../QuestionItemComp'
import { FilterParams } from './QuestionFilterComp'
import { router } from '@kit.ArkUI'
@Component
export struct QuestionListComp {
// 这个数据是从HomeCategoryComp中传入的(父 传 子)
@Prop typeid: number = 0
@State list: QuestionItem[] = []
// 定义一个状态属性,用来和Refresh组件进行双向数据绑定
@State isRefreshing: boolean = false
page: number = 0 //当前列表数据的页码
@State isloadMore: boolean = false // 加载更多
@Prop keyword: string = ''
// 实例化自定义弹窗对象
dialog = new CustomDialogController({
builder: HdLoadingDialog({ message: '正在加载中...' }),
customStyle: true, //表示关闭CustomDialogController自己的样式,只显示组件定义的样式
alignment: DialogAlignment.Center //弹窗的位置在中间
})
// 定义一个参数,用@Watch来监听其变化,从而主动执行getList方法
@Prop @Watch('getList') params: FilterParams = { index: 0, sort: 0 } as FilterParams
selfIndex: number = 0 // 应该由Cate组件传入,表示当前List组件是属于哪个分类索引下的组件
// // source这个参数代表的是哪个参数的改变引起的监听器执行
// loadData(source:string) {
// AlertDialog.show({ message: JSON.stringify('watch触发了' + source) })
// }
aboutToAppear(): void {
// 注释原因:因为在List组件上增加了触底加载更多的方法onReachEnd,此时这个方法在页面进入的时候会执行一次,所以注释掉
// aboutToAppear中的主动请求数据方法
// this.getList()
}
async getList(p?: string) {
// 优化:如果当前params中的index 和 List组件本身的index一致,应该执行请求,否则不允许执行
if (this.params.index != this.selfIndex) {
return
}
if (p == 'params') {
this.page = 1
}
try {
// 打开自定义弹窗
this.dialog.open()
let res = await HdHttp.Get<iQuestion>('hm/question/list', new Object({
questionBankType: 10, //接口必选参数,默认填写10
type: this.typeid, // 由于我们是要查看某个分类下面的数据,所以必须填写type参数
page: this.page,
sort: this.params.sort, //浏览量:20从低到高21从高到底 ,0:默认
keyword: this.keyword // 这是搜索关键字
}))
// 判断当前如果是触底加载更多,才追加到数据后面,如果是下拉刷新,应该直接赋值
if (this.isloadMore) {
this.list.push(...res.data.rows)
} else {
this.list = res.data.rows
}
// 增加将this.list中的所有id变成一个数组保存到AppStroage中
// 1. 使用map将this.list数组中的id变成一个 ['12','13','25']
let ids = this.list.map(item => item.id)
// 2. 将ids数组保存到AppStroage中
AppStorage.setOrCreate('list', ids)
// 关闭下拉动画
this.isRefreshing = false
this.isloadMore = false //数据触底加载完成
// 关闭自定义弹窗
this.dialog.close()
} catch (err) {
// 关闭下拉动画
this.isRefreshing = false
this.isloadMore = false //数据触底加载完成
// 关闭自定义弹窗
this.dialog.close()
}
}
build() {
Column() {
Text(this.typeid.toString()).fontSize(30)
Refresh({ refreshing: $$this.isRefreshing }) {
List() {
ForEach(this.list, (item: QuestionItem) => {
ListItem() {
QuestionItemComp({
item: item
})
.padding({ left: 10, right: 10 })
}
.onClick(() => {
router.pushUrl({ url: 'pages/QuestionDetailPage', params: { id: item.id } })
})
})
}
.edgeEffect(EdgeEffect.None) // 关闭list组件的回弹效果,从而优化触底会发2次请求的问题
.onReachEnd(() => {
// 防止用户鼠标或者手指抖动
if (this.isloadMore == false) {
this.isloadMore = true
// 页面进入即执行一次
this.page++
this.getList()
}
})
}
.onRefreshing(() => {
// 重新请求服务器数据
this.page = 1 //下拉刷新的时候,将当前页码重置为1
this.getList()
})
}
}
}
【搜索页】-2个优化
- SearchPage页面软键盘优化
import { promptAction, router } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'
import { QuestionListComp } from '../views/home/QuestionListComp'
import { FilterParams } from '../views/home/QuestionFilterComp'
import { Logger } from '../common/utils/Logger'
interface iRouterParams {
keywordText: string
}
@Entry
@Component
struct SearchPage {
@State keyword: string = ''
@StorageProp('topHeight') topHeight: number = 0
@State isdel: boolean = false
@State kdList: string[] = []
@State isSearch: boolean = true
@StorageLink('filter') params: FilterParams = { sort: 0, index: 0 }
@State fsable:boolean = false
@StorageLink('startInterval') startIntervalFlag : boolean = true
async aboutToAppear() {
this.kdList = await this.getData()
// 接收搜索关键字
const p = router.getParams() as iRouterParams
this.keyword = p.keywordText
}
// 准备用户首选项的 保存数据方法+删除方法 + 获取方法
async getData() {
const pre = preferences.getPreferencesSync(getContext(), { name: 'keywordStore' })
return pre.getSync('keyword', []) as string[] //['html5','css3'] -> 如果没有 []
}
// 保存数据
async saveData(kdText: string) {
const pre = preferences.getPreferencesSync(getContext(), { name: 'keywordStore' })
// 1. 先从用户首选项获取老数据
const oldData = pre.getSync('keyword', []) as string[]
// 2. 如果kdText在用户首选项中存来了,就不存了
if (oldData.includes(kdText)) {
return
}
oldData.push(kdText)
// 3. 将新数据保存到首选项中
pre.putSync('keyword', oldData)
// 4. 写入到文件
pre.flushSync()
}
// 删除数据(指定文本删除,全部删除)
async delData(kdText?: string) {
const pre = preferences.getPreferencesSync(getContext(), { name: 'keywordStore' })
if (kdText) {
// 指定文本删除
// 1. 获取老数据 2 删除指定的文本(内存中的数据) 3 将数据写回到磁盘
const oldData = pre.getSync('keyword', []) as string[]
const index = oldData.findIndex(item => item === kdText)
// 对未找到的关键字不执行删除动作
if (index < 0) {
return
}
oldData.splice(index, 1)
pre.putSync('keyword', oldData)
pre.flushSync()
} else {
// 全部删除
pre.deleteSync('keyword')
pre.flushSync()
}
}
@Builder
itemBuilder(text: string) {
Row({ space: 20 }) {
Text(text)
.onClick(()=>{
this.isSearch = true
this.keyword = text
this.fsable = false
})
if (this.isdel) {
Text('x')
.height(30)
.onClick(async () => {
// 1. 调用删除方法
await this.delData(text)
// 2. 重新获取数据
this.kdList = await this.getData()
})
}
}
.margin({ right: 10, top: 10 })
.padding({
left: 15,
right: 15,
top: 5,
bottom: 5
})
.backgroundColor('rgba(0,0,0,0.05)')
.borderRadius(20)
}
onBackPress(): boolean | void {
Logger.debug('onBackPress')
this.startIntervalFlag = false
this.startIntervalFlag = true
return false // 使用原有的路由方式来回退
}
build() {
Navigation() {
Column({ space: 15 }) {
// 1. 搜索关键字
TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })// 回车的时候会触发
.onSubmit(async () => {
// AlertDialog.show({ message: this.keyword })
// 1. 将关键字保存到首选项中
await this.saveData(this.keyword)
// 2. 获取用户首选项数据回显在页面上
this.kdList = await this.getData()
this.isSearch = true
// AlertDialog.show({message:JSON.stringify('保存成功')})
})
.onClick(()=>{
this.isSearch = false
this.fsable = true
})
.focusable(this.fsable)
if (this.isSearch) {
// 使用搜索组件
Column(){
QuestionListComp({keyword:this.keyword,selfIndex:this.params.index})
}
} else {
// 2. 关键字列表
Row() {
Text('搜索记录').fontSize(20).fontWeight(800)
Row() {
if (this.isdel) {
Text('全部删除')
.onClick(async () => {
// AlertDialog.show({ message: '补上全部删除逻辑' })
// 1. 调用删除方法
await this.delData()
// 2. 重新获取数据
this.kdList = await this.getData()
})
Text(' | ')
Text('取消删除')
.onClick(() => {
this.isdel = false
})
} else {
Image($r('app.media.ic_common_delete'))
.height(28)
.onClick(() => {
this.isdel = true
})
}
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 3. 关键字列表
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.kdList, (item: string) => {
this.itemBuilder(item)
})
}
}
}
.padding(15)
}
.padding({ top: this.topHeight })
.titleMode(NavigationTitleMode.Mini)
.title('搜索页面')
}
}
- HdSearch组件定时器优化
import { router } from '@kit.ArkUI'
import { Logger } from '../utils/Logger'
@Component
export struct HdSearch {
@State
readonlyMode: boolean = true
@State ph: string = 'html'
bg: string = ''
color: string = ''
// 1.准备用户喜好的关键字
keywords: string[] = ['html', 'css', 'js', 'vue', 'react']
index: number = 0
intervalNum: number = -1
@StorageLink('startInterval') @Watch('startInterval') startIntervalFlag : boolean = true
// 2. 进入这个组件之后,开启定时器(setInterval(()=>{},3000))每隔三秒中轮播里面的关键字
aboutToAppear(): void {
// Logger.debug('aboutToAppear')
this.startInterval()
}
startInterval(){
if(!this.startIntervalFlag) return
this.intervalNum = setInterval(() => {
Logger.debug('setInterval')
// 1. 定义一个索引
// 2. 索引自增后从keywords中取值赋值给 ph这个状态变量 ->自动刷新UI
this.index++
if (this.index >= this.keywords.length) {
this.index = 0
}
this.ph = this.keywords[this.index]
}, 3000)
}
build() {
Row() {
Row({ space: 4 }) {
Image($r("app.media.ic_common_search"))
.width($r('app.float.hd_search_icon_size'))
.aspectRatio(1)
.fillColor(this.color || $r('app.color.common_gray_02'))
Text(this.ph || $r('app.string.hd_search_placeholder'))
.fontColor(this.color || $r('app.color.common_gray_02'))
.fontSize($r('app.float.common_font14'))
.onClick(() => {
// 跳转之前,将定时器移除
clearInterval(this.intervalNum)
router.pushUrl({ url: 'pages/SearchPage', params: { keywordText: this.ph } })
})
}
.layoutWeight(1)
.height($r('app.float.hd_search_height'))
.backgroundColor(this.bg || $r('app.color.common_gray_border'))
.borderRadius($r('app.float.hd_search_radius'))
.justifyContent(FlexAlign.Center)
}.onClick(() => {
// router.replaceUrl({ url: 'pages/SearchPage' })
})
}
}
import { promptAction, router } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'
import { QuestionListComp } from '../views/home/QuestionListComp'
import { FilterParams } from '../views/home/QuestionFilterComp'
import { Logger } from '../common/utils/Logger'
interface iRouterParams {
keywordText: string
}
@Entry
@Component
struct SearchPage {
@State keyword: string = ''
@StorageProp('topHeight') topHeight: number = 0
@State isdel: boolean = false
@State kdList: string[] = []
@State isSearch: boolean = true
@StorageLink('filter') params: FilterParams = { sort: 0, index: 0 }
@State fsable:boolean = false
@StorageLink('startInterval') startIntervalFlag : boolean = true
async aboutToAppear() {
this.kdList = await this.getData()
// 接收搜索关键字
const p = router.getParams() as iRouterParams
this.keyword = p.keywordText
}
// 准备用户首选项的 保存数据方法+删除方法 + 获取方法
async getData() {
const pre = preferences.getPreferencesSync(getContext(), { name: 'keywordStore' })
return pre.getSync('keyword', []) as string[] //['html5','css3'] -> 如果没有 []
}
// 保存数据
async saveData(kdText: string) {
const pre = preferences.getPreferencesSync(getContext(), { name: 'keywordStore' })
// 1. 先从用户首选项获取老数据
const oldData = pre.getSync('keyword', []) as string[]
// 2. 如果kdText在用户首选项中存来了,就不存了
if (oldData.includes(kdText)) {
return
}
oldData.push(kdText)
// 3. 将新数据保存到首选项中
pre.putSync('keyword', oldData)
// 4. 写入到文件
pre.flushSync()
}
// 删除数据(指定文本删除,全部删除)
async delData(kdText?: string) {
const pre = preferences.getPreferencesSync(getContext(), { name: 'keywordStore' })
if (kdText) {
// 指定文本删除
// 1. 获取老数据 2 删除指定的文本(内存中的数据) 3 将数据写回到磁盘
const oldData = pre.getSync('keyword', []) as string[]
const index = oldData.findIndex(item => item === kdText)
// 对未找到的关键字不执行删除动作
if (index < 0) {
return
}
oldData.splice(index, 1)
pre.putSync('keyword', oldData)
pre.flushSync()
} else {
// 全部删除
pre.deleteSync('keyword')
pre.flushSync()
}
}
@Builder
itemBuilder(text: string) {
Row({ space: 20 }) {
Text(text)
.onClick(()=>{
this.isSearch = true
this.keyword = text
this.fsable = false
})
if (this.isdel) {
Text('x')
.height(30)
.onClick(async () => {
// 1. 调用删除方法
await this.delData(text)
// 2. 重新获取数据
this.kdList = await this.getData()
})
}
}
.margin({ right: 10, top: 10 })
.padding({
left: 15,
right: 15,
top: 5,
bottom: 5
})
.backgroundColor('rgba(0,0,0,0.05)')
.borderRadius(20)
}
onBackPress(): boolean | void {
Logger.debug('onBackPress')
this.startIntervalFlag = false
this.startIntervalFlag = true
return false // 使用原有的路由方式来回退
}
build() {
Navigation() {
Column({ space: 15 }) {
// 1. 搜索关键字
TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })// 回车的时候会触发
.onSubmit(async () => {
// AlertDialog.show({ message: this.keyword })
// 1. 将关键字保存到首选项中
await this.saveData(this.keyword)
// 2. 获取用户首选项数据回显在页面上
this.kdList = await this.getData()
this.isSearch = true
// AlertDialog.show({message:JSON.stringify('保存成功')})
})
.onClick(()=>{
this.isSearch = false
this.fsable = true
})
.focusable(this.fsable)
if (this.isSearch) {
// 使用搜索组件
Column(){
QuestionListComp({keyword:this.keyword,selfIndex:this.params.index})
}
} else {
// 2. 关键字列表
Row() {
Text('搜索记录').fontSize(20).fontWeight(800)
Row() {
if (this.isdel) {
Text('全部删除')
.onClick(async () => {
// AlertDialog.show({ message: '补上全部删除逻辑' })
// 1. 调用删除方法
await this.delData()
// 2. 重新获取数据
this.kdList = await this.getData()
})
Text(' | ')
Text('取消删除')
.onClick(() => {
this.isdel = false
})
} else {
Image($r('app.media.ic_common_delete'))
.height(28)
.onClick(() => {
this.isdel = true
})
}
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 3. 关键字列表
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.kdList, (item: string) => {
this.itemBuilder(item)
})
}
}
}
.padding(15)
}
.padding({ top: this.topHeight })
.titleMode(NavigationTitleMode.Mini)
.title('搜索页面')
}
}
【我的】- 功能流程
课程目标
- 完成编辑个人信息:① 上传头像 ② 修改昵称
- 完成累计学时统计
- 完成单词用法高亮、AVPlayer朗读单词功能
【我的-编辑个人信息页】-静态页
课程目标
- 迁移下面代码到ProfileEditPage.ets页面中
- 使用@StorageProp('user') 同步用户头像和昵称数据
import { promptAction } from '@kit.ArkUI'
import { iLoginUserModel } from '../models/AccountModel'
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(() => {
// 选择头像并上传this.pickerAvatar()
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({ left: (45), right: (45), top: (15), bottom: (15) })
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
【我的-编辑个人信息页】- 头像上传
课程目标
- 完成 利用picker api选择1张图片
- 完成利用 request.uploadFile 进行图片上传
- get请求
userInfo
接口刷新用户数据,更新AppStorage("user")中的用户缓存数据
【头像上传】- 使用photoAccessHelper选择一张图片
步骤:
- 实例化选择器参数(使用new PhotoSelectOptions())
- 实例化图片选择器 (使用newPhotoViewPicker() )
- 调用图片选择器的select方法传入选择器参数完成图片选取获得结果
import { iLoginUserModel } from '../models/datamodel'
// import { picker } from '@kit.CoreFileKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 1. 选择系统相册的图片
async selectImage(maxnum: number) {
// 1. 定义打开系统相册的相关参数
// const options = new picker.PhotoSelectOptions()
const options = new photoAccessHelper.PhotoSelectOptions()
options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
options.maxSelectNumber = maxnum
// 2. 打开和选择
let viewer = new photoAccessHelper.PhotoViewPicker()
const res = await viewer.select(options)
AlertDialog.show({message:JSON.stringify(res.photoUris,null,2)})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 选择头像并上传this.pickerAvatar()
// 文件的本地存储路径。
await this.selectImage(6)
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
【头像上传】- 拷贝选择图片到缓存目录
步骤:
- 用上下文获取当前应用的缓存目录-> getContext().cacheDir
- 利用Date.now()随机生成图片名字filename,扩展名为jpg
- 利用 fs.openSync方法打开图片,准备拷贝到缓存目录 ->const file = fs.openSync(uri, fs.OpenMode.READ_ONLY)
- 利用fs.copyFileSync(file.fd, fullPath) 拷贝文件到缓存目录
此时可以获得当前图片的一个缓存地址供reqeust.uploadFile使用:'internal://cache/' + filename
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
【头像上传】- 利用request.uploadFile 进行图片上传
上传接口文档
步骤:
- 准备好参数调用request.uploadFile()获得上传对象 uploader
'Content-Type': 'multipart/form-data'
- 给uploader对象注册progress事件,监听上传进度 requestRes.on("progress", (uploadedSize: number, totalSize: number)=>{})
- 给uploader对象注册fail事件,监听报错信息requestRes.on('fail', (taskStates) => {})
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
// 3. 头像上传
async upload(filename: string, type: string) {
let uploador = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${this.currentUser.token}`
},
files: [
{
filename: filename,
type: type,
name: 'file',
uri: 'internal://cache/' + filename
}
],
data: []
})
// 1.监控文件上传失败事件
// 不能监听所有异常
uploador.on('fail', (err) => {
// AlertDialog.show({ message: 'fail-->' + JSON.stringify(err, null, 2) })
Logger.error('头像上传失败', JSON.stringify(err))
})
// 2. 监控服务器响应回来的数据
uploador.on('headerReceive', (res) => {
// AlertDialog.show({ message: '完成-->' + JSON.stringify(res, null, 2) })
})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
// AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
// 3. 利用reqeust.uploadFile完成图片上传
await this.upload(fileInfo[0], fileInfo[1])
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
【头像上传】- 重新调用接口获取最新用户数据
步骤:
-
- get请求
userInfo
接口 重新获取用户数据 - 通过@StorageLink(''user")完成 用户头像字段avatar的修改
- get请求
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageLink('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
// 3. 头像上传
async upload(filename: string, type: string) {
let uploador = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${this.currentUser.token}`
},
files: [
{
filename: filename,
type: type,
name: 'file',
uri: 'internal://cache/' + filename
}
],
data: []
})
// 1.监控文件上传失败事件
// 不能监听所有异常
uploador.on('fail', (err) => {
// AlertDialog.show({ message: 'fail-->' + JSON.stringify(err, null, 2) })
Logger.error('头像上传失败', JSON.stringify(err))
})
// 2. 监控服务器响应回来的数据
uploador.on('headerReceive', async (res) => {
// AlertDialog.show({ message: '完成-->' + JSON.stringify(res, null, 2) })
// 这个方法一旦触发,那么服务器的头像已经上传完毕并且更新了
// 这是去重新获取https://api-harmony-teach.itheima.net/hm/userInfo中的头像地址就是我们上传以后的新的头像地址
let newUserInfo = await HdHttp.Get<object>('hm/userInfo')
this.currentUser.avatar = newUserInfo.data['avatar']
// AlertDialog.show({ message: JSON.stringify('老头像地址:'+this.currentUser.avatar +' 新的头像地址:' + newUserInfo.data['avatar']) })
})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
// AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
// 3. 利用reqeust.uploadFile完成图片上传
await this.upload(fileInfo[0], fileInfo[1])
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
【头像上传】- 上传百分比实时更新
业务分析:
- ✔️需要在上传的时候获取上传进度百分比(request.uploadFile的progress事件来完成 )
- 需要使用emiiter来将上传进度百分数字从ProfileEditPage.ets 传给 HdLoadingDialog.ets页面
问题:使用传统的响应式方式是无法向@CustomDialog组件实时更新数据的
解决:需要使用emitter来实时更新@CustomDialog组件中的变量,从而达到页面数据的实时更新
progress事件监听上传进度
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageLink('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
// 3. 头像上传
async upload(filename: string, type: string) {
let uploador = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${this.currentUser.token}`
},
files: [
{
filename: filename,
type: type,
name: 'file',
uri: 'internal://cache/' + filename
}
],
data: []
})
// 1.监控文件上传失败事件
// 不能监听所有异常
uploador.on('fail', (err) => {
// AlertDialog.show({ message: 'fail-->' + JSON.stringify(err, null, 2) })
Logger.error('头像上传失败', JSON.stringify(err))
})
// 2. 监控服务器响应回来的数据
uploador.on('headerReceive', async (res) => {
// AlertDialog.show({ message: '完成-->' + JSON.stringify(res, null, 2) })
// 这个方法一旦触发,那么服务器的头像已经上传完毕并且更新了
// 这是去重新获取https://api-harmony-teach.itheima.net/hm/userInfo中的头像地址就是我们上传以后的新的头像地址
let newUserInfo = await HdHttp.Get<object>('hm/userInfo')
this.currentUser.avatar = newUserInfo.data['avatar']
// AlertDialog.show({ message: JSON.stringify('老头像地址:'+this.currentUser.avatar +' 新的头像地址:' + newUserInfo.data['avatar']) })
})
// 3. 监控当前的上传进度
uploador.on('progress', (uploadedSize, totalSize) => {
// uploadedSize -> 当前已经上传的大小
// totalSize -> 要上传的总大小
Logger.info(uploadedSize.toString(), totalSize.toString())
let pnum = (uploadedSize / totalSize * 100).toFixed(0) // 计算出百分数 %
})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
// AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
// 3. 利用reqeust.uploadFile完成图片上传
await this.upload(fileInfo[0], fileInfo[1])
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
打开自定义弹窗
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
import { HdLoadingDialog } from '../components/HdLoadingDialog';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageLink('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 实例化自定义弹窗对象
dialog = new CustomDialogController({
builder: HdLoadingDialog({ message: '上传:' }),
customStyle: true
})
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
// 3. 头像上传
async upload(filename: string, type: string) {
// 打开上传提示弹窗
this.dialog.open()
let uploador = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${this.currentUser.token}`
},
files: [
{
filename: filename,
type: type,
name: 'file',
uri: 'internal://cache/' + filename
}
],
data: []
})
// 1.监控文件上传失败事件
// 不能监听所有异常
uploador.on('fail', (err) => {
// AlertDialog.show({ message: 'fail-->' + JSON.stringify(err, null, 2) })
Logger.error('头像上传失败', JSON.stringify(err))
// 关闭上传提示弹窗
this.dialog.close()
})
// 2. 监控服务器响应回来的数据
uploador.on('headerReceive', async (res) => {
// 关闭上传提示弹窗
this.dialog.close()
// AlertDialog.show({ message: '完成-->' + JSON.stringify(res, null, 2) })
// 这个方法一旦触发,那么服务器的头像已经上传完毕并且更新了
// 这是去重新获取https://api-harmony-teach.itheima.net/hm/userInfo中的头像地址就是我们上传以后的新的头像地址
let newUserInfo = await HdHttp.Get<object>('hm/userInfo')
this.currentUser.avatar = newUserInfo.data['avatar']
// AlertDialog.show({ message: JSON.stringify('老头像地址:'+this.currentUser.avatar +' 新的头像地址:' + newUserInfo.data['avatar']) })
})
// 3. 监控当前的上传进度
uploador.on('progress', (uploadedSize, totalSize) => {
// uploadedSize -> 当前已经上传的大小
// totalSize -> 要上传的总大小
Logger.info(uploadedSize.toString(), totalSize.toString())
let pnum = (uploadedSize / totalSize * 100).toFixed(0) // 计算出百分数 %
})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
// AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
// 3. 利用reqeust.uploadFile完成图片上传
await this.upload(fileInfo[0], fileInfo[1])
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
【新知识】- emitter核心api
课程目标
- 理解emitter核心api的作用和工作机制
emitter什么是?emitter主要提供发送和处理事件的能力,包括订阅事件(on)、发送事件(emit)、取消订阅(off)事件的功能。我们通过发送事件,来触发所有订阅的事件的执行。
应用场景:可以通过emitter向@CustomDialog组件来实时传递数据
【头像上传】- 使用emitter实现上传进度百分比更新
使用CustomDialogController 弹出正在上传中的提示,并且需要展示上传百分比
步骤:
- 在src/main/ets/pages/ProfileEditPage.ets中利用emitter发送事件并传递数据
- 在LoadingDialog.ets 中利用emitter 完成事件订阅emitter.on(),来接收上传进度数据,并在页面上更新上传进度数据
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { emitter, request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
import { HdLoadingDialog } from '../components/HdLoadingDialog';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageLink('user') currentUser: iLoginUserModel = {} as iLoginUserModel
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 实例化自定义弹窗对象
dialog = new CustomDialogController({
builder: HdLoadingDialog({ message: '上传:' }),
customStyle: true
})
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
// 3. 头像上传
async upload(filename: string, type: string) {
// 打开上传提示弹窗
this.dialog.open()
let uploador = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${this.currentUser.token}`
},
files: [
{
filename: filename,
type: type,
name: 'file',
uri: 'internal://cache/' + filename
}
],
data: []
})
// 1.监控文件上传失败事件
// 不能监听所有异常
uploador.on('fail', (err) => {
// AlertDialog.show({ message: 'fail-->' + JSON.stringify(err, null, 2) })
Logger.error('头像上传失败', JSON.stringify(err))
// 关闭上传提示弹窗
this.dialog.close()
})
// 2. 监控服务器响应回来的数据
uploador.on('headerReceive', async (res) => {
// 关闭上传提示弹窗
this.dialog.close()
// AlertDialog.show({ message: '完成-->' + JSON.stringify(res, null, 2) })
// 这个方法一旦触发,那么服务器的头像已经上传完毕并且更新了
// 这是去重新获取https://api-harmony-teach.itheima.net/hm/userInfo中的头像地址就是我们上传以后的新的头像地址
let newUserInfo = await HdHttp.Get<object>('hm/userInfo')
this.currentUser.avatar = newUserInfo.data['avatar']
// AlertDialog.show({ message: JSON.stringify('老头像地址:'+this.currentUser.avatar +' 新的头像地址:' + newUserInfo.data['avatar']) })
})
// 3. 监控当前的上传进度
uploador.on('progress', (uploadedSize, totalSize) => {
// uploadedSize -> 当前已经上传的大小
// totalSize -> 要上传的总大小
Logger.info(uploadedSize.toString(), totalSize.toString())
let pnum = (uploadedSize / totalSize * 100).toFixed(0) // 计算出百分数 %
// 使用emitter将pnum这个数据发送出去
emitter.emit({ eventId: 0 }, { data: { pstr: pnum + '%' } })
})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
// AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
// 3. 利用reqeust.uploadFile完成图片上传
await this.upload(fileInfo[0], fileInfo[1])
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: this.currentUser.nickName || '昵称' })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(() => {
// 修改昵称 this.updateNickName()
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}
/*
* 自定义弹窗有规则:
* 1. 必须有 @CustomDialog
* 2.里面使用 controller: CustomDialogController 定义一个固定的控制器对象
* */
import { emitter } from '@kit.BasicServicesKit'
import { Logger } from '../utils/Logger'
@CustomDialog
export struct HdLoadingDialog {
@Prop message: string = ''
controller: CustomDialogController
aboutToAppear(): void {
// 注册emitter的on事件来监听emit发送过来的数据
emitter.on({ eventId: 0 }, (rec) => {
let pstr = rec.data!['pstr'] as string
this.message = '上传:' + pstr
})
}
build() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
LoadingProgress().width(30).height(30).color('#fff')
if (this.message) {
Text(this.message).fontSize((14)).fontColor('#fff')
}
}
.width(150)
.height(50)
.padding(10)
.backgroundColor('rgba(0,0,0,0.5)')
.borderRadius(8)
}
}
【我的-编辑个人信息页】-昵称更新
课程目标
- post请求
userInfo/profile
接口(接口文档)完成用户昵称更新
import { iLoginUserModel } from '../models/datamodel'
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { emitter, request } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
import { HdHttp } from '../utils/request';
import { HdLoadingDialog } from '../components/HdLoadingDialog';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct ProfileEditPage {
// 获取登录用户数据
@StorageLink('user') currentUser: iLoginUserModel = {} as iLoginUserModel
@State nickName: string = this.currentUser.nickName
// 获取安全区域高度数据
@StorageProp("topHeight") topHeight: number = 0
// 实例化自定义弹窗对象
dialog = new CustomDialogController({
builder: HdLoadingDialog({ message: '上传:' }),
customStyle: true
})
// 1. 使用picker选择相册中的图片
async selectImage(maxnum: number) {
// 1.1 实例化选择参数
let opts = new picker.PhotoSelectOptions()
opts.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
opts.maxSelectNumber = maxnum
// 1.2 打开相册来选择照片返回(选择相册照片的数组)
let viewer = new picker.PhotoViewPicker()
let res = await viewer.select(opts)
return res.photoUris
}
// 2. 拷贝到应用程序缓存目录
async copyToCacheDir(photoImagePath: string) {
// 1. 使用openSync将相册中的图片加载到内存中得到内存的数字指向
let file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2. 使用copyFileSync完成图片拷贝到应用程序缓存中
let dir = getContext().cacheDir
let type = 'jpg'
let filename = Date.now() + '.' + type
let fullpath = dir + '/' + filename
fs.copyFileSync(file.fd, fullpath)
// 3. 返回文件名和文件的扩展名
// ['123123234.jpg','jpg']
return [filename, type]
}
// 3. 头像上传
async upload(filename: string, type: string) {
// 打开上传提示弹窗
this.dialog.open()
let uploador = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${this.currentUser.token}`
},
files: [
{
filename: filename,
type: type,
name: 'file',
uri: 'internal://cache/' + filename
}
],
data: []
})
// 1.监控文件上传失败事件
// 不能监听所有异常
uploador.on('fail', (err) => {
// AlertDialog.show({ message: 'fail-->' + JSON.stringify(err, null, 2) })
Logger.error('头像上传失败', JSON.stringify(err))
// 关闭上传提示弹窗
this.dialog.close()
})
// 2. 监控服务器响应回来的数据
uploador.on('headerReceive', async (res) => {
// 关闭上传提示弹窗
this.dialog.close()
// AlertDialog.show({ message: '完成-->' + JSON.stringify(res, null, 2) })
// 这个方法一旦触发,那么服务器的头像已经上传完毕并且更新了
// 这是去重新获取https://api-harmony-teach.itheima.net/hm/userInfo中的头像地址就是我们上传以后的新的头像地址
let newUserInfo = await HdHttp.Get<object>('hm/userInfo')
this.currentUser.avatar = newUserInfo.data['avatar']
// AlertDialog.show({ message: JSON.stringify('老头像地址:'+this.currentUser.avatar +' 新的头像地址:' + newUserInfo.data['avatar']) })
})
// 3. 监控当前的上传进度
uploador.on('progress', (uploadedSize, totalSize) => {
// uploadedSize -> 当前已经上传的大小
// totalSize -> 要上传的总大小
Logger.info(uploadedSize.toString(), totalSize.toString())
let pnum = (uploadedSize / totalSize * 100).toFixed(0) // 计算出百分数 %
// 使用emitter将pnum这个数据发送出去
emitter.emit({ eventId: 0 }, { data: { pstr: pnum + '%' } })
})
}
build() {
Navigation() {
Stack() {
List() {
ListItem() {
Row() {
Text('头像')
// 回显用户头像
Image(this.currentUser.avatar || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 1. 使用picker选择相册中的图片
let urls = await this.selectImage(1)
// AlertDialog.show({ message: JSON.stringify(urls[0]) })
// 2. 利用fs将相册图片拷贝到缓存目录中
let fileInfo = await this.copyToCacheDir(urls[0])
// AlertDialog.show({ message: JSON.stringify(fileInfo, null, 2) })
// 3. 利用reqeust.uploadFile完成图片上传
await this.upload(fileInfo[0], fileInfo[1])
})
}.width('100%').height((60)).justifyContent(FlexAlign.SpaceBetween)
}
ListItem() {
Row() {
Text('昵称')
// 回显用户昵称
TextInput({ text: $$this.nickName })
.textAlign(TextAlign.End)
.layoutWeight(1)
.padding(0)
.height((60))
.backgroundColor(Color.Transparent)
.borderRadius(0)
.onSubmit(async () => {
//1. 调用接口修改昵称
// https://api-harmony-teach.itheima.net/hm/userInfo/profile
// 传参:nickName
await HdHttp.Post<object>('hm/userInfo/profile', new Object({
nickName: this.nickName
}))
// 2. 将最新的昵称同步给 currentUser中的nickName
this.currentUser.nickName = this.nickName
promptAction.showToast({ message: '昵称修改成功 ' })
})
}.width('100%').height(60).justifyContent(FlexAlign.SpaceBetween)
}
}
.width('100%')
.height('100%')
.padding({
left: (45),
right: (45),
top: (15),
bottom: (15)
})
.divider({ strokeWidth: 0.5, color: '#f5f5f5' })
}.width('100%')
.height('100%')
}
.padding({ top: this.topHeight + 10 })
.title('完善个人信息')
.titleMode(NavigationTitleMode.Mini)
.mode(NavigationMode.Stack)
.linearGradient({
colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]
})
}
}