初始化项目
16
attractor-ui/.gitignore
vendored
Normal 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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
## 技术文档
|
||||
|
||||
- 官网网站:[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>
|
||||
36
attractor-ui/api/device.js
Normal 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
@@ -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
|
||||
})
|
||||
}
|
||||
52
attractor-ui/api/system/dict/data.js
Normal 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'
|
||||
})
|
||||
}
|
||||
60
attractor-ui/api/system/dict/type.js
Normal 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'
|
||||
})
|
||||
}
|
||||
41
attractor-ui/api/system/user.js
Normal 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
|
||||
})
|
||||
}
|
||||
167
attractor-ui/components/uni-section/uni-section.vue
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
43
attractor-ui/pages/common/textview/index.vue
Normal 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>
|
||||
34
attractor-ui/pages/common/webview/index.vue
Normal 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>
|
||||
1252
attractor-ui/pages/devices/attractor/attractor.vue
Normal file
276
attractor-ui/pages/devices/detail/detail.vue
Normal 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>
|
||||
229
attractor-ui/pages/devices/devices.vue
Normal 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>
|
||||
430
attractor-ui/pages/devices/master.vue
Normal 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>
|
||||
3
attractor-ui/pages/index.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<view>设备列表</view>
|
||||
</template>
|
||||
145
attractor-ui/pages/login.vue
Normal 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>
|
||||
22
attractor-ui/pages/member/member.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<view>
|
||||
成员管理
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
22
attractor-ui/pages/member/permi.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
173
attractor-ui/pages/mine/about/index.vue
Normal 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>
|
||||
618
attractor-ui/pages/mine/avatar/index.vue
Normal 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>
|
||||
199
attractor-ui/pages/mine/help/index.vue
Normal 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>
|
||||
179
attractor-ui/pages/mine/index.vue
Normal 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>
|
||||
127
attractor-ui/pages/mine/info/edit.vue
Normal 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>
|
||||
44
attractor-ui/pages/mine/info/index.vue
Normal 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>
|
||||
85
attractor-ui/pages/mine/pwd/index.vue
Normal 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>
|
||||
78
attractor-ui/pages/mine/setting/index.vue
Normal 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>
|
||||
189
attractor-ui/pages/register.vue
Normal 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>
|
||||
22
attractor-ui/pages/team/detail.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<view>
|
||||
团队配置
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
22
attractor-ui/pages/team/team.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<view>
|
||||
团队管理
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
208
attractor-ui/pages/work/index.vue
Normal 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>
|
||||
207
attractor-ui/pages/work/search.vue
Normal 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>
|
||||
44
attractor-ui/permission.js
Normal 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)
|
||||
}
|
||||
})
|
||||
})
|
||||
60
attractor-ui/plugins/auth.js
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
14
attractor-ui/plugins/index.js
Normal 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
|
||||
}
|
||||
}
|
||||
78
attractor-ui/plugins/modal.js
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
30
attractor-ui/plugins/tab.js
Normal 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()
|
||||
}
|
||||
}
|
||||
BIN
attractor-ui/static/favicon.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
90
attractor-ui/static/font/iconfont.css
Normal 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";
|
||||
}
|
||||
|
||||
BIN
attractor-ui/static/font/iconfont.ttf
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 713 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 75 KiB |
BIN
attractor-ui/static/images/banner/banner01.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
attractor-ui/static/images/banner/banner02.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
attractor-ui/static/images/banner/banner03.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
attractor-ui/static/images/profile.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
attractor-ui/static/images/tabbar/home.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
attractor-ui/static/images/tabbar/home_.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
attractor-ui/static/images/tabbar/mine.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
attractor-ui/static/images/tabbar/mine_.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
attractor-ui/static/images/tabbar/work.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
attractor-ui/static/images/tabbar/work_.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
20
attractor-ui/static/index.html
Normal 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>
|
||||
BIN
attractor-ui/static/logo.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
attractor-ui/static/logo200.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
5142
attractor-ui/static/scss/colorui.css
Normal file
90
attractor-ui/static/scss/global.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
attractor-ui/static/scss/index.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
// global
|
||||
@import "./global.scss";
|
||||
// color-ui
|
||||
@import "@/static/scss/colorui.css";
|
||||
// iconfont
|
||||
@import "@/static/font/iconfont.css";
|
||||
9
attractor-ui/store/getters.js
Normal 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
|
||||
15
attractor-ui/store/index.js
Normal 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
|
||||
123
attractor-ui/store/modules/user.js
Normal 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
@@ -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;
|
||||
33
attractor-ui/uni_modules/uni-badge/changelog.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## 1.2.2(2023-01-28)
|
||||
- 修复 运行/打包 控制台警告问题
|
||||
## 1.2.1(2022-09-05)
|
||||
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
|
||||
## 1.2.0(2021-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.7(2021-11-08)
|
||||
- 优化 升级ui
|
||||
- 修改 size 属性默认值调整为 small
|
||||
- 修改 type 属性,默认值调整为 error,info 替换 default
|
||||
## 1.1.6(2021-09-22)
|
||||
- 修复 在字节小程序上样式不生效的 bug
|
||||
## 1.1.5(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.4(2021-07-29)
|
||||
- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
|
||||
## 1.1.3(2021-06-24)
|
||||
- 优化 示例项目
|
||||
## 1.1.1(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.0(2021-05-12)
|
||||
- 新增 uni-badge 的 absolute 属性,支持定位
|
||||
- 新增 uni-badge 的 offset 属性,支持定位偏移
|
||||
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
|
||||
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
|
||||
- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
|
||||
## 1.0.7(2021-05-07)
|
||||
- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
|
||||
- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
|
||||
- 新增 uni-badge 属性 custom-style, 支持自定义样式
|
||||
## 1.0.6(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -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>
|
||||
85
attractor-ui/uni_modules/uni-badge/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
attractor-ui/uni_modules/uni-badge/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## Badge 数字角标
|
||||
> **组件名:uni-badge**
|
||||
> 代码块: `uBadge`
|
||||
|
||||
数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
|
||||
|
||||
6
attractor-ui/uni_modules/uni-breadcrumb/changelog.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 0.1.2(2022-06-08)
|
||||
- 修复 微信小程序 separator 不显示的Bug
|
||||
## 0.1.1(2022-06-02)
|
||||
- 新增 支持 uni.scss 修改颜色
|
||||
## 0.1.0(2022-04-21)
|
||||
- 初始化
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
88
attractor-ui/uni_modules/uni-breadcrumb/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
attractor-ui/uni_modules/uni-breadcrumb/readme.md
Normal 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)
|
||||
30
attractor-ui/uni_modules/uni-calendar/changelog.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## 1.4.12(2024-09-21)
|
||||
- 修复 calendar在选择日期范围后重新选择日期需要点两次的Bug
|
||||
## 1.4.11(2024-01-10)
|
||||
- 修复 回到今天时,月份显示不一致问题
|
||||
## 1.4.10(2023-04-10)
|
||||
- 修复 某些情况 monthSwitch 未触发的Bug
|
||||
## 1.4.9(2023-02-02)
|
||||
- 修复 某些情况切换月份错误的Bug
|
||||
## 1.4.8(2023-01-30)
|
||||
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
|
||||
## 1.4.7(2022-09-16)
|
||||
- 优化 支持使用 uni-scss 控制主题色
|
||||
## 1.4.6(2022-09-08)
|
||||
- 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件的Bug
|
||||
## 1.4.5(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.4(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.3(2021-09-22)
|
||||
- 修复 startDate、 endDate 属性失效的Bug
|
||||
## 1.4.2(2021-08-24)
|
||||
- 新增 支持国际化
|
||||
## 1.4.1(2021-08-05)
|
||||
- 修复 弹出层被 tabbar 遮盖的Bug
|
||||
## 1.4.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.3.16(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.3.15(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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": "六"
|
||||
}
|
||||