u
This commit is contained in:
1 parent
2f8a815c62
commit
a09fdf0a66
127 files changed
+4518
-1159
No files matched your search
@@ -31,18 +31,7 @@ function handleInitData() {
|
||||
.then((res) => {
|
||||
console.log(res)
|
||||
if (res.isSuccess) {
|
||||
const list: PostTag[] = res.data || []
|
||||
|
||||
commonOptions.postTagOptions.splice(
|
||||
0,
|
||||
commonOptions.postTagOptions.length,
|
||||
{
|
||||
id: 0,
|
||||
name: '全部',
|
||||
icon: 'fluent-color:apps-28',
|
||||
},
|
||||
...list,
|
||||
)
|
||||
commonOptions.postTagOptions.splice(0, commonOptions.postTagOptions.length, ...res.data || [])
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '初始化失败,请刷新页面后重试')
|
||||
|
||||
@@ -44,7 +44,7 @@ function handleClickEmoji(emoji: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover trigger="click" position="tl">
|
||||
<el-popover trigger="click" placement="top-start">
|
||||
<SvgIcon icon="majesticons:emoji-happy-line" class="chat-window-bottom-icon" />
|
||||
<template #content>
|
||||
<div class="emoji-container">
|
||||
|
||||
@@ -56,7 +56,7 @@ function onSearch() {
|
||||
0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
.query-item {
|
||||
flex: 1;
|
||||
border-right: 1px solid #ddd;
|
||||
// border-right: 1px solid #ddd;
|
||||
:deep(.el-select__wrapper) {
|
||||
height: 60px;
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = defineProps<{
|
||||
url?: string
|
||||
size?: number
|
||||
width?: number
|
||||
height?: number
|
||||
round?: boolean
|
||||
}>()
|
||||
|
||||
const fallback = '/no-avatar.png'
|
||||
const currentSrc = ref(props.url?.trim() || fallback)
|
||||
|
||||
// 监听 url 变化
|
||||
watch(
|
||||
() => props.url,
|
||||
(newUrl) => {
|
||||
currentSrc.value = newUrl?.trim() || fallback
|
||||
},
|
||||
)
|
||||
|
||||
// 加载失败兜底
|
||||
function onError() {
|
||||
currentSrc.value = fallback
|
||||
}
|
||||
|
||||
// 尺寸样式
|
||||
const sizeStyle = computed(() => {
|
||||
// width / height 优先
|
||||
if (props.width || props.height) {
|
||||
return {
|
||||
width: props.width ? `${props.width}px` : undefined,
|
||||
height: props.height ? `${props.height}px` : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
// size(正方形)
|
||||
if (typeof props.size === 'number') {
|
||||
return {
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
v-bind="$attrs"
|
||||
:src="currentSrc"
|
||||
alt="图片"
|
||||
class="base-img"
|
||||
:class="{ 'is-round': round }"
|
||||
:style="sizeStyle"
|
||||
@error="onError"
|
||||
>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.base-img {
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 圆形 */
|
||||
.is-round {
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
User,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import SafeImage from '@/components/SafeImage.vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -23,7 +24,7 @@ function navigateTo(path: string) {
|
||||
}
|
||||
|
||||
function exitLogin() {
|
||||
userStore.clearUserInfo()
|
||||
userStore.clear()
|
||||
|
||||
router.push('/auth/login')
|
||||
}
|
||||
@@ -63,10 +64,10 @@ function isActive(path: string) {
|
||||
|
||||
<el-dropdown>
|
||||
<div class="user">
|
||||
<el-avatar :size="36" :src="userStore.userInfo.company.logo ? userStore.userInfo.company.logo : '/no-avatar.png'" />
|
||||
<SafeImage :url="userStore.user.avatar || userStore.company.logo" :size="36" round />
|
||||
<div class="info">
|
||||
<span class="name">{{ userStore.userInfo.user.name }}</span>
|
||||
<span class="company">{{ userStore.userInfo.company.name }}</span>
|
||||
<span class="name">{{ userStore.user.name }}</span>
|
||||
<span class="company">{{ userStore.company.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -27,5 +27,5 @@ export function listPostCommentApi(id: number) {
|
||||
}
|
||||
|
||||
export function deletePostCommentApi(id: number) {
|
||||
return httpGet<void>(`/post/comment/delete/${id}`)
|
||||
return httpPostJson<void>(`/post/comment/delete/${id}`)
|
||||
}
|
||||
@@ -50,15 +50,15 @@ axiosInstance.interceptors.request.use(
|
||||
|
||||
if (needAuth) {
|
||||
if (!userStore.isLogined) {
|
||||
userStore.clearUserInfo()
|
||||
userStore.clear()
|
||||
router.push('/login')
|
||||
return Promise.reject(new Error('尚未登录,请先登录!'))
|
||||
}
|
||||
}
|
||||
|
||||
config.headers.Authorization = `Bearer ${userStore.userInfo.token}`
|
||||
config.headers['X-Company-Id'] = userStore.userInfo.company.id
|
||||
config.headers['X-User-Id'] = userStore.userInfo.user.id
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`
|
||||
config.headers['X-Company-Id'] = userStore.company.id
|
||||
config.headers['X-User-Id'] = userStore.user.id
|
||||
|
||||
return config
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: '/company/detail',
|
||||
component: () => import('@/views/company/detail/index.vue'),
|
||||
component: () => import('@/views/company/detail.vue'),
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import type { Area, CompanyQuery, Industry, PostTag } from '@/types/base'
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
export const useCommonStore = defineStore(
|
||||
'common',
|
||||
() => {
|
||||
const areaOptions = reactive<Area[]>([])
|
||||
const industryOptions = reactive<Industry[]>([])
|
||||
const postTagOptions = reactive<PostTag[]>([])
|
||||
export const useCommonStore = defineStore('common', () => {
|
||||
const areaOptions = reactive<Area[]>([])
|
||||
const industryOptions = reactive<Industry[]>([])
|
||||
const postTagOptions = reactive<PostTag[]>([]) // 原始列表,不含“全部”
|
||||
|
||||
const companyQuery = ref<CompanyQuery>({
|
||||
industries: [],
|
||||
areas: [],
|
||||
name: '',
|
||||
})
|
||||
const companyQuery = ref<CompanyQuery>({
|
||||
industries: [],
|
||||
areas: [],
|
||||
name: '',
|
||||
})
|
||||
|
||||
return {
|
||||
areaOptions,
|
||||
industryOptions,
|
||||
companyQuery,
|
||||
postTagOptions,
|
||||
}
|
||||
},
|
||||
)
|
||||
// 计算属性:带“全部”选项
|
||||
const postTagOptionsWithAll = computed(() => [
|
||||
{ id: 0, name: '全部', icon: 'fluent-color:apps-28' },
|
||||
...postTagOptions,
|
||||
])
|
||||
|
||||
return {
|
||||
areaOptions,
|
||||
industryOptions,
|
||||
companyQuery,
|
||||
postTagOptions, // 原始数据
|
||||
postTagOptionsWithAll, // 带“全部”,响应式
|
||||
}
|
||||
})
|
||||
@@ -1,69 +1,93 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
interface IUserInfo {
|
||||
user: {
|
||||
id: number
|
||||
name: string
|
||||
position: string
|
||||
role: number
|
||||
}
|
||||
company: {
|
||||
id: number
|
||||
logo: string
|
||||
name: string
|
||||
}
|
||||
token: string
|
||||
interface IUser {
|
||||
id: number
|
||||
role: number
|
||||
name: string
|
||||
position: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
interface ICompany {
|
||||
id: number
|
||||
logo: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
'user',
|
||||
() => {
|
||||
const userInfo = ref<IUserInfo>({
|
||||
user: {
|
||||
const user = ref<IUser>({
|
||||
id: 0,
|
||||
role: 0,
|
||||
name: '',
|
||||
position: '',
|
||||
avatar: '',
|
||||
})
|
||||
|
||||
const company = ref<ICompany>({
|
||||
id: 0,
|
||||
logo: '',
|
||||
name: '',
|
||||
})
|
||||
|
||||
const token = ref('')
|
||||
|
||||
// ===== setters =====
|
||||
const setUser = (val: IUser) => {
|
||||
user.value = val
|
||||
}
|
||||
|
||||
const setCompany = (val: ICompany) => {
|
||||
company.value = val
|
||||
}
|
||||
|
||||
const setToken = (val: string) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
// 一次性设置(登录时用)
|
||||
const setLoginInfo = (data: {
|
||||
user: IUser
|
||||
company: ICompany
|
||||
token: string
|
||||
}) => {
|
||||
user.value = data.user
|
||||
company.value = data.company
|
||||
token.value = data.token
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
user.value = {
|
||||
id: 0,
|
||||
role: 0,
|
||||
name: '',
|
||||
position: '',
|
||||
role: 0,
|
||||
},
|
||||
company: {
|
||||
avatar: '',
|
||||
}
|
||||
company.value = {
|
||||
id: 0,
|
||||
logo: '',
|
||||
name: '',
|
||||
},
|
||||
token: '',
|
||||
})
|
||||
|
||||
const setUserInfo = (val: IUserInfo) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const clearUserInfo = () => {
|
||||
userInfo.value = {
|
||||
user: {
|
||||
id: 0,
|
||||
name: '',
|
||||
position: '',
|
||||
role: 0,
|
||||
},
|
||||
company: {
|
||||
id: 0,
|
||||
logo: '',
|
||||
name: '',
|
||||
},
|
||||
token: '',
|
||||
}
|
||||
token.value = ''
|
||||
}
|
||||
|
||||
// ✅ 判断是否登录
|
||||
// ===== computed =====
|
||||
const isLogined = computed(() => {
|
||||
return !!(userInfo.value.token && userInfo.value.user.id && userInfo.value.company.id)
|
||||
return !!(token.value && user.value.id && company.value.id)
|
||||
})
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
setUserInfo,
|
||||
clearUserInfo,
|
||||
user,
|
||||
company,
|
||||
token,
|
||||
setUser,
|
||||
setCompany,
|
||||
setToken,
|
||||
setLoginInfo,
|
||||
clear,
|
||||
isLogined,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,15 +9,18 @@ export interface LoginEmailCodeApiParams {
|
||||
}
|
||||
|
||||
export interface LoginApiResult {
|
||||
companyId: number
|
||||
companyLogo: string
|
||||
companyName: string
|
||||
|
||||
userId: number
|
||||
userRole: number
|
||||
userName: string
|
||||
userPosition: string
|
||||
|
||||
company: {
|
||||
id: number
|
||||
logo: string
|
||||
name: string
|
||||
}
|
||||
user: {
|
||||
id: number
|
||||
role: number
|
||||
name: string
|
||||
position: string
|
||||
avatar: string
|
||||
}
|
||||
token: string
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface CompanyUser {
|
||||
email: string
|
||||
phone: string
|
||||
canuse: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
export interface CompanyUserForm extends CompanyUser {
|
||||
@@ -34,6 +35,8 @@ export interface AddCompanyUserParams {
|
||||
position: string
|
||||
phone: string
|
||||
canuse: string
|
||||
|
||||
avatar: string
|
||||
}
|
||||
export interface UpdateCompanyUserParams {
|
||||
id: number
|
||||
@@ -46,6 +49,8 @@ export interface UpdateCompanyUserParams {
|
||||
position: string
|
||||
phone: string
|
||||
canuse: string
|
||||
|
||||
avatar: string
|
||||
}
|
||||
|
||||
export interface DeleteCompanyUserParams {
|
||||
|
||||
@@ -68,15 +68,15 @@ export interface PostCompany {
|
||||
name: string
|
||||
logo: string
|
||||
profile: string
|
||||
|
||||
establishmentDate: string
|
||||
areas: string[]
|
||||
industries: string[]
|
||||
}
|
||||
|
||||
export interface CreatePostCommentParams {
|
||||
fatherId: number | null
|
||||
postId: number
|
||||
|
||||
rootId: number | null
|
||||
fatherId: number | null
|
||||
content: string
|
||||
}
|
||||
export interface CreatePostCommentResult {
|
||||
@@ -90,6 +90,7 @@ export interface CreatePostCommentResult {
|
||||
|
||||
export interface PostComment {
|
||||
id: number
|
||||
rootId: number
|
||||
fatherId: number | null
|
||||
content: string
|
||||
createTime: string
|
||||
|
||||
@@ -44,3 +44,72 @@ export function formatPostTime(input?: dayjs.ConfigType): string {
|
||||
// 非本年
|
||||
return time.format('YYYY年MM月DD日')
|
||||
}
|
||||
|
||||
export function formatCommentTime(input?: dayjs.ConfigType): string {
|
||||
if (!input)
|
||||
return ''
|
||||
|
||||
const time = dayjs(input)
|
||||
if (!time.isValid())
|
||||
return ''
|
||||
|
||||
const now = dayjs()
|
||||
const diffSeconds = now.diff(time, 'second')
|
||||
|
||||
// 未来时间兜底
|
||||
if (diffSeconds < 0) {
|
||||
return time.format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
// 1 分钟内
|
||||
if (diffSeconds < 60) {
|
||||
return '刚刚'
|
||||
}
|
||||
|
||||
const diffMinutes = now.diff(time, 'minute')
|
||||
if (diffMinutes < 60) {
|
||||
return `${diffMinutes}分钟前`
|
||||
}
|
||||
|
||||
const diffHours = now.diff(time, 'hour')
|
||||
if (diffHours < 24) {
|
||||
return `${diffHours}小时前`
|
||||
}
|
||||
|
||||
const diffDays = now.diff(time, 'day')
|
||||
if (diffDays < 7) {
|
||||
return `${diffDays}天前`
|
||||
}
|
||||
|
||||
// 本年
|
||||
if (time.year() === now.year()) {
|
||||
return time.format('MM-DD HH:mm')
|
||||
}
|
||||
|
||||
// 非本年
|
||||
return time.format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
type DateFormat
|
||||
= | 'YYYY'
|
||||
| 'MM'
|
||||
| 'DD'
|
||||
| 'YYYY-MM'
|
||||
| 'YYYY-MM-DD'
|
||||
| 'YYYY-MM-DD HH:mm'
|
||||
| 'YYYY-MM-DD HH:mm:ss'
|
||||
| 'YYYY年MM月DD日'
|
||||
|
||||
export function formatDate(
|
||||
input?: dayjs.ConfigType,
|
||||
format: DateFormat = 'YYYY-MM-DD',
|
||||
): string {
|
||||
if (!input)
|
||||
return ''
|
||||
|
||||
const date = dayjs(input)
|
||||
if (!date.isValid())
|
||||
return ''
|
||||
|
||||
return date.format(format)
|
||||
}
|
||||
@@ -133,20 +133,7 @@ async function submitForm(formEl: FormInstance | undefined) {
|
||||
if (res.isSuccess) {
|
||||
ElMessage.success('登录成功')
|
||||
|
||||
userStore.setUserInfo({
|
||||
user: {
|
||||
id: res.data.userId,
|
||||
name: res.data.userName,
|
||||
position: res.data.userPosition,
|
||||
role: res.data.userRole,
|
||||
},
|
||||
company: {
|
||||
id: res.data.companyId,
|
||||
logo: res.data.companyLogo,
|
||||
name: res.data.companyName,
|
||||
},
|
||||
token: res.data.token,
|
||||
})
|
||||
userStore.setLoginInfo(res.data)
|
||||
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
+2
-4
@@ -8,6 +8,7 @@ import AnchorMenu from '@/components/anchor-menu/index.vue'
|
||||
|
||||
import WangEditor from '@/components/wang-editor/index.vue'
|
||||
import { getCompanyDetailApi } from '@/request/api/company'
|
||||
import { formatDate } from '@/utils/time'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -110,14 +111,11 @@ function handleUpdateEditorDirectory(menus: AnchorMenuItem[]) {
|
||||
{{ companyDetail.email || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="成立日期">
|
||||
-
|
||||
{{ formatDate(companyDetail.establishmentDate, 'YYYY-MM-DD') || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="区域">
|
||||
{{ companyDetail.areas.join('/') || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="详细地址" :span="2">
|
||||
-
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
@@ -77,19 +77,20 @@ function toDetail(data: Company) {
|
||||
</div>
|
||||
<div class="company-list">
|
||||
<div v-for="item in companyList" :key="item.id" class="company-item">
|
||||
<el-avatar shape="square" :src="item.logo || '/no-avatar.png'" :size="70" class="company-logo" />
|
||||
|
||||
<SafeImage :url="item.logo" :size="70" class="company-logo" />
|
||||
<div class="company-info">
|
||||
<div class="company-header">
|
||||
<div class="company-name">
|
||||
<div class="company-name" @click="toDetail(item)">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
|
||||
<div class="company-tags">
|
||||
<el-tag v-for="tag in item.industries" :key="tag" type="primary" effect="dark">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-tags">
|
||||
<el-tag v-for="tag in item.industries" :key="tag" type="primary" effect="dark">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<p class="company-profile">
|
||||
{{ item.profile || '-' }}
|
||||
</p>
|
||||
@@ -109,14 +110,11 @@ function toDetail(data: Company) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-actions">
|
||||
<el-button @click="toDetail(item)">
|
||||
<i-ri:information-2-line />
|
||||
<span class="button-text">
|
||||
详细
|
||||
</span>
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
bg
|
||||
>
|
||||
<i-ri:message-3-fill />
|
||||
<span class="button-text">
|
||||
发起聊天
|
||||
@@ -219,7 +217,7 @@ function toDetail(data: Company) {
|
||||
|
||||
.company-tags {
|
||||
margin-bottom: 6px;
|
||||
> * {
|
||||
> *:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
+143
-55
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PostComment, PublicPostDetail } from '@/types/post'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import { createPostCommentApi } from '@/request/api/post'
|
||||
import { createPostCommentApi, deletePostCommentApi } from '@/request/api/post'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatCommentTime } from '@/utils/time'
|
||||
import CommentInput from './comment-input.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -12,10 +13,15 @@ const props = defineProps<{
|
||||
postDetail: PublicPostDetail
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', val: PostComment[]): void
|
||||
}>()
|
||||
|
||||
const commentList = ref<PostComment[]>([])
|
||||
const userStore = useUserStore()
|
||||
|
||||
const replyCommentId = ref<number | null>(0)
|
||||
const replyComment = ref<PostComment | null>(null)
|
||||
|
||||
const replyCommentLoading = ref<boolean>(false)
|
||||
|
||||
watch(
|
||||
@@ -29,44 +35,65 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
function handleReplyComment(commentId: number) {
|
||||
if (replyCommentId.value === commentId) {
|
||||
replyCommentId.value = null
|
||||
function handleReplyComment(comment: PostComment, e: MouseEvent) {
|
||||
if (replyComment.value?.id === comment.id) {
|
||||
replyComment.value = null
|
||||
}
|
||||
else {
|
||||
replyCommentId.value = commentId
|
||||
replyComment.value = comment
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
window.scrollTo({
|
||||
top: document.documentElement.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
const el = e.currentTarget as HTMLElement
|
||||
if (!el)
|
||||
return
|
||||
|
||||
const rect = el.getBoundingClientRect()
|
||||
const viewportHeight = window.innerHeight
|
||||
const scrollOffset = 70
|
||||
|
||||
// 点击点在视口上半部分 → 往上滚
|
||||
if (rect.top < viewportHeight / 2) {
|
||||
window.scrollBy({
|
||||
top: -scrollOffset,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
// 点击点在视口下半部分 → 往下滚
|
||||
else {
|
||||
window.scrollBy({
|
||||
top: scrollOffset,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function findRootComment(commentList: PostComment[], commentId: number | null): PostComment | undefined {
|
||||
// 使用 for 循环遍历一级评论列表
|
||||
function findRootCommentByRootId(commentList: PostComment[], rootId: number | undefined): PostComment | undefined {
|
||||
if (!rootId)
|
||||
return undefined
|
||||
|
||||
// 遍历根评论列表
|
||||
for (const item of commentList) {
|
||||
// 如果找到目标评论,直接返回该评论
|
||||
if (item.id === commentId) {
|
||||
// 根评论的 id 或 rootId 与目标匹配
|
||||
if (item.id === rootId || item.rootId === rootId) {
|
||||
return item
|
||||
}
|
||||
|
||||
// 如果该评论有子评论,检查子评论
|
||||
if (item.children) {
|
||||
const childComment = item.children.find(child => child.id === commentId)
|
||||
if (childComment) {
|
||||
return item
|
||||
}
|
||||
// 子评论中如果存在目标 rootId,则返回对应根评论
|
||||
if (item.children && item.children.some(child => child.rootId === rootId)) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到对应评论,返回 undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
function handleReplyCommentSubmit(content: string) {
|
||||
if (replyComment.value === null) {
|
||||
ElMessage.error('请选中需要回复的评论')
|
||||
return
|
||||
}
|
||||
if (content === '') {
|
||||
ElMessage.error('评论内容不能为空!')
|
||||
return
|
||||
@@ -75,16 +102,17 @@ function handleReplyCommentSubmit(content: string) {
|
||||
replyCommentLoading.value = true
|
||||
|
||||
createPostCommentApi({
|
||||
fatherId: replyCommentId.value,
|
||||
rootId: replyComment.value.rootId,
|
||||
fatherId: replyComment.value.id,
|
||||
postId: props.postDetail.id,
|
||||
content,
|
||||
})
|
||||
.then((res) => {
|
||||
// 显示成功消息
|
||||
ElMessage.success(res.message)
|
||||
ElMessage.success('评论成功')
|
||||
|
||||
// 查找根评论
|
||||
const rootComment = findRootComment(commentList.value, replyCommentId.value)
|
||||
const rootComment = findRootCommentByRootId(commentList.value, replyComment.value?.rootId)
|
||||
|
||||
// 如果根评论存在,并且根评论没有子评论,初始化子评论数组
|
||||
if (rootComment) {
|
||||
@@ -94,16 +122,17 @@ function handleReplyCommentSubmit(content: string) {
|
||||
|
||||
// 将新回复添加到子评论中
|
||||
rootComment.children.push({
|
||||
rootId: rootComment.id,
|
||||
id: res.data.id,
|
||||
fatherId: res.data.fatherId,
|
||||
content: res.data.content,
|
||||
createTime: res.data.createTime,
|
||||
children: [],
|
||||
publishUser: {
|
||||
id: userStore.userInfo.user.id,
|
||||
companyId: userStore.userInfo.company.id,
|
||||
name: userStore.userInfo.user.name,
|
||||
avatar: userStore.userInfo.company.logo,
|
||||
id: userStore.user.id,
|
||||
companyId: userStore.company.id,
|
||||
name: userStore.user.name,
|
||||
avatar: userStore.company.logo,
|
||||
},
|
||||
replyUser: {
|
||||
id: rootComment.publishUser.id,
|
||||
@@ -115,33 +144,68 @@ function handleReplyCommentSubmit(content: string) {
|
||||
}
|
||||
else {
|
||||
commentList.value.push({
|
||||
rootId: res.data.id,
|
||||
id: res.data.id,
|
||||
fatherId: res.data.fatherId,
|
||||
content: res.data.content,
|
||||
createTime: res.data.createTime,
|
||||
children: [],
|
||||
publishUser: {
|
||||
id: userStore.userInfo.user.id,
|
||||
companyId: userStore.userInfo.company.id,
|
||||
name: userStore.userInfo.user.name,
|
||||
avatar: userStore.userInfo.company.logo,
|
||||
id: userStore.user.id,
|
||||
companyId: userStore.company.id,
|
||||
name: userStore.user.name,
|
||||
avatar: userStore.company.logo,
|
||||
},
|
||||
replyUser: null,
|
||||
})
|
||||
}
|
||||
|
||||
emits('update:modelValue', commentList.value)
|
||||
})
|
||||
|
||||
.finally(() => {
|
||||
replyCommentId.value = null
|
||||
replyComment.value = null
|
||||
replyCommentLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function removeCommentById(list: PostComment[], id: number): PostComment[] {
|
||||
return list
|
||||
.filter(item => item.id !== id)
|
||||
.map(item => ({
|
||||
...item,
|
||||
children: item.children ? removeCommentById(item.children, id) : [],
|
||||
}))
|
||||
}
|
||||
|
||||
function handleDeleteSelfComment(data: PostComment) {
|
||||
ElMessageBox.confirm('确定删除该条评论么?', '提示')
|
||||
.then(() => {
|
||||
deletePostCommentApi(data.id)
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
commentList.value = removeCommentById(commentList.value, data.id)
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '删除失败,请稍后再试')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
ElMessage.error('删除失败,请稍后再试')
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="item in commentList" :key="item.id" class="custom-comment">
|
||||
<div class="custom-comment-left">
|
||||
<el-avatar class="custom-comment-avatar" :src="item.publishUser.avatar || '/no-avatar.png'" :size="70" />
|
||||
<SafeImage :url="item.publishUser.avatar" :size="32" round />
|
||||
</div>
|
||||
<div class="custom-comment-right">
|
||||
<div class="custom-comment-meta">
|
||||
@@ -149,33 +213,42 @@ function handleReplyCommentSubmit(content: string) {
|
||||
<div class="custom-comment-author">
|
||||
{{ item.publishUser.name }}
|
||||
</div>
|
||||
<div v-if="item.publishUser.companyId === postDetail.companyId" class="custom-comment-author-tag">
|
||||
<!-- <div v-if="item.publishUser.companyId === postDetail.companyId" class="custom-comment-author-tag">
|
||||
作者
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="custom-comment-time">
|
||||
{{ item.createTime }}
|
||||
{{ formatCommentTime(item.createTime) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-comment-content" v-html="item.content" />
|
||||
<div class="custom-comment-actions">
|
||||
<div class="custom-comment-action" :style="{ color: replyCommentId === item.id ? '#165dff' : '#1d2129' }" @click="handleReplyComment(item.id)">
|
||||
<div class="custom-comment-action" :style="{ color: replyComment?.id === item.id ? '#165dff' : '#8A919F' }" @click="handleReplyComment(item, $event)">
|
||||
<SvgIcon icon="mingcute:message-3-line" />
|
||||
<div>{{ replyCommentId === item.id ? "取消回复" : "回复" }}</div>
|
||||
<div>{{ replyComment?.id === item.id ? "取消回复" : "回复" }}</div>
|
||||
</div>
|
||||
<div class="custom-comment-action flex ml-auto">
|
||||
<SvgIcon icon="akar-icons:more-horizontal-fill" />
|
||||
<div class="custom-comment-action" style="margin-left: auto;">
|
||||
<el-dropdown>
|
||||
<SvgIcon icon="akar-icons:more-horizontal-fill" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="userStore.user.id === item.publishUser.id" @click="handleDeleteSelfComment(item)">
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 父级评论输入框 -->
|
||||
<CommentInput v-if="replyCommentId === item.id" :loading="replyCommentLoading" @submit="handleReplyCommentSubmit" />
|
||||
<CommentInput v-if="replyComment?.id === item.id" :loading="replyCommentLoading" @submit="handleReplyCommentSubmit" />
|
||||
|
||||
<!-- 子评论 -->
|
||||
<div v-if="item.children && item.children.length > 0" class="custom-comment-children">
|
||||
<div v-for="child in item.children" :key="child.id" class="custom-comment">
|
||||
<div class="custom-comment-left">
|
||||
<el-avatar class="custom-comment-avatar" :src="item.publishUser.avatar || '/no-avatar.png'" :size="70" />
|
||||
<SafeImage :url="item.publishUser.avatar" :size="32" round />
|
||||
</div>
|
||||
<div class="custom-comment-right">
|
||||
<div class="custom-comment-meta">
|
||||
@@ -183,33 +256,44 @@ function handleReplyCommentSubmit(content: string) {
|
||||
<div class="custom-comment-author">
|
||||
{{ child.publishUser.name }}
|
||||
</div>
|
||||
<div v-if="child.publishUser.companyId === postDetail.companyId" class="custom-comment-author-tag">
|
||||
<!-- <div v-if="child.publishUser.companyId === postDetail.companyId" class="custom-comment-author-tag">
|
||||
作者
|
||||
</div>
|
||||
</div> -->
|
||||
<template v-if="child.replyUser && child.fatherId !== item.id">
|
||||
<div>回复</div>
|
||||
<div style="margin: 0px 8px;">
|
||||
回复
|
||||
</div>
|
||||
<div class="custom-comment-author">
|
||||
{{ child.replyUser.name }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="custom-comment-time">
|
||||
{{ child.createTime }}
|
||||
{{ formatCommentTime(item.createTime) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-comment-content">
|
||||
{{ child.content }}
|
||||
</div>
|
||||
<div class="custom-comment-actions">
|
||||
<div class="custom-comment-action" :style="{ color: replyCommentId === child.id ? '#165dff' : '#1d2129' }" @click="handleReplyComment(child.id)">
|
||||
<div class="custom-comment-action" :style="{ color: replyComment?.id === child.id ? '#165dff' : '#8A919F' }" @click="handleReplyComment(child, $event)">
|
||||
<SvgIcon icon="mingcute:message-3-line" />
|
||||
<div>{{ replyCommentId === child.id ? "取消回复" : "回复" }}</div>
|
||||
<div>{{ replyComment?.id === child.id ? "取消回复" : "回复" }}</div>
|
||||
</div>
|
||||
<div class="custom-comment-action flex ml-auto">
|
||||
<SvgIcon icon="akar-icons:more-horizontal-fill" />
|
||||
<div class="custom-comment-action" style="margin-left: auto;">
|
||||
<el-dropdown>
|
||||
<SvgIcon icon="akar-icons:more-horizontal-fill" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="userStore.user.id === child.publishUser.id" @click="handleDeleteSelfComment(child)">
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<CommentInput v-if="replyCommentId === child.id" :loading="replyCommentLoading" @submit="handleReplyCommentSubmit" />
|
||||
<CommentInput v-if="replyComment?.id === child.id" :loading="replyCommentLoading" @submit="handleReplyCommentSubmit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,11 +339,11 @@ function handleReplyCommentSubmit(content: string) {
|
||||
color: #4e5969;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.custom-comment-author {
|
||||
cursor: pointer;
|
||||
color: #515767;
|
||||
}
|
||||
.custom-comment-author-tag {
|
||||
background-color: #165dff;
|
||||
@@ -275,13 +359,14 @@ function handleReplyCommentSubmit(content: string) {
|
||||
}
|
||||
.custom-comment-content {
|
||||
font-size: 14px;
|
||||
color: #1d2129;
|
||||
color: #252933;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.custom-comment-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
color: #8a919f;
|
||||
.custom-comment-action {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
@@ -290,6 +375,9 @@ function handleReplyCommentSubmit(content: string) {
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
color: #1d2129;
|
||||
svg {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
.custom-comment-action:hover {
|
||||
color: #165dff;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
import type { AnchorMenuItem } from '@/components/anchor-menu/type'
|
||||
import type { PostComment, PostCompany, PublicPostDetail } from '@/types/post'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import AnchorMenu from '@/components/anchor-menu/index.vue'
|
||||
import CustomEmpty from '@/components/CustomEmpty.vue'
|
||||
import WandEditor from '@/components/wang-editor/index.vue'
|
||||
import { createPostCommentApi, getPostCompanyById, getPublicPostDetailApi, listPostCommentApi } from '@/request/api/post'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatPostTime } from '@/utils/time'
|
||||
import { formatDate, formatPostTime } from '@/utils/time'
|
||||
import CommentInput from './components/comment-input.vue'
|
||||
import CommentList from './components/comment-list.vue'
|
||||
|
||||
@@ -25,6 +25,7 @@ const postCompany = ref<PostCompany>({
|
||||
name: '',
|
||||
logo: '',
|
||||
profile: '',
|
||||
establishmentDate: '',
|
||||
areas: [],
|
||||
industries: [],
|
||||
})
|
||||
@@ -49,6 +50,10 @@ const anchorMenu = ref<AnchorMenuItem[]>([])
|
||||
const rootComment = ref<string>('')
|
||||
const rootCommentLoading = ref<boolean>(false)
|
||||
|
||||
const totalCommentCount = computed(() => {
|
||||
return getTotalCommentCount(postComments.value)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (!id) {
|
||||
router.push('/post')
|
||||
@@ -102,6 +107,7 @@ function handleSubmitRootComment(content: string) {
|
||||
|
||||
createPostCommentApi({
|
||||
fatherId: null,
|
||||
rootId: null,
|
||||
postId: postDetail.value.id,
|
||||
content,
|
||||
})
|
||||
@@ -111,16 +117,17 @@ function handleSubmitRootComment(content: string) {
|
||||
ElMessage.success(res.message)
|
||||
|
||||
postComments.value.push({
|
||||
rootId: res.data.id,
|
||||
id: res.data.id,
|
||||
fatherId: res.data.fatherId,
|
||||
content: res.data.content,
|
||||
createTime: res.data.createTime,
|
||||
children: [],
|
||||
publishUser: {
|
||||
id: userStore.userInfo.user.id,
|
||||
companyId: userStore.userInfo.company.id,
|
||||
name: userStore.userInfo.user.name,
|
||||
avatar: userStore.userInfo.company.logo,
|
||||
id: userStore.user.id,
|
||||
companyId: userStore.company.id,
|
||||
name: userStore.user.name,
|
||||
avatar: userStore.company.logo,
|
||||
},
|
||||
replyUser: null,
|
||||
})
|
||||
@@ -133,90 +140,109 @@ function handleSubmitRootComment(content: string) {
|
||||
rootCommentLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getTotalCommentCount(list: PostComment[]): number {
|
||||
let count = 0
|
||||
|
||||
for (const comment of list) {
|
||||
count += 1 // 一级评论
|
||||
if (comment.children && comment.children.length > 0) {
|
||||
count += comment.children.length // 所有子评论(已扁平)
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="post">
|
||||
<div class="post-left">
|
||||
<el-affix :offset="90">
|
||||
<el-anchor type="underline">
|
||||
<AnchorMenu :menu="anchorMenu" />
|
||||
</el-anchor>
|
||||
</el-affix>
|
||||
</div>
|
||||
<CustomLoading v-if="loading" />
|
||||
|
||||
<div class="post-center">
|
||||
<div class="post-main">
|
||||
<div class="post-title">
|
||||
{{ postDetail.title }}
|
||||
<template v-else>
|
||||
<div class="post-left">
|
||||
<el-affix :offset="90">
|
||||
<el-anchor>
|
||||
<AnchorMenu :menu="anchorMenu" />
|
||||
</el-anchor>
|
||||
</el-affix>
|
||||
</div>
|
||||
|
||||
<div class="post-center">
|
||||
<div class="post-main">
|
||||
<div class="post-title">
|
||||
{{ postDetail.title }}
|
||||
</div>
|
||||
<div class="post-meta">
|
||||
<div class="post-meta-item">
|
||||
<SvgIcon icon="mdi:alarm" :size="18" />
|
||||
<span>{{ formatPostTime(postDetail.activeTime) }} </span>
|
||||
</div>
|
||||
<div class="post-meta-item">
|
||||
<SvgIcon icon="mdi:eye-outline" :size="18" />
|
||||
<span>{{ postDetail.viewCount }} </span>
|
||||
</div>
|
||||
<div class="post-meta-item">
|
||||
<SvgIcon icon="mdi:comment-outline" :size="16" />
|
||||
<span>{{ totalCommentCount }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="post-meta">
|
||||
<div class="post-meta-item">
|
||||
<SvgIcon icon="mdi:alarm" :size="18" />
|
||||
<span>{{ formatPostTime(postDetail.activeTime) }} </span>
|
||||
|
||||
<div class="post-content">
|
||||
<WandEditor v-model="postDetail.content" mode="view" @update:directory="handleUpdateEditorDirectory" />
|
||||
</div>
|
||||
|
||||
<div class="post-comment">
|
||||
<div class="divider" />
|
||||
|
||||
<div class="post-comment-title">
|
||||
评论
|
||||
</div>
|
||||
<div class="post-meta-item">
|
||||
<SvgIcon icon="mdi:eye-outline" :size="18" />
|
||||
<span>{{ postDetail.viewCount }} </span>
|
||||
|
||||
<div class="primary-comment">
|
||||
<el-avatar class="post-comment-avatar" :src="userStore.company.logo || '/no-avatar.png'" :size="46" />
|
||||
|
||||
<CommentInput v-model="rootComment" :loading="rootCommentLoading" class="post-comment-input" @submit="handleSubmitRootComment" />
|
||||
</div>
|
||||
<div class="post-meta-item">
|
||||
<SvgIcon icon="mdi:comment-outline" :size="16" />
|
||||
<span>{{ postDetail.commentCount }} </span>
|
||||
|
||||
<div class="reply-comment">
|
||||
<div v-if="postComments.length > 0" class="reply-comment-list">
|
||||
<CommentList v-model="postComments" :post-detail="postDetail" />
|
||||
</div>
|
||||
<CustomEmpty v-else title="暂时没有评论 🤔" :in-container="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-content">
|
||||
<WandEditor v-model="postDetail.content" mode="view" @update:directory="handleUpdateEditorDirectory" />
|
||||
</div>
|
||||
<div class="post-right">
|
||||
<el-affix :offset="70">
|
||||
<div class="post-company">
|
||||
<el-avatar :src="postCompany.logo || '/no-avatar.png'" :size="70" class="company-logo" />
|
||||
<div class="company-info">
|
||||
<h3 class="company-name">
|
||||
{{ postCompany.name }}
|
||||
</h3>
|
||||
|
||||
<div class="post-comment">
|
||||
<div class="divider" />
|
||||
<div v-if="postCompany.areas.length > 0" class="company-meta">
|
||||
<template v-if="postCompany.establishmentDate">
|
||||
成立于 {{ formatDate(postCompany.establishmentDate, 'YYYY') }} 年 ·
|
||||
</template>
|
||||
{{ postCompany.areas.join('/') }}
|
||||
</div>
|
||||
|
||||
<div class="post-comment-title">
|
||||
评论
|
||||
</div>
|
||||
<div class="company-desc">
|
||||
{{ postCompany.profile }}
|
||||
</div>
|
||||
|
||||
<div class="primary-comment">
|
||||
<el-avatar class="post-comment-avatar" :src="userStore.userInfo.company.logo || '/no-avatar.png'" :size="46" />
|
||||
|
||||
<CommentInput v-model="rootComment" :loading="rootCommentLoading" class="post-comment-input" @submit="handleSubmitRootComment" />
|
||||
</div>
|
||||
|
||||
<div class="reply-comment">
|
||||
<div v-if="postComments.length > 0" class="reply-comment-list">
|
||||
<CommentList v-model="postComments" :post-detail="postDetail" />
|
||||
</div>
|
||||
<CustomEmpty v-else title="暂时没有评论 🤔" :in-container="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-right">
|
||||
<el-affix :offset="70">
|
||||
<div class="post-company">
|
||||
<el-avatar :src="postCompany.logo || '/no-avatar.png'" :size="70" class="company-logo" />
|
||||
<div class="company-info">
|
||||
<h3 class="company-name">
|
||||
{{ postCompany.name }}
|
||||
</h3>
|
||||
|
||||
<div v-if="postCompany.areas.length > 0" class="company-meta">
|
||||
<!-- 成立于 2012 年 · 上海 -->
|
||||
{{ postCompany.areas.join("/") }}
|
||||
</div>
|
||||
|
||||
<div class="company-desc">
|
||||
{{ postCompany.profile }}
|
||||
</div>
|
||||
|
||||
<div class="company-tags">
|
||||
<span v-for="industry in postCompany.industries" :key="industry" class="tag">{{ industry }}</span>
|
||||
<div class="company-tags">
|
||||
<span v-for="industry in postCompany.industries" :key="industry" class="tag">{{ industry }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-affix>
|
||||
</div>
|
||||
</el-affix>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -224,7 +250,6 @@ function handleSubmitRootComment(content: string) {
|
||||
.post {
|
||||
padding: 24px 100px;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
.post-left {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
@@ -238,13 +263,16 @@ function handleSubmitRootComment(content: string) {
|
||||
}
|
||||
|
||||
.post-center {
|
||||
background-color: #fff;
|
||||
min-width: 600px;
|
||||
flex: 1;
|
||||
margin: 0px 32px;
|
||||
|
||||
.post-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
padding: 0px 16px 12px 16px;
|
||||
padding: 12px 16px 12px 16px;
|
||||
width: 100%;
|
||||
|
||||
.post-title {
|
||||
@@ -329,12 +357,14 @@ function handleSubmitRootComment(content: string) {
|
||||
flex-shrink: 0;
|
||||
|
||||
.post-company {
|
||||
background-color: #fff;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
margin: 0 auto;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
.company-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useCommonStore } from '@/store/common'
|
||||
import { formatPostTime } from '@/utils/time'
|
||||
|
||||
const router = useRouter()
|
||||
const { postTagOptions } = useCommonStore()
|
||||
const { postTagOptionsWithAll } = useCommonStore()
|
||||
|
||||
const activeNames = ref(['1'])
|
||||
const selectedTag = ref<PostTag | null>(null)
|
||||
@@ -39,7 +39,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
watch(
|
||||
postTagOptions,
|
||||
postTagOptionsWithAll,
|
||||
(list) => {
|
||||
if (list.length && !selectedTag.value) {
|
||||
selectedTag.value = list[0]
|
||||
@@ -107,7 +107,7 @@ function toDetail(data: PublicPost) {
|
||||
<el-collapse-item title="标签" name="1">
|
||||
<ul class="tag-list">
|
||||
<li
|
||||
v-for="item in postTagOptions"
|
||||
v-for="item in postTagOptionsWithAll"
|
||||
:key="item.id"
|
||||
class="tag-item"
|
||||
:class="{ active: selectedTag && (selectedTag.id === item.id) }"
|
||||
|
||||
@@ -332,9 +332,9 @@ function handleToEdit(row?: Post) {
|
||||
<div>{{ item.updateTime }}</div>
|
||||
<div class="post-meta-divider" />
|
||||
<div>{{ item.viewCount }} 浏览</div>
|
||||
<div>{{ item.likeCount }} 点赞</div>
|
||||
<!-- <div>{{ item.likeCount }} 点赞</div> -->
|
||||
<div>{{ item.commentCount }} 评论</div>
|
||||
<div>{{ item.favoriteCount }} 收藏</div>
|
||||
<!-- <div>{{ item.favoriteCount }} 收藏</div> -->
|
||||
</div>
|
||||
<div v-if="item.tags && item.tags.length > 0" class="post-tags">
|
||||
<div v-for="tag in item.tags" :key="tag" class="post-tag">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NavMenuItem } from '@/components/nav-menu/type'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import NavMenu from '@/components/nav-menu/index.vue'
|
||||
@@ -195,6 +194,7 @@ watch(searchKey, async (val) => {
|
||||
border-radius: 0px;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 0px 160px;
|
||||
// border-right: 1px solid rgb(232.9, 232.9, 232.9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
function getCompanyDetail() {
|
||||
getCompanyDetailApi(userStore.userInfo.company.id).then((res) => {
|
||||
getCompanyDetailApi(userStore.company.id).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
companyDetail.value = res.data
|
||||
}
|
||||
@@ -124,8 +124,8 @@ async function handleSubmit(formEl: FormInstance | undefined) {
|
||||
logo: companyDetail.value.logo,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
userStore.userInfo.company.name = companyDetail.value.name
|
||||
userStore.userInfo.company.logo = companyDetail.value.logo
|
||||
userStore.company.name = companyDetail.value.name
|
||||
userStore.company.logo = companyDetail.value.logo
|
||||
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
@@ -155,6 +155,7 @@ const activeCollapseNames = ref(['1', '2', '3'])
|
||||
:rules="formRules"
|
||||
label-width="auto"
|
||||
label-position="top"
|
||||
style="width: 700px;"
|
||||
>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
@@ -214,6 +215,7 @@ const activeCollapseNames = ref(['1', '2', '3'])
|
||||
:model="companyDetail"
|
||||
:rules="formRules"
|
||||
label-width="auto"
|
||||
style="width: 700px;"
|
||||
>
|
||||
<el-form-item prop="profile">
|
||||
<el-input v-model="companyDetail.profile" type="textarea" />
|
||||
@@ -284,9 +286,6 @@ const activeCollapseNames = ref(['1', '2', '3'])
|
||||
}
|
||||
.form-footer {
|
||||
width: 100%;
|
||||
box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.08);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
align-self: flex-end;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
|
||||
+44
-3
@@ -2,6 +2,7 @@
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import type { PropType } from 'vue'
|
||||
import type { CompanyUser, CompanyUserForm } from '@/types/company/user'
|
||||
import type { UploadImageResponse } from '@/utils/file'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import EmailCodeInput from '@/components/EmailCodeInput.vue'
|
||||
@@ -9,6 +10,7 @@ import { authCodeVerifyApi } from '@/request/api/auth/code'
|
||||
import { addCompanyUserApi, updateCompanyUserApi } from '@/request/api/company/user'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
|
||||
import { uploadToAliyun } from '@/utils/file'
|
||||
import { validatePhone } from '@/utils/validate'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -20,6 +22,8 @@ const props = defineProps({
|
||||
|
||||
const emits = defineEmits(['customClose', 'dataChange'])
|
||||
|
||||
const imageUpload = ref<UploadImageResponse | null>(null)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const roleOptions = [
|
||||
{
|
||||
@@ -41,15 +45,28 @@ const form = ref<CompanyUserForm>({
|
||||
position: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
avatar: userStore.company.logo,
|
||||
canuse: '1',
|
||||
password: '',
|
||||
emailCode: '',
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.user,
|
||||
(user) => {
|
||||
if (!user)
|
||||
imageUpload.value = null
|
||||
if (!user) {
|
||||
form.value.id = 0
|
||||
form.value.role = 1
|
||||
form.value.name = ''
|
||||
form.value.position = ''
|
||||
form.value.email = ''
|
||||
form.value.phone = ''
|
||||
form.value.avatar = userStore.company.logo
|
||||
form.value.canuse = '1'
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
form.value = { ...user, password: '', emailCode: '' }
|
||||
originEmail.value = user.email
|
||||
@@ -161,17 +178,31 @@ async function handleSubmit(formEl: FormInstance | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查图片上传
|
||||
if (imageUpload.value) {
|
||||
uploadToAliyun(imageUpload.value.blob, imageUpload.value.name, imageUpload.value.size).then((res) => {
|
||||
form.value.avatar = res.url
|
||||
})
|
||||
}
|
||||
|
||||
// 提交用户数据
|
||||
const userApi = isUpdate.value ? updateCompanyUserApi : addCompanyUserApi
|
||||
|
||||
let avatar = form.value.avatar
|
||||
if (!avatar) {
|
||||
avatar = userStore.company.logo || ''
|
||||
}
|
||||
|
||||
const response = await userApi({
|
||||
id: form.value.id,
|
||||
companyId: userStore.userInfo.company.id,
|
||||
companyId: userStore.company.id,
|
||||
email: form.value.email,
|
||||
password: form.value.password,
|
||||
name: form.value.name,
|
||||
position: form.value.position,
|
||||
phone: form.value.phone,
|
||||
canuse: form.value.canuse,
|
||||
avatar,
|
||||
})
|
||||
|
||||
if (response.isSuccess) {
|
||||
@@ -200,7 +231,7 @@ async function handleSubmit(formEl: FormInstance | undefined) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer v-bind="$attrs" title="成员编辑" resizable>
|
||||
<el-drawer v-bind="$attrs" title="成员编辑" resizable destroy-on-close>
|
||||
<el-alert
|
||||
title="提示"
|
||||
style="margin-bottom: 24px;"
|
||||
@@ -220,6 +251,16 @@ async function handleSubmit(formEl: FormInstance | undefined) {
|
||||
label-width="auto"
|
||||
label-position="right"
|
||||
>
|
||||
<el-form-item label="头像" prop="avatar">
|
||||
<ImageUploadCard
|
||||
v-model="form.avatar"
|
||||
@change="(val) => {
|
||||
imageUpload = val
|
||||
|
||||
form.avatar = val?.url || ''
|
||||
}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
|
||||
@@ -24,7 +24,7 @@ const queryForm = reactive<QueryCompanyUserParams>({
|
||||
currentPage: page.current,
|
||||
pageSize: page.pageSize,
|
||||
|
||||
companyId: userStore.userInfo.company.id,
|
||||
companyId: userStore.company.id,
|
||||
name: '',
|
||||
phone: '',
|
||||
position: '',
|
||||
@@ -142,7 +142,7 @@ function handleDelteRows(val?: CompanyUser) {
|
||||
},
|
||||
).then(() => {
|
||||
deleteCompanyUserApi({
|
||||
companyId: userStore.userInfo.company.id,
|
||||
companyId: userStore.company.id,
|
||||
ids: deleteIds,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
@@ -251,6 +251,13 @@ function handleDelteRows(val?: CompanyUser) {
|
||||
align="center"
|
||||
:selectable="selectable"
|
||||
/>
|
||||
<el-table-column prop="avatar" label="头像" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<div style="display: flex;align-items: center;justify-content: center;">
|
||||
<SafeImage :url="scope.row.avatar" :size="48" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" align="center">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.role === 1">
|
||||
|
||||
Reference in new issue
Block a user