本人是 JS 攻城狮,Java 属于初学状态,写的不好的地方还请多多指教!

为什么决定开发一个星座运势服务卡片?主要有以下几点:
老年人和女性都比较关心运势,卡片类型刚好能够清晰展示运势,同时也不需要太多操作,与服务卡片理念契合(弱交互、强服务)。
聚合 API 上刚好有相应接口,可以直接调用,比较方便。
实现效果

https://developer.harmonyos.com/cn/docs/documentation/doc-guides/create_new_project-0000001053342414
https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-components-common-atomic-layout-0000001062070665


创建项目
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/installation_process-0000001071425528
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/hello_world-0000001054516888


填写创建的工程信息:
Project Type:工程的类型。
Service:原子化服务。原子化服务调试、运行时,在设备桌面上没有应用图标、免安装的。
Application:传统方式需要安装的应用。
Compatible API Version:兼容的 SDK 最低版本。
Show in Service Center:是否在服务中心露出。如果 Project Type 为 Service,则会同步创建一个 2*2 的服务卡片模板。
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/create_new_project-0000001053342414


卡片 JS UI 部分
①hml 部分
布局如下:

代码如下:
<div class="normal_container" >
<!-- 2X2 2X4 布局-->
<div class="container_top" @click="clickToApp" >
<!-- 2X2 布局-->
<div class="top_left">
<div class="left_item">
<text class="left_item_text conName">
{{ conName }}
</text>
</div>
<div class="left_item">
<text class="left_item_text conDate">
{{ conDate }}
</text>
</div>
<div class="conAll left_item">
<div class="s_score">
<text class="left_item_text label_2X2">
综合
</text>
<image class="s_star_2X2" for="{{ conAllStarY }}" src="/common/starY.png"></image>
<image class="s_star" for="{{ conAllStarN }}" src="/common/starN.png"></image>
</div>
</div>
<div class="conPari left_item">
<text class="left_item_text" >
速配星座
</text>
<text class="left_item_text conPari_text">
{{ conPari }}
</text>
</div>
</div>
<!-- 2X4 布局-->
<div class="top_right">
<image src="{{ imgSrc }}" class="con_image" ></image>
</div>
</div>
<!-- 4X4 布局半部分-->
<div class="container_bottom" @click="clickToApp" >
<div class="list_item item_line_top" >
<div class="s_score">
<text class="label">
爱情
</text>
<image class="s_star" for="{{ conLoveStarY }}" src="/common/starY.png"></image>
<image class="s_star" for="{{ conLoveStarN }}" src="/common/starN.png"></image>
</div>
<div class="s_score" style="margin-left: 40px;" >
<text class="label">
理财
</text>
<image class="s_star" for="{{ conMoneyStarY }}" src="/common/starY.png"></image>
<image class="s_star" for="{{ conMoneyStarN }}" src="/common/starN.png"></image>
</div>
</div>
<div class="list_item">
<div class="s_score">
<text class="label">
工作
</text>
<image class="s_star" for="{{ conWorkStarY }}" src="/common/starY.png"></image>
<image class="s_star" for="{{ conWorkStarN }}" src="/common/starN.png"></image>
</div>
<div class="s_score" style="margin-left: 40px;" >
<text class="label">
健康
</text>
<image class="s_star" for="{{ conHealthStarY }}" src="/common/starY.png"></image>
<image class="s_star" for="{{ conHealthStarN }}" src="/common/starN.png"></image>
</div>
</div>
<div class="item_line_bottom" >
<div class="s_score">
<text class="label">
幸运数字
</text>
<text class="score_text">
{{ conNumber }}
</text>
</div>
<div class="s_score" style="margin-left: 78px;" >
<text class="label">
幸运颜色
</text>
<text class="score_text">
{{ conColor }}
</text>
</div>
</div>
<div class="list_item_more" >
<div class="list_item_box1">
<text class="item_title_text">
综合概述
</text>
</div>
<div class="list_item_box2">
<text class="item_sub_text">
{{ conSummary }}
</text>
</div>
</div>
</div>
</div>
开发过程的问题如下:
hml 文本文字必须写在 text 标签中:
<text>文字</text>
hml 动态数据不可以一下,写两个:
<text >// 只会渲染第一个
{{ conName }} {{ conData }}
</text>
<text > // 不支持,直接当文本处理
{{ conName + conDate }}
</text>
<div if="{{ flag - 1}}">
<div if="{{ !flag}}">
list list-item 标签中无法触发点击事件:
<list @click="handle"> // 无法触发
<list-item>
</list>
②css 部分
代码部分:
.normal_container {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
background: linear-gradient(to right, #6a11cb 0%, #667eea 100%);
}
.container_top{
display: flex;
}
/*2X2 布局样式 */
.top_left{
width: 165px;
height: 150px;
display-index: 3;
display: flex;
flex-direction: column;
padding: 12px 10px;
}
.left_item{
margin-top: 8px;
}
.left_item_text{
font-size: 16px;
color: #ffffff;
}
.conDate{
font-size: 14px;
}
.conPari_text{
font-size: 18px;
margin-left: 6px;
}
.s_star_2X2{
width: 16px;
height: 16px;
margin-left: 2px;
}
.label_2X2{
margin-right: 8px;
font-size: 16px;
color: #fff;
}
/*2X4 布局样式右 */
.top_right{
width: 150px;
height: 150px;
display-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.con_image{
width: 120px;
height: 120px;
border-radius: 70px;
border: 4px solid #fdfbfb;
}
/*4X4 布局样式下 */
.container_bottom{
width: 100%;
display-index: 1;
padding: 0px 10px;
display: flex;
flex-direction: column;
}
.list_item{
margin-bottom: 6px;
}
.list_item_more{
display: flex;
flex-direction: column;
}
.list_item_box2{
width: 100%;
height: 80px;
margin-top: -5px;
}
.item_title_text{
font-size: 18px;
color: #fff;
padding: 8px 0 0px;
}
.item_sub_text{
font-size: 14px;
color: #fff;
/*配套使用*/
text-overflow: ellipsis;
max-lines: 3;
/*配套使用*/
line-height: 22px;
}
.item_line_bottom{
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
.item_line_top{
border-top: 1px solid #eee;
padding-top: 8px;
}
/* 通用样式 */
/* 评分星 */
.s_score{
display: flex;
align-items: center;
}
.s_star{
width: 14px;
height: 14px;
margin-left: 2px;
}
.label{
margin-right: 8px;
font-size: 14px;
color: #fff;
}
.score_text{
font-size: 14px;
color: #fff;
margin-left: 6px;
}
遇到的问题:
(1)css boder-raduis 不支持百分比
(2)css 不支持嵌套:
.divClass .text{} // 不支持
(3)css font-size color 不支持继承,font-size color 等文本必须写在 text 标签内,写在父级就不会相应。
(4)css 不支持大部分伪类选择器 (:nth-child(n)、:before、:after)。
③json 部分
{
"data": {
"imgSrc": "/common/constellation/天蝎.jpg",
"conName": "天蝎座",
"conDate": "2021年07月27日",
"conPari": "金牛座",
"conNumber": 1,
"conColor": "古铜色",
"conSummary": "有些思考的小漩涡,可能让你忽然的放空,生活中许多的细节让你感触良多,五味杂陈,常常有时候就慢动作定格,想法在某处冻结停留,陷入一阵自我对话的沉思之中,这个时候你不喜欢被打扰或询问,也不想让某些想法曝光,个性变得有些隐晦。",
"conAllStarY": [],
"conAllStarN": [],
"conLoveStarY": [],
"conLoveStarN": [],
"conMoneyStarY": [],
"conMoneyStarN": [],
"conWorkStarY": [],
"conWorkStarN": [],
"conHealthStarY": [],
"conHealthStarN": []
},
"actions": {
"clickToApp": {// 跳转事件,跳转到相应内容页
"action": "router",
"abilityName": "com.example.fortune.MainAbility",
"params": {
"message": ""// 点击传递数据
}
}
}
}
遇到的问题:json 中定义数组,如“conAllStarY”: [“Y”, “Y”],在 java 中动态修改它为[“Y”, “Y”, “Y”, “Y”],则实际渲染出来有问题。
打印出来的数组正确,for 循环出来的数组只有两位。最后将初始数组改为空数组才解决了这个 bug。
<text>{{ conWorkStarY }}</text>
<text for="{{conWorkStarY}}" >a</text>
结果:
[“Y”, “Y”, “Y”, “Y”]aa
④java 代码逻辑
公共类:
public class UpdataJSData {
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, UpdataJSData.class.getName());
// 数据请求
public static void getJsonData(String constellation, ZZRCallBack.CallBackString callBackName ) {
String url = "http://web.juhe.cn:8080/constellation/getAll";
Map<String,String> map = new HashMap<>();
map.put("key", "9d075905a3e1c33224ac5bb42f4de458");
map.put("consName", constellation);
map.put("type", "today");
ZZRHttp.get(url, map, callBackName);
}
// 将请求到的数据更新到fortuneData中,并返回回去
public static ZSONObject setJson(String response) {
ZSONObject fortuneData = ZSONObject.stringToZSON(response);
int error_code = fortuneData.getIntValue("error_code");
if (error_code == 0) {
HiLog.info(TAG,"返回的数据正确,设置js中的json数据");
String[][] all = formatConversion(fortuneData.getString("all"));
String[][] love = formatConversion(fortuneData.getString("love"));
String[][] money = formatConversion(fortuneData.getString("money"));
String[][] work = formatConversion(fortuneData.getString("work"));
String[][] health = formatConversion(fortuneData.getString("health"));
HiLog.info(TAG,health.toString());
fortuneData.put("imgSrc", "/common/constellation/" + fortuneData.getString("name").toString().substring(0, 2) + ".jpg");
fortuneData.put("conName", fortuneData.getString("name"));// 星座名称
fortuneData.put("imgSrc", "/common/constellation/" + fortuneData.getString("name").toString().substring(0, 2) + ".jpg");// 星座图片路径拼接
fortuneData.put("conName", fortuneData.getString("name"));// 星座名称
fortuneData.put("conDate", fortuneData.getString("datetime"));// 当前事件
fortuneData.put("conPari", fortuneData.getString("QFriend"));// 速配星座
fortuneData.put("conNumber", fortuneData.getString("number"));// 幸运数组
fortuneData.put("conColor", fortuneData.getString("color"));// 幸运颜色
fortuneData.put("conSummary", fortuneData.getString("summary"));// 描述信息
fortuneData.put("conAllStarY", all[0]);// 综合评分 标记星
fortuneData.put("conAllStarN", all[1]);// 综合评分 未标记星
fortuneData.put("conLoveStarY", love[0]);// 爱情评分 标记星
fortuneData.put("conLoveStarN", love[1]);// 爱情评分 未标记星
fortuneData.put("conMoneyStarY", money[0]);// 理财评分 标记星
fortuneData.put("conMoneyStarN", money[1]);// 理财评分 未标记星
fortuneData.put("conWorkStarY", work[0]);// 工作评分 标记星
fortuneData.put("conWorkStarN", work[1]);// 工作评分 未标记星
fortuneData.put("conHealthStarY", health[0]);// 健康评分 标记星
fortuneData.put("conHealthStarN", health[1]);// 健康评分 未标记星
} else {
HiLog.info(TAG,"请求出错,code=" + error_code);
}
return fortuneData;
}
// 对分数数据进行封装,转换为二维数组
private static String[][] formatConversion(String star) {
int myData = Integer.parseInt(star);
int myDataY = myData / 20;
int myDataN = 5 - myDataY;
HiLog.info(TAG,"star=" + star + "myDataY=" + myDataY + " myDataN=" + myDataN);
String myListY[] = new String[myDataY];
String myListN[] = new String[myDataN];
for(int i = 0; i < myDataY; i++) {
myListY[i] = "Y";
}
for(int i = 0; i < myDataN; i++) {
myListN[i] = "N";
}
String[][] myList = {myListY, myListN};
return myList;
}
}
数据初始化:创建卡片时,调用 MainAbility 中 onCreateForm 方法。
实现代码:
protected ProviderFormInfo onCreateForm(Intent intent) {
HiLog.info(TAG, "更新数据:onCreateForm");
ProviderFormInfo providerFormInfo = new ProviderFormInfo();
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
formController = (formController == null) ? formControllerManager.createFormController(formId,
formName, dimension) : formController;
if (formController == null) {
HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
return null;
}
// 调用公共类中获取数据,传入一个callback方法
UpdataJSData.getJsonData("处女座", new ZZRCallBack.CallBackString() {
public void onFailure(int code, String errorMessage) {
//http访问出错了,此部分内容在主线程中工作;
//可以更新UI等操作,请不要执行阻塞操作。
HiLog.info(TAG,"更新数据:数据请求失败"+code+","+errorMessage);
}
public void onResponse(String response) {
//http访问成功,此部分内容在主线程中工作;
//可以更新UI等操作,但请不要执行阻塞操作。
HiLog.info(TAG,"更新数据:数据请求成功");
ZSONObject fortuneData = setJson(response);
try {
HiLog.info(TAG,"更新数据");
updateForm(formId, new FormBindingData(fortuneData));
} catch (FormException e) {
HiLog.info(TAG,"更新数据失败");
}
}
});
return providerFormInfo;
}
采用定时刷新,星座运势不需要周期性刷新。
config.js 中设置定时刷新的时间:
"forms": [
{
"scheduledUpdateTime": "10:12", // 定时刷新时间点
"updateEnabled": true, // 是否周期性刷新
"updateDuration": 0,
...
}
]
定时刷新会触发 widget->WidgetImpl 文件的 updateFormData 方法:
public void updateFormData(long formId, Object... vars) {
HiLog.info(TAG, "更新数据");
UpdataJSData.getJsonData("双鱼座", new ZZRCallBack.CallBackString() {
public void onFailure(int code, String errorMessage) {
//http访问出错了,此部分内容在主线程中工作;
//可以更新UI等操作,请不要执行阻塞操作。
HiLog.info(TAG,"数据请求失败"+code+","+errorMessage);
}
public void onResponse(String response) {
//http访问成功,此部分内容在主线程中工作;
//可以更新UI等操作,但请不要执行阻塞操作。
HiLog.info(TAG,"数据请求成功");
ZSONObject fortuneData = setJson(response);
try {
HiLog.info(TAG,"更新数据");
((MainAbility)context).updateForm(formId, new FormBindingData(fortuneData)); //调用MainAbility的方法updateForm(),并将formBindingData作为第二个实参
} catch (FormException e) {
HiLog.info(TAG,"更新数据失败");
}
}
});
}
内容页部分
这边就贴一下 js 代码:
import http from '@ohos.net.http';// 导入http
export default {
data: {
conName: "天蝎座",
options: {
key: "9d075905a3e1c33224ac5bb42f4de458",
type: "today"
},
url: "",
conData: {
name: "天蝎座",
datetime: "2021年7月28号",
img: "/common/images/constellation/天蝎.jpg",
QFriend: "金牛",
number: "4",
color: "粉色",
summary: "有些思考的小漩涡,可能让你忽然的放空,生活中许多的细节让你感触良多,五味杂陈,常常有时候就慢动作定格,想法在某处冻结停留,陷入一阵自我对话的沉思之中,这个时候你不喜欢被打扰或询问,也不想让某些想法曝光,个性变得有些隐晦。"
},
responseData: {}
},
onInit() {
this.getHttp();// 发送请求
},
// 输入框改变触发
changeCon(v) {
this.conName = v.value;
console.log(v.value+ 'change conName');
},
getHttp() {
this.url = `http://web.juhe.cn:8080/constellation/getAll?key=${this.options.key}&consName=${this.conName}&type=${this.options.type}`
let httpRequest = http.createHttp();
httpRequest.request(this.url, {
method: 'GET'
},(err, data) => {
if (err == null) {
let conData = JSON.parse(data.result);
console.log('Result:' + data.result);
if (conData.error_code == 0) {
conData.img = `/common/images/constellation/${(conData.name).slice(0, 2)}.jpg`
conData.allY = this.getNewArr(conData.all, "Y");
conData.allN = this.getNewArr(conData.all, "N");
conData.healthY = this.getNewArr(conData.health, "Y");
conData.healthN = this.getNewArr(conData.health, "N");
conData.loveY = this.getNewArr(conData.love, "Y");
conData.loveN = this.getNewArr(conData.love, "N");
conData.moneyY = this.getNewArr(conData.money, "Y");
conData.moneyN = this.getNewArr(conData.money, "N");
conData.workY = this.getNewArr(conData.work, "Y");
conData.workN = this.getNewArr(conData.work, "N");
this.conData = conData;
} else {
console.error("请求失败");
}
} else {
console.log('error:' + JSON.stringify(err));
}
});
},
// 数字转化为数组不然for循环不出来
getNewArr(score, type){
score = parseInt(parseInt(score) / 20);
let arr = [];
if (type === "N") {
console.log('type' + type)
score = 5 - score;
for (var i = 0; i < score; i++) {
arr.push(type);
}
} else {
for (var i = 0; i < score; i++) {
arr.push(type);
}
}
return arr;
}
}
hml 中 for 循环时,最好绑定 tid,不然会复用。
搜索框的 value 不是双向绑定的,这边需要监听个 change 事件,来改变 value。
js 部分数据请求时,采用 api6,需要自己更改配置设置为兼容 api6。
总结
onStart:主要做一些 js 方事件处理。
onCreateForm:创建卡片的时候需要。
updateFormData:定时刷新和周期性刷新会走到这里
源码地址:
https://gitee.com/chinasoft6_ohos/forturne
一周年庆 抽奖活动

关注鸿蒙技术社区订阅号,转发此图文至朋友圈,公众号回复转发的截图,抽鸿蒙新款 matepad11、价值 399、299 元的鸿蒙盲盒和 2000ml 健康随身杯!
回复“周年庆”抽奖
👇

点“阅读原文”了解更多




