完成天气弹框
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 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(() => {
|
|||
<div
|
||||
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 overflow-hidden transition-all"
|
||||
style={{
|
||||
width: full.value ? '100%' : '900px',
|
||||
height: full.value ? '100vh' : '540px',
|
||||
width: full.value ? '100%' : '984px',
|
||||
height: full.value ? '100vh' : '580px',
|
||||
borderRadius: full.value ? '0' : '1rem'
|
||||
}}
|
||||
>
|
||||
{/* 关闭按钮 */}
|
||||
<div
|
||||
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 ' +
|
||||
(router.path === 'global-adder' ? 'top-6 right-4' : 'top-2 right-2')
|
||||
'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-8' : 'top-2 right-6')
|
||||
}
|
||||
onClick={() => {
|
||||
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>
|
||||
{/* 全屏按钮 */}
|
||||
{noFullList.includes(router.path) ? null : (
|
||||
{!fullList.includes(router.path) ? null : (
|
||||
<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={() => {
|
||||
full.value = !full.value
|
||||
}}
|
||||
>
|
||||
<OhVueIcon
|
||||
name={full.value ? 'md-closefullscreen' : 'md-openinfull'}
|
||||
scale={0.6}
|
||||
fill="white"
|
||||
/>
|
||||
<div class={' items-center justify-center hidden group-hover:flex'}>
|
||||
<OhVueIcon
|
||||
name={full.value ? 'md-closefullscreen' : 'md-openinfull'}
|
||||
scale={0.6}
|
||||
fill="white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -83,23 +87,22 @@ export default defineComponent(() => {
|
|||
) : router.path === 'global-adder' ? (
|
||||
<AdderPage />
|
||||
) : router.path === 'global-background' ? (
|
||||
<BackgroundSwtich/>
|
||||
) :
|
||||
router.path.startsWith('widget-') ? (
|
||||
(() => {
|
||||
const name = router.path.split('-')[1]
|
||||
const selected = widgetList.find((el) => el.name === name)
|
||||
if (!selected)
|
||||
return (
|
||||
<div class="w-full h-full flex justify-center items-center text-black/80">
|
||||
组件维护中
|
||||
</div>
|
||||
)
|
||||
const compo = selected.modal
|
||||
console.log(compo)
|
||||
return <compo />
|
||||
})()
|
||||
) : null}
|
||||
<BackgroundSwtich />
|
||||
) : router.path.startsWith('widget-') ? (
|
||||
(() => {
|
||||
const name = router.path.split('-')[1]
|
||||
const selected = widgetList.find((el) => el.name === name)
|
||||
if (!selected)
|
||||
return (
|
||||
<div class="w-full h-full flex justify-center items-center text-black/80">
|
||||
组件维护中
|
||||
</div>
|
||||
)
|
||||
const compo = selected.modal
|
||||
console.log(compo)
|
||||
return <compo />
|
||||
})()
|
||||
) : null}
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
|
|
17
src/main.css
17
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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@ export default defineStore(
|
|||
showTime: true,
|
||||
showAdder: true,
|
||||
// 尺寸
|
||||
blockSize: 6,
|
||||
blockSize: 6.7,
|
||||
blockPadding: 1,
|
||||
mainWidth: 70,
|
||||
blockRadius: 0.2,
|
||||
|
|
|
@ -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 () => <div class="w-full h-full bg-red-50"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg,#5996ff 0%,#4862ff 100%)'
|
||||
}}>{}</div>
|
||||
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={{
|
||||
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(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 useWeatherStore from './useWeatherStore'
|
||||
import WeatherIcon from './weatherIcon'
|
||||
import dayjs from 'dayjs'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default defineComponent(() => {
|
||||
const weather = useWeatherStore()
|
||||
|
||||
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'}>
|
||||
<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">
|
||||
<span class="font-bold text-[14px] leading-[14px]">
|
||||
<span class="font-bold text-[14px] leading-[14px] ">
|
||||
{weather.weatherData?.city.name}
|
||||
</span>
|
||||
</div>
|
||||
<span class={'tracking-tight flex gap-x-1 items-center'}>
|
||||
<span class="text-[12px]">
|
||||
{weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°
|
||||
</span>
|
||||
<span class="text-[12px] bg-white/[.2] rounded-xl px-2 py-[2px] font-din font-bold">
|
||||
{weather.weatherData?.base.ltemp}°/{weather.weatherData?.base.htemp}°
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
||||
<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
|
||||
.filter((_, idx) => idx < 3)
|
||||
.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={{
|
||||
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-[73px] absolute left-0 top-0 '}
|
||||
class={'w-full h-[100px] absolute left-0 top-0 '}
|
||||
style={{
|
||||
backgroundImage: `url('/weather_img/img/weather_bg_mdeium.webp')`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: '100%'
|
||||
backgroundSize: 'contain'
|
||||
}}
|
||||
></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(() => {
|
||||
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 useWeatherStore from './useWeatherStore'
|
||||
import WeatherIcon from './weatherIcon'
|
||||
|
||||
export default defineComponent(() => {
|
||||
const weather = useWeatherStore()
|
||||
|
@ -15,6 +16,7 @@ export default defineComponent(() => {
|
|||
<div class="flex flex-col gap-y-[1px]">
|
||||
<div class="flex items-center gap-x-1">
|
||||
<span class="font-bold text-[14px] leading-[14px]">{weather.weatherData?.city.name}</span>
|
||||
<WeatherIcon size={16} weather={weather.weatherData?.base.weather}></WeatherIcon>
|
||||
</div>
|
||||
<span class={'tracking-tight flex gap-x-1 items-center'}>
|
||||
<span class="text-[12px]">{weather.weatherData?.base.weather}</span>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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