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>
|
||||||
@@ -32,35 +32,40 @@
|
|||||||
pluginId: NeedPluginIds.PluginLinks,
|
pluginId: NeedPluginIds.PluginLinks,
|
||||||
tips: '啊偶,功能正在维护中...',
|
tips: '啊偶,功能正在维护中...',
|
||||||
callback: (isAvailable) => {
|
callback: (isAvailable) => {
|
||||||
if (!isAvailable) { return }
|
if (!isAvailable)
|
||||||
|
return
|
||||||
uni.pageScrollTo({
|
uni.pageScrollTo({
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
})
|
})
|
||||||
handleGetLinkGroupData()
|
handleGetLinkGroupData()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
/** 小程序 tab:plugin-uni-halo */
|
/** 小程序 tab:plugin-uni-halo */
|
||||||
const { pluginId: miniPluginId, checking: miniChecking, tips: miniTips, available: miniPluginAvailable, check: checkMiniPluginAvailable } = usePluginAvailable({
|
const { pluginId: miniPluginId, checking: miniChecking, tips: miniTips, available: miniPluginAvailable, check: checkMiniPluginAvailable } = usePluginAvailable({
|
||||||
pluginId: NeedPluginIds.PluginUniHalo,
|
pluginId: NeedPluginIds.PluginUniHalo,
|
||||||
tips: '啊偶,功能正在维护中...', callback: (isAvailable) => {
|
tips: '啊偶,功能正在维护中...',
|
||||||
if (!isAvailable) { return }
|
callback: (isAvailable) => {
|
||||||
|
if (!isAvailable)
|
||||||
|
return
|
||||||
uni.pageScrollTo({
|
uni.pageScrollTo({
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
})
|
})
|
||||||
handleGetMiniProgramLinks()
|
handleGetMiniProgramLinks()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 重新检测站点插件:可用则拉取友链数据(供 uh-plugin-unavailable 刷新按钮) */
|
/** 重新检测站点插件:可用则拉取友链数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||||
async function handleSitePluginRefresh() {
|
async function handleSitePluginRefresh() {
|
||||||
if (await checkSitePluginAvailable()) { handleGetLinkGroupData() }
|
if (await checkSitePluginAvailable())
|
||||||
|
handleGetLinkGroupData()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新检测小程序插件:可用则拉取小程序链接数据(供 uh-plugin-unavailable 刷新按钮) */
|
/** 重新检测小程序插件:可用则拉取小程序链接数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||||
async function handleMiniPluginRefresh() {
|
async function handleMiniPluginRefresh() {
|
||||||
if (await checkMiniPluginAvailable()) { handleGetMiniProgramLinks() }
|
if (await checkMiniPluginAvailable())
|
||||||
|
handleGetMiniProgramLinks()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- tabs ---------------- */
|
/* ---------------- tabs ---------------- */
|
||||||
@@ -78,7 +83,8 @@
|
|||||||
|
|
||||||
// 审核模式下小程序 tab 隐藏,强制停留在站点 tab
|
// 审核模式下小程序 tab 隐藏,强制停留在站点 tab
|
||||||
watch(() => appConfigStore.auditModeEnabled, (enabled) => {
|
watch(() => appConfigStore.auditModeEnabled, (enabled) => {
|
||||||
if (enabled) { activeTabIndex.value = 0 }
|
if (enabled)
|
||||||
|
activeTabIndex.value = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ==================== 站点 tab(plugin-links) ==================== */
|
/* ==================== 站点 tab(plugin-links) ==================== */
|
||||||
@@ -94,7 +100,8 @@
|
|||||||
|
|
||||||
/* ---------------- 数据加载 ---------------- */
|
/* ---------------- 数据加载 ---------------- */
|
||||||
function findLinkGroupDisplayNameByGroupMetadataName(groupName?: string): string {
|
function findLinkGroupDisplayNameByGroupMetadataName(groupName?: string): string {
|
||||||
if (linkGroupList.value.length === 0) { return groupName || '未分组' }
|
if (linkGroupList.value.length === 0)
|
||||||
|
return groupName || '未分组'
|
||||||
const found = linkGroupList.value.find(item => item.metadata.name === groupName)
|
const found = linkGroupList.value.find(item => item.metadata.name === groupName)
|
||||||
return found?.spec.displayName || groupName || '未分组'
|
return found?.spec.displayName || groupName || '未分组'
|
||||||
}
|
}
|
||||||
@@ -134,7 +141,8 @@
|
|||||||
groupName: findLinkGroupDisplayNameByGroupMetadataName(item.spec.groupName),
|
groupName: findLinkGroupDisplayNameByGroupMetadataName(item.spec.groupName),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
dataList.value = dataList.value.concat(list)
|
// 分页加载时累加,首次/刷新时覆盖(修复重复数据)
|
||||||
|
dataList.value = isLoadMore.value ? dataList.value.concat(list) : list
|
||||||
await sleep(600)
|
await sleep(600)
|
||||||
updateSiteLoadingStatus(
|
updateSiteLoadingStatus(
|
||||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||||
@@ -151,6 +159,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 站点友链按分组聚合(无分组链接归入「未分组」,参考小程序 tab 分组展示) */
|
||||||
|
const siteGroups = computed(() => {
|
||||||
|
const map = new Map<string, ILink[]>()
|
||||||
|
for (const link of dataList.value) {
|
||||||
|
const key = link.spec.groupName || '未分组'
|
||||||
|
if (!map.has(key))
|
||||||
|
map.set(key, [])
|
||||||
|
map.get(key)!.push(link)
|
||||||
|
}
|
||||||
|
return Array.from(map.entries()).map(([groupName, links]) => ({ groupName, links }))
|
||||||
|
})
|
||||||
|
|
||||||
/* ---------------- 站点交互 ---------------- */
|
/* ---------------- 站点交互 ---------------- */
|
||||||
function handleOnLinkEvent(link: ILink) {
|
function handleOnLinkEvent(link: ILink) {
|
||||||
detail.value = { show: true, data: link }
|
detail.value = { show: true, data: link }
|
||||||
@@ -169,8 +189,36 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function toSubmitLinkPage() {
|
/** 站点友链申请弹窗 */
|
||||||
uni.navigateTo({ url: '/pages-blog/submit-link/submit-link' })
|
const siteApplyShow = ref(false)
|
||||||
|
/** 站点友链信息弹窗 */
|
||||||
|
const infoShow = ref(false)
|
||||||
|
|
||||||
|
function handleOpenSiteApply() {
|
||||||
|
siteApplyShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSiteApplyClose(data: { isSubmit: boolean, refresh: boolean }) {
|
||||||
|
siteApplyShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpenInfo() {
|
||||||
|
infoShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInfoClose() {
|
||||||
|
infoShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 小程序友链信息弹窗(展示小程序申请提交的信息) */
|
||||||
|
const miniInfoShow = ref(false)
|
||||||
|
|
||||||
|
function handleOpenMiniInfo() {
|
||||||
|
miniInfoShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMiniInfoClose() {
|
||||||
|
miniInfoShow.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleToTopPage(duration = 500) {
|
function handleToTopPage(duration = 500) {
|
||||||
@@ -323,7 +371,8 @@
|
|||||||
|
|
||||||
onReachBottom(() => {
|
onReachBottom(() => {
|
||||||
if (activeTabIndex.value === 0) {
|
if (activeTabIndex.value === 0) {
|
||||||
if (!sitePluginAvailable.value) { return }
|
if (!sitePluginAvailable.value)
|
||||||
|
return
|
||||||
if (hasNext.value) {
|
if (hasNext.value) {
|
||||||
queryParams.value.page += 1
|
queryParams.value.page += 1
|
||||||
isLoadMore.value = true
|
isLoadMore.value = true
|
||||||
@@ -348,10 +397,12 @@
|
|||||||
<wd-sticky>
|
<wd-sticky>
|
||||||
<scroll-view scroll-x class="w-full whitespace-nowrap">
|
<scroll-view scroll-x class="w-full whitespace-nowrap">
|
||||||
<view class="flex gap-2 px-3 pb-1 pt-3">
|
<view class="flex gap-2 px-3 pb-1 pt-3">
|
||||||
<view v-for="(tab, index) in friendLinkTabs" :key="tab.key"
|
<view
|
||||||
|
v-for="(tab, index) in friendLinkTabs" :key="tab.key"
|
||||||
class="uh-global-card-glass uh-shadow-xs inline-block border rounded-2xl px-5 py-1.5 text-sm"
|
class="uh-global-card-glass uh-shadow-xs inline-block border rounded-2xl px-5 py-1.5 text-sm"
|
||||||
:class="activeTabIndex === index ? 'bg-primary font-bold' : 'text-gray-500'"
|
:class="activeTabIndex === index ? 'bg-primary font-bold' : 'text-gray-500'"
|
||||||
@click="handleOnTabChange({ index })">
|
@click="handleOnTabChange({ index })"
|
||||||
|
>
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -360,25 +411,36 @@
|
|||||||
|
|
||||||
<!-- ==================== 站点 tab ==================== -->
|
<!-- ==================== 站点 tab ==================== -->
|
||||||
<template v-if="activeTabIndex === 0">
|
<template v-if="activeTabIndex === 0">
|
||||||
<uh-plugin-unavailable v-if="!sitePluginAvailable" :plugin-id="sitePluginId" :error-text="siteTips"
|
<uh-plugin-unavailable
|
||||||
:checking="siteChecking" @on-refresh="handleSitePluginRefresh" />
|
v-if="!sitePluginAvailable" :plugin-id="sitePluginId" :error-text="siteTips"
|
||||||
|
:checking="siteChecking" @on-refresh="handleSitePluginRefresh"
|
||||||
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<view v-if="siteLoadingStatus !== 'success'">
|
<view v-if="siteLoadingStatus !== 'success'">
|
||||||
<uh-data-loading :loading-status="siteLoadingStatus" empty-text="啊偶,博主还没有朋友呢~"
|
<uh-data-loading
|
||||||
@refresh="handleGetData" />
|
:loading-status="siteLoadingStatus" empty-text="啊偶,博主还没有朋友呢~"
|
||||||
|
@refresh="handleGetData"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="content pt-4">
|
<view v-else class="content pt-4">
|
||||||
<!-- 友链列表 -->
|
<!-- 友链分组列表(参考小程序 tab 分组展示) -->
|
||||||
<view class="box-border flex flex-col gap-4 px-4 pb-4">
|
<view class="box-border px-4 pb-4">
|
||||||
<view v-for="link in dataList" :key="link.metadata?.name || link.spec.displayName">
|
<view v-for="group in siteGroups" :key="group.groupName" class="group-item mb-4">
|
||||||
<view class="uh-global-card-glass overflow-hidden box-border flex rounded-xl p-3"
|
<view class="mb-3 flex items-center">
|
||||||
@click="handleOnLinkEvent(link)">
|
<text class="mr-2 inline-block h-4 w-1 rounded-full bg-secondary" />
|
||||||
|
<text class="text-sm text-gray-900 font-bold">{{ group.groupName }}</text>
|
||||||
|
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
||||||
|
</view>
|
||||||
|
<view class="group-cards flex flex-col gap-3">
|
||||||
|
<view
|
||||||
|
v-for="link in group.links" :key="link.metadata?.name || link.spec.displayName"
|
||||||
|
class="uh-global-card-glass box-border flex overflow-hidden rounded-xl p-3"
|
||||||
|
@click="handleOnLinkEvent(link)"
|
||||||
|
>
|
||||||
<image class="h-16 w-16 shrink-0 rounded-lg" :src="link.spec.logo" mode="aspectFill" />
|
<image class="h-16 w-16 shrink-0 rounded-lg" :src="link.spec.logo" mode="aspectFill" />
|
||||||
<view class="overflow-hidden box-border flex flex-1 flex-col justify-center pl-4">
|
<view class="box-border flex flex-1 flex-col justify-center overflow-hidden pl-4">
|
||||||
<view class="flex items-center text-sm text-gray-900 font-bold">
|
<view class="flex items-center text-sm text-gray-900 font-bold">
|
||||||
<text
|
|
||||||
class="shrink-0 mr-3 rounded-md bg-secondary px-1.5 py-0.5 text-xs text-[#4d7c0f] font-normal">{{ link.spec.groupName || '暂未分组' }}</text>
|
|
||||||
<text class="flex-1 truncate">{{ link.spec.displayName }}</text>
|
<text class="flex-1 truncate">{{ link.spec.displayName }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400">
|
<view class="mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400">
|
||||||
@@ -391,43 +453,49 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 悬浮按钮 -->
|
<!-- 底部悬浮操作栏(通用组件) -->
|
||||||
<view class="flot-buttons fixed bottom-8 right-3 z-999 flex flex-col gap-1.5">
|
<uh-links-actions :actions="['apply', 'info']" @apply="handleOpenSiteApply" @info="handleOpenInfo" />
|
||||||
<view v-if="!haloPluginConfigs?.linksSubmitPlugin?.enabled"
|
|
||||||
class="fab-btn uh-global-card-glass h-11 w-11 flex items-center justify-center rounded-full"
|
|
||||||
@click="toSubmitLinkPage">
|
|
||||||
<wd-icon name="edit" size="20px" color="#6b7280" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 详情弹窗 -->
|
<!-- 详情弹窗 -->
|
||||||
<uh-glass-popup v-model="detail.show" position="bottom" :z-index="999"
|
<uh-glass-popup
|
||||||
custom-class="rounded-xl !border">
|
v-model="detail.show" position="bottom" :z-index="999"
|
||||||
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
custom-class="rounded-xl !border"
|
||||||
|
>
|
||||||
|
<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">
|
<view class="w-full flex flex-col gap-y-1">
|
||||||
<text class="text-md font-bold">站点详情</text>
|
<text class="text-md font-bold">站点详情</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="absolute right-4 top-4 w-6 h-6 uh-global-card-glass shadow-none border rounded-lg text-center"
|
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||||||
@click="miniDetail.show = false">
|
@click="miniDetail.show = false"
|
||||||
<wd-icon name="close" size="32rpx" class="text-gray-500"></wd-icon>
|
>
|
||||||
|
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view v-if="detail.data" :scroll-y="true" :show-scrollbar="false"
|
<scroll-view
|
||||||
class="box-border p-4 max-h-[60vh]">
|
v-if="detail.data" :scroll-y="true" :show-scrollbar="false"
|
||||||
|
class="box-border max-h-[60vh] p-4"
|
||||||
|
>
|
||||||
<view class="flex">
|
<view class="flex">
|
||||||
<image class="h-20 w-20 shrink-0 rounded-2xl uh-global-card-glass"
|
<image
|
||||||
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill" />
|
class="uh-global-card-glass h-20 w-20 shrink-0 rounded-2xl"
|
||||||
<view class="ml-4 flex flex-1 flex-col gap-y-1.5 justify-center">
|
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view class="ml-4 flex flex-1 flex-col justify-center gap-y-1.5">
|
||||||
<view class="text-md text-gray-900 font-bold">
|
<view class="text-md text-gray-900 font-bold">
|
||||||
{{ detail.data.spec.displayName }}
|
{{ detail.data.spec.displayName }}
|
||||||
</view>
|
</view>
|
||||||
<view class="flex items-center gap-x-2">
|
<view class="flex items-center gap-x-2">
|
||||||
<text
|
<text
|
||||||
class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">{{ detail.data.spec.groupName }}</text>
|
class="uh-global-card-glass uh-shadow-xs border rounded-lg bg-secondary px-2 py-0.5 text-xs text-gray-500 text-gray-900"
|
||||||
|
>
|
||||||
|
{{ detail.data.spec.groupName }}
|
||||||
|
</text>
|
||||||
<text
|
<text
|
||||||
class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">
|
class="uh-global-card-glass uh-shadow-xs border rounded-lg bg-secondary px-2 py-0.5 text-xs text-gray-500 text-gray-900"
|
||||||
|
>
|
||||||
复制地址
|
复制地址
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -451,11 +519,15 @@
|
|||||||
|
|
||||||
<!-- ==================== 小程序 tab ==================== -->
|
<!-- ==================== 小程序 tab ==================== -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<uh-plugin-unavailable v-if="!miniPluginAvailable" :plugin-id="miniPluginId" :error-text="miniTips"
|
<uh-plugin-unavailable
|
||||||
:checking="miniChecking" @on-refresh="handleMiniPluginRefresh" />
|
v-if="!miniPluginAvailable" :plugin-id="miniPluginId" :error-text="miniTips"
|
||||||
|
:checking="miniChecking" @on-refresh="handleMiniPluginRefresh"
|
||||||
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<uh-data-loading v-if="miniLoadingStatus !== 'success'" :loading-status="miniLoadingStatus"
|
<uh-data-loading
|
||||||
empty-text="还没有收录的小程序呢~" @refresh="handleGetMiniProgramLinks" />
|
v-if="miniLoadingStatus !== 'success'" :loading-status="miniLoadingStatus"
|
||||||
|
empty-text="还没有收录的小程序呢~" @refresh="handleGetMiniProgramLinks"
|
||||||
|
/>
|
||||||
<view v-else class="content flex flex-1 flex-col">
|
<view v-else class="content flex flex-1 flex-col">
|
||||||
<!-- 分组列表 -->
|
<!-- 分组列表 -->
|
||||||
<view class="box-border flex-1 p-3">
|
<view class="box-border flex-1 p-3">
|
||||||
@@ -463,26 +535,37 @@
|
|||||||
<view class="mb-3 flex items-center">
|
<view class="mb-3 flex items-center">
|
||||||
<text class="mr-2 inline-block h-4 w-1 rounded-full bg-secondary" />
|
<text class="mr-2 inline-block h-4 w-1 rounded-full bg-secondary" />
|
||||||
<text
|
<text
|
||||||
class="text-sm text-gray-900 font-bold">{{ group.displayName || '未分组' }}</text>
|
class="text-sm text-gray-900 font-bold"
|
||||||
|
>
|
||||||
|
{{ group.displayName || '未分组' }}
|
||||||
|
</text>
|
||||||
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="group-cards flex flex-col gap-4">
|
<view class="group-cards flex flex-col gap-4">
|
||||||
<view v-for="link in group.links" :key="link.metadata?.name"
|
<view
|
||||||
class="uh-global-card-glass box-border uh-shadow-xs flex items-center rounded-2xl p-3"
|
v-for="link in group.links" :key="link.metadata?.name"
|
||||||
@click="handleOnMiniLinkEvent(link)">
|
class="uh-global-card-glass uh-shadow-xs box-border flex items-center rounded-2xl p-3"
|
||||||
<image class="h-16 w-16 shrink-0 rounded-lg"
|
@click="handleOnMiniLinkEvent(link)"
|
||||||
:src="checkImageUrl(link.spec?.miniProgramCode)" mode="aspectFill" />
|
>
|
||||||
|
<image
|
||||||
|
class="h-16 w-16 shrink-0 rounded-lg"
|
||||||
|
:src="checkImageUrl(link.spec?.miniProgramCode)" mode="aspectFill"
|
||||||
|
/>
|
||||||
<view class="box-border flex flex-1 flex-col pl-4">
|
<view class="box-border flex flex-1 flex-col pl-4">
|
||||||
<view
|
<view
|
||||||
class="overflow-hidden truncate whitespace-nowrap text-[30rpx] text-gray-900 font-bold">
|
class="overflow-hidden truncate whitespace-nowrap text-[30rpx] text-gray-900 font-bold"
|
||||||
|
>
|
||||||
{{ link.spec?.displayName }}
|
{{ link.spec?.displayName }}
|
||||||
</view>
|
</view>
|
||||||
<view v-if="link.spec?.authorName"
|
<view
|
||||||
class="mini-author mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400">
|
v-if="link.spec?.authorName"
|
||||||
|
class="mini-author mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
|
||||||
|
>
|
||||||
{{ link.spec.authorName }}
|
{{ link.spec.authorName }}
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="mini-desc mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-500">
|
class="mini-desc mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-500"
|
||||||
|
>
|
||||||
{{ link.spec?.description || '暂无简介~' }}
|
{{ link.spec?.description || '暂无简介~' }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -492,37 +575,38 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 申请收录悬浮按钮 -->
|
<!-- 底部悬浮操作栏(通用组件) -->
|
||||||
<view class="fixed bottom-8 right-3 z-50">
|
<uh-links-actions :actions="['apply', 'info']" @apply="handleOpenApply" @info="handleOpenMiniInfo" />
|
||||||
<view
|
|
||||||
class="box-border flex flex-col w-11 h-11 items-center justify-center rounded-full bg-gray-900"
|
|
||||||
@click="handleOpenApply">
|
|
||||||
<text class="text-xs text-white">申请</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 小程序详情弹窗 -->
|
<!-- 小程序详情弹窗 -->
|
||||||
<uh-glass-popup v-model="miniDetail.show" :z-index="999" position="bottom"
|
<uh-glass-popup
|
||||||
custom-class="!rounded-xl !border">
|
v-model="miniDetail.show" :z-index="999" position="bottom"
|
||||||
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
custom-class="!rounded-xl !border"
|
||||||
|
>
|
||||||
|
<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">
|
<view class="w-full flex flex-col gap-y-1">
|
||||||
<text class="text-md font-bold">小程序详情</text>
|
<text class="text-md font-bold">小程序详情</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="absolute right-4 top-4 w-6 h-6 uh-global-card-glass shadow-none border rounded-lg text-center"
|
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||||||
@click="miniDetail.show = false">
|
@click="miniDetail.show = false"
|
||||||
<wd-icon name="close" size="32rpx" class="text-gray-500"></wd-icon>
|
>
|
||||||
|
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view v-if="miniDetail.data" :scroll-y="true" :show-scrollbar="false"
|
<scroll-view
|
||||||
class="box-border p-4 max-h-[60vh]">
|
v-if="miniDetail.data" :scroll-y="true" :show-scrollbar="false"
|
||||||
|
class="box-border max-h-[60vh] p-4"
|
||||||
|
>
|
||||||
<!-- 太阳码大图(点击预览/长按保存) -->
|
<!-- 太阳码大图(点击预览/长按保存) -->
|
||||||
<view class="code-area flex flex-col items-center">
|
<view class="code-area flex flex-col items-center">
|
||||||
<image class="code-img h-32 w-32 rounded-xl"
|
<image
|
||||||
|
class="code-img h-32 w-32 rounded-xl"
|
||||||
:src="checkImageUrl(miniDetail.data.spec?.miniProgramCode)" mode="aspectFill"
|
:src="checkImageUrl(miniDetail.data.spec?.miniProgramCode)" mode="aspectFill"
|
||||||
@click="handlePreviewMiniProgramCode(miniDetail.data)"
|
@click="handlePreviewMiniProgramCode(miniDetail.data)"
|
||||||
@longpress="handleSaveMiniProgramCode(miniDetail.data)" />
|
@longpress="handleSaveMiniProgramCode(miniDetail.data)"
|
||||||
|
/>
|
||||||
<view class="code-tip mt-3 flex items-center text-xs text-gray-400">
|
<view class="code-tip mt-3 flex items-center text-xs text-gray-400">
|
||||||
<wd-icon name="picture" size="14px" color="#a8a294" />
|
<wd-icon name="picture" size="14px" color="#a8a294" />
|
||||||
<text class="ml-1">点击预览,长按保存太阳码</text>
|
<text class="ml-1">点击预览,长按保存太阳码</text>
|
||||||
@@ -532,55 +616,80 @@
|
|||||||
<!-- 名称与分组 -->
|
<!-- 名称与分组 -->
|
||||||
<view class="mini-head mt-5 flex items-center">
|
<view class="mini-head mt-5 flex items-center">
|
||||||
<text
|
<text
|
||||||
class="mini-name text-[34rpx] text-gray-900 font-bold">{{ miniDetail.data.spec?.displayName }}</text>
|
class="mini-name text-[34rpx] text-gray-900 font-bold"
|
||||||
<text v-if="miniDetail.data.spec?.groupName"
|
>
|
||||||
class="group-tag ml-3 rounded-md bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f]">
|
{{ miniDetail.data.spec?.displayName }}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
v-if="miniDetail.data.spec?.groupName"
|
||||||
|
class="group-tag ml-3 rounded-md bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f]"
|
||||||
|
>
|
||||||
{{ miniDetail.data.spec.groupName }}
|
{{ miniDetail.data.spec.groupName }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 描述 -->
|
<!-- 描述 -->
|
||||||
<view v-if="miniDetail.data.spec?.description"
|
<view
|
||||||
class="mini-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]">
|
v-if="miniDetail.data.spec?.description"
|
||||||
|
class="mini-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]"
|
||||||
|
>
|
||||||
{{ miniDetail.data.spec.description }}
|
{{ miniDetail.data.spec.description }}
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 作者信息 -->
|
<!-- 作者信息 -->
|
||||||
<view
|
<view
|
||||||
v-if="miniDetail.data.spec?.authorName || miniDetail.data.spec?.avatar || miniDetail.data.spec?.website"
|
v-if="miniDetail.data.spec?.authorName || miniDetail.data.spec?.avatar || miniDetail.data.spec?.website"
|
||||||
class="mini-author-info mt-5 flex items-center rounded-xl bg-[#f6f3ee] p-4">
|
class="mini-author-info mt-5 flex items-center rounded-xl bg-[#f6f3ee] p-4"
|
||||||
<image v-if="miniDetail.data.spec?.avatar"
|
>
|
||||||
|
<image
|
||||||
|
v-if="miniDetail.data.spec?.avatar"
|
||||||
class="author-avatar h-[72rpx] w-[72rpx] shrink-0 rounded-full"
|
class="author-avatar h-[72rpx] w-[72rpx] shrink-0 rounded-full"
|
||||||
:src="checkAvatarUrl(miniDetail.data.spec.avatar)" mode="aspectFill" />
|
:src="checkAvatarUrl(miniDetail.data.spec.avatar)" mode="aspectFill"
|
||||||
|
/>
|
||||||
<view class="author-detail ml-4 flex flex-1 flex-col">
|
<view class="author-detail ml-4 flex flex-1 flex-col">
|
||||||
<text v-if="miniDetail.data.spec?.authorName"
|
<text
|
||||||
class="author-name text-[28rpx] text-gray-900 font-medium">{{ miniDetail.data.spec.authorName }}</text>
|
v-if="miniDetail.data.spec?.authorName"
|
||||||
<text v-if="miniDetail.data.spec?.website"
|
class="author-name text-[28rpx] text-gray-900 font-medium"
|
||||||
|
>
|
||||||
|
{{ miniDetail.data.spec.authorName }}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
v-if="miniDetail.data.spec?.website"
|
||||||
class="author-website mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
|
class="author-website mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
|
||||||
@click="handleCopyMiniProgramCode(miniDetail.data)">
|
@click="handleCopyMiniProgramCode(miniDetail.data)"
|
||||||
|
>
|
||||||
网站:{{ miniDetail.data.spec.website }}
|
网站:{{ miniDetail.data.spec.website }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 小程序地址 -->
|
<!-- 小程序地址 -->
|
||||||
<view v-if="miniDetail.data.spec?.link"
|
|
||||||
class="mini-link mt-5 flex items-center justify-between rounded-xl bg-secondary p-4">
|
|
||||||
<view
|
<view
|
||||||
class="link-text flex-1 overflow-hidden truncate whitespace-nowrap text-[26rpx] text-[#4d7c0f]">
|
v-if="miniDetail.data.spec?.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]"
|
||||||
|
>
|
||||||
{{ miniDetail.data.spec.link }}
|
{{ miniDetail.data.spec.link }}
|
||||||
</view>
|
</view>
|
||||||
<text class="ml-3 shrink-0 text-[26rpx] text-[#4d7c0f] font-bold"
|
<text
|
||||||
@click="handleCopyMiniProgramCode(miniDetail.data)">复制</text>
|
class="ml-3 shrink-0 text-[26rpx] text-[#4d7c0f] font-bold"
|
||||||
|
@click="handleCopyMiniProgramCode(miniDetail.data)"
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 预览图轮播 -->
|
<!-- 预览图轮播 -->
|
||||||
<view v-if="miniDetail.data.spec?.screenshots?.length" class="mini-screenshots mt-6">
|
<view v-if="miniDetail.data.spec?.screenshots?.length" class="mini-screenshots mt-6">
|
||||||
<swiper class="screenshots-swiper h-[360rpx] w-full" indicator-dots circular>
|
<swiper class="screenshots-swiper h-[360rpx] w-full" indicator-dots circular>
|
||||||
<swiper-item v-for="(img, idx) in miniDetail.data.spec.screenshots" :key="idx">
|
<swiper-item v-for="(img, idx) in miniDetail.data.spec.screenshots" :key="idx">
|
||||||
<image class="screenshot-img h-full w-full rounded-xl" :src="checkImageUrl(img)"
|
<image
|
||||||
|
class="screenshot-img h-full w-full rounded-xl" :src="checkImageUrl(img)"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
@click="handlePreviewMiniProgramCode({ spec: { miniProgramCode: img } } as IMiniProgramLink)" />
|
@click="handlePreviewMiniProgramCode({ spec: { miniProgramCode: img } } as IMiniProgramLink)"
|
||||||
|
/>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
</view>
|
</view>
|
||||||
@@ -591,5 +700,12 @@
|
|||||||
<uh-links-mini-apply :show="applyShow" @on-close="handleApplyClose" />
|
<uh-links-mini-apply :show="applyShow" @on-close="handleApplyClose" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 站点友链申请弹窗 -->
|
||||||
|
<uh-links-site-apply :show="siteApplyShow" @on-close="handleSiteApplyClose" />
|
||||||
|
<!-- 站点友链信息弹窗 -->
|
||||||
|
<uh-links-site-info :show="infoShow" @on-close="handleInfoClose" />
|
||||||
|
<!-- 小程序友链信息弹窗(展示小程序申请提交的信息) -->
|
||||||
|
<uh-links-mini-info :show="miniInfoShow" @on-close="handleMiniInfoClose" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { debounce } from '@/utils/debounce'
|
import { debounce } from '@/utils/debounce'
|
||||||
|
import { sleep } from '@/utils/common'
|
||||||
import { calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
import { calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||||
import type { IVoteItem } from '@/api/types/uni-halo'
|
import type { IVoteItem } from '@/api/types/uni-halo'
|
||||||
|
|
||||||
@@ -20,16 +21,22 @@
|
|||||||
const appConfigStore = useAppConfigStore()
|
const appConfigStore = useAppConfigStore()
|
||||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||||
|
|
||||||
/** 依赖插件(plugin-vote,参考 gallery 对象传参模式) */
|
|
||||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||||
pluginId: NeedPluginIds.PluginVote,
|
pluginId: NeedPluginIds.PluginVote,
|
||||||
tips: '检测到当前插件没有安装或者启用,无法使用投票功能哦,请联系管理员',
|
tips: '啊偶,功能正在维护中...',
|
||||||
|
callback: (isAvailable) => {
|
||||||
|
if (!isAvailable) { return }
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
handleGetData()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 重新检测插件:可用则拉取数据(供 uh-plugin-unavailable 刷新按钮) */
|
/** 重新检测插件:可用则拉取数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||||
async function handlePluginRefresh() {
|
async function handlePluginRefresh() {
|
||||||
if (await checkPluginAvailable())
|
if (await checkPluginAvailable()) { handleGetData() }
|
||||||
handleGetData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 状态 ---------------- */
|
/* ---------------- 状态 ---------------- */
|
||||||
@@ -38,7 +45,6 @@
|
|||||||
const hasNext = ref(false)
|
const hasNext = ref(false)
|
||||||
const isLoadMore = ref(false)
|
const isLoadMore = ref(false)
|
||||||
const loadMoreText = ref('加载中...')
|
const loadMoreText = ref('加载中...')
|
||||||
/** 是否已投过滤(前端过滤,接口无此参数) */
|
|
||||||
const filterIsVoted = ref<boolean | undefined>(undefined)
|
const filterIsVoted = ref<boolean | undefined>(undefined)
|
||||||
const queryParams = ref<Record<string, unknown>>({
|
const queryParams = ref<Record<string, unknown>>({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
@@ -124,8 +130,7 @@
|
|||||||
|
|
||||||
function handleSelectFilter(option : IFilterOption) {
|
function handleSelectFilter(option : IFilterOption) {
|
||||||
const item = filterPopup.value.item
|
const item = filterPopup.value.item
|
||||||
if (!item)
|
if (!item) { return }
|
||||||
return
|
|
||||||
filterValues.value[item.key] = option.value
|
filterValues.value[item.key] = option.value
|
||||||
filterPopup.value.show = false
|
filterPopup.value.show = false
|
||||||
|
|
||||||
@@ -169,7 +174,6 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showLoading({ mask: true, title: '加载中...' })
|
|
||||||
if (!isLoadMore.value) {
|
if (!isLoadMore.value) {
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||||
}
|
}
|
||||||
@@ -179,7 +183,6 @@
|
|||||||
const res = await getVoteList({ ...queryParams.value })
|
const res = await getVoteList({ ...queryParams.value })
|
||||||
hasNext.value = res.data.hasNext || false
|
hasNext.value = res.data.hasNext || false
|
||||||
|
|
||||||
// 加工列表数据(与旧项目一致):isVoted/_uh_state/_uh_type
|
|
||||||
const tempItems = res.data.items.map((item) => {
|
const tempItems = res.data.items.map((item) => {
|
||||||
item.spec = item.spec || {}
|
item.spec = item.spec || {}
|
||||||
item.spec.disabled = true
|
item.spec.disabled = true
|
||||||
@@ -202,7 +205,7 @@
|
|||||||
if (filterIsVoted.value !== undefined) {
|
if (filterIsVoted.value !== undefined) {
|
||||||
dataList.value = dataList.value.filter(x => x.spec?.isVoted === filterIsVoted.value)
|
dataList.value = dataList.value.filter(x => x.spec?.isVoted === filterIsVoted.value)
|
||||||
}
|
}
|
||||||
|
await sleep(600)
|
||||||
updateLoadingStatus(
|
updateLoadingStatus(
|
||||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||||
)
|
)
|
||||||
@@ -214,10 +217,7 @@
|
|||||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setTimeout(() => {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,18 +281,16 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||||||
<!-- 自定义导航 -->
|
|
||||||
<uh-navbar default-title="投票中心" title-color="text-gray-900" />
|
<uh-navbar default-title="投票中心" title-color="text-gray-900" />
|
||||||
|
|
||||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||||
:checking="checking" @on-refresh="handlePluginRefresh" />
|
:checking="checking" @on-refresh="handlePluginRefresh" />
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- 顶部搜索框 -->
|
|
||||||
<view class="box-border w-screen px-3 pt-2">
|
<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">
|
<view class="uh-global-card-glass flex h-9 items-center gap-3 rounded-full px-5">
|
||||||
<wd-icon name="search" size="16px" />
|
<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"
|
placeholder="搜索投票..." placeholder-class="text-gray-400" confirm-type="search"
|
||||||
@input="handleOnInput" @confirm="handleOnSearch">
|
@input="handleOnInput" @confirm="handleOnSearch">
|
||||||
<view v-if="queryParams.keyword" class="flex items-center"
|
<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 class="box-border flex items-center justify-between mt-1 py-2 gap-x-2">
|
||||||
<view v-for="f in filterConfig" :key="f.key"
|
<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="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)">
|
: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">
|
<text class="text-xs truncate">
|
||||||
{{ filterLabels[f.key] }}
|
{{ filterLabels[f.key] }}
|
||||||
</text>
|
</text>
|
||||||
<wd-icon name="arrow-down" size="10px" />
|
<wd-icon name="arrow-down" size="24rpx" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载/错误/空占位(状态机) -->
|
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus" empty-text="博主还未发布投票~" min-height="65vh" @refresh="handleGetData" />
|
empty-text="还没有任何投票哦~" min-height="70vh" @refresh="handleGetData" />
|
||||||
|
|
||||||
<view v-else class="box-border flex flex-col gap-4 p-3">
|
<view v-else class="box-border flex flex-col gap-4 p-3">
|
||||||
<block v-if="dataList.length !== 0">
|
<block v-if="dataList.length !== 0">
|
||||||
@@ -335,7 +334,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="flex flex-col gap-2">
|
<view class="flex flex-col gap-2">
|
||||||
<view v-for="opt in filterPopup.item.options" :key="opt.label"
|
<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'"
|
:class="filterValues[filterPopup.item.key] === opt.value ? 'bg-primary text-gray-900 font-bold' : 'text-gray-700'"
|
||||||
@click="handleSelectFilter(opt)">
|
@click="handleSelectFilter(opt)">
|
||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
|
|||||||
Reference in New Issue
Block a user