Compare commits

5 Commits

Author SHA1 Message Date
qsh
183416a1b3 sc 2025-08-20 11:46:34 +08:00
qsh
c786995532 sc 2025-08-12 11:05:11 +08:00
qsh
1d3b4944e5 sc 2025-08-11 18:39:20 +08:00
qsh
7f10d7bcd7 sc 2025-08-11 18:17:59 +08:00
qsh
21b8f1bb12 sc 2025-08-11 18:04:34 +08:00
14 changed files with 377 additions and 146 deletions

View File

@@ -4,11 +4,10 @@ VITE_NODE_ENV=development
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='http://localhost:48080' # VITE_BASE_URL='http://localhost:48080'
# VITE_BASE_URL='http://47.98.161.246:48080' VITE_BASE_URL='http://47.98.161.246:48080'
# VITE_BASE_URL='http://114.55.169.15:48080' # VITE_BASE_URL='http://114.215.207.150:48080'
#VITE_BASE_URL='http://114.215.207.150:48080'
# 上传路径 # 上传路径
VITE_UPLOAD_URL='http://47.98.161.246:48080/admin-api/system/file/upload' VITE_UPLOAD_URL='http://47.98.161.246:48080/admin-api/system/file/upload'

View File

@@ -4,10 +4,8 @@ VITE_NODE_ENV=production
VITE_DEV=false VITE_DEV=false
# 请求路径 # 请求路径
#VITE_BASE_URL='http://47.98.161.246:48080'
VITE_BASE_URL='http://localhost:48080' VITE_BASE_URL='http://localhost:48080'
# 上传路径 # 上传路径
VITE_UPLOAD_URL='http://47.98.161.246:48080/admin-api/system/file/upload' VITE_UPLOAD_URL='http://47.98.161.246:48080/admin-api/system/file/upload'

View File

@@ -43,3 +43,19 @@ export const getMeetingPage = (params) => {
// headers: { 'instance-id': 1016 } // headers: { 'instance-id': 1016 }
}) })
} }
// 刷新微信群列表
export const refreshWxGroupList = () => {
return request.get({
url: '/admin-api/system/wx/reFreshWeChatGroupList'
// headers: { 'instance-id': 1016 }
})
}
// 获取微信群聊列表
export const getWxGroupList = () => {
return request.get({
url: '/admin-api/system/wx/getWeChatGroupList'
// headers: { 'instance-id': 1016 }
})
}

View File

@@ -49,7 +49,10 @@ router.beforeEach(async (to, from, next) => {
}) })
const redirectPath = from.query.redirect || to.path const redirectPath = from.query.redirect || to.path
const redirect = decodeURIComponent(redirectPath) const redirect = decodeURIComponent(redirectPath)
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } const nextData =
to.path === redirect
? { ...to, replace: true, query: to.query }
: { path: redirect, query: to.query }
next(nextData) next(nextData)
} else { } else {
next() next()
@@ -59,10 +62,23 @@ router.beforeEach(async (to, from, next) => {
if (whiteList.indexOf(to.path) !== -1) { if (whiteList.indexOf(to.path) !== -1) {
next() next()
} else { } else {
const tenantId = getTenantId() const tenantId = getTenantId() || to.query?.tenantId
const appId = getAppId() const appId = getAppId() || to.query?.appId
if (tenantId && appId) { if (tenantId && appId) {
next(`/login?tenantId=${tenantId}&appId=${appId}&redirect=${to.fullPath}`) // 否则全部重定向到登录页 let redirectPath = to.fullPath
const p = to.fullPath.split('?')
if (p.length > 1) {
// 过滤掉query参数中的tenantId和appId
redirectPath =
p[0] +
'?' +
p[1]
.split('&')
.filter((item) => !item.startsWith('tenantId=') && !item.startsWith('appId='))
.join('&')
}
next(`/login?tenantId=${tenantId}&appId=${appId}&redirect=${redirectPath}`) // 否则全部重定向到登录页
} else { } else {
// next(`/login?redirect=${to.fullPath}`) // next(`/login?redirect=${to.fullPath}`)
// 否则全部重定向到平台登陆页 // 否则全部重定向到平台登陆页

View File

@@ -139,6 +139,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
noTagsView: true noTagsView: true
} }
}, },
{
path: '/meeting-summary',
component: () => import('@/views/OKR/Meeting/MeetingSummary.vue'),
name: 'MeetingSummary',
meta: {
hidden: true,
title: '会议纪要',
noTagsView: true
}
},
{ {
path: '/sso', path: '/sso',
component: () => import('@/views/Login/Login.vue'), component: () => import('@/views/Login/Login.vue'),

View File

@@ -33,6 +33,7 @@
<el-select <el-select
v-model="formData.leaderUserId" v-model="formData.leaderUserId"
clearable clearable
multiple
filterable filterable
placeholder="请输入负责人" placeholder="请输入负责人"
> >
@@ -136,13 +137,17 @@ const open = async (type: string, id?: number) => {
try { try {
formData.value = await DeptApi.getDept(id) formData.value = await DeptApi.getDept(id)
formData.value.remark = formData.value.remark || '' formData.value.remark = formData.value.remark || ''
formData.value.leaderUserId = formData.value.leaderUserId?.map((it) => it + '') || []
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} }
// 获得用户列表 // 获得用户列表
getEmployeeSimpleList().then((data) => { getEmployeeSimpleList().then((data) => {
employeeOptions.value = data employeeOptions.value = data.map((it) => ({
...it,
id: it.id + ''
}))
}) })
//实例 //实例
getSimpleAppList().then((data) => { getSimpleAppList().then((data) => {
@@ -186,7 +191,7 @@ const resetForm = () => {
parentId: undefined, parentId: undefined,
name: undefined, name: undefined,
sort: 1, sort: 1,
leaderUserId: undefined, leaderUserId: [],
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: undefined, remark: undefined,
instanceId: undefined instanceId: undefined

View File

@@ -267,14 +267,18 @@ async function getList() {
return pre.concat(cur.userDingAttendanceRespVOList) return pre.concat(cur.userDingAttendanceRespVOList)
}, []) }, [])
} else { } else {
tableList.value = data.list.map((it, index) => ({ tableList.value = data.list.map((it, index) => {
...it, const arr = it.userDingAttendanceRespVOList.filter((user) => user.needAttendance)
userDingAttendanceRespVOList: it.userDingAttendanceRespVOList.sort((pre, cur) => return {
pre.employeeName.localeCompare(cur.employeeName) ...it,
), userDingAttendanceRespVOList: multiFieldSort(arr, [
id: index + 1, { key: 'dept' },
edit: it.status == 1 ? '2' : '0' { key: 'employeeName' }
})) ]),
id: index + 1,
edit: it.status == 1 ? '2' : '0'
}
})
} }
total.value = data.total total.value = data.total
} catch (err) { } catch (err) {
@@ -284,6 +288,32 @@ async function getList() {
} }
} }
function multiFieldSort(arr, fields) {
return arr.sort((a, b) => {
// 遍历每个排序字段
for (const field of fields) {
const { key, order = 'asc' } = field
const valueA = a[key]
const valueB = b[key]
// 处理不同类型的比较
let compareResult
if (typeof valueA === 'number' && typeof valueB === 'number') {
compareResult = valueA - valueB // 数字比较
} else {
compareResult = String(valueA).localeCompare(String(valueB)) // 字符串比较(兼容其他类型)
}
// 如果当前字段值不相等,直接返回比较结果(根据排序方向调整)
if (compareResult !== 0) {
return order === 'desc' ? -compareResult : compareResult
}
}
// 所有字段都相等,保持原有顺序
return 0
})
}
function spanMethod({ row, columnIndex }) { function spanMethod({ row, columnIndex }) {
if (row.userDingAttendanceRespVOList && row.userDingAttendanceRespVOList.length > 0) { if (row.userDingAttendanceRespVOList && row.userDingAttendanceRespVOList.length > 0) {
if (columnIndex === 0) { if (columnIndex === 0) {

View File

@@ -392,9 +392,10 @@ async function getList() {
} else { } else {
tableList.value = data.list.map((it, index) => ({ tableList.value = data.list.map((it, index) => ({
...it, ...it,
userSalaryGrantRespVOList: it.userSalaryGrantRespVOList.sort((pre, cur) => userSalaryGrantRespVOList: multiFieldSort(it.userSalaryGrantRespVOList, [
pre.name.localeCompare(cur.name) { key: 'dept' },
), { key: 'name' }
]),
id: index + 1, id: index + 1,
edit: it.status == 1 ? '2' : '0' edit: it.status == 1 ? '2' : '0'
})) }))
@@ -407,6 +408,32 @@ async function getList() {
} }
} }
function multiFieldSort(arr, fields) {
return arr.sort((a, b) => {
// 遍历每个排序字段
for (const field of fields) {
const { key, order = 'asc' } = field
const valueA = a[key]
const valueB = b[key]
// 处理不同类型的比较
let compareResult
if (typeof valueA === 'number' && typeof valueB === 'number') {
compareResult = valueA - valueB // 数字比较
} else {
compareResult = String(valueA).localeCompare(String(valueB)) // 字符串比较(兼容其他类型)
}
// 如果当前字段值不相等,直接返回比较结果(根据排序方向调整)
if (compareResult !== 0) {
return order === 'desc' ? -compareResult : compareResult
}
}
// 所有字段都相等,保持原有顺序
return 0
})
}
const createSalaryRef = ref() const createSalaryRef = ref()
function craeteSalary() { function craeteSalary() {
createSalaryRef.value.open() createSalaryRef.value.open()

View File

@@ -127,6 +127,8 @@ const formLogin = ref()
const { validForm } = useFormValid(formLogin) const { validForm } = useFormValid(formLogin)
const { setLoginState, getLoginState } = useLoginState() const { setLoginState, getLoginState } = useLoginState()
const { currentRoute, push } = useRouter() const { currentRoute, push } = useRouter()
const route = useRoute()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
const redirect = ref('') const redirect = ref('')
const loginLoading = ref(false) const loginLoading = ref(false)
@@ -217,9 +219,12 @@ const handleLogin = async (params) => {
if (redirect.value.indexOf('sso') !== -1) { if (redirect.value.indexOf('sso') !== -1) {
window.location.href = window.location.href.replace('/login?redirect=', '') window.location.href = window.location.href.replace('/login?redirect=', '')
} else { } else {
push({ path: redirect.value || permissionStore.addRouters[0].path }) push({
path: redirect.value || permissionStore.addRouters[0].path,
query: route.redirectedFrom?.query
})
} }
} catch { } catch (err) {
loginLoading.value = false loginLoading.value = false
} finally { } finally {
setTimeout(() => { setTimeout(() => {
@@ -229,10 +234,12 @@ const handleLogin = async (params) => {
} }
} }
// const routerParams = ref(undefined)
watch( watch(
() => currentRoute.value, () => currentRoute.value,
(route) => { (route1) => {
redirect.value = route?.query?.redirect redirect.value = route1?.redirectedFrom?.path
// routerParams.value = route?.redirectedFrom?.query
}, },
{ {
immediate: true immediate: true

View File

@@ -4,8 +4,8 @@
<div class="flex justify-between mb-4 bg-white" v-if="!isDetail"> <div class="flex justify-between mb-4 bg-white" v-if="!isDetail">
<b class="text-20px">{{ form.meetingId ? '修改会议' : '新增会议' }}</b> <b class="text-20px">{{ form.meetingId ? '修改会议' : '新增会议' }}</b>
<div> <div>
<el-button @click="submit()">保存至草稿</el-button> <el-button @click="submit(true)">保存至草稿</el-button>
<el-button type="success" @click="submit()">保存</el-button> <el-button type="success" @click="submit(false)">保存</el-button>
</div> </div>
</div> </div>
<!-- </el-affix> --> <!-- </el-affix> -->
@@ -108,7 +108,7 @@
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" :offset="0" v-if="!isAllActived"> <el-col :span="24" :offset="0" v-if="form.meetingId && !isAllActived">
<el-form-item label="缺席原因" prop="absentReason"> <el-form-item label="缺席原因" prop="absentReason">
<el-input v-model="form.absentReason" placeholder="请输入缺席原因" /> <el-input v-model="form.absentReason" placeholder="请输入缺席原因" />
</el-form-item> </el-form-item>
@@ -116,64 +116,59 @@
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24"> <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24">
<el-tabs v-model="currentUserId" tab-position="top"> <el-tabs v-model="currentUserId" tab-position="top" @tab-click="userTabChange">
<el-tab-pane <el-tab-pane
v-for="item in form.contentList" v-for="item in form.meetingContentList"
:key="item.id" :key="item.userId"
:label="item.name" :label="item.userName"
:name="item.id" :name="item.userId + ''"
@click="userTabChange(item)"
/> />
</el-tabs> </el-tabs>
<el-tabs v-model="currentContentId" tab-position="left" addable @edit="handleTabsEdit"> <el-tabs v-model="currentContentId" tab-position="left" addable @edit="handleTabsEdit">
<el-tab-pane <el-tab-pane
v-for="(item, index) in form.contentList.find((it) => it.id == currentUserId) v-for="(item, index) in form.meetingContentList.find(
?.contentArr" (it) => it.userId == currentUserId
:key="item.id" )?.userMeetingContentList"
:label="item.title" :key="index"
:name="item.id" :label="'内容' + (index + 1)"
:name="index"
:closable="index > 0" :closable="index > 0"
> >
<div v-if="!!isDetail" v-dompurify-html="item.content" class="w-full"></div> <div v-if="!!isDetail" v-dompurify-html="item.content" class="w-full"></div>
<Editor v-else v-model="item.content" height="500px" style="width: 100%" /> <Editor v-else v-model="item.content" height="500px" style="width: 100%" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<!-- <el-form-item label="会议内容" prop="meetingContent">
<div v-if="!!isDetail" v-dompurify-html="form.meetingContent" class="w-full"></div>
<Editor v-else v-model="form.meetingContent" height="500px" style="width: 100%" />
</el-form-item> -->
</el-col> </el-col>
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" v-if="!!form.meetingId"> <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" v-if="!!form.meetingId">
<div v-if="!!isDetail" v-dompurify-html="form.meetingSummary" class="w-full"></div> <div v-if="!!isDetail" v-dompurify-html="form.meetingSummary" class="w-full"></div>
<div v-else> <div v-else>
<el-tabs v-model="summaryIdx" addable @edit="meetingSummaryEdit"> <el-tabs v-model="summaryIdx" addable @edit="meetingSummaryEdit">
<el-tab-pane <el-tab-pane
v-for="(item, index) in form.meetingList" v-for="(item, index) in form.meetingSummaryList"
:key="index" :key="index"
:label="`会议纪要${index || ''}`" :label="`会议纪要${index || ''}`"
:name="index" :name="index"
:closable="index > 0" :closable="index > 0"
> >
<Editor <Editor
v-model="item.meetingSummary" v-model="item.summary"
:toolbarConfig="toolbarConfig" :toolbarConfig="toolbarConfig"
height="300px" height="350px"
placeholder="请输入会议纪要" placeholder="请输入会议纪要"
style="width: 100%" style="width: 100%"
/> />
<div class="mt-10px"> <div class="mt-10px">
<el-form-item label="是否创建待办" label-width="auto"> <el-form-item label="是否创建待办" label-width="auto">
<el-radio-group v-model="item.createWait"> <el-radio-group v-model="item.isCreateAgentWork">
<el-radio :label="true" :value="true"> 创建待办 </el-radio> <el-radio :label="true" :value="true"> 创建待办 </el-radio>
<el-radio :label="false" :value="false"> 不创建待办 </el-radio> <el-radio :label="false" :value="false"> 不创建待办 </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</div> </div>
<div class="flex items-center" v-if="item.createWait"> <div class="flex items-center" v-if="item.isCreateAgentWork">
<el-select <el-select
class="flex-1" class="flex-1"
v-model="item.userIdList" v-model="item.agentUserList"
placeholder="选择执行人" placeholder="选择执行人"
clearable clearable
filterable filterable
@@ -204,20 +199,23 @@
</div> </div>
<div class="mt-10px"> <div class="mt-10px">
<el-form-item label="会议纪要发送至群聊:" label-width="auto"> <el-form-item label="会议纪要发送至群聊:" label-width="auto">
<el-input v-model="form.groupName" placeholder="请输入群聊名称" /> <el-select
filterable
clearable
v-model="form.wxId"
placeholder="请输入群聊名称"
style="width: 100%"
>
<el-option
v-for="item in groupOptions"
:key="item.wxGroupId"
:label="item.wxGroupName"
:value="item.wxGroupId"
/>
</el-select>
</el-form-item> </el-form-item>
</div> </div>
</div> </div>
<!-- <el-form-item label="会议纪要" prop="meetingSummary" label-width="80px">
<div v-if="!!isDetail" v-dompurify-html="form.meetingSummary" class="w-full"></div>
<Editor
v-else
v-model="form.meetingSummary"
:toolbarConfig="toolbarConfig"
height="300px"
style="width: 100%"
/>
</el-form-item> -->
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
@@ -227,6 +225,7 @@
<script setup name="MeetingInfo"> <script setup name="MeetingInfo">
import { listToTree } from '@/utils/tree' import { listToTree } from '@/utils/tree'
import { getAllNodeTree } from '@/api/okr/okr' import { getAllNodeTree } from '@/api/okr/okr'
import { getWaitPage } from '@/api/okr/wait'
import * as MeetingApi from '@/api/okr/meeting' import * as MeetingApi from '@/api/okr/meeting'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { getEmployeeSimpleList } from '@/api/pers/employee' import { getEmployeeSimpleList } from '@/api/pers/employee'
@@ -246,7 +245,7 @@ const defaultProps = {
const isDetail = route.query.isDetail const isDetail = route.query.isDetail
const currentUserId = ref(undefined) const currentUserId = ref(undefined)
const currentContentId = ref('') // 默认选中第一个标签页 const currentContentId = ref(0) // 默认选中第一个标签页
const summaryIdx = ref(0) // 会议纪要的索引 const summaryIdx = ref(0) // 会议纪要的索引
const toolbarConfig = { const toolbarConfig = {
@@ -254,25 +253,25 @@ const toolbarConfig = {
} }
onMounted(async () => { onMounted(async () => {
getWxGroupOptions()
await getOptions() await getOptions()
if (route.params.id && route.params.id != 0) { if (route.params.id && route.params.id != 0) {
// 这里可以调用API获取会议详情数据 // 这里可以调用API获取会议详情数据
getMeetingInfo(route.params.id) getMeetingInfo(route.params.id)
} else { } else {
form.value.contentList = [ form.value.meetingContentList = [
{ {
id: userStore.getUser.id + '', userId: userStore.getUser.id + '',
name: userStore.getUser.nickname, userName: userStore.getUser.nickname,
contentArr: [ userMeetingContentList: [
{ {
id: crypto.randomUUID(),
title: '主要内容',
content: '' content: ''
} }
] ]
} }
] ]
currentContentId.value = form.value.contentList[0].contentArr[0].id currentContentId.value = 0
searchUserWait([userStore.getUser.id])
} }
}) })
@@ -287,24 +286,12 @@ function getOptions() {
}) })
userOptions.value = employeeResp.map((it) => ({ ...it, id: it.id + '' })) userOptions.value = employeeResp.map((it) => ({ ...it, id: it.id + '' }))
form.value.expectUsers = [userStore.getUser.id + ''] // 默认添加当前用户为预约参会人员 form.value.expectUsers = [userStore.getUser.id + ''] // 默认添加当前用户为预约参会人员
// form.value.actualUsers = [userStore.getUser.id + '']
currentUserId.value = userStore.getUser.id + '' // 默认选中当前用户 currentUserId.value = userStore.getUser.id + '' // 默认选中当前用户
// handleUserChange()
}) })
.catch((error) => { .catch((error) => {
console.error('获取数据失败:', error) console.error('获取数据失败:', error)
}) })
// // 获取OKR节点数据
// getAllNodeTree().then((resp) => {
// peroidList.value = listToTree(resp?.tree || [], {
// id: 'nodeId',
// pid: 'parentId',
// children: 'children'
// })
// })
// // 获取人员数据
// getEmployeeSimpleList().then((data) => {
// userOptions.value = data.map((it) => ({ ...it, id: it.id + '' }))
// })
} }
const form = ref({ const form = ref({
@@ -320,8 +307,8 @@ const form = ref({
meetingContent: '', meetingContent: '',
meetingSummary: '', meetingSummary: '',
absentReason: '', absentReason: '',
contentList: [], meetingContentList: [],
meetingList: [] meetingSummaryList: []
}) })
const rules = { const rules = {
meetingSubject: [{ required: true, message: '请输入会议主题', trigger: 'blur' }], meetingSubject: [{ required: true, message: '请输入会议主题', trigger: 'blur' }],
@@ -349,6 +336,24 @@ const getMeetingInfo = async (meetingId) => {
const resp = await MeetingApi.getMeetingDetail({ meetingId }) const resp = await MeetingApi.getMeetingDetail({ meetingId })
loading.value = false loading.value = false
if (resp) { if (resp) {
let summaryList = []
if (resp.meetingSummaryList && resp.meetingSummaryList.length > 0) {
summaryList = resp.meetingSummaryList.map((item) => ({
...item,
isCreateAgentWork: !!item.isCreateAgentWork,
agentUserList: item.agentUserList ? item.agentUserList.map((it) => it + '') : []
}))
} else {
summaryList = [
{
summary: resp.meetingSummary || '',
agentUserList: [],
endDate: '',
isCreateAgentWork: true
}
]
}
form.value = { form.value = {
...form.value, ...form.value,
...resp, ...resp,
@@ -356,16 +361,12 @@ const getMeetingInfo = async (meetingId) => {
expectEndTime: formatDate(resp.expectEndTime, 'YYYY-MM-DD HH:mm'), expectEndTime: formatDate(resp.expectEndTime, 'YYYY-MM-DD HH:mm'),
expectUsers: resp.expectUsers || [], expectUsers: resp.expectUsers || [],
actualUsers: resp.actualUsers || [], actualUsers: resp.actualUsers || [],
meetingList: resp.meetingList || [ meetingSummaryList: summaryList
{
meetingSummary: resp.meetingSummary || '',
userIdList: [],
endDate: '',
createWait: true
}
]
} }
handleUserChange(resp.expectUsers) currentContentId.value = 0
expectUserOptions.value = userOptions.value.filter((user) =>
form.value.expectUsers.some((it) => it == user.id)
)
} }
} catch (error) { } catch (error) {
loading.value = false loading.value = false
@@ -373,64 +374,61 @@ const getMeetingInfo = async (meetingId) => {
} }
} }
function userTabChange(val) { const groupOptions = ref([])
currentContentId.value = val.contentArr[0].id
function getWxGroupOptions() {
MeetingApi.refreshWxGroupList().then(() => {
MeetingApi.getWxGroupList()
.then((resp) => {
groupOptions.value = resp || []
})
.catch((error) => {
console.error('获取微信群列表失败:', error)
})
})
}
function userTabChange() {
currentContentId.value = 0
} }
function handleTabsEdit(targetName, action) { function handleTabsEdit(targetName, action) {
if (action === 'add') { if (action === 'add') {
form.value.contentList.forEach((item) => { form.value.meetingContentList.forEach((item) => {
if (item.id === currentUserId.value) { if (item.userId == currentUserId.value) {
item.contentArr.push({ item.userMeetingContentList.push({
id: crypto.randomUUID(),
title: '次要内容',
content: '' content: ''
}) })
} }
}) })
} else if (action === 'remove') { } else if (action === 'remove') {
form.value.contentList.forEach((item) => { form.value.meetingContentList.forEach((item) => {
if (item.id === currentUserId.value) { if (item.userId == currentUserId.value) {
const idx = item.contentArr.findIndex((item) => item.id == targetName) item.userMeetingContentList.splice(targetName, 1)
item.contentArr.splice(idx, 1)
// 如果删除的是当前选中的标签页,则切换到下一个标签页 // 如果删除的是当前选中的标签页,则切换到下一个标签页
if (currentContentId.value === targetName) { if (currentContentId.value == targetName) {
currentContentId.value = item.contentArr[idx - 1]?.id || item.contentArr[idx + 1]?.id currentContentId.value = targetName - 1
} }
} }
}) })
const tabs = editableTabs.value
let activeName = editableTabsValue.value
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
editableTabsValue.value = activeName
editableTabs.value = tabs.filter((tab) => tab.name !== targetName)
} }
} }
function meetingSummaryEdit(targetName, action) { function meetingSummaryEdit(targetName, action) {
if (action === 'add') { if (action === 'add') {
form.value.meetingList.push({ form.value.meetingSummaryList.push({
meetingSummary: '', summary: '',
userIdList: [], agentUserList: [],
endDate: '', endDate: '',
createWait: true isCreateAgentWork: true
}) })
} else if (action === 'remove') { } else if (action === 'remove') {
form.value.meetingList.splice(targetName, 1) form.value.meetingSummaryList.splice(targetName, 1)
summaryIdx.value = 0 summaryIdx.value = targetName - 1
} }
} }
const checkedUsers = ref([])
function handleUserChange(val) { function handleUserChange(val) {
// 当预约参会人员变化时,更新实际参会人员选项 // 当预约参会人员变化时,更新实际参会人员选项
expectUserOptions.value = userOptions.value.filter((user) => expectUserOptions.value = userOptions.value.filter((user) =>
@@ -440,49 +438,100 @@ function handleUserChange(val) {
form.value.actualUsers = [...form.value.expectUsers] form.value.actualUsers = [...form.value.expectUsers]
} }
// 先过滤掉不存在的参会人员 // 先过滤掉不存在的参会人员
form.value.contentList = form.value.contentList.filter((item) => { form.value.meetingContentList = form.value.meetingContentList.filter((item) => {
return val.some((it) => it == item.id) return val.some((it) => it == item.userId)
}) })
// 再补充新增的 // 再补充新增的
val.map((item) => { val.map((item) => {
if (!form.value.contentList.some((it) => it.id == item)) { if (!form.value.meetingContentList.some((it) => it.userId == item)) {
form.value.contentList.push({ form.value.meetingContentList.push({
id: item, userId: item,
name: userOptions.value.find((it) => it.id == item).name, userName: userOptions.value.find((it) => it.id == item).name,
contentArr: [ userMeetingContentList: [
{ {
id: crypto.randomUUID(),
title: '主要内容',
content: '' content: ''
} }
] ]
}) })
} }
}) })
if (!form.value.meetingId) {
searchUserWait(val)
}
}
function searchUserWait(val) {
val.map((item) => {
if (!checkedUsers.value.includes(item)) {
checkedUsers.value.push(item)
// 新增会议时,根据参会人员拉取代办事项
getWaitPage({
pageNo: 1,
pageSize: -1,
workUserId: item,
creator: userStore.getUser.id,
completeStatus: 1
}).then((resp) => {
joinContent(resp.list)
})
}
})
}
function joinContent(arr) {
arr.map((wait) => {
let text = '<p>未完成事项:' + wait.title + '</p>'
text += wait.content
text += `<p>执行人:${wait.userNameStr}</p>`
text += `<p>截止日期:${formatDate(wait.endDate, 'YYYY-MM-DD')}</p>`
text += '<p><br></p>'
form.value.meetingContentList[0].userMeetingContentList[0].content =
text + form.value.meetingContentList[0].userMeetingContentList[0].content
})
} }
const router = useRouter() const router = useRouter()
async function submit() { async function submit(isDraft = false) {
// 校验表单 // 校验表单
if (!formRef.value) return if (!formRef.value) return
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
if (!valid) return if (!valid) return
try { try {
form.value.isDraft = isDraft
// 提交表单数据 // 提交表单数据
if (form.value.meetingId) { if (form.value.meetingId) {
if (form.value.status == 2 && !form.value.meetingSummary) { if (
form.value.status == 2 &&
form.value.meetingSummaryList.some((it) => it.summary.trim() == '')
) {
message.error('会议结束时,会议纪要不能为空') message.error('会议结束时,会议纪要不能为空')
return return
} else {
let text = ''
form.value.meetingSummaryList.map((item, index) => {
if (form.value.meetingSummaryList.length > 1) {
text += '<p>会议纪要' + (index + 1) + '</p>'
}
text += item.summary
if (item.isCreateAgentWork) {
text += `<p>执行人:`
item.agentUserList.map((it) => {
if (it) {
text += `${userOptions.value.find((user) => user.id == it).name} `
}
})
text += '</p>'
text += `<p>截止日期:${item.endDate}</p>`
}
text += '<p><br></p>'
})
form.value.meetingSummary = text
} }
// 更新会议 // 更新会议
await MeetingApi.updateMeeting(form.value) await MeetingApi.updateMeeting(form.value)
message.success('会议更新成功') message.success('会议更新成功')
} else { } else {
if (form.value.status == 1 && !form.value.meetingContent) {
message.error('预约会议时,会议内容不能为空')
return
}
form.value.actualUsers = [] form.value.actualUsers = []
// 新增会议 // 新增会议
await MeetingApi.createMeeting(form.value) await MeetingApi.createMeeting(form.value)

View File

@@ -0,0 +1,48 @@
<template>
<div>
<el-descriptions :column="1" border label-width="90px" class="w-full">
<el-descriptions-item label="会议主题">{{ meetingInfo.meetingSubject }}</el-descriptions-item>
<el-descriptions-item label="会议时间">{{ meetingInfo.startTime }}</el-descriptions-item>
<el-descriptions-item label="发起人">{{ meetingInfo.creatorName }}</el-descriptions-item>
<el-descriptions-item label="会议纪要">
<div v-dompurify-html="meetingInfo.meetingSummary"></div>
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script setup name="MeettingSummary">
import { getMeetingDetail } from '@/api/okr/meeting'
import { formatDate } from '@/utils/formatTime'
const route = useRoute()
onMounted(() => {
if (route.query.id && route.query.id != 0) {
// 这里可以调用API获取会议详情数据
getMeetingInfo(route.query.id)
} else {
console.error('会议不存在')
}
})
const meetingInfo = ref({})
const getMeetingInfo = async (meetingId) => {
try {
// 调用API获取会议详情
const resp = await getMeetingDetail({ meetingId })
if (resp) {
meetingInfo.value = {
meetingSubject: resp.meetingSubject,
meetingSummary: resp.meetingSummary,
startTime: formatDate(resp.startTime, 'YYYY-MM-DD HH:mm'),
creatorName: resp.creatorName
}
}
} catch (error) {
console.error('获取会议详情失败:', error)
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -11,6 +11,15 @@
@keyup.enter="handleSearch" @keyup.enter="handleSearch"
/> />
</el-form-item> </el-form-item>
<el-form-item>
<el-input
v-model="searchForm.creator"
placeholder="发起人"
style="width: 120px"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-select <el-select
v-model="searchForm.status" v-model="searchForm.status"
@@ -58,6 +67,7 @@
<el-table-column prop="meetingSubject" label="会议主题" /> <el-table-column prop="meetingSubject" label="会议主题" />
<el-table-column prop="startTime" label="会议时间" width="170px" /> <el-table-column prop="startTime" label="会议时间" width="170px" />
<el-table-column prop="meetingRoom" label="会议地点" width="140px" /> <el-table-column prop="meetingRoom" label="会议地点" width="140px" />
<el-table-column prop="creatorName" label="发起人" width="100px" />
<el-table-column prop="expectEndTime" label="预计结束时间" width="170px" /> <el-table-column prop="expectEndTime" label="预计结束时间" width="170px" />
<el-table-column prop="expectUserName" label="预约参会人员" /> <el-table-column prop="expectUserName" label="预约参会人员" />
<el-table-column prop="actualUserName" label="实际参会人员" /> <el-table-column prop="actualUserName" label="实际参会人员" />
@@ -117,6 +127,7 @@ const message = useMessage()
const searchForm = ref({ const searchForm = ref({
meetingSubject: undefined, meetingSubject: undefined,
creator: undefined,
status: '1', status: '1',
dateRange: [], dateRange: [],
nodeId: undefined, nodeId: undefined,

View File

@@ -4,7 +4,7 @@
<el-form <el-form
:model="form" :model="form"
ref="formRef" ref="formRef"
:disabled="formType == 'do'" :disabled="['do', 'detail'].includes(formType)"
:rules="rules" :rules="rules"
label-width="80px" label-width="80px"
class="flex-1" class="flex-1"
@@ -239,7 +239,9 @@ const followList = ref([])
function open(type, id) { function open(type, id) {
show.value = true show.value = true
title.value = { create: '新增待办', update: '修改待办', do: '更新待办进度' }[type] title.value = { create: '新增待办', update: '修改待办', do: '更新待办进度', detail: '待办详情' }[
type
]
formType.value = type formType.value = type
resetForm() resetForm()
if (id) { if (id) {

View File

@@ -139,6 +139,15 @@
> >
修改 修改
</el-button> </el-button>
<el-button
v-if="row.completeStatus == 2"
style="padding: 0; margin-right: 10px; margin-left: 0"
type="primary"
text
@click="handleDetail(row)"
>
详情
</el-button>
<el-button <el-button
v-if="row.creator == currentUserId && row.completeStatus == 1" v-if="row.creator == currentUserId && row.completeStatus == 1"
style="padding: 0; margin-right: 10px; margin-left: 0" style="padding: 0; margin-right: 10px; margin-left: 0"
@@ -271,6 +280,10 @@ function handleEdit(row) {
waitDialogRef.value.open('update', row.workId) waitDialogRef.value.open('update', row.workId)
} }
function handleDetail(row) {
waitDialogRef.value.open('detail', row.workId)
}
function handleDelete(row) { function handleDelete(row) {
message.confirm('确定删除待办事项吗?').then(() => { message.confirm('确定删除待办事项吗?').then(() => {
deleteWait(row.workId).then(() => { deleteWait(row.workId).then(() => {