console
import { createApp, reactive, nextTick } from 'https://ax.minicg.com/pvue.es.js';
const { log, dir, table, clear, warn, error } = console; clear();
const State = reactive({
events: new EventTarget(),
keywords: '',
filteredData: null,
})
createApp({
State,
Root,
Header,
Search,
News,
}).mount()
function Root(props={}) {
const { appName, api } = props
return {
$template: `
<div class="flex flex-col w-[375px] h-[667px] bg-white rounded-3xl overflow-hidden shadow-xl" @vue:mounted="mounted">
<div v-scope="Header({ title:appName })"></div>
<div v-scope="Search()"></div>
<div v-scope="News({api:'${api}'})" v-if="showNews" class="flex-1 overflow-y-auto pb-12" ref="news"></div>
<img v-else class="m-auto pointer-events-none" src="https://ax.minicg.com/no-data.svg" />
<button class="w-full p-4 text-sm text-blue-500 bg-white/90 backdrop-blur shadow !outline-none hover:underline active:bg-gray-50" @click="toggleNews">显示/隐藏新闻</button>
</div>
`,
appName,
showNews: true,
toggleNews() {
this.showNews = !this.showNews
},
mounted() {
log('Root mounted', this)
}
}
}
function Header(props={}) {
const { title } = props
return {
$template: `
<div class="flex justify-between items-start px-4 pt-6 pb-2 font-medium text-2xl text-black" @vue:mounted="mounted">
<div class="flex flex-col justify-center gap-1">
<h2>{{title}}</h2>
<p class="text-xs text-black/40">Latest updated: {{new Date().toISOString().split('T')[0]}}</p>
</div>
<i class="ri-refresh-line text-xl px-1 cursor-pointer duration-200 hover:rotate-180 active:text-blue-500" @click="refresh"></i>
</div>
`,
title,
refresh() {
State.events.dispatchEvent( new Event('REFRESH_NEWS') )
},
mounted() {
log('Header mounted', this)
}
}
}
function Search(props={}) {
return {
$template: `
<div class="px-4 py-2" @vue:mounted="mounted">
<div class="flex gap-2 px-4 items-center bg-gray-100 rounded-full">
<i class="ri-search-line"></i>
<input class="w-full px-0 py-3 text-gray-600 border-0 !ring-0 bg-transparent text-sm"
type="text" placeholder="Search..."
v-model="State.keywords"
/>
<i v-show="State.keywords!==''" class="ri-close-circle-fill text-black/20 duration-200 cursor-pointer hover:text-black" @click="State.keywords=''"></i>
</div>
</div>
`,
mounted() {
log('Search mounted', this)
}
}
}
function News(props={}) {
const { api } = props
return {
$template: `
<div class="flex h-full" @vue:mounted="mounted" @vue:unmounted="unmounted">
<ul v-show="!isLoading" class="flex flex-col text-gray-700 cursor-pointer" v-effect="onFilter(State.keywords)">
<li v-for="item in filteredData" class="flex p-4 gap-3 duration-200 hover:bg-gray-50 active:bg-blue-50" @click="window.open(item.url)">
<div class="w-[100px] h-[100px] rounded-lg overflow-hidden">
<img class="w-full h-full object-cover" :src="item.pic">
</div>
<div class="flex-1 flex flex-col gap-2 justify-between py-1">
<div class="flex flex-col gap-2">
<h3 class="line-clamp-1">{{item.title}}</h3>
<p class="text-xs text-gray-500 line-clamp-2">{{item.desc}}</p>
</div>
<p class="text-xs text-gray-400">热度: {{ (Number(item.hot)/10000).toFixed(1) }}万</p>
</div>
</li>
</ul>
<i v-show="isLoading" class="ri-loader-line text-blue-500 text-3xl p-5 m-auto animate-spin"></i>
</div>
`,
api,
data: [],
filteredData: [],
isLoading: false,
mounted() {
log('News mounted', this)
this.loadData()
State.events.addEventListener('REFRESH_NEWS', this.onRefresh)
},
onFilter(keywords) {
this.filteredData = this.data.filter(item => ['title', 'desc'].some(key => item[key].includes(keywords)))
},
onRefresh(e) {
log('onRefresh')
this.loadData()
},
async loadData(e) {
this.isLoading = true
this.data = []
const resp = await fetch(this.api)
const data = await resp.json()
this.data = data.data.map(item=>{
return {
pic: item.pic,
title: item.title,
desc: item.desc,
hot: item.hot
}
})
this.filteredData = this.data
this.isLoading = false
},
unmounted() {
log('News unmounted')
State.events.removeEventListener("REFRESH_NEWS", this.onRefresh);
}
}
}
<div v-scope="Root({
appName: '百度新闻',
api: 'https://hot.cigh.cn/baidu'
})"></div>
html, body {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}