diff --git a/public/weather_img/img/weather_bg_large.webp b/public/weather_img/img/weather_bg_large.webp
new file mode 100644
index 0000000..57d4589
Binary files /dev/null and b/public/weather_img/img/weather_bg_large.webp differ
diff --git a/src/GlobalModal.tsx b/src/GlobalModal.tsx
index 8db28a9..f6d1aa7 100644
--- a/src/GlobalModal.tsx
+++ b/src/GlobalModal.tsx
@@ -9,7 +9,7 @@ const SearchPage = asyncLoader(() => import('@/layout/header/search/SearchPage')
const AdderPage = asyncLoader(() => import('@/layout/adder/AdderPage'))
const BackgroundSwtich = asyncLoader(() => import('@/layout/background/BackgroundSwtich'))
-const noFullList: RouteStr[] = ['global-search', 'global-adder', 'global-background']
+const fullList: RouteStr[] = []
export default defineComponent(() => {
const router = useRouterStore()
@@ -43,36 +43,40 @@ export default defineComponent(() => {
{/* 关闭按钮 */}
{
router.back()
}}
>
-
+
+
+
{/* 全屏按钮 */}
- {noFullList.includes(router.path) ? null : (
+ {!fullList.includes(router.path) ? null : (
{
full.value = !full.value
}}
>
-
+
+
+
)}
@@ -83,23 +87,22 @@ export default defineComponent(() => {
) : router.path === 'global-adder' ? (
) : router.path === 'global-background' ? (
-
- ) :
- router.path.startsWith('widget-') ? (
- (() => {
- const name = router.path.split('-')[1]
- const selected = widgetList.find((el) => el.name === name)
- if (!selected)
- return (
-
- 组件维护中
-
- )
- const compo = selected.modal
- console.log(compo)
- return
- })()
- ) : null}
+
+ ) : router.path.startsWith('widget-') ? (
+ (() => {
+ const name = router.path.split('-')[1]
+ const selected = widgetList.find((el) => el.name === name)
+ if (!selected)
+ return (
+
+ 组件维护中
+
+ )
+ const compo = selected.modal
+ console.log(compo)
+ return
+ })()
+ ) : null}
diff --git a/src/main.css b/src/main.css
index e477f7b..56b04fa 100644
--- a/src/main.css
+++ b/src/main.css
@@ -3,28 +3,29 @@
@tailwind utilities;
:root {
- font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family:
- SourceHanSans,
+ SourceHanSansCN-Regular,
-apple-system,
- BlinkMacSystemFont,
- Helvetica Neue,
PingFang SC,
Microsoft YaHei,
- Source Han Sans SC,
Noto Sans CJK SC,
- WenQuanYi Micro Hei,
- sans-serif;
+ WenQuanYi Micro Hei !important;
}
@font-face {
- font-family: 'SourceHanSans';
+ font-family: 'SourceHanSansCN-Regular';
src: url('/fonts/SourceHanSansCN-Regular.subset.otf') format('truetype');
font-weight: normal;
font-style: normal;
}
+@font-face {
+ font-family: 'SourceHanSansCN-bold';
+ src: url('/fonts/SourceHanSansCN-Regular.subset.otf') format('truetype');
+ font-weight: 700;
+ font-style: normal;
+}
body {
/* ! 全局禁用鼠标选择,需要在其他位置放开 */
user-select: none;
diff --git a/src/main.ts b/src/main.ts
index 6c56604..e08fc26 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -8,12 +8,14 @@ import getFp from './utils/getFp'
import vOutsideClick from './utils/vOutsideClick'
import dayjs from 'dayjs'
import Toast, { useToast, type PluginOptions } from 'vue-toastification'
+import customParseFormat from 'dayjs/plugin/customParseFormat'
import 'vue-toastification/dist/index.css'
import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn')
const app = createApp(App)
export const globalToast = useToast()
+dayjs.extend(customParseFormat)
// ! persist 利用 localstorage,请不要在大量数据时使用
// 大量数据(扩张内容,文件),清直接使用 ./db.ts
diff --git a/src/settings/useSettingsStore.ts b/src/settings/useSettingsStore.ts
index 996f4a5..4ce8147 100644
--- a/src/settings/useSettingsStore.ts
+++ b/src/settings/useSettingsStore.ts
@@ -19,7 +19,7 @@ export default defineStore(
showTime: true,
showAdder: true,
// 尺寸
- blockSize: 6,
+ blockSize: 6.7,
blockPadding: 1,
mainWidth: 70,
blockRadius: 0.2,
diff --git a/src/widgets/weather/Large.tsx b/src/widgets/weather/Large.tsx
index ce10fde..d6bc224 100644
--- a/src/widgets/weather/Large.tsx
+++ b/src/widgets/weather/Large.tsx
@@ -1,10 +1,69 @@
import { defineComponent } from 'vue'
import useWeatherStore from './useWeatherStore'
-
+import WeatherIcon from './weatherIcon'
+import dayjs from 'dayjs'
+import clsx from 'clsx'
export default defineComponent(() => {
const weather = useWeatherStore()
- return () =>
+
{weather.weatherData?.base.stemp}°
-
+
-
+
{weather.weatherData?.city.name}
-
-
- {weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°
-
+
+ {weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°
-
{weather.weatherData?.base.weather}
+
+
+ {weather.weatherData?.base.weather}
+
+
+
+
+ {weather.weatherData?.weathers7
+ .filter((_, idx) => idx < 3)
+ .map((item, idx) => {
+ return (
+
+
+ {idx === 0 ? '今天' : idx === 1 ? '明天' : dayjs().format('ddd')}
+
+
+
+ {item.ltemp + '°/' + item.htemp + '°'}
+
+
+ )
+ })}
+
diff --git a/src/widgets/weather/Modal.tsx b/src/widgets/weather/Modal.tsx
index 9c16be7..388bca6 100644
--- a/src/widgets/weather/Modal.tsx
+++ b/src/widgets/weather/Modal.tsx
@@ -1,5 +1,333 @@
-import { defineComponent } from 'vue'
-
+import { Cascader, message } from 'ant-design-vue'
+import { computed, defineComponent, onMounted, ref, type HTMLAttributes, type VNodeRef } from 'vue'
+import useWeatherStore from './useWeatherStore'
+import type { DefaultOptionType } from 'ant-design-vue/es/select'
+import { addIcons, OhVueIcon } from 'oh-vue-icons'
+import clsx from 'clsx'
+import { FaMinus, HiSolidLocationMarker } from 'oh-vue-icons/icons'
+import WeatherIcon from './weatherIcon'
+import dayjs from 'dayjs'
+addIcons(FaMinus, HiSolidLocationMarker)
export default defineComponent(() => {
- return () =>
+ const store = useWeatherStore()
+ const hourRef = ref
(null)
+ const cascaderTriiger = ref(true)
+ const searchValue = ref('')
+ const filter = (inputValue: string, path: DefaultOptionType[]) =>
+ path.some(
+ (option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1
+ )
+ onMounted(() => {
+ store.queryAll()
+ })
+ const nowWeather = computed(() => {
+ if (store.state.selectedCityCode === -1) return store.weatherData
+ const idx = store.state.weatherList.findIndex((val) => val.id === store.state.selectedCityCode)
+
+ if (idx !== -1) {
+ return store.state.weatherList[idx].data
+ } else {
+ return store.weatherData
+ }
+ })
+ function getPracticeIndex(val: string) {
+ const numVal = parseFloat(val) || 0
+ if (numVal < 115) {
+ return '适宜晨练'
+ } else {
+ return '不宜晨练'
+ }
+ }
+ function pollution(val: string) {
+ const numVal = parseFloat(val) || 0
+ if (numVal < 35) {
+ return '优'
+ } else if (numVal < 75) {
+ return '良'
+ } else if (numVal < 115) {
+ return '轻度污染'
+ } else if (numVal < 150) {
+ return '中度污染'
+ } else if (numVal < 250) {
+ return '重度污染'
+ } else if (numVal >= 250) {
+ return '严重污染'
+ } else {
+ return '未知'
+ }
+ }
+ function shirtIndex(val: string) {
+ const numVal = parseFloat(val) || 0
+ if (numVal < 6) {
+ return '寒冷'
+ } else if (numVal < 10.9) {
+ return '冷'
+ } else if (numVal < 14.9) {
+ return '凉'
+ } else if (numVal < 17.9) {
+ return '温凉'
+ } else if (numVal < 20.9) {
+ return '凉舒适'
+ } else if (numVal < 23.9) {
+ return '舒适'
+ } else if (numVal < 27.9) {
+ return '热舒适'
+ } else if (numVal >= 28) {
+ return '炎热'
+ } else {
+ return '温凉'
+ }
+ }
+ function coldIndex(val: string) {
+ const numVal = parseFloat(val) || 0
+ if (numVal < 6) {
+ return '易发感冒'
+ } else if (numVal < 10.9) {
+ return '较易发感冒'
+ } else {
+ return '不易发感冒'
+ }
+ }
+ return () => (
+
+
+
+ {cascaderTriiger.value && (
+ {
+ cascaderTriiger.value = false
+ setTimeout(() => {
+ cascaderTriiger.value = true
+ }, 0)
+
+ if (store.state.weatherList.findIndex((val) => val.id === option[2].value) !== -1) {
+ message.info('该地区已添加')
+ return
+ }
+ if (store.state.weatherList.length >= 5) {
+ message.info('最多只能添加5个地区')
+ return
+ }
+ store.state.weatherList.push({
+ id: option[2].value as number,
+ name: option[2].label,
+ data: await store.query(option[2].value as number)
+ })
+ store.state.selectedCityCode = option[2].value as number
+ }}
+ notFoundContent="没有找到该地区"
+ onClick={() => {
+ if (store.state.cityOptions.length === 0) store.queryCity()
+ }}
+ searchValue={searchValue.value}
+ placeholder="搜索其他地区天气"
+ showSearch={{ filter }}
+ onSearch={(value) => {
+ searchValue.value = value
+ }}
+ />
+ )}
+
+
+
{
+ store.state.selectedCityCode = -1
+ }}
+ class={clsx(
+ 'flex flex-col justify-between gap-x-4 w-full h-[60px] relative py-2 px-3 rounded-lg cursor-pointer hover:bg-gradient-to-b from-[#d6ecff] to-[#dce3ff]',
+ store.state.selectedCityCode === -1
+ ? 'bg-gradient-to-b from-[#d6ecff] to-[#dce3ff]'
+ : 'bg-black/[0.05]'
+ )}
+ >
+
+
+
+ {store.weatherData?.city.name}
+
+
{store.weatherData?.base.stemp}°
+
+
+
+
+ {store.weatherData?.base.weather}
+
+
+ {store.weatherData?.base.ltemp}°/{store.weatherData?.base.htemp}°
+
+
+
+ {store.state.weatherList.map((item) => (
+
{
+ store.state.selectedCityCode = item.id
+ }}
+ class={clsx(
+ 'flex flex-col group justify-between gap-x-4 w-full h-[65px] group relative py-2 px-3 rounded-lg cursor-pointer hover:bg-gradient-to-b from-[#d6ecff] to-[#dce3ff]',
+ store.state.selectedCityCode === item.id
+ ? 'bg-gradient-to-b from-[#d6ecff] to-[#dce3ff]'
+ : 'bg-black/[0.05]'
+ )}
+ key={item.id}
+ >
+
+
+ {item.data?.city.name}
+
+
{item.data?.base.stemp}°
+
+
+
+
+ {item.data?.base.weather}
+
+
+ {item.data?.base.ltemp}°/{item.data?.base.htemp}°
+
+
+
{
+ e.stopPropagation()
+
+ store.state.weatherList = store.state.weatherList.filter(
+ (val) => val.id !== item.id
+ )
+ store.state.selectedCityCode = -1
+ }}
+ >
+
+
+
+ ))}
+
+
+
+
+
+
+ {store.weatherData?.city.name}
+ {nowWeather.value?.base.stemp}°
+
+ {nowWeather.value?.base.weather}
+
+ {nowWeather.value?.base.ltemp}°/{nowWeather.value?.base.htemp}°
+
+
+
+
+ {nowWeather.value?.base.WD} {nowWeather.value?.base.WS}
+
+
+
+
+
+ 晨练指数:
+ {getPracticeIndex(nowWeather.value?.base.aqi || '0')}
+
+
+ 空气质量:
+ {pollution(nowWeather.value?.base.pm25 || '0')}
+
+
+ 穿衣指数:
+ {shirtIndex(nowWeather.value?.base.stemp || '0')}
+
+
+ 雨伞指数:
+
+ {nowWeather.value?.hour24.some((val) => val.weather.includes('雨'))
+ ? '需带伞'
+ : '无需带伞'}
+
+
+
+ 感冒指数:
+ {coldIndex(nowWeather.value?.base.stemp || '0')}
+
{' '}
+
+ 紫外线指数:
+ 紫外线{nowWeather.value?.base.ultraviolet}
+
+
+
+
+
+
+
+
7日天气预报
+
+ {nowWeather.value?.weathers7.map((item, idx) => {
+ return (
+
+
+ {item.ltemp + '°/' + item.htemp + '°'}
+
+
+
+ {item.weather}
+
+
+
+ {dayjs(item.date, 'MM月DD日').format('MM/DD')}
+
+
+ {idx === 0
+ ? '今天'
+ : idx === 1
+ ? '明天'
+ : dayjs(item.date, 'MM月DD日').format('ddd')}
+
+
+
+ )
+ })}
+
+
+
+
+
+
+ )
})
diff --git a/src/widgets/weather/Small.tsx b/src/widgets/weather/Small.tsx
index ef55e43..b000d32 100644
--- a/src/widgets/weather/Small.tsx
+++ b/src/widgets/weather/Small.tsx
@@ -1,5 +1,6 @@
import { defineComponent } from 'vue'
import useWeatherStore from './useWeatherStore'
+import WeatherIcon from './weatherIcon'
export default defineComponent(() => {
const weather = useWeatherStore()
@@ -15,6 +16,7 @@ export default defineComponent(() => {
{weather.weatherData?.city.name}
+
{weather.weatherData?.base.weather}
diff --git a/src/widgets/weather/useWeatherStore.ts b/src/widgets/weather/useWeatherStore.ts
index ef70253..0be54ec 100644
--- a/src/widgets/weather/useWeatherStore.ts
+++ b/src/widgets/weather/useWeatherStore.ts
@@ -19,6 +19,7 @@ type WeatherState = {
selected: number
selectedCityCode: number
cityOptions: AntdCityOptions[]
+ weatherList: { id: number; data?: WeatherData; name: '' }[]
}
const baseWeatherUrl = 'https://i.tianqi.com/'
@@ -27,7 +28,8 @@ export default defineStore('weather', () => {
const state = reactive({
selected: 0,
selectedCityCode: -1,
- cityOptions: []
+ cityOptions: [],
+ weatherList: []
} as WeatherState)
const city = ref('')
@@ -53,18 +55,40 @@ export default defineStore('weather', () => {
}
const fetchWeather = async (code: number) => {
try {
- const nowResponse = await fetch(baseWeatherUrl + '?c=mfjson' + (code ? '&id=' + code : ''))
+ const nowResponse = await fetch(baseWeatherUrl + '?c=mfjson' + (code !== -1 ? '&id=' + code : ''))
const res = await nowResponse.json()
-
- weatherData.value = res
+ return res
} catch (e) {
return
}
}
+ const queryAll = () => {
+ state.weatherList.forEach(async item => {
+ item.data = await fetchWeather(item.id)
+ })
+ }
+ const query = async (code: number) => {
+ const data = await fetchWeather(code)
+ return data
+ }
watch(
() => state.selectedCityCode,
- (e) => {
- fetchWeather(e)
+ async (e) => {
+ if (e === -1) {
+ const data = await fetchWeather(e)
+
+ weatherData.value = data
+ } else {
+
+ const idx = state.weatherList.findIndex((item) => item.id === e)
+
+ if (idx === -1) {
+ if (!state.weatherList[idx]?.data) return
+ const data = await fetchWeather(e)
+ state.weatherList[idx].data = data
+ }
+
+ }
},
{
immediate: true
@@ -75,7 +99,9 @@ export default defineStore('weather', () => {
state,
city,
weatherData,
- queryCity
+ queryCity,
+ queryAll,
+ query
}
}, {
persist: true
diff --git a/src/widgets/weather/weatherIcon.tsx b/src/widgets/weather/weatherIcon.tsx
new file mode 100644
index 0000000..e7ea211
--- /dev/null
+++ b/src/widgets/weather/weatherIcon.tsx
@@ -0,0 +1,110 @@
+import { computed, defineComponent, render } from 'vue'
+
+export default defineComponent({
+ name: 'WeatherIcon',
+ props: {
+ weather: {
+ type: String,
+ default: ''
+ },
+ size: {
+ type: Number,
+ default: 24
+ }
+ },
+ setup: (props, ctx) => {
+ const list = [
+ '阴', //0
+ '大雪', //1
+ '暴雪', //2
+ '中雪', //3
+ '小雪', //4
+ '雨夹雪', // 5
+ '阵雪', //6
+ '雪', //7
+ '中雨', //8
+ '小阵雨',
+ '雷阵雨伴有冰雹', // 10
+ '阵雨',
+ '冻雨',
+ '雷阵雨',
+ '大雨',
+ '暴雨', // 15
+ '大暴雨',
+ '特大暴雨',
+ '夜间小雨',
+ '小雨',
+ '晴转小雨', // 20
+ '多云转晴',
+ '睛转多云',
+ '多云',
+ '大部份多云',
+ '打雷', // 25
+ '冷',
+ '霜',
+ '强沙尘暴',
+ '沙尘暴',
+ '龙卷风', // 30
+ '杨沙',
+ '雾',
+ '晴',
+ '霾',
+ '风', // 35
+ '大风',
+ '飓风',
+ '未知天气', //38
+ '多云转阴',
+ '晴转多云', //40
+ '阴转小雨',
+ '多云转阴',
+ '轻度雾霾', //43
+ '轻度雾霾转多云', //44
+ '小雨转多云', //45
+ '小雨转晴',
+ '阴转晴',
+ '中度雾霾', //48
+ '阴转多云',
+ '阴到小雨', //50
+ '晴转雪 ',
+ '多云转雨',
+ '风转晴',
+ '多云转小雪',
+ '雪转雨', //55
+ '小雨转雨',
+ '多云转雪',
+ '小雪转多云',
+ '晴转阴',
+ '阴转中雨', //60
+ '雪转多云',
+ '小雨转阴', //62
+ '小雨转中雨',
+ '多云转小雨',
+ '小雨到大雨', //65
+ '轻度雾霾转小雨',
+ '轻度雾霾转晴',
+ '小雨转雪',
+ '阴转雨', //69
+ '中雨转阴', //70
+ '中雨转小雨', //71
+ '大雨转小雨' //72
+ ]
+ const idx = computed(() => list.indexOf(props.weather || '未知天气'))
+
+ const url = computed(() => {
+ return idx.value < 0 ? '/weather_img/38.png' : `/weather_img/${idx.value}.png`
+ })
+ return () => (
+ <>
+
+ >
+ )
+ }
+})