init
This commit is contained in:
202
pc/pages/account/security.vue
Normal file
202
pc/pages/account/security.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="px-[30px] py-5 user-info">
|
||||
<div class="border-b border-br pb-5">
|
||||
<span class="text-2xl font-medium">账号安全</span>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<div class="info-item leading-10 flex justify-between">
|
||||
<div class="item-name">登录密码</div>
|
||||
<div>
|
||||
<ElButton
|
||||
link
|
||||
type="primary"
|
||||
@click="showMobilePopup = true"
|
||||
>
|
||||
{{ userInfo.isPassword ? '点击修改' : '点击设置' }}
|
||||
<Icon name="el-icon-ArrowRight" />
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item leading-10 flex justify-between">
|
||||
<div class="item-name">绑定微信</div>
|
||||
<div>
|
||||
{{ userInfo.isBindMnp ? '已绑定' : '未绑定' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<ElDialog
|
||||
v-model="showMobilePopup"
|
||||
:width="400"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="px-5">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-4xl">
|
||||
{{
|
||||
userInfo.isPassword
|
||||
? '修改登录密码'
|
||||
: '设置登录密码'
|
||||
}}
|
||||
</span>
|
||||
<ElButton
|
||||
type="primary"
|
||||
link
|
||||
@click="toForgetPwd"
|
||||
v-if="userInfo.isPassword"
|
||||
>
|
||||
忘记原密码
|
||||
</ElButton>
|
||||
</div>
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
class="mt-[35px]"
|
||||
size="large"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
>
|
||||
<ElFormItem
|
||||
prop="oldPassword"
|
||||
v-if="userInfo.isPassword"
|
||||
>
|
||||
<ElInput
|
||||
v-model="formData.oldPassword"
|
||||
placeholder="请输入原密码"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="password">
|
||||
<ElInput
|
||||
v-model="formData.password"
|
||||
placeholder="请输入6-20位数字+字母或符号组合"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="passwordConfirm">
|
||||
<ElInput
|
||||
v-model="formData.passwordConfirm"
|
||||
placeholder="请再次输入密码"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem class="mt-[60px]">
|
||||
<ElButton
|
||||
class="w-full"
|
||||
type="primary"
|
||||
@click="handleConfirmLock"
|
||||
:loading="isLock"
|
||||
>
|
||||
确认
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
</ElDialog>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getUserInfo, userChangePwd } from '@/api/user'
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElButton,
|
||||
FormInstance,
|
||||
FormRules,
|
||||
ElDialog
|
||||
} from 'element-plus'
|
||||
import {
|
||||
PopupTypeEnum,
|
||||
useAccount
|
||||
} from '~~/layouts/components/account/useAccount'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import feedback from '~~/utils/feedback'
|
||||
const { data: userInfo, refresh } = await useAsyncData(() => getUserInfo(), {
|
||||
default: () => ({}),
|
||||
initialCache: false
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const showMobilePopup = ref(false)
|
||||
const { setPopupType, toggleShowPopup } = useAccount()
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const formRules: FormRules = {
|
||||
oldPassword: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入原密码',
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入6-20位数字+字母或符号组合',
|
||||
trigger: ['change', 'blur']
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '密码长度应为6-20',
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
],
|
||||
passwordConfirm: [
|
||||
{
|
||||
validator(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== formData.password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
]
|
||||
}
|
||||
const formData = reactive({
|
||||
oldPassword: '',
|
||||
password: '',
|
||||
passwordConfirm: ''
|
||||
})
|
||||
|
||||
const toForgetPwd = () => {
|
||||
showMobilePopup.value = false
|
||||
setPopupType(PopupTypeEnum.FORGOT_PWD)
|
||||
toggleShowPopup(true)
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
await formRef.value?.validate()
|
||||
await userChangePwd(formData)
|
||||
feedback.msgSuccess('修改成功')
|
||||
userStore.logout()
|
||||
showMobilePopup.value = false
|
||||
refresh()
|
||||
}
|
||||
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
|
||||
definePageMeta({
|
||||
module: 'personal',
|
||||
auth: true
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.user-info {
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
padding: 10px 0;
|
||||
.item-name {
|
||||
width: 80px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
75
pc/pages/index.vue
Normal file
75
pc/pages/index.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="index">
|
||||
<div class="flex">
|
||||
<ElCarousel
|
||||
v-if="getSwiperData.enabled"
|
||||
class="w-[750px] flex-none mr-5"
|
||||
trigger="click"
|
||||
height="340px"
|
||||
>
|
||||
<ElCarouselItem v-for="item in getSwiperData.data" :key="item">
|
||||
<NuxtLink :to="item.link.path" target="_blank">
|
||||
<ElImage
|
||||
class="w-full h-full rounded-[8px] bg-white overflow-hidden"
|
||||
:src="appStore.getImageUrl(item.image)"
|
||||
fit="contain"
|
||||
/>
|
||||
</NuxtLink>
|
||||
</ElCarouselItem>
|
||||
</ElCarousel>
|
||||
<InformationCard
|
||||
link="/information/new"
|
||||
class="flex-1 min-w-0"
|
||||
header="最新资讯"
|
||||
:data="pageData.new"
|
||||
:show-time="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5 flex">
|
||||
<InformationCard
|
||||
link="/information"
|
||||
class="w-[750px] flex-none mr-5"
|
||||
header="全部资讯"
|
||||
:data="pageData.all"
|
||||
:only-title="false"
|
||||
/>
|
||||
<InformationCard
|
||||
link="/information/hot"
|
||||
class="flex-1"
|
||||
header="热门资讯"
|
||||
:data="pageData.hot"
|
||||
:only-title="false"
|
||||
image-size="mini"
|
||||
:show-author="false"
|
||||
:show-desc="false"
|
||||
:show-click="false"
|
||||
:border="false"
|
||||
:title-line="2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ElCarousel, ElCarouselItem, ElImage } from 'element-plus'
|
||||
import { getIndex } from '@/api/shop'
|
||||
import { useAppStore } from '~~/stores/app'
|
||||
const appStore = useAppStore()
|
||||
const { data: pageData } = await useAsyncData(() => getIndex(), {
|
||||
default: () => ({
|
||||
all: [],
|
||||
hot: [],
|
||||
new: [],
|
||||
pages: []
|
||||
})
|
||||
})
|
||||
|
||||
const getSwiperData = computed(() => {
|
||||
try {
|
||||
const data = JSON.parse(pageData.value.pages)
|
||||
return data.find((item) => item.name === 'banner')?.content
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
88
pc/pages/information/[source]/index.vue
Normal file
88
pc/pages/information/[source]/index.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="min-h-full flex flex-col">
|
||||
<div class="text-4xl mb-5">
|
||||
<span v-if="route.query.keywords">
|
||||
查找"{{ route.query.keywords }}"
|
||||
</span>
|
||||
<span v-else>{{ route.query.name || getSourceText }}</span>
|
||||
</div>
|
||||
<div v-loading="pending">
|
||||
<div
|
||||
class="bg-white px-5 rounded overflow-hidden"
|
||||
v-if="data.lists.length"
|
||||
>
|
||||
<div class="pt-5 text-tx-secondary" v-if="route.query.keywords">
|
||||
为您找到相关结果 {{ data.count }}个
|
||||
</div>
|
||||
<InformationItems
|
||||
v-for="item in data.lists"
|
||||
:key="item.id"
|
||||
:id="item.id"
|
||||
:title="item.title"
|
||||
:desc="item.intro"
|
||||
:click="item.visit"
|
||||
:author="item.author"
|
||||
:create-time="item.createTime"
|
||||
:image="item.image"
|
||||
:only-title="false"
|
||||
/>
|
||||
<div class="py-4 flex justify-end">
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNo"
|
||||
:total="data.count"
|
||||
:page-size="params.pageSize"
|
||||
hide-on-single-page
|
||||
@current-change="refresh()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex-1 flex justify-center items-center">
|
||||
<el-empty
|
||||
:image="empty_news"
|
||||
description="暂无资讯"
|
||||
:image-size="250"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ElPagination, ElEmpty } from 'element-plus'
|
||||
import empty_news from '@/assets/images/empty_news.png'
|
||||
import { getArticleList } from '~~/api/news'
|
||||
const route = useRoute()
|
||||
const sort = computed(() =>
|
||||
route.params.source == 'search' ? '' : route.params.source
|
||||
)
|
||||
const keyword = computed(() => route.query.keywords || '')
|
||||
const cid = computed(() => route.query.cid || '')
|
||||
const params = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 15,
|
||||
keyword,
|
||||
cid,
|
||||
sort
|
||||
})
|
||||
const { data, refresh, pending } = await useAsyncData(
|
||||
() => getArticleList(params),
|
||||
{
|
||||
initialCache: false
|
||||
}
|
||||
)
|
||||
|
||||
const getSourceText = computed(() => {
|
||||
switch (route.params.source) {
|
||||
case 'hot':
|
||||
return '热门资讯'
|
||||
case 'new':
|
||||
return ' 最新资讯'
|
||||
default:
|
||||
return '全部资讯'
|
||||
}
|
||||
})
|
||||
|
||||
watch([() => route.query.keywords, () => route.query.cid], () => {
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
132
pc/pages/information/detail/[id].vue
Normal file
132
pc/pages/information/detail/[id].vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
当前位置:
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item :to="{ path: '/information' }">
|
||||
资讯中心
|
||||
</el-breadcrumb-item>
|
||||
<el-breadcrumb-item
|
||||
:to="{
|
||||
path: `/information/search`,
|
||||
query: {
|
||||
cid: newsDetail.cid,
|
||||
name: newsDetail.category
|
||||
}
|
||||
}"
|
||||
>
|
||||
{{ newsDetail.category }}
|
||||
</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>文章详情</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="flex gap-4 mt-5">
|
||||
<div class="w-[750px] bg-body rounded-[8px] flex-none p-5">
|
||||
<div class="border-b border-br pb-4">
|
||||
<span class="font-medium text-[22px]">
|
||||
{{ newsDetail.title }}
|
||||
</span>
|
||||
<div
|
||||
class="mt-3 text-tx-secondary flex items-center flex-wrap"
|
||||
>
|
||||
<span v-if="newsDetail.author">
|
||||
{{ newsDetail.author }} |
|
||||
</span>
|
||||
<span class="mr-5">{{ newsDetail.createTime }}</span>
|
||||
<div class="flex items-center">
|
||||
<Icon name="el-icon-View" />
|
||||
<span> {{ newsDetail.visit }}人浏览</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="newsDetail.summary"
|
||||
class="bg-page mt-4 p-3 rounded-lg"
|
||||
>
|
||||
摘要:{{ newsDetail.summary }}
|
||||
</div>
|
||||
<div class="py-4" v-html="newsDetail.content"></div>
|
||||
<div class="flex justify-center mt-[40px]">
|
||||
<ElButton size="large" round @click="handelCollectLock">
|
||||
<Icon
|
||||
:name="`el-icon-${
|
||||
newsDetail.isCollect ? 'StarFilled' : 'Star'
|
||||
}`"
|
||||
:size="newsDetail.isCollect ? 22 : 18"
|
||||
:color="
|
||||
newsDetail.isCollect ? '#FF2C2F' : 'inherit'
|
||||
"
|
||||
/>
|
||||
{{ newsDetail.isCollect ? '取消收藏' : '点击收藏' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
<div class="border-t border-br mt-[30px]">
|
||||
<div class="mt-5 flex">
|
||||
<span class="text-tx-regular">上一篇:</span>
|
||||
<NuxtLink
|
||||
v-if="newsDetail.prev"
|
||||
class="flex-1 hover:underline"
|
||||
:to="`/information/detail/${newsDetail.prev?.id}`"
|
||||
>
|
||||
{{ newsDetail.prev?.title }}
|
||||
</NuxtLink>
|
||||
<span v-else> 暂无相关文章 </span>
|
||||
</div>
|
||||
<div class="mt-5 flex">
|
||||
<span class="text-tx-regular">下一篇:</span>
|
||||
<NuxtLink
|
||||
v-if="newsDetail.next"
|
||||
class="flex-1 hover:underline"
|
||||
:to="`/information/detail/${newsDetail.next?.id}`"
|
||||
>
|
||||
{{ newsDetail.next?.title }}
|
||||
</NuxtLink>
|
||||
<span v-else> 暂无相关文章 </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<InformationCard
|
||||
class="flex-1"
|
||||
header="相关资讯"
|
||||
:data="newsDetail.news"
|
||||
:only-title="false"
|
||||
image-size="mini"
|
||||
:show-author="false"
|
||||
:show-desc="false"
|
||||
:show-click="false"
|
||||
:border="false"
|
||||
:title-line="2"
|
||||
source="new"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ElBreadcrumb, ElBreadcrumbItem, ElButton } from 'element-plus'
|
||||
import { addCollect, cancelCollect, getArticleDetail } from '~~/api/news'
|
||||
import feedback from '~~/utils/feedback'
|
||||
const route = useRoute()
|
||||
const { data: newsDetail, refresh } = await useAsyncData(
|
||||
() =>
|
||||
getArticleDetail({
|
||||
id: route.params.id
|
||||
}),
|
||||
{
|
||||
initialCache: false
|
||||
}
|
||||
)
|
||||
|
||||
const handelCollect = async () => {
|
||||
const articleId = route.params.id
|
||||
if (newsDetail.value.isCollect) {
|
||||
await cancelCollect({ articleId })
|
||||
feedback.msgSuccess('已取消收藏')
|
||||
} else {
|
||||
await addCollect({ articleId })
|
||||
feedback.msgSuccess('收藏成功')
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
const { lockFn: handelCollectLock } = useLockFn(handelCollect)
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
83
pc/pages/information/index.vue
Normal file
83
pc/pages/information/index.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-4xl mb-5">资讯中心</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<InformationCard
|
||||
v-for="item in newsLists"
|
||||
style="width: calc(50% - 8px)"
|
||||
:key="item.id"
|
||||
:header="item.name"
|
||||
:data="item.article"
|
||||
:link="`/information/search?cid=${item.id}&name=${item.name}`"
|
||||
>
|
||||
<template #content="{ data }">
|
||||
<div class="px-4 py-5">
|
||||
<div class="flex gap-2.5">
|
||||
<div
|
||||
class="w-[180px] bg-page rounded overflow-hidden"
|
||||
v-for="(item, index) in splitData(data)
|
||||
.topThree"
|
||||
:key="item.id"
|
||||
>
|
||||
<InformationItems
|
||||
:index="index"
|
||||
:id="item.id"
|
||||
:title="item.title"
|
||||
:author="item.author"
|
||||
:create-time="item.createTime"
|
||||
:image="item.image || placeholder"
|
||||
:only-title="false"
|
||||
:border="false"
|
||||
:show-author="false"
|
||||
:show-desc="false"
|
||||
:show-time="false"
|
||||
:show-click="false"
|
||||
:is-horizontal="true"
|
||||
>
|
||||
<template #title="{ title }">
|
||||
<span class="line-clamp-2">{{
|
||||
title
|
||||
}}</span>
|
||||
</template>
|
||||
</InformationItems>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in splitData(data).remain"
|
||||
:key="item.id"
|
||||
>
|
||||
<InformationItems
|
||||
:id="item.id"
|
||||
:title="item.title"
|
||||
:author="item.author"
|
||||
:create-time="item.createTime"
|
||||
:image="item.image"
|
||||
:only-title="true"
|
||||
:show-time="false"
|
||||
>
|
||||
<template #title="{ title }">
|
||||
<span class="line-clamp-1">{{
|
||||
title
|
||||
}}</span>
|
||||
</template>
|
||||
</InformationItems>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</InformationCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getArticleCenter } from '~~/api/news'
|
||||
import placeholder from '@/assets/images/placeholder.png'
|
||||
const { data: newsLists } = await useAsyncData(() => getArticleCenter())
|
||||
const splitData = (data) => {
|
||||
const size = 3
|
||||
return {
|
||||
topThree: data.slice(0, size),
|
||||
remain: data.slice(size)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
25
pc/pages/policy/[type].vue
Normal file
25
pc/pages/policy/[type].vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="bg-white render-html p-[30px] w-[1200px] mx-auto min-h-screen">
|
||||
<h1 class="text-center">{{ data.name }}</h1>
|
||||
<div class="mx-auto" v-html="data.content"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getPolicy } from '~~/api/app'
|
||||
|
||||
const route = useRoute()
|
||||
const { data } = await useAsyncData(
|
||||
() =>
|
||||
getPolicy({
|
||||
type: route.params.type
|
||||
}),
|
||||
{
|
||||
initialCache: false
|
||||
}
|
||||
)
|
||||
|
||||
definePageMeta({
|
||||
layout: 'blank'
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
85
pc/pages/user/collection.vue
Normal file
85
pc/pages/user/collection.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="px-[30px] py-5 user-info min-h-full flex flex-col">
|
||||
<div class="border-b border-br pb-5">
|
||||
<span class="text-2xl font-medium">我的收藏</span>
|
||||
</div>
|
||||
<div v-if="data.lists.length">
|
||||
<div
|
||||
class="cursor-pointer"
|
||||
v-for="item in data.lists"
|
||||
:key="item.id"
|
||||
@click="$router.push(`/information/detail/${item.articleId}`)"
|
||||
>
|
||||
<div class="border-b border-br py-4 flex items-center">
|
||||
<ElImage
|
||||
v-if="item.image"
|
||||
class="flex-none w-[180px] h-[135px] mr-4"
|
||||
:src="item.image"
|
||||
fit="cover"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="text-lg font-medium line-clamp-1">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
|
||||
<div class="text-tx-regular line-clamp-2 mt-4">
|
||||
{{ item.intro }}
|
||||
</div>
|
||||
<div
|
||||
class="mt-5 text-tx-secondary flex justify-between"
|
||||
>
|
||||
<div>收藏于{{ item.createTime }}</div>
|
||||
<ElButton
|
||||
link
|
||||
@click.stop="handelCollect(item.articleId)"
|
||||
>
|
||||
取消收藏
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-4 flex justify-end">
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNo"
|
||||
:total="data.count"
|
||||
:page-size="params.pageSize"
|
||||
hide-on-single-page
|
||||
layout="total, prev, pager, next, jumper"
|
||||
@current-change="refresh()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 justify-center items-center" v-else>
|
||||
<el-empty
|
||||
:image="empty_news"
|
||||
description="暂无收藏"
|
||||
:image-size="250"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { cancelCollect, getCollect } from '~~/api/news'
|
||||
import empty_news from '@/assets/images/empty_news.png'
|
||||
import { ElImage, ElButton, ElPagination, ElEmpty } from 'element-plus'
|
||||
import feedback from '~~/utils/feedback'
|
||||
const params = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 15
|
||||
})
|
||||
const { data, refresh } = await useAsyncData(() => getCollect(params), {
|
||||
initialCache: false
|
||||
})
|
||||
const handelCollect = async (articleId) => {
|
||||
await cancelCollect({ articleId })
|
||||
feedback.msgSuccess('已取消收藏')
|
||||
refresh()
|
||||
}
|
||||
definePageMeta({
|
||||
module: 'personal',
|
||||
auth: true
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
201
pc/pages/user/info.vue
Normal file
201
pc/pages/user/info.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div class="px-[30px] py-5 user-info">
|
||||
<div class="border-b border-br pb-5">
|
||||
<span class="text-2xl font-medium">个人信息</span>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<div class="info-item">
|
||||
<div class="item-name">头像</div>
|
||||
<div class="avatar">
|
||||
<ElAvatar :size="60" :src="userInfo.avatar"></ElAvatar>
|
||||
<div class="change-btn">
|
||||
<CropperUpload
|
||||
@change="setUserInfo($event, UserFieldEnum.AVATAR)"
|
||||
>
|
||||
<span class="text-xs text-white">修改</span>
|
||||
</CropperUpload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item leading-10">
|
||||
<div class="item-name">账号</div>
|
||||
<div>
|
||||
{{ userInfo.username }}
|
||||
<ClientOnly>
|
||||
<PopoverInput
|
||||
class="inline-block"
|
||||
@confirm="
|
||||
setUserInfo($event, UserFieldEnum.USERNAME)
|
||||
"
|
||||
:limit="30"
|
||||
show-limit
|
||||
>
|
||||
<ElButton link>
|
||||
<Icon name="el-icon-Edit" :size="16" />
|
||||
</ElButton>
|
||||
</PopoverInput>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item leading-10">
|
||||
<div class="item-name">昵称</div>
|
||||
<div>
|
||||
{{ userInfo.nickname }}
|
||||
<ClientOnly>
|
||||
<PopoverInput
|
||||
class="inline-block"
|
||||
@confirm="
|
||||
setUserInfo($event, UserFieldEnum.NICKNAME)
|
||||
"
|
||||
:limit="30"
|
||||
show-limit
|
||||
>
|
||||
<ElButton link>
|
||||
<Icon name="el-icon-Edit" :size="16" />
|
||||
</ElButton>
|
||||
</PopoverInput>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item leading-10">
|
||||
<div class="item-name">性别</div>
|
||||
<div>
|
||||
<span>
|
||||
{{ userInfo.sex }}
|
||||
</span>
|
||||
<ClientOnly>
|
||||
<PopoverInput
|
||||
class="inline-block"
|
||||
type="select"
|
||||
:teleported="false"
|
||||
:options="[
|
||||
{
|
||||
label: '未知',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '男',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '女',
|
||||
value: 2
|
||||
}
|
||||
]"
|
||||
@confirm="setUserInfo($event, UserFieldEnum.SEX)"
|
||||
>
|
||||
<ElButton link>
|
||||
<Icon name="el-icon-Edit" :size="16" />
|
||||
</ElButton>
|
||||
</PopoverInput>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item leading-10">
|
||||
<div class="item-name">手机号</div>
|
||||
<div v-if="userInfo.mobile">
|
||||
{{ userInfo.mobile }}
|
||||
</div>
|
||||
|
||||
<ElButton link type="primary" @click="changeMobile">
|
||||
{{ userInfo.mobile ? '更换手机号' : '绑定手机号' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
<div class="info-item leading-10">
|
||||
<div class="item-name">注册时间</div>
|
||||
<div>
|
||||
{{ userInfo.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[60px] flex justify-center">
|
||||
<ElButton type="primary" @click="handleLogout">退出登录</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ElAvatar, ElButton } from 'element-plus'
|
||||
import { getUserInfo, userEdit } from '@/api/user'
|
||||
import CropperUpload from '@/components/cropper-upload/index.vue'
|
||||
import PopoverInput from '@/components/popover-input/index.vue'
|
||||
import {
|
||||
useAccount,
|
||||
PopupTypeEnum
|
||||
} from '@/layouts/components/account/useAccount'
|
||||
import feedback from '~~/utils/feedback'
|
||||
import { useUserStore } from '~~/stores/user'
|
||||
const { setPopupType, toggleShowPopup, showPopup } = useAccount()
|
||||
const userStore = useUserStore()
|
||||
// 用户资料
|
||||
enum UserFieldEnum {
|
||||
NONE = '',
|
||||
AVATAR = 'avatar',
|
||||
USERNAME = 'username',
|
||||
NICKNAME = 'nickname',
|
||||
SEX = 'sex'
|
||||
}
|
||||
|
||||
const { data: userInfo, refresh } = await useAsyncData(() => getUserInfo(), {
|
||||
initialCache: false
|
||||
})
|
||||
const setUserInfo = async (
|
||||
value: string,
|
||||
type: UserFieldEnum
|
||||
): Promise<void> => {
|
||||
await userEdit({
|
||||
field: type,
|
||||
value: value
|
||||
})
|
||||
feedback.msgSuccess('操作成功')
|
||||
refresh()
|
||||
}
|
||||
|
||||
const changeMobile = () => {
|
||||
setPopupType(PopupTypeEnum.BIND_MOBILE)
|
||||
toggleShowPopup(true)
|
||||
}
|
||||
|
||||
watch(showPopup, (value) => {
|
||||
if (!value) {
|
||||
refresh()
|
||||
}
|
||||
})
|
||||
|
||||
const handleLogout = async () => {
|
||||
await feedback.confirm('确定退出登录吗?')
|
||||
userStore.logout()
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
module: 'personal',
|
||||
auth: true
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.user-info {
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
padding: 10px 0;
|
||||
.item-name {
|
||||
width: 80px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
@apply relative flex cursor-pointer;
|
||||
.change-btn {
|
||||
display: none;
|
||||
height: 50%;
|
||||
line-height: 30px;
|
||||
@apply absolute bg-[rgba(0,0,0,0.5)] w-full text-center bottom-0 rounded-b-full;
|
||||
}
|
||||
&:hover {
|
||||
.change-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user