mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
feat: 新增友链相关组件与优化投票页面
1. 新增友链底部悬浮操作组件uh-links-actions 2. 新增站点友链信息弹窗组件uh-links-site-info 3. 新增站点友链申请表单组件uh-links-site-apply 4. 新增小程序友链信息弹窗组件uh-links-mini-info 5. 优化投票页面:替换提示文案、添加插件检测回调、新增防抖sleep工具、调整样式与交互细节
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 友链底部悬浮操作栏(通用组件)
|
||||
* 参考瞬间详情页底部悬浮按钮设计;通过 :actions 控制显示的按钮,事件回调 emit('apply') / emit('info')
|
||||
* 站点 tab / 小程序 tab 分别引入(如 :actions="['apply','info']" @apply=... @info=...)
|
||||
*/
|
||||
withDefaults(defineProps<{
|
||||
/** 需要显示的按钮:apply=提交申请,info=友链信息;包含即显示 */
|
||||
actions?: string[]
|
||||
}>(), {
|
||||
actions: () => ['apply', 'info'],
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'apply'): void
|
||||
(e: 'info'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="fixed bottom-8 left-1/2 z-10 flex items-center justify-center pb-safe -translate-x-1/2">
|
||||
<view class="uh-global-card-glass box-border flex items-center justify-center gap-2 border rounded-full p-1 text-primary">
|
||||
<view
|
||||
v-if="actions.includes('apply')"
|
||||
class="uh-global-card-glass box-border h-9 flex flex-1 items-center justify-center gap-x-1 border rounded-full px-8 shadow-none"
|
||||
@click="emit('apply')"
|
||||
>
|
||||
<wd-icon name="edit" size="36rpx" />
|
||||
<text class="shrink-0 text-sm text-gray-900 font-semibold">提交申请</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="actions.includes('info')"
|
||||
class="uh-global-card-glass box-border h-9 flex flex-1 items-center justify-center gap-x-1 border rounded-full px-8 shadow-none"
|
||||
@click="emit('info')"
|
||||
>
|
||||
<wd-icon name="info" size="36rpx" />
|
||||
<text class="shrink-0 text-sm text-gray-900 font-semibold">友链信息</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -0,0 +1,208 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 小程序友链信息弹窗
|
||||
* 展示本站小程序申请提交的信息,字段结构与小程序提交申请弹窗(uh-links-mini-apply)一致:
|
||||
* 小程序名称/太阳码/跳转地址/作者昵称/作者头像/作者网站/描述/申请说明/邮箱
|
||||
* 数据源:linksSubmitPlugin 配置(blogName→名称、blogLogo→太阳码、blogUrl→跳转地址、blogDesc→描述)
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
show?: boolean
|
||||
}>(), {
|
||||
show: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'on-close'): void
|
||||
}>()
|
||||
|
||||
const isShow = ref(false)
|
||||
const appConfigStore = useAppConfigStore()
|
||||
|
||||
/** 小程序申请信息(字段与 uh-links-mini-apply 表单一致,从 linksSubmitPlugin 配置读取) */
|
||||
const miniInfo = computed(() => {
|
||||
const cfg = (appConfigStore.configs.pluginConfig?.linksSubmitPlugin || {}) as Record<string, unknown>
|
||||
const str = (key: string, fallback = '') => String(cfg[key] || fallback || '')
|
||||
return {
|
||||
displayName: str('blogName'),
|
||||
miniProgramCode: str('blogLogo'),
|
||||
link: str('blogUrl'),
|
||||
authorName: str('authorName'),
|
||||
avatar: str('avatar'),
|
||||
website: str('website'),
|
||||
description: str('blogDesc'),
|
||||
applyRemark: str('applyRemark'),
|
||||
email: str('email'),
|
||||
}
|
||||
})
|
||||
|
||||
const hasInfo = computed(() => !!(miniInfo.value.displayName || miniInfo.value.miniProgramCode))
|
||||
|
||||
/** 复制跳转地址 */
|
||||
function handleCopyLink() {
|
||||
if (!miniInfo.value.link) {
|
||||
uni.showToast({ icon: 'none', title: '暂未填写跳转地址' })
|
||||
return
|
||||
}
|
||||
uni.setClipboardData({
|
||||
data: miniInfo.value.link,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
uni.showToast({ icon: 'none', title: '地址复制成功!' })
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ icon: 'none', title: '复制失败!' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** 复制小程序申请信息(拼接文案) */
|
||||
function handleCopyInfo() {
|
||||
const info = miniInfo.value
|
||||
const text = [
|
||||
info.displayName ? `小程序名称:${info.displayName}` : '',
|
||||
info.link ? `小程序地址:${info.link}` : '',
|
||||
info.authorName ? `作者昵称:${info.authorName}` : '',
|
||||
info.website ? `作者网站:${info.website}` : '',
|
||||
info.description ? `小程序描述:${info.description}` : '',
|
||||
info.applyRemark ? `申请说明:${info.applyRemark}` : '',
|
||||
info.email ? `通知邮箱:${info.email}` : '',
|
||||
].filter(Boolean).join('\n')
|
||||
uni.setClipboardData({
|
||||
data: text,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
uni.showToast({ icon: 'none', title: '复制成功!' })
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ icon: 'none', title: '复制失败!' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** 预览太阳码 */
|
||||
function handlePreviewCode() {
|
||||
if (!miniInfo.value.miniProgramCode)
|
||||
return
|
||||
uni.previewImage({
|
||||
urls: [checkImageUrl(miniInfo.value.miniProgramCode)],
|
||||
current: checkImageUrl(miniInfo.value.miniProgramCode),
|
||||
})
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
isShow.value = false
|
||||
emit('on-close')
|
||||
}
|
||||
|
||||
watch(() => props.show, (val) => {
|
||||
isShow.value = val
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<uh-glass-popup
|
||||
v-model="isShow" :z-index="100" position="bottom"
|
||||
custom-class="!border rounded-xl" @close="handleClose"
|
||||
>
|
||||
<view class="relative box-border w-full flex items-center justify-around px-4 pt-4">
|
||||
<view class="w-full flex flex-col gap-y-1">
|
||||
<text class="text-md font-bold">小程序友链信息</text>
|
||||
<text class="text-xs text-gray-500">本站小程序申请提交的信息,欢迎互换</text>
|
||||
</view>
|
||||
<view
|
||||
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||||
@click="handleClose"
|
||||
>
|
||||
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view :scroll-y="true" :show-scrollbar="false" class="box-border max-h-[60vh] p-4">
|
||||
<!-- 未配置信息占位 -->
|
||||
<view v-if="!hasInfo" class="py-10 text-center text-xs text-gray-400">
|
||||
暂未配置小程序申请信息
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<!-- 太阳码大图(点击预览) -->
|
||||
<view v-if="miniInfo.miniProgramCode" class="code-area flex flex-col items-center">
|
||||
<image
|
||||
class="code-img h-32 w-32 rounded-xl"
|
||||
:src="checkImageUrl(miniInfo.miniProgramCode)" mode="aspectFill"
|
||||
@click="handlePreviewCode"
|
||||
/>
|
||||
<view class="code-tip mt-3 flex items-center text-xs text-gray-400">
|
||||
<wd-icon name="picture" size="14px" color="#a8a294" />
|
||||
<text class="ml-1">点击预览太阳码</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 名称 -->
|
||||
<view v-if="miniInfo.displayName" class="mini-head mt-5 flex items-center">
|
||||
<text class="mini-name text-[34rpx] text-gray-900 font-bold">{{ miniInfo.displayName }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 描述 -->
|
||||
<view v-if="miniInfo.description" class="mini-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]">
|
||||
{{ miniInfo.description }}
|
||||
</view>
|
||||
|
||||
<!-- 作者信息 -->
|
||||
<view
|
||||
v-if="miniInfo.authorName || miniInfo.avatar || miniInfo.website"
|
||||
class="mini-author-info mt-5 flex items-center rounded-xl bg-[#f6f3ee] p-4"
|
||||
>
|
||||
<image
|
||||
v-if="miniInfo.avatar" class="author-avatar h-[72rpx] w-[72rpx] shrink-0 rounded-full"
|
||||
:src="checkAvatarUrl(miniInfo.avatar)" mode="aspectFill"
|
||||
/>
|
||||
<view class="author-detail ml-4 flex flex-1 flex-col">
|
||||
<text
|
||||
v-if="miniInfo.authorName"
|
||||
class="author-name text-[28rpx] text-gray-900 font-medium"
|
||||
>
|
||||
{{ miniInfo.authorName }}
|
||||
</text>
|
||||
<text
|
||||
v-if="miniInfo.website"
|
||||
class="author-website mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
|
||||
>
|
||||
网站:{{ miniInfo.website }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 跳转地址 -->
|
||||
<view v-if="miniInfo.link" class="mini-link mt-5 flex items-center justify-between rounded-xl bg-secondary p-4">
|
||||
<view class="link-text flex-1 overflow-hidden truncate whitespace-nowrap text-[26rpx] text-[#4d7c0f]">
|
||||
{{ miniInfo.link }}
|
||||
</view>
|
||||
<text class="ml-3 shrink-0 text-[26rpx] text-[#4d7c0f] font-bold" @click="handleCopyLink">复制</text>
|
||||
</view>
|
||||
|
||||
<!-- 申请说明 / 邮箱 -->
|
||||
<view
|
||||
v-if="miniInfo.applyRemark || miniInfo.email"
|
||||
class="mini-extra mt-5 flex flex-col gap-2 rounded-xl bg-[#f6f3ee] p-4 text-xs text-gray-500"
|
||||
>
|
||||
<view v-if="miniInfo.applyRemark">
|
||||
<text class="text-gray-400">申请说明:</text>{{ miniInfo.applyRemark }}
|
||||
</view>
|
||||
<view v-if="miniInfo.email">
|
||||
<text class="text-gray-400">通知邮箱:</text>{{ miniInfo.email }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="my-6">
|
||||
<uh-button custom-class="py-2 !rounded-xl" @click="handleCopyInfo">
|
||||
复制小程序申请信息
|
||||
</uh-button>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</uh-glass-popup>
|
||||
</template>
|
||||
@@ -0,0 +1,231 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 友链申请表单弹窗(站点版,源自旧页面 pages-blog/submit-link)
|
||||
* 参考小程序申请弹窗 uh-links-mini-apply 的封装方式(uh-glass-popup bottom + 表单 + 提交)
|
||||
* 提交后等待站长审核,通过后展示在「站点」列表中
|
||||
*/
|
||||
import { ref, watch } from 'vue'
|
||||
import { submitLink } from '@/api/uni-halo'
|
||||
import type { ISubmitLinkForm } from '@/api/types/uni-halo'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
show?: boolean
|
||||
}>(), {
|
||||
show: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'on-close', data: { isSubmit: boolean, refresh: boolean }): void
|
||||
}>()
|
||||
|
||||
const isShow = ref(false)
|
||||
|
||||
interface IApplyForm {
|
||||
name: string
|
||||
url: string
|
||||
logo: string
|
||||
linkPageUrl: string
|
||||
email: string
|
||||
rssUrl: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const form = ref<IApplyForm>({
|
||||
name: '',
|
||||
url: '',
|
||||
logo: '',
|
||||
linkPageUrl: '',
|
||||
email: '',
|
||||
rssUrl: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
function handleResetForm() {
|
||||
form.value = {
|
||||
name: '',
|
||||
url: '',
|
||||
logo: '',
|
||||
linkPageUrl: '',
|
||||
email: '',
|
||||
rssUrl: '',
|
||||
description: '',
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsUrl(url: string): boolean {
|
||||
return /^https?:\/\//i.test(url)
|
||||
}
|
||||
|
||||
function checkIsEmail(email: string): boolean {
|
||||
return /^[\w.-]+@[\w-]+(?:\.[\w-]+)+$/.test(email)
|
||||
}
|
||||
|
||||
/** 提交校验 */
|
||||
function validateForm(): boolean {
|
||||
if (!form.value.name.trim()) {
|
||||
uni.showToast({ icon: 'none', title: '请输入网站名称!' })
|
||||
return false
|
||||
}
|
||||
if (!checkIsUrl(form.value.url)) {
|
||||
uni.showToast({ icon: 'none', title: '请输入正确的网站地址!' })
|
||||
return false
|
||||
}
|
||||
if (form.value.logo.trim() && !checkIsUrl(form.value.logo.trim())) {
|
||||
uni.showToast({ icon: 'none', title: '请输入正确的Logo地址!' })
|
||||
return false
|
||||
}
|
||||
if (form.value.email.trim() && !checkIsEmail(form.value.email.trim())) {
|
||||
uni.showToast({ icon: 'none', title: '请输入正确的邮箱地址!' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 提交申请 */
|
||||
async function handleSubmit() {
|
||||
if (!validateForm())
|
||||
return
|
||||
|
||||
submitting.value = true
|
||||
uni.showLoading({ title: '正在提交...' })
|
||||
try {
|
||||
const payload: ISubmitLinkForm = {
|
||||
name: form.value.name.trim(),
|
||||
url: form.value.url.trim(),
|
||||
logo: form.value.logo.trim() || undefined,
|
||||
linkPageUrl: form.value.linkPageUrl.trim() || undefined,
|
||||
email: form.value.email.trim() || undefined,
|
||||
rssUrl: form.value.rssUrl.trim() || undefined,
|
||||
description: form.value.description.trim() || undefined,
|
||||
}
|
||||
const res = await submitLink(payload)
|
||||
const msg = res.data?.msg || res.data?.message || res.message || '提交成功'
|
||||
uni.showToast({ icon: 'none', title: msg })
|
||||
if (res.code === 200 || res.code === undefined) {
|
||||
handleClose(true)
|
||||
handleResetForm()
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('友链申请提交失败', err)
|
||||
uni.showToast({ icon: 'none', title: '提交失败,请稍后重试!' })
|
||||
}
|
||||
finally {
|
||||
submitting.value = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
function handleOnChange(isOpen: boolean) {
|
||||
isShow.value = isOpen
|
||||
if (!isOpen)
|
||||
emit('on-close', { isSubmit: false, refresh: false })
|
||||
}
|
||||
|
||||
function handleClose(refresh = false) {
|
||||
isShow.value = false
|
||||
emit('on-close', { isSubmit: true, refresh })
|
||||
}
|
||||
|
||||
watch(() => props.show, (val) => {
|
||||
if (!val)
|
||||
return
|
||||
isShow.value = true
|
||||
handleResetForm()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<uh-glass-popup
|
||||
v-model="isShow" :z-index="100" position="bottom"
|
||||
custom-class="!border rounded-xl" @close="handleClose(false)"
|
||||
>
|
||||
<view class="relative mb-4 box-border w-full flex items-center justify-around px-4 pt-4">
|
||||
<view class="w-full flex flex-col gap-y-1">
|
||||
<text class="text-md font-bold">申请友链</text>
|
||||
<text class="text-xs text-gray-500">提交后等待站长审核,通过后展示在「站点」列表中</text>
|
||||
</view>
|
||||
<view
|
||||
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||||
@click="handleClose(false)"
|
||||
>
|
||||
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view :scroll-y="true" :show-scrollbar="false" class="box-border max-h-[60vh] p-4 pt-0">
|
||||
<view class="mb-5 flex items-center">
|
||||
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">名称 *</text>
|
||||
<input
|
||||
v-model="form.name"
|
||||
class="uh-global-card-glass h-9 flex-1 border rounded-xl px-4 text-sm shadow-none"
|
||||
placeholder="请输入网站名称"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="mb-5 flex items-center">
|
||||
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">网址 *</text>
|
||||
<input
|
||||
v-model="form.url"
|
||||
class="uh-global-card-glass h-9 flex-1 border rounded-xl px-4 text-sm shadow-none"
|
||||
placeholder="请输入网站地址"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="mb-5 flex items-center">
|
||||
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">Logo</text>
|
||||
<input
|
||||
v-model="form.logo"
|
||||
class="uh-global-card-glass h-9 flex-1 border rounded-xl px-4 text-sm shadow-none"
|
||||
placeholder="请输入网站Logo(选填)"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="mb-5 flex items-center">
|
||||
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">邮箱</text>
|
||||
<input
|
||||
v-model="form.email"
|
||||
class="uh-global-card-glass h-9 flex-1 border rounded-xl px-4 text-sm shadow-none"
|
||||
placeholder="请输入邮箱(选填)"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="mb-5 flex items-center">
|
||||
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">友链页面</text>
|
||||
<input
|
||||
v-model="form.linkPageUrl"
|
||||
class="uh-global-card-glass h-9 flex-1 border rounded-xl px-4 text-sm shadow-none"
|
||||
placeholder="贵站友情链接页面地址(选填)"
|
||||
>
|
||||
</view>
|
||||
<view class="mb-2 pl-[140rpx] text-xs text-gray-400 -mt-3">
|
||||
(即包含本站链接的页面)
|
||||
</view>
|
||||
|
||||
<view class="mb-5 flex items-center">
|
||||
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">RSS地址</text>
|
||||
<input
|
||||
v-model="form.rssUrl"
|
||||
class="uh-global-card-glass h-9 flex-1 border rounded-xl px-4 text-sm shadow-none"
|
||||
placeholder="用于抓取文章(选填)"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="mb-5">
|
||||
<text class="label mb-2 block text-sm text-[#666]">网站描述</text>
|
||||
<textarea
|
||||
v-model="form.description"
|
||||
class="uh-global-card-glass box-border h-24 w-full flex-1 border rounded-xl p-3 text-sm shadow-none"
|
||||
placeholder="请输入网站描述,不超过30字符(选填)" :maxlength="30"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="my-6">
|
||||
<uh-button custom-class="py-2 !rounded-xl" :loading="submitting" @click="handleSubmit">
|
||||
提交申请
|
||||
</uh-button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</uh-glass-popup>
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 站点友链信息弹窗(源自旧页面 pages-blog/submit-link 的博客详情弹窗,重设计为底部玻璃弹窗)
|
||||
* 展示本站友链交换信息(博客名片 + 复制交换信息 + 站点缩略图)
|
||||
* 数据源为 linksSubmitPlugin 配置(即本站申请提交的信息)
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { checkAvatarUrl } from '@/utils/url'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
show?: boolean
|
||||
}>(), {
|
||||
show: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'on-close'): void
|
||||
}>()
|
||||
|
||||
const isShow = ref(false)
|
||||
const appConfigStore = useAppConfigStore()
|
||||
|
||||
const blogDetail = computed(() => (appConfigStore.configs.pluginConfig?.linksSubmitPlugin as {
|
||||
blogName?: string
|
||||
blogUrl?: string
|
||||
blogLogo?: string
|
||||
blogDesc?: string
|
||||
} | undefined) || {})
|
||||
|
||||
/** 友链交换信息文案(复制用) */
|
||||
const calcBlogContent = computed(() => `
|
||||
博客名称:${blogDetail.value.blogName || ''}
|
||||
博客地址:${blogDetail.value.blogUrl || ''}
|
||||
博客logo:${checkAvatarUrl(blogDetail.value.blogLogo)}
|
||||
博客简介:${blogDetail.value.blogDesc || ''}
|
||||
`)
|
||||
|
||||
function calcSiteThumbnail(val?: string): string {
|
||||
if (!val)
|
||||
return ''
|
||||
const _val = val.endsWith('/') ? val : `${val}/`
|
||||
return `https://image.thum.io/get/width/1000/crop/800/${_val}`
|
||||
}
|
||||
|
||||
function handleCopyLink() {
|
||||
uni.setClipboardData({
|
||||
data: calcBlogContent.value,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
uni.showToast({ icon: 'none', title: '复制成功!' })
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ icon: 'none', title: '复制失败!' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
isShow.value = false
|
||||
emit('on-close')
|
||||
}
|
||||
|
||||
watch(() => props.show, (val) => {
|
||||
isShow.value = val
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<uh-glass-popup
|
||||
v-model="isShow" :z-index="100" position="bottom"
|
||||
custom-class="!border rounded-xl" @close="handleClose"
|
||||
>
|
||||
<view class="relative box-border w-full flex items-center justify-around px-4 pt-4">
|
||||
<view class="w-full flex flex-col gap-y-1">
|
||||
<text class="text-md font-bold">友链信息</text>
|
||||
<text class="text-xs text-gray-500">本站友链交换信息,欢迎申请互换友链</text>
|
||||
</view>
|
||||
<view
|
||||
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||||
@click="handleClose"
|
||||
>
|
||||
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view :scroll-y="true" :show-scrollbar="false" class="box-border max-h-[60vh] p-4">
|
||||
<!-- 博客名片 -->
|
||||
<view class="flex items-center">
|
||||
<image
|
||||
class="uh-global-card-glass h-20 w-20 shrink-0 rounded-2xl"
|
||||
:src="checkAvatarUrl(blogDetail.blogLogo)" mode="aspectFill"
|
||||
/>
|
||||
<view class="ml-4 flex flex-1 flex-col justify-center gap-y-1">
|
||||
<text class="text-md text-gray-900 font-bold">
|
||||
{{ blogDetail.blogName || '未命名博客' }}
|
||||
</text>
|
||||
<text class="text-xs text-gray-500">
|
||||
{{ blogDetail.blogDesc || '这个博主很懒,没写简介~' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 交换信息文案 -->
|
||||
<view class="mt-4 whitespace-pre-wrap text-xs text-gray-600 leading-5">
|
||||
<text>{{ calcBlogContent }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 站点缩略图 -->
|
||||
<image
|
||||
v-if="blogDetail.blogUrl" class="mt-4 h-[320rpx] w-full rounded-xl"
|
||||
:src="calcSiteThumbnail(blogDetail.blogUrl)" mode="aspectFill"
|
||||
/>
|
||||
|
||||
<view class="my-6">
|
||||
<uh-button custom-class="py-2 !rounded-xl" @click="handleCopyLink">
|
||||
复制友链交换信息
|
||||
</uh-button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</uh-glass-popup>
|
||||
</template>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { debounce } from '@/utils/debounce'
|
||||
import { sleep } from '@/utils/common'
|
||||
import { calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||
import type { IVoteItem } from '@/api/types/uni-halo'
|
||||
|
||||
@@ -20,16 +21,22 @@
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||
|
||||
/** 依赖插件(plugin-vote,参考 gallery 对象传参模式) */
|
||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||
pluginId: NeedPluginIds.PluginVote,
|
||||
tips: '检测到当前插件没有安装或者启用,无法使用投票功能哦,请联系管理员',
|
||||
tips: '啊偶,功能正在维护中...',
|
||||
callback: (isAvailable) => {
|
||||
if (!isAvailable) { return }
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 0,
|
||||
})
|
||||
handleGetData()
|
||||
}
|
||||
})
|
||||
|
||||
/** 重新检测插件:可用则拉取数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||
async function handlePluginRefresh() {
|
||||
if (await checkPluginAvailable())
|
||||
handleGetData()
|
||||
if (await checkPluginAvailable()) { handleGetData() }
|
||||
}
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
@@ -38,7 +45,6 @@
|
||||
const hasNext = ref(false)
|
||||
const isLoadMore = ref(false)
|
||||
const loadMoreText = ref('加载中...')
|
||||
/** 是否已投过滤(前端过滤,接口无此参数) */
|
||||
const filterIsVoted = ref<boolean | undefined>(undefined)
|
||||
const queryParams = ref<Record<string, unknown>>({
|
||||
keyword: '',
|
||||
@@ -124,8 +130,7 @@
|
||||
|
||||
function handleSelectFilter(option : IFilterOption) {
|
||||
const item = filterPopup.value.item
|
||||
if (!item)
|
||||
return
|
||||
if (!item) { return }
|
||||
filterValues.value[item.key] = option.value
|
||||
filterPopup.value.show = false
|
||||
|
||||
@@ -169,7 +174,6 @@
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ mask: true, title: '加载中...' })
|
||||
if (!isLoadMore.value) {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
}
|
||||
@@ -179,7 +183,6 @@
|
||||
const res = await getVoteList({ ...queryParams.value })
|
||||
hasNext.value = res.data.hasNext || false
|
||||
|
||||
// 加工列表数据(与旧项目一致):isVoted/_uh_state/_uh_type
|
||||
const tempItems = res.data.items.map((item) => {
|
||||
item.spec = item.spec || {}
|
||||
item.spec.disabled = true
|
||||
@@ -202,7 +205,7 @@
|
||||
if (filterIsVoted.value !== undefined) {
|
||||
dataList.value = dataList.value.filter(x => x.spec?.isVoted === filterIsVoted.value)
|
||||
}
|
||||
|
||||
await sleep(600)
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
@@ -214,10 +217,7 @@
|
||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
}, 500)
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,18 +281,16 @@
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="投票中心" title-color="text-gray-900" />
|
||||
|
||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||
:checking="checking" @on-refresh="handlePluginRefresh" />
|
||||
|
||||
<template v-else>
|
||||
<!-- 顶部搜索框 -->
|
||||
<view class="box-border w-screen px-3 pt-2">
|
||||
<view class="uh-global-card-glass flex h-9 items-center gap-3 rounded-full px-5">
|
||||
<wd-icon name="search" size="16px" />
|
||||
<input v-model="queryParams.keyword as string" class="flex-1 text-[26rpx] text-gray-900"
|
||||
<input v-model="queryParams.keyword" class="flex-1 text-[26rpx] text-gray-900"
|
||||
placeholder="搜索投票..." placeholder-class="text-gray-400" confirm-type="search"
|
||||
@input="handleOnInput" @confirm="handleOnSearch">
|
||||
<view v-if="queryParams.keyword" class="flex items-center"
|
||||
@@ -304,17 +302,18 @@
|
||||
<view class="box-border flex items-center justify-between mt-1 py-2 gap-x-2">
|
||||
<view v-for="f in filterConfig" :key="f.key"
|
||||
class="uh-global-card-glass border rounded-full box-border flex flex-1 items-center justify-center gap-1 px-2 py-1 text-gray-500"
|
||||
:class="[filterValues[f.key]?'bg-secondary text-gray-900 font-bold':'bg-white/80 text-gray-600']" @click="handleOpenFilter(f)">
|
||||
<text class="text-xs truncate" >
|
||||
:class="[filterValues[f.key]?'bg-secondary text-gray-900 font-bold':'bg-white/80 text-gray-600']"
|
||||
@click="handleOpenFilter(f)">
|
||||
<text class="text-xs truncate">
|
||||
{{ filterLabels[f.key] }}
|
||||
</text>
|
||||
<wd-icon name="arrow-down" size="10px" />
|
||||
<wd-icon name="arrow-down" size="24rpx" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus" empty-text="博主还未发布投票~" min-height="65vh" @refresh="handleGetData" />
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
empty-text="还没有任何投票哦~" min-height="70vh" @refresh="handleGetData" />
|
||||
|
||||
<view v-else class="box-border flex flex-col gap-4 p-3">
|
||||
<block v-if="dataList.length !== 0">
|
||||
@@ -335,7 +334,7 @@
|
||||
</view>
|
||||
<view class="flex flex-col gap-2">
|
||||
<view v-for="opt in filterPopup.item.options" :key="opt.label"
|
||||
class="uh-global-card-glass border box-border rounded-xl px-5 py-2 text-center text-sm"
|
||||
class="uh-global-card-glass shadow-none border box-border rounded-xl px-5 py-2 text-center text-sm"
|
||||
:class="filterValues[filterPopup.item.key] === opt.value ? 'bg-primary text-gray-900 font-bold' : 'text-gray-700'"
|
||||
@click="handleSelectFilter(opt)">
|
||||
{{ opt.label }}
|
||||
|
||||
Reference in New Issue
Block a user