完成天气弹框
This commit is contained in:
parent
6d9ddcf23b
commit
c9cdd4d727
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -9,7 +9,7 @@ const SearchPage = asyncLoader(() => import('@/layout/header/search/SearchPage')
|
||||||
const AdderPage = asyncLoader(() => import('@/layout/adder/AdderPage'))
|
const AdderPage = asyncLoader(() => import('@/layout/adder/AdderPage'))
|
||||||
const BackgroundSwtich = asyncLoader(() => import('@/layout/background/BackgroundSwtich'))
|
const BackgroundSwtich = asyncLoader(() => import('@/layout/background/BackgroundSwtich'))
|
||||||
|
|
||||||
const noFullList: RouteStr[] = ['global-search', 'global-adder', 'global-background']
|
const fullList: RouteStr[] = []
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const router = useRouterStore()
|
const router = useRouterStore()
|
||||||
|
@ -43,37 +43,41 @@ export default defineComponent(() => {
|
||||||
<div
|
<div
|
||||||
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 overflow-hidden transition-all"
|
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 overflow-hidden transition-all"
|
||||||
style={{
|
style={{
|
||||||
width: full.value ? '100%' : '900px',
|
width: full.value ? '100%' : '984px',
|
||||||
height: full.value ? '100vh' : '540px',
|
height: full.value ? '100vh' : '580px',
|
||||||
borderRadius: full.value ? '0' : '1rem'
|
borderRadius: full.value ? '0' : '1rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 关闭按钮 */}
|
{/* 关闭按钮 */}
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
'w-5 h-5 flex justify-center items-center rounded-full overflow-hidden absolute bg-red-500/70 hover:bg-red-500 transition-all cursor-pointer z-30 ' +
|
'w-[16px] h-[16px] group flex justify-center items-center rounded-full overflow-hidden absolute bg-red-500/70 hover:bg-red-500 transition-all cursor-pointer z-30 ' +
|
||||||
(router.path === 'global-adder' ? 'top-6 right-4' : 'top-2 right-2')
|
(router.path === 'global-adder' ? 'top-6 right-8' : 'top-2 right-6')
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.back()
|
router.back()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OhVueIcon name="md-close" scale={0.7} fill="white" />
|
<div class={' items-center justify-center hidden group-hover:flex'}>
|
||||||
|
<OhVueIcon name="md-close" scale={0.6} fill="white" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* 全屏按钮 */}
|
{/* 全屏按钮 */}
|
||||||
{noFullList.includes(router.path) ? null : (
|
{!fullList.includes(router.path) ? null : (
|
||||||
<div
|
<div
|
||||||
class="w-5 h-5 flex justify-center items-center rounded-full overflow-hidden absolute top-2 right-10 bg-green-500/70 hover:bg-green-500 transition-all cursor-pointer z-30"
|
class="w-[16px] group h-[16px] flex justify-center items-center rounded-full overflow-hidden absolute top-2 right-12 bg-green-500/70 hover:bg-green-500 transition-all cursor-pointer z-30"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
full.value = !full.value
|
full.value = !full.value
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div class={' items-center justify-center hidden group-hover:flex'}>
|
||||||
<OhVueIcon
|
<OhVueIcon
|
||||||
name={full.value ? 'md-closefullscreen' : 'md-openinfull'}
|
name={full.value ? 'md-closefullscreen' : 'md-openinfull'}
|
||||||
scale={0.6}
|
scale={0.6}
|
||||||
fill="white"
|
fill="white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div class="w-full h-full flex justify-center items-center">
|
<div class="w-full h-full flex justify-center items-center">
|
||||||
|
@ -84,8 +88,7 @@ export default defineComponent(() => {
|
||||||
<AdderPage />
|
<AdderPage />
|
||||||
) : router.path === 'global-background' ? (
|
) : router.path === 'global-background' ? (
|
||||||
<BackgroundSwtich />
|
<BackgroundSwtich />
|
||||||
) :
|
) : router.path.startsWith('widget-') ? (
|
||||||
router.path.startsWith('widget-') ? (
|
|
||||||
(() => {
|
(() => {
|
||||||
const name = router.path.split('-')[1]
|
const name = router.path.split('-')[1]
|
||||||
const selected = widgetList.find((el) => el.name === name)
|
const selected = widgetList.find((el) => el.name === name)
|
||||||
|
|
17
src/main.css
17
src/main.css
|
@ -3,28 +3,29 @@
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
font-family:
|
font-family:
|
||||||
SourceHanSans,
|
SourceHanSansCN-Regular,
|
||||||
-apple-system,
|
-apple-system,
|
||||||
BlinkMacSystemFont,
|
|
||||||
Helvetica Neue,
|
|
||||||
PingFang SC,
|
PingFang SC,
|
||||||
Microsoft YaHei,
|
Microsoft YaHei,
|
||||||
Source Han Sans SC,
|
|
||||||
Noto Sans CJK SC,
|
Noto Sans CJK SC,
|
||||||
WenQuanYi Micro Hei,
|
WenQuanYi Micro Hei !important;
|
||||||
sans-serif;
|
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'SourceHanSans';
|
font-family: 'SourceHanSansCN-Regular';
|
||||||
src: url('/fonts/SourceHanSansCN-Regular.subset.otf') format('truetype');
|
src: url('/fonts/SourceHanSansCN-Regular.subset.otf') format('truetype');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: 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 {
|
body {
|
||||||
/* ! 全局禁用鼠标选择,需要在其他位置放开 */
|
/* ! 全局禁用鼠标选择,需要在其他位置放开 */
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
|
@ -8,12 +8,14 @@ import getFp from './utils/getFp'
|
||||||
import vOutsideClick from './utils/vOutsideClick'
|
import vOutsideClick from './utils/vOutsideClick'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import Toast, { useToast, type PluginOptions } from 'vue-toastification'
|
import Toast, { useToast, type PluginOptions } from 'vue-toastification'
|
||||||
|
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||||
import 'vue-toastification/dist/index.css'
|
import 'vue-toastification/dist/index.css'
|
||||||
import 'dayjs/locale/zh-cn'
|
import 'dayjs/locale/zh-cn'
|
||||||
dayjs.locale('zh-cn')
|
dayjs.locale('zh-cn')
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
export const globalToast = useToast()
|
export const globalToast = useToast()
|
||||||
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
// ! persist 利用 localstorage,请不要在大量数据时使用
|
// ! persist 利用 localstorage,请不要在大量数据时使用
|
||||||
// 大量数据(扩张内容,文件),清直接使用 ./db.ts
|
// 大量数据(扩张内容,文件),清直接使用 ./db.ts
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default defineStore(
|
||||||
showTime: true,
|
showTime: true,
|
||||||
showAdder: true,
|
showAdder: true,
|
||||||
// 尺寸
|
// 尺寸
|
||||||
blockSize: 6,
|
blockSize: 6.7,
|
||||||
blockPadding: 1,
|
blockPadding: 1,
|
||||||
mainWidth: 70,
|
mainWidth: 70,
|
||||||
blockRadius: 0.2,
|
blockRadius: 0.2,
|
||||||
|
|
|
@ -1,10 +1,69 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import useWeatherStore from './useWeatherStore'
|
import useWeatherStore from './useWeatherStore'
|
||||||
|
import WeatherIcon from './weatherIcon'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import clsx from 'clsx'
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const weather = useWeatherStore()
|
const weather = useWeatherStore()
|
||||||
return () => <div class="w-full h-full bg-red-50"
|
return () => (
|
||||||
|
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
|
||||||
|
<div class={'z-10 relative flex justify-evenly px-3 py-3 items-center text-white'}>
|
||||||
|
<span class="text-[26px] font-din font-bold">{weather.weatherData?.base.stemp}°</span>
|
||||||
|
<div class="flex flex-col justify-between items-center h-full">
|
||||||
|
<div class="flex items-center gap-x-1">
|
||||||
|
<span class="font-bold text-[14px] leading-[14px] ">
|
||||||
|
{weather.weatherData?.city.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[12px] bg-white/[.2] rounded-xl px-4 py-[2px] font-din font-bold">
|
||||||
|
最低{weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°最高
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class={'flex flex-col justify-center items-center'}>
|
||||||
|
<WeatherIcon size={30} weather={weather.weatherData?.base.weather}></WeatherIcon>
|
||||||
|
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 ">
|
||||||
|
<div class="w-full h-full flex justify-evenly items-end pb-[6px]">
|
||||||
|
{weather.weatherData?.weathers7.map((item, idx) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(
|
||||||
|
'flex flex-col items-center justify-center gap-y-[2px] py-[14px] px-[3px] rounded-[15px] ',
|
||||||
|
idx === 0 ? 'text-white' : 'text-[#333]'
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg,#5996ff 0%,#4862ff 100%)'
|
backgroundImage:
|
||||||
}}>{}</div>
|
idx === 0
|
||||||
|
? 'linear-gradient(135deg,#5996ff,#4862ff)'
|
||||||
|
: 'linear-gradient(135deg, rgba(89, 150, 255, .1), #4862ff1a)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="text-[12px]">
|
||||||
|
{idx === 0
|
||||||
|
? '今天'
|
||||||
|
: idx === 1
|
||||||
|
? '明天'
|
||||||
|
: dayjs(item.date, 'MM月DD日').format('ddd')}
|
||||||
|
</span>
|
||||||
|
<WeatherIcon size={20} weather={item.weather}></WeatherIcon>
|
||||||
|
<span class="text-[12px] font-bold font-din ">
|
||||||
|
{item.ltemp + '°/' + item.htemp + '°'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class={'w-full h-[100px] absolute left-0 top-0 '}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('/weather_img/img/weather_bg_large.webp')`,
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: 'contain'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,33 +1,67 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import useWeatherStore from './useWeatherStore'
|
import useWeatherStore from './useWeatherStore'
|
||||||
|
import WeatherIcon from './weatherIcon'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const weather = useWeatherStore()
|
const weather = useWeatherStore()
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class="w-full h-full bg-[#ecfbff] ">
|
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
|
||||||
<div class={'z-10 relative flex justify-between px-3 py-3 items-center text-white'}>
|
<div class={'z-10 relative flex justify-between px-3 py-3 items-center text-white'}>
|
||||||
<span class="text-[26px] font-din font-bold">{weather.weatherData?.base.stemp}°</span>
|
<span class="text-[26px] font-din font-bold">{weather.weatherData?.base.stemp}°</span>
|
||||||
<div class="flex flex-col gap-y-[1px]">
|
<div class="flex flex-col justify-between items-center h-full">
|
||||||
<div class="flex items-center gap-x-1">
|
<div class="flex items-center gap-x-1">
|
||||||
<span class="font-bold text-[14px] leading-[14px] ">
|
<span class="font-bold text-[14px] leading-[14px] ">
|
||||||
{weather.weatherData?.city.name}
|
{weather.weatherData?.city.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class={'tracking-tight flex gap-x-1 items-center'}>
|
<span class="text-[12px] bg-white/[.2] rounded-xl px-2 py-[2px] font-din font-bold">
|
||||||
<span class="text-[12px]">
|
|
||||||
{weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°
|
{weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°
|
||||||
</span>
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class={'flex flex-col justify-center items-center'}>
|
||||||
|
<WeatherIcon size={30} weather={weather.weatherData?.base.weather}></WeatherIcon>
|
||||||
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 ">
|
||||||
|
<div class="w-full h-full flex justify-evenly items-end pb-[6px]">
|
||||||
|
{weather.weatherData?.weathers7
|
||||||
|
.filter((_, idx) => idx < 3)
|
||||||
|
.map((item, idx) => {
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
class={'w-full h-[73px] absolute left-0 top-0 '}
|
class={clsx(
|
||||||
|
'flex flex-col items-center justify-center gap-y-[2px] py-[14px] px-[3px] rounded-[15px] ',
|
||||||
|
idx === 0 ? 'text-white' : 'text-[#333]'
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
idx === 0
|
||||||
|
? 'linear-gradient(135deg,#5996ff,#4862ff)'
|
||||||
|
: 'linear-gradient(135deg, rgba(89, 150, 255, .1), #4862ff1a)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="text-[12px]">
|
||||||
|
{idx === 0 ? '今天' : idx === 1 ? '明天' : dayjs().format('ddd')}
|
||||||
|
</span>
|
||||||
|
<WeatherIcon size={20} weather={item.weather}></WeatherIcon>
|
||||||
|
<span class="text-[12px] font-bold font-din ">
|
||||||
|
{item.ltemp + '°/' + item.htemp + '°'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class={'w-full h-[100px] absolute left-0 top-0 '}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url('/weather_img/img/weather_bg_mdeium.webp')`,
|
backgroundImage: `url('/weather_img/img/weather_bg_mdeium.webp')`,
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundSize: '100%'
|
backgroundSize: 'contain'
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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(() => {
|
export default defineComponent(() => {
|
||||||
return () => <div class="w-full h-full bg-red-50"></div>
|
const store = useWeatherStore()
|
||||||
|
const hourRef = ref<VNodeRef | null>(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 () => (
|
||||||
|
<div
|
||||||
|
class="w-full h-full bg-red-50 flex "
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(180deg,#dcefff 0%,#e7ecff 100%)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={' w-[228px] px-6 pt-6 h-full bg-white/[.7] rounded-[20px]'}>
|
||||||
|
<div class={'relative '}>
|
||||||
|
{cascaderTriiger.value && (
|
||||||
|
<Cascader
|
||||||
|
class={'w-full shadow-sm rounded-lg border-[1px] '}
|
||||||
|
options={store.state.cityOptions}
|
||||||
|
onChange={async (e: any, option: DefaultOptionType[]) => {
|
||||||
|
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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class={'w-full flex flex-col gap-y-2 mt-4'}>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
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]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class={'flex items-center justify-between gap-x-1 text-[#333]'}>
|
||||||
|
<div class={'flex items-center gap-x-1 flex-1'}>
|
||||||
|
<OhVueIcon name={HiSolidLocationMarker.name} fill="#333" scale={0.8}></OhVueIcon>
|
||||||
|
<span class={'text-[14px]'}>{store.weatherData?.city.name}</span>
|
||||||
|
</div>
|
||||||
|
<span class={'font-bold text-[16px]'}>{store.weatherData?.base.stemp}°</span>
|
||||||
|
</div>
|
||||||
|
<div class={'flex items-center justify-between gap-x-1 text-[#333]'}>
|
||||||
|
<div class={'flex items-center text-[12px] gap-x-1 '}>
|
||||||
|
<WeatherIcon weather={store.weatherData?.base.weather} size={16}></WeatherIcon>
|
||||||
|
<span>{store.weatherData?.base.weather}</span>
|
||||||
|
</div>
|
||||||
|
<span class={'text-[12px]'}>
|
||||||
|
{store.weatherData?.base.ltemp}°/{store.weatherData?.base.htemp}°
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{store.state.weatherList.map((item) => (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<div class={'flex items-center justify-between gap-x-1 text-[#333]'}>
|
||||||
|
<div class={'flex items-center gap-x-1 flex-1'}>
|
||||||
|
<span class={'text-[14px]'}>{item.data?.city.name}</span>
|
||||||
|
</div>
|
||||||
|
<span class={'font-bold text-[16px]'}>{item.data?.base.stemp}°</span>
|
||||||
|
</div>
|
||||||
|
<div class={'flex items-center justify-between gap-x-1 text-[#333]'}>
|
||||||
|
<div class={'flex items-center text-[12px] gap-x-1 '}>
|
||||||
|
<WeatherIcon weather={item.data?.base.weather} size={16}></WeatherIcon>
|
||||||
|
<span>{item.data?.base.weather}</span>
|
||||||
|
</div>
|
||||||
|
<span class={'text-[12px]'}>
|
||||||
|
{item.data?.base.ltemp}°/{item.data?.base.htemp}°
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class={
|
||||||
|
'absolute hidden top-[-6px] z-10 right-[-6px] group-hover:flex bg-black/20 cursor-default rounded-full h-[20px] w-[20px] items-center justify-center'
|
||||||
|
}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
store.state.weatherList = store.state.weatherList.filter(
|
||||||
|
(val) => val.id !== item.id
|
||||||
|
)
|
||||||
|
store.state.selectedCityCode = -1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={'w-[70%] h-[2px] bg-white/80 rounded-sm'}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={'flex-1 w-0'}>
|
||||||
|
<div class={'w-full h-full px-7 py-4 flex flex-col gap-y-3'}>
|
||||||
|
<div class="flex">
|
||||||
|
<div class={'flex flex-col text-[#333]'}>
|
||||||
|
<span class="text-[#666]">{store.weatherData?.city.name}</span>
|
||||||
|
<span class={'text-[57px] font-bold'}>{nowWeather.value?.base.stemp}°</span>
|
||||||
|
<span class="text-[14px] flex gap-x-1">
|
||||||
|
{nowWeather.value?.base.weather}
|
||||||
|
<span>
|
||||||
|
{nowWeather.value?.base.ltemp}°/{nowWeather.value?.base.htemp}°
|
||||||
|
</span>
|
||||||
|
<WeatherIcon weather={nowWeather.value?.base.weather} size={20}></WeatherIcon>
|
||||||
|
</span>
|
||||||
|
<span class={'text-[14px] text-[#666]'}>
|
||||||
|
{nowWeather.value?.base.WD} {nowWeather.value?.base.WS}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class={'flex-1 flex items-center justify-center'}>
|
||||||
|
<div class={'grid grid-cols-3 grid-rows-2 text-[#666] text-[12px] gap-x-3 gap-y-2'}>
|
||||||
|
<div class="flex gap-x-1">
|
||||||
|
<span>晨练指数:</span>
|
||||||
|
<span>{getPracticeIndex(nowWeather.value?.base.aqi || '0')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-x-1">
|
||||||
|
<span>空气质量:</span>
|
||||||
|
<span>{pollution(nowWeather.value?.base.pm25 || '0')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-x-1">
|
||||||
|
<span>穿衣指数:</span>
|
||||||
|
<span>{shirtIndex(nowWeather.value?.base.stemp || '0')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-x-1">
|
||||||
|
<span>雨伞指数:</span>
|
||||||
|
<span>
|
||||||
|
{nowWeather.value?.hour24.some((val) => val.weather.includes('雨'))
|
||||||
|
? '需带伞'
|
||||||
|
: '无需带伞'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-x-1">
|
||||||
|
<span>感冒指数:</span>
|
||||||
|
<span>{coldIndex(nowWeather.value?.base.stemp || '0')}</span>
|
||||||
|
</div>{' '}
|
||||||
|
<div class="flex gap-x-1">
|
||||||
|
<span>紫外线指数:</span>
|
||||||
|
<span>紫外线{nowWeather.value?.base.ultraviolet}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={'w-full bg-white/60 rounded-xl py-2 px-3 '}>
|
||||||
|
<span class={'text-[#999] text-[14px]'}>24小时预报</span>
|
||||||
|
<div
|
||||||
|
ref={hourRef}
|
||||||
|
class="flex w-full flex-grow-0 overflow-x-scroll gap-x-2 mt-1 scrollbar-hide"
|
||||||
|
onWheel={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (!hourRef.value) return
|
||||||
|
|
||||||
|
hourRef.value.scrollLeft += e.deltaY
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{nowWeather.value?.hour24.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
class={clsx(
|
||||||
|
'flex flex-col shrink-0 items-center w-[60px] text-[#333] rounded-lg py-4 justify-center gap-y-3',
|
||||||
|
idx === 0 ? 'bg-black/[.05]' : 'hover:bg-black/[0.05]'
|
||||||
|
)}
|
||||||
|
key={item.hour}
|
||||||
|
>
|
||||||
|
<WeatherIcon weather={item.weather} size={20}></WeatherIcon>
|
||||||
|
<span>{item.stemp}°</span>
|
||||||
|
<span>{idx === 0 ? '现在' : item.hour + '时'}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={'flex-1 h-0 w-full'}>
|
||||||
|
<div class={' h-full w-full bg-slate-50/[.7] rounded-xl py-2 px-3 flex flex-col'}>
|
||||||
|
<span class={'text-[#999] text-[14px]'}>7日天气预报</span>
|
||||||
|
<div class="w-full grid grid-cols-7 gap-x-2 grid-rows-1 flex-1 items-end pb-[6px] mt-1">
|
||||||
|
{nowWeather.value?.weathers7.map((item, idx) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(
|
||||||
|
'flex flex-col items-center justify-between gap-y-[2px] text-[#333] text-[14px] h-full py-[14px] px-[3px] rounded-[15px] ',
|
||||||
|
idx === 0 ? 'bg-black/[0.05]' : 'hover:bg-black/[0.05]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span class=" font-bold font-din ">
|
||||||
|
{item.ltemp + '°/' + item.htemp + '°'}
|
||||||
|
</span>
|
||||||
|
<div class={'flex flex-col gap-y-1 justify-center items-center'}>
|
||||||
|
<WeatherIcon size={20} weather={item.weather}></WeatherIcon>
|
||||||
|
<span class={'text-[14px]'}>{item.weather}</span>
|
||||||
|
</div>
|
||||||
|
<div class={'flex flex-col items-center justify-center text-[12px] '}>
|
||||||
|
<span class={'text-[#999]'}>
|
||||||
|
{dayjs(item.date, 'MM月DD日').format('MM/DD')}
|
||||||
|
</span>
|
||||||
|
<span class={'text-[14px]'}>
|
||||||
|
{idx === 0
|
||||||
|
? '今天'
|
||||||
|
: idx === 1
|
||||||
|
? '明天'
|
||||||
|
: dayjs(item.date, 'MM月DD日').format('ddd')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import useWeatherStore from './useWeatherStore'
|
import useWeatherStore from './useWeatherStore'
|
||||||
|
import WeatherIcon from './weatherIcon'
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const weather = useWeatherStore()
|
const weather = useWeatherStore()
|
||||||
|
@ -15,6 +16,7 @@ export default defineComponent(() => {
|
||||||
<div class="flex flex-col gap-y-[1px]">
|
<div class="flex flex-col gap-y-[1px]">
|
||||||
<div class="flex items-center gap-x-1">
|
<div class="flex items-center gap-x-1">
|
||||||
<span class="font-bold text-[14px] leading-[14px]">{weather.weatherData?.city.name}</span>
|
<span class="font-bold text-[14px] leading-[14px]">{weather.weatherData?.city.name}</span>
|
||||||
|
<WeatherIcon size={16} weather={weather.weatherData?.base.weather}></WeatherIcon>
|
||||||
</div>
|
</div>
|
||||||
<span class={'tracking-tight flex gap-x-1 items-center'}>
|
<span class={'tracking-tight flex gap-x-1 items-center'}>
|
||||||
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
||||||
|
|
|
@ -19,6 +19,7 @@ type WeatherState = {
|
||||||
selected: number
|
selected: number
|
||||||
selectedCityCode: number
|
selectedCityCode: number
|
||||||
cityOptions: AntdCityOptions[]
|
cityOptions: AntdCityOptions[]
|
||||||
|
weatherList: { id: number; data?: WeatherData; name: '' }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseWeatherUrl = 'https://i.tianqi.com/'
|
const baseWeatherUrl = 'https://i.tianqi.com/'
|
||||||
|
@ -27,7 +28,8 @@ export default defineStore('weather', () => {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
selected: 0,
|
selected: 0,
|
||||||
selectedCityCode: -1,
|
selectedCityCode: -1,
|
||||||
cityOptions: []
|
cityOptions: [],
|
||||||
|
weatherList: []
|
||||||
} as WeatherState)
|
} as WeatherState)
|
||||||
|
|
||||||
const city = ref('')
|
const city = ref('')
|
||||||
|
@ -53,18 +55,40 @@ export default defineStore('weather', () => {
|
||||||
}
|
}
|
||||||
const fetchWeather = async (code: number) => {
|
const fetchWeather = async (code: number) => {
|
||||||
try {
|
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()
|
const res = await nowResponse.json()
|
||||||
|
return res
|
||||||
weatherData.value = res
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return
|
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(
|
watch(
|
||||||
() => state.selectedCityCode,
|
() => state.selectedCityCode,
|
||||||
(e) => {
|
async (e) => {
|
||||||
fetchWeather(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
|
immediate: true
|
||||||
|
@ -75,7 +99,9 @@ export default defineStore('weather', () => {
|
||||||
state,
|
state,
|
||||||
city,
|
city,
|
||||||
weatherData,
|
weatherData,
|
||||||
queryCity
|
queryCity,
|
||||||
|
queryAll,
|
||||||
|
query
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
persist: true
|
persist: true
|
||||||
|
|
|
@ -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 () => (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: `${props.size}px`,
|
||||||
|
height: `${props.size}px`
|
||||||
|
}}
|
||||||
|
class={' object-contain'}
|
||||||
|
src={url.value}
|
||||||
|
alt="weather icon"
|
||||||
|
></img>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue