feat: 组件与页面
@ -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"
|
||||
},
|
||||
|
@ -14,7 +14,8 @@
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"packNpmRelationList": []
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "2.30.3",
|
||||
|
@ -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'
|
||||
}
|
||||
})
|
||||
|
BIN
src/assets/images/bg.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
src/assets/images/icons/back.png
Normal file
After Width: | Height: | Size: 164 B |
BIN
src/assets/images/icons/data/icon1.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/icons/data/icon2.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/images/icons/data/icon3.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/images/icons/data/icon4.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/icons/data/icon5.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/icons/data/icon6.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
6
src/assets/images/icons/data/index.ts
Normal file
@ -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';
|
BIN
src/assets/images/icons/question-line.png
Normal file
After Width: | Height: | Size: 706 B |
BIN
src/assets/images/loginBg.png
Normal file
After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 895 B |
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 670 B |
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 575 B |
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 633 B |
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 537 B |
20
src/components/customized/navigation/index.tsx
Normal file
@ -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 (
|
||||
<View className="w-full h-[180rpx] flex items-center justify-between pt-[90rpx] px-3">
|
||||
<View className="w-8 flex justify-center" onClick={() => {
|
||||
Taro.navigateBack()
|
||||
}}>
|
||||
{ showBack ? <Image src={arrow} className="w-6 h-6"></Image> : null }
|
||||
</View>
|
||||
<View className=" from-neutral-100 text-base text-center" style={{ color: 'white' }}>{ title }</View>
|
||||
<View className="w-8"></View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default HeaderNation;
|
6
src/config/axios/errorCode.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
'401': '认证失败,无法访问系统资源',
|
||||
'403': '当前操作没有权限',
|
||||
'404': '访问资源不存在',
|
||||
default: '系统未知错误,请反馈给管理员'
|
||||
}
|
0
src/config/axios/index.ts
Normal file
109
src/config/axios/service.ts
Normal file
@ -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<any>) => {
|
||||
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
|
||||
}
|
||||
}
|
||||
)
|
5
src/config/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const axiosConfig = {
|
||||
baseUrl: '',
|
||||
timeout: 30000,
|
||||
withCredentials: false // 禁用 Cookie 等信息
|
||||
}
|
@ -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 (
|
||||
<View className="h-[100vh] flex flex-col-reverse">
|
||||
<View
|
||||
className="h-[100vh] flex flex-col-reverse"
|
||||
style={{ 'backgroundImage': `url(${basicBg})`, 'backgroundSize': '100% auto', 'backgroundRepeat': 'no-repeat' }}
|
||||
>
|
||||
<View className="flex justify-evenly h-[6rem] align-top">
|
||||
{
|
||||
bottomMenuList.map(item => {
|
||||
@ -79,6 +96,15 @@ const Index = () => {
|
||||
: null
|
||||
}
|
||||
</View>
|
||||
<HeaderNation title={
|
||||
curSelectedItem === 'iot'
|
||||
? '物联网'
|
||||
: curSelectedItem === 'msg'
|
||||
? '消息中心'
|
||||
: curSelectedItem === 'equip'
|
||||
? '巡检设备'
|
||||
: ''
|
||||
} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -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 (
|
||||
<View className="p-1 h-full">
|
||||
<View className="h-full flex flex-col">
|
||||
<View className="flex px-2 py-1 border-b-2 border-slate-100">
|
||||
<View className="flex px-2 py-1">
|
||||
{
|
||||
topMenus.map(item => {
|
||||
return (
|
||||
<View
|
||||
className={`p-1 px-3 ${selectedMenuId === item.id ? 'text-lime-600 border-b-4 border-lime-800' : ''}`}
|
||||
className={`p-1 px-3 ${selectedMenuId === item.id ? 'border-b-4 border-lime-800' : ''}`}
|
||||
key={item.id}
|
||||
style={{ color: 'white' }}
|
||||
onClick={() => setSelectedMenuId(item.id)}
|
||||
>{ item.title }</View>
|
||||
)
|
||||
})
|
||||
}
|
||||
</View>
|
||||
<View className="h-full border flex flex-col space-y-2 p-2 rounded-sm border-slate-50">
|
||||
<View className="h-full flex flex-col space-y-3 p-2 rounded-sm">
|
||||
{
|
||||
dataList.map(item => {
|
||||
return (
|
||||
<View className="border border-slate-300 px-4 py-2 rounded-md shadow-lg flex space-x-4">
|
||||
<View
|
||||
className="px-4 py-2 rounded-md shadow-xl flex space-x-4 bg-white border border-slate-200"
|
||||
onClick={() => {
|
||||
Taro.navigateTo({ url: '/pages/weatherStation/index?id=' + item.id })
|
||||
}}
|
||||
>
|
||||
<View className="w-12 h-12 relative top-1">
|
||||
<Image src={img} className="w-12 h-12"></Image>
|
||||
</View>
|
||||
|
3
src/pages/login/index.config.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '登录'
|
||||
})
|
76
src/pages/login/index.tsx
Normal file
@ -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 (
|
||||
<View className="w-full h-[100vh] relative">
|
||||
<Image src={loginBg} className="w-full h-full" />
|
||||
<View className="absolute left-0 top-20 pt-3">
|
||||
<View className="text-slate-800 font-bold text-2xl px-6 pt-4">
|
||||
您好,
|
||||
</View>
|
||||
<View className="text-slate-800 font-bold text-2xl px-6 pt-3">
|
||||
欢迎登录
|
||||
</View>
|
||||
<View className="w-[100vw] mt-7 p-3 bg-white rounded-md">
|
||||
<View className="p-2">
|
||||
<View>用户名</View>
|
||||
<View className=" border-b-2 border-slate-400 py-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="请输入用户名"
|
||||
onInput={(_username) => setUserName((_username.target as any).value)}
|
||||
></Input>
|
||||
</View>
|
||||
</View>
|
||||
<View className="p-2">
|
||||
<View>密码</View>
|
||||
<View className=" border-b-2 border-slate-400 py-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="请输入密码"
|
||||
onInput={(_password) => setPassWord((_password.target as any).value)}
|
||||
></Input>
|
||||
</View>
|
||||
</View>
|
||||
<View className="flex flex-row-reverse p-1">
|
||||
<View className="flex items-center">
|
||||
<Image src={questionIcon} className="w-[1.1rem] h-[1.1rem]"></Image>
|
||||
<Text className="text-[1rem] p-1" style={{ color: '#0fc87c' }}>忘记密码?</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="p-2">
|
||||
<Button
|
||||
className="rounded-full"
|
||||
style={{
|
||||
background: 'linear-gradient(to right, #19b3c2, #0fc87e)',
|
||||
color: 'white'
|
||||
}}
|
||||
onClick={() => handleLogin()}
|
||||
>登录</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login;
|
3
src/pages/weatherStation/index.config.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '气象站'
|
||||
})
|
142
src/pages/weatherStation/index.tsx
Normal file
@ -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 (
|
||||
<View className="w-full flex justify-between text-[.8rem] p-2 pb-1">
|
||||
<View className=" font-bold">{ title }</View>
|
||||
<View style={{ color: '#999999' }}>{ updateTime !== '' ? '数据更新于' + updateTime : '' }</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const IconLabelValue = ({ value, unit, label, img }) => {
|
||||
return (
|
||||
<View className="flex items-center justify-center">
|
||||
<View className="flex flex-col items-start px-3 w-[calc(90% - 2.5rem)]">
|
||||
<View className="flex items-end pb-2">
|
||||
<View className="text-[1rem] font-bold text-primary">{ value }</View>
|
||||
<View
|
||||
className="text-slate-600 text-[.7rem] px-2"
|
||||
style={{ 'color': '#252525A5' }}
|
||||
>{ unit }</View>
|
||||
</View>
|
||||
<View className="text-[.7rem]">{ label }</View>
|
||||
</View>
|
||||
<View className="w-10 h-10 rounded-sm shadow-sm">
|
||||
<Image src={img} className="w-full h-full"></Image>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const GridContainer = ({ children }) => {
|
||||
return (
|
||||
<View className="border border-slate-200 px-4 py-3 rounded-md shadow-xl grid grid-cols-2">
|
||||
{ children }
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const BasicStatus = (status = true) => {
|
||||
return (
|
||||
<View
|
||||
style={{ backgroundColor: status ? '#0fc87c' : '#ff0000', 'color': 'white' }}
|
||||
className={ 'text-[.7rem] px-[.4rem] py-[.1rem] ' + (status ? 'rounded-sm text-white' : '') }
|
||||
>{ status ? '在线' : '离线' }</View>
|
||||
)
|
||||
}
|
||||
|
||||
const BasicInfoCard = ({ title, info, status, imgSrc }) => {
|
||||
return (
|
||||
<View
|
||||
className="bg-white px-4 py-3 rounded-md shadow-xl flex space-x-4"
|
||||
>
|
||||
<View className="w-16 h-14">
|
||||
<Image src={imgSrc} className="w-16 h-14"></Image>
|
||||
</View>
|
||||
<View className="w-full">
|
||||
<View className="font-bold text-[.9rem]">{ title }</View>
|
||||
<View className="text-[.8rem] text-slate-600 pb-2">{ info }</View>
|
||||
<View className="flex">{ status ? BasicStatus() : '' }</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<View className="h-[100vh] flex flex-col" style={{ 'backgroundImage': `url(${basicBg})`, 'backgroundSize': '100% auto', 'backgroundRepeat': 'no-repeat' }}>
|
||||
<HeaderNation title="气象站" />
|
||||
<View className="h-full flex flex-col px-1">
|
||||
<View className="flex px-2 py-1">
|
||||
{
|
||||
deviceList.map(item => {
|
||||
return (
|
||||
<View
|
||||
className={`p-1 px-3 ${curDeviceId === item.id ? 'text-lime-600 border-b-4 border-lime-800' : ''}`}
|
||||
key={item.id}
|
||||
style={{ color: 'white' }}
|
||||
onClick={() => setCurDeviceId(item.id)}
|
||||
>{ item.title }</View>
|
||||
)
|
||||
})
|
||||
}
|
||||
</View>
|
||||
<View className="h-full flex flex-col space-y-2 p-2 rounded-sm">
|
||||
<BasicInfoCard imgSrc={img} title={'大棚环境监测一体机'} info={'BNYKLH09LK001'} status={true} />
|
||||
<SubTitle title="实时数据" updateTime="2023-04-04" />
|
||||
<GridContainer>
|
||||
<View className="p-2 py-3">
|
||||
<IconLabelValue value={27.34} label={'大气温度'} unit={'℃'} img={icon1} />
|
||||
</View>
|
||||
<View className="p-2 py-3">
|
||||
<IconLabelValue value={27.34} label={'大气温度'} unit={'℃'} img={icon2} />
|
||||
</View>
|
||||
<View className="p-2 py-3">
|
||||
<IconLabelValue value={27.34} label={'大气温度'} unit={'℃'} img={icon3} />
|
||||
</View>
|
||||
<View className="p-2 py-3">
|
||||
<IconLabelValue value={27.34} label={'大气温度'} unit={'℃'} img={icon4} />
|
||||
</View>
|
||||
</GridContainer>
|
||||
<SubTitle title="数据监测" />
|
||||
<View className="border border-slate-200 px-4 py-3 rounded-md shadow-xl">
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default WeatherStation;
|
@ -2,7 +2,10 @@
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{html,js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {}
|
||||
extend: {},
|
||||
textColor: {
|
||||
'primary': '#0fc87c'
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
// v3 版本的 tailwindcss 有些不同
|
||||
|