提交 fbd7ec53 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 848469dd
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --mode dev", "dev": "vite --mode dev",
"build": "vue-tsc --noEmit --watch && vite build --mode prod && npm run deploy", "build": "npm run typecheck && vite build --mode prod && npm run deploy",
"build:test": "vue-tsc --noEmit && vite build --mode test", "build:test": "npm run typecheck && vite build --mode test",
"build:pre": "vue-tsc --noEmit && vite build --mode pre", "build:pre": "npm run typecheck && vite build --mode pre",
"preview": "vite preview --port 5050", "preview": "vite preview --port 5050",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
......
<script setup lang="ts">
const props = defineProps<{ data: any }>()
const files = computed<{ name: string; url: string }[]>(() => {
try {
return JSON.parse(props.data.file)
} catch (error) {
return []
}
})
</script>
<template>
<AppContainer backgroundColor="#fff" headerAlign="center" v-if="data" class="doc">
<div class="doc-hd">
<h1>{{ data.title }}</h1>
<p>
<span>{{ data.created_time }}</span>
<span>{{ data.pv }}观看</span>
</p>
</div>
<div class="doc-bd">
<article v-html="data.desc"></article>
</div>
<ul class="doc-files" v-if="files.length">
<li v-for="(item, index) in files" :key="index">
<a :href="item.url" :download="item.name" target="_blank"><van-icon name="link-o" />{{ item.name }}</a>
</li>
</ul>
</AppContainer>
</template>
<style lang="scss" scoped>
.doc-hd {
h1 {
font-size: 0.32rem;
font-weight: 500;
line-height: 0.45rem;
color: #333333;
}
p {
margin-top: 0.15rem;
font-size: 0.24rem;
font-weight: 400;
color: #b2b2b2;
span + span {
margin-left: 0.5rem;
}
}
}
.doc-bd {
padding: 0.5rem 0;
font-size: 0.24rem;
font-weight: 400;
color: #666666;
line-height: 0.42rem;
:deep(img) {
max-width: 100%;
}
}
.doc-files {
border-top: 0.01rem solid #f5f5f5;
padding-top: 0.1rem;
li {
line-height: 0.6rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.van-icon-link-o {
margin-right: 0.1rem;
}
}
</style>
...@@ -11,16 +11,29 @@ import { ImagePreview } from 'vant' ...@@ -11,16 +11,29 @@ import { ImagePreview } from 'vant'
const props = defineProps<{ data: any }>() const props = defineProps<{ data: any }>()
const emit = defineEmits(['submitComment', 'load']) const emit = defineEmits(['submitComment', 'load'])
const imageList = computed<string[]>(() => { const imageList = $computed<string[]>(() => {
try { try {
return JSON.parse(props.data.picture) const picture = JSON.parse(props.data.picture)
return picture.map((item: string | { name: string; url: string }) => {
if (typeof item === 'string') {
return item
}
return item.url
})
} catch (error) {
return []
}
})
const fileList = $computed<{ name: string; url: string }[]>(() => {
try {
return JSON.parse(props.data.file)
} catch (error) { } catch (error) {
return [] return []
} }
}) })
// 图片预览 // 图片预览
const onImagePreview = (index: number) => { const onImagePreview = (index: number) => {
ImagePreview({ images: imageList.value, startPosition: index }) ImagePreview({ images: imageList, startPosition: index })
} }
// 评论 // 评论
...@@ -64,9 +77,14 @@ const onSubmitComment = (data: any) => { ...@@ -64,9 +77,14 @@ const onSubmitComment = (data: any) => {
</div> </div>
<div class="publish-item-bd"> <div class="publish-item-bd">
<div class="publish-content" v-html="data.content || data.desc"></div> <div class="publish-content" v-html="data.content || data.desc"></div>
<ul class="publish-picture"> <ul class="publish-picture" v-if="imageList.length">
<li v-for="(item, index) in imageList" :key="index" @click="onImagePreview(index)"><img :src="item" /></li> <li v-for="(item, index) in imageList" :key="index" @click="onImagePreview(index)"><img :src="item" /></li>
</ul> </ul>
<ul class="publish-files" v-if="fileList.length">
<li v-for="(item, index) in fileList" :key="index">
<a :href="item.url" :download="item.name" target="_blank"><van-icon name="link-o" />{{ item.name }}</a>
</li>
</ul>
<div class="publish-tools"> <div class="publish-tools">
<p class="t1">{{ data.created_time }}</p> <p class="t1">{{ data.created_time }}</p>
<p class="t2" @click="showComment()">评论</p> <p class="t2" @click="showComment()">评论</p>
...@@ -153,6 +171,19 @@ const onSubmitComment = (data: any) => { ...@@ -153,6 +171,19 @@ const onSubmitComment = (data: any) => {
} }
} }
} }
.publish-files {
margin-top: 0.1rem;
li {
font-size: 0.24rem;
line-height: 0.6rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.van-icon-link-o {
margin-right: 0.1rem;
}
}
.publish-tools { .publish-tools {
margin-top: 0.1rem; margin-top: 0.1rem;
display: flex; display: flex;
......
...@@ -40,6 +40,7 @@ const containerPaddingValue = computed(() => { ...@@ -40,6 +40,7 @@ const containerPaddingValue = computed(() => {
<style lang="scss"> <style lang="scss">
.app-container { .app-container {
margin-bottom: 0.3rem;
padding: v-bind(containerPaddingValue); padding: v-bind(containerPaddingValue);
border-radius: 0.2rem; border-radius: 0.2rem;
background: v-bind(backgroundColor); background: v-bind(backgroundColor);
...@@ -48,7 +49,10 @@ const containerPaddingValue = computed(() => { ...@@ -48,7 +49,10 @@ const containerPaddingValue = computed(() => {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 0.32rem; font-size: 0.32rem;
padding: 0.2rem 0; padding: 0.24rem 0;
.van-icon-arrow-left {
margin-left: -2px;
}
} }
.app-container-hd__title { .app-container-hd__title {
flex: 1; flex: 1;
......
<script setup lang="ts"> <script setup lang="ts">
import md5 from 'blueimp-md5' import md5 from 'blueimp-md5'
import { ref, withDefaults, watch, computed } from 'vue'
import { getSignature, uploadFile } from '@/api/base' import { getSignature, uploadFile } from '@/api/base'
import type { UploaderFileListItem } from 'vant' import type { UploaderFileListItem } from 'vant'
const props = withDefaults(defineProps<{ modelValue?: string | string[]; prefix?: string }>(), { const props = withDefaults(defineProps<{ modelValue?: string | string[]; prefix?: string }>(), {
prefix: 'upload/psp/' prefix: 'upload/psp/'
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue', 'success'])
const fileList = ref<UploaderFileListItem[]>([]) let fileList = $ref<UploaderFileListItem[]>([])
// 是否为列表 // 是否为列表
const isFileList = computed(() => { const isFileList = $computed(() => {
return Array.isArray(props.modelValue) return Array.isArray(props.modelValue)
}) })
// 文件上传数量限制 // 文件上传数量限制
const maxCount = computed(() => { const maxCount = $computed(() => {
return isFileList.value ? Infinity : 1 return isFileList ? Infinity : 1
}) })
watch( watch(
() => props.modelValue, () => props.modelValue,
value => { value => {
fileList.value = Array.isArray(value) ? value.map((url: string) => ({ url })) : (value && [{ url: value }]) || [] fileList = Array.isArray(value) ? value.map((item: any) => ({ url: item.url, name: item.name })) : (value && [{ url: value, name: value }]) || []
} }
) )
// 更新modelValue // 更新modelValue
const handleEmitUpdate = () => { const handleEmitUpdate = () => {
const value = isFileList.value ? fileList.value.map((item: any) => item.url) : fileList.value[0]?.url const value = isFileList ? fileList.map((item: any) => ({ name: item.name, url: item.url })) : fileList[0]?.url
emit('update:modelValue', value) emit('update:modelValue', value)
} }
...@@ -43,6 +42,7 @@ const afterRead = async (file: any) => { ...@@ -43,6 +42,7 @@ const afterRead = async (file: any) => {
file.status = 'uploading' file.status = 'uploading'
file.message = '上传中...' file.message = '上传中...'
file.url = `${signature.host}/${key}` file.url = `${signature.host}/${key}`
file.name = rawFileName
// 上传文件 // 上传文件
const params = { const params = {
key, key,
...@@ -57,6 +57,7 @@ const afterRead = async (file: any) => { ...@@ -57,6 +57,7 @@ const afterRead = async (file: any) => {
file.status = 'done' file.status = 'done'
file.message = '上传成功' file.message = '上传成功'
handleEmitUpdate() handleEmitUpdate()
emit('success', file)
}) })
.catch(() => { .catch(() => {
file.status = 'failed' file.status = 'failed'
...@@ -66,10 +67,5 @@ const afterRead = async (file: any) => { ...@@ -66,10 +67,5 @@ const afterRead = async (file: any) => {
</script> </script>
<template> <template>
<van-uploader <van-uploader v-model="fileList" :after-read="afterRead" :max-count="maxCount" @delete="handleEmitUpdate" v-bind="$attrs"></van-uploader>
v-model="fileList"
:after-read="afterRead"
:max-count="maxCount"
@delete="handleEmitUpdate"
></van-uploader>
</template> </template>
<script setup lang="ts">
import md5 from 'blueimp-md5'
import { getSignature, uploadFile } from '@/api/base'
const props = withDefaults(defineProps<{ modelValue: string; prefix?: string }>(), {
prefix: 'upload/psp/'
})
const emit = defineEmits(['update:modelValue', 'success'])
// 上传
const afterRead = async (file: any) => {
// 获取签名
const signature: any = await getSignature()
const rawFile = file.file
const rawFileName = rawFile?.name || ''
const key = props.prefix + md5(rawFileName + new Date().getTime()) + rawFileName.substr(rawFileName.lastIndexOf('.'))
file.status = 'uploading'
file.message = '上传中...'
file.url = `${signature.host}/${key}`
file.name = rawFileName
// 上传文件
const params = {
key,
OSSAccessKeyId: signature.accessid,
policy: signature.policy,
signature: signature.signature,
success_action_status: '200',
file: rawFile
}
uploadFile(params).then(() => {
emit('update:modelValue', file.url)
emit('success', file.url)
})
}
</script>
<template>
<van-uploader :after-read="afterRead" v-bind="$attrs">
<slot>
<img :src="modelValue" />
</slot>
</van-uploader>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { getDocView } from '../api' import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>() const props = defineProps<{ id: string }>()
const data = ref() const data = ref()
...@@ -16,46 +16,5 @@ onMounted(() => { ...@@ -16,46 +16,5 @@ onMounted(() => {
</script> </script>
<template> <template>
<AppContainer backgroundColor="#fff" headerAlign="center" v-if="data" class="doc"> <DocView :data="data"></DocView>
<div class="doc-hd">
<h1>{{ data.title }}</h1>
<p>
<span>{{ data.created_time }}</span>
<span>{{ data.pv }}观看</span>
</p>
</div>
<div class="doc-bd">
<article v-html="data.desc"></article>
</div>
</AppContainer>
</template> </template>
<style lang="scss" scoped>
.doc-hd {
h1 {
font-size: 0.32rem;
font-weight: 500;
line-height: 0.45rem;
color: #333333;
}
p {
margin-top: 0.15rem;
font-size: 0.24rem;
font-weight: 400;
color: #b2b2b2;
span + span {
margin-left: 0.5rem;
}
}
}
.doc-bd {
padding: 0.5rem 0;
font-size: 0.24rem;
font-weight: 400;
color: #666666;
line-height: 0.42rem;
:deep(img) {
max-width: 100%;
}
}
</style>
...@@ -47,7 +47,7 @@ function showTips() { ...@@ -47,7 +47,7 @@ function showTips() {
<div class="box box-notice"> <div class="box box-notice">
<h2>入学通知书</h2> <h2>入学通知书</h2>
<p class="t1">报名之后即可查询</p> <p class="t1">报名之后即可查询</p>
<p class="t2"><a @click="showTips">去看看 ></a></p> <p class="t2"><a href="https://prp.ezijing.com/personal">去看看 ></a></p>
</div> </div>
</div> </div>
</div> </div>
...@@ -84,7 +84,8 @@ function showTips() { ...@@ -84,7 +84,8 @@ function showTips() {
.box { .box {
width: 3.13rem; width: 3.13rem;
height: 3.43rem; height: 3.43rem;
background: #ffffff; background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
background-size: 100% auto;
border-radius: 0.1rem; border-radius: 0.1rem;
ul { ul {
overflow: hidden; overflow: hidden;
......
...@@ -46,12 +46,12 @@ function handleViewDoc(data: IDocItem) { ...@@ -46,12 +46,12 @@ function handleViewDoc(data: IDocItem) {
font-weight: 600; font-weight: 600;
color: #4e4e4e; color: #4e4e4e;
} }
ul { // ul {
display: grid; // display: grid;
grid-template-columns: 1fr 1fr; // grid-template-columns: 1fr 1fr;
column-gap: 0.2rem; // column-gap: 0.2rem;
row-gap: 0.2rem; // row-gap: 0.2rem;
} // }
li { li {
height: 0.75rem; height: 0.75rem;
padding: 0 0.14rem; padding: 0 0.14rem;
...@@ -64,6 +64,9 @@ function handleViewDoc(data: IDocItem) { ...@@ -64,6 +64,9 @@ function handleViewDoc(data: IDocItem) {
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
} }
li + li {
margin-top: 0.2rem;
}
p { p {
flex: 1; flex: 1;
font-size: 0.24rem; font-size: 0.24rem;
......
...@@ -41,7 +41,7 @@ function showTips() { ...@@ -41,7 +41,7 @@ function showTips() {
<LearningMapCourse></LearningMapCourse> <LearningMapCourse></LearningMapCourse>
</div> </div>
</van-tab> </van-tab>
<van-tab title="解释文档"> <van-tab title="解释文档" v-if="false">
<div class="learn-box learn-docs"> <div class="learn-box learn-docs">
<ul> <ul>
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)"> <li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
......
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { getDocView } from '../api' import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>() const props = defineProps<{ id: string }>()
const data = ref() const data = ref()
...@@ -16,46 +16,5 @@ onMounted(() => { ...@@ -16,46 +16,5 @@ onMounted(() => {
</script> </script>
<template> <template>
<AppContainer backgroundColor="#fff" headerAlign="center" v-if="data" class="doc"> <DocView :data="data"></DocView>
<div class="doc-hd">
<h1>{{ data.title }}</h1>
<p>
<span>{{ data.created_time }}</span>
<span>{{ data.pv }}观看</span>
</p>
</div>
<div class="doc-bd">
<article v-html="data.desc"></article>
</div>
</AppContainer>
</template> </template>
<style lang="scss" scoped>
.doc-hd {
h1 {
font-size: 0.32rem;
font-weight: 500;
line-height: 0.45rem;
color: #333333;
}
p {
margin-top: 0.15rem;
font-size: 0.24rem;
font-weight: 400;
color: #b2b2b2;
span + span {
margin-left: 0.5rem;
}
}
}
.doc-bd {
padding: 0.5rem 0;
font-size: 0.24rem;
font-weight: 400;
color: #666666;
line-height: 0.42rem;
:deep(img) {
max-width: 100%;
}
}
</style>
import httpRequest from '@/utils/axios'
// 获取消息列表
export function getMessageList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/message/audit-list', { params })
}
// 获取文档数据
export function getDocView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/message/doc-view', { params })
}
// 团队成员审核
export function teamAudit(data: { id: string; status: '1' | '4'; audit_comment?: string }) {
return httpRequest.post('/api/psp/v1/message/audit', data)
}
...@@ -5,6 +5,10 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -5,6 +5,10 @@ export const routes: Array<RouteRecordRaw> = [
{ {
path: '/message', path: '/message',
component: AppLayout, component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }] meta: { requireLogin: true },
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: ':id', component: () => import('./views/View.vue'), props: true }
]
} }
] ]
export interface MessageType {
created_time: string
desc_type: string
desc_type_name: string
file: string
id: string
message_info?: { title: string; pv: string; read: boolean }
audit_info?: { user_name: string; user_id: string; apply_comment: string }
picture: string
type: '4' | '999'
url: string
}
<script setup lang="ts"></script> <script setup lang="ts">
import dayjs from 'dayjs'
import { Dialog } from 'vant'
import { getMessageList, teamAudit } from '../api'
import type { MessageType } from '../types'
interface Info {
loading: boolean
page: number
total: number
list: MessageType[]
}
const router = useRouter()
const dataset = reactive<Info>({ loading: false, page: 1, total: 0, list: [] })
// 消息类型名称
function getMessageTypeName(data: MessageType) {
const map = { 4: '系统消息', 999: '成员审核' }
return map[data.type] || data.type
}
// 日期转换
function formatDate(time: string) {
return dayjs(time).format('MM/DD')
}
// 查看消息
function onViewMessage(data: MessageType) {
// 成员审核
if (data.type === '999') {
onTeamAudit(data)
} else {
// 打开跳转链接
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push({ path: `/message/${data.id}` })
}
}
}
// 团队成员审核
function onTeamAudit(data: MessageType) {
Dialog.confirm({
cancelButtonText: '不允许',
confirmButtonText: '允许',
message: data.audit_info?.user_name + '申请加入团队',
beforeClose: action => {
const status = action === 'confirm' ? '1' : '4'
return teamAudit({ id: data.id, status }).then(() => {
getMessages(true)
return true
})
}
})
}
// 获取消息列表
const getMessages = (isReset?: boolean) => {
if (isReset) {
dataset.page = 1
}
dataset.loading = true
getMessageList({ page: dataset.page, page_size: 10 })
.then(res => {
const { total, list } = res.data
dataset.total = total
dataset.list = isReset ? list : dataset.list.concat(list)
if (dataset.list.length <= total) {
dataset.page++
}
})
.finally(() => {
dataset.loading = false
})
}
// 滚动加载
useInfiniteScroll(
document,
() => {
!dataset.loading && getMessages()
},
{ distance: 10 }
)
onMounted(() => {
getMessages()
})
</script>
<template> <template>
<div class="home"></div> <AppContainer title="消息通知" headerAlign="center">
<div class="message-list">
<div class="message-item" v-for="item in dataset.list" :key="item.id" @click="onViewMessage(item)">
<div class="message-item-hd">
<span class="message-item__name">{{ getMessageTypeName(item) }}</span>
<span class="message-item__time">{{ formatDate(item.created_time) }}</span>
</div>
<div class="message-item-bd">
<p v-if="item.message_info">{{ item.message_info.title }}</p>
<p v-if="item.audit_info">{{ item.audit_info.user_name }}申请加入团队</p>
</div>
</div>
</div>
</AppContainer>
</template> </template>
<style lang="scss">
.message-item {
padding: 0.24rem 0.3rem;
background-color: #fff;
border-radius: 0.2rem;
margin-bottom: 0.2rem;
cursor: pointer;
}
.message-item-hd {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.1rem;
line-height: 0.4rem;
}
.message-item__name {
font-size: 0.28rem;
color: #333;
}
.message-item__time {
font-size: 0.24rem;
color: #999;
}
.message-item-bd {
font-size: 0.34rem;
font-weight: 500;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>()
const data = ref()
function fetchDocView() {
getDocView({ id: props.id }).then(res => {
data.value = res.data
})
}
onMounted(() => {
fetchDocView()
})
</script>
<template>
<DocView :data="data"></DocView>
</template>
...@@ -9,3 +9,8 @@ export function getMyInfo() { ...@@ -9,3 +9,8 @@ export function getMyInfo() {
export function getCourseList(params?: { page_size?: number; page?: number }) { export function getCourseList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/course-list', { params }) return httpRequest.get('/api/psp/v1/learning/course-list', { params })
} }
// 持证人权益-上传头像
export function updateAvatar(data: { avatar: string }) {
return httpRequest.post('/api/psp/v1/welfare/avatar', data)
}
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import AppUploadSignImage from '@/components/base/AppUploadSignImage.vue'
import { getMyInfo } from '../api' import { getMyInfo, updateAvatar } from '../api'
import { logout } from '@/api/base' import { logout } from '@/api/base'
const info = ref() import { Toast } from 'vant'
const teamInfo = ref()
const fetchMyInfo = () => { let info = $ref<{ avatar: string; name: string; star: string }>()
let teamInfo = $ref<{ star: number; name?: string; team_id?: string }>({ star: 0 })
function fetchMyInfo() {
getMyInfo().then(res => { getMyInfo().then(res => {
info.value = res.data.info || {} info = res.data.info || {}
teamInfo.value = res.data.team_info || { star: 0 } teamInfo = res.data.team_info || { star: 0 }
}) })
} }
onMounted(() => { onMounted(() => {
fetchMyInfo() fetchMyInfo()
}) })
const menus: Array<{ const menus = computed<
name: string Array<{
path: string name: string
icon: string path: string
}> = [ icon: string
{ }>
path: '/learn/course', >(() => {
name: '我的课程', return [
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_1.png' {
} path: '/learn/course',
// { name: '我的课程',
// path: '/team/view/my', icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_1.png'
// name: '我的团队', },
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_2.png' {
// }, path: teamInfo.team_id ? `/team/view/${teamInfo.team_id}` : '/team',
// { name: '我的团队',
// path: '/', icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_2.png'
// name: '我的问答', },
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_3.png' {
// }, path: '/qa',
// { name: '知识输出者',
// path: '/', icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_3.png'
// name: '申请导师', }
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_4.png' // {
// }, // path: '/',
// { // name: '申请导师',
// path: '/', // icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_4.png'
// name: '申请紫荆奖', // },
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_5.png' // {
// }, // path: '/',
// { // name: '申请紫荆奖',
// path: '/', // icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_5.png'
// name: '参加PRP大会', // },
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_6.png' // {
// } // path: '/',
] // name: '参加PRP大会',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_6.png'
// }
]
})
// 退出登录 // 退出登录
const onLogout = () => { const onLogout = () => {
...@@ -57,11 +63,23 @@ const onLogout = () => { ...@@ -57,11 +63,23 @@ const onLogout = () => {
window.location.href = '/' window.location.href = '/'
}) })
} }
// 修改头像
function onUploadSuccess(url: string) {
updateAvatar({ avatar: url }).then(() => {
Toast.success('上传成功')
})
}
</script> </script>
<template> <template>
<div class="my" v-if="info"> <div class="my" v-if="info">
<div class="user"> <div class="user">
<Avatar :src="info.avatar"></Avatar> <div class="user-avatar">
<AppUploadSignImage v-model="info.avatar" @success="onUploadSuccess">
<Avatar :src="info.avatar"></Avatar>
<span class="user-avatar__edit">编辑</span>
</AppUploadSignImage>
</div>
<p>{{ info.name }}</p> <p>{{ info.name }}</p>
</div> </div>
<div class="quantity"> <div class="quantity">
...@@ -97,14 +115,7 @@ const onLogout = () => { ...@@ -97,14 +115,7 @@ const onLogout = () => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin: 0 auto; margin: 0 auto;
img {
width: 1.5rem;
height: 1.5rem;
background: #fff;
border-radius: 50%;
object-fit: cover;
overflow: hidden;
}
p { p {
margin-top: 0.25rem; margin-top: 0.25rem;
font-size: 0.32rem; font-size: 0.32rem;
...@@ -112,6 +123,32 @@ const onLogout = () => { ...@@ -112,6 +123,32 @@ const onLogout = () => {
color: #333; color: #333;
} }
} }
.user-avatar {
position: relative;
width: 1.5rem;
height: 1.5rem;
background: #fff;
border-radius: 50%;
overflow: hidden;
img {
width: 1.5rem;
height: 1.5rem;
object-fit: cover;
overflow: hidden;
}
}
.user-avatar__edit {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 0.4rem;
font-size: 0.24rem;
color: #fff;
line-height: 0.4rem;
text-align: center;
background-color: #4e4e4e;
}
.quantity { .quantity {
display: flex; display: flex;
margin-top: 0.4rem; margin-top: 0.4rem;
......
...@@ -64,6 +64,7 @@ const onSubmitComment = (data: any, action: string) => { ...@@ -64,6 +64,7 @@ const onSubmitComment = (data: any, action: string) => {
} }
onMounted(() => { onMounted(() => {
getQuestions()
useWXShare({ desc: '输出即是输入 成就专业【知识输出者】' }) useWXShare({ desc: '输出即是输入 成就专业【知识输出者】' })
}) })
</script> </script>
...@@ -90,7 +91,7 @@ onMounted(() => { ...@@ -90,7 +91,7 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.list-card { .list-card {
margin-top: 0.52rem; margin-top: 0.52rem;
margin-bottom: 0.2rem; margin-bottom: 0.3rem;
background: #fff; background: #fff;
border-radius: 0.2rem; border-radius: 0.2rem;
overflow: hidden; overflow: hidden;
......
...@@ -21,17 +21,13 @@ export function joinTeam(data: { id: string }) { ...@@ -21,17 +21,13 @@ export function joinTeam(data: { id: string }) {
} }
// 团队-上传资料/发布讨论 // 团队-上传资料/发布讨论
export function createTeamPosts(data: { export function createTeamPosts(data: { type: string; team_visible: string; title: string; desc: string; picture?: string; file?: string }) {
type: string
team_visible: string
title: string
desc: string
picture?: string
file?: string
}) {
return httpRequest.post('/api/psp/v1/team/upload', data) return httpRequest.post('/api/psp/v1/team/upload', data)
} }
// 团队-成员列表
export function getTeamMemberList(params: { id: string; type: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/v1/team/members', { params })
}
// 团队-团队文件/讨论列表 // 团队-团队文件/讨论列表
export function getTeamFileOrQuestions(params: { id: string; type: string; page?: number; page_size?: number }) { export function getTeamFileOrQuestions(params: { id: string; type: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/v1/team/files-or-questions', { params }) return httpRequest.get('/api/psp/v1/team/files-or-questions', { params })
......
...@@ -54,13 +54,7 @@ const onSubmitComment = (data: any, action: string) => { ...@@ -54,13 +54,7 @@ const onSubmitComment = (data: any, action: string) => {
<div class="button"><router-link :to="{ name: 'teamFilePublish', params: { id } }">上传</router-link></div> <div class="button"><router-link :to="{ name: 'teamFilePublish', params: { id } }">上传</router-link></div>
</template> </template>
<template v-if="dataset.list?.length"> <template v-if="dataset.list?.length">
<PublishItem <PublishItem v-for="item in dataset.list" :data="item" :key="item.id" @submitComment="onSubmitComment"></PublishItem>
a="123"
v-for="item in dataset.list"
:data="item"
:key="item.id"
@submitComment="onSubmitComment"
></PublishItem>
</template> </template>
<van-empty description="暂无内容" v-else /> <van-empty description="暂无内容" v-else />
</AppCard> </AppCard>
......
<script setup lang="ts">
import { getTeamMemberList } from '../api'
const props = defineProps<{ id: string }>()
// 获取列表数据
const page = ref<number>(1)
const dataset = reactive<{ total: number; list: Record<string, any>[] }>({ total: 0, list: [] })
async function fetchList(isRefresh?: boolean) {
if (isRefresh) {
page.value = 1
}
const { data } = await getTeamMemberList({ id: props.id, type: 'file', page: page.value, page_size: 20 })
dataset.total = data.total
dataset.list = isRefresh ? data.list : [...dataset.list, ...data.list]
}
onMounted(() => {
fetchList()
})
</script>
<template>
<AppCard>
<div class="member-item" v-for="item in dataset.list" :key="item.user_id">
<Avatar :src="item.user_info.avatar" class="member-item-avatar"></Avatar>
<div class="member-item-content">
<h5>{{ item.user_info.name }}</h5>
<ul>
<li v-for="label in item.user_info.label" :key="label">{{ label }}</li>
</ul>
</div>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.member-item {
display: flex;
align-items: center;
background-color: #fff;
padding: 0.24rem;
margin-bottom: 0.2rem;
border-radius: 0.2rem;
}
.member-item-avatar {
width: 0.68rem;
height: 0.68rem;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
}
.member-item-content {
flex: 1;
margin-left: 0.18rem;
h5 {
font-size: 0.24rem;
font-weight: 400;
line-height: 0.34rem;
color: #333333;
}
ul {
display: flex;
}
li {
margin-top: 0.05rem;
padding: 0 0.2rem;
font-size: 0.18rem;
font-weight: 300;
line-height: 0.28rem;
color: #666666;
background: #edf6ff;
}
li + li {
margin-left: 0.1rem;
}
}
</style>
...@@ -47,12 +47,7 @@ const onSubmitComment = (data: any, action: string) => { ...@@ -47,12 +47,7 @@ const onSubmitComment = (data: any, action: string) => {
<div class="button"><router-link :to="{ name: 'teamQuestionPublish', params: { id } }"> 发布</router-link></div> <div class="button"><router-link :to="{ name: 'teamQuestionPublish', params: { id } }"> 发布</router-link></div>
</template> </template>
<template v-if="dataset.list?.length"> <template v-if="dataset.list?.length">
<PublishItem <PublishItem v-for="item in dataset.list" :data="item" :key="item.id" @submitComment="onSubmitComment"></PublishItem>
v-for="item in dataset.list"
:data="item"
:key="item.id"
@submitComment="onSubmitComment"
></PublishItem>
</template> </template>
<van-empty description="暂无内容" v-else /> <van-empty description="暂无内容" v-else />
</AppCard> </AppCard>
......
export interface TeamType {
id: string
name: string
slogan: string
brief: string
logo: string
star: string
members_count: string
files_count: string
questions_count: string
}
<script setup lang="ts"> <script setup lang="ts">
import { Toast } from 'vant' import { Toast } from 'vant'
import MemberList from '../components/MemberList.vue'
import FileList from '../components/FileList.vue' import FileList from '../components/FileList.vue'
import QuestionList from '../components/QuestionList.vue' import QuestionList from '../components/QuestionList.vue'
import * as api from '../api' import * as api from '../api'
import type { TeamType } from '../types'
const props = defineProps<{ id: string }>() const props = defineProps<{ id: string }>()
const detail = ref() let detail = $ref<{ my_team: null | { team_id: string }; team_info: TeamType }>()
// 获取详情信息 // 获取详情信息
async function fetchDetailInfo() { async function fetchDetailInfo() {
const { data } = await api.getTeam({ id: props.id }) const { data } = await api.getTeam({ id: props.id })
detail.value = data detail = data
} }
onMounted(() => { onMounted(() => {
fetchDetailInfo() fetchDetailInfo()
}) })
// 是否可以加入团队
const canJoin = computed(() => {
return !detail.my_team?.team_id
})
// 是否加入该团队
const isJoined = computed(() => {
return detail.my_team?.team_id === detail.team_info.id
})
// 加入团队 // 加入团队
async function joinTeam() { async function joinTeam() {
await api.joinTeam({ id: props.id }) await api.joinTeam({ id: props.id })
...@@ -32,23 +43,21 @@ async function joinTeam() { ...@@ -32,23 +43,21 @@ async function joinTeam() {
<h2>{{ detail.team_info.name }}</h2> <h2>{{ detail.team_info.name }}</h2>
<h6>{{ detail.team_info.slogan }}</h6> <h6>{{ detail.team_info.slogan }}</h6>
<p> <p>
<span>人员数{{ detail.team_info.members_count }}</span <span>人员数 {{ detail.team_info.members_count }}</span>
><span>积分数{{ detail.team_info.star }}</span> <span>积分数 {{ detail.team_info.star }}</span>
</p> </p>
<p> <p>
<span>资料数{{ detail.team_info.files_count }}</span <span>资料数 {{ detail.team_info.files_count }}</span>
><span>问答数{{ detail.team_info.questions_count }}</span> <span>问答数 {{ detail.team_info.questions_count }}</span>
</p> </p>
<van-button round v-if="!detail.is_sign_in" @click="joinTeam">加入</van-button> <div class="info-join" @click="joinTeam" v-if="canJoin">加入</div>
<div class="info-joined" v-if="isJoined">已加入</div>
</div> </div>
</div> </div>
<van-tabs <van-tabs shrink color="#033974" line-width="30px" background="transparent" title-active-color="#033974" title-inactive-color="#4E4E4E">
shrink <van-tab title="成员">
color="#033974" <MemberList :id="id"></MemberList>
background="transparent" </van-tab>
title-active-color="#033974"
title-inactive-color="#4E4E4E"
>
<van-tab title="资料"> <van-tab title="资料">
<FileList :id="id"></FileList> <FileList :id="id"></FileList>
</van-tab> </van-tab>
...@@ -81,21 +90,52 @@ async function joinTeam() { ...@@ -81,21 +90,52 @@ async function joinTeam() {
h2 { h2 {
font-size: 0.32rem; font-size: 0.32rem;
font-weight: 500; font-weight: 500;
line-height: 0.45rem;
color: #333333; color: #333333;
} }
h6 { h6 {
margin-top: 0.08rem; margin-top: 0.02rem;
margin-bottom: 0.12rem;
font-size: 0.28rem; font-size: 0.28rem;
font-weight: 400; font-weight: 400;
line-height: 0.4rem;
color: #4e4e4e; color: #4e4e4e;
} }
p { p {
margin-top: 0.1rem; margin-top: 0.02rem;
font-size: 0.24rem; font-size: 0.24rem;
line-height: 0.3rem;
color: #999999; color: #999999;
span + span { span + span {
margin-left: 0.2rem; margin-left: 0.4rem;
} }
} }
} }
.info-join {
margin-top: 0.3rem;
display: inline-block;
min-width: 1.3rem;
height: 0.44rem;
font-size: 0.24rem;
line-height: 0.44rem;
color: #fff;
text-align: center;
background: #033974;
border-radius: 0.22rem;
}
.info-joined {
margin-top: 0.3rem;
font-size: 0.24rem;
line-height: 0.44rem;
color: #033974;
}
:deep(.van-tabs__nav) {
padding-left: 0;
padding-right: 0;
padding-bottom: 8px;
}
:deep(.van-tab--shrink) {
padding: 0;
margin-right: 30px;
}
</style> </style>
...@@ -4,7 +4,7 @@ import { fileURLToPath, URL } from 'url' ...@@ -4,7 +4,7 @@ import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import checker from 'vite-plugin-checker' // import checker from 'vite-plugin-checker'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
...@@ -16,8 +16,8 @@ export default defineConfig(({ mode }) => { ...@@ -16,8 +16,8 @@ export default defineConfig(({ mode }) => {
imports: ['vue', 'vue/macros', 'vue-router', '@vueuse/core'], imports: ['vue', 'vue/macros', 'vue-router', '@vueuse/core'],
dts: true, dts: true,
eslintrc: { enabled: true } eslintrc: { enabled: true }
}), })
checker({ vueTsc: true, eslint: { lintCommand: 'eslint "./src/**/*.{vue,js,jsx,ts,tsx}"' } }) // checker({ vueTsc: true, eslint: { lintCommand: 'eslint "./src/**/*.{vue,js,jsx,ts,tsx}"' } })
], ],
server: { server: {
open: true, open: true,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论