diff --git a/package.json b/package.json index d08a4c0..1b16c95 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,9 @@ "@tarojs/runtime": "3.6.23", "@tarojs/shared": "3.6.23", "@tarojs/taro": "3.6.23", + "axios": "^1.6.8", "clsx": "^2.1.0", + "qs": "^6.12.0", "react": "^18.0.0", "react-dom": "^18.0.0" }, diff --git a/project.config.json b/project.config.json index 3d09de5..8457eb6 100644 --- a/project.config.json +++ b/project.config.json @@ -14,7 +14,8 @@ "ignore": [], "disablePlugins": [], "outputPath": "" - } + }, + "packNpmRelationList": [] }, "compileType": "miniprogram", "libVersion": "2.30.3", diff --git a/src/app.config.ts b/src/app.config.ts index 783aba6..6590522 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -2,6 +2,7 @@ export default defineAppConfig({ pages: [ // 首页y以及首页tab页内容 'pages/index/index', // 首页 + 'pages/login/index', // 登录页 // 被其他页面引用的组件不要写在这里 // 'pages/iot/index', // 物联网 @@ -9,12 +10,14 @@ export default defineAppConfig({ // 'pages/equipment/index', // 巡检设备 // 跳转页面 + 'pages/weatherStation/index', // 气象站 'pages/msgDetail/index', // 消息详情 ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#0fc87c', navigationBarTitleText: 'WeChat', - navigationBarTextStyle: 'white' + navigationBarTextStyle: 'white', + navigationStyle: 'custom' } }) diff --git a/src/assets/images/bg.png b/src/assets/images/bg.png new file mode 100644 index 0000000..f84a390 Binary files /dev/null and b/src/assets/images/bg.png differ diff --git a/src/assets/images/icons/back.png b/src/assets/images/icons/back.png new file mode 100644 index 0000000..d127668 Binary files /dev/null and b/src/assets/images/icons/back.png differ diff --git a/src/assets/images/icons/data/icon1.png b/src/assets/images/icons/data/icon1.png new file mode 100644 index 0000000..8c6eacb Binary files /dev/null and b/src/assets/images/icons/data/icon1.png differ diff --git a/src/assets/images/icons/data/icon2.png b/src/assets/images/icons/data/icon2.png new file mode 100644 index 0000000..dfd3a9b Binary files /dev/null and b/src/assets/images/icons/data/icon2.png differ diff --git a/src/assets/images/icons/data/icon3.png b/src/assets/images/icons/data/icon3.png new file mode 100644 index 0000000..a4a86f2 Binary files /dev/null and b/src/assets/images/icons/data/icon3.png differ diff --git a/src/assets/images/icons/data/icon4.png b/src/assets/images/icons/data/icon4.png new file mode 100644 index 0000000..85af9e2 Binary files /dev/null and b/src/assets/images/icons/data/icon4.png differ diff --git a/src/assets/images/icons/data/icon5.png b/src/assets/images/icons/data/icon5.png new file mode 100644 index 0000000..764d58d Binary files /dev/null and b/src/assets/images/icons/data/icon5.png differ diff --git a/src/assets/images/icons/data/icon6.png b/src/assets/images/icons/data/icon6.png new file mode 100644 index 0000000..d90f79e Binary files /dev/null and b/src/assets/images/icons/data/icon6.png differ diff --git a/src/assets/images/icons/data/index.ts b/src/assets/images/icons/data/index.ts new file mode 100644 index 0000000..50271c3 --- /dev/null +++ b/src/assets/images/icons/data/index.ts @@ -0,0 +1,6 @@ +export {default as icon1} from './icon1.png'; +export {default as icon2} from './icon2.png'; +export {default as icon3} from './icon3.png'; +export {default as icon4} from './icon4.png'; +export {default as icon5} from './icon5.png'; +export {default as icon6} from './icon6.png'; \ No newline at end of file diff --git a/src/assets/images/icons/question-line.png b/src/assets/images/icons/question-line.png new file mode 100644 index 0000000..d2d4956 Binary files /dev/null and b/src/assets/images/icons/question-line.png differ diff --git a/src/assets/images/loginBg.png b/src/assets/images/loginBg.png new file mode 100644 index 0000000..829ddd6 Binary files /dev/null and b/src/assets/images/loginBg.png differ diff --git a/src/assets/images/tab/tab1.png b/src/assets/images/tab/tab1.png index b7b003a..a0f2140 100644 Binary files a/src/assets/images/tab/tab1.png and b/src/assets/images/tab/tab1.png differ diff --git a/src/assets/images/tab/tab1_selected.png b/src/assets/images/tab/tab1_selected.png index 0152caf..6d04fb3 100644 Binary files a/src/assets/images/tab/tab1_selected.png and b/src/assets/images/tab/tab1_selected.png differ diff --git a/src/assets/images/tab/tab2.png b/src/assets/images/tab/tab2.png index 06f1ab8..76d194c 100644 Binary files a/src/assets/images/tab/tab2.png and b/src/assets/images/tab/tab2.png differ diff --git a/src/assets/images/tab/tab3.png b/src/assets/images/tab/tab3.png index 89f0e3f..052ee78 100644 Binary files a/src/assets/images/tab/tab3.png and b/src/assets/images/tab/tab3.png differ diff --git a/src/assets/images/tab/tab3_selected.png b/src/assets/images/tab/tab3_selected.png index 2911018..274a42c 100644 Binary files a/src/assets/images/tab/tab3_selected.png and b/src/assets/images/tab/tab3_selected.png differ diff --git a/src/components/customized/navigation/index.tsx b/src/components/customized/navigation/index.tsx new file mode 100644 index 0000000..69399fe --- /dev/null +++ b/src/components/customized/navigation/index.tsx @@ -0,0 +1,20 @@ +import { Image, View } from "@tarojs/components" +import Taro from "@tarojs/taro"; +import arrow from '../../../assets/images/icons/back.png' + +const HeaderNation = ({ title = '' }) => { + const showBack = Taro.getCurrentPages().length > 1 + return ( + + { + Taro.navigateBack() + }}> + { showBack ? : null } + + { title } + + + ) +} + +export default HeaderNation; \ No newline at end of file diff --git a/src/config/axios/errorCode.ts b/src/config/axios/errorCode.ts new file mode 100644 index 0000000..94d719f --- /dev/null +++ b/src/config/axios/errorCode.ts @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + default: '系统未知错误,请反馈给管理员' +} diff --git a/src/config/axios/index.ts b/src/config/axios/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts new file mode 100644 index 0000000..50f3baf --- /dev/null +++ b/src/config/axios/service.ts @@ -0,0 +1,109 @@ +import axios, { AxiosError, AxiosInstance, AxiosRequestHeaders, AxiosResponse, InternalAxiosRequestConfig } from "axios"; +import { axiosConfig } from ".."; +import Taro from "@tarojs/taro"; +import qs from 'qs' +import errorCode from './errorCode' + +// 请求白名单,无须token的接口 +const whiteList: string[] = ['/login', '/refresh-token'] + +// 是否正在刷新中 +let isRefreshToken = false +// 请求队列 +let requestList: any[] = [] + +// 创建实例 +const service: AxiosInstance = axios.create({ + baseURL: axiosConfig.baseUrl, + timeout: axiosConfig.timeout, + withCredentials: axiosConfig.withCredentials +}); + +const getAccessToken = () => { + return Taro.getStorageSync('token') +} + +// request拦截器 +service.interceptors.request.use((config: InternalAxiosRequestConfig) => { + // 是否需要设置 token + let isToken = (config!.headers || {}).isToken === false + whiteList.some((v) => { + if (config.url) { + config.url.indexOf(v) > -1 + return (isToken = false) + } + }) + if (getAccessToken() && !isToken) { + ; (config as any).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token + } + const params = config.params || {} + const data = config.data || false + + if ( + config.method?.toUpperCase() === 'POST' && + (config.headers as AxiosRequestHeaders)['Content-Type'] === + 'application/x-www-form-urlencoded' + ) { config.data = qs.stringify(data) } + + // get参数编码 + if (config.method?.toUpperCase() === 'GET' && params) { + config.params = {} + const paramsStr = qs.stringify(params, { allowDots: true }) + if (paramsStr) { + config.url = config.url + '?' + paramsStr + } + } + + return config +}, (error: AxiosError) => { + Promise.reject(error) +}) + +// response 拦截器 +service.interceptors.response.use( + async (response: AxiosResponse) => { + let { data } = response + const config = response.config + if (!data) { throw new Error() } + + const code = data.code || 200 // 默认成功 + // 获取错误信息 + const msg = data.msg || errorCode[code] || errorCode['default'] + + if (code === 401) { + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 + // isRefreshToken false 未开始刷新token 进入刷新token程序 + if (!isRefreshToken) { + isRefreshToken = true + try { + // 使用默认的用户名密码进行登录操作 + const token = 'request login' + Taro.setStorageSync('token', token) + config.headers!.Authorization = 'Bearer ' + getAccessToken() + requestList.forEach((cb: any) => { cb() }) + requestList = [] + return service(config) + } catch (e) { + // 刷新失败时 处理错误 + requestList = [] // 清空队列 + return Promise.reject() + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 正在尝试刷新token,放入等待队列 + return new Promise((resolve) => { + requestList.push(() => { + config.headers!.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(service(config)) + }) + }) + } + } else if (code === 500) { + return Promise.reject(new Error(msg)) + } else { + return data + } + } +) \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..eaf7a0b --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,5 @@ +export const axiosConfig = { + baseUrl: '', + timeout: 30000, + withCredentials: false // 禁用 Cookie 等信息 +} \ No newline at end of file diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index b39edc5..3f38f32 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -2,6 +2,7 @@ import { Image, View } from "@tarojs/components"; import "./index.scss"; import { useState } from "react"; import Iot from "../iot"; +import basicBg from '../../assets/images/bg.png' import MsgCenter from "../msgCenter"; import EquipMent from "../equipment"; import tab1Icon from '../../assets/images/tab/tab1.png' @@ -12,10 +13,23 @@ import tab3Icon from '../../assets/images/tab/tab3.png' import tab3Icon_selected from '../../assets/images/tab/tab3_selected.png' import Taro from "@tarojs/taro"; +import HeaderNation from "../../components/customized/navigation"; const Index = () => { const [curSelectedItem, setCurSelectedItem] = useState('iot') Taro.setNavigationBarTitle({ title: '物联网' }) + + // 判断权限,没有权限进入登录页面 + const handleAuth = () => { + const token = Taro.getStorageSync('token') + if (token) { + // Taro.redirectTo({ url: '/pages/login/index' }) + } else { + Taro.redirectTo({ url: '/pages/login/index' }) + } + } + handleAuth() + const bottomMenuList = [ { id: 'iot', @@ -41,7 +55,10 @@ const Index = () => { ] return ( - + { bottomMenuList.map(item => { @@ -79,6 +96,15 @@ const Index = () => { : null } + ); }; diff --git a/src/pages/iot/index.tsx b/src/pages/iot/index.tsx index ea1a7c6..b4e1c99 100644 --- a/src/pages/iot/index.tsx +++ b/src/pages/iot/index.tsx @@ -4,6 +4,7 @@ import onlineIcon from '../../assets/images/icons/online.png' import offlineIcon from '../../assets/images/icons/offline.png' import errorIcon from '../../assets/images/icons/error.png' import img from '../../assets/images/img.png' +import Taro from "@tarojs/taro" const Iot = () => { const [selectedMenuId, setSelectedMenuId] = useState('all') @@ -40,24 +41,30 @@ const Iot = () => { return ( - + { topMenus.map(item => { return ( setSelectedMenuId(item.id)} >{ item.title } ) }) } - + { dataList.map(item => { return ( - + { + Taro.navigateTo({ url: '/pages/weatherStation/index?id=' + item.id }) + }} + > diff --git a/src/pages/login/index.config.ts b/src/pages/login/index.config.ts new file mode 100644 index 0000000..38f5101 --- /dev/null +++ b/src/pages/login/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '登录' +}) \ No newline at end of file diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx new file mode 100644 index 0000000..3f5536d --- /dev/null +++ b/src/pages/login/index.tsx @@ -0,0 +1,76 @@ +import { Button, Image, Input, Text, View } from "@tarojs/components" +import loginBg from '../../assets/images/loginBg.png' +import questionIcon from '../../assets/images/icons/question-line.png' +import Taro from "@tarojs/taro" +import { useState } from "react" + +const Login = () => { + const [username, setUserName] = useState('') + const [password, setPassWord] = useState('') + const handleLogin = () => { + if (!username || !password) return + Taro.setStorageSync('username', username) + Taro.setStorageSync('password', password) + // 请求完成后跳转 + Taro.showLoading({ title: '加载中...'}) + setTimeout(() => { + Taro.setStorageSync('token', 'testToken') + Taro.hideLoading() + Taro.redirectTo({ url: '/pages/index/index' }) + }, 2000) + } + + return ( + + + + + 您好, + + + 欢迎登录 + + + + 用户名 + + setUserName((_username.target as any).value)} + > + + + + 密码 + + setPassWord((_password.target as any).value)} + > + + + + + + 忘记密码? + + + + + + + + + ) +} + +export default Login; \ No newline at end of file diff --git a/src/pages/weatherStation/index.config.ts b/src/pages/weatherStation/index.config.ts new file mode 100644 index 0000000..e6b75f9 --- /dev/null +++ b/src/pages/weatherStation/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '气象站' +}) \ No newline at end of file diff --git a/src/pages/weatherStation/index.tsx b/src/pages/weatherStation/index.tsx new file mode 100644 index 0000000..ef022b0 --- /dev/null +++ b/src/pages/weatherStation/index.tsx @@ -0,0 +1,142 @@ +import { Image, View } from "@tarojs/components" +import Taro from "@tarojs/taro"; +import { useState } from "react"; +import img from '../../assets/images/img.png' +import basicBg from '../../assets/images/bg.png' +import HeaderNation from "../../components/customized/navigation"; +import { icon1, icon2, icon3, icon4 } from "../../assets/images/icons/data"; + +const SubTitle = ({title = '', updateTime = ''}) => { + return ( + + { title } + { updateTime !== '' ? '数据更新于' + updateTime : '' } + + ) +} + +const IconLabelValue = ({ value, unit, label, img }) => { + return ( + + + + { value } + { unit } + + { label } + + + + + + ) +} + +const GridContainer = ({ children }) => { + return ( + + { children } + + ) +} + +const BasicStatus = (status = true) => { + return ( + { status ? '在线' : '离线' } + ) +} + +const BasicInfoCard = ({ title, info, status, imgSrc }) => { + return ( + + + + + + { title } + { info } + { status ? BasicStatus() : '' } + + + ) +} + +const WeatherStation = () => { + const baseId = Taro.getCurrentInstance().router?.params.id || 0; // 基地id + const [curDeviceId, setCurDeviceId] = useState('1') + const deviceList = [ + { + id: '1', + title: '设备1' + }, + { + id: '2', + title: '设备2' + }, + { + id: '3', + title: '设备3' + }, + ] + + const [data, setData] = useState([ + { genre: 'Sports', sold: 275 }, + { genre: 'Strategy', sold: 115 }, + { genre: 'Action', sold: 120 }, + { genre: 'Shooter', sold: 350 }, + { genre: 'Other', sold: 150 }, + ]) + + return ( + + + + + { + deviceList.map(item => { + return ( + setCurDeviceId(item.id)} + >{ item.title } + ) + }) + } + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default WeatherStation; \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index e4b02b2..8c0ef2a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,10 @@ module.exports = { content: ["./src/**/*.{html,js,ts,jsx,tsx}"], theme: { - extend: {} + extend: {}, + textColor: { + 'primary': '#0fc87c' + } }, plugins: [], // v3 版本的 tailwindcss 有些不同