caolin 6 days ago
commit 5df023dc3f
  1. 16
      src/api/okr/meeting.js
  2. 5
      src/permission.js
  3. 10
      src/router/modules/remaining.ts
  4. 9
      src/views/Basic/Dept/DeptForm.vue
  5. 46
      src/views/Home/FalseDiligenceReport/index.vue
  6. 33
      src/views/Home/Salary/index.vue
  7. 15
      src/views/Login/components/LoginForm.vue
  8. 247
      src/views/OKR/Meeting/MeetingInfo.vue
  9. 48
      src/views/OKR/Meeting/MeetingSummary.vue
  10. 11
      src/views/OKR/Meeting/index.vue

@ -43,3 +43,19 @@ export const getMeetingPage = (params) => {
// 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 }
})
}

@ -49,7 +49,10 @@ router.beforeEach(async (to, from, next) => {
})
const redirectPath = from.query.redirect || to.path
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)
} else {
next()

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

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

@ -267,14 +267,18 @@ async function getList() {
return pre.concat(cur.userDingAttendanceRespVOList)
}, [])
} else {
tableList.value = data.list.map((it, index) => ({
...it,
userDingAttendanceRespVOList: it.userDingAttendanceRespVOList.sort((pre, cur) =>
pre.employeeName.localeCompare(cur.employeeName)
),
id: index + 1,
edit: it.status == 1 ? '2' : '0'
}))
tableList.value = data.list.map((it, index) => {
const arr = it.userDingAttendanceRespVOList.filter((user) => user.needAttendance)
return {
...it,
userDingAttendanceRespVOList: multiFieldSort(arr, [
{ key: 'dept' },
{ key: 'employeeName' }
]),
id: index + 1,
edit: it.status == 1 ? '2' : '0'
}
})
}
total.value = data.total
} 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 }) {
if (row.userDingAttendanceRespVOList && row.userDingAttendanceRespVOList.length > 0) {
if (columnIndex === 0) {

@ -392,9 +392,10 @@ async function getList() {
} else {
tableList.value = data.list.map((it, index) => ({
...it,
userSalaryGrantRespVOList: it.userSalaryGrantRespVOList.sort((pre, cur) =>
pre.name.localeCompare(cur.name)
),
userSalaryGrantRespVOList: multiFieldSort(it.userSalaryGrantRespVOList, [
{ key: 'dept' },
{ key: 'name' }
]),
id: index + 1,
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()
function craeteSalary() {
createSalaryRef.value.open()

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

@ -4,8 +4,8 @@
<div class="flex justify-between mb-4 bg-white" v-if="!isDetail">
<b class="text-20px">{{ form.meetingId ? '修改会议' : '新增会议' }}</b>
<div>
<el-button @click="submit()">保存至草稿</el-button>
<el-button type="success" @click="submit()">保存</el-button>
<el-button @click="submit(true)">保存至草稿</el-button>
<el-button type="success" @click="submit(false)">保存</el-button>
</div>
</div>
<!-- </el-affix> -->
@ -108,7 +108,7 @@
</el-checkbox-group>
</el-form-item>
</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-input v-model="form.absentReason" placeholder="请输入缺席原因" />
</el-form-item>
@ -116,64 +116,59 @@
</el-row>
<el-row :gutter="20">
<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
v-for="item in form.contentList"
:key="item.id"
:label="item.name"
:name="item.id"
@click="userTabChange(item)"
v-for="item in form.meetingContentList"
:key="item.userId"
:label="item.userName"
:name="item.userId + ''"
/>
</el-tabs>
<el-tabs v-model="currentContentId" tab-position="left" addable @edit="handleTabsEdit">
<el-tab-pane
v-for="(item, index) in form.contentList.find((it) => it.id == currentUserId)
?.contentArr"
:key="item.id"
:label="item.title"
:name="item.id"
v-for="(item, index) in form.meetingContentList.find(
(it) => it.userId == currentUserId
)?.userMeetingContentList"
:key="index"
:label="'内容' + (index + 1)"
:name="index"
:closable="index > 0"
>
<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%" />
</el-tab-pane>
</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 :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-else>
<el-tabs v-model="summaryIdx" addable @edit="meetingSummaryEdit">
<el-tab-pane
v-for="(item, index) in form.meetingList"
v-for="(item, index) in form.meetingSummaryList"
:key="index"
:label="`会议纪要${index || ''}`"
:name="index"
:closable="index > 0"
>
<Editor
v-model="item.meetingSummary"
v-model="item.summary"
:toolbarConfig="toolbarConfig"
height="300px"
height="350px"
placeholder="请输入会议纪要"
style="width: 100%"
/>
<div class="mt-10px">
<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="false" :value="false"> 不创建待办 </el-radio>
</el-radio-group>
</el-form-item>
</div>
<div class="flex items-center" v-if="item.createWait">
<div class="flex items-center" v-if="item.isCreateAgentWork">
<el-select
class="flex-1"
v-model="item.userIdList"
v-model="item.agentUserList"
placeholder="选择执行人"
clearable
filterable
@ -204,20 +199,23 @@
</div>
<div class="mt-10px">
<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>
</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-row>
</el-form>
@ -246,7 +244,7 @@ const defaultProps = {
const isDetail = route.query.isDetail
const currentUserId = ref(undefined)
const currentContentId = ref('') //
const currentContentId = ref(0) //
const summaryIdx = ref(0) //
const toolbarConfig = {
@ -254,25 +252,24 @@ const toolbarConfig = {
}
onMounted(async () => {
getWxGroupOptions()
await getOptions()
if (route.params.id && route.params.id != 0) {
// API
getMeetingInfo(route.params.id)
} else {
form.value.contentList = [
form.value.meetingContentList = [
{
id: userStore.getUser.id + '',
name: userStore.getUser.nickname,
contentArr: [
userId: userStore.getUser.id + '',
userName: userStore.getUser.nickname,
userMeetingContentList: [
{
id: crypto.randomUUID(),
title: '主要内容',
content: ''
}
]
}
]
currentContentId.value = form.value.contentList[0].contentArr[0].id
currentContentId.value = 0
}
})
@ -287,24 +284,12 @@ function getOptions() {
})
userOptions.value = employeeResp.map((it) => ({ ...it, id: it.id + '' }))
form.value.expectUsers = [userStore.getUser.id + ''] //
// form.value.actualUsers = [userStore.getUser.id + '']
currentUserId.value = userStore.getUser.id + '' //
// handleUserChange()
})
.catch((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({
@ -320,8 +305,8 @@ const form = ref({
meetingContent: '',
meetingSummary: '',
absentReason: '',
contentList: [],
meetingList: []
meetingContentList: [],
meetingSummaryList: []
})
const rules = {
meetingSubject: [{ required: true, message: '请输入会议主题', trigger: 'blur' }],
@ -349,6 +334,24 @@ const getMeetingInfo = async (meetingId) => {
const resp = await MeetingApi.getMeetingDetail({ meetingId })
loading.value = false
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,
...resp,
@ -356,16 +359,12 @@ const getMeetingInfo = async (meetingId) => {
expectEndTime: formatDate(resp.expectEndTime, 'YYYY-MM-DD HH:mm'),
expectUsers: resp.expectUsers || [],
actualUsers: resp.actualUsers || [],
meetingList: resp.meetingList || [
{
meetingSummary: resp.meetingSummary || '',
userIdList: [],
endDate: '',
createWait: true
}
]
meetingSummaryList: summaryList
}
handleUserChange(resp.expectUsers)
currentContentId.value = 0
expectUserOptions.value = userOptions.value.filter((user) =>
form.value.expectUsers.some((it) => it == user.id)
)
}
} catch (error) {
loading.value = false
@ -373,61 +372,57 @@ const getMeetingInfo = async (meetingId) => {
}
}
function userTabChange(val) {
currentContentId.value = val.contentArr[0].id
const groupOptions = ref([])
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) {
if (action === 'add') {
form.value.contentList.forEach((item) => {
if (item.id === currentUserId.value) {
item.contentArr.push({
id: crypto.randomUUID(),
title: '次要内容',
form.value.meetingContentList.forEach((item) => {
if (item.userId == currentUserId.value) {
item.userMeetingContentList.push({
content: ''
})
}
})
} else if (action === 'remove') {
form.value.contentList.forEach((item) => {
if (item.id === currentUserId.value) {
const idx = item.contentArr.findIndex((item) => item.id == targetName)
item.contentArr.splice(idx, 1)
form.value.meetingContentList.forEach((item) => {
if (item.userId == currentUserId.value) {
item.userMeetingContentList.splice(targetName, 1)
//
if (currentContentId.value === targetName) {
currentContentId.value = item.contentArr[idx - 1]?.id || item.contentArr[idx + 1]?.id
if (currentContentId.value == targetName) {
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) {
if (action === 'add') {
form.value.meetingList.push({
meetingSummary: '',
userIdList: [],
form.value.meetingSummaryList.push({
summary: '',
agentUserList: [],
endDate: '',
createWait: true
isCreateAgentWork: true
})
} else if (action === 'remove') {
form.value.meetingList.splice(targetName, 1)
summaryIdx.value = 0
form.value.meetingSummaryList.splice(targetName, 1)
summaryIdx.value = targetName - 1
}
}
@ -440,19 +435,17 @@ function handleUserChange(val) {
form.value.actualUsers = [...form.value.expectUsers]
}
//
form.value.contentList = form.value.contentList.filter((item) => {
return val.some((it) => it == item.id)
form.value.meetingContentList = form.value.meetingContentList.filter((item) => {
return val.some((it) => it == item.userId)
})
//
val.map((item) => {
if (!form.value.contentList.some((it) => it.id == item)) {
form.value.contentList.push({
id: item,
name: userOptions.value.find((it) => it.id == item).name,
contentArr: [
if (!form.value.meetingContentList.some((it) => it.userId == item)) {
form.value.meetingContentList.push({
userId: item,
userName: userOptions.value.find((it) => it.id == item).name,
userMeetingContentList: [
{
id: crypto.randomUUID(),
title: '主要内容',
content: ''
}
]
@ -462,27 +455,47 @@ function handleUserChange(val) {
}
const router = useRouter()
async function submit() {
async function submit(isDraft = false) {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
try {
form.value.isDraft = isDraft
//
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('会议结束时,会议纪要不能为空')
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)
message.success('会议更新成功')
} else {
if (form.value.status == 1 && !form.value.meetingContent) {
message.error('预约会议时,会议内容不能为空')
return
}
form.value.actualUsers = []
//
await MeetingApi.createMeeting(form.value)

@ -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>

@ -11,6 +11,15 @@
@keyup.enter="handleSearch"
/>
</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-select
v-model="searchForm.status"
@ -58,6 +67,7 @@
<el-table-column prop="meetingSubject" label="会议主题" />
<el-table-column prop="startTime" label="会议时间" width="170px" />
<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="expectUserName" label="预约参会人员" />
<el-table-column prop="actualUserName" label="实际参会人员" />
@ -117,6 +127,7 @@ const message = useMessage()
const searchForm = ref({
meetingSubject: undefined,
creator: undefined,
status: '1',
dateRange: [],
nodeId: undefined,

Loading…
Cancel
Save