first commit

This commit is contained in:
chorblack 2025-04-26 22:39:35 +08:00
commit 314efc7ff3
19 changed files with 1789 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
.DS_Store
node_modules
/dist
/build
/logs
# sign
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
.ide
.preview.json

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
package.json
manifest.json
README.md
# assets/js
src/assets/js/*.js

36
README.md Normal file
View File

@ -0,0 +1,36 @@
# 手表示例模版
## 文件结构
```
├── sign # 存储 rpk 包签名模块(须自行生成);
│ ├── certificate.pem # 证书文件
│ └── private.pem # 私钥文件
└── src
│ ├── assets # 公用的资源(images/styles/字体...)
│ │ ├──images # 存储 png/jpg/svg 等公共图片资源
│ │ └──styles # 存放 less/css/sass 等公共样式资源
│ ├── pages # 统一存放项目页面级代码
│ ├── app.ux # 应用程序代码的入口文件
│ ├── manifest.json # 配置蓝河应用基本信息
│ └── components # 存放蓝河应用组件
└── package.json # 定义项目需要的各种模块及配置信息
```
### 模版说明
- `Demo` 页面:示例页面;
- `DemoDetail`页面:详情页面;
## 如何使用
- **内置样式处理方案**;「蓝河应用」支持 `sass` 的预编译;这里采取 [dart sass](https://sass-lang.com/documentation) 方案,并内置了部分变量,以及常用混合方法,使得可以轻松开启样式编写、复用、修改等;
- **添加新增页面命令脚本**;如果需要新建页面,只需运行:`yarn gen YourPageName` ,当然,也可以根据需要,自行定定制模板:*/command/gen/template.ux*
- **集成 [Prettier](https://prettier.io/)**;在检测代码中潜在问题的同时,统一团队代码规范、风格(`js``less``scss`等),从而促使写出高质量代码,以提升工作效率(尤其针对团队开发)
## 内置命令
| 命令 | 描述 | 备注 |
|---|---|---|
| `yarn gen ` | 新增「蓝河应用」页面 | 助你高效生成页面,模版可自定义,推荐 ✓|
| `yarn prettier` | 一键美化代码(js/css/less/ux) | 实在是团队开发好帮手,推荐 ✓ |

17
jsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"noImplicitAny": false,
"strict": true,
"noUnusedLocals": true,
"noImplicitThis": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"checkJs": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "talk",
"version": "1.1.0",
"packageManager": "pnpm@7.1.0",
"description": "Watch Template",
"scripts": {
"gen": "node ./scripts/gen/index.js",
"precommit-msg": "echo '🚧 start pre-commit checks ...' && exit 0",
"prettier": "node ./scripts/selfCloseInputTag.js && prettier --write \"src/**/*.{js,ux,less,scss,css}\""
},
"devDependencies": {
"colors": "^1.4.0",
"husky": "^4.3.0",
"lint-staged": "^10.5.1",
"prettier": "^1.15.3",
"prettier-plugin-ux": "^0.3.0"
},
"prettier": {
"singleQuote": true,
"semi": false,
"printWidth": 80,
"proseWrap": "never",
"tabWidth": 2
},
"husky": {
"hooks": {
"pre-commit": "yarn run precommit-msg && lint-staged"
}
},
"lint-staged": {
"**/**.{ux,js,json,pcss,md,vue}": [
"prettier --write",
"git add"
]
},
"keywords": [
"多终端应用",
"手表示例",
"手表模版"
]
}

1013
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

87
scripts/gen/index.js Normal file
View File

@ -0,0 +1,87 @@
/**
* @desc: gen script commandmake a new page generated by one click.
* @author: nicejade
*/
const fs = require('fs')
const path = require('path')
const colors = require('colors')
const newFolderName = process.argv[2]
String.prototype.firstUpperCase = function() {
return this.replace(/\b(\w)/g, $1 => {
return $1.toLowerCase()
})
}
const resolve = dir => {
return path.join(__dirname, '../..', dir)
}
const successExecPrint = msg => {
console.log(
colors.green(``) +
colors.cyan(`${msg} `) +
colors.green('task has been successfully executed.')
)
}
function createNewPage(newFolderPath) {
const mReg = new RegExp('@PAGE_CLASS_NAME', 'g')
const pageContent = fs.readFileSync(`${__dirname}/template.ux`, 'UTF-8')
const rootClassName = newFolderName
.firstUpperCase()
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
const newContent = pageContent.replace(mReg, rootClassName)
fs.mkdirSync(newFolderPath, 0777)
fs.writeFile(`${newFolderPath}/index.ux`, newContent, error => {
if (error) throw `Something went wrong: ${error}`
})
successExecPrint('Create New Page')
}
function saveRouter2Manifest() {
const manifestPath = resolve('/src/manifest.json')
let manifestConf = fs.readFileSync(manifestPath, 'UTF-8')
manifestConf = JSON.parse(manifestConf)
const routerPages = manifestConf.router.pages
routerPages[`pages/${newFolderName}`] = {
component: 'index'
}
manifestConf = JSON.stringify(manifestConf, null, 2)
fs.writeFile(manifestPath, manifestConf, error => {
if (error) throw `Something went wrong[@saveRouter2Manifest]: ${error}`
})
successExecPrint('Save Router Into Manifest')
}
function main() {
if (!newFolderName) {
return console.warn(
`⚠️ Please enter the name of the page you want to create.`.underline.red
)
}
const folderNameReg = /^[A-Z][[A-Za-z0-9]+$/
if (!folderNameReg.test(newFolderName)) {
return console.warn(
`⚠️ Please enter the standard Folder name. Eg: XyzAbcde.`.underline.red
)
}
const newFolderPath = path.join(__dirname, `../../src/pages/${newFolderName}`)
const isExist = fs.existsSync(newFolderPath)
if (isExist) {
return console.warn(
`⚠️ ${newFolderName} already exists in the /src/pages/ directory.`
.underline.red
)
}
createNewPage(newFolderPath)
saveRouter2Manifest()
}
main()

27
scripts/gen/template.ux Normal file
View File

@ -0,0 +1,27 @@
<template>
<div class="wrapper">
<text class="title">{{ title }}</text>
</div>
</template>
<script>
export default {
data: {
title: '欢迎体验多终端应用开发'
},
onInit() {}
}
</script>
<style lang="scss">
@import './../../assets/styles/style.scss';
.wrapper {
@include flex-box-mixins(column, center, center);
.title {
font-size: 8 * $size-factor;
text-align: center;
color: $black;
}
}
</style>

View File

@ -0,0 +1,36 @@
/**
* @file: selfCloseInputTag.js
* @desc: 遍历指定目录下 .ux 文件将其中 input 标签由 <input **></input> 转换为 <input ** />
* @date: 2019-01-23
*/
const fs = require('fs')
const path = require('path')
const codePath = './src/'
const main = codePath => {
const traversing = cpath => {
const files = fs.readdirSync(cpath)
files.forEach(fileName => {
const fPath = path.join(cpath, fileName)
const stats = fs.statSync(fPath)
stats.isDirectory() && traversing(fPath)
stats.isFile() && fPath.endsWith('.ux') && matchAndReplace(fPath)
})
}
traversing(codePath)
}
const matchAndReplace = path => {
const pageContent = fs.readFileSync(path, 'UTF-8')
const newContent = pageContent.replace(
/(<)([\s]*?)(input\b[^\/]*?)>[\s\S]*?<\/input>/gm,
'$1$3 />'
)
fs.writeFile(path, newContent, error => {
if (error) throw `Something went wrong: ${error}`
})
}
main(codePath)

9
src/app.ux Normal file
View File

@ -0,0 +1,9 @@
<script>
/**
* 应用级别的配置,供所有页面公用
*/
export default {
onCreate() {}
}
</script>

BIN
src/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View File

@ -0,0 +1,5 @@
@mixin flex-box-mixins($direction: row, $justify: center, $align-items: center) {
flex-direction: $direction;
justify-content: $justify;
align-items: $align-items;
}

View File

@ -0,0 +1,2 @@
@import './variables.scss';
@import './mixins.scss';

View File

@ -0,0 +1,9 @@
$brand: #09ba07;
$white: #ffffff;
$black: #000000;
$grey: #9393aa;
$red: #fa0101;
$green: #ffff00;
$size-factor: 5px;

36
src/manifest.json Normal file
View File

@ -0,0 +1,36 @@
{
"package": "com.chorblack.talk",
"name": "talk",
"versionName": "1.0.0",
"versionCode": 1,
"appCategory": [
"other"
],
"icon": "/assets/images/logo.png",
"features": [
{
"name": "blueos.app.appmanager.router"
}
],
"deviceTypeList": [
"watch",
"watch-square"
],
"config": {
"designWidth": 466
},
"router": {
"entry": "pages/Demo",
"pages": {
"pages/Demo": {
"component": "index"
},
"pages/DemoDetail": {
"component": "index"
}
}
},
"display": {
"backgroundColor": "#ffffff"
}
}

411
src/pages/Demo/index.ux Normal file
View File

@ -0,0 +1,411 @@
<template>
<scroll
class="wrapper"
scrollbar="{{true}}"
bounces="{{true}}"
scroll-y="{{true}}"
id="wrapperbox">
<scroll
id="msgbox"
bounces="false"
scroll-y="true"
@scrolltop="scrolltopHandler"
@scrollbottom="scrollbottomHandler"
scroll-bottom="{{scrollbottom}}"
class="message-pannel">
<div
class="loading-wrap"
if="{{showLoading}}"
style="height:{{loadingHeight}}px">
<vw-loading size="48" class="loading"></vw-loading>
</div>
<div class="message" for="{{msgList}}">
<div class="message-item our" if="{{$item.side==='our'}}">
<text class="txt txt-our" if="{{$item.message}}">
{{ $item.message }}
</text>
<image class="img our" if="{{$item.img}}" src="{{$item.img}}"></image>
</div>
<div class="message-item other" else>
<text class="txt txt-other" if="{{$item.message}}">
{{ $item.message }}
</text>
<image
class="img other"
if="{{$item.img}}"
src="{{$item.img}}"></image>
</div>
</div>
<div id="item-arrow" class="message-item-arrow" style="height: 184px">
<image
src="../../assets/images/message-arrow.png"
class="message-arrow"></image>
</div>
</scroll>
<scroll scroll-y="true" class="reply-pannel">
<div class="message-item-arrow" style="height: 124px">
<image
src="../../assets/images/reply-arrow.png"
class="reply-arrow"></image>
</div>
<div class="input-box">
<input
for="{{replyMsg}}"
class="btn"
type="button"
value="{{$item.value}}"
@click="reply($item.text)" />
</div>
</scroll>
</scroll>
</template>
<script>
let num = 1
let a, b, c
export const listenMsg = (callback) => {
a = setInterval(() => {
const msg = '对方发来消息' + num++
console.log('msg', msg)
callback && callback(msg)
}, 5000)
return a
}
let histroy = [
{
side: 'other',
message: `借我500块钱!`,
},
{
side: 'other',
message: '急用!',
},
{
side: 'other',
message: '收到回复!',
},
{
side: 'our',
message: '🧧请查收',
},
{
side: 'other',
message: '我们来念诗吧',
},
{
side: 'our',
message: '好啊',
},
{
side: 'other',
message: '天苍苍,野茫茫,风吹草地,现牛羊',
},
{
side: 'our',
message: '床前明月光,疑是地上霜,举头望明月,低头思故乡',
},
]
export const getHistroyMsg = (callback) => {
const arr = histroy.splice(0, 4)
c = setTimeout(() => {
callback(arr)
}, 1000)
return c
}
export const fastReplyMsg = [
{
value: '语音',
text: '((( 15s',
},
{
value: '表情',
text: '😄',
},
{
value: 'OK',
text: 'OK',
},
{
value: '好的',
text: '好的',
},
{
value: '是的',
text: '是的',
},
{
value: '我在路上',
text: '我在路上',
},
{
value: '我到了',
text: '我到了',
},
{
value: '现在忙',
text: '现在忙,过会儿回复',
},
]
export const msgList = [
{
side: 'our',
message: '新年快乐😄',
},
{
side: 'other',
message: '新年快乐!',
},
{
side: 'our',
message: '开工大吉',
},
{
side: 'other',
message: '开工大吉!!',
},
{
side: 'our',
message: '最近怎么样了啊',
},
{
side: 'other',
message: '最近很好',
},
{
side: 'our',
message: '那就好',
},
{
side: 'our',
message: '好的',
},
{
side: 'our',
message: '你借我的钱什么时候还',
},
{ side: 'other', img: '../../assets/images/message-arrow.png' },
{
side: 'our',
message: '还在吗😊',
},
]
let loadHistroyMsgTimeId = -1
let listenMsgTimeId = -1
let getHistroyMsgTimeId = -1
export default {
data: {
showLoading: false,
scrollbottom: '0',
clientHeight: 750,
loadingHeight: 100,
msgListRectHeight: -1,
msgList: msgList,
replyMsg: fastReplyMsg,
},
onShow() {
//listenMsgTimeId = listenMsg(this.otherPushMsg)
},
onDestroy() {
clearTimeout(loadHistroyMsgTimeId)
clearTimeout(getHistroyMsgTimeId)
clearInterval(listenMsgTimeId)
clearInterval(a)
clearTimeout(b)
clearTimeout(c)
},
async otherPushMsg(msg) {
const bFlag = await this.isArrowBottom()
this.pushMsg(msg, 'other')
this.nextTick(() => {
if (bFlag) {
this.$element('msgbox').scrollBy({
top: 10000,
left: 0,
behavior: 'smooth',
})
}
})
},
/**
* 模拟 nextTick 功能
*/
async nextTick(callBack) {
const height = await this.getRect()
if (height != this.msgListRectHeight) {
callBack && callBack()
} else {
b = setTimeout(() => {
this.nextTick(callBack)
}, 100)
}
},
isArrowBottom() {
return new Promise((reslove) => {
this.$element('item-arrow').getBoundingClientRect({
success: (data) => {
reslove(data.bottom <= this.clientHeight)
},
})
})
},
async scrolltopHandler() {
if (this.showLoading) return
const oldHeight = await this.getRect()
this.showLoading = true
getHistroyMsgTimeId = getHistroyMsg((msgs) => {
this.msgList = [...msgs, ...this.msgList]
this.scrollbottom = `${oldHeight - this.clientHeight}`
this.showLoading = false
this.scrollbottom = `${
parseFloat(this.scrollbottom) + this.loadingHeight
}`
})
},
getRect() {
return new Promise((reslove) => {
this.$element('msgbox').getScrollRect({
success({ height }) {
reslove(height)
},
})
})
},
reply(message) {
this.pushMsg(message, 'our')
this.nextTick(() => {
this.$element('msgbox').scrollBy({
top: 10000,
left: 0,
behavior: 'smooth',
})
this.$element('wrapperbox').scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
})
})
},
async pushMsg(message, side) {
this.msgListRectHeight = await this.getRect()
this.msgList.push({
side,
message,
})
},
}
</script>
<style lang="scss">
.wrapper {
width: 466px;
height: 100%;
flex-direction: column;
scroll-snap-type: y mandatory;
background-color: #000000;
}
.loading-wrap {
justify-content: center;
width: 466px;
height: 100px;
}
@keyframes Rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.loading {
width: 80px;
height: 80px;
/* 单个动画 */
animation-name: Rotate;
animation-fill-mode: forwards;
animation-duration: 1000ms;
}
.message-pannel {
width: 466px;
height: 100%;
scroll-snap-align: end;
justify-content: space-between;
flex-direction: column;
}
.message {
width: 466px;
border: 1px solid #000;
margin-top: 10px;
}
.message-item {
width: 466px;
padding: 0 60px;
align-items: center;
justify-content: center;
}
.our {
align-items: center;
justify-content: flex-end;
}
.other {
align-items: center;
justify-content: flex-start;
}
.icon {
width: 80px;
height: 80px;
}
.img {
object-fit: contain;
width: 300px;
}
.txt {
color: #f0f8ff;
padding: 20px;
max-width: 350px;
background-color: #405eff;
}
.txt-our {
margin-right: 10px;
}
.txt-other {
margin-left: 10px;
}
.reply-pannel {
width: 466px;
height: 100%;
scroll-snap-align: start;
}
.message-item-arrow {
width: 466px;
align-items: center;
justify-content: center;
}
.message-arrow {
height: 32px;
margin-bottom: 82px;
width: 32px;
}
.reply-arrow {
height: 32px;
width: 32px;
border: 1px solid #000;
}
.input-box {
width: 466px;
flex-direction: column;
align-items: center;
justify-content: center;
}
.btn {
width: 300px;
height: 80px;
padding-left: 20px;
margin-bottom: 20px;
background-color: #405eff;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<div class="wrapper">
<text class="title">{{ text }}</text>
</div>
</template>
<script>
export default {
data: {
text: '你好,世界'
}
}
</script>
<style lang="scss">
@import './../../assets/styles/style.scss';
.wrapper {
@include flex-box-mixins(column, center, center);
margin: 0 10 * $size-factor;
.title {
font-size: 8 * $size-factor;
text-align: center;
color: $black;
}
}
</style>