Commit ca761014 by lijiabin

【需求 20331】 feat: 完成关于首次上线的活动相关的功能

parent dffcec96
<template>
<span @click="handleClick">
<slot></slot>
</span>
</template>
<script setup lang="ts">
import { useActivityStore } from '@/stores/activity'
import { RewardButtonEnum } from '@/constants'
const { pageKey } = defineProps<{
pageKey: RewardButtonEnum
}>()
const activityStore = useActivityStore()
// 调用接口让他跳出动画
const handleClick = () => {
console.log('点击了')
activityStore.triggerPageReward(pageKey)
}
</script>
<template>
<Teleport to="body">
<Transition name="reward-toast">
<div
v-if="activityStore.showRewardAnimation"
class="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none"
>
<div class="relative flex flex-col items-center gap-4">
<!-- 光晕背景 -->
<div class="absolute inset--20 rounded-full bg-amber-400/10 blur-3xl animate-pulse" />
<!-- 碎片 +1 -->
<div class="reward-item reward-item-1">
<div
class="flex items-center gap-2.5 px-6 py-3 rounded-full bg-gradient-to-r from-indigo-500 to-purple-500 shadow-[0_4px_24px_rgba(99,102,241,0.5)]"
>
<span class="text-2xl">🧩</span>
<span class="text-white text-lg font-bold tracking-wide"
>碎片 +{{ activityStore.rewardText.fragment }}</span
>
</div>
</div>
<!-- 亚币 +2 -->
<div class="reward-item reward-item-2">
<div
class="flex items-center gap-2.5 px-6 py-3 rounded-full bg-gradient-to-r from-amber-400 to-orange-500 shadow-[0_4px_24px_rgba(245,158,11,0.5)]"
>
<span class="text-2xl">💰</span>
<span class="text-white text-lg font-bold tracking-wide"
>亚币 +{{ activityStore.rewardText.yabi }}</span
>
</div>
</div>
<!-- 星星粒子效果 -->
<div class="absolute inset--10">
<span v-for="i in 8" :key="i" class="particle" :style="getParticleStyle(i)" />
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import { useActivityStore } from '@/stores'
const activityStore = useActivityStore()
const getParticleStyle = (index: number) => {
const angle = (index / 8) * 360
const delay = index * 0.1
return {
'--angle': `${angle}deg`,
'--delay': `${delay}s`,
}
}
</script>
<style scoped>
.reward-toast-enter-active {
transition: opacity 0.3s ease;
}
.reward-toast-leave-active {
transition: opacity 0.5s ease 1.8s;
}
.reward-toast-enter-from,
.reward-toast-leave-to {
opacity: 0;
}
.reward-item {
opacity: 0;
animation: rewardSlideUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.reward-item-1 {
animation-delay: 0.1s;
}
.reward-item-2 {
animation-delay: 0.35s;
}
@keyframes rewardSlideUp {
0% {
opacity: 0;
transform: translateY(30px) scale(0.7);
}
60% {
opacity: 1;
transform: translateY(-8px) scale(1.05);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.particle {
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
border-radius: 50%;
background: linear-gradient(135deg, #fbbf24, #a78bfa);
opacity: 0;
animation: particleBurst 1.2s ease-out var(--delay) forwards;
}
@keyframes particleBurst {
0% {
opacity: 1;
transform: translate(-50%, -50%) rotate(var(--angle)) translateX(0) scale(1);
}
70% {
opacity: 0.8;
transform: translate(-50%, -50%) rotate(var(--angle)) translateX(80px) scale(0.8);
}
100% {
opacity: 0;
transform: translate(-50%, -50%) rotate(var(--angle)) translateX(120px) scale(0);
}
}
</style>
<template>
<el-dialog
v-model="activityStore.showSurvey"
title=""
width="480px"
:close-on-click-modal="false"
class="satisfaction-survey-dialog"
@close="handleClose"
>
<div class="flex flex-col gap-5">
<!-- 头部 -->
<div class="text-center">
<div class="text-2xl mb-2">📋</div>
<h3 class="text-lg font-bold text-gray-800 mb-1">使用体验反馈</h3>
<p class="text-xs text-gray-400">您的反馈对我们非常重要,仅需1分钟</p>
</div>
<!-- Q1: 满意度评分 -->
<div class="flex flex-col gap-2">
<label class="text-sm font-medium text-gray-700">整体满意度</label>
<div class="flex items-center justify-center gap-2">
<button
v-for="star in 5"
:key="star"
class="text-2xl transition-all duration-200 cursor-pointer hover:scale-125 border-none bg-transparent p-1"
:class="star <= form.rating ? 'grayscale-0' : 'grayscale opacity-30'"
@click="form.rating = star"
>
</button>
</div>
<div class="text-center text-xs text-gray-400">
{{ ratingLabels[form.rating] || '请点击星星评分' }}
</div>
</div>
<!-- Q2: 最喜欢的功能 -->
<div class="flex flex-col gap-2">
<label class="text-sm font-medium text-gray-700">最喜欢的功能(可多选)</label>
<div class="flex flex-wrap gap-2">
<button
v-for="feat in featureOptions"
:key="feat.value"
class="px-3 py-1.5 rounded-full text-xs border transition-all duration-200 cursor-pointer"
:class="
form.favoriteFeatures.includes(feat.value)
? 'bg-indigo-50 border-indigo-300 text-indigo-600'
: 'bg-white border-gray-200 text-gray-500 hover:border-gray-300'
"
@click="toggleFeature(feat.value)"
>
{{ feat.label }}
</button>
</div>
</div>
<!-- Q3: 改进建议 -->
<div class="flex flex-col gap-2">
<label class="text-sm font-medium text-gray-700">改进建议(选填)</label>
<el-input
v-model="form.suggestion"
type="textarea"
:rows="3"
placeholder="说说您的想法,帮助我们做得更好..."
maxlength="300"
show-word-limit
/>
</div>
<!-- 提交按钮 -->
<div class="flex gap-3 mt-1">
<el-button class="flex-1" @click="handleClose">下次再说</el-button>
<el-button
type="primary"
class="flex-1 bg-gradient-to-r from-indigo-500 to-purple-500! border-none!"
:disabled="form.rating === 0"
@click="handleSubmit"
>
提交反馈
</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { useActivityStore } from '@/stores'
import { submitSurvey } from '@/api'
import { push } from 'notivue'
const activityStore = useActivityStore()
const form = reactive({
rating: 0,
favoriteFeatures: [] as string[],
suggestion: '',
})
const ratingLabels: Record<number, string> = {
1: '非常不满意',
2: '不太满意',
3: '一般',
4: '比较满意',
5: '非常满意',
}
const featureOptions = [
{ value: 'post', label: '帖子/实践' },
{ value: 'video', label: '视频' },
{ value: 'question', label: '问吧' },
{ value: 'sign', label: '签到/任务' },
{ value: 'store', label: '积分商城' },
{ value: 'auction', label: '限时竞拍' },
{ value: 'lottery', label: '每日抽奖' },
{ value: 'column', label: '专栏/专访' },
]
const toggleFeature = (value: string) => {
const idx = form.favoriteFeatures.indexOf(value)
if (idx >= 0) {
form.favoriteFeatures.splice(idx, 1)
} else {
form.favoriteFeatures.push(value)
}
}
const handleSubmit = async () => {
if (form.rating === 0) return
try {
await submitSurvey({
rating: form.rating,
favoriteFeatures: form.favoriteFeatures,
suggestion: form.suggestion,
})
push.success('感谢您的反馈!')
activityStore.markSurveySubmitted()
} catch {
push.success('感谢您的反馈!')
activityStore.markSurveySubmitted()
}
}
const handleClose = () => {
activityStore.showSurvey = false
}
</script>
<style>
.satisfaction-survey-dialog .el-dialog__header {
display: none;
}
.satisfaction-survey-dialog .el-dialog__body {
padding: 24px;
}
</style>
import type { SetupContext } from 'vue'
import RewardButton from '@/components/common/RewardButton/index.vue'
import { RewardButtonEnum } from '@/constants'
type TypeOfValue = string | number
interface TabsProps<T> {
tabs: {
label: string
value: T
pageKey?: RewardButtonEnum
}[]
modelValue: T
}
......@@ -25,21 +28,41 @@ export default function Tabs<T extends TypeOfValue>(
{ tabs, modelValue }: TabsProps<T>,
{ emit }: SetupContext<TabsEmits<T>>,
) {
return tabs.map((tab) => (
<div
key={tab.value}
class={[
BASE_TAB_CALASSES,
{
[ACTIVE_TAB_CLASSES]: modelValue === tab.value,
},
]}
onClick={() => {
emit('update:modelValue', tab.value)
emit('change', tab.value)
}}
>
{tab.label}
</div>
))
return tabs.map((tab) =>
tab.pageKey ? (
<RewardButton pageKey={tab.pageKey}>
<div
key={tab.value}
class={[
BASE_TAB_CALASSES,
{
[ACTIVE_TAB_CLASSES]: modelValue === tab.value,
},
]}
onClick={() => {
emit('update:modelValue', tab.value)
emit('change', tab.value)
}}
>
{tab.label}
</div>
</RewardButton>
) : (
<div
key={tab.value}
class={[
BASE_TAB_CALASSES,
{
[ACTIVE_TAB_CLASSES]: modelValue === tab.value,
},
]}
onClick={() => {
emit('update:modelValue', tab.value)
emit('change', tab.value)
}}
>
{tab.label}
</div>
),
)
}
<template>
<Draggable
v-show="showOnlineTime"
:initial-value="{ x: x, y: y }"
storage-key="vueuse-draggable"
storage-type="session"
class="fixed z-2"
>
<div
class="group flex items-center gap-2.5 py-2.5 pr-4 pl-3 bg-#fff rounded-xl shadow-[inset_0_2px_6px_rgba(0,0,0,0.08)] cursor-pointer transition-all duration-250 relative shadow-[inset_0_2px_8px_rgba(0,0,0,0.12)]"
<transition name="online-time">
<Draggable
v-show="showOnlineTime"
:initial-value="{ x: x, y: y }"
storage-key="vueuse-draggable"
storage-type="session"
class="fixed z-2"
>
<!-- 在线指示灯 -->
<div
class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0 animate-pulse shadow-[0_0_6px_rgba(34,197,94,0.4)]"
></div>
class="group flex items-center gap-2.5 py-2.5 pr-4 pl-3 bg-#fff rounded-xl shadow-[inset_0_2px_6px_rgba(0,0,0,0.08)] cursor-pointer transition-all duration-250 relative shadow-[inset_0_2px_8px_rgba(0,0,0,0.12)]"
>
<!-- 在线指示灯 -->
<div
class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0 animate-pulse shadow-[0_0_6px_rgba(34,197,94,0.4)]"
></div>
<!-- 时长内容 -->
<div class="flex flex-col leading-tight">
<span class="text-base text-gray-400 tracking-wider font-medium">今日在线时长</span>
<span class="text-base font-bold text-blue-600 tabular-nums tracking-wider">
{{ formatSeconds }}
</span>
</div>
<!-- 时长内容 -->
<div class="flex flex-col leading-tight">
<span class="text-base text-gray-400 tracking-wider font-medium">今日在线时长</span>
<span class="text-base font-bold text-blue-600 tabular-nums tracking-wider">
{{ formatSeconds }}
</span>
</div>
<!-- 关闭按钮 -->
<div
class="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-white text-gray-400 shadow-sm flex items-center justify-center opacity-0 scale-60 transition-all duration-200 cursor-pointer group-hover:opacity-100 group-hover:scale-100 hover:!bg-red-500 hover:!text-white"
title="关闭"
@click.stop="showOnlineTime = false"
>
<svg viewBox="0 0 12 12" class="w-2.5 h-2.5">
<path
d="M1.5.4L6 4.9 10.5.4l1.1 1.1L7.1 6l4.5 4.5-1.1 1.1L6 7.1 1.5 11.6.4 10.5 4.9 6 .4 1.5z"
fill="currentColor"
/>
</svg>
<!-- 关闭按钮 -->
<div
class="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-white text-gray-400 shadow-sm flex items-center justify-center opacity-0 scale-60 transition-all duration-200 cursor-pointer group-hover:opacity-100 group-hover:scale-100 hover:!bg-red-500 hover:!text-white"
title="关闭"
@click.stop="showOnlineTime = false"
>
<svg viewBox="0 0 12 12" class="w-2.5 h-2.5">
<path
d="M1.5.4L6 4.9 10.5.4l1.1 1.1L7.1 6l4.5 4.5-1.1 1.1L6 7.1 1.5 11.6.4 10.5 4.9 6 .4 1.5z"
fill="currentColor"
/>
</svg>
</div>
</div>
</div>
</Draggable>
</Draggable>
</transition>
</template>
<script setup lang="ts">
......@@ -86,3 +88,15 @@ onUnmounted(() => {
clearInterval(timer2)
})
</script>
<style lang="scss" scoped>
.online-time-enter-from,
.online-time-leave-to {
opacity: 0;
transform: translateY(10px);
}
.online-time-enter-active,
.online-time-leave-active {
transition: all 0.3s ease;
}
</style>
......@@ -36,22 +36,25 @@
</div>
<!-- 右侧菜单 -->
<div class="flex items-center gap-1 flex-shrink-0 sm:gap-2 lg:gap-4">
<div
class="group h-80% flex items-center cursor-pointer px-2 py-1 sm:px-3 sm:py-2 rounded-lg transition-all duration-200 hover:shadow-lg hover:bg-white/60"
@click="router.push('/userPage')"
>
<el-avatar
:size="30"
:src="userInfo.hiddenAvatar"
class="border-3 border-blue-200 shadow-lg flex-shrink-0 transition-transform duration-200 group-hover:scale-105"
/>
<span
class="ml-2 text-sm whitespace-nowrap hidden lg:inline transition-all duration-300 ease-out group-hover:translate-y--0.5 text-gray-700"
<RewardButton :pageKey="RewardButtonEnum.USER_PAGE">
<div
class="group h-80% flex items-center cursor-pointer px-2 py-1 sm:px-3 sm:py-2 rounded-lg transition-all duration-200 hover:shadow-lg hover:bg-white/60"
@click="router.push('/userPage')"
>
个人中心
</span>
</div>
<el-avatar
:size="30"
:src="userInfo.hiddenAvatar"
class="border-3 border-blue-200 shadow-lg flex-shrink-0 transition-transform duration-200 group-hover:scale-105"
/>
<span
class="ml-2 text-sm whitespace-nowrap hidden lg:inline transition-all duration-300 ease-out group-hover:translate-y--0.5 text-gray-700"
>
个人中心
</span>
</div>
</RewardButton>
<div
class="group flex items-center cursor-pointer px-2 py-1 sm:px-3 sm:py-2 rounded-lg transition-all duration-200 hover:shadow-lg hover:bg-white/60"
@click="router.push('/auction')"
......@@ -89,20 +92,42 @@
</button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="ArticleTypeEnum.POST">帖子</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.PRACTICE">实践</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.VIDEO">视频</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.QUESTION">问吧</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.POST">
<RewardButton :pageKey="RewardButtonEnum.PUBLISH_LONG_ARTICLE">
帖子
</RewardButton>
</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.PRACTICE">
<RewardButton :pageKey="RewardButtonEnum.PUBLISH_LONG_ARTICLE">
实践
</RewardButton>
</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.VIDEO">
<RewardButton :pageKey="RewardButtonEnum.PUBLISH_LONG_ARTICLE">
视频
</RewardButton>
</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.QUESTION">
<RewardButton :pageKey="RewardButtonEnum.PUBLISH_LONG_ARTICLE">
问吧
</RewardButton>
</el-dropdown-item>
<el-dropdown-item
v-if="userInfo.isOfficialAccount || userInfo.isAdmin"
:command="ArticleTypeEnum.COLUMN"
>专栏</el-dropdown-item
>
<RewardButton :pageKey="RewardButtonEnum.PUBLISH_LONG_ARTICLE">
专栏
</RewardButton>
</el-dropdown-item>
<el-dropdown-item
v-if="userInfo.isOfficialAccount || userInfo.isAdmin"
:command="ArticleTypeEnum.INTERVIEW"
>专访</el-dropdown-item
>
<RewardButton :pageKey="RewardButtonEnum.PUBLISH_LONG_ARTICLE">
专访
</RewardButton>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
......@@ -110,7 +135,10 @@
</div>
</div>
<div class="flex-1 w-full flex items-center justify-center">
<div class="container max-h-none transition-all duration-300 min-h-[calc(100vh-96px)]">
<div
class="container max-h-none transition-all duration-300 min-h-[calc(100vh-96px)]"
:class="{ 'px-10!': !isHomePage }"
>
<router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in">
<!-- 注释不能放到keep-alive下面 route是最终的路由信息 Component是当前n级路由的组件 二级路由 homePage videoDetail -->
......@@ -123,27 +151,37 @@
</div>
</div>
<OnlineTime v-if="showOnlineTime" />
<PublishDialog ref="PublishDialogRef" />
<!-- 活动相关全局组件 -->
<CgGuide />
<RewardToast />
<!-- <SatisfactionSurvey /> -->
</template>
<script setup lang="tsx" name="CultureLayout">
import { Search } from '@element-plus/icons-vue'
import OnlineTime from './components/onlineTime.vue'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
import PublishDialog from './components/publishDialog.vue'
import CgGuide from '@/components/common/CgGuide/index.vue'
import RewardToast from '@/components/common/RewardToast/index.vue'
// import SatisfactionSurvey from '@/components/common/SatisfactionSurvey/index.vue'
import { ArticleTypeEnum } from '@/constants'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
import { useActivityStore } from '@/stores/activity'
import RewardButton from '@/components/common/RewardButton/index.vue'
import { RewardButtonEnum } from '@/constants'
const userStore = useUserStore()
const activityStore = useActivityStore()
const { userInfo } = storeToRefs(userStore)
const router = useRouter()
const route = useRoute()
const isHomePage = computed(() => route.path.includes('/homePage'))
const search = ref('')
const PublishDialogRef = useTemplateRef<InstanceType<typeof PublishDialog>>('PublishDialogRef')
console.log(route)
const showSearchInupt = computed(() => route.path !== '/searchPage')
// 获取二级路由的 key
const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
......@@ -174,12 +212,17 @@ const handlePost = async (type: ArticleTypeEnum) => {
router.push(`/publishLongArticle/${type}`)
} else if (type === ArticleTypeEnum.PRACTICE) {
router.push(`/publishLongArticle/${type}`)
// PublishDialogRef.value?.open(type)
} else {
router.push(`/publishLongArticle/${type}`)
}
}
const isDropdownHover = ref(false)
// 首页触发CG相关引导
onMounted(() => {
console.log(1222)
activityStore.triggerCgGuide()
})
</script>
<style lang="scss" scoped>
.layout-culture {
......
import { defineStore } from 'pinia'
import { getUserLaunchCampaignStatus, markUserGuide, markUserClickedRewardButton } from '@/api'
import dayjs from 'dayjs'
import { RewardButtonEnum } from '@/constants'
import { useYaBiStore } from './yabi'
const STORAGE_KEYS = {
CG_WATCHED: 'activity_cg_watched',
CLAIMED_PAGES: 'activity_claimed_pages',
CLICK_COUNT: 'activity_click_count',
SURVEY_SUBMITTED: 'activity_survey_submitted',
} as const
export const useActivityStore = defineStore('activity', () => {
// ========== CG 引导 ==========
const isLaunchCampaignTime = ref(false)
// 2026.3.16 00:00:00 - 2026.3.22 23:59:59
const launchCampaignRange: [number, number] = [
dayjs('2026-03-16 00:00:00').unix(),
dayjs('2026-03-22 23:59:59').unix(),
]
// cg动画引导是否显示
const showCgGuide = ref(false)
// 普通的动画引导
const showCommonGuide = ref(false)
const markUserGuideWatched = async (key: 'isView' | 'isViewGuide') => {
await markUserGuide(key)
}
const triggerCgGuide = async () => {
const { data } = await getUserLaunchCampaignStatus()
// 用户是否观看过CG
if (!data.isView) {
showCgGuide.value = true
}
if (!data.isViewGuide) {
showCommonGuide.value = true
}
// 是否在活动期间
if (data.currentTime >= launchCampaignRange[0] && data.currentTime <= launchCampaignRange[1]) {
isLaunchCampaignTime.value = true
}
}
// ========== 碎片奖励动画 ==========
const showRewardAnimation = ref(false)
const rewardText = ref({ fragment: 1, yabi: 2 })
const triggerPageReward = async (pageKey: RewardButtonEnum) => {
const { data } = await markUserClickedRewardButton(pageKey)
const yabiStore = useYaBiStore()
yabiStore.fetchYaBiData()
if (data) {
setTimeout(() => {
showRewardAnimation.value = true
setTimeout(() => {
showRewardAnimation.value = false
}, 2500)
}, 500)
}
}
// ========== 满意度问卷 ==========
const clickCount = ref(0)
const surveySubmitted = ref(false)
const showSurvey = ref(false)
const surveyThreshold = ref(999999)
const incrementClickCount = () => {
if (surveySubmitted.value) return
clickCount.value++
localStorage.setItem(STORAGE_KEYS.CLICK_COUNT, String(clickCount.value))
if (clickCount.value >= surveyThreshold.value && !surveySubmitted.value) {
showSurvey.value = true
}
}
const markSurveySubmitted = () => {
surveySubmitted.value = true
localStorage.setItem(STORAGE_KEYS.SURVEY_SUBMITTED, '1')
showSurvey.value = false
}
return {
isLaunchCampaignTime,
// CG
// hasWatchedCg,
showCgGuide,
showCommonGuide,
markUserGuideWatched,
triggerCgGuide,
// 碎片奖励
showRewardAnimation,
rewardText,
triggerPageReward,
// 问卷
clickCount,
surveySubmitted,
showSurvey,
surveyThreshold,
incrementClickCount,
markSurveySubmitted,
}
})
......@@ -6,3 +6,4 @@ export * from './video'
export * from './question'
export * from './yabi'
export * from './onlineTime'
export * from './activity'
......@@ -33,13 +33,19 @@ import ColumnList from './components/columnList.vue'
import InterviewList from './components/interviewList.vue'
import PracticeList from './components/practiceList.vue'
import VideoList from './components/videoList.vue'
import { RewardButtonEnum } from '@/constants'
const tabs = [
{ label: '专栏', value: '专栏', component: ColumnList },
{ label: '实践', value: '实践', component: PracticeList },
{ label: '专访', value: '专访', component: InterviewList },
{ label: '视频', value: '视频', component: VideoList },
{ label: '关爱基金', value: '关爱基金', component: null },
{ label: '专栏', value: '专栏', component: ColumnList, pageKey: undefined },
{ label: '实践', value: '实践', component: PracticeList, pageKey: RewardButtonEnum.YA_PRACTICE },
{
label: '专访',
value: '专访',
component: InterviewList,
pageKey: RewardButtonEnum.YA_INTERVIEW,
},
{ label: '视频', value: '视频', component: VideoList, pageKey: RewardButtonEnum.YA_VIDEO },
{ label: '关爱基金', value: '关爱基金', component: null, pageKey: undefined },
]
const activeTab = ref('专栏')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment