初始化项目

This commit is contained in:
2026-04-07 11:18:02 +08:00
commit e277bb47cb
1114 changed files with 125107 additions and 0 deletions

16
attractor-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
######################################################################
# Build Tools
/unpackage/*
/node_modules/*
######################################################################
# Development Tools
/.idea/*
/.vscode/*
/.hbuilderx/*
package-lock.json
yarn.lock

34
attractor-ui/App.vue Normal file
View File

@@ -0,0 +1,34 @@
<script>
import config from './config'
import { getToken } from '@/utils/auth'
import updateManager from '@/utils/upgrade.js'
export default {
onLaunch() {
this.initApp()
updateManager.checkUpdate()
},
methods: {
initApp() {
// #ifdef APP-PLUS
plus.navigator.closeSplashscreen()
// #endif
this.initConfig()
this.ensureAuthState()
},
initConfig() {
this.globalData.config = config
},
ensureAuthState() {
const token = getToken()
const pages = getCurrentPages()
const currentRoute = pages && pages.length ? `/${pages[0].route}` : ''
if (!token && currentRoute !== '/pages/login') {
uni.reLaunch({ url: '/pages/login' })
}
}
}
}
</script>
<style lang="scss">
@import '@/static/scss/index.scss';
</style>

21
attractor-ui/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 若依
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

51
attractor-ui/README.md Normal file
View File

@@ -0,0 +1,51 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-43e3941654fa3054c9684bf53d1b1d356a1.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v1.2.0</h1>
<h4 align="center">基于UniApp开发的轻量级移动端框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-App/stargazers"><img src="https://gitee.com/y_project/RuoYi-App/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-App"><img src="https://img.shields.io/badge/RuoYi-v1.2.0-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-App/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介
RuoYi App 移动解决方案采用uniapp框架一份代码多终端适配同时支持APP、小程序、H5实现了与[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)、[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)完美对接的移动解决方案!目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。
* 提供了 [RuoYi-App-Vue2](https://gitee.com/y_project/RuoYi-App) 和 [RuoYi-App-Vue3](https://gitee.com/y_project/RuoYi-App/tree/vue3) 两个版本,分别基于 Vue2/Vuex 和 Vue3/Pinia 技术栈,以满足不同项目的技术选型需求。
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Cloud](https://github.com/yangzongzhuan/RuoYi-Cloud) 版本。
* 应用框架基于[uniapp](https://uniapp.dcloud.net.cn/)支持小程序、H5、Android和IOS。
* 前端组件采用[uni-ui](https://github.com/dcloudio/uni-ui)全端兼容的高性能UI框架。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
## 技术文档
- 官网网站:[http://ruoyi.vip](http://ruoyi.vip)
- 文档地址:[http://doc.ruoyi.vip](http://doc.ruoyi.vip)
- H5页体验[http://h5.ruoyi.vip](http://h5.ruoyi.vip)
- QQ交流群 ①133713780(满)、②146013835(满)、③189091635
- 小程序体验
<img src="https://oscimg.oschina.net/oscnet/up-26c76dc90b92acdbd9ac8cd5252f07c8ad9.jpg" alt="小程序演示"/>
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-21f6f842fdc94540469b4eb43fdadbaf7f8.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-a6f23cf9a371a30165e135eff6d9ae89a9d.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-ff5f62016bf6624c1ff27eee57499dccd44.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b9a582fdb26ec69d407fabd044d2c8494df.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-96427ee08fca29d77934cfc8d1b1a637cef.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5fdadc582d24cccd7727030d397b63185a3.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-0a36797b6bcc50c36d40c3c782665b89efc.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d77995cc00687cedd00d5ac7d68a07ea276.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-fa8f5ab20becf59b4b38c1b92a9989e7109.png"/></td>
</tr>
</table>

View File

@@ -0,0 +1,36 @@
import request from '@/utils/request'
// 查询当前登录用户设备列表u_device
export function listMyDevices(params) {
return request({
url: '/system/app/device/list',
method: 'get',
params
})
}
// 新增设备u_device
export function addMyDevice(data) {
return request({
url: '/system/app/device',
method: 'post',
data
})
}
// 修改设备u_device
export function updateMyDevice(data) {
return request({
url: '/system/app/device',
method: 'put',
data
})
}
// 删除设备u_device支持批量
export function deleteMyDevices(ids) {
return request({
url: `/system/app/device/${ids}`,
method: 'delete'
})
}

71
attractor-ui/api/login.js Normal file
View File

@@ -0,0 +1,71 @@
import request from '@/utils/request'
// 账号密码登录
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
url: '/login',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 手机号验证码登录(后端自动注册新用户)
export function mobileLogin(mobile, code) {
return request({
url: '/mobile/login',
headers: {
isToken: false
},
method: 'post',
data: { mobile, code }
})
}
// 注册方法
export function register(data) {
return request({
url: '/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 获取用户详细信息
export function getInfo() {
return request({
'url': '/getInfo',
'method': 'get'
})
}
// 退出方法
export function logout() {
return request({
'url': '/logout',
'method': 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
'url': '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}

View File

@@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询字典数据列表
export function listData(query) {
return request({
url: '/system/dict/data/list',
method: 'get',
params: query
})
}
// 查询字典数据详细
export function getData(dictCode) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'get'
})
}
// 根据字典类型查询字典数据信息
export function getDicts(dictType) {
return request({
url: '/system/dict/data/type/' + dictType,
method: 'get'
})
}
// 新增字典数据
export function addData(data) {
return request({
url: '/system/dict/data',
method: 'post',
data: data
})
}
// 修改字典数据
export function updateData(data) {
return request({
url: '/system/dict/data',
method: 'put',
data: data
})
}
// 删除字典数据
export function delData(dictCode) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'delete'
})
}

View File

@@ -0,0 +1,60 @@
import request from '@/utils/request'
// 查询字典类型列表
export function listType(query) {
return request({
url: '/system/dict/type/list',
method: 'get',
params: query
})
}
// 查询字典类型详细
export function getType(dictId) {
return request({
url: '/system/dict/type/' + dictId,
method: 'get'
})
}
// 新增字典类型
export function addType(data) {
return request({
url: '/system/dict/type',
method: 'post',
data: data
})
}
// 修改字典类型
export function updateType(data) {
return request({
url: '/system/dict/type',
method: 'put',
data: data
})
}
// 删除字典类型
export function delType(dictId) {
return request({
url: '/system/dict/type/' + dictId,
method: 'delete'
})
}
// 刷新字典缓存
export function refreshCache() {
return request({
url: '/system/dict/type/refreshCache',
method: 'delete'
})
}
// 获取字典选择框列表
export function optionselect() {
return request({
url: '/system/dict/type/optionselect',
method: 'get'
})
}

View File

@@ -0,0 +1,41 @@
import upload from '@/utils/upload'
import request from '@/utils/request'
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
data: data
})
}
// 查询用户个人信息
export function getUserProfile() {
return request({
url: '/system/user/profile',
method: 'get'
})
}
// 修改用户个人信息
export function updateUserProfile(data) {
return request({
url: '/system/user/profile',
method: 'put',
data: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return upload({
url: '/system/user/profile/avatar',
name: data.name,
filePath: data.filePath
})
}

View File

@@ -0,0 +1,167 @@
<template>
<view class="uni-section">
<view class="uni-section-header" @click="onClick">
<view class="uni-section-header__decoration" v-if="type" :class="type" />
<slot v-else name="decoration"></slot>
<view class="uni-section-header__content">
<text :style="{'font-size':titleFontSize,'color':titleColor}" class="uni-section__content-title" :class="{'distraction':!subTitle}">{{ title }}</text>
<text v-if="subTitle" :style="{'font-size':subTitleFontSize,'color':subTitleColor}" class="uni-section-header__content-sub">{{ subTitle }}</text>
</view>
<view class="uni-section-header__slot-right">
<slot name="right"></slot>
</view>
</view>
<view class="uni-section-content" :style="{padding: _padding}">
<slot />
</view>
</view>
</template>
<script>
/**
* Section 标题栏
* @description 标题栏
* @property {String} type = [line|circle|square] 标题装饰类型
* @value line 竖线
* @value circle 圆形
* @value square 正方形
* @property {String} title 主标题
* @property {String} titleFontSize 主标题字体大小
* @property {String} titleColor 主标题字体颜色
* @property {String} subTitle 副标题
* @property {String} subTitleFontSize 副标题字体大小
* @property {String} subTitleColor 副标题字体颜色
* @property {String} padding 默认插槽 padding
*/
export default {
name: 'UniSection',
emits:['click'],
props: {
type: {
type: String,
default: ''
},
title: {
type: String,
required: true,
default: ''
},
titleFontSize: {
type: String,
default: '14px'
},
titleColor:{
type: String,
default: '#333'
},
subTitle: {
type: String,
default: ''
},
subTitleFontSize: {
type: String,
default: '12px'
},
subTitleColor: {
type: String,
default: '#999'
},
padding: {
type: [Boolean, String],
default: false
}
},
computed:{
_padding(){
if(typeof this.padding === 'string'){
return this.padding
}
return this.padding?'10px':''
}
},
watch: {
title(newVal) {
if (uni.report && newVal !== '') {
uni.report('title', newVal)
}
}
},
methods: {
onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss" >
$uni-primary: #2979ff !default;
.uni-section {
background-color: #fff;
.uni-section-header {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 12px 10px;
font-weight: normal;
&__decoration{
margin-right: 6px;
background-color: $uni-primary;
&.line {
width: 4px;
height: 12px;
border-radius: 10px;
}
&.circle {
width: 8px;
height: 8px;
border-top-right-radius: 50px;
border-top-left-radius: 50px;
border-bottom-left-radius: 50px;
border-bottom-right-radius: 50px;
}
&.square {
width: 8px;
height: 8px;
}
}
&__content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
flex: 1;
color: #333;
.distraction {
flex-direction: row;
align-items: center;
}
&-sub {
margin-top: 2px;
}
}
&__slot-right{
font-size: 14px;
}
}
.uni-section-content{
font-size: 14px;
}
}
</style>

26
attractor-ui/config.js Normal file
View File

@@ -0,0 +1,26 @@
// 应用全局配置
module.exports = {
baseUrl: 'https://vue.ruoyi.vip/prod-api',
// baseUrl: 'http://localhost:8080',
// 应用信息
appInfo: {
// 应用名称
name: "ruoyi-app",
// 应用版本
version: "1.2.0",
// 应用logo
logo: "/static/logo.png",
// 官方网站
site_url: "http://ruoyi.vip",
// 政策协议
agreements: [{
title: "隐私政策",
url: "https://ruoyi.vip/protocol.html"
},
{
title: "用户服务协议",
url: "https://ruoyi.vip/protocol.html"
}
]
}
}

20
attractor-ui/main.js Normal file
View File

@@ -0,0 +1,20 @@
import Vue from 'vue'
import App from './App'
import store from './store' // store
import plugins from './plugins' // plugins
import './permission' // permission
import { getDicts } from "@/api/system/dict/data"
Vue.use(plugins)
Vue.config.productionTip = false
Vue.prototype.$store = store
Vue.prototype.getDicts = getDicts
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()

106
attractor-ui/manifest.json Normal file
View File

@@ -0,0 +1,106 @@
{
"name" : "Attractor",
"appid" : "__UNI__6006F61",
"description" : "",
"versionName" : "1.2.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
"usingComponents" : true,
"nvueCompiler" : "uni-app",
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {
"Bluetooth" : {}
},
"distribute" : {
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"ios" : {
"dSYMs" : false
},
"sdkConfigs" : {},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
}
}
},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wxbcaa5108c55345cb",
"setting" : {
"urlCheck" : false,
"es6" : false,
"minified" : true,
"postcss" : true
},
"optimization" : {
"subPackages" : true
},
"usingComponents" : true
},
"vueVersion" : "2",
"h5" : {
"template" : "static/index.html",
"devServer" : {
"port" : 9090,
"https" : false
},
"title" : "RuoYi-App",
"router" : {
"mode" : "hash",
"base" : "./"
}
}
}

157
attractor-ui/pages.json Normal file
View File

@@ -0,0 +1,157 @@
{
"pages": [{
"path": "pages/devices/devices",
"style": {
"navigationBarTitleText": "我的设备"
}
},{
"path": "pages/login",
"style": {
"navigationBarTitleText": "登录"
}
}, {
"path": "pages/register",
"style": {
"navigationBarTitleText": "注册"
}
}, {
"path": "pages/index",
"style": {
"navigationBarTitleText": "若依移动端框架",
"navigationStyle": "custom"
}
}, {
"path": "pages/work/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "公告"
}
}, {
"path": "pages/work/search",
"style": {
"navigationBarTitleText": "搜索公告"
}
}, {
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "我的"
}
}, {
"path": "pages/mine/avatar/index",
"style": {
"navigationBarTitleText": "修改头像"
}
}, {
"path": "pages/mine/info/index",
"style": {
"navigationBarTitleText": "个人信息"
}
}, {
"path": "pages/mine/info/edit",
"style": {
"navigationBarTitleText": "编辑资料"
}
}, {
"path": "pages/mine/pwd/index",
"style": {
"navigationBarTitleText": "修改密码"
}
}, {
"path": "pages/mine/setting/index",
"style": {
"navigationBarTitleText": "应用设置"
}
}, {
"path": "pages/mine/help/index",
"style": {
"navigationBarTitleText": "常见问题"
}
}, {
"path": "pages/mine/about/index",
"style": {
"navigationBarTitleText": "关于我们"
}
}, {
"path": "pages/common/webview/index",
"style": {
"navigationBarTitleText": "浏览网页"
}
}, {
"path": "pages/common/textview/index",
"style": {
"navigationBarTitleText": "浏览文本"
}
},
{
"path": "pages/devices/attractor/attractor",
"style": {
"navigationBarTitleText": "电疗仪"
}
},
{
"path": "pages/devices/master",
"style": {
"navigationBarTitleText": "设备管理"
}
},
{
"path": "pages/team/team",
"style": {
"navigationBarTitleText": "团队管理"
}
},
{
"path": "pages/member/member",
"style": {
"navigationBarTitleText": "成员管理"
}
},
{
"path": "pages/team/detail",
"style": {
"navigationBarTitleText": "团队配置"
}
},
{
"path": "pages/member/permi",
"style": {
"navigationBarTitleText": "成员权限"
}
},
{
"path": "pages/devices/detail/detail",
"style": {
"navigationBarTitleText": "设备详情"
}
}
],
"tabBar": {
"color": "#000000",
"selectedColor": "#000000",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/devices/devices",
"iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home_.png",
"text": "连接"
}, {
"pagePath": "pages/work/index",
"iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png",
"text": "社区"
}, {
"pagePath": "pages/mine/index",
"iconPath": "static/images/tabbar/mine.png",
"selectedIconPath": "static/images/tabbar/mine_.png",
"text": "我的"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "RuoYi",
"navigationBarBackgroundColor": "#FFFFFF"
}
}

View File

@@ -0,0 +1,43 @@
<template>
<view>
<uni-card class="view-title" :title="title">
<text class="uni-body view-content">{{ content }}</text>
</uni-card>
</view>
</template>
<script>
export default {
data() {
return {
title: '',
content: ''
}
},
onLoad(options) {
this.title = options.title
this.content = options.content
uni.setNavigationBarTitle({
title: options.title
})
}
}
</script>
<style scoped>
page {
background-color: #ffffff;
}
.view-title {
font-weight: bold;
}
.view-content {
font-size: 26rpx;
padding: 12px 5px 0;
color: #333;
line-height: 24px;
font-weight: normal;
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<view v-if="params.url">
<web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
params: {},
webviewStyles: {
progress: {
color: "#FF3333"
}
}
}
},
props: {
src: {
type: [String],
default: null
}
},
onLoad(event) {
this.params = event
if (event.title) {
uni.setNavigationBarTitle({
title: event.title
})
}
}
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,276 @@
<template>
<view class="device-detail-page">
<!-- 顶部导航 -->
<view class="nav-bar">
<button class="back-btn" @click="goBack"> 返回</button>
<text class="nav-title">设备详情</text>
<button class="save-btn" @click="saveDevice">保存</button>
</view>
<!-- 基础信息编辑区 -->
<view class="base-info">
<text class="section-title">基础信息</text>
<!-- 设备图片编辑 -->
<view class="info-item">
<text class="label">设备图片</text>
<view class="img-upload">
<image class="device-img" :src="currentDevice.image" mode="aspectFill"></image>
<button class="change-img-btn" @click="chooseImage">更换图片</button>
</view>
</view>
<!-- 设备名称编辑 -->
<view class="info-item">
<text class="label">设备名称</text>
<input
class="input"
v-model="currentDevice.name"
placeholder="请输入设备名称"
/>
</view>
<!-- SN码不可编辑 -->
<view class="info-item">
<text class="label">SN码</text>
<text class="sn-text">{{ currentDevice.sn }}</text>
</view>
<!-- 设备类型不可编辑 -->
<view class="info-item">
<text class="label">设备类型</text>
<text class="type-text">
{{ deviceTabs.find(item => item.type === currentDevice.type).name || '未知类型' }}
</text>
</view>
</view>
<!-- 使用者/团队管理区 -->
<view class="manager-info">
<text class="section-title">归属管理</text>
<!-- 使用者选择 -->
<view class="info-item">
<text class="label">使用者</text>
<picker
mode="selector"
:range="userList"
@change="selectUser"
>
<view class="picker">
{{ currentDevice.user || '请选择使用者' }}
</view>
</picker>
</view>
<!-- 所属团队选择 -->
<view class="info-item">
<text class="label">所属团队</text>
<picker
mode="selector"
:range="teamList"
@change="selectTeam"
>
<view class="picker">
{{ currentDevice.team || '请选择所属团队' }}
</view>
</picker>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentDevice: {}, // 当前编辑的设备信息
deviceTabs: [
{ name: '全部设备', type: '' },
{ name: '智能硬件', type: 'hardware' },
{ name: '传感器', type: 'sensor' },
{ name: '控制器', type: 'controller' }
],
// 模拟使用者列表
userList: ['张三', '李四', '王五', '赵六'],
// 模拟团队列表
teamList: ['研发部', '测试部', '运维部', '市场部']
}
},
onLoad(options) {
// 获取设备ID并从列表中匹配设备信息
const deviceId = Number(options.id);
// 从全局/本地存储获取设备列表实际项目建议用vuex/本地存储)
const deviceList = getApp().globalData.deviceList || [];
this.currentDevice = JSON.parse(JSON.stringify(deviceList.find(item => item.id === deviceId)));
// 若未找到设备,返回上一页
if (!this.currentDevice) {
uni.showToast({ title: '设备不存在', icon: 'none' });
setTimeout(() => this.goBack(), 1500);
}
},
onShow() {
// 初始化全局设备列表(方便跨页面共享)
if (!getApp().globalData.deviceList) {
getApp().globalData.deviceList = [
{
id: 1,
name: '温湿度传感器',
sn: 'SN20260210001',
type: 'sensor',
image: '/static/device/sensor.png',
user: '张三',
team: '研发部'
},
{
id: 2,
name: '智能控制器',
sn: 'SN20260210002',
type: 'controller',
image: '/static/device/controller.png',
user: '李四',
team: '测试部'
}
];
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
},
// 选择设备图片uniapp上传图片API
async chooseImage() {
try {
const res = await uni.chooseImage({
count: 1, // 仅选1张
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'] // 相册/相机
});
// 模拟上传图片实际项目需上传到服务器替换为返回的图片URL
this.currentDevice.image = res.tempFilePaths[0];
} catch (err) {
console.error('选择图片失败:', err);
uni.showToast({ title: '选择图片失败', icon: 'none' });
}
},
// 选择使用者
selectUser(e) {
this.currentDevice.user = this.userList[e.detail.value];
},
// 选择所属团队
selectTeam(e) {
this.currentDevice.team = this.teamList[e.detail.value];
},
// 保存设备信息
saveDevice() {
// 更新全局设备列表
const deviceList = getApp().globalData.deviceList;
const index = deviceList.findIndex(item => item.id === this.currentDevice.id);
if (index > -1) {
deviceList[index] = this.currentDevice;
getApp().globalData.deviceList = deviceList;
}
uni.showToast({ title: '保存成功', icon: 'success' });
setTimeout(() => this.goBack(), 1000);
}
}
};
</script>
<style scoped>
/* 页面整体样式 */
.device-detail-page {
background-color: #f5f5f5;
min-height: 100vh;
}
/* 导航栏 */
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 20px;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.back-btn, .save-btn {
background-color: transparent;
border: none;
font-size: 14px;
}
.back-btn {
color: #666;
}
.save-btn {
color: #409eff;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
/* 信息区块 */
.base-info, .manager-info {
background-color: #fff;
margin: 10px;
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.section-title {
display: block;
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 1px solid #f0f0f0;
}
/* 信息项 */
.info-item {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.label {
width: 80px;
font-size: 14px;
color: #666;
}
.input, .picker, .sn-text, .type-text {
flex: 1;
padding: 8px 10px;
font-size: 14px;
}
.input {
border: 1px solid #e5e5e5;
border-radius: 4px;
}
.sn-text, .type-text {
color: #999;
}
/* 图片上传区 */
.img-upload {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
}
.device-img {
width: 60px;
height: 60px;
border-radius: 8px;
}
.change-img-btn {
background-color: #e5e5e5;
color: #666;
border: none;
border-radius: 4px;
padding: 5px 10px;
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,229 @@
<template>
<view class="device-page">
<view class="search-row">
<view class="search-box">
<uni-icons type="search" size="16" color="#7b8794"></uni-icons>
<input class="search-input" placeholder="搜索设备名称或SN" v-model="searchKeyword" @confirm="fetchMyDevices" />
<button v-if="searchKeyword" class="clear-btn" @click="clearSearch">×</button>
</view>
<button class="scan-chip" @click="refreshScan">扫描连接</button>
</view>
<view class="section" v-if="scannedDevices.length">
<view class="section-head">
<text class="section-title">可连接设备</text>
<text class="section-sub">点击连接后自动写入后端</text>
</view>
<view v-for="s in scannedDevices" :key="s.deviceId" class="device-card" @click="askConnect(s)">
<image class="device-icon" :src="getIconForType()" mode="aspectFit" />
<view class="device-main">
<text class="device-name">{{ s.name }}</text>
<text class="device-meta">SN: {{ s.deviceId }}</text>
</view>
<view class="state-pill running">连接</view>
</view>
</view>
<scroll-view class="content" scroll-y>
<view class="section">
<view class="section-head">
<text class="section-title">我的设备</text>
<text class="section-sub">后端持久化数据</text>
</view>
<view v-if="deviceList.length === 0" class="empty-card">
<text>暂无设备请先扫描连接</text>
</view>
<view v-for="item in deviceList" :key="item.deviceId" class="device-card" @click="gotoDetail(item)">
<image class="device-icon" :src="getIconForType()" mode="aspectFit" />
<view class="device-main">
<text class="device-name">{{ item.deviceName || '未命名设备' }}</text>
<text class="device-meta">SN: {{ item.deviceSn || '--' }}</text>
<text class="device-meta" v-if="item.lastConnectedAt">最近连接{{ formatTime(item.lastConnectedAt) }}</text>
</view>
<view class="device-actions">
<view class="state-pill" :class="item.status === '0' ? 'running' : 'offline'">
{{ item.status === '0' ? '正常' : '停用' }}
</view>
<button class="delete-btn" @click.stop="removeDevice(item)">
<uni-icons type="trash" size="12" color="#64748b"></uni-icons>
<text>删除</text>
</button>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { listMyDevices, deleteMyDevices, addMyDevice, updateMyDevice } from '@/api/device'
export default {
data() {
return {
searchKeyword: '',
deviceList: [],
scannedDevices: [],
_bluetoothFoundHandler: null
}
},
onLoad() {
this.fetchMyDevices()
},
onUnload() {
this.stopScan()
},
methods: {
fetchMyDevices() {
const params = {}
if ((this.searchKeyword || '').trim()) params.deviceName = this.searchKeyword.trim()
listMyDevices(params).then(res => {
this.deviceList = Array.isArray(res.rows) ? res.rows : []
})
},
clearSearch() {
this.searchKeyword = ''
this.fetchMyDevices()
},
refreshScan() {
this.scannedDevices = []
this.startScan()
},
startScan() {
uni.openBluetoothAdapter({
success: () => {
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: () => {
if (this._bluetoothFoundHandler) {
try { uni.offBluetoothDeviceFound(this._bluetoothFoundHandler) } catch (e) {}
}
this._bluetoothFoundHandler = res => {
const arr = res.devices || []
arr.forEach(dev => {
const id = dev.deviceId
const name = (dev.name || dev.localName || '').trim()
if (!id || !name) return
if (this.scannedDevices.some(s => s.deviceId === id)) return
this.scannedDevices.push({ deviceId: id, name })
})
}
uni.onBluetoothDeviceFound(this._bluetoothFoundHandler)
uni.showToast({ title: '扫描中...', icon: 'none' })
setTimeout(() => this.stopScan(), 10000)
}
})
},
fail: () => uni.showToast({ title: '请开启蓝牙权限', icon: 'none' })
})
},
stopScan() {
try { uni.stopBluetoothDevicesDiscovery({}) } catch (e) {}
if (this._bluetoothFoundHandler) {
try { uni.offBluetoothDeviceFound(this._bluetoothFoundHandler) } catch (e) {}
this._bluetoothFoundHandler = null
}
},
askConnect(device) {
uni.showModal({
title: '连接确认',
content: `连接设备「${device.name}」后将自动写入后端,是否继续?`,
success: res => {
if (res.confirm) this.connectDevice(device)
}
})
},
connectDevice(device) {
uni.showLoading({ title: '连接中...' })
uni.createBLEConnection({
deviceId: device.deviceId,
success: () => {
this.upsertDeviceToServer(device).finally(() => uni.hideLoading())
},
fail: () => {
uni.hideLoading()
uni.showToast({ title: '连接失败', icon: 'none' })
}
})
},
upsertDeviceToServer(device) {
const exist = this.deviceList.find(d => d.deviceSn === device.deviceId)
const payload = {
deviceName: device.name,
deviceSn: device.deviceId,
deviceType: 'attractor',
status: '0',
lastConnectedAt: new Date().toISOString()
}
if (exist) {
return updateMyDevice({ ...payload, deviceId: exist.deviceId }).then(() => {
uni.showToast({ title: '连接成功,已更新', icon: 'success' })
this.fetchMyDevices()
})
}
return addMyDevice(payload).then(() => {
uni.showToast({ title: '连接成功,已入库', icon: 'success' })
this.fetchMyDevices()
})
},
formatTime(ts) {
if (!ts) return ''
const d = new Date(ts)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const h = String(d.getHours()).padStart(2, '0')
const min = String(d.getMinutes()).padStart(2, '0')
return `${y}-${m}-${day} ${h}:${min}`
},
getIconForType() {
return '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png'
},
removeDevice(item) {
uni.showModal({
title: '删除设备',
content: '确认删除该设备吗?',
success: res => {
if (res.confirm) {
deleteMyDevices(item.deviceId).then(() => {
uni.showToast({ title: '删除成功', icon: 'none' })
this.fetchMyDevices()
})
}
}
})
},
gotoDetail(device) {
if (!device || !device.deviceSn) return
uni.navigateTo({ url: `/pages/devices/attractor/attractor?deviceId=${encodeURIComponent(device.deviceSn)}` })
}
}
}
</script>
<style scoped>
.device-page { min-height: 100vh; background: #f4f7f8; padding: 20rpx 20rpx 0; box-sizing: border-box; }
.search-row { display: flex; gap: 12rpx; align-items: center; margin-bottom: 18rpx; }
.search-box { flex: 1; height: 72rpx; display: flex; align-items: center; gap: 10rpx; padding: 0 16rpx; background: #fff; border-radius: 14rpx; border: 1rpx solid #e7eeef; }
.search-input { flex: 1; font-size: 24rpx; color: #0f172a; }
.clear-btn { border: none; background: transparent; font-size: 32rpx; color: #9ca3af; line-height: 1; }
.scan-chip { height: 72rpx; padding: 0 16rpx; border-radius: 14rpx; background: #e6f5f4; border: 1rpx solid #bfe9e7; display: flex; align-items: center; font-size: 22rpx; color: #0f7f7a; }
.content { height: calc(100vh - 240rpx); }
.section { margin-bottom: 20rpx; }
.section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
.section-title { font-size: 26rpx; font-weight: 600; color: #0f172a; }
.section-sub { font-size: 22rpx; color: #94a3b8; }
.empty-card { background: #fff; border-radius: 16rpx; border: 1rpx dashed #d6dde3; padding: 24rpx; text-align: center; color: #94a3b8; font-size: 24rpx; }
.device-card { display: flex; align-items: center; padding: 18rpx; border-radius: 18rpx; background: #fff; border: 1rpx solid #e7eeef; margin-bottom: 12rpx; }
.device-icon { width: 70rpx; height: 70rpx; border-radius: 16rpx; background: #f2f7f6; margin-right: 16rpx; }
.device-main { flex: 1; display: flex; flex-direction: column; gap: 4rpx; }
.device-name { font-size: 26rpx; font-weight: 600; color: #0f172a; }
.device-meta { font-size: 22rpx; color: #94a3b8; }
.device-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 10rpx; }
.state-pill { padding: 4rpx 12rpx; border-radius: 999rpx; font-size: 20rpx; font-weight: 600; background: #f1f5f9; color: #64748b; }
.state-pill.running { background: #ecfdf3; color: #059669; }
.state-pill.offline { background: #f1f5f9; color: #94a3b8; }
.delete-btn { display: inline-flex; align-items: center; gap: 6rpx; height: 44rpx; padding: 0 12rpx; border-radius: 12rpx; border: 1rpx solid #e2e8f0; background: #f8fafc; color: #64748b; font-size: 20rpx; }
</style>

View File

@@ -0,0 +1,430 @@
<template>
<view class="master-page">
<view class="hero-card">
<view>
<text class="hero-title">设备管理</text>
<text class="hero-sub">统一管理你的 Attractor 设备</text>
</view>
<button class="hero-btn" @click="openAddDeviceModal">新增设备</button>
</view>
<view class="filter-row">
<view
v-for="(tab, index) in deviceTabs"
:key="tab.type || index"
class="filter-pill"
:class="{ active: activeTab === index }"
@click="switchTab(index)"
>
{{ tab.name }}
</view>
</view>
<scroll-view class="device-list" scroll-y>
<view v-if="filteredDevices.length === 0" class="empty-card">
<text class="empty-title">当前分类暂无设备</text>
<text class="empty-sub">你可以点击右上角新增设备进行添加</text>
</view>
<view
class="device-card"
v-for="device in filteredDevices"
:key="device.id"
@click="toDeviceDetail(device.id)"
>
<image class="device-icon" :src="device.image" mode="aspectFit" />
<view class="device-main">
<text class="device-name">{{ device.name }}</text>
<text class="device-sn">SN: {{ device.sn }}</text>
<view class="meta-row">
<text class="meta-tag">{{ typeText(device.type) }}</text>
<text class="meta-text" v-if="device.user">使用者{{ device.user }}</text>
</view>
</view>
<button class="delete-btn" @click.stop="deleteDevice(device.id)">
<uni-icons type="trash" size="13" color="#64748b"></uni-icons>
<text>删除</text>
</button>
</view>
</scroll-view>
<uni-popup ref="addDevicePopup" type="center">
<view class="popup-card">
<text class="popup-title">新增设备</text>
<button class="pair-btn" @click="bluetoothPair">蓝牙配对设备</button>
<view class="form-item">
<text class="form-label">设备名称</text>
<input class="form-input" v-model="newDevice.name" placeholder="请输入设备名称" />
</view>
<view class="form-item">
<text class="form-label">SN </text>
<input class="form-input" v-model="newDevice.sn" placeholder="配对后自动填充/手动输入" />
</view>
<view class="form-item">
<text class="form-label">设备类型</text>
<picker :range="pickerTypes" :value="newDevice.type" @change="onTypeChange">
<view class="picker-view">{{ pickerTypes[newDevice.type] }}</view>
</picker>
</view>
<view class="popup-actions">
<button class="ghost" @click="closeAddDeviceModal">取消</button>
<button class="primary" @click="confirmAddDevice">确认添加</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
data() {
return {
deviceTabs: [
{ name: '全部', type: '' },
{ name: '智能硬件', type: 'hardware' },
{ name: '传感器', type: 'sensor' },
{ name: '控制器', type: 'controller' }
],
activeTab: 0,
deviceList: [
{
id: 1,
name: 'Attractor 主控器',
sn: 'ATR-20260327001',
type: 'controller',
image: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png',
user: 'Attractor_001'
},
{
id: 2,
name: '环境传感器 A1',
sn: 'ATR-20260327002',
type: 'sensor',
image: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png',
user: ''
}
],
newDevice: {
name: '',
sn: '',
type: 0
},
pickerTypes: ['智能硬件', '传感器', '控制器']
}
},
computed: {
filteredDevices() {
if (this.activeTab === 0) return this.deviceList
const targetType = this.deviceTabs[this.activeTab].type
return this.deviceList.filter(item => item.type === targetType)
}
},
methods: {
typeText(type) {
const map = {
hardware: '智能硬件',
sensor: '传感器',
controller: '控制器'
}
return map[type] || '未知类型'
},
switchTab(index) {
this.activeTab = index
},
openAddDeviceModal() {
this.newDevice = { name: '', sn: '', type: 0 }
this.$refs.addDevicePopup.open()
},
closeAddDeviceModal() {
this.$refs.addDevicePopup.close()
},
onTypeChange(e) {
this.newDevice.type = Number(e.detail.value)
},
bluetoothPair() {
this.newDevice.sn = `ATR-${Date.now().toString().slice(-8)}`
uni.showToast({ title: '配对成功SN 已填充', icon: 'none' })
},
confirmAddDevice() {
if (!this.newDevice.name || !this.newDevice.sn) {
uni.showToast({ title: '设备名称和 SN 不能为空', icon: 'none' })
return
}
const typeMap = ['hardware', 'sensor', 'controller']
const id = Math.max(...this.deviceList.map(d => d.id), 0) + 1
this.deviceList.unshift({
id,
name: this.newDevice.name,
sn: this.newDevice.sn,
type: typeMap[this.newDevice.type],
image: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png',
user: ''
})
this.closeAddDeviceModal()
uni.showToast({ title: '设备已添加', icon: 'success' })
},
deleteDevice(id) {
uni.showModal({
title: '删除设备',
content: '确认删除该设备吗?',
success: res => {
if (res.confirm) {
this.deviceList = this.deviceList.filter(item => item.id !== id)
uni.showToast({ title: '删除成功', icon: 'none' })
}
}
})
},
toDeviceDetail(id) {
uni.navigateTo({
url: '/pages/devices/detail/detail?id=' + id
})
}
}
}
</script>
<style scoped>
.master-page {
min-height: 100vh;
background: #f4f7f8;
padding: 20rpx;
box-sizing: border-box;
}
.hero-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 18rpx;
padding: 22rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14rpx;
}
.hero-title {
display: block;
font-size: 34rpx;
font-weight: 700;
color: #0f172a;
}
.hero-sub {
display: block;
margin-top: 4rpx;
font-size: 22rpx;
color: #94a3b8;
}
.hero-btn {
height: 62rpx;
line-height: 62rpx;
padding: 0 20rpx;
border-radius: 14rpx;
border: 1rpx solid #bfe9e7;
background: #e6f5f4;
color: #0f7f7a;
font-size: 24rpx;
}
.filter-row {
display: flex;
gap: 10rpx;
overflow-x: auto;
padding-bottom: 8rpx;
margin-bottom: 10rpx;
}
.filter-pill {
flex-shrink: 0;
padding: 8rpx 18rpx;
border-radius: 999rpx;
border: 1rpx solid #e2e8f0;
background: #ffffff;
color: #64748b;
font-size: 23rpx;
}
.filter-pill.active {
border-color: #bfe9e7;
background: #e6f5f4;
color: #0f7f7a;
font-weight: 600;
}
.device-list {
height: calc(100vh - 240rpx);
}
.empty-card {
background: #ffffff;
border: 1rpx dashed #d7dee5;
border-radius: 16rpx;
padding: 40rpx 20rpx;
text-align: center;
}
.empty-title {
display: block;
font-size: 26rpx;
color: #64748b;
}
.empty-sub {
display: block;
margin-top: 8rpx;
font-size: 22rpx;
color: #94a3b8;
}
.device-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 16rpx;
padding: 16rpx;
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.device-icon {
width: 72rpx;
height: 72rpx;
border-radius: 14rpx;
margin-right: 14rpx;
background: #f0fdfa;
}
.device-main {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.device-name {
font-size: 26rpx;
color: #0f172a;
font-weight: 600;
}
.device-sn,
.meta-text {
font-size: 22rpx;
color: #94a3b8;
}
.meta-row {
display: flex;
gap: 10rpx;
align-items: center;
}
.meta-tag {
font-size: 21rpx;
color: #0f7f7a;
background: #e6f5f4;
border-radius: 8rpx;
padding: 2rpx 10rpx;
}
.delete-btn {
display: inline-flex;
align-items: center;
gap: 6rpx;
height: 46rpx;
padding: 0 12rpx;
border-radius: 12rpx;
border: 1rpx solid #e2e8f0;
background: #f8fafc;
color: #64748b;
font-size: 20rpx;
}
.popup-card {
width: 620rpx;
background: #ffffff;
border-radius: 18rpx;
padding: 24rpx;
box-sizing: border-box;
}
.popup-title {
display: block;
text-align: center;
font-size: 30rpx;
color: #0f172a;
font-weight: 700;
margin-bottom: 16rpx;
}
.pair-btn {
height: 68rpx;
line-height: 68rpx;
border-radius: 14rpx;
border: 1rpx solid #bfe9e7;
background: #e6f5f4;
color: #0f7f7a;
font-size: 24rpx;
margin-bottom: 16rpx;
}
.form-item {
margin-bottom: 12rpx;
}
.form-label {
display: block;
font-size: 23rpx;
color: #64748b;
margin-bottom: 6rpx;
}
.form-input,
.picker-view {
height: 64rpx;
line-height: 64rpx;
border-radius: 12rpx;
border: 1rpx solid #e2e8f0;
background: #f8fafc;
padding: 0 14rpx;
font-size: 24rpx;
color: #0f172a;
}
.popup-actions {
margin-top: 16rpx;
display: flex;
gap: 10rpx;
}
.popup-actions button {
flex: 1;
height: 68rpx;
line-height: 68rpx;
border-radius: 12rpx;
font-size: 24rpx;
}
.popup-actions .ghost {
border: 1rpx solid #e2e8f0;
background: #f8fafc;
color: #64748b;
}
.popup-actions .primary {
border: 1rpx solid #bfe9e7;
background: #e6f5f4;
color: #0f7f7a;
}
</style>

View File

@@ -0,0 +1,3 @@
<template>
<view>设备列表</view>
</template>

View File

@@ -0,0 +1,145 @@
<template>
<view class="login-page">
<view class="hero">
<image class="logo" src="/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png" mode="aspectFit" />
<text class="title">Attractor 登录</text>
<text class="sub">手机号一键登录新用户自动注册</text>
</view>
<view class="form-card">
<view class="input-row">
<uni-icons type="phone" size="18" color="#0f7f7a"></uni-icons>
<input
v-model="mobile"
class="input"
type="number"
maxlength="11"
placeholder="请输入手机号"
/>
</view>
<view class="hint-row">
<text class="hint">验证码已内置666666测试阶段</text>
</view>
<button class="login-btn" @click="handleMobileLogin">登录 / 自动注册</button>
</view>
</view>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
data() {
return {
mobile: ''
}
},
onLoad() {
if (getToken()) {
this.$tab.reLaunch('/pages/devices/devices')
}
},
methods: {
handleMobileLogin() {
const mobile = (this.mobile || '').trim()
if (!/^1\d{10}$/.test(mobile)) {
this.$modal.msgError('请输入正确的11位手机号')
return
}
this.$modal.loading('登录中,请稍候...')
this.$store.dispatch('MobileLogin', mobile).then(() => {
return this.$store.dispatch('GetInfo')
}).then(() => {
this.$modal.closeLoading()
this.$tab.reLaunch('/pages/devices/devices')
}).catch(() => {
this.$modal.closeLoading()
})
}
}
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
background: #f4f7f8;
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.hero {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 80rpx;
margin-bottom: 36rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
border-radius: 24rpx;
}
.title {
margin-top: 16rpx;
font-size: 38rpx;
font-weight: 700;
color: #0f172a;
}
.sub {
margin-top: 6rpx;
font-size: 24rpx;
color: #94a3b8;
}
.form-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 20rpx;
padding: 24rpx;
}
.input-row {
height: 82rpx;
border: 1rpx solid #bfe9e7;
background: #f2fbfa;
border-radius: 14rpx;
display: flex;
align-items: center;
gap: 12rpx;
padding: 0 16rpx;
}
.input {
flex: 1;
font-size: 28rpx;
color: #0f172a;
}
.hint-row {
margin-top: 12rpx;
}
.hint {
font-size: 22rpx;
color: #64748b;
}
.login-btn {
margin-top: 22rpx;
height: 82rpx;
line-height: 82rpx;
border-radius: 14rpx;
border: 1rpx solid #bfe9e7;
background: #1f9f9a;
color: #ffffff;
font-size: 28rpx;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,22 @@
<template>
<view>
成员管理
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,22 @@
<template>
<view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,173 @@
<template>
<view class="manual-page">
<view class="hero-card">
<image class="logo" src="/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png" mode="aspectFit"></image>
<view class="hero-text">
<text class="title">Attractor 设备与软件说明</text>
<text class="subtitle">让连接控制与日常使用更清晰</text>
</view>
</view>
<scroll-view class="manual-list" scroll-y>
<view class="section-card">
<text class="section-title">软件说明</text>
<view class="row">
<text class="label">当前版本</text>
<text class="value">v{{ version }}</text>
</view>
<text class="desc">Attractor App 用于设备连接参数设置状态查看与快捷控制建议保持应用为最新版本以获得更稳定的蓝牙连接和更完整的功能体验</text>
</view>
<view class="section-card">
<text class="section-title">设备连接说明</text>
<text class="bullet">1. 首次使用前请确保设备电量充足并处于可连接状态</text>
<text class="bullet">2. 打开我的设备页面后系统会自动搜索附近设备</text>
<text class="bullet">3. 点击候选设备并确认连接成功后会进入我的设备列表</text>
<text class="bullet">4. 若连接失败请重启设备蓝牙并靠近手机后重试</text>
</view>
<view class="section-card">
<text class="section-title">治疗参数说明</text>
<text class="bullet"> 模式舒缓模式适合日常放松强效模式适合更高强度需求</text>
<text class="bullet"> 档位建议从低档逐步上调避免突然增加刺激强度</text>
<text class="bullet"> 频率/脉宽请根据个人体感逐步调整若不适请立即停止</text>
<text class="bullet"> 电压支持按+/-</text>
</view>
<view class="section-card">
<text class="section-title">安全与注意事项</text>
<text class="bullet"> 使用过程中若出现明显不适请立即停止并咨询专业人士</text>
<text class="bullet"> 请勿在驾驶洗浴睡眠等不适宜场景中使用</text>
<text class="bullet"> 设备异常发热无响应或连接不稳定时请先断电后再排查</text>
</view>
<view class="section-card">
<text class="section-title">技术支持</text>
<view class="row">
<text class="label">支持邮箱</text>
<text class="value">support@attractor.app</text>
</view>
<view class="row">
<text class="label">服务时间</text>
<text class="value">工作日 09:00 - 18:00</text>
</view>
</view>
<view class="footer-tip">
<text>© 2026 Attractor. All Rights Reserved.</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
version: getApp().globalData?.config?.appInfo?.version || '2.3.0'
}
}
}
</script>
<style scoped>
.manual-page {
min-height: 100vh;
background: #f4f7f8;
padding: 20rpx;
box-sizing: border-box;
}
.hero-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 18rpx;
padding: 22rpx;
display: flex;
align-items: center;
margin-bottom: 14rpx;
}
.logo {
width: 90rpx;
height: 90rpx;
border-radius: 18rpx;
margin-right: 16rpx;
}
.hero-text {
display: flex;
flex-direction: column;
}
.title {
font-size: 31rpx;
font-weight: 700;
color: #0f172a;
}
.subtitle {
margin-top: 4rpx;
font-size: 22rpx;
color: #94a3b8;
}
.manual-list {
height: calc(100vh - 170rpx);
}
.section-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 16rpx;
padding: 18rpx;
margin-bottom: 12rpx;
}
.section-title {
display: block;
font-size: 28rpx;
font-weight: 700;
color: #0f7f7a;
margin-bottom: 10rpx;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.label {
font-size: 24rpx;
color: #475569;
}
.value {
font-size: 24rpx;
color: #0f172a;
font-weight: 600;
}
.desc {
font-size: 24rpx;
color: #334155;
line-height: 1.65;
}
.bullet {
display: block;
font-size: 24rpx;
color: #334155;
line-height: 1.65;
margin-bottom: 6rpx;
}
.footer-tip {
text-align: center;
color: #94a3b8;
font-size: 22rpx;
padding: 16rpx 0 20rpx;
}
</style>

View File

@@ -0,0 +1,618 @@
<template>
<view class="container">
<view class="page-body uni-content-info">
<view class='cropper-content'>
<view v-if="isShowImg" class="uni-corpper" :style="'width:'+cropperInitW+'px;height:'+cropperInitH+'px;background:#000'">
<view class="uni-corpper-content" :style="'width:'+cropperW+'px;height:'+cropperH+'px;left:'+cropperL+'px;top:'+cropperT+'px'">
<image :src="imageSrc" :style="'width:'+cropperW+'px;height:'+cropperH+'px'"></image>
<view class="uni-corpper-crop-box" @touchstart.stop="contentStartMove" @touchmove.stop="contentMoveing" @touchend.stop="contentTouchEnd"
:style="'left:'+cutL+'px;top:'+cutT+'px;right:'+cutR+'px;bottom:'+cutB+'px'">
<view class="uni-cropper-view-box">
<view class="uni-cropper-dashed-h"></view>
<view class="uni-cropper-dashed-v"></view>
<view class="uni-cropper-line-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-line-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-line-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-line-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-tr" data-drag="topTight"></view>
<view class="uni-cropper-point point-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-rb" data-drag="rightBottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-bl" data-drag="bottomLeft"></view>
<view class="uni-cropper-point point-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-lt" data-drag="leftTop"></view>
</view>
</view>
</view>
</view>
</view>
<view class='cropper-config'>
<button type="primary reverse" @click="getImage" style='margin-top: 30rpx;'> 选择头像 </button>
<button type="warn" @click="getImageInfo" style='margin-top: 30rpx;'> 提交 </button>
</view>
<canvas canvas-id="myCanvas" :style="'position:absolute;border: 1px solid red; width:'+imageW+'px;height:'+imageH+'px;top:-9999px;left:-9999px;'"></canvas>
</view>
</view>
</template>
<script>
import config from '@/config'
import store from "@/store"
import { uploadAvatar } from "@/api/system/user"
const baseUrl = config.baseUrl
let sysInfo = uni.getSystemInfoSync()
let SCREEN_WIDTH = sysInfo.screenWidth
let PAGE_X, // 手按下的x位置
PAGE_Y, // 手按下y的位置
PR = sysInfo.pixelRatio, // dpi
T_PAGE_X, // 手移动的时候x的位置
T_PAGE_Y, // 手移动的时候Y的位置
CUT_L, // 初始化拖拽元素的left值
CUT_T, // 初始化拖拽元素的top值
CUT_R, // 初始化拖拽元素的
CUT_B, // 初始化拖拽元素的
CUT_W, // 初始化拖拽元素的宽度
CUT_H, // 初始化拖拽元素的高度
IMG_RATIO, // 图片比例
IMG_REAL_W, // 图片实际的宽度
IMG_REAL_H, // 图片实际的高度
DRAFG_MOVE_RATIO = 1, //移动时候的比例,
INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
DRAW_IMAGE_W = sysInfo.screenWidth // 设置生成的图片宽度
export default {
/**
* 页面的初始数据
*/
data() {
return {
imageSrc: store.getters.avatar,
isShowImg: false,
// 初始化的宽高
cropperInitW: SCREEN_WIDTH,
cropperInitH: SCREEN_WIDTH,
// 动态的宽高
cropperW: SCREEN_WIDTH,
cropperH: SCREEN_WIDTH,
// 动态的left top值
cropperL: 0,
cropperT: 0,
transL: 0,
transT: 0,
// 图片缩放值
scaleP: 0,
imageW: 0,
imageH: 0,
// 裁剪框 宽高
cutL: 0,
cutT: 0,
cutB: SCREEN_WIDTH,
cutR: '100%',
qualityWidth: DRAW_IMAGE_W,
innerAspectRadio: DRAFG_MOVE_RATIO
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
this.loadImage()
},
methods: {
setData: function (obj) {
let that = this
Object.keys(obj).forEach(function (key) {
that.$set(that.$data, key, obj[key])
})
},
getImage: function () {
var _this = this
uni.chooseImage({
success: function (res) {
_this.setData({
imageSrc: res.tempFilePaths[0],
})
_this.loadImage()
},
})
},
loadImage: function () {
var _this = this
uni.getImageInfo({
src: _this.imageSrc,
success: function success(res) {
IMG_RATIO = 1 / 1
if (IMG_RATIO >= 1) {
IMG_REAL_W = SCREEN_WIDTH
IMG_REAL_H = SCREEN_WIDTH / IMG_RATIO
} else {
IMG_REAL_W = SCREEN_WIDTH * IMG_RATIO
IMG_REAL_H = SCREEN_WIDTH
}
let minRange = IMG_REAL_W > IMG_REAL_H ? IMG_REAL_W : IMG_REAL_H
INIT_DRAG_POSITION = minRange > INIT_DRAG_POSITION ? INIT_DRAG_POSITION : minRange
// 根据图片的宽高显示不同的效果 保证图片可以正常显示
if (IMG_RATIO >= 1) {
let cutT = Math.ceil((SCREEN_WIDTH / IMG_RATIO - (SCREEN_WIDTH / IMG_RATIO - INIT_DRAG_POSITION)) / 2)
let cutB = cutT
let cutL = Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH + INIT_DRAG_POSITION) / 2)
let cutR = cutL
_this.setData({
cropperW: SCREEN_WIDTH,
cropperH: SCREEN_WIDTH / IMG_RATIO,
// 初始化left right
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH / IMG_RATIO) / 2),
cutL: cutL,
cutT: cutT,
cutR: cutR,
cutB: cutB,
// 图片缩放值
imageW: IMG_REAL_W,
imageH: IMG_REAL_H,
scaleP: IMG_REAL_W / SCREEN_WIDTH,
qualityWidth: DRAW_IMAGE_W,
innerAspectRadio: IMG_RATIO
})
} else {
let cutL = Math.ceil((SCREEN_WIDTH * IMG_RATIO - (SCREEN_WIDTH * IMG_RATIO)) / 2)
let cutR = cutL
let cutT = Math.ceil((SCREEN_WIDTH - INIT_DRAG_POSITION) / 2)
let cutB = cutT
_this.setData({
cropperW: SCREEN_WIDTH * IMG_RATIO,
cropperH: SCREEN_WIDTH,
// 初始化left right
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH * IMG_RATIO) / 2),
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
cutL: cutL,
cutT: cutT,
cutR: cutR,
cutB: cutB,
// 图片缩放值
imageW: IMG_REAL_W,
imageH: IMG_REAL_H,
scaleP: IMG_REAL_W / SCREEN_WIDTH,
qualityWidth: DRAW_IMAGE_W,
innerAspectRadio: IMG_RATIO
})
}
_this.setData({
isShowImg: true
})
uni.hideLoading()
}
})
},
// 拖动时候触发的touchStart事件
contentStartMove(e) {
PAGE_X = e.touches[0].pageX
PAGE_Y = e.touches[0].pageY
},
// 拖动时候触发的touchMove事件
contentMoveing(e) {
var _this = this
var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
// 左移
if (dragLengthX > 0) {
if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL
} else {
if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR
}
if (dragLengthY > 0) {
if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT
} else {
if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB
}
this.setData({
cutL: this.cutL - dragLengthX,
cutT: this.cutT - dragLengthY,
cutR: this.cutR + dragLengthX,
cutB: this.cutB + dragLengthY
})
PAGE_X = e.touches[0].pageX
PAGE_Y = e.touches[0].pageY
},
contentTouchEnd() {
},
// 获取图片
getImageInfo() {
var _this = this
uni.showLoading({
title: '图片生成中...',
})
// 将图片写入画布
const ctx = uni.createCanvasContext('myCanvas')
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H)
ctx.draw(true, () => {
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H
uni.canvasToTempFilePath({
x: canvasL,
y: canvasT,
width: canvasW,
height: canvasH,
destWidth: canvasW,
destHeight: canvasH,
quality: 0.5,
canvasId: 'myCanvas',
success: function (res) {
uni.hideLoading()
let data = {name: 'avatarfile', filePath: res.tempFilePath}
uploadAvatar(data).then(response => {
store.commit('SET_AVATAR', baseUrl + response.imgUrl)
uni.showToast({ title: "修改成功", icon: 'success' })
uni.navigateBack()
})
}
})
})
},
// 设置大小的时候触发的touchStart事件
dragStart(e) {
T_PAGE_X = e.touches[0].pageX
T_PAGE_Y = e.touches[0].pageY
CUT_L = this.cutL
CUT_R = this.cutR
CUT_B = this.cutB
CUT_T = this.cutT
},
// 设置大小的时候触发的touchMove事件
dragMove(e) {
var _this = this
var dragType = e.target.dataset.drag
switch (dragType) {
case 'right':
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
if (CUT_R + dragLength < 0) dragLength = -CUT_R
this.setData({
cutR: CUT_R + dragLength
})
break
case 'left':
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
if (CUT_L - dragLength < 0) dragLength = CUT_L
if ((CUT_L - dragLength) > (this.cropperW - this.cutR)) dragLength = CUT_L - (this.cropperW - this.cutR)
this.setData({
cutL: CUT_L - dragLength
})
break
case 'top':
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
if (CUT_T - dragLength < 0) dragLength = CUT_T
if ((CUT_T - dragLength) > (this.cropperH - this.cutB)) dragLength = CUT_T - (this.cropperH - this.cutB)
this.setData({
cutT: CUT_T - dragLength
})
break
case 'bottom':
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
if (CUT_B + dragLength < 0) dragLength = -CUT_B
this.setData({
cutB: CUT_B + dragLength
})
break
case 'rightBottom':
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R
let cutB = CUT_B + dragLengthY
let cutR = CUT_R + dragLengthX
this.setData({
cutB: cutB,
cutR: cutR
})
break
default:
break
}
}
}
}
</script>
<style scoped>
.cropper-config {
padding: 20rpx 40rpx;
}
.cropper-content {
min-height: 750rpx;
width: 100%;
}
.uni-corpper {
position: relative;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
box-sizing: border-box;
}
.uni-corpper-content {
position: relative;
}
.uni-corpper-content image {
display: block;
width: 100%;
min-width: 0 !important;
max-width: none !important;
height: 100%;
min-height: 0 !important;
max-height: none !important;
image-orientation: 0deg !important;
margin: 0 auto;
}
/* 移动图片效果 */
.uni-cropper-drag-box {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
cursor: move;
background: rgba(0, 0, 0, 0.6);
z-index: 1;
}
/* 内部的信息 */
.uni-corpper-crop-box {
position: absolute;
background: rgba(255, 255, 255, 0.3);
z-index: 2;
}
.uni-corpper-crop-box .uni-cropper-view-box {
position: relative;
display: block;
width: 100%;
height: 100%;
overflow: visible;
outline: 1rpx solid #69f;
outline-color: rgba(102, 153, 255, .75)
}
/* 横向虚线 */
.uni-cropper-dashed-h {
position: absolute;
top: 33.33333333%;
left: 0;
width: 100%;
height: 33.33333333%;
border-top: 1rpx dashed rgba(255, 255, 255, 0.5);
border-bottom: 1rpx dashed rgba(255, 255, 255, 0.5);
}
/* 纵向虚线 */
.uni-cropper-dashed-v {
position: absolute;
left: 33.33333333%;
top: 0;
width: 33.33333333%;
height: 100%;
border-left: 1rpx dashed rgba(255, 255, 255, 0.5);
border-right: 1rpx dashed rgba(255, 255, 255, 0.5);
}
/* 四个方向的线 为了之后的拖动事件*/
.uni-cropper-line-t {
position: absolute;
display: block;
width: 100%;
background-color: #69f;
top: 0;
left: 0;
height: 1rpx;
opacity: 0.1;
cursor: n-resize;
}
.uni-cropper-line-t::before {
content: '';
position: absolute;
top: 50%;
right: 0rpx;
width: 100%;
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
bottom: 0;
height: 41rpx;
background: transparent;
z-index: 11;
}
.uni-cropper-line-r {
position: absolute;
display: block;
background-color: #69f;
top: 0;
right: 0rpx;
width: 1rpx;
opacity: 0.1;
height: 100%;
cursor: e-resize;
}
.uni-cropper-line-r::before {
content: '';
position: absolute;
top: 0;
left: 50%;
width: 41rpx;
-webkit-transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0);
bottom: 0;
height: 100%;
background: transparent;
z-index: 11;
}
.uni-cropper-line-b {
position: absolute;
display: block;
width: 100%;
background-color: #69f;
bottom: 0;
left: 0;
height: 1rpx;
opacity: 0.1;
cursor: s-resize;
}
.uni-cropper-line-b::before {
content: '';
position: absolute;
top: 50%;
right: 0rpx;
width: 100%;
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
bottom: 0;
height: 41rpx;
background: transparent;
z-index: 11;
}
.uni-cropper-line-l {
position: absolute;
display: block;
background-color: #69f;
top: 0;
left: 0;
width: 1rpx;
opacity: 0.1;
height: 100%;
cursor: w-resize;
}
.uni-cropper-line-l::before {
content: '';
position: absolute;
top: 0;
left: 50%;
width: 41rpx;
-webkit-transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0);
bottom: 0;
height: 100%;
background: transparent;
z-index: 11;
}
.uni-cropper-point {
width: 5rpx;
height: 5rpx;
background-color: #69f;
opacity: .75;
position: absolute;
z-index: 3;
}
.point-t {
top: -3rpx;
left: 50%;
margin-left: -3rpx;
cursor: n-resize;
}
.point-tr {
top: -3rpx;
left: 100%;
margin-left: -3rpx;
cursor: n-resize;
}
.point-r {
top: 50%;
left: 100%;
margin-left: -3rpx;
margin-top: -3rpx;
cursor: n-resize;
}
.point-rb {
left: 100%;
top: 100%;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
cursor: n-resize;
width: 36rpx;
height: 36rpx;
background-color: #69f;
position: absolute;
z-index: 1112;
opacity: 1;
}
.point-b {
left: 50%;
top: 100%;
margin-left: -3rpx;
margin-top: -3rpx;
cursor: n-resize;
}
.point-bl {
left: 0%;
top: 100%;
margin-left: -3rpx;
margin-top: -3rpx;
cursor: n-resize;
}
.point-l {
left: 0%;
top: 50%;
margin-left: -3rpx;
margin-top: -3rpx;
cursor: n-resize;
}
.point-lt {
left: 0%;
top: 0%;
margin-left: -3rpx;
margin-top: -3rpx;
cursor: n-resize;
}
/* 裁剪框预览内容 */
.uni-cropper-viewer {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.uni-cropper-viewer image {
position: absolute;
z-index: 2;
}
</style>

View File

@@ -0,0 +1,199 @@
<template>
<view class="record-page">
<view class="header-card">
<text class="title">使用记录</text>
<text class="sub">最近设备连接与控制行为</text>
</view>
<scroll-view class="record-list" scroll-y>
<view v-if="records.length === 0" class="empty-card">
<text class="empty-title">暂无使用记录</text>
<text class="empty-sub">连接设备并执行操作后会自动生成记录</text>
</view>
<view v-for="item in records" :key="item.id" class="record-card">
<view class="row top">
<text class="device-name">{{ item.deviceName }}</text>
<text class="time">{{ item.time }}</text>
</view>
<view class="row middle">
<text class="device-id">{{ item.deviceId }}</text>
<text class="status" :class="item.statusClass">{{ item.statusText }}</text>
</view>
<view class="event-wrap">
<text class="event-tag">{{ item.eventType }}</text>
<text class="event-desc">{{ item.desc }}</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
records: [
{
id: 1,
deviceName: 'Attractor 主设备',
deviceId: '8A:E5:FC:D4:C0:50',
time: '2026-03-27 14:18',
eventType: '连接',
desc: '设备连接成功并进入参数页面',
statusText: '成功',
statusClass: 'ok'
},
{
id: 2,
deviceName: 'Attractor 主设备',
deviceId: '8A:E5:FC:D4:C0:50',
time: '2026-03-27 14:22',
eventType: '快捷启动',
desc: '首页快捷启动指令发送完成0x30/0x01',
statusText: '成功',
statusClass: 'ok'
},
{
id: 3,
deviceName: 'Attractor 主设备',
deviceId: '8A:E5:FC:D4:C0:50',
time: '2026-03-27 14:26',
eventType: '快捷停止',
desc: '首页快捷停止指令发送完成0x30/0x00',
statusText: '成功',
statusClass: 'ok'
}
]
}
}
}
</script>
<style scoped>
.record-page {
min-height: 100vh;
background: #f4f7f8;
padding: 20rpx;
box-sizing: border-box;
}
.header-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 14rpx;
}
.title {
display: block;
font-size: 32rpx;
font-weight: 700;
color: #0f172a;
}
.sub {
display: block;
margin-top: 4rpx;
font-size: 22rpx;
color: #94a3b8;
}
.record-list {
height: calc(100vh - 150rpx);
}
.record-card {
background: #ffffff;
border: 1rpx solid #e7eeef;
border-radius: 16rpx;
padding: 18rpx;
margin-bottom: 12rpx;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
}
.row.top {
margin-bottom: 8rpx;
}
.row.middle {
margin-bottom: 10rpx;
}
.device-name {
font-size: 26rpx;
font-weight: 600;
color: #0f172a;
}
.time {
font-size: 21rpx;
color: #94a3b8;
}
.device-id {
font-size: 22rpx;
color: #64748b;
}
.status {
font-size: 20rpx;
border-radius: 999rpx;
padding: 4rpx 12rpx;
}
.status.ok {
color: #0f7f7a;
background: #e6f5f4;
}
.event-wrap {
display: flex;
gap: 10rpx;
align-items: flex-start;
}
.event-tag {
font-size: 21rpx;
color: #0f7f7a;
background: #f0fdfa;
border-radius: 8rpx;
padding: 4rpx 10rpx;
white-space: nowrap;
}
.event-desc {
font-size: 23rpx;
color: #334155;
line-height: 1.55;
}
.empty-card {
background: #ffffff;
border: 1rpx dashed #d6dde3;
border-radius: 16rpx;
padding: 36rpx 20rpx;
text-align: center;
}
.empty-title {
display: block;
font-size: 26rpx;
color: #64748b;
}
.empty-sub {
display: block;
margin-top: 6rpx;
font-size: 22rpx;
color: #94a3b8;
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<view class="mine-page">
<view class="profile-card">
<image class="avatar" :src="avatar" mode="aspectFill"></image>
<view class="profile-info">
<text class="name">{{ name }}</text>
<text class="desc">Attractor 用户</text>
</view>
</view>
<view class="menu-section">
<view class="menu-item" @click="goToDevice">
<view class="left">
<uni-icons type="gear" size="20" color="#1f9f9a"></uni-icons>
<text class="label">我的设备</text>
</view>
<uni-icons type="right" size="16" color="#94a3b8"></uni-icons>
</view>
<view class="menu-item" @click="goToNotice">
<view class="left">
<uni-icons type="notification" size="20" color="#1f9f9a"></uni-icons>
<text class="label">公告中心</text>
</view>
<uni-icons type="right" size="16" color="#94a3b8"></uni-icons>
</view>
<view class="menu-item" @click="goToUsageRecord">
<view class="left">
<uni-icons type="calendar" size="20" color="#1f9f9a"></uni-icons>
<text class="label">使用记录</text>
</view>
<uni-icons type="right" size="16" color="#94a3b8"></uni-icons>
</view>
<view class="menu-item" @click="goToDeviceGuide">
<view class="left">
<uni-icons type="help" size="20" color="#1f9f9a"></uni-icons>
<text class="label">设备说明</text>
</view>
<uni-icons type="right" size="16" color="#94a3b8"></uni-icons>
</view>
<view class="menu-item" @click="resetDeviceFactory">
<view class="left">
<uni-icons type="reload" size="20" color="#ef4444"></uni-icons>
<text class="label danger">恢复出厂</text>
</view>
<uni-icons type="right" size="16" color="#94a3b8"></uni-icons>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
name: 'Attractor_001',
avatar: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png'
}
},
methods: {
goToDevice() {
uni.navigateTo({
url: '/pages/devices/master'
})
},
goToNotice() {
uni.switchTab({
url: '/pages/work/index'
})
},
goToUsageRecord() {
uni.navigateTo({
url: '/pages/mine/help/index'
})
},
goToDeviceGuide() {
uni.navigateTo({
url: '/pages/mine/about/index'
})
},
resetDeviceFactory() {
uni.showModal({
title: '恢复出厂',
content: '确认恢复出厂设置吗?该操作无法撤销。',
confirmText: '确认恢复',
confirmColor: '#ef4444',
success: res => {
if (res.confirm) {
uni.showToast({ title: '已提交恢复指令', icon: 'none' })
}
}
})
}
}
}
</script>
<style scoped>
.mine-page {
min-height: 100vh;
background: #f4f7f8;
padding: 24rpx;
box-sizing: border-box;
}
.profile-card {
background: #ffffff;
border-radius: 20rpx;
padding: 24rpx;
display: flex;
align-items: center;
margin-bottom: 18rpx;
border: 1rpx solid #e7eeef;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 20rpx;
background: #f2f4f7;
}
.profile-info {
display: flex;
flex-direction: column;
}
.name {
font-size: 34rpx;
font-weight: 700;
color: #0f172a;
}
.desc {
font-size: 24rpx;
color: #94a3b8;
margin-top: 6rpx;
}
.menu-section {
background: #ffffff;
border-radius: 20rpx;
border: 1rpx solid #e7eeef;
padding: 6rpx 0;
}
.menu-item {
height: 92rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.menu-item + .menu-item {
border-top: 1rpx solid #f1f5f9;
}
.left {
display: flex;
align-items: center;
gap: 12rpx;
}
.label {
font-size: 28rpx;
color: #1e293b;
}
.label.danger {
color: #ef4444;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<view class="container">
<view class="example">
<uni-forms ref="form" :model="user" labelWidth="80px">
<uni-forms-item label="用户昵称" name="nickName">
<uni-easyinput v-model="user.nickName" placeholder="请输入昵称" />
</uni-forms-item>
<uni-forms-item label="手机号码" name="phonenumber">
<uni-easyinput v-model="user.phonenumber" placeholder="请输入手机号码" />
</uni-forms-item>
<uni-forms-item label="邮箱" name="email">
<uni-easyinput v-model="user.email" placeholder="请输入邮箱" />
</uni-forms-item>
<uni-forms-item label="性别" name="sex" required>
<uni-data-checkbox v-model="user.sex" :localdata="sexs" />
</uni-forms-item>
</uni-forms>
<button type="primary" @click="submit">提交</button>
</view>
</view>
</template>
<script>
import { getUserProfile } from "@/api/system/user"
import { updateUserProfile } from "@/api/system/user"
export default {
data() {
return {
user: {
nickName: "",
phonenumber: "",
email: "",
sex: ""
},
sexs: [{
text: '男',
value: "0"
}, {
text: '女',
value: "1"
}],
rules: {
nickName: {
rules: [{
required: true,
errorMessage: '用户昵称不能为空'
}]
},
phonenumber: {
rules: [{
required: true,
errorMessage: '手机号码不能为空'
}, {
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
errorMessage: '请输入正确的手机号码'
}]
},
email: {
rules: [{
required: true,
errorMessage: '邮箱地址不能为空'
}, {
format: 'email',
errorMessage: '请输入正确的邮箱地址'
}]
}
}
}
},
onLoad() {
this.getUser()
},
onReady() {
this.$refs.form.setRules(this.rules)
},
methods: {
getUser() {
getUserProfile().then(response => {
this.user = response.data
})
},
submit(ref) {
this.$refs.form.validate().then(res => {
updateUserProfile(this.user).then(response => {
this.$modal.msgSuccess("修改成功")
})
})
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #ffffff;
}
.example {
padding: 15px;
background-color: #fff;
}
.segmented-control {
margin-bottom: 15px;
}
.button-group {
margin-top: 15px;
display: flex;
justify-content: space-around;
}
.form-item {
display: flex;
align-items: center;
flex: 1;
}
.button {
display: flex;
align-items: center;
height: 35px;
line-height: 35px;
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<view class="container">
<uni-list>
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'person-filled'}" title="昵称" :rightText="user.nickName" />
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'phone-filled'}" title="手机号码" :rightText="user.phonenumber" />
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'email-filled'}" title="邮箱" :rightText="user.email" />
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'auth-filled'}" title="岗位" :rightText="postGroup" />
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'staff-filled'}" title="角色" :rightText="roleGroup" />
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'calendar-filled'}" title="创建日期" :rightText="user.createTime" />
</uni-list>
</view>
</template>
<script>
import { getUserProfile } from "@/api/system/user"
export default {
data() {
return {
user: {},
roleGroup: "",
postGroup: ""
}
},
onLoad() {
this.getUser()
},
methods: {
getUser() {
getUserProfile().then(response => {
this.user = response.data
this.roleGroup = response.roleGroup
this.postGroup = response.postGroup
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #ffffff;
}
</style>

View File

@@ -0,0 +1,85 @@
<template>
<view class="pwd-retrieve-container">
<uni-forms ref="form" :value="user" labelWidth="80px">
<uni-forms-item name="oldPassword" label="旧密码">
<uni-easyinput type="password" v-model="user.oldPassword" placeholder="请输入旧密码" />
</uni-forms-item>
<uni-forms-item name="newPassword" label="新密码">
<uni-easyinput type="password" v-model="user.newPassword" placeholder="请输入新密码" />
</uni-forms-item>
<uni-forms-item name="confirmPassword" label="确认密码">
<uni-easyinput type="password" v-model="user.confirmPassword" placeholder="请确认新密码" />
</uni-forms-item>
<button type="primary" @click="submit">提交</button>
</uni-forms>
</view>
</template>
<script>
import { updateUserPwd } from "@/api/system/user"
export default {
data() {
return {
user: {
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined
},
rules: {
oldPassword: {
rules: [{
required: true,
errorMessage: '旧密码不能为空'
}]
},
newPassword: {
rules: [{
required: true,
errorMessage: '新密码不能为空',
},
{
minLength: 6,
maxLength: 20,
errorMessage: '长度在 6 到 20 个字符'
}
]
},
confirmPassword: {
rules: [{
required: true,
errorMessage: '确认密码不能为空'
}, {
validateFunction: (rule, value, data) => data.newPassword === value,
errorMessage: '两次输入的密码不一致'
}
]
}
}
}
},
onReady() {
this.$refs.form.setRules(this.rules)
},
methods: {
submit() {
this.$refs.form.validate().then(res => {
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => {
this.$modal.msgSuccess("修改成功")
})
})
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #ffffff;
}
.pwd-retrieve-container {
padding-top: 36rpx;
padding: 15px;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<view class="setting-container" :style="{height: `${windowHeight}px`}">
<view class="menu-list">
<view class="list-cell list-cell-arrow" @click="handleToPwd">
<view class="menu-item-box">
<view class="iconfont icon-password menu-icon"></view>
<view>修改密码</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleToUpgrade">
<view class="menu-item-box">
<view class="iconfont icon-refresh menu-icon"></view>
<view>检查更新</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleCleanTmp">
<view class="menu-item-box">
<view class="iconfont icon-clean menu-icon"></view>
<view>清理缓存</view>
</view>
</view>
</view>
<view class="cu-list menu">
<view class="cu-item item-box">
<view class="content text-center" @click="handleLogout">
<text class="text-black">退出登录</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
windowHeight: uni.getSystemInfoSync().windowHeight
}
},
methods: {
handleToPwd() {
this.$tab.navigateTo('/pages/mine/pwd/index')
},
handleToUpgrade() {
this.$modal.showToast('模块建设中~')
},
handleCleanTmp() {
this.$modal.showToast('模块建设中~')
},
handleLogout() {
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
this.$store.dispatch('LogOut').then(() => {}).finally(()=>{
this.$tab.reLaunch('/pages/index')
})
})
}
}
}
</script>
<style lang="scss" scoped>
.page {
background-color: #f8f8f8;
}
.item-box {
background-color: #FFFFFF;
margin: 30rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 10rpx;
border-radius: 8rpx;
color: #303133;
font-size: 32rpx;
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<view class="normal-login-container">
<view class="logo-content align-center justify-center flex">
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
</image>
<text class="title">若依移动端注册</text>
</view>
<view class="login-form-content">
<view class="input-item flex align-center">
<view class="iconfont icon-user icon"></view>
<input v-model="registerForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
</view>
<view class="input-item flex align-center">
<view class="iconfont icon-password icon"></view>
<input v-model="registerForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
</view>
<view class="input-item flex align-center">
<view class="iconfont icon-password icon"></view>
<input v-model="registerForm.confirmPassword" type="password" class="input" placeholder="请输入重复密码" maxlength="20" />
</view>
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
<view class="iconfont icon-code icon"></view>
<input v-model="registerForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
<view class="login-code">
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
</view>
</view>
<view class="action-btn">
<button @click="handleRegister()" class="register-btn cu-btn block bg-blue lg round">注册</button>
</view>
</view>
<view class="xieyi text-center">
<text @click="handleUserLogin" class="text-blue">使用已有账号登录</text>
</view>
</view>
</template>
<script>
import { getCodeImg, register } from '@/api/login'
export default {
data() {
return {
codeUrl: "",
captchaEnabled: true,
globalConfig: getApp().globalData.config,
registerForm: {
username: "",
password: "",
confirmPassword: "",
code: "",
uuid: ""
}
}
},
created() {
this.getCode()
},
methods: {
// 用户登录
handleUserLogin() {
this.$tab.navigateTo(`/pages/login`)
},
// 获取图形验证码
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (this.captchaEnabled) {
this.codeUrl = 'data:image/gif;base64,' + res.img
this.registerForm.uuid = res.uuid
}
})
},
// 注册方法
async handleRegister() {
if (this.registerForm.username === "") {
this.$modal.msgError("请输入您的账号")
} else if (this.registerForm.password === "") {
this.$modal.msgError("请输入您的密码")
} else if (this.registerForm.confirmPassword === "") {
this.$modal.msgError("请再次输入您的密码")
} else if (this.registerForm.password !== this.registerForm.confirmPassword) {
this.$modal.msgError("两次输入的密码不一致")
} else if (this.registerForm.code === "" && this.captchaEnabled) {
this.$modal.msgError("请输入验证码")
} else {
this.$modal.loading("注册中,请耐心等待...")
this.register()
}
},
// 用户注册
async register() {
register(this.registerForm).then(res => {
this.$modal.closeLoading()
uni.showModal({
title: "系统提示",
content: "恭喜你,您的账号 " + this.registerForm.username + " 注册成功!",
success: function (res) {
if (res.confirm) {
uni.redirectTo({ url: `/pages/login` });
}
}
})
}).catch(() => {
if (this.captchaEnabled) {
this.getCode()
}
})
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #ffffff;
}
.normal-login-container {
width: 100%;
.logo-content {
width: 100%;
font-size: 21px;
text-align: center;
padding-top: 15%;
image {
border-radius: 4px;
}
.title {
margin-left: 10px;
}
}
.login-form-content {
text-align: center;
margin: 20px auto;
margin-top: 15%;
width: 80%;
.input-item {
margin: 20px auto;
background-color: #f5f6f7;
height: 45px;
border-radius: 20px;
.icon {
font-size: 38rpx;
margin-left: 10px;
color: #999;
}
.input {
width: 100%;
font-size: 14px;
line-height: 20px;
text-align: left;
padding-left: 15px;
}
}
.register-btn {
margin-top: 40px;
height: 45px;
}
.xieyi {
color: #333;
margin-top: 20px;
}
.login-code {
height: 38px;
float: right;
.login-code-img {
height: 38px;
position: absolute;
margin-left: 10px;
width: 200rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,22 @@
<template>
<view>
团队配置
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,22 @@
<template>
<view>
团队管理
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,208 @@
<template>
<view class="notice-page">
<scroll-view class="notice-list" scroll-y>
<view v-for="item in announcementList" :key="item.id" class="notice-card">
<view class="notice-head">
<image class="avatar" :src="item.avatar" mode="aspectFill" />
<view class="author-wrap">
<text class="author">{{ item.author }}</text>
<text class="publish-time">{{ item.publishTime }}</text>
</view>
<view class="type-tag">{{ item.type }}</view>
</view>
<text class="notice-title">{{ item.title }}</text>
<text class="notice-content">{{ item.content }}</text>
<view v-if="item.keywords && item.keywords.length" class="keyword-row">
<text v-for="k in item.keywords" :key="k" class="keyword">#{{ k }}</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
announcementList: [
{
id: 1,
author: '官方公告',
avatar: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png',
type: '版本更新',
title: 'V2.3.0 上线:设备连接流程与控制链路全面优化',
content: '本次版本重点优化了蓝牙连接时序与重连稳定性,新增设备连接历史缓存、候选设备自动扫描与连接确认流程,同时对参数页布局进行重构。若你在旧版本中遇到“连接成功但不可控”的情况,建议升级后重新绑定一次设备。',
publishTime: '2026-03-27 10:30',
keywords: ['连接优化', '稳定性', 'UI重构']
},
{
id: 2,
author: '官方公告',
avatar: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png',
type: '功能调整',
title: '设备页交互升级:新增首页快捷启停与运行状态同步',
content: '为了减少操作路径,首页“我的设备”现已支持快捷启动/停止。操作成功后将同步更新设备状态标签(运行中/已停止),并写入最近控制时间。若首次点击出现等待,请保持设备开机并靠近手机,系统会自动完成连接补偿后发送指令。',
publishTime: '2026-03-27 11:15',
keywords: ['快捷控制', '状态同步', '设备页']
},
{
id: 3,
author: '官方公告',
avatar: '/static/images/app_icon_pack_schemeA_tealLogo/master_1024.png',
type: '体验优化',
title: '公告中心改版:支持顶部搜索与精准筛选',
content: '公告中心已由“消息流”改造为“公告流”,顶部新增搜索入口,可按标题、正文关键词和标签快速检索历史公告。后续我们还会补充“置顶公告”“版本分类”和“发布时间筛选”能力,便于你快速定位需要的信息。',
publishTime: '2026-03-27 12:05',
keywords: ['公告中心', '搜索', '信息检索']
}
]
}
},
onLoad() {
uni.setStorageSync('ANNOUNCEMENT_LIST', this.announcementList)
},
methods: {
goSearch() {
uni.navigateTo({
url: '/pages/work/search'
})
}
}
}
</script>
<style scoped>
.notice-page {
min-height: 100vh;
background: #f4f6f9;
padding: 18rpx;
box-sizing: border-box;
}
.top-nav {
background: #ffffff;
border: 1rpx solid #edf2f7;
border-radius: 18rpx;
padding: 18rpx;
margin-bottom: 16rpx;
}
.nav-title-wrap {
margin-bottom: 14rpx;
}
.nav-title {
font-size: 34rpx;
font-weight: 700;
color: #111827;
}
.nav-sub {
font-size: 22rpx;
color: #94a3b8;
margin-top: 4rpx;
display: block;
}
.search-entry {
height: 64rpx;
border: 1rpx solid #e5e7eb;
border-radius: 14rpx;
background: #f8fafc;
display: flex;
align-items: center;
padding: 0 16rpx;
gap: 10rpx;
}
.search-entry-text {
font-size: 24rpx;
color: #64748b;
}
.notice-list {
height: calc(100vh - 210rpx);
box-sizing: border-box;
}
.notice-card {
background: #ffffff;
border-radius: 18rpx;
border: 1rpx solid #edf2f7;
padding: 20rpx;
margin-bottom: 14rpx;
}
.notice-head {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.avatar {
width: 58rpx;
height: 58rpx;
border-radius: 50%;
margin-right: 10rpx;
}
.author-wrap {
flex: 1;
display: flex;
flex-direction: column;
}
.author {
font-size: 25rpx;
color: #111827;
font-weight: 600;
}
.publish-time {
font-size: 21rpx;
color: #9ca3af;
margin-top: 2rpx;
}
.type-tag {
padding: 4rpx 10rpx;
border-radius: 8rpx;
background: #ecfeff;
color: #0f766e;
font-size: 20rpx;
}
.notice-title {
font-size: 29rpx;
color: #0f172a;
font-weight: 700;
line-height: 1.4;
margin-bottom: 10rpx;
display: block;
}
.notice-content {
font-size: 25rpx;
color: #334155;
line-height: 1.65;
}
.keyword-row {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 12rpx;
}
.keyword {
font-size: 20rpx;
color: #0f766e;
background: #f0fdfa;
border-radius: 8rpx;
padding: 4rpx 10rpx;
}
</style>

View File

@@ -0,0 +1,207 @@
<template>
<view class="search-page">
<view class="search-top">
<view class="search-bar">
<uni-icons type="search" size="16" color="#6B7280"></uni-icons>
<input
class="search-input"
v-model="keyword"
placeholder="搜索公告标题、正文、标签"
confirm-type="search"
@confirm="onSearch"
/>
<button v-if="keyword" class="clear-btn" @click="clearKeyword">×</button>
</view>
<text class="cancel" @click="goBack">取消</text>
</view>
<scroll-view class="result-list" scroll-y>
<view v-if="filteredList.length === 0" class="empty-block">
<text class="empty-title">未找到相关公告</text>
<text class="empty-sub">试试更短关键词或更换标签词</text>
</view>
<view v-for="item in filteredList" :key="item.id" class="result-card">
<view class="result-head">
<text class="result-type">{{ item.type }}</text>
<text class="result-time">{{ item.publishTime }}</text>
</view>
<text class="result-title">{{ item.title }}</text>
<text class="result-content">{{ item.content }}</text>
<view class="result-keyword-row" v-if="item.keywords && item.keywords.length">
<text v-for="k in item.keywords" :key="k" class="result-keyword">#{{ k }}</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
keyword: '',
announcementList: []
}
},
computed: {
filteredList() {
const k = this.keyword.trim().toLowerCase()
if (!k) return this.announcementList
return this.announcementList.filter(item => {
const keywordHit = Array.isArray(item.keywords)
? item.keywords.some(tag => (tag || '').toLowerCase().includes(k))
: false
return (
(item.type || '').toLowerCase().includes(k) ||
(item.title || '').toLowerCase().includes(k) ||
(item.content || '').toLowerCase().includes(k) ||
keywordHit
)
})
}
},
onLoad() {
const list = uni.getStorageSync('ANNOUNCEMENT_LIST')
this.announcementList = Array.isArray(list) ? list : []
},
methods: {
onSearch() {},
clearKeyword() {
this.keyword = ''
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style scoped>
.search-page {
min-height: 100vh;
background: #f4f6f9;
padding: 18rpx;
box-sizing: border-box;
}
.search-top {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 16rpx;
}
.search-bar {
flex: 1;
display: flex;
align-items: center;
gap: 10rpx;
background: #ffffff;
border: 1rpx solid #e5e7eb;
border-radius: 14rpx;
padding: 0 14rpx;
height: 68rpx;
}
.search-input {
flex: 1;
font-size: 25rpx;
color: #111827;
}
.clear-btn {
background: transparent;
border: none;
color: #94a3b8;
font-size: 38rpx;
line-height: 1;
padding: 0;
}
.cancel {
color: #0f766e;
font-size: 25rpx;
}
.result-list {
height: calc(100vh - 110rpx);
}
.result-card {
background: #ffffff;
border: 1rpx solid #edf2f7;
border-radius: 16rpx;
padding: 18rpx;
margin-bottom: 12rpx;
}
.result-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.result-type {
font-size: 20rpx;
color: #0f766e;
background: #f0fdfa;
border-radius: 8rpx;
padding: 4rpx 8rpx;
}
.result-time {
font-size: 20rpx;
color: #9ca3af;
}
.result-title {
display: block;
font-size: 27rpx;
color: #0f172a;
font-weight: 700;
line-height: 1.4;
margin-bottom: 8rpx;
}
.result-content {
display: block;
font-size: 24rpx;
color: #334155;
line-height: 1.6;
}
.result-keyword-row {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 10rpx;
}
.result-keyword {
font-size: 20rpx;
color: #0f766e;
background: #f0fdfa;
border-radius: 8rpx;
padding: 3rpx 8rpx;
}
.empty-block {
text-align: center;
padding: 90rpx 0;
}
.empty-title {
display: block;
font-size: 28rpx;
color: #64748b;
margin-bottom: 10rpx;
}
.empty-sub {
display: block;
font-size: 22rpx;
color: #94a3b8;
}
</style>

View File

@@ -0,0 +1,44 @@
import { getToken } from '@/utils/auth'
// 登录页面
const loginPage = '/pages/login'
// 页面白名单
const whiteList = [
'/pages/login',
'/pages/register',
'/pages/common/webview/index'
]
// 检查地址白名单
function checkWhite(url) {
const path = (url || '').split('?')[0]
return whiteList.indexOf(path) !== -1
}
// 页面跳转验证拦截器
const list = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']
list.forEach(item => {
uni.addInterceptor(item, {
invoke(to) {
const hasToken = !!getToken()
if (hasToken) {
if (to.url === loginPage) {
uni.reLaunch({ url: '/pages/devices/devices' })
return false
}
return true
}
if (checkWhite(to.url)) {
return true
}
uni.reLaunch({ url: loginPage })
return false
},
fail(err) {
console.log(err)
}
})
})

View File

@@ -0,0 +1,60 @@
import store from '@/store'
function authPermission(permission) {
const all_permission = "*:*:*"
const permissions = store.getters && store.getters.permissions
if (permission && permission.length > 0) {
return permissions.some(v => {
return all_permission === v || v === permission
})
} else {
return false
}
}
function authRole(role) {
const super_admin = "admin"
const roles = store.getters && store.getters.roles
if (role && role.length > 0) {
return roles.some(v => {
return super_admin === v || v === role
})
} else {
return false
}
}
export default {
// 验证用户是否具备某权限
hasPermi(permission) {
return authPermission(permission)
},
// 验证用户是否含有指定权限,只需包含其中一个
hasPermiOr(permissions) {
return permissions.some(item => {
return authPermission(item)
})
},
// 验证用户是否含有指定权限,必须全部拥有
hasPermiAnd(permissions) {
return permissions.every(item => {
return authPermission(item)
})
},
// 验证用户是否具备某角色
hasRole(role) {
return authRole(role)
},
// 验证用户是否含有指定角色,只需包含其中一个
hasRoleOr(roles) {
return roles.some(item => {
return authRole(item)
})
},
// 验证用户是否含有指定角色,必须全部拥有
hasRoleAnd(roles) {
return roles.every(item => {
return authRole(item)
})
}
}

View File

@@ -0,0 +1,14 @@
import tab from './tab'
import auth from './auth'
import modal from './modal'
export default {
install(Vue) {
// 页签操作
Vue.prototype.$tab = tab
// 认证对象
Vue.prototype.$auth = auth
// 模态框对象
Vue.prototype.$modal = modal
}
}

View File

@@ -0,0 +1,78 @@
export default {
// 消息提示
msg(content) {
uni.showToast({
title: content,
icon: 'none'
})
},
// 错误消息
msgError(content) {
uni.showToast({
title: content,
icon: 'error'
})
},
// 成功消息
msgSuccess(content) {
uni.showToast({
title: content,
icon: 'success'
})
},
// 隐藏消息
hideMsg(content) {
uni.hideToast()
},
// 弹出提示
alert(content, title) {
uni.showModal({
title: title || '系统提示',
content: content,
showCancel: false
})
},
// 确认窗体
confirm(content, title) {
return new Promise((resolve, reject) => {
uni.showModal({
title: title || '系统提示',
content: content,
cancelText: '取消',
confirmText: '确定',
success: function(res) {
if (res.confirm) {
resolve(res.confirm)
}
}
})
})
},
// 提示信息
showToast(option) {
if (typeof option === "object") {
uni.showToast(option)
} else {
uni.showToast({
title: option,
icon: "none",
duration: 2500
})
}
},
// 打开遮罩层
loading(content) {
uni.showLoading({
title: content,
icon: 'none'
})
},
// 关闭遮罩层
closeLoading() {
try {
uni.hideLoading()
} catch (e) {
console.log(e)
}
}
}

View File

@@ -0,0 +1,30 @@
export default {
// 关闭所有页面,打开到应用内的某个页面
reLaunch(url) {
return uni.reLaunch({
url: url
})
},
// 跳转到tabBar页面并关闭其他所有非tabBar页面
switchTab(url) {
return uni.switchTab({
url: url
})
},
// 关闭当前页面,跳转到应用内的某个页面
redirectTo(url) {
return uni.redirectTo({
url: url
})
},
// 保留当前页面,跳转到应用内的某个页面
navigateTo(url) {
return uni.navigateTo({
url: url
})
},
// 关闭当前页面,返回上一页面或多级页面
navigateBack() {
return uni.navigateBack()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,90 @@
@font-face {
font-family: "iconfont";
src: url('@/static/font/iconfont.ttf') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
display: inline-block;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-user:before {
content: "\e7ae";
}
.icon-password:before {
content: "\e8b2";
}
.icon-code:before {
content: "\e699";
}
.icon-setting:before {
content: "\e6cc";
}
.icon-share:before {
content: "\e739";
}
.icon-edit:before {
content: "\e60c";
}
.icon-version:before {
content: "\e63f";
}
.icon-service:before {
content: "\e6ff";
}
.icon-friendfill:before {
content: "\e726";
}
.icon-community:before {
content: "\e741";
}
.icon-people:before {
content: "\e736";
}
.icon-dianzan:before {
content: "\ec7f";
}
.icon-right:before {
content: "\e7eb";
}
.icon-logout:before {
content: "\e61d";
}
.icon-help:before {
content: "\e616";
}
.icon-github:before {
content: "\e628";
}
.icon-aixin:before {
content: "\e601";
}
.icon-clean:before {
content: "\e607";
}
.icon-refresh:before {
content: "\e604";
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="shortcut icon" type="image/x-icon" href="<%= BASE_URL %>static/favicon.ico">
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
</head>
<body>
<noscript>
<strong>本站点必须要开启JavaScript才能运行.</strong>
</noscript>
<div id="app"></div>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,90 @@
.text-center {
text-align: center;
}
.font-13 {
font-size: 13px;
}
.font-12 {
font-size: 12px;
}
.font-11 {
font-size: 11px;
}
.text-grey1 {
color: #888;
}
.text-grey2 {
color: #aaa;
}
.list-cell-arrow::before {
content: ' ';
height: 10px;
width: 10px;
border-width: 2px 2px 0 0;
border-color: #c0c0c0;
border-style: solid;
-webkit-transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
position: absolute;
top: 50%;
margin-top: -6px;
right: 30rpx;
}
.list-cell {
position: relative;
width: 100%;
box-sizing: border-box;
background-color: #fff;
color: #333;
padding: 26rpx 30rpx;
}
.list-cell:first-child {
border-radius: 8rpx 8rpx 0 0;
}
.list-cell:last-child {
border-radius: 0 0 8rpx 8rpx;
}
.list-cell::after {
content: '';
position: absolute;
border-bottom: 1px solid #eaeef1;
-webkit-transform: scaleY(0.5) translateZ(0);
transform: scaleY(0.5) translateZ(0);
transform-origin: 0 100%;
bottom: 0;
right: 0;
left: 0;
pointer-events: none;
}
.menu-list {
margin: 15px 15px;
.menu-item-box {
width: 100%;
display: flex;
align-items: center;
.menu-icon {
color: #007AFF;
font-size: 16px;
margin-right: 5px;
}
.text-right {
margin-left: auto;
margin-right: 34rpx;
color: #999;
}
}
}

View File

@@ -0,0 +1,6 @@
// global
@import "./global.scss";
// color-ui
@import "@/static/scss/colorui.css";
// iconfont
@import "@/static/font/iconfont.css";

View File

@@ -0,0 +1,9 @@
const getters = {
token: state => state.user.token,
avatar: state => state.user.avatar,
id: state => state.user.id,
name: state => state.user.name,
roles: state => state.user.roles,
permissions: state => state.user.permissions
}
export default getters

View File

@@ -0,0 +1,15 @@
import Vue from 'vue'
import Vuex from 'vuex'
import user from '@/store/modules/user'
import getters from './getters'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
},
getters
})
export default store

View File

@@ -0,0 +1,123 @@
import config from '@/config'
import storage from '@/utils/storage'
import constant from '@/utils/constant'
import { isHttp, isEmpty } from "@/utils/validate"
import { login, mobileLogin, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import defAva from '@/static/images/profile.jpg'
const baseUrl = config.baseUrl
const user = {
state: {
token: getToken(),
id: storage.get(constant.id),
name: storage.get(constant.name),
avatar: storage.get(constant.avatar),
roles: storage.get(constant.roles),
permissions: storage.get(constant.permissions)
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_ID: (state, id) => {
state.id = id
storage.set(constant.id, id)
},
SET_NAME: (state, name) => {
state.name = name
storage.set(constant.name, name)
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
storage.set(constant.avatar, avatar)
},
SET_ROLES: (state, roles) => {
state.roles = roles
storage.set(constant.roles, roles)
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
storage.set(constant.permissions, permissions)
}
},
actions: {
// 账号密码登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 手机号登录(验证码固定 666666
MobileLogin({ commit }, mobile) {
return new Promise((resolve, reject) => {
mobileLogin((mobile || '').trim(), '666666').then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
let avatar = user.avatar || ""
if (!isHttp(avatar)) {
avatar = (isEmpty(avatar)) ? defAva : baseUrl + avatar
}
const userid = (isEmpty(user) || isEmpty(user.userId)) ? "" : user.userId
const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user.userName
if (res.roles && res.roles.length > 0) {
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_ID', userid)
commit('SET_NAME', username)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
storage.clean()
resolve()
}).catch(error => {
reject(error)
})
})
}
}
}
export default user

64
attractor-ui/uni.scss Normal file
View File

@@ -0,0 +1,64 @@
/**
* uni-app内置的常用样式变量
*/
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#e5e5e5;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;

View File

@@ -0,0 +1,33 @@
## 1.2.22023-01-28
- 修复 运行/打包 控制台警告问题
## 1.2.12022-09-05
- 修复 当 text 超过 max-num 时badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
## 1.2.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
## 1.1.72021-11-08
- 优化 升级ui
- 修改 size 属性默认值调整为 small
- 修改 type 属性,默认值调整为 errorinfo 替换 default
## 1.1.62021-09-22
- 修复 在字节小程序上样式不生效的 bug
## 1.1.52021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.42021-07-29
- 修复 去掉 nvue 不支持css 的 align-self 属性nvue 下不暂支持 absolute 属性
## 1.1.32021-06-24
- 优化 示例项目
## 1.1.12021-05-12
- 新增 组件示例地址
## 1.1.02021-05-12
- 新增 uni-badge 的 absolute 属性,支持定位
- 新增 uni-badge 的 offset 属性,支持定位偏移
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
- 优化 uni-badge 属性 custom-style 支持以对象形式自定义样式
## 1.0.72021-05-07
- 修复 uni-badge 在 App 端数字小于10时不是圆形的bug
- 修复 uni-badge 在父元素不是 flex 布局时宽度缩小的bug
- 新增 uni-badge 属性 custom-style 支持自定义样式
## 1.0.62021-02-04
- 调整为uni_modules目录规范

View File

@@ -0,0 +1,268 @@
<template>
<view class="uni-badge--x">
<slot />
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
class="uni-badge" @click="onClick()">{{displayValue}}</text>
</view>
</template>
<script>
/**
* Badge 数字角标
* @description 数字角标一般和其它控件列表、9宫格等配合使用用于进行数量提示默认为实心灰色背景
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
* @property {String} text 角标内容
* @property {String} size = [normal|small] 角标内容
* @property {String} type = [info|primary|success|warning|error] 颜色类型
* @value info 灰色
* @value primary 蓝色
* @value success 绿色
* @value warning 黄色
* @value error 红色
* @property {String} inverted = [true|false] 是否无需背景颜色
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
* @value rightTop 右上
* @value rightBottom 右下
* @value leftTop 左上
* @value leftBottom 左下
* @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px[10, 10] 表示向 absolute 指定的内偏移 10px
* @property {String} isDot = [true|false] 是否显示为一个小点
* @event {Function} click 点击 Badge 触发事件
* @example <uni-badge text="1"></uni-badge>
*/
export default {
name: 'UniBadge',
emits: ['click'],
props: {
type: {
type: String,
default: 'error'
},
inverted: {
type: Boolean,
default: false
},
isDot: {
type: Boolean,
default: false
},
maxNum: {
type: Number,
default: 99
},
absolute: {
type: String,
default: ''
},
offset: {
type: Array,
default () {
return [0, 0]
}
},
text: {
type: [String, Number],
default: ''
},
size: {
type: String,
default: 'small'
},
customStyle: {
type: Object,
default () {
return {}
}
}
},
data() {
return {};
},
computed: {
width() {
return String(this.text).length * 8 + 12
},
classNames() {
const {
inverted,
type,
size,
absolute
} = this
return [
inverted ? 'uni-badge--' + type + '-inverted' : '',
'uni-badge--' + type,
'uni-badge--' + size,
absolute ? 'uni-badge--absolute' : ''
].join(' ')
},
positionStyle() {
if (!this.absolute) return {}
let w = this.width / 2,
h = 10
if (this.isDot) {
w = 5
h = 5
}
const x = `${- w + this.offset[0]}px`
const y = `${- h + this.offset[1]}px`
const whiteList = {
rightTop: {
right: x,
top: y
},
rightBottom: {
right: x,
bottom: y
},
leftBottom: {
left: x,
bottom: y
},
leftTop: {
left: x,
top: y
}
}
const match = whiteList[this.absolute]
return match ? match : whiteList['rightTop']
},
dotStyle() {
if (!this.isDot) return {}
return {
width: '10px',
minWidth: '0',
height: '10px',
padding: '0',
borderRadius: '10px'
}
},
displayValue() {
const {
isDot,
text,
maxNum
} = this
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
}
},
methods: {
onClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" >
$uni-primary: #2979ff !default;
$uni-success: #4cd964 !default;
$uni-warning: #f0ad4e !default;
$uni-error: #dd524d !default;
$uni-info: #909399 !default;
$bage-size: 12px;
$bage-small: scale(0.8);
.uni-badge--x {
/* #ifdef APP-NVUE */
// align-self: flex-start;
/* #endif */
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
position: relative;
}
.uni-badge--absolute {
position: absolute;
}
.uni-badge--small {
transform: $bage-small;
transform-origin: center center;
}
.uni-badge {
/* #ifndef APP-NVUE */
display: flex;
overflow: hidden;
box-sizing: border-box;
font-feature-settings: "tnum";
min-width: 20px;
/* #endif */
justify-content: center;
flex-direction: row;
height: 20px;
padding: 0 4px;
line-height: 18px;
color: #fff;
border-radius: 100px;
background-color: $uni-info;
background-color: transparent;
border: 1px solid #fff;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-size: $bage-size;
/* #ifdef H5 */
z-index: 999;
cursor: pointer;
/* #endif */
&--info {
color: #fff;
background-color: $uni-info;
}
&--primary {
background-color: $uni-primary;
}
&--success {
background-color: $uni-success;
}
&--warning {
background-color: $uni-warning;
}
&--error {
background-color: $uni-error;
}
&--inverted {
padding: 0 5px 0 0;
color: $uni-info;
}
&--info-inverted {
color: $uni-info;
background-color: transparent;
}
&--primary-inverted {
color: $uni-primary;
background-color: transparent;
}
&--success-inverted {
color: $uni-success;
background-color: transparent;
}
&--warning-inverted {
color: $uni-warning;
background-color: transparent;
}
&--error-inverted {
color: $uni-error;
background-color: transparent;
}
}
</style>

View File

@@ -0,0 +1,85 @@
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.2.2",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",
"badge",
"uni-ui",
"uniui",
"数字角标",
"徽章"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
## Badge 数字角标
> **组件名uni-badge**
> 代码块: `uBadge`
数字角标一般和其它控件列表、9宫格等配合使用用于进行数量提示默认为实心灰色背景
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@@ -0,0 +1,6 @@
## 0.1.22022-06-08
- 修复 微信小程序 separator 不显示的Bug
## 0.1.12022-06-02
- 新增 支持 uni.scss 修改颜色
## 0.1.02022-04-21
- 初始化

View File

@@ -0,0 +1,121 @@
<template>
<view class="uni-breadcrumb-item">
<view :class="{
'uni-breadcrumb-item--slot': true,
'uni-breadcrumb-item--slot-link': to && currentPage !== to
}" @click="navTo">
<slot />
</view>
<i v-if="separatorClass" class="uni-breadcrumb-item--separator" :class="separatorClass" />
<text v-else class="uni-breadcrumb-item--separator">{{ separator }}</text>
</view>
</template>
<script>
/**
* BreadcrumbItem 面包屑导航子组件
* @property {String/Object} to 路由跳转页面路径/对象
* @property {Boolean} replace 在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持)
*/
export default {
data() {
return {
currentPage: ""
}
},
options: {
virtualHost: true
},
props: {
to: {
type: String,
default: ''
},
replace:{
type: Boolean,
default: false
}
},
inject: {
uniBreadcrumb: {
from: "uniBreadcrumb",
default: null
}
},
created(){
const pages = getCurrentPages()
const page = pages[pages.length-1]
if(page){
this.currentPage = `/${page.route}`
}
},
computed: {
separator() {
return this.uniBreadcrumb.separator
},
separatorClass() {
return this.uniBreadcrumb.separatorClass
}
},
methods: {
navTo() {
const { to } = this
if (!to || this.currentPage === to){
return
}
if(this.replace){
uni.redirectTo({
url:to
})
}else{
uni.navigateTo({
url:to
})
}
}
}
}
</script>
<style lang="scss">
$uni-primary: #2979ff !default;
$uni-base-color: #6a6a6a !default;
$uni-main-color: #3a3a3a !default;
.uni-breadcrumb-item {
display: flex;
align-items: center;
white-space: nowrap;
font-size: 14px;
&--slot {
color: $uni-base-color;
padding: 0 10px;
&-link {
color: $uni-main-color;
font-weight: bold;
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
&:hover {
color: $uni-primary;
}
}
}
&--separator {
font-size: 12px;
color: $uni-base-color;
}
&:first-child &--slot {
padding-left: 0;
}
&:last-child &--separator {
display: none;
}
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<view class="uni-breadcrumb">
<slot />
</view>
</template>
<script>
/**
* Breadcrumb 面包屑导航父组件
* @description 显示当前页面的路径,快速返回之前的任意页面
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
* @property {String} separator 分隔符,默认为斜杠'/'
* @property {String} separatorClass 图标分隔符 class
*/
export default {
options: {
virtualHost: true
},
props: {
separator: {
type: String,
default: '/'
},
separatorClass: {
type: String,
default: ''
}
},
provide() {
return {
uniBreadcrumb: this
}
}
}
</script>
<style lang="scss">
.uni-breadcrumb {
display: flex;
}
</style>

View File

@@ -0,0 +1,88 @@
{
"id": "uni-breadcrumb",
"displayName": "uni-breadcrumb 面包屑",
"version": "0.1.2",
"description": "Breadcrumb 面包屑",
"keywords": [
"uni-breadcrumb",
"breadcrumb",
"uni-ui",
"面包屑导航",
"面包屑"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,66 @@
## breadcrumb 面包屑导航
> **组件名uni-breadcrumb**
> 代码块: `ubreadcrumb`
显示当前页面的路径,快速返回之前的任意页面。
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 基本用法
在 ``template`` 中使用组件
```html
<uni-breadcrumb separator="/">
<uni-breadcrumb-item v-for="(route,index) in routes" :key="index" :to="route.to">{{route.name}}</uni-breadcrumb-item>
</uni-breadcrumb>
```
```js
export default {
name: "uni-stat-breadcrumb",
data() {
return {
routes: [{
to: '/A',
name: 'A页面'
}, {
to: '/B',
name: 'B页面'
}, {
to: '/C',
name: 'C页面'
}]
};
}
}
```
## API
### Breadcrumb Props
|属性名 |类型 |默认值 |说明 |
|:-: |:-: |:-: |:-: |
|separator |String |斜杠'/' |分隔符 |
|separatorClass |String | |图标分隔符 class |
### Breadcrumb Item Props
|属性名 |类型 |默认值 |说明 |
|:-: |:-: |:-: |:-: |
|to |String | |路由跳转页面路径 |
|replace|Boolean | |在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持) |
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb](https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb)

View File

@@ -0,0 +1,30 @@
## 1.4.122024-09-21
- 修复 calendar在选择日期范围后重新选择日期需要点两次的Bug
## 1.4.112024-01-10
- 修复 回到今天时,月份显示不一致问题
## 1.4.102023-04-10
- 修复 某些情况 monthSwitch 未触发的Bug
## 1.4.92023-02-02
- 修复 某些情况切换月份错误的Bug
## 1.4.82023-01-30
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
## 1.4.72022-09-16
- 优化 支持使用 uni-scss 控制主题色
## 1.4.62022-09-08
- 修复 表头年月切换导致改变当前日期为选择月1号且未触发change事件的Bug
## 1.4.52022-02-25
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.42022-02-25
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.32021-09-22
- 修复 startDate、 endDate 属性失效的Bug
## 1.4.22021-08-24
- 新增 支持国际化
## 1.4.12021-08-05
- 修复 弹出层被 tabbar 遮盖的Bug
## 1.4.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.3.162021-05-12
- 新增 组件示例地址
## 1.3.152021-02-04
- 调整为uni_modules目录规范

View File

@@ -0,0 +1,544 @@
/**
* @1900-2100区间内的公历、农历互转
* @charset UTF-8
* @github https://github.com/jjonline/calendar.js
* @Author Jea杨(JJonline@JJonline.Cn)
* @Time 2014-7-21
* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
* @Version 1.0.3
* @公历转农历calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
* @农历转公历calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
*/
/* eslint-disable */
var calendar = {
/**
* 农历1900-2100的润大小信息表
* @Array Of Property
* @return Hex
*/
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
/** Add By JJonline@JJonline.Cn**/
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
0x0d520], // 2100
/**
* 公历每个月份的天数普通表
* @Array Of Property
* @return Number
*/
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
/**
* 天干地支之天干速查表
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
* @return Cn string
*/
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
/**
* 天干地支之地支速查表
* @Array Of Property
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
* @return Cn string
*/
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
/**
* 天干地支之地支速查表<=>生肖
* @Array Of Property
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
* @return Cn string
*/
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
/**
* 24节气速查表
* @Array Of Property
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
* @return Cn string
*/
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
/**
* 1900-2100各年的24节气日期速查表
* @Array Of Property
* @return 0x string For splice
*/
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
/**
* 数字转中文速查表
* @Array Of Property
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
* @return Cn string
*/
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
/**
* 日期转农历称呼速查表
* @Array Of Property
* @trans ['初','十','廿','卅']
* @return Cn string
*/
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
/**
* 月份转农历称呼速查表
* @Array Of Property
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
* @return Cn string
*/
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
/**
* 返回农历y年一整年的总天数
* @param lunar Year
* @return Number
* @eg:var count = calendar.lYearDays(1987) ;//count=387
*/
lYearDays: function (y) {
var i; var sum = 348
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
return (sum + this.leapDays(y))
},
/**
* 返回农历y年闰月是哪个月若y年没有闰月 则返回0
* @param lunar Year
* @return Number (0-12)
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
*/
leapMonth: function (y) { // 闰字编码 \u95f0
return (this.lunarInfo[y - 1900] & 0xf)
},
/**
* 返回农历y年闰月的天数 若该年没有闰月则返回0
* @param lunar Year
* @return Number (0、29、30)
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
*/
leapDays: function (y) {
if (this.leapMonth(y)) {
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
}
return (0)
},
/**
* 返回农历y年m月非闰月的总天数计算m为闰月时的天数请使用leapDays方法
* @param lunar Year
* @return Number (-1、29、30)
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
*/
monthDays: function (y, m) {
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12参数错误返回-1
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
},
/**
* 返回公历(!)y年m月的天数
* @param solar Year
* @return Number (-1、28、29、30、31)
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
*/
solarDays: function (y, m) {
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var ms = m - 1
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
} else {
return (this.solarMonth[ms])
}
},
/**
* 农历年份转换为干支纪年
* @param lYear 农历年的年份数
* @return Cn string
*/
toGanZhiYear: function (lYear) {
var ganKey = (lYear - 3) % 10
var zhiKey = (lYear - 3) % 12
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
},
/**
* 公历月、日判断所属星座
* @param cMonth [description]
* @param cDay [description]
* @return Cn string
*/
toAstro: function (cMonth, cDay) {
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
},
/**
* 传入offset偏移量返回干支
* @param offset 相对甲子的偏移量
* @return Cn string
*/
toGanZhi: function (offset) {
return this.Gan[offset % 10] + this.Zhi[offset % 12]
},
/**
* 传入公历(!)y年获得该年第n个节气的公历日期
* @param y公历年(1900-2100)n二十四节气中的第几个节气(1~24)从n=1(小寒)算起
* @return day Number
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
*/
getTerm: function (y, n) {
if (y < 1900 || y > 2100) { return -1 }
if (n < 1 || n > 24) { return -1 }
var _table = this.sTermInfo[y - 1900]
var _info = [
parseInt('0x' + _table.substr(0, 5)).toString(),
parseInt('0x' + _table.substr(5, 5)).toString(),
parseInt('0x' + _table.substr(10, 5)).toString(),
parseInt('0x' + _table.substr(15, 5)).toString(),
parseInt('0x' + _table.substr(20, 5)).toString(),
parseInt('0x' + _table.substr(25, 5)).toString()
]
var _calday = [
_info[0].substr(0, 1),
_info[0].substr(1, 2),
_info[0].substr(3, 1),
_info[0].substr(4, 2),
_info[1].substr(0, 1),
_info[1].substr(1, 2),
_info[1].substr(3, 1),
_info[1].substr(4, 2),
_info[2].substr(0, 1),
_info[2].substr(1, 2),
_info[2].substr(3, 1),
_info[2].substr(4, 2),
_info[3].substr(0, 1),
_info[3].substr(1, 2),
_info[3].substr(3, 1),
_info[3].substr(4, 2),
_info[4].substr(0, 1),
_info[4].substr(1, 2),
_info[4].substr(3, 1),
_info[4].substr(4, 2),
_info[5].substr(0, 1),
_info[5].substr(1, 2),
_info[5].substr(3, 1),
_info[5].substr(4, 2)
]
return parseInt(_calday[n - 1])
},
/**
* 传入农历数字月份返回汉语通俗表示法
* @param lunar month
* @return Cn string
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
*/
toChinaMonth: function (m) { // 月 => \u6708
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var s = this.nStr3[m - 1]
s += '\u6708'// 加上月字
return s
},
/**
* 传入农历日期数字返回汉字表示法
* @param lunar day
* @return Cn string
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
*/
toChinaDay: function (d) { // 日 => \u65e5
var s
switch (d) {
case 10:
s = '\u521d\u5341'; break
case 20:
s = '\u4e8c\u5341'; break
case 30:
s = '\u4e09\u5341'; break
default :
s = this.nStr2[Math.floor(d / 10)]
s += this.nStr1[d % 10]
}
return (s)
},
/**
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
* @param y year
* @return Cn string
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
*/
getAnimal: function (y) {
return this.Animals[(y - 4) % 12]
},
/**
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
* @param y solar year
* @param m solar month
* @param d solar day
* @return JSON object
* @eg:console.log(calendar.solar2lunar(1987,11,01));
*/
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
// 年份限定、上限
if (y < 1900 || y > 2100) {
return -1// undefined转换为数字变为NaN
}
// 公历传参最下限
if (y == 1900 && m == 1 && d < 31) {
return -1
}
// 未传参 获得当天
if (!y) {
var objDate = new Date()
} else {
var objDate = new Date(y, parseInt(m) - 1, d)
}
var i; var leap = 0; var temp = 0
// 修正ymd参数
var y = objDate.getFullYear()
var m = objDate.getMonth() + 1
var d = objDate.getDate()
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
for (i = 1900; i < 2101 && offset > 0; i++) {
temp = this.lYearDays(i)
offset -= temp
}
if (offset < 0) {
offset += temp; i--
}
// 是否今天
var isTodayObj = new Date()
var isToday = false
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
isToday = true
}
// 星期几
var nWeek = objDate.getDay()
var cWeek = this.nStr1[nWeek]
// 数字表示周几顺应天朝周一开始的惯例
if (nWeek == 0) {
nWeek = 7
}
// 农历年
var year = i
var leap = this.leapMonth(i) // 闰哪个月
var isLeap = false
// 效验闰月
for (i = 1; i < 13 && offset > 0; i++) {
// 闰月
if (leap > 0 && i == (leap + 1) && isLeap == false) {
--i
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
} else {
temp = this.monthDays(year, i)// 计算农历普通月天数
}
// 解除闰月
if (isLeap == true && i == (leap + 1)) { isLeap = false }
offset -= temp
}
// 闰月导致数组下标重叠取反
if (offset == 0 && leap > 0 && i == leap + 1) {
if (isLeap) {
isLeap = false
} else {
isLeap = true; --i
}
}
if (offset < 0) {
offset += temp; --i
}
// 农历月
var month = i
// 农历日
var day = offset + 1
// 天干地支处理
var sm = m - 1
var gzY = this.toGanZhiYear(year)
// 当月的两个节气
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
// 依据12节气修正干支月
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
if (d >= firstNode) {
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
}
// 传入的日期的节气与否
var isTerm = false
var Term = null
if (firstNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 2]
}
if (secondNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 1]
}
// 日柱 当月一日与 1900/1/1 相差天数
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
var gzD = this.toGanZhi(dayCyclical + d - 1)
// 该日期所属的星座
var astro = this.toAstro(m, d)
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
},
/**
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
* @param y lunar year
* @param m lunar month
* @param d lunar day
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
* @return JSON object
* @eg:console.log(calendar.lunar2solar(1987,9,10));
*/
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
var isLeapMonth = !!isLeapMonth
var leapOffset = 0
var leapMonth = this.leapMonth(y)
var leapDay = this.leapDays(y)
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
var day = this.monthDays(y, m)
var _day = day
// bugFix 2016-9-25
// if month is leap, _day use leapDays method
if (isLeapMonth) {
_day = this.leapDays(y, m)
}
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
// 计算农历的时间差
var offset = 0
for (var i = 1900; i < y; i++) {
offset += this.lYearDays(i)
}
var leap = 0; var isAdd = false
for (var i = 1; i < m; i++) {
leap = this.leapMonth(y)
if (!isAdd) { // 处理闰月
if (leap <= i && leap > 0) {
offset += this.leapDays(y); isAdd = true
}
}
offset += this.monthDays(y, i)
}
// 转换闰月农历 需补充该年闰月的前一个月的时差
if (isLeapMonth) { offset += day }
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
var cY = calObj.getUTCFullYear()
var cM = calObj.getUTCMonth() + 1
var cD = calObj.getUTCDate()
return this.solar2lunar(cY, cM, cD)
}
}
export default calendar

View File

@@ -0,0 +1,12 @@
{
"uni-calender.ok": "ok",
"uni-calender.cancel": "cancel",
"uni-calender.today": "today",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN"
}

View File

@@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@@ -0,0 +1,12 @@
{
"uni-calender.ok": "确定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

Some files were not shown because too many files have changed in this diff Show More