删除全部页面
This commit is contained in:
parent
12be97d132
commit
f647208b6e
|
@ -5,9 +5,6 @@
|
|||
<el-row :gutter="16" justify="space-between">
|
||||
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex items-center">
|
||||
<el-avatar :src="avatar" :size="70" class="mr-16px">
|
||||
<img src="@/assets/imgs/avatar.gif" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<div class="text-20px">
|
||||
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
|
||||
|
@ -150,9 +147,6 @@
|
|||
<el-skeleton :loading="loading" animated>
|
||||
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
|
||||
<div class="flex items-center">
|
||||
<el-avatar :src="avatar" :size="35" class="mr-16px">
|
||||
<img src="@/assets/imgs/avatar.gif" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<div class="text-14px">
|
||||
<Highlight :keys="item.keys.map((v) => t(v))">
|
||||
|
@ -187,7 +181,7 @@ const { t } = useI18n()
|
|||
const userStore = useUserStore()
|
||||
const { setWatermark } = useWatermark()
|
||||
const loading = ref(true)
|
||||
const avatar = userStore.getUser.avatar
|
||||
// const avatar = userStore.getUser.avatar
|
||||
const username = userStore.getUser.nickname
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
// 获取统计数
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="user w-1/3" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.user.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ProfileUser />
|
||||
</el-card>
|
||||
<el-card class="user ml-3 w-2/3" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.info.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
||||
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
||||
<BasicInfo />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
||||
<ResetPwd />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
||||
<UserSocial v-model:activeName="activeName" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
||||
|
||||
const { t } = useI18n()
|
||||
defineOptions({ name: 'Profile' })
|
||||
const activeName = ref('basicInfo')
|
||||
</script>
|
||||
<style scoped>
|
||||
.user {
|
||||
max-height: 960px;
|
||||
padding: 15px 20px 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-card .el-card__header, .el-card .el-card__body) {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.profile-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
|
||||
.el-tabs--left .el-tabs__content {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,96 +0,0 @@
|
|||
<template>
|
||||
<Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
|
||||
<template #sex="form">
|
||||
<el-radio-group v-model="form['sex']">
|
||||
<el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
|
||||
<el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</Form>
|
||||
<div style="text-align: center">
|
||||
<XButton :title="t('common.save')" type="primary" @click="submit()" />
|
||||
<XButton :title="t('common.reset')" type="danger" @click="init()" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import {
|
||||
getUserProfile,
|
||||
updateUserProfile,
|
||||
UserProfileUpdateReqVO
|
||||
} from '@/api/system/user/profile'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BasicInfo' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const userStore = useUserStore()
|
||||
// 表单校验
|
||||
const rules = reactive<FormRules>({
|
||||
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
||||
email: [
|
||||
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' },
|
||||
{
|
||||
type: 'email',
|
||||
message: t('profile.rules.truemail'),
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: t('profile.rules.phone'), trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
message: t('profile.rules.truephone'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'nickname',
|
||||
label: t('profile.user.nickname'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
label: t('profile.user.mobile'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: t('profile.user.email'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'sex',
|
||||
label: t('profile.user.sex'),
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
])
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const submit = () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const data = unref(formRef)?.formModel as UserProfileUpdateReqVO
|
||||
await updateUserProfile(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
const profile = await init()
|
||||
userStore.setUserNicknameAction(profile.nickname)
|
||||
}
|
||||
})
|
||||
}
|
||||
const init = async () => {
|
||||
const res = await getUserProfile()
|
||||
unref(formRef)?.setValues(res)
|
||||
return res
|
||||
}
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
</script>
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<UserAvatar :img="userInfo?.avatar" />
|
||||
</div>
|
||||
<ul class="list-group list-group-striped">
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:user" />
|
||||
{{ t('profile.user.username') }}
|
||||
<div class="pull-right">{{ userInfo?.username }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:phone" />
|
||||
{{ t('profile.user.mobile') }}
|
||||
<div class="pull-right">{{ userInfo?.mobile }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="fontisto:email" />
|
||||
{{ t('profile.user.email') }}
|
||||
<div class="pull-right">{{ userInfo?.email }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="carbon:tree-view-alt" />
|
||||
{{ t('profile.user.dept') }}
|
||||
<div v-if="userInfo?.dept" class="pull-right">{{ userInfo?.dept.name }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:suitcase" />
|
||||
{{ t('profile.user.posts') }}
|
||||
<div v-if="userInfo?.posts" class="pull-right">
|
||||
{{ userInfo?.posts.map((post) => post.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="icon-park-outline:peoples" />
|
||||
{{ t('profile.user.roles') }}
|
||||
<div v-if="userInfo?.roles" class="pull-right">
|
||||
{{ userInfo?.roles.map((role) => role.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:calendar" />
|
||||
{{ t('profile.user.createTime') }}
|
||||
<div class="pull-right">{{ formatDate(userInfo.createTime) }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import UserAvatar from './UserAvatar.vue'
|
||||
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
|
||||
defineOptions({ name: 'ProfileUser' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const userInfo = ref({} as ProfileVO)
|
||||
const getUserInfo = async () => {
|
||||
const users = await getUserProfile()
|
||||
userInfo.value = users
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getUserInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-center {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-group-striped > .list-group-item {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding: 11px 0;
|
||||
margin-bottom: -1px;
|
||||
font-size: 13px;
|
||||
border-top: 1px solid #e7eaec;
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
</style>
|
|
@ -1,73 +0,0 @@
|
|||
<template>
|
||||
<el-form ref="formRef" :model="password" :rules="rules" :label-width="200">
|
||||
<el-form-item :label="t('profile.password.oldPassword')" prop="oldPassword">
|
||||
<InputPassword v-model="password.oldPassword" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.password.newPassword')" prop="newPassword">
|
||||
<InputPassword v-model="password.newPassword" strength />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.password.confirmPassword')" prop="confirmPassword">
|
||||
<InputPassword v-model="password.confirmPassword" strength />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<XButton :title="t('common.save')" type="primary" @click="submit(formRef)" />
|
||||
<XButton :title="t('common.reset')" type="danger" @click="reset(formRef)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { updateUserPassword } from '@/api/system/user/profile'
|
||||
|
||||
defineOptions({ name: 'ResetPwd' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const formRef = ref<FormInstance>()
|
||||
const password = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 表单校验
|
||||
const equalToPassword = (_rule, value, callback) => {
|
||||
if (password.newPassword !== value) {
|
||||
callback(new Error(t('profile.password.diffPwd')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
oldPassword: [
|
||||
{ required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: t('profile.password.newPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: t('profile.password.cfPwdMsg'), trigger: 'blur' },
|
||||
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const submit = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
await updateUserPassword(password.oldPassword, password.newPassword)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const reset = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,45 +0,0 @@
|
|||
<template>
|
||||
<div class="change-avatar">
|
||||
<CropperAvatar
|
||||
ref="cropperRef"
|
||||
:btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
|
||||
:showBtn="false"
|
||||
:value="img"
|
||||
width="120px"
|
||||
@change="handelUpload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { uploadAvatar } from '@/api/system/user/profile'
|
||||
import { CropperAvatar } from '@/components/Cropper'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
|
||||
defineOptions({ name: 'UserAvatar' })
|
||||
|
||||
defineProps({
|
||||
img: propTypes.string.def('')
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
const cropperRef = ref()
|
||||
const handelUpload = async ({ data }) => {
|
||||
const res = await uploadAvatar({ avatarFile: data })
|
||||
cropperRef.value.close()
|
||||
userStore.setUserAvatarAction(res.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.change-avatar {
|
||||
img {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,107 +0,0 @@
|
|||
<template>
|
||||
<el-table :data="socialUsers" :show-header="false">
|
||||
<el-table-column fixed="left" title="序号" type="seq" width="60" />
|
||||
<el-table-column align="left" label="社交平台" width="120">
|
||||
<template #default="{ row }">
|
||||
<img :src="row.img" alt="" class="h-5 align-middle" />
|
||||
<p class="mr-5">{{ row.title }}</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.openid">
|
||||
已绑定
|
||||
<XTextButton class="mr-5" title="(解绑)" type="primary" @click="unbind(row)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
未绑定
|
||||
<XTextButton class="mr-5" title="(绑定)" type="primary" @click="bind(row)" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
||||
|
||||
defineOptions({ name: 'UserSocial' })
|
||||
defineProps<{
|
||||
activeName: string
|
||||
}>()
|
||||
const message = useMessage()
|
||||
const socialUsers = ref<any[]>([])
|
||||
const userInfo = ref<ProfileVO>()
|
||||
|
||||
const initSocial = async () => {
|
||||
socialUsers.value = [] // 重置避免无限增长
|
||||
const res = await getUserProfile()
|
||||
userInfo.value = res
|
||||
for (const i in SystemUserSocialTypeEnum) {
|
||||
const socialUser = { ...SystemUserSocialTypeEnum[i] }
|
||||
socialUsers.value.push(socialUser)
|
||||
if (userInfo.value?.socialUsers) {
|
||||
for (const j in userInfo.value.socialUsers) {
|
||||
if (socialUser.type === userInfo.value.socialUsers[j].type) {
|
||||
socialUser.openid = userInfo.value.socialUsers[j].openid
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const route = useRoute()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:activeName', v: string): void
|
||||
}>()
|
||||
const bindSocial = () => {
|
||||
// 社交绑定
|
||||
const type = getUrlValue('type')
|
||||
const code = route.query.code
|
||||
const state = route.query.state
|
||||
if (!code) {
|
||||
return
|
||||
}
|
||||
socialBind(type, code, state).then(() => {
|
||||
message.success('绑定成功')
|
||||
emit('update:activeName', 'userSocial')
|
||||
})
|
||||
}
|
||||
|
||||
// 双层 encode 需要在回调后进行 decode
|
||||
function getUrlValue(key: string): string {
|
||||
const url = new URL(decodeURIComponent(location.href))
|
||||
return url.searchParams.get(key) ?? ''
|
||||
}
|
||||
|
||||
const bind = (row) => {
|
||||
// 双层 encode 解决钉钉回调 type 参数丢失的问题
|
||||
const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)
|
||||
// 进行跳转
|
||||
socialAuthRedirect(row.type, encodeURIComponent(redirectUri)).then((res) => {
|
||||
window.location.href = res
|
||||
})
|
||||
}
|
||||
const unbind = async (row) => {
|
||||
const res = await socialUnbind(row.type, row.openid)
|
||||
if (res) {
|
||||
row.openid = undefined
|
||||
}
|
||||
message.success('解绑成功')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initSocial()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
() => {
|
||||
bindSocial()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
|
@ -1,7 +0,0 @@
|
|||
import BasicInfo from './BasicInfo.vue'
|
||||
import ProfileUser from './ProfileUser.vue'
|
||||
import ResetPwd from './ResetPwd.vue'
|
||||
import UserAvatarVue from './UserAvatar.vue'
|
||||
import UserSocial from './UserSocial.vue'
|
||||
|
||||
export { BasicInfo, ProfileUser, ResetPwd, UserAvatarVue, UserSocial }
|
|
@ -1,28 +0,0 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Redirect' })
|
||||
|
||||
const { currentRoute, replace } = useRouter()
|
||||
const { params, query } = unref(currentRoute)
|
||||
const { path, _redirect_type = 'path' } = params
|
||||
|
||||
Reflect.deleteProperty(params, '_redirect_type')
|
||||
Reflect.deleteProperty(params, 'path')
|
||||
|
||||
const _path = Array.isArray(path) ? path.join('/') : path
|
||||
|
||||
if (_redirect_type === 'name') {
|
||||
replace({
|
||||
name: _path,
|
||||
query,
|
||||
params
|
||||
})
|
||||
} else {
|
||||
replace({
|
||||
path: _path.startsWith('/') ? _path : '/' + _path,
|
||||
query
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -1,166 +0,0 @@
|
|||
<template>
|
||||
<el-container>
|
||||
<!-- 左侧:会话列表 -->
|
||||
<el-aside width="260px" class="conversation-container">
|
||||
<!-- 左顶部:新建对话 -->
|
||||
<el-button class="w-1/1" type="primary">
|
||||
<Icon icon="ep:plus" class="mr-5px" />
|
||||
新建对话
|
||||
</el-button>
|
||||
<!-- 左顶部:搜索对话 -->
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
class="mt-10px"
|
||||
placeholder="搜索历史记录"
|
||||
@keyup="searchConversation"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="ep:search" />
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- 左中间:对话列表 -->
|
||||
<div class="conversation-list" :style="{ height: leftHeight + 'px' }">
|
||||
<el-row v-for="conversation in conversationList" :key="conversation.id">
|
||||
<div
|
||||
:class="conversation.id === conversationId ? 'conversation active' : 'conversation'"
|
||||
@click="changeConversation(conversation)"
|
||||
>
|
||||
<el-image :src="conversation.avatar" class="avatar" />
|
||||
<span class="title">{{ conversation.title }}</span>
|
||||
<span class="button">
|
||||
<!-- TODO 芋艿:缺置顶按钮 -->
|
||||
<el-icon title="编辑" @click="updateConversationTitle(conversation)">
|
||||
<Icon icon="ep:edit" />
|
||||
</el-icon>
|
||||
<el-icon title="删除会话" @click="deleteConversationTitle(conversation)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
<!-- 左底部:工具栏 TODO 芋艿:50% 不太对 -->
|
||||
<div class="tool-box">
|
||||
<sapn class="w-1/2"> <Icon icon="ep:user" /> 角色仓库 </sapn>
|
||||
<sapn class="w-1/2"> <Icon icon="ep:delete" />清空未置顶对话</sapn>
|
||||
</div>
|
||||
</el-aside>
|
||||
<!-- 右侧:会话详情 -->
|
||||
<el-container class="detail-container">
|
||||
<!-- 右顶部 TODO 芋艿:右对齐 -->
|
||||
<el-header class="header">
|
||||
<el-button>3.5-turbo-0125 <Icon icon="ep:setting" /></el-button>
|
||||
<el-button>
|
||||
<Icon icon="ep:user" />
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="ep:download" />
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="ep:arrow-up" />
|
||||
</el-button>
|
||||
</el-header>
|
||||
<el-main>对话列表</el-main>
|
||||
<el-footer>发送消息框</el-footer>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const conversationList = [
|
||||
{
|
||||
id: 1,
|
||||
title: '测试标题',
|
||||
avatar:
|
||||
'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '测试对话',
|
||||
avatar:
|
||||
'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png'
|
||||
}
|
||||
]
|
||||
const conversationId = ref(1)
|
||||
const searchName = ref('')
|
||||
const leftHeight = window.innerHeight - 240 // TODO 芋艿:这里还不太对
|
||||
|
||||
const changeConversation = (conversation) => {
|
||||
console.log(conversation)
|
||||
conversationId.value = conversation.id
|
||||
// TODO 芋艿:待实现
|
||||
}
|
||||
|
||||
const updateConversationTitle = (conversation) => {
|
||||
console.log(conversation)
|
||||
// TODO 芋艿:待实现
|
||||
}
|
||||
|
||||
const deleteConversationTitle = (conversation) => {
|
||||
console.log(conversation)
|
||||
// TODO 芋艿:待实现
|
||||
}
|
||||
|
||||
const searchConversation = () => {
|
||||
// TODO 芋艿:待实现
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.conversation-container {
|
||||
.conversation-list {
|
||||
.conversation {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 5px 5px 0 0;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
// TODO 芋艿:这里不太对
|
||||
background-color: #343540;
|
||||
.button {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
padding: 5px 10px;
|
||||
max-width: 220px;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.button {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
top: 16px;
|
||||
.el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-box {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 0 20px 10px 20px;
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,124 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="分类名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入分类名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类标志" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入分类标志" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类排序" prop="sort">
|
||||
<el-input-number
|
||||
v-model="formData.sort"
|
||||
placeholder="请输入分类排序"
|
||||
class="!w-1/1"
|
||||
:precision="0"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
|
||||
/** BPM 流程分类 表单 */
|
||||
defineOptions({ name: 'CategoryForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
sort: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '分类名不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '分类标志不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '分类状态不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await CategoryApi.getCategory(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as CategoryVO
|
||||
if (formType.value === 'create') {
|
||||
await CategoryApi.createCategory(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CategoryApi.updateCategory(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
sort: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,200 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="分类名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入分类名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类标志" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
placeholder="请输入分类标志"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择分类状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:category:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="分类编号" align="center" prop="id" />
|
||||
<el-table-column label="分类名" align="center" prop="name" />
|
||||
<el-table-column label="分类标志" align="center" prop="code" />
|
||||
<el-table-column label="分类描述" align="center" prop="description" />
|
||||
<el-table-column label="分类状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分类排序" align="center" prop="sort" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:category:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:category:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CategoryForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
// import download from '@/utils/download'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import CategoryForm from './CategoryForm.vue'
|
||||
|
||||
/** BPM 流程分类 列表 */
|
||||
defineOptions({ name: 'BpmCategory' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<CategoryVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
// const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CategoryApi.getCategoryPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await CategoryApi.deleteCategory(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,149 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="定义编号" align="center" prop="id" width="400" />
|
||||
<el-table-column label="流程名称" align="center" prop="name" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="定义分类" align="center" prop="categoryName" width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button v-else type="primary" link @click="handleFormDetail(scope.row)">
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row">v{{ scope.row.version }}</el-tag>
|
||||
<el-tag type="warning" v-else>未部署</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="version" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
|
||||
<el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="部署时间"
|
||||
align="center"
|
||||
prop="deploymentTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="定义描述"
|
||||
align="center"
|
||||
prop="description"
|
||||
width="300"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
|
||||
<!-- 弹窗:流程模型图的预览 -->
|
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
v-model="bpmnXml"
|
||||
:value="bpmnXml as any"
|
||||
v-bind="bpmnControlForm"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
|
||||
defineOptions({ name: 'BpmProcessDefinition' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
key: query.key
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await DefinitionApi.getProcessDefinitionPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程图的详情按钮操作 */
|
||||
const bpmnDetailVisible = ref(false)
|
||||
const bpmnXml = ref(null)
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const handleBpmnDetail = async (row) => {
|
||||
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
|
||||
bpmnDetailVisible.value = true
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,121 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 表单设计器 -->
|
||||
<FcDesigner ref="designer" height="780px">
|
||||
<template #handle>
|
||||
<el-button round size="small" type="primary" @click="handleSave">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</FcDesigner>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单保存的弹窗 -->
|
||||
<Dialog v-model="dialogVisible" title="保存表单" width="600">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="表单名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入表单名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useFormCreateDesigner } from '@/components/FormCreate'
|
||||
|
||||
defineOptions({ name: 'BpmFormEditor' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const { query } = useRoute() // 路由信息
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const designer = ref() // 表单设计器
|
||||
useFormCreateDesigner(designer) // 表单设计器增强
|
||||
const dialogVisible = ref(false) // 弹窗是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:提交的按钮禁用
|
||||
const formData = ref({
|
||||
name: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
remark: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 处理保存按钮 */
|
||||
const handleSave = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as FormApi.FormVO
|
||||
data.conf = encodeConf(designer) // 表单配置
|
||||
data.fields = encodeFields(designer) // 表单字段
|
||||
if (!data.id) {
|
||||
await FormApi.createForm(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await FormApi.updateForm(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
close()
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
push('/bpm/manager/form')
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 场景一:新增表单
|
||||
const id = query.id as unknown as number
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
// 场景二:修改表单
|
||||
const data = await FormApi.getForm(id)
|
||||
formData.value = data
|
||||
setConfAndFields(designer, data.conf, data.fields)
|
||||
})
|
||||
</script>
|
|
@ -1,195 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="审批接入(流程表单)" url="https://doc.iocoder.cn/bpm/use-bpm-form/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="表单名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入表单名"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['bpm:form:create']" plain type="primary" @click="openForm()">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="编号" prop="id" />
|
||||
<el-table-column align="center" label="表单名" prop="name" />
|
||||
<el-table-column align="center" label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="备注" prop="remark" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['bpm:form:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm(scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['bpm:form:query']" link @click="openDetail(scope.row.id)">
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['bpm:form:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单详情的弹窗 -->
|
||||
<Dialog v-model="detailVisible" title="表单详情" width="800">
|
||||
<form-create :option="detailData.option" :rule="detailData.rule" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
|
||||
defineOptions({ name: 'BpmForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { currentRoute, push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await FormApi.getFormPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const openForm = (id?: number) => {
|
||||
const toRouter: { name: string; query?: { id: number } } = {
|
||||
name: 'BpmFormEditor'
|
||||
}
|
||||
// 表单新建的时候id传的是event需要排除
|
||||
if (typeof id === 'number') {
|
||||
toRouter.query = {
|
||||
id
|
||||
}
|
||||
}
|
||||
push(toRouter)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FormApi.deleteForm(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const openDetail = async (rowId: number) => {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(rowId)
|
||||
setConfAndFields2(detailData, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
detailVisible.value = true
|
||||
}
|
||||
/**表单保存返回后重新加载列表 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
getList()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,132 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="组名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入组名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="formData.description" placeholder="请输入描述" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="成员" prop="userIds">
|
||||
<el-select v-model="formData.userIds" multiple placeholder="请选择成员">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'UserGroupForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
userIds: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
|
||||
userIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await UserGroupApi.getUserGroup(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as UserGroupApi.UserGroupVO
|
||||
if (formType.value === 'create') {
|
||||
await UserGroupApi.createUserGroup(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await UserGroupApi.updateUserGroup(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
userIds: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,191 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="组名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入组名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:user-group:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="组名" align="center" prop="name" />
|
||||
<el-table-column label="描述" align="center" prop="description" />
|
||||
<el-table-column label="成员" align="center">
|
||||
<template #default="scope">
|
||||
<span v-for="userId in scope.row.userIds" :key="userId" class="pr-5px">
|
||||
{{ userList.find((user) => user.id === userId)?.nickname }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:user-group:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:user-group:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<UserGroupForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import UserGroupForm from './UserGroupForm.vue'
|
||||
import { UserVO } from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmUserGroup' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
status: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const userList = ref<UserVO[]>([]) // 用户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await UserGroupApi.getUserGroupPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await UserGroupApi.deleteUserGroup(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
|
@ -1,239 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="600">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="formData.key"
|
||||
:disabled="!!formData.id"
|
||||
placeholder="请输入流标标识"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
v-if="!formData.id"
|
||||
class="item"
|
||||
content="新建后,流程标识不可修改!"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<i class="el-icon-question" style="padding-left: 5px"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else class="item" content="流程标识不可修改!" effect="light" placement="top">
|
||||
<i class="el-icon-question" style="padding-left: 5px"></i>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="formData.name"
|
||||
:disabled="!!formData.id"
|
||||
clearable
|
||||
placeholder="请输入流程名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.id" label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="formData.category"
|
||||
clearable
|
||||
placeholder="请选择流程分类"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.id" label="流程图标" prop="icon">
|
||||
<UploadImg v-model="formData.icon" :limit="1" height="128px" width="128px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程描述" prop="description">
|
||||
<el-input v-model="formData.description" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
<div v-if="formData.id">
|
||||
<el-form-item label="表单类型" prop="formType">
|
||||
<el-radio-group v-model="formData.formType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
|
||||
<el-select v-model="formData.formId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="form in formList"
|
||||
:key="form.id"
|
||||
:label="form.name"
|
||||
:value="form.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.formType === 20"
|
||||
label="表单提交路由"
|
||||
prop="formCustomCreatePath"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.formCustomCreatePath"
|
||||
placeholder="请输入表单提交路由"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<i class="el-icon-question" style="padding-left: 5px"></i>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.formType === 20"
|
||||
label="表单查看地址"
|
||||
prop="formCustomViewPath"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.formCustomViewPath"
|
||||
placeholder="请输入表单查看的组件地址"
|
||||
style="width: 330px"
|
||||
/>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<i class="el-icon-question" style="padding-left: 5px"></i>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'ModelForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
formType: 10,
|
||||
name: '',
|
||||
category: undefined,
|
||||
icon: undefined,
|
||||
description: '',
|
||||
formId: '',
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
|
||||
key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
|
||||
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
|
||||
icon: [{ required: true, message: '参数图标不能为空', trigger: 'blur' }],
|
||||
value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
|
||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const formList = ref([]) // 流程表单的下拉框的数据
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ModelApi.getModel(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得流程表单的下拉框的数据
|
||||
formList.value = await FormApi.getFormSimpleList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ModelApi.ModelVO
|
||||
if (formType.value === 'create') {
|
||||
await ModelApi.createModel(data)
|
||||
// 提示,引导用户做后续的操作
|
||||
await ElMessageBox.alert(
|
||||
'<strong>新建模型成功!</strong>后续需要执行如下 3 个步骤:' +
|
||||
'<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
|
||||
'<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
|
||||
'<div>3. 点击【发布流程】按钮,完成流程的最终发布</div>' +
|
||||
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
|
||||
'重要提示',
|
||||
{
|
||||
dangerouslyUseHTMLString: true,
|
||||
type: 'success'
|
||||
}
|
||||
)
|
||||
} else {
|
||||
await ModelApi.updateModel(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
formType: 10,
|
||||
name: '',
|
||||
category: undefined,
|
||||
icon: '',
|
||||
description: '',
|
||||
formId: '',
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,141 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="导入流程" width="400">
|
||||
<div>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="importUrl"
|
||||
:auto-upload="false"
|
||||
:data="formData"
|
||||
:disabled="formLoading"
|
||||
:headers="uploadHeaders"
|
||||
:limit="1"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
accept=".bpmn, .xml"
|
||||
drag
|
||||
name="bpmnFile"
|
||||
>
|
||||
<Icon class="el-icon--upload" icon="ep:upload-filled" />
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="color: red">
|
||||
提示:仅允许导入“bpm”或“xml”格式文件!
|
||||
</div>
|
||||
<div>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="formData.key"
|
||||
placeholder="请输入流标标识"
|
||||
style="width: 250px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input v-model="formData.name" clearable placeholder="请输入流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程描述" prop="description">
|
||||
<el-input v-model="formData.description" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'ModelImportForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
key: '',
|
||||
name: '',
|
||||
description: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const uploadRef = ref() // 上传 Ref
|
||||
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref([]) // 文件列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
formLoading.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = async (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
// 提示成功
|
||||
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('导入流程失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
// 重置表单
|
||||
formData.value = {
|
||||
key: '',
|
||||
name: '',
|
||||
description: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
|
@ -1,115 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 流程设计器,负责绘制流程等 -->
|
||||
<MyProcessDesigner
|
||||
key="designer"
|
||||
v-if="xmlString !== undefined"
|
||||
v-model="xmlString"
|
||||
:value="xmlString"
|
||||
v-bind="controlForm"
|
||||
keyboard
|
||||
ref="processDesigner"
|
||||
@init-finished="initModeler"
|
||||
:additionalModel="controlForm.additionalModel"
|
||||
@save="save"
|
||||
/>
|
||||
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
||||
<MyProcessPenal
|
||||
key="penal"
|
||||
:bpmnModeler="modeler as any"
|
||||
:prefix="controlForm.prefix"
|
||||
class="process-panel"
|
||||
:model="model"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MyProcessDesigner, MyProcessPenal } from '@/components/bpmnProcessDesigner/package'
|
||||
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
|
||||
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
|
||||
// 自定义左侧菜单(修改 默认任务 为 用户任务)
|
||||
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
|
||||
defineOptions({ name: 'BpmModelEditor' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const { query } = useRoute() // 路由的查询
|
||||
const message = useMessage() // 国际化
|
||||
|
||||
const xmlString = ref(undefined) // BPMN XML
|
||||
const modeler = ref(null) // BPMN Modeler
|
||||
const controlForm = ref({
|
||||
simulation: true,
|
||||
labelEditing: false,
|
||||
labelVisible: false,
|
||||
prefix: 'flowable',
|
||||
headerButtonSize: 'mini',
|
||||
additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
|
||||
})
|
||||
const model = ref<ModelApi.ModelVO>() // 流程模型的信息
|
||||
|
||||
/** 初始化 modeler */
|
||||
const initModeler = (item) => {
|
||||
setTimeout(() => {
|
||||
modeler.value = item
|
||||
}, 10)
|
||||
}
|
||||
|
||||
/** 添加/修改模型 */
|
||||
const save = async (bpmnXml) => {
|
||||
const data = {
|
||||
...model.value,
|
||||
bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
|
||||
} as unknown as ModelApi.ModelVO
|
||||
// 提交
|
||||
if (data.id) {
|
||||
await ModelApi.updateModel(data)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await ModelApi.createModel(data)
|
||||
message.success('新增成功')
|
||||
}
|
||||
// 跳转回去
|
||||
close()
|
||||
}
|
||||
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
const modelId = query.modelId as unknown as number
|
||||
if (!modelId) {
|
||||
message.error('缺少模型 modelId 编号')
|
||||
return
|
||||
}
|
||||
// 查询模型
|
||||
const data = await ModelApi.getModel(modelId)
|
||||
if (!data.bpmnXml) {
|
||||
// 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的
|
||||
data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
|
||||
<process id="${data.key}" name="${data.name}" isExecutable="true" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram">
|
||||
<bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>`
|
||||
}
|
||||
model.value = {
|
||||
...data,
|
||||
bpmnXml: undefined // 清空 bpmnXml 属性
|
||||
}
|
||||
xmlString.value = data.bpmnXml
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.process-panel__container {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
right: 60px;
|
||||
}
|
||||
</style>
|
|
@ -1,415 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="流程设计器(BPMN)" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
|
||||
<doc-alert
|
||||
title="流程设计器(钉钉、飞书)"
|
||||
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
|
||||
/>
|
||||
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
|
||||
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="请输入流程标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:model:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新建流程
|
||||
</el-button>
|
||||
<el-button type="success" plain @click="openImportForm" v-hasPermi="['bpm:model:import']">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 导入流程
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程标识" align="center" prop="key" width="200" />
|
||||
<el-table-column label="流程名称" align="center" prop="name" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程图标" align="center" prop="icon" width="100">
|
||||
<template #default="scope">
|
||||
<el-image :src="scope.row.icon" class="w-32px h-32px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程分类" align="center" prop="categoryName" width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === 20"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="最新部署的流程定义" align="center">
|
||||
<el-table-column
|
||||
label="流程版本"
|
||||
align="center"
|
||||
prop="processDefinition.version"
|
||||
width="100"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.processDefinition">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="激活状态"
|
||||
align="center"
|
||||
prop="processDefinition.version"
|
||||
width="85"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-if="scope.row.processDefinition"
|
||||
v-model="scope.row.processDefinition.suspensionState"
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
@change="handleChangeState(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="180">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.processDefinition">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="240" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
修改流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
设计流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleSimpleDesign(scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
仿钉钉设计流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-hasPermi="['bpm:model:deploy']"
|
||||
>
|
||||
发布流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-definition:query']"
|
||||
@click="handleDefinitionList(scope.row)"
|
||||
>
|
||||
流程定义
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:model:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改流程 -->
|
||||
<ModelForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 表单弹窗:导入流程 -->
|
||||
<ModelImportForm ref="importFormRef" @success="getList" />
|
||||
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
|
||||
<!-- 弹窗:流程模型图的预览 -->
|
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
v-model="bpmnXML"
|
||||
:value="bpmnXML as any"
|
||||
v-bind="bpmnControlForm"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
key: undefined,
|
||||
name: undefined,
|
||||
category: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList: any = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ModelApi.getModelPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const importFormRef = ref()
|
||||
const openImportForm = () => {
|
||||
importFormRef.value.open()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
const statusState = state === 1 ? '激活' : '挂起'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, state)
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.processDefinition.suspensionState = state === 1 ? 2 : 1
|
||||
}
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row) => {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSimpleDesign = (row) => {
|
||||
push({
|
||||
name: 'SimpleWorkflowDesignEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否部署该流程!!')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('部署成功'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(row.formId)
|
||||
setConfAndFields2(formDetailPreview, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程图的详情按钮操作 */
|
||||
const bpmnDetailVisible = ref(false)
|
||||
const bpmnXML = ref(null)
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const handleBpmnDetail = async (row) => {
|
||||
const data = await ModelApi.getModel(row.id)
|
||||
bpmnXML.value = data.bpmnXml || ''
|
||||
bpmnDetailVisible.value = true
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
|
@ -1,164 +0,0 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="请假类型" prop="type">
|
||||
<el-select v-model="formData.type" clearable placeholder="请选择请假类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
v-model="formData.startTime"
|
||||
clearable
|
||||
placeholder="请选择开始时间"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
v-model="formData.endTime"
|
||||
clearable
|
||||
placeholder="请选择结束时间"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="原因" prop="reason">
|
||||
<el-input v-model="formData.reason" placeholder="请输请假原因" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-col v-if="startUserSelectTasks.length > 0">
|
||||
<el-card class="mb-10px">
|
||||
<template #header>指定审批人</template>
|
||||
<el-form
|
||||
:model="startUserSelectAssignees"
|
||||
:rules="startUserSelectAssigneesFormRules"
|
||||
ref="startUserSelectAssigneesFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="userTask in startUserSelectTasks"
|
||||
:key="userTask.id"
|
||||
:label="`任务【${userTask.name}】`"
|
||||
:prop="userTask.id"
|
||||
>
|
||||
<el-select
|
||||
v-model="startUserSelectAssignees[userTask.id]"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-form-item>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmOALeaveCreate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: undefined,
|
||||
reason: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
|
||||
reason: [{ required: true, message: '请假原因不能为空', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请假开始时间不能为空', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
// 指定审批人
|
||||
const processDefineKey = 'oa_leave' // 流程定义 Key
|
||||
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 校验指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
await startUserSelectAssigneesFormRef.value.validate()
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = { ...formData.value } as unknown as LeaveApi.LeaveVO
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
data.startUserSelectAssignees = startUserSelectAssignees.value
|
||||
}
|
||||
await LeaveApi.createLeave(data)
|
||||
message.success('发起成功')
|
||||
// 关闭当前 Tab
|
||||
delView(unref(currentRoute))
|
||||
await push({ name: 'BpmOALeave' })
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
|
||||
undefined,
|
||||
processDefineKey
|
||||
)
|
||||
if (!processDefinitionDetail) {
|
||||
message.error('OA 请假的流程模型未配置,请检查!')
|
||||
return
|
||||
}
|
||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
// 设置校验规则
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[userTask.id] = []
|
||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -1,51 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="请假类型">
|
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="detailData.type" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">
|
||||
{{ formatDate(detailData.startTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">
|
||||
{{ formatDate(detailData.endTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="原因">
|
||||
{{ detailData.reason }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
|
||||
defineOptions({ name: 'BpmOALeaveDetail' })
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const props = defineProps({
|
||||
id: propTypes.number.def(undefined)
|
||||
})
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref<any>({}) // 详情数据
|
||||
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
|
||||
|
||||
/** 获得数据 */
|
||||
const getInfo = async () => {
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = await LeaveApi.getLeave(props.id || queryId)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open: getInfo }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getInfo()
|
||||
})
|
||||
</script>
|
|
@ -1,258 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="审批接入(业务表单)" url="https://doc.iocoder.cn/bpm/use-business-form/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="请假类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择请假类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="申请时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批结果" prop="result">
|
||||
<el-select
|
||||
v-model="queryParams.result"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择审批结果"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="原因" prop="reason">
|
||||
<el-input
|
||||
v-model="queryParams.reason"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入原因"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="handleCreate()">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
发起请假
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="申请编号" prop="id" />
|
||||
<el-table-column align="center" label="状态" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="请假类型" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="原因" prop="reason" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="申请时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['bpm:oa-leave:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['bpm:oa-leave:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleProcessDetail(scope.row)"
|
||||
>
|
||||
进度
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.result === 1"
|
||||
v-hasPermi="['bpm:oa-leave:create']"
|
||||
link
|
||||
type="danger"
|
||||
@click="cancelLeave(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
||||
defineOptions({ name: 'BpmOALeave' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const router = useRouter() // 路由
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: undefined,
|
||||
status: undefined,
|
||||
reason: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await LeaveApi.getLeavePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加操作 */
|
||||
const handleCreate = () => {
|
||||
router.push({ name: 'OALeaveCreate' })
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const handleDetail = (row: LeaveApi.LeaveVO) => {
|
||||
router.push({
|
||||
name: 'OALeaveDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消请假操作 */
|
||||
const cancelLeave = async (row) => {
|
||||
// 二次确认
|
||||
// @ts-expect-error
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 审批进度 */
|
||||
const handleProcessDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstanceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// fix: 列表不刷新的问题。
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
() => {
|
||||
getList()
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,114 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="表达式" prop="expression">
|
||||
<el-input type="textarea" v-model="formData.expression" placeholder="请输入表达式" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessExpressionForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
expression: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
expression: [{ required: true, message: '表达式不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ProcessExpressionApi.getProcessExpression(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ProcessExpressionVO
|
||||
if (formType.value === 'create') {
|
||||
await ProcessExpressionApi.createProcessExpression(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ProcessExpressionApi.updateProcessExpression(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
expression: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,182 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="流程表达式" url="https://doc.iocoder.cn/bpm/expression/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:process-expression:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表达式" align="center" prop="expression" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:process-expression:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:process-expression:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ProcessExpressionForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
|
||||
import ProcessExpressionForm from './ProcessExpressionForm.vue'
|
||||
|
||||
/** BPM 流程表达式列表 */
|
||||
defineOptions({ name: 'BpmProcessExpression' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ProcessExpressionVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
// const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessExpressionApi.getProcessExpressionPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ProcessExpressionApi.deleteProcessExpression(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,258 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" /> -->
|
||||
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
|
||||
<el-tabs tab-position="left" v-model="categoryActive">
|
||||
<el-tab-pane
|
||||
:label="category.name"
|
||||
:name="category.code"
|
||||
:key="category.code"
|
||||
v-for="category in categoryList"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
:lg="6"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
v-for="definition in categoryProcessDefinitionList"
|
||||
:key="definition.id"
|
||||
>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="mb-20px cursor-pointer"
|
||||
@click="handleSelect(definition)"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<el-image :src="definition.icon" class="w-32px h-32px" />
|
||||
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 第二步,填写表单,进行流程的提交 -->
|
||||
<ContentWrap v-else>
|
||||
<el-card class="box-card">
|
||||
<div class="clearfix">
|
||||
<span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
|
||||
<el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
|
||||
<Icon icon="ep:delete" /> 选择其它流程
|
||||
</el-button>
|
||||
</div>
|
||||
<el-col :span="16" :offset="6" style="margin-top: 20px">
|
||||
<form-create
|
||||
:rule="detailForm.rule"
|
||||
v-model:api="fApi"
|
||||
v-model="detailForm.value"
|
||||
:option="detailForm.option"
|
||||
@submit="submitForm"
|
||||
>
|
||||
<template #type-startUserSelect>
|
||||
<el-col :span="24">
|
||||
<el-card class="mb-10px">
|
||||
<template #header>指定审批人</template>
|
||||
<el-form
|
||||
:model="startUserSelectAssignees"
|
||||
:rules="startUserSelectAssigneesFormRules"
|
||||
ref="startUserSelectAssigneesFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="userTask in startUserSelectTasks"
|
||||
:key="userTask.id"
|
||||
:label="`任务【${userTask.name}】`"
|
||||
:prop="userTask.id"
|
||||
>
|
||||
<el-select
|
||||
v-model="startUserSelectAssignees[userTask.id]"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</form-create>
|
||||
</el-col>
|
||||
</el-card>
|
||||
<!-- 流程图预览 -->
|
||||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCreate' })
|
||||
|
||||
const route = useRoute() // 路由
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const processInstanceId: any = route.query.processInstanceId
|
||||
const loading = ref(true) // 加载中
|
||||
const categoryList:any = ref([]) // 分类的列表
|
||||
const categoryActive = ref('') // 选中的分类
|
||||
const processDefinitionList = ref([]) // 流程定义的列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 流程分类
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
if (categoryList.value.length > 0) {
|
||||
categoryActive.value = categoryList.value[0].code
|
||||
}
|
||||
// 流程定义
|
||||
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
|
||||
suspensionState: 1
|
||||
})
|
||||
|
||||
// 如果 processInstanceId 非空,说明是重新发起
|
||||
if (processInstanceId?.length > 0) {
|
||||
const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
|
||||
if (!processInstance) {
|
||||
message.error('重新发起流程失败,原因:流程实例不存在')
|
||||
return
|
||||
}
|
||||
const processDefinition = processDefinitionList.value.find(
|
||||
(item: any) => item.key == processInstance.processDefinition?.key
|
||||
)
|
||||
if (!processDefinition) {
|
||||
message.error('重新发起流程失败,原因:流程定义不存在')
|
||||
return
|
||||
}
|
||||
await handleSelect(processDefinition, processInstance.formVariables)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 选中分类对应的流程定义列表 */
|
||||
const categoryProcessDefinitionList: any = computed(() => {
|
||||
return processDefinitionList.value.filter((item: any) => item.category == categoryActive.value)
|
||||
})
|
||||
|
||||
// ========== 表单相关 ==========
|
||||
const fApi = ref<ApiAttrs>()
|
||||
const detailForm: any = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程表单详情
|
||||
const selectProcessDefinition = ref() // 选择的流程定义
|
||||
|
||||
// 指定审批人
|
||||
const bpmnXML = ref(null) // BPMN 数据
|
||||
const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 处理选择流程的按钮操作 **/
|
||||
const handleSelect = async (row?, formVariables?) => {
|
||||
// 设置选择的流程
|
||||
selectProcessDefinition.value = row
|
||||
|
||||
// 重置指定审批人
|
||||
startUserSelectTasks.value = []
|
||||
startUserSelectAssignees.value = {}
|
||||
startUserSelectAssigneesFormRules.value = {}
|
||||
|
||||
// 情况一:流程表单
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
// 加载流程图
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
|
||||
if (processDefinitionDetail) {
|
||||
bpmnXML.value = processDefinitionDetail.bpmnXml
|
||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
detailForm.value.rule.push({
|
||||
type: 'startUserSelect',
|
||||
props: {
|
||||
title: '指定审批人'
|
||||
}
|
||||
})
|
||||
// 设置校验规则
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[userTask.id] = []
|
||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
}
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async (formData) => {
|
||||
if (!fApi.value || !selectProcessDefinition.value) {
|
||||
return
|
||||
}
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
await startUserSelectAssigneesFormRef.value.validate()
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
fApi.value.btn.loading(true)
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: selectProcessDefinition.value.id,
|
||||
variables: formData,
|
||||
startUserSelectAssignees: startUserSelectAssignees.value
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
// 跳转回去
|
||||
delView(unref(currentRoute))
|
||||
await push({
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
fApi.value.btn.loading(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,54 +0,0 @@
|
|||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">流程图</span>
|
||||
</template>
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
:activityData="activityList"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
:processInstanceData="processInstance"
|
||||
:taskData="tasks"
|
||||
:value="bpmnXml"
|
||||
v-bind="bpmnControlForm"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as ActivityApi from '@/api/bpm/activity'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
id: propTypes.string, // 流程实例的编号
|
||||
processInstance: propTypes.any, // 流程实例的信息
|
||||
tasks: propTypes.array, // 流程任务的数组
|
||||
bpmnXml: propTypes.string // BPMN XML
|
||||
})
|
||||
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const activityList = ref([]) // 任务列表
|
||||
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.loading,
|
||||
async (value) => {
|
||||
if (value && props.id) {
|
||||
activityList.value = await ActivityApi.getActivityList({
|
||||
processInstanceId: props.id
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
.box-card {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
|
@ -1,175 +0,0 @@
|
|||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批记录</span>
|
||||
</template>
|
||||
<el-col :offset="3" :span="17">
|
||||
<div class="block">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-if="processInstance.endTime"
|
||||
:type="getProcessInstanceTimelineItemType(processInstance)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
结束流程:在 {{ formatDate(processInstance?.endTime) }} 结束
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="processInstance.status"
|
||||
/>
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in tasks"
|
||||
:key="index"
|
||||
:type="getTaskTimelineItemType(item)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
审批任务:{{ item.name }}
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
v-if="!isEmpty(item.children)"
|
||||
@click="openChildrenTask(item)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:memo" /> 子任务
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
size="small"
|
||||
v-if="item.formId > 0"
|
||||
@click="handleFormDetail(item)"
|
||||
>
|
||||
<Icon icon="ep:document" /> 查看表单
|
||||
</el-button>
|
||||
</p>
|
||||
<el-card :body-style="{ padding: '10px' }">
|
||||
<label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
|
||||
审批人:{{ item.assigneeUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ item.assigneeUser.deptName }}</el-tag>
|
||||
</label>
|
||||
<label v-if="item.createTime" style="font-weight: normal">创建时间:</label>
|
||||
<label style="font-weight: normal; color: #8a909c">
|
||||
{{ formatDate(item?.createTime) }}
|
||||
</label>
|
||||
<label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
|
||||
审批时间:
|
||||
</label>
|
||||
<label v-if="item.endTime" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatDate(item?.endTime) }}
|
||||
</label>
|
||||
<label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
|
||||
耗时:
|
||||
</label>
|
||||
<label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatPast2(item?.durationInMillis) }}
|
||||
</label>
|
||||
<p v-if="item.reason"> 审批建议:{{ item.reason }} </p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item type="success">
|
||||
<p style="font-weight: 700">
|
||||
发起流程:【{{ processInstance.startUser?.nickname }}】在
|
||||
{{ formatDate(processInstance?.startTime) }} 发起【 {{ processInstance.name }} 】流程
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-card>
|
||||
|
||||
<!-- 弹窗:子任务 -->
|
||||
<TaskSignList ref="taskSignListRef" @success="refresh" />
|
||||
<!-- 弹窗:表单 -->
|
||||
<Dialog title="表单详情" v-model="taskFormVisible" width="600">
|
||||
<form-create
|
||||
ref="fApi"
|
||||
v-model="taskForm.value"
|
||||
:option="taskForm.option"
|
||||
:rule="taskForm.rule"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate, formatPast2 } from '@/utils/formatTime'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import TaskSignList from './dialog/TaskSignList.vue'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
||||
|
||||
defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
processInstance: propTypes.object, // 流程实例
|
||||
tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
|
||||
})
|
||||
|
||||
/** 获得流程实例对应的颜色 */
|
||||
const getProcessInstanceTimelineItemType = (item: any) => {
|
||||
if (item.status === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.status === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.status === 4) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 获得任务对应的颜色 */
|
||||
const getTaskTimelineItemType = (item: any) => {
|
||||
if ([0, 1, 6, 7].includes(item.status)) {
|
||||
return 'primary'
|
||||
}
|
||||
if (item.status === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.status === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.status === 4) {
|
||||
return 'info'
|
||||
}
|
||||
if (item.status === 5) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 子任务 */
|
||||
const taskSignListRef = ref()
|
||||
const openChildrenTask = (item: any) => {
|
||||
taskSignListRef.value.open(item)
|
||||
}
|
||||
|
||||
/** 查看表单 */
|
||||
const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
|
||||
const taskForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程任务的表单详情
|
||||
const taskFormVisible = ref(false)
|
||||
const handleFormDetail = async (row) => {
|
||||
// 设置表单
|
||||
setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables)
|
||||
// 弹窗打开
|
||||
taskFormVisible.value = true
|
||||
// 隐藏提交、重置按钮,设置禁用只读
|
||||
await nextTick()
|
||||
fApi.value.fapi.btn.show(false)
|
||||
fApi.value?.fapi?.resetBtn.show(false)
|
||||
fApi.value?.fapi?.disabled(true)
|
||||
}
|
||||
|
||||
/** 刷新数据 */
|
||||
const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
</script>
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="委派任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="接收人" prop="delegateUserId">
|
||||
<el-select v-model="formData.delegateUserId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="委派理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入委派理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmTaskDelegateForm' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '委派理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.delegateTask(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,90 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="回退任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="退回节点" prop="targetTaskDefinitionKey">
|
||||
<el-select v-model="formData.targetTaskDefinitionKey" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in returnList"
|
||||
:key="item.taskDefinitionKey"
|
||||
:label="item.name"
|
||||
:value="item.taskDefinitionKey"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="回退理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入回退理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" name="TaskRollbackDialogForm" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
targetTaskDefinitionKey: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
targetTaskDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const returnList = ref([] as any)
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
returnList.value = await TaskApi.getTaskListByReturn(id)
|
||||
if (returnList.value.length === 0) {
|
||||
message.warning('当前没有可回退的节点')
|
||||
return false
|
||||
}
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.returnTask(formData.value)
|
||||
message.success('回退成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
targetTaskDefinitionKey: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="加签" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="加签处理人" prop="userIds">
|
||||
<el-select v-model="formData.userIds" multiple clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="加签理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入加签理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm('before')">
|
||||
向前加签
|
||||
</el-button>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm('after')">
|
||||
向后加签
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'TaskSignCreateForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
userIds: [],
|
||||
type: '',
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
userIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async (type: string) => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
formData.value.type = type
|
||||
try {
|
||||
await TaskApi.signCreateTask(formData.value)
|
||||
message.success('加签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
userIds: [],
|
||||
type: '',
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="减签" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="减签任务" prop="id">
|
||||
<el-radio-group v-model="formData.id">
|
||||
<el-radio-button v-for="item in childrenTaskList" :key="item.id" :label="item.id">
|
||||
{{ item.name }}
|
||||
({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
|
||||
{{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="减签理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入减签理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'TaskSignDeleteForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const childrenTaskList = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
childrenTaskList.value = await TaskApi.getChildrenTaskList(id)
|
||||
if (isEmpty(childrenTaskList.value)) {
|
||||
message.warning('当前没有可减签的任务')
|
||||
return false
|
||||
}
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.signDeleteTask(formData.value)
|
||||
message.success('减签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,106 +0,0 @@
|
|||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="子任务" size="880px">
|
||||
<!-- 当前任务 -->
|
||||
<template #header>
|
||||
<h4>【{{ parentTask.name }} 】审批人:{{ parentTask?.assigneeUser?.nickname }}</h4>
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
v-if="isSignDeleteButtonVisible(parentTask)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSignDelete(parentTask)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 子任务列表 -->
|
||||
<el-table :data="parentTask.children" style="width: 100%" row-key="id" border>
|
||||
<el-table-column prop="assigneeUser.nickname" label="审批人" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assigneeUser.deptName" label="所在部门" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="提交时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" prop="operation" width="90">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="isSignDeleteButtonVisible(scope.row)"
|
||||
type="danger"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleSignDelete(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 减签 -->
|
||||
<TaskSignDeleteForm ref="taskSignDeleteFormRef" @success="handleSignDeleteSuccess" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import TaskSignDeleteForm from './TaskSignDeleteForm.vue'
|
||||
|
||||
defineOptions({ name: 'TaskSignList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const drawerVisible = ref(false) // 抽屉的是否展示
|
||||
const parentTask = ref({} as any)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (task: any) => {
|
||||
if (isEmpty(task.children)) {
|
||||
message.warning('该任务没有子任务')
|
||||
return
|
||||
}
|
||||
parentTask.value = task
|
||||
// 展开抽屉
|
||||
drawerVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 发起减签 */
|
||||
const taskSignDeleteFormRef = ref()
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const handleSignDelete = (item: any) => {
|
||||
taskSignDeleteFormRef.value.open(item.id)
|
||||
}
|
||||
const handleSignDeleteSuccess = () => {
|
||||
emit('success')
|
||||
// 关闭抽屉
|
||||
drawerVisible.value = false
|
||||
}
|
||||
|
||||
/** 是否显示减签按钮 */
|
||||
const isSignDeleteButtonVisible = (task: any) => {
|
||||
return task && task.children && !isEmpty(task.children)
|
||||
}
|
||||
</script>
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="转派任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="新审批人" prop="assigneeUserId">
|
||||
<el-select v-model="formData.assigneeUserId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="转派理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入转派理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'TaskTransferForm' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '转派理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.transferTask(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,381 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 审批信息 -->
|
||||
<el-card
|
||||
v-for="(item, index) in runningTasks"
|
||||
:key="index"
|
||||
v-loading="processInstanceLoading"
|
||||
class="box-card"
|
||||
>
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span>
|
||||
</template>
|
||||
<el-col :offset="6" :span="16">
|
||||
<el-form
|
||||
:ref="'form' + index"
|
||||
:model="auditForms[index]"
|
||||
:rules="auditRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item v-if="processInstance && processInstance.name" label="流程名">
|
||||
{{ processInstance.name }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
|
||||
{{ processInstance?.startUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">
|
||||
填写表单【{{ runningTasks[index]?.formName }}】
|
||||
</span>
|
||||
</template>
|
||||
<form-create
|
||||
v-model="approveForms[index].value"
|
||||
v-model:api="approveFormFApis[index]"
|
||||
:option="approveForms[index].option"
|
||||
:rule="approveForms[index].rule"
|
||||
/>
|
||||
</el-card>
|
||||
<el-form-item label="审批建议" prop="reason">
|
||||
<el-input
|
||||
v-model="auditForms[index].reason"
|
||||
placeholder="请输入审批建议"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送人" prop="copyUserIds">
|
||||
<el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
|
||||
<el-button type="success" @click="handleAudit(item, true)">
|
||||
<Icon icon="ep:select" />
|
||||
通过
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleAudit(item, false)">
|
||||
<Icon icon="ep:close" />
|
||||
不通过
|
||||
</el-button>
|
||||
<el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)">
|
||||
<Icon icon="ep:edit" />
|
||||
转办
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleDelegate(item)">
|
||||
<Icon icon="ep:position" />
|
||||
委派
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSign(item)">
|
||||
<Icon icon="ep:plus" />
|
||||
加签
|
||||
</el-button>
|
||||
<el-button type="warning" @click="handleBack(item)">
|
||||
<Icon icon="ep:back" />
|
||||
回退
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-card>
|
||||
|
||||
<!-- 申请信息 -->
|
||||
<el-card v-loading="processInstanceLoading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-document">申请信息【{{ processInstance.name }}】</span>
|
||||
</template>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16">
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<ProcessInstanceTaskList
|
||||
:loading="tasksLoad"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
@refresh="getTaskList"
|
||||
/>
|
||||
|
||||
<!-- 高亮流程图 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:id="`${id}`"
|
||||
:bpmn-xml="bpmnXml"
|
||||
:loading="processInstanceLoading"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
/>
|
||||
|
||||
<!-- 弹窗:转派审批人 -->
|
||||
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:回退节点 -->
|
||||
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import TaskReturnForm from './dialog/TaskReturnForm.vue'
|
||||
import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
|
||||
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
||||
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||
const id = query.id as unknown as string // 流程实例的编号
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const bpmnXml = ref('') // BPMN XML
|
||||
const tasksLoad = ref(true) // 任务的加载中
|
||||
const tasks = ref<any[]>([]) // 任务列表
|
||||
// ========== 审批信息 ==========
|
||||
const runningTasks = ref<any[]>([]) // 运行中的任务
|
||||
const auditForms = ref<any[]>([]) // 审批任务的表单
|
||||
const auditRule = reactive({
|
||||
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息
|
||||
const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi
|
||||
|
||||
// ========== 申请信息 ==========
|
||||
const fApi = ref<ApiAttrs>() //
|
||||
const detailForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
|
||||
watch(
|
||||
() => approveFormFApis.value,
|
||||
(value) => {
|
||||
value?.forEach((api) => {
|
||||
api.btn.show(false)
|
||||
api.resetBtn.show(false)
|
||||
})
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 处理审批通过和不通过的操作 */
|
||||
const handleAudit = async (task, pass) => {
|
||||
// 1.1 获得对应表单
|
||||
const index = runningTasks.value.indexOf(task)
|
||||
const auditFormRef = proxy.$refs['form' + index][0]
|
||||
// 1.2 校验表单
|
||||
const elForm = unref(auditFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 2.1 提交审批
|
||||
const data = {
|
||||
id: task.id,
|
||||
reason: auditForms.value[index].reason,
|
||||
copyUserIds: auditForms.value[index].copyUserIds
|
||||
}
|
||||
if (pass) {
|
||||
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
const formCreateApi = approveFormFApis.value[index]
|
||||
if (formCreateApi) {
|
||||
await formCreateApi.validate()
|
||||
data.variables = approveForms.value[index].value
|
||||
}
|
||||
await TaskApi.approveTask(data)
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
await TaskApi.rejectTask(data)
|
||||
message.success('审批不通过成功')
|
||||
}
|
||||
// 2.2 加载最新数据
|
||||
getDetail()
|
||||
}
|
||||
|
||||
/** 转派审批人 */
|
||||
const taskTransferFormRef = ref()
|
||||
const openTaskUpdateAssigneeForm = (id: string) => {
|
||||
taskTransferFormRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 处理审批退回的操作 */
|
||||
const taskDelegateForm = ref()
|
||||
const handleDelegate = async (task) => {
|
||||
taskDelegateForm.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 处理审批退回的操作 */
|
||||
const taskReturnFormRef = ref()
|
||||
const handleBack = async (task: any) => {
|
||||
taskReturnFormRef.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 处理审批加签的操作 */
|
||||
const taskSignCreateFormRef = ref()
|
||||
const handleSign = async (task: any) => {
|
||||
taskSignCreateFormRef.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = () => {
|
||||
// 1. 获得流程实例相关
|
||||
getProcessInstance()
|
||||
// 2. 获得流程任务列表(审批记录)
|
||||
getTaskList()
|
||||
}
|
||||
|
||||
/** 加载流程实例 */
|
||||
const BusinessFormComponent = ref(null) // 异步组件
|
||||
const getProcessInstance = async () => {
|
||||
try {
|
||||
processInstanceLoading.value = true
|
||||
const data = await ProcessInstanceApi.getProcessInstance(id)
|
||||
if (!data) {
|
||||
message.error('查询不到流程信息!')
|
||||
return
|
||||
}
|
||||
processInstance.value = data
|
||||
|
||||
// 设置表单信息
|
||||
const processDefinition = data.processDefinition
|
||||
if (processDefinition.formType === 10) {
|
||||
setConfAndFields2(
|
||||
detailForm,
|
||||
processDefinition.formConf,
|
||||
processDefinition.formFields,
|
||||
data.formVariables
|
||||
)
|
||||
nextTick().then(() => {
|
||||
fApi.value?.btn.show(false)
|
||||
fApi.value?.resetBtn.show(false)
|
||||
fApi.value?.disabled(true)
|
||||
})
|
||||
} else {
|
||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||
}
|
||||
|
||||
// 加载流程图
|
||||
bpmnXml.value = (
|
||||
await DefinitionApi.getProcessDefinition(processDefinition.id as number)
|
||||
)?.bpmnXml
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载任务列表 */
|
||||
const getTaskList = async () => {
|
||||
runningTasks.value = []
|
||||
auditForms.value = []
|
||||
approveForms.value = []
|
||||
approveFormFApis.value = []
|
||||
try {
|
||||
// 获得未取消的任务
|
||||
tasksLoad.value = true
|
||||
const data = await TaskApi.getTaskListByProcessInstanceId(id)
|
||||
tasks.value = []
|
||||
// 1.1 移除已取消的审批
|
||||
data.forEach((task) => {
|
||||
if (task.status !== 4) {
|
||||
tasks.value.push(task)
|
||||
}
|
||||
})
|
||||
// 1.2 排序,将未完成的排在前面,已完成的排在后面;
|
||||
tasks.value.sort((a, b) => {
|
||||
// 有已完成的情况,按照完成时间倒序
|
||||
if (a.endTime && b.endTime) {
|
||||
return b.endTime - a.endTime
|
||||
} else if (a.endTime) {
|
||||
return 1
|
||||
} else if (b.endTime) {
|
||||
return -1
|
||||
// 都是未完成,按照创建时间倒序
|
||||
} else {
|
||||
return b.createTime - a.createTime
|
||||
}
|
||||
})
|
||||
|
||||
// 获得需要自己审批的任务
|
||||
loadRunningTask(tasks.value)
|
||||
} finally {
|
||||
tasksLoad.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 runningTasks 中的任务
|
||||
*/
|
||||
const loadRunningTask = (tasks) => {
|
||||
tasks.forEach((task) => {
|
||||
if (!isEmpty(task.children)) {
|
||||
loadRunningTask(task.children)
|
||||
}
|
||||
// 2.1 只有待处理才需要
|
||||
if (task.status !== 1 && task.status !== 6) {
|
||||
return
|
||||
}
|
||||
// 2.2 自己不是处理人
|
||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||
return
|
||||
}
|
||||
// 2.3 添加到处理任务
|
||||
runningTasks.value.push({ ...task })
|
||||
auditForms.value.push({
|
||||
reason: '',
|
||||
copyUserIds: []
|
||||
})
|
||||
|
||||
// 2.4 处理 approve 表单
|
||||
if (task.formId && task.formConf) {
|
||||
const approveForm = {}
|
||||
setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariable)
|
||||
approveForms.value.push(approveForm)
|
||||
} else {
|
||||
approveForms.value.push({}) // 占位,避免为空
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
getDetail()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
|
@ -1,260 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属流程" prop="processDefinitionId">
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionId"
|
||||
placeholder="请输入流程定义的编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCreate()"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
|
||||
<el-table-column
|
||||
label="流程分类"
|
||||
align="center"
|
||||
prop="categoryName"
|
||||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button link type="primary" v-else @click="handleCreate(scope.row.id)">
|
||||
重新发起
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceMy' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
processDefinitionId: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList: any = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceMyPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 发起流程操作 **/
|
||||
const handleCreate = (id?) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceCreate',
|
||||
query: { processInstanceId: id }
|
||||
})
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
|
@ -1,255 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="发起人" prop="startUserId">
|
||||
<el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属流程" prop="processDefinitionId">
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionId"
|
||||
placeholder="请输入流程定义的编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
|
||||
<el-table-column
|
||||
label="流程分类"
|
||||
align="center"
|
||||
prop="categoryName"
|
||||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
|
||||
<el-table-column label="发起部门" align="center" prop="startUser.deptName" width="120" />
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="169">
|
||||
<template #default="scope">
|
||||
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { cancelProcessInstanceByAdmin } from '@/api/bpm/processInstance'
|
||||
|
||||
// 它和【我的流程】的差异是,该菜单可以看全部的流程实例
|
||||
defineOptions({ name: 'BpmProcessInstanceManager' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
startUserId: undefined,
|
||||
name: '',
|
||||
processDefinitionId: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceManagerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
|
@ -1,162 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select
|
||||
v-model="formData.type"
|
||||
placeholder="请选择类型"
|
||||
@change="formData.event = undefined"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="事件" prop="event">
|
||||
<el-select v-model="formData.event" placeholder="请选择事件">
|
||||
<el-option
|
||||
v-for="event in formData.type == 'execution'
|
||||
? ['start', 'end']
|
||||
: ['create', 'assignment', 'complete', 'delete', 'update', 'timeout']"
|
||||
:label="event"
|
||||
:value="event"
|
||||
:key="event"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="值类型" prop="valueType">
|
||||
<el-select v-model="formData.valueType" placeholder="请选择值类型">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类路径" prop="value" v-if="formData.type == 'class'">
|
||||
<el-input v-model="formData.value" placeholder="请输入类路径" />
|
||||
</el-form-item>
|
||||
<el-form-item label="表达式" prop="value" v-else>
|
||||
<el-input v-model="formData.value" placeholder="请输入表达式" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessListenerForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: undefined,
|
||||
event: undefined,
|
||||
valueType: undefined,
|
||||
value: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
event: [{ required: true, message: '监听事件不能为空', trigger: 'blur' }],
|
||||
valueType: [{ required: true, message: '值类型不能为空', trigger: 'change' }],
|
||||
value: [{ required: true, message: '值不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ProcessListenerApi.getProcessListener(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ProcessListenerVO
|
||||
if (formType.value === 'create') {
|
||||
await ProcessListenerApi.createProcessListener(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ProcessListenerApi.updateProcessListener(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
event: undefined,
|
||||
valueType: undefined,
|
||||
value: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,185 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="执行监听器、任务监听器" url="https://doc.iocoder.cn/bpm/listener/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="85px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE)"
|
||||
:key="dict.value as any"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:process-listener:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="事件" align="center" prop="event" />
|
||||
<el-table-column label="值类型" align="center" prop="valueType">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
|
||||
:value="scope.row.valueType"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="值" align="center" prop="value" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:process-listener:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:process-listener:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ProcessListenerForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
|
||||
import ProcessListenerForm from './ProcessListenerForm.vue'
|
||||
|
||||
/** BPM 流程 列表 */
|
||||
defineOptions({ name: 'BpmProcessListener' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ProcessListenerVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
event: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
// const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessListenerApi.getProcessListenerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ProcessListenerApi.deleteProcessListener(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,28 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<section class="dingflow-design">
|
||||
<div class="box-scale">
|
||||
<nodeWrap v-model:nodeConfig="nodeConfig" />
|
||||
<div class="end-node">
|
||||
<div class="end-node-circle"></div>
|
||||
<div class="end-node-text">流程结束</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
|
||||
defineOptions({ name: 'SimpleWorkflowDesignEditor' })
|
||||
let nodeConfig = ref({
|
||||
nodeName: '发起人',
|
||||
type: 0,
|
||||
id: 'root',
|
||||
formPerms: {},
|
||||
nodeUserList: [],
|
||||
childNode: {}
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
|
||||
</style>
|
|
@ -1,137 +0,0 @@
|
|||
<!-- 工作流 - 抄送我的流程 -->
|
||||
<template>
|
||||
<!-- <doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form ref="queryFormRef" :inline="true" class="-mb-15px" label-width="68px">
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.processInstanceName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入流程名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="流程名" prop="processInstanceName" min-width="180" />
|
||||
<el-table-column align="center" label="流程发起人" prop="startUserName" min-width="100" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="流程发起时间"
|
||||
prop="processInstanceStartTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="抄送任务" prop="taskName" min-width="180" />
|
||||
<el-table-column align="center" label="抄送人" prop="creatorName" min-width="100" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="抄送时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCopy' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
processInstanceId: '',
|
||||
processInstanceName: '',
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceCopyPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstanceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,170 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入任务名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="发起人"
|
||||
prop="processInstance.startUser.nickname"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="发起时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务开始时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务结束时间"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="180" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="流程编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
defineOptions({ name: 'BpmTodoTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getTaskDonePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,166 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入任务名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="发起人"
|
||||
prop="processInstance.startUser.nickname"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="发起时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务开始时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务结束时间"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="审批人" prop="assigneeUser.nickname" width="100" />
|
||||
<el-table-column align="center" label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="180" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="流程编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
// 它和【待办任务】【已办任务】的差异是,该菜单可以看全部的流程任务
|
||||
defineOptions({ name: 'BpmManagerTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getTaskManagerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,152 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入任务名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="发起人"
|
||||
prop="processInstance.startUser.nickname"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="发起时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="流程编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">办理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
defineOptions({ name: 'BpmTodoTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getTaskTodoPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,153 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl">分配给我的线索</div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="状态" prop="followUpStatus">
|
||||
<el-select
|
||||
v-model="queryParams.followUpStatus"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in FOLLOWUP_STATUS"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="线索名称" align="center" prop="name" fixed="left" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="线索来源" align="center" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
label="最后跟进时间"
|
||||
align="center"
|
||||
prop="contactLastTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100" />
|
||||
<el-table-column
|
||||
label="更新时间"
|
||||
align="center"
|
||||
prop="updateTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { FOLLOWUP_STATUS } from './common'
|
||||
|
||||
defineOptions({ name: 'CrmClueFollowList' })
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
followUpStatus: false,
|
||||
transformStatus: false
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ClueApi.getCluePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 打开线索详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmClueDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,247 +0,0 @@
|
|||
<!-- 待审核合同 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl">待审核合同</div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="合同状态" prop="auditStatus">
|
||||
<el-select
|
||||
v-model="queryParams.auditStatus"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in AUDIT_STATUS"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" fixed="left" label="合同编号" prop="no" width="180" />
|
||||
<el-table-column align="center" fixed="left" label="合同名称" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户名称" prop="customerName" width="120">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="商机名称" prop="businessName" width="130">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openBusinessDetail(scope.row.businessId)"
|
||||
>
|
||||
{{ scope.row.businessName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同金额(元)"
|
||||
prop="totalPrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="下单时间"
|
||||
prop="orderDate"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同开始时间"
|
||||
prop="startTime"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同结束时间"
|
||||
prop="endTime"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column align="center" label="客户签约人" prop="contactName" width="130">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openContactDetail(scope.row.signContactId)"
|
||||
>
|
||||
{{ scope.row.signContactName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="已回款金额(元)"
|
||||
prop="totalReceivablePrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="未回款金额(元)"
|
||||
prop="totalReceivablePrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.totalReceivablePrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
|
||||
<el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="90">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
v-hasPermi="['crm:contract:update']"
|
||||
type="primary"
|
||||
@click="handleProcessDetail(scope.row)"
|
||||
>
|
||||
查看审批
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="CheckContract">
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { AUDIT_STATUS } from './common'
|
||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: 1, // 我负责的
|
||||
auditStatus: 10
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ContractApi.getContractPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查看审批 */
|
||||
const handleProcessDetail = (row: ContractApi.ContractVO) => {
|
||||
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
|
||||
}
|
||||
|
||||
/** 打开合同详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContractDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const openContactDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开商机详情 */
|
||||
const openBusinessDetail = (id: number) => {
|
||||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,246 +0,0 @@
|
|||
<!-- 即将到期的合同 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl"> 即将到期的合同 </div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="到期状态" prop="expiryType">
|
||||
<el-select
|
||||
v-model="queryParams.expiryType"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in CONTRACT_EXPIRY_TYPE"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" fixed="left" label="合同编号" prop="no" width="180" />
|
||||
<el-table-column align="center" fixed="left" label="合同名称" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户名称" prop="customerName" width="120">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="商机名称" prop="businessName" width="130">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openBusinessDetail(scope.row.businessId)"
|
||||
>
|
||||
{{ scope.row.businessName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同金额(元)"
|
||||
prop="totalPrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="下单时间"
|
||||
prop="orderDate"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同开始时间"
|
||||
prop="startTime"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同结束时间"
|
||||
prop="endTime"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column align="center" label="客户签约人" prop="contactName" width="130">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openContactDetail(scope.row.signContactId)"
|
||||
>
|
||||
{{ scope.row.signContactName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="已回款金额(元)"
|
||||
prop="totalReceivablePrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="未回款金额(元)"
|
||||
prop="totalReceivablePrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.totalReceivablePrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
|
||||
<el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="90">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
v-hasPermi="['crm:contract:update']"
|
||||
type="primary"
|
||||
@click="handleProcessDetail(scope.row)"
|
||||
>
|
||||
查看审批
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="EndContract">
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import { fenToYuanFormat } from '@/utils/formatter'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { CONTRACT_EXPIRY_TYPE } from './common'
|
||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: '1', // 自己负责的
|
||||
expiryType: 1
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ContractApi.getContractPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查看审批 */
|
||||
const handleProcessDetail = (row: ContractApi.ContractVO) => {
|
||||
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
|
||||
}
|
||||
|
||||
/** 打开合同详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContractDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const openContactDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开商机详情 */
|
||||
const openBusinessDetail = (id: number) => {
|
||||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,170 +0,0 @@
|
|||
<!-- 分配给我的客户 -->
|
||||
<!-- WHERE followUpStatus = ? -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl">分配给我的客户</div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="状态" prop="followUpStatus">
|
||||
<el-select
|
||||
v-model="queryParams.followUpStatus"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in FOLLOWUP_STATUS"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="客户名称" fixed="left" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column align="center" label="锁定状态" prop="lockStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
||||
<el-table-column align="center" label="距离进入公海天数" prop="poolDay" width="140">
|
||||
<template #default="scope"> {{ scope.row.poolDay }} 天</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { FOLLOWUP_STATUS } from './common'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerFollowList' })
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: 1,
|
||||
followUpStatus: false
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerApi.getCustomerPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,169 +0,0 @@
|
|||
<!-- 待进入公海的客户 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl"> 待进入公海的客户 </div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="归属" prop="sceneType">
|
||||
<el-select
|
||||
v-model="queryParams.sceneType"
|
||||
class="!w-240px"
|
||||
placeholder="归属"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in SCENE_TYPES"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="客户名称" fixed="left" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column align="center" label="锁定状态" prop="lockStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
||||
<el-table-column align="center" label="距离进入公海天数" prop="poolDay" width="140">
|
||||
<template #default="scope"> {{ scope.row.poolDay }} 天</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { SCENE_TYPES } from './common'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerPutPoolRemindList' })
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: 1, // 我负责的
|
||||
pool: true // 固定 公海参数为 true
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerApi.getPutPoolRemindCustomerPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
|
@ -1,180 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl"> 今日需联系客户 </div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="状态" prop="contactStatus">
|
||||
<el-select
|
||||
v-model="queryParams.contactStatus"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in CONTACT_STATUS"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="归属" prop="sceneType">
|
||||
<el-select
|
||||
v-model="queryParams.sceneType"
|
||||
class="!w-240px"
|
||||
placeholder="归属"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in SCENE_TYPES"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="客户名称" fixed="left" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column align="center" label="锁定状态" prop="lockStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
||||
<el-table-column align="center" label="距离进入公海天数" prop="poolDay" width="140">
|
||||
<template #default="scope"> {{ scope.row.poolDay }} 天</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { CONTACT_STATUS, SCENE_TYPES } from './common'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerTodayContactList' })
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
contactStatus: 1,
|
||||
sceneType: 1,
|
||||
pool: null // 是否公海数据
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerApi.getCustomerPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
|
@ -1,201 +0,0 @@
|
|||
<!-- 待审核回款 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl"> 待审核回款 </div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="合同状态" prop="auditStatus">
|
||||
<el-select
|
||||
v-model="queryParams.auditStatus"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in AUDIT_STATUS"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column align="center" fixed="left" label="回款编号" prop="no" width="180">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.no }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户名称" prop="customerName" width="120">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="合同编号" prop="contractNo" width="180">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openContractDetail(scope.row.contractId)"
|
||||
>
|
||||
{{ scope.row.contract.no }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter2"
|
||||
align="center"
|
||||
label="回款日期"
|
||||
prop="returnTime"
|
||||
width="150px"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="回款金额(元)"
|
||||
prop="price"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="回款方式" prop="returnType" width="130px">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE" :value="scope.row.returnType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同金额(元)"
|
||||
prop="contract.totalPrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
|
||||
<el-table-column align="center" fixed="right" label="回款状态" prop="auditStatus" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="180px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['crm:receivable:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleProcessDetail(scope.row)"
|
||||
>
|
||||
查看审批
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import * as ReceivableApi from '@/api/crm/receivable'
|
||||
import { AUDIT_STATUS } from './common'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'CrmReceivableAuditList' })
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
auditStatus: 10
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ReceivableApi.getReceivablePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查看审批 */
|
||||
const handleProcessDetail = (row: ReceivableApi.ReceivableVO) => {
|
||||
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
|
||||
}
|
||||
|
||||
/** 打开回款详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmReceivableDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开合同详情 */
|
||||
const openContractDetail = (id: number) => {
|
||||
push({ name: 'CrmContractDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,220 +0,0 @@
|
|||
<!-- 待回款提醒 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<div class="pb-5 text-xl">待回款提醒</div>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="合同状态" prop="remindType">
|
||||
<el-select
|
||||
v-model="queryParams.remindType"
|
||||
class="!w-240px"
|
||||
placeholder="状态"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="(option, index) in RECEIVABLE_REMIND_TYPE"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="150">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="合同编号" prop="contractNo" width="200px" />
|
||||
<el-table-column align="center" label="期数" prop="period">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.period }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="计划回款金额(元)"
|
||||
prop="price"
|
||||
width="160"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter2"
|
||||
align="center"
|
||||
label="计划回款日期"
|
||||
prop="returnTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="提前几天提醒" prop="remindDays" width="150" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="提醒日期"
|
||||
prop="remindTime"
|
||||
width="180px"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column align="center" label="回款方式" prop="returnType" width="130px">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE" :value="scope.row.returnType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="备注" prop="remark" />
|
||||
<el-table-column label="负责人" prop="ownerUserName" width="120" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="实际回款金额(元)"
|
||||
prop="receivable.price"
|
||||
width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-text v-if="scope.row.receivable">
|
||||
{{ erpPriceInputFormatter(scope.row.receivable.price) }}
|
||||
</el-text>
|
||||
<el-text v-else>{{ erpPriceInputFormatter(0) }}</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="实际回款日期"
|
||||
prop="receivable.returnTime"
|
||||
width="180px"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="实际回款金额(元)"
|
||||
prop="receivable.price"
|
||||
width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-text v-if="scope.row.receivable">
|
||||
{{ erpPriceInputFormatter(scope.row.price - scope.row.receivable.price) }}
|
||||
</el-text>
|
||||
<el-text v-else>{{ erpPriceInputFormatter(scope.row.price) }}</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
<el-table-column align="center" fixed="right" label="操作" width="180px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['crm:receivable:create']"
|
||||
link
|
||||
type="success"
|
||||
@click="openReceivableForm(scope.row)"
|
||||
:disabled="scope.row.receivableId"
|
||||
>
|
||||
创建回款
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ReceivableForm ref="receivableFormRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import * as ReceivablePlanApi from '@/api/crm/receivable/plan'
|
||||
import { RECEIVABLE_REMIND_TYPE } from './common'
|
||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import ReceivableForm from '@/views/crm/receivable/ReceivableForm.vue'
|
||||
|
||||
defineOptions({ name: 'ReceivablePlanRemindList' })
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
remindType: 1
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ReceivablePlanApi.getReceivablePlanPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 创建回款操作 */
|
||||
const receivableFormRef = ref()
|
||||
const openReceivableForm = (row: ReceivablePlanApi.ReceivablePlanVO) => {
|
||||
receivableFormRef.value.open('create', undefined, row)
|
||||
}
|
||||
|
||||
/** 打开详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmReceivablePlanDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
await getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
|
@ -1,39 +0,0 @@
|
|||
/** 跟进状态 */
|
||||
export const FOLLOWUP_STATUS = [
|
||||
{ label: '待跟进', value: false },
|
||||
{ label: '已跟进', value: true }
|
||||
]
|
||||
|
||||
/** 归属范围 */
|
||||
export const SCENE_TYPES = [
|
||||
{ label: '我负责的', value: 1 },
|
||||
{ label: '我参与的', value: 2 },
|
||||
{ label: '下属负责的', value: 3 }
|
||||
]
|
||||
|
||||
/** 联系状态 */
|
||||
export const CONTACT_STATUS = [
|
||||
{ label: '今日需联系', value: 1 },
|
||||
{ label: '已逾期', value: 2 },
|
||||
{ label: '已联系', value: 3 }
|
||||
]
|
||||
|
||||
/** 审批状态 */
|
||||
export const AUDIT_STATUS = [
|
||||
{ label: '待审批', value: 10 },
|
||||
{ label: '审核通过', value: 20 },
|
||||
{ label: '审核不通过', value: 30 }
|
||||
]
|
||||
|
||||
/** 回款提醒类型 */
|
||||
export const RECEIVABLE_REMIND_TYPE = [
|
||||
{ label: '待回款', value: 1 },
|
||||
{ label: '已逾期', value: 2 },
|
||||
{ label: '已回款', value: 3 }
|
||||
]
|
||||
|
||||
/** 合同过期状态 */
|
||||
export const CONTRACT_EXPIRY_TYPE = [
|
||||
{ label: '即将过期', value: 1 },
|
||||
{ label: '已过期', value: 2 }
|
||||
]
|
|
@ -1,177 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【通用】跟进记录、待办事项" url="https://doc.iocoder.cn/crm/follow-up/" /> -->
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4" class="min-w-[200px]">
|
||||
<div class="side-item-list">
|
||||
<div
|
||||
v-for="(item, index) in leftSides"
|
||||
:key="index"
|
||||
:class="leftMenu == item.menu ? 'side-item-select' : 'side-item-default'"
|
||||
class="side-item"
|
||||
@click="sideClick(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
<el-badge v-if="item.count > 0" :max="99" :value="item.count" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="20" :xs="24">
|
||||
<CustomerTodayContactList v-if="leftMenu === 'customerTodayContact'" />
|
||||
<ClueFollowList v-if="leftMenu === 'clueFollow'" />
|
||||
<ContractAuditList v-if="leftMenu === 'contractAudit'" />
|
||||
<ReceivableAuditList v-if="leftMenu === 'receivableAudit'" />
|
||||
<ContractRemindList v-if="leftMenu === 'contractRemind'" />
|
||||
<CustomerFollowList v-if="leftMenu === 'customerFollow'" />
|
||||
<CustomerPutPoolRemindList v-if="leftMenu === 'customerPutPoolRemind'" />
|
||||
<ReceivablePlanRemindList v-if="leftMenu === 'receivablePlanRemind'" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import CustomerFollowList from './components/CustomerFollowList.vue'
|
||||
import CustomerTodayContactList from './components/CustomerTodayContactList.vue'
|
||||
import CustomerPutPoolRemindList from './components/CustomerPutPoolRemindList.vue'
|
||||
import ClueFollowList from './components/ClueFollowList.vue'
|
||||
import ContractAuditList from './components/ContractAuditList.vue'
|
||||
import ContractRemindList from './components/ContractRemindList.vue'
|
||||
import ReceivablePlanRemindList from './components/ReceivablePlanRemindList.vue'
|
||||
import ReceivableAuditList from './components/ReceivableAuditList.vue'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import * as ReceivableApi from '@/api/crm/receivable'
|
||||
import * as ReceivablePlanApi from '@/api/crm/receivable/plan'
|
||||
|
||||
defineOptions({ name: 'CrmBacklog' })
|
||||
|
||||
const leftMenu = ref('customerTodayContact')
|
||||
|
||||
const clueFollowCount = ref(0)
|
||||
const customerFollowCount = ref(0)
|
||||
const customerPutPoolRemindCount = ref(0)
|
||||
const customerTodayContactCount = ref(0)
|
||||
const contractAuditCount = ref(0)
|
||||
const contractRemindCount = ref(0)
|
||||
const receivableAuditCount = ref(0)
|
||||
const receivablePlanRemindCount = ref(0)
|
||||
|
||||
const leftSides = ref([
|
||||
{
|
||||
name: '今日需联系客户',
|
||||
menu: 'customerTodayContact',
|
||||
count: customerTodayContactCount
|
||||
},
|
||||
{
|
||||
name: '分配给我的线索',
|
||||
menu: 'clueFollow',
|
||||
count: clueFollowCount
|
||||
},
|
||||
{
|
||||
name: '分配给我的客户',
|
||||
menu: 'customerFollow',
|
||||
count: customerFollowCount
|
||||
},
|
||||
{
|
||||
name: '待进入公海的客户',
|
||||
menu: 'customerPutPoolRemind',
|
||||
count: customerPutPoolRemindCount
|
||||
},
|
||||
{
|
||||
name: '待审核合同',
|
||||
menu: 'contractAudit',
|
||||
count: contractAuditCount
|
||||
},
|
||||
{
|
||||
name: '待审核回款',
|
||||
menu: 'receivableAudit',
|
||||
count: receivableAuditCount
|
||||
},
|
||||
{
|
||||
name: '待回款提醒',
|
||||
menu: 'receivablePlanRemind',
|
||||
count: receivablePlanRemindCount
|
||||
},
|
||||
{
|
||||
name: '即将到期的合同',
|
||||
menu: 'contractRemind',
|
||||
count: contractRemindCount
|
||||
}
|
||||
])
|
||||
|
||||
/** 侧边点击 */
|
||||
const sideClick = (item: any) => {
|
||||
leftMenu.value = item.menu
|
||||
}
|
||||
|
||||
const getCount = () => {
|
||||
CustomerApi.getTodayContactCustomerCount().then(
|
||||
(count) => (customerTodayContactCount.value = count)
|
||||
)
|
||||
CustomerApi.getPutPoolRemindCustomerCount().then(
|
||||
(count) => (customerPutPoolRemindCount.value = count)
|
||||
)
|
||||
CustomerApi.getFollowCustomerCount().then((count) => (customerFollowCount.value = count))
|
||||
ClueApi.getFollowClueCount().then((count) => (clueFollowCount.value = count))
|
||||
ContractApi.getAuditContractCount().then((count) => (contractAuditCount.value = count))
|
||||
ContractApi.getRemindContractCount().then((count) => (contractRemindCount.value = count))
|
||||
ReceivableApi.getAuditReceivableCount().then((count) => (receivableAuditCount.value = count))
|
||||
ReceivablePlanApi.getReceivablePlanRemindCount().then(
|
||||
(count) => (receivablePlanRemindCount.value = count)
|
||||
)
|
||||
}
|
||||
|
||||
/** 激活时 */
|
||||
onActivated(async () => {
|
||||
getCount()
|
||||
})
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
getCount()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.side-item-list {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
font-size: 14px;
|
||||
background-color: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 5px;
|
||||
|
||||
.side-item {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
padding: 0 20px;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.side-item-default {
|
||||
color: var(--el-text-color-primary);
|
||||
border-right: 2px solid transparent;
|
||||
}
|
||||
|
||||
.side-item-select {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-right: 2px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-badge :deep(.el-badge__content) {
|
||||
top: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 15px;
|
||||
}
|
||||
</style>
|
|
@ -1,287 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1280">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="商机名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入商机名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select
|
||||
v-model="formData.ownerUserId"
|
||||
:disabled="formType !== 'create'"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="客户名称" prop="customerId">
|
||||
<el-select
|
||||
:disabled="formData.customerDefault"
|
||||
v-model="formData.customerId"
|
||||
placeholder="请选择客户"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="商机状态组" prop="statusTypeId">
|
||||
<el-select
|
||||
v-model="formData.statusTypeId"
|
||||
placeholder="请选择商机状态组"
|
||||
clearable
|
||||
class="w-1/1"
|
||||
:disabled="formType !== 'create'"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in statusTypeList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="预计成交日期" prop="dealTime">
|
||||
<el-date-picker
|
||||
v-model="formData.dealTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择预计成交日期"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 子表的表单 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||
<el-tab-pane label="产品清单" name="product">
|
||||
<BusinessProductForm
|
||||
ref="productFormRef"
|
||||
:products="formData.products"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="产品总金额" prop="totalProductPrice">
|
||||
<el-input
|
||||
disabled
|
||||
v-model="formData.totalProductPrice"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="整单折扣(%)" prop="discountPercent">
|
||||
<el-input-number
|
||||
v-model="formData.discountPercent"
|
||||
placeholder="请输入整单折扣"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="折扣后金额" prop="price">
|
||||
<el-input
|
||||
disabled
|
||||
v-model="formData.totalPrice"
|
||||
placeholder="请输入商机金额"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import * as BusinessStatusApi from '@/api/crm/business/status'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import BusinessProductForm from './components/BusinessProductForm.vue'
|
||||
import { erpPriceMultiply, erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
customerId: undefined,
|
||||
ownerUserId: undefined,
|
||||
statusTypeId: undefined,
|
||||
dealTime: undefined,
|
||||
discountPercent: 0,
|
||||
totalProductPrice: undefined,
|
||||
totalPrice: undefined,
|
||||
remark: undefined,
|
||||
products: [],
|
||||
contactId: undefined,
|
||||
customerDefault: false
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '商机名称不能为空', trigger: 'blur' }],
|
||||
customerId: [{ required: true, message: '客户不能为空', trigger: 'blur' }],
|
||||
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }],
|
||||
statusTypeId: [{ required: true, message: '商机状态组不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const statusTypeList = ref([]) // 商机状态类型列表
|
||||
const customerList = ref([]) // 客户列表的数据
|
||||
|
||||
/** 子表的表单 */
|
||||
const subTabsName = ref('product')
|
||||
const productFormRef = ref()
|
||||
|
||||
/** 计算 discountPrice、totalPrice 价格 */
|
||||
watch(
|
||||
() => formData.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
const totalProductPrice = val.products.reduce((prev, curr) => prev + curr.totalPrice, 0)
|
||||
const discountPrice =
|
||||
val.discountPercent != null
|
||||
? erpPriceMultiply(totalProductPrice, val.discountPercent / 100.0)
|
||||
: 0
|
||||
const totalPrice = totalProductPrice - discountPrice
|
||||
// 赋值
|
||||
formData.value.totalProductPrice = totalProductPrice
|
||||
formData.value.totalPrice = totalPrice
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number, customerId?: number, contactId?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await BusinessApi.getBusiness(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
if (customerId) {
|
||||
formData.value.customerId = customerId
|
||||
formData.value.customerDefault = true // 默认客户的选择,不允许变
|
||||
}
|
||||
// 自动关联 contactId 联系人编号
|
||||
if (contactId) {
|
||||
formData.value.contactId = contactId
|
||||
}
|
||||
}
|
||||
// 获得客户列表
|
||||
customerList.value = await CustomerApi.getCustomerSimpleList()
|
||||
// 加载商机状态类型列表
|
||||
statusTypeList.value = await BusinessStatusApi.getBusinessStatusTypeSimpleList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 默认新建时选中自己
|
||||
if (formType.value === 'create') {
|
||||
formData.value.ownerUserId = useUserStore().getUser.id
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
await productFormRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as BusinessApi.BusinessVO
|
||||
if (formType.value === 'create') {
|
||||
await BusinessApi.createBusiness(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await BusinessApi.updateBusiness(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
customerId: undefined,
|
||||
ownerUserId: undefined,
|
||||
statusTypeId: undefined,
|
||||
dealTime: undefined,
|
||||
discountPercent: 0,
|
||||
totalProductPrice: undefined,
|
||||
totalPrice: undefined,
|
||||
remark: undefined,
|
||||
products: [],
|
||||
contactId: undefined,
|
||||
customerDefault: false
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,108 +0,0 @@
|
|||
<template>
|
||||
<Dialog title="变更商机状态" v-model="dialogVisible" width="400">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="商机阶段" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择商机阶段" class="w-1/1">
|
||||
<el-option
|
||||
v-for="item in statusList"
|
||||
:key="item.id"
|
||||
:label="item.name + '(赢单率:' + item.percent + '%)'"
|
||||
:value="item.id"
|
||||
/>
|
||||
<el-option
|
||||
v-for="item in BusinessStatusApi.DEFAULT_STATUSES"
|
||||
:key="item.endStatus"
|
||||
:label="item.name + '(赢单率:' + item.percent + '%)'"
|
||||
:value="-item.endStatus"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import * as BusinessStatusApi from '@/api/crm/business/status'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
statusId: undefined,
|
||||
endStatus: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
status: [{ required: true, message: '商机阶段不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const statusList = ref([]) // 商机状态列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (business: BusinessApi.BusinessVO) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value = {
|
||||
id: business.id,
|
||||
statusId: business.statusId,
|
||||
endStatus: business.endStatus,
|
||||
status: business.endStatus != null ? -business.endStatus : business.statusId
|
||||
}
|
||||
// 加载状态列表
|
||||
formLoading.value = true
|
||||
try {
|
||||
statusList.value = await BusinessStatusApi.getBusinessStatusSimpleList(business.statusTypeId)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await BusinessApi.updateBusinessStatus({
|
||||
id: formData.value.id,
|
||||
statusId: formData.value.status > 0 ? formData.value.status : undefined,
|
||||
endStatus: formData.value.status < 0 ? -formData.value.status : undefined
|
||||
})
|
||||
message.success('更新商机状态成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
statusId: undefined,
|
||||
endStatus: undefined,
|
||||
status: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,186 +0,0 @@
|
|||
<template>
|
||||
<!-- 操作栏 -->
|
||||
<el-row justify="end">
|
||||
<el-button @click="openForm">
|
||||
<Icon class="mr-5px" icon="ep:opportunity" />
|
||||
创建商机
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="openBusinessModal"
|
||||
v-hasPermi="['crm:contact:create-business']"
|
||||
v-if="queryParams.contactId"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:circle-plus" />关联
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="deleteContactBusinessList"
|
||||
v-hasPermi="['crm:contact:delete-business']"
|
||||
v-if="queryParams.contactId"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:remove" />解除关联
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-table
|
||||
ref="businessRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<el-table-column type="selection" width="55" v-if="queryParams.contactId" />
|
||||
<el-table-column label="商机名称" fixed="left" align="center" prop="name">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="商机金额"
|
||||
align="center"
|
||||
prop="price"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column label="客户名称" align="center" prop="customerName" />
|
||||
<el-table-column label="商机组" align="center" prop="statusTypeName" />
|
||||
<el-table-column label="商机阶段" align="center" prop="statusName" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加 -->
|
||||
<BusinessForm ref="formRef" @success="getList" />
|
||||
<!-- 关联商机选择弹框 -->
|
||||
<BusinessListModal
|
||||
ref="businessModalRef"
|
||||
:customer-id="props.customerId"
|
||||
@success="createContactBusinessList"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import BusinessForm from './../BusinessForm.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import BusinessListModal from './BusinessListModal.vue'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
defineOptions({ name: 'CrmBusinessList' })
|
||||
const props = defineProps<{
|
||||
bizType: number // 业务类型
|
||||
bizId: number // 业务编号
|
||||
customerId?: number // 关联联系人与商机时,需要传入 customerId 进行筛选
|
||||
contactId?: number // 特殊:联系人编号;在【联系人】详情中,可以传递联系人编号,默认新建的商机关联到该联系人
|
||||
}>()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
customerId: undefined as unknown, // 允许 undefined + number
|
||||
contactId: undefined as unknown // 允许 undefined + number
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 置空参数
|
||||
queryParams.customerId = undefined
|
||||
queryParams.contactId = undefined
|
||||
// 执行查询
|
||||
let data = { list: [], total: 0 }
|
||||
switch (props.bizType) {
|
||||
case BizTypeEnum.CRM_CUSTOMER:
|
||||
queryParams.customerId = props.bizId
|
||||
data = await BusinessApi.getBusinessPageByCustomer(queryParams)
|
||||
break
|
||||
case BizTypeEnum.CRM_CONTACT:
|
||||
queryParams.contactId = props.bizId
|
||||
data = await BusinessApi.getBusinessPageByContact(queryParams)
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open('create', null, props.customerId, props.contactId)
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开联系人与商机的关联弹窗 */
|
||||
const businessModalRef = ref()
|
||||
const openBusinessModal = () => {
|
||||
businessModalRef.value.open()
|
||||
}
|
||||
const createContactBusinessList = async (businessIds: number[]) => {
|
||||
const data = {
|
||||
contactId: props.bizId,
|
||||
businessIds: businessIds
|
||||
} as ContactApi.ContactBusinessReqVO
|
||||
businessRef.value.getSelectionRows().forEach((row: BusinessApi.BusinessVO) => {
|
||||
data.businessIds.push(row.id)
|
||||
})
|
||||
await ContactApi.createContactBusinessList(data)
|
||||
// 刷新列表
|
||||
message.success('关联商机成功')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 解除联系人与商机的关联 */
|
||||
const businessRef = ref()
|
||||
const deleteContactBusinessList = async () => {
|
||||
const data = {
|
||||
contactId: props.bizId,
|
||||
businessIds: businessRef.value.getSelectionRows().map((row: BusinessApi.BusinessVO) => row.id)
|
||||
} as ContactApi.ContactBusinessReqVO
|
||||
if (data.businessIds.length === 0) {
|
||||
return message.error('未选择商机')
|
||||
}
|
||||
await ContactApi.deleteContactBusinessList(data)
|
||||
// 刷新列表
|
||||
message.success('取关商机成功')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
|
||||
watch(
|
||||
() => [props.bizId, props.bizType],
|
||||
() => {
|
||||
handleQuery()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
</script>
|
|
@ -1,156 +0,0 @@
|
|||
<template>
|
||||
<Dialog title="关联商机" v-model="dialogVisible">
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="商机名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入商机名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button type="primary" @click="openForm()" v-hasPermi="['crm:business:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
ref="businessRef"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="商机名称" fixed="left" align="center" prop="name">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="商机金额"
|
||||
align="center"
|
||||
prop="totalPrice"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column label="客户名称" align="center" prop="customerName" />
|
||||
<el-table-column label="商机组" align="center" prop="statusTypeName" />
|
||||
<el-table-column label="商机阶段" align="center" prop="statusName" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 表单弹窗:添加 -->
|
||||
<BusinessForm ref="formRef" @success="getList" />
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import BusinessForm from '../BusinessForm.vue'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const props = defineProps<{
|
||||
customerId: number
|
||||
}>()
|
||||
defineOptions({ name: 'BusinessListModal' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
customerId: props.customerId
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
queryParams.customerId = props.customerId // 解决 props.customerId 没更新到 queryParams 上的问题
|
||||
await getList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await BusinessApi.getBusinessPageByCustomer(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open('create')
|
||||
}
|
||||
|
||||
/** 关联商机提交 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const businessRef = ref()
|
||||
const submitForm = async () => {
|
||||
const businessIds = businessRef.value
|
||||
.getSelectionRows()
|
||||
.map((row: BusinessApi.BusinessVO) => row.id)
|
||||
if (businessIds.length === 0) {
|
||||
return message.error('未选择商机')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('success', businessIds, businessRef.value.getSelectionRows())
|
||||
}
|
||||
|
||||
/** 打开商机详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||||
}
|
||||
</script>
|
|
@ -1,183 +0,0 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
v-loading="formLoading"
|
||||
label-width="0px"
|
||||
:inline-message="true"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<el-table :data="formData" class="-mt-10px">
|
||||
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||
<el-table-column label="产品名称" min-width="180">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
|
||||
<el-select
|
||||
v-model="row.productId"
|
||||
clearable
|
||||
filterable
|
||||
@change="onChangeProduct($event, row)"
|
||||
placeholder="请选择产品"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="条码" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-form-item class="mb-0px!">
|
||||
<el-input disabled v-model="row.productNo" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格(元)" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-form-item class="mb-0px!">
|
||||
<el-input disabled v-model="row.productPrice" :formatter="erpPriceInputFormatter" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价(元)" fixed="right" min-width="140">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.businessPrice`" class="mb-0px!">
|
||||
<el-input-number
|
||||
v-model="row.businessPrice"
|
||||
controls-position="right"
|
||||
:min="0.001"
|
||||
:precision="2"
|
||||
class="!w-100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" prop="count" fixed="right" min-width="120">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
|
||||
<el-input-number
|
||||
v-model="row.count"
|
||||
controls-position="right"
|
||||
:min="0.001"
|
||||
:precision="3"
|
||||
class="!w-100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合计" prop="totalPrice" fixed="right" min-width="140">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!">
|
||||
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="60">
|
||||
<template #default="{ $index }">
|
||||
<el-button @click="handleDelete($index)" link>—</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<el-row justify="center" class="mt-3" v-if="!disabled">
|
||||
<el-button @click="handleAdd" round>+ 添加产品</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ProductApi from '@/api/crm/product'
|
||||
import { erpPriceInputFormatter, erpPriceMultiply } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
const props = defineProps<{
|
||||
products: undefined
|
||||
disabled: false
|
||||
}>()
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
|
||||
businessPrice: [{ required: true, message: '合同价格不能为空', trigger: 'blur' }],
|
||||
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref([]) // 表单 Ref
|
||||
const productList = ref<ProductApi.ProductVO[]>([]) // 产品列表
|
||||
|
||||
/** 初始化设置产品项 */
|
||||
watch(
|
||||
() => props.products,
|
||||
async (val) => {
|
||||
formData.value = val
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 监听合同产品变化,计算合同产品总价 */
|
||||
watch(
|
||||
() => formData.value,
|
||||
(val) => {
|
||||
if (!val || val.length === 0) {
|
||||
return
|
||||
}
|
||||
// 循环处理
|
||||
val.forEach((item) => {
|
||||
if (item.businessPrice != null && item.count != null) {
|
||||
item.totalPrice = erpPriceMultiply(item.businessPrice, item.count)
|
||||
} else {
|
||||
item.totalPrice = undefined
|
||||
}
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
const row = {
|
||||
id: undefined,
|
||||
productId: undefined,
|
||||
productUnit: undefined, // 产品单位
|
||||
productNo: undefined, // 产品条码
|
||||
productPrice: undefined, // 产品价格
|
||||
businessPrice: undefined,
|
||||
count: 1
|
||||
}
|
||||
formData.value.push(row)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (index: number) => {
|
||||
formData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 处理产品变更 */
|
||||
const onChangeProduct = (productId, row) => {
|
||||
const product = productList.value.find((item) => item.id === productId)
|
||||
if (product) {
|
||||
row.productUnit = product.unit
|
||||
row.productNo = product.no
|
||||
row.productPrice = product.price
|
||||
row.businessPrice = product.price
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = () => {
|
||||
return formRef.value.validate()
|
||||
}
|
||||
defineExpose({ validate })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
productList.value = await ProductApi.getProductSimpleList()
|
||||
})
|
||||
</script>
|
|
@ -1,37 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<el-col>
|
||||
<el-row>
|
||||
<span class="text-xl font-bold">{{ business.name }}</span>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 右上:按钮 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-descriptions :column="5" direction="vertical">
|
||||
<el-descriptions-item label="客户名称">{{ business.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商机金额(元)">
|
||||
{{ erpPriceInputFormatter(business.totalPrice) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="商机组">{{ business.statusTypeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="负责人">{{ business.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(business.createTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { erpPriceInputFormatter } from '@/utils'
|
||||
|
||||
const { business } = defineProps<{ business: BusinessApi.BusinessVO }>()
|
||||
</script>
|
|
@ -1,61 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="basicInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">基本信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="商机姓名">{{ business.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ business.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商机金额(元)">
|
||||
{{ erpPriceInputFormatter(business.totalPrice) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预计成交日期">
|
||||
{{ formatDate(business.dealTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下次联系时间">
|
||||
{{ formatDate(business.contactNextTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="商机状态组">
|
||||
{{ business.statusTypeName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="商机阶段">{{ business.statusName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ business.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="systemInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">系统信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="负责人">{{ business.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进时间">
|
||||
{{ formatDate(business.contactLastTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ business.creatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(business.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(business.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { erpPriceInputFormatter } from '@/utils'
|
||||
|
||||
const { business } = defineProps<{
|
||||
business: BusinessApi.BusinessVO
|
||||
}>()
|
||||
|
||||
// 展示的折叠面板
|
||||
const activeNames = ref(['basicInfo', 'systemInfo'])
|
||||
</script>
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-table :data="business.products" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="产品名称"
|
||||
fixed="left"
|
||||
prop="productName"
|
||||
min-width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.productName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品条码" align="center" prop="productNo" min-width="120" />
|
||||
<el-table-column align="center" label="产品单位" prop="productUnit" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="产品价格(元)"
|
||||
align="center"
|
||||
prop="productPrice"
|
||||
min-width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="商机价格(元)"
|
||||
align="center"
|
||||
prop="businessPrice"
|
||||
min-width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="数量"
|
||||
prop="count"
|
||||
min-width="100px"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="合计金额(元)"
|
||||
align="center"
|
||||
prop="totalPrice"
|
||||
min-width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-table>
|
||||
<el-row class="mt-10px" justify="end">
|
||||
<el-col :span="3"> 整单折扣:{{ erpPriceInputFormatter(business.discountPercent) }}% </el-col>
|
||||
<el-col :span="4">
|
||||
产品总金额:{{ erpPriceInputFormatter(business.totalProductPrice) }} 元
|
||||
</el-col>
|
||||
</el-row>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
const { business } = defineProps<{
|
||||
business: BusinessApi.BusinessVO
|
||||
}>()
|
||||
</script>
|
|
@ -1,146 +0,0 @@
|
|||
<template>
|
||||
<BusinessDetailsHeader v-loading="loading" :business="business">
|
||||
<el-button v-if="permissionListRef?.validateWrite" @click="openForm('update', business.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="permissionListRef?.validateWrite"
|
||||
:disabled="business.endStatus"
|
||||
type="success"
|
||||
@click="openStatusForm()"
|
||||
>
|
||||
变更商机状态
|
||||
</el-button>
|
||||
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
|
||||
转移
|
||||
</el-button>
|
||||
</BusinessDetailsHeader>
|
||||
<el-col>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="跟进记录">
|
||||
<FollowUpList :biz-id="businessId" :biz-type="BizTypeEnum.CRM_BUSINESS" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="详细资料">
|
||||
<BusinessDetailsInfo :business="business" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="联系人" lazy>
|
||||
<ContactList
|
||||
:biz-id="business.id!"
|
||||
:biz-type="BizTypeEnum.CRM_BUSINESS"
|
||||
:business-id="business.id"
|
||||
:customer-id="business.customerId"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="产品">
|
||||
<BusinessProductList :business="business" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="合同" lazy>
|
||||
<ContractList :biz-id="business.id!" :biz-type="BizTypeEnum.CRM_BUSINESS" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志">
|
||||
<OperateLogV2 :log-list="logList" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员">
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="business.id!"
|
||||
:biz-type="BizTypeEnum.CRM_BUSINESS"
|
||||
:show-action="true"
|
||||
@quit-team="close"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<BusinessForm ref="formRef" @success="getBusiness" />
|
||||
<BusinessUpdateStatusForm ref="statusFormRef" @success="getBusiness" />
|
||||
<CrmTransferForm ref="transferFormRef" :biz-type="BizTypeEnum.CRM_BUSINESS" @success="close" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import BusinessDetailsHeader from './BusinessDetailsHeader.vue'
|
||||
import BusinessDetailsInfo from './BusinessDetailsInfo.vue'
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import BusinessForm from '@/views/crm/business/BusinessForm.vue'
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
import ContactList from '@/views/crm/contact/components/ContactList.vue'
|
||||
import BusinessUpdateStatusForm from '@/views/crm/business/BusinessUpdateStatusForm.vue'
|
||||
import ContractList from '@/views/crm/contract/components/ContractList.vue'
|
||||
import BusinessProductList from '@/views/crm/business/detail/BusinessProductList.vue'
|
||||
|
||||
defineOptions({ name: 'CrmBusinessDetail' })
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const businessId = ref(0) // 线索编号
|
||||
const loading = ref(true) // 加载中
|
||||
const business = ref<BusinessApi.BusinessVO>({} as BusinessApi.BusinessVO) // 商机详情
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
|
||||
|
||||
/** 获取详情 */
|
||||
const getBusiness = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
business.value = await BusinessApi.getBusiness(businessId.value)
|
||||
await getOperateLog(businessId.value)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 变更商机状态 */
|
||||
const statusFormRef = ref()
|
||||
const openStatusForm = () => {
|
||||
statusFormRef.value.open(business.value)
|
||||
}
|
||||
|
||||
/** 联系人转移 */
|
||||
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref
|
||||
const transfer = () => {
|
||||
transferFormRef.value?.open(business.value.id)
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (contactId: number) => {
|
||||
if (!contactId) {
|
||||
return
|
||||
}
|
||||
const data = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_BUSINESS,
|
||||
bizId: contactId
|
||||
})
|
||||
logList.value = data.list
|
||||
}
|
||||
|
||||
/** 关闭窗口 */
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const { params } = useRoute()
|
||||
onMounted(async () => {
|
||||
if (!params.id) {
|
||||
message.warning('参数错误,商机不能为空!')
|
||||
close()
|
||||
return
|
||||
}
|
||||
businessId.value = params.id as unknown as number
|
||||
await getBusiness()
|
||||
})
|
||||
</script>
|
|
@ -1,275 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【商机】商机管理、商机状态" url="https://doc.iocoder.cn/crm/business/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="商机名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商机名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['crm:business:create']" type="primary" @click="openForm('create')">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:business:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="我负责的" name="1" />
|
||||
<el-tab-pane label="我参与的" name="2" />
|
||||
<el-tab-pane label="下属负责的" name="3" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" fixed="left" label="商机名称" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
align="center"
|
||||
label="商机金额(元)"
|
||||
prop="totalPrice"
|
||||
width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="预计成交日期"
|
||||
prop="dealTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
fixed="right"
|
||||
label="商机状态组"
|
||||
prop="statusTypeName"
|
||||
width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
fixed="right"
|
||||
label="商机阶段"
|
||||
prop="statusName"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="130px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['crm:business:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:business:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<BusinessForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import BusinessForm from './BusinessForm.vue'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { TabsPaneContext } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CrmBusiness' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: '1', // 默认和 activeName 相等
|
||||
name: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const activeName = ref('1') // 列表 tab
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await BusinessApi.getBusinessPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName!.toString()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await BusinessApi.deleteBusiness(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await BusinessApi.exportBusiness(queryParams)
|
||||
download.excel(data, '商机.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,194 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="状态组名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入状态组名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用部门" prop="deptIds">
|
||||
<template #label>
|
||||
<Tooltip message="不选择部门时,默认全公司生效" title="应用部门" />
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="deptList"
|
||||
:props="defaultProps"
|
||||
:check-strictly="!checkStrictly"
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="阶段设置" prop="statuses">
|
||||
<el-table
|
||||
border
|
||||
style="width: 100%"
|
||||
:data="formData.statuses.concat(BusinessStatusApi.DEFAULT_STATUSES)"
|
||||
>
|
||||
<el-table-column align="center" label="阶段" width="70">
|
||||
<template #default="scope">
|
||||
<el-text v-if="!scope.row.defaultStatus">阶段 {{ scope.$index + 1 }}</el-text>
|
||||
<el-text v-else>结束</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="阶段名称" width="160" prop="name">
|
||||
<template #default="{ row }">
|
||||
<el-input v-if="!row.endStatus" v-model="row.name" placeholder="请输入状态名称" />
|
||||
<el-text v-else>{{ row.name }}</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="140" align="center" label="赢单率(%)" prop="percent">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="!row.endStatus"
|
||||
v-model="row.percent"
|
||||
placeholder="请输入赢单率"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
<el-text v-else>{{ row.percent }}</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="!scope.row.endStatus"
|
||||
link
|
||||
type="primary"
|
||||
@click="addStatus(scope.$index)"
|
||||
>
|
||||
添加
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!scope.row.endStatus"
|
||||
link
|
||||
type="danger"
|
||||
@click="deleteStatusArea(scope.$index)"
|
||||
:disabled="formData.statuses.length <= 1"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as BusinessStatusApi from '@/api/crm/business/status'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的组:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
deptIds: [],
|
||||
statuses: []
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '状态组名不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const deptList = ref<Tree[]>([]) // 树形结构
|
||||
const treeRef = ref() // 菜单树组件 Ref
|
||||
const checkStrictly = ref(true) // 是否严格模式,即父子不关联
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await BusinessStatusApi.getBusinessStatus(id)
|
||||
treeRef.value.setCheckedKeys(formData.value.deptIds)
|
||||
if (formData.value.statuses.length == 0) {
|
||||
addStatus()
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
addStatus()
|
||||
}
|
||||
// 加载部门树
|
||||
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as BusinessStatusApi.BusinessStatusTypeVO
|
||||
data.deptIds = treeRef.value.getCheckedKeys(false)
|
||||
if (formType.value === 'create') {
|
||||
await BusinessStatusApi.createBusinessStatus(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await BusinessStatusApi.updateBusinessStatus(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
checkStrictly.value = true
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
deptIds: [],
|
||||
statuses: []
|
||||
}
|
||||
treeRef.value?.setCheckedNodes([])
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 添加状态 */
|
||||
const addStatus = () => {
|
||||
const data = formData.value
|
||||
data.statuses.push({
|
||||
name: '',
|
||||
percent: undefined
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除状态 */
|
||||
const deleteStatusArea = (index: number) => {
|
||||
const data = formData.value
|
||||
data.statuses.splice(index, 1)
|
||||
}
|
||||
</script>
|
|
@ -1,150 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【商机】商机管理、商机状态" url="https://doc.iocoder.cn/crm/business/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['crm:business-status:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="状态组名" align="center" prop="name" />
|
||||
<el-table-column label="应用部门" align="center" prop="deptNames">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row?.deptNames?.length > 0">
|
||||
{{ scope.row.deptNames.join(' ') }}
|
||||
</span>
|
||||
<span v-else>全公司</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建人" align="center" prop="creator" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['crm:business-status:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['crm:business-status:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<BusinessStatusForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
// import download from '@/utils/download'
|
||||
import * as BusinessStatusApi from '@/api/crm/business/status'
|
||||
import BusinessStatusForm from './BusinessStatusForm.vue'
|
||||
// import { deleteBusinessStatus } from '@/api/crm/business/status'
|
||||
|
||||
defineOptions({ name: 'CrmBusinessStatus' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
// const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await BusinessStatusApi.getBusinessStatusPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// /** 搜索按钮操作 */
|
||||
// const handleQuery = () => {
|
||||
// queryParams.pageNo = 1
|
||||
// getList()
|
||||
// }
|
||||
|
||||
// /** 重置按钮操作 */
|
||||
// const resetQuery = () => {
|
||||
// queryFormRef.value.resetFields()
|
||||
// handleQuery()
|
||||
// }
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await BusinessStatusApi.deleteBusinessStatus(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,259 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="线索名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入线索名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户来源" prop="source">
|
||||
<el-select v-model="formData.source" placeholder="请选择客户来源" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select
|
||||
v-model="formData.ownerUserId"
|
||||
:disabled="formType !== 'create'"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="电话" prop="telephone">
|
||||
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信" prop="wechat">
|
||||
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="QQ" prop="qq">
|
||||
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户行业" prop="industryId">
|
||||
<el-select v-model="formData.industryId" placeholder="请选择客户行业" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户级别" prop="level">
|
||||
<el-select v-model="formData.level" placeholder="请选择客户级别" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地址" prop="areaId">
|
||||
<el-cascader
|
||||
v-model="formData.areaId"
|
||||
:options="areaList"
|
||||
:props="defaultProps"
|
||||
class="w-1/1"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择城市"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="详细地址" prop="detailAddress">
|
||||
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="下次联系时间" prop="contactNextTime">
|
||||
<el-date-picker
|
||||
v-model="formData.contactNextTime"
|
||||
placeholder="选择下次联系时间"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
import { defaultProps } from '@/utils/tree'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const areaList = ref([]) // 地区列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
contactNextTime: undefined,
|
||||
ownerUserId: 0,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
qq: undefined,
|
||||
wechat: undefined,
|
||||
email: undefined,
|
||||
areaId: undefined,
|
||||
detailAddress: undefined,
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '线索名称不能为空', trigger: 'blur' }],
|
||||
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ClueApi.getClue(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得地区列表
|
||||
areaList.value = await AreaApi.getAreaTree()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 默认新建时选中自己
|
||||
if (formType.value === 'create') {
|
||||
formData.value.ownerUserId = useUserStore().getUser.id
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ClueApi.ClueVO
|
||||
if (formType.value === 'create') {
|
||||
await ClueApi.createClue(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ClueApi.updateClue(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
contactNextTime: undefined,
|
||||
ownerUserId: 0,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
qq: undefined,
|
||||
wechat: undefined,
|
||||
email: undefined,
|
||||
areaId: undefined,
|
||||
detailAddress: undefined,
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,43 +0,0 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<!-- 左上:线索基本信息 -->
|
||||
<el-col>
|
||||
<el-row>
|
||||
<span class="text-xl font-bold">{{ clue.name }}</span>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 右上:按钮 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-descriptions :column="5" direction="vertical">
|
||||
<el-descriptions-item label="线索来源">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机"> {{ clue.mobile }} </el-descriptions-item>
|
||||
<el-descriptions-item label="负责人">
|
||||
{{ clue.ownerUserName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(clue.createTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'CrmClueDetailsHeader' })
|
||||
defineProps<{
|
||||
clue: ClueApi.ClueVO // 线索信息
|
||||
loading: boolean // 加载中
|
||||
}>()
|
||||
</script>
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-collapse v-model="activeNames" class="">
|
||||
<el-collapse-item name="basicInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">基本信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="线索名称">
|
||||
{{ clue.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户来源">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ clue.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电话">{{ clue.telephone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ clue.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">
|
||||
{{ clue.areaName }} {{ clue.detailAddress }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="QQ">{{ clue.qq }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信">{{ clue.wechat }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户行业">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="clue.industryId" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户级别">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="clue.level" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下次联系时间">
|
||||
{{ formatDate(clue.contactNextTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ clue.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="systemInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">系统信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="负责人">{{ clue.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进记录">
|
||||
{{ clue.contactLastContent }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进时间">
|
||||
{{ formatDate(clue.contactLastTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ clue.creatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(clue.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(clue.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'CrmClueDetailsInfo' })
|
||||
const { clue } = defineProps<{
|
||||
clue: ClueApi.ClueVO // 线索明细
|
||||
}>()
|
||||
|
||||
const activeNames = ref(['basicInfo', 'systemInfo']) // 展示的折叠面板
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
|
@ -1,130 +0,0 @@
|
|||
<template>
|
||||
<ClueDetailsHeader :clue="clue" :loading="loading">
|
||||
<el-button
|
||||
v-if="permissionListRef?.validateWrite"
|
||||
v-hasPermi="['crm:clue:update']"
|
||||
type="primary"
|
||||
@click="openForm"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
|
||||
转移
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="permissionListRef?.validateOwnerUser && !clue.transformStatus"
|
||||
type="success"
|
||||
@click="handleTransform"
|
||||
>
|
||||
转化为客户
|
||||
</el-button>
|
||||
<el-button v-else disabled type="success">已转化客户</el-button>
|
||||
</ClueDetailsHeader>
|
||||
<el-col>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="跟进记录">
|
||||
<FollowUpList :biz-id="clueId" :biz-type="BizTypeEnum.CRM_CLUE" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="基本信息">
|
||||
<ClueDetailsInfo :clue="clue" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员">
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="clue.id!"
|
||||
:biz-type="BizTypeEnum.CRM_CLUE"
|
||||
:show-action="true"
|
||||
@quit-team="close"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志">
|
||||
<OperateLogV2 :log-list="logList" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ClueForm ref="formRef" @success="getClue" />
|
||||
<CrmTransferForm ref="transferFormRef" :biz-type="BizTypeEnum.CRM_CLUE" @success="close" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import ClueForm from '@/views/crm/clue/ClueForm.vue'
|
||||
import ClueDetailsHeader from './ClueDetailsHeader.vue' // 线索明细 - 头部
|
||||
import ClueDetailsInfo from './ClueDetailsInfo.vue' // 线索明细 - 详细信息
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import type { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
|
||||
defineOptions({ name: 'CrmClueDetail' })
|
||||
|
||||
const clueId = ref(0) // 线索编号
|
||||
const loading = ref(true) // 加载中
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
|
||||
|
||||
/** 获取详情 */
|
||||
const clue = ref<ClueApi.ClueVO>({} as ClueApi.ClueVO) // 线索详情
|
||||
const getClue = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
clue.value = await ClueApi.getClue(clueId.value)
|
||||
await getOperateLog()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑线索 */
|
||||
const formRef = ref<InstanceType<typeof ClueForm>>() // 线索表单 Ref
|
||||
const openForm = () => {
|
||||
formRef.value?.open('update', clueId.value)
|
||||
}
|
||||
|
||||
/** 线索转移 */
|
||||
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 线索转移表单 ref
|
||||
const transfer = () => {
|
||||
transferFormRef.value?.open(clueId.value)
|
||||
}
|
||||
|
||||
/** 转化为客户 */
|
||||
const handleTransform = async () => {
|
||||
await message.confirm(`确定将【${clue.value.name}】转化为客户吗?`)
|
||||
await ClueApi.transformClue(clueId.value)
|
||||
message.success(`转化客户【${clue.value.name}】成功`)
|
||||
await getClue()
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async () => {
|
||||
const data = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_CLUE,
|
||||
bizId: clueId.value
|
||||
})
|
||||
logList.value = data.list
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const { params } = useRoute()
|
||||
onMounted(() => {
|
||||
if (!params.id) {
|
||||
message.warning('参数错误,线索不能为空!')
|
||||
close()
|
||||
return
|
||||
}
|
||||
clueId.value = params.id as unknown as number
|
||||
getClue()
|
||||
})
|
||||
</script>
|
|
@ -1,270 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【线索】线索管理" url="https://doc.iocoder.cn/crm/clue/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="线索名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入线索名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="转化状态" prop="transformStatus">
|
||||
<el-select v-model="queryParams.transformStatus" class="!w-240px">
|
||||
<el-option :value="false" label="未转化" />
|
||||
<el-option :value="true" label="已转化" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input
|
||||
v-model="queryParams.mobile"
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="telephone">
|
||||
<el-input
|
||||
v-model="queryParams.telephone"
|
||||
placeholder="请输入电话"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:clue:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['crm:clue:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="我负责的" name="1" />
|
||||
<el-tab-pane label="我参与的" name="2" />
|
||||
<el-tab-pane label="下属负责的" name="3" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="线索名称" align="center" prop="name" fixed="left" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="线索来源" align="center" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
label="最后跟进时间"
|
||||
align="center"
|
||||
prop="contactLastTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100" />
|
||||
<el-table-column
|
||||
label="更新时间"
|
||||
align="center"
|
||||
prop="updateTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
<el-table-column label="操作" align="center" min-width="110" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['crm:clue:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['crm:clue:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ClueForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ClueApi from '@/api/crm/clue'
|
||||
import ClueForm from './ClueForm.vue'
|
||||
import { TabsPaneContext } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CrmClue' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: '1', // 默认和 activeName 相等
|
||||
name: null,
|
||||
telephone: null,
|
||||
mobile: null,
|
||||
transformStatus: false
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const activeName = ref('1') // 列表 tab
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ClueApi.getCluePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName!.toString()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 打开线索详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmClueDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ClueApi.deleteClue(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ClueApi.exportClue(queryParams)
|
||||
download.excel(data, '线索.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,310 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人姓名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select
|
||||
v-model="formData.ownerUserId"
|
||||
:disabled="formType !== 'create'"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户名称" prop="customerId">
|
||||
<el-select
|
||||
:disabled="formData.customerDefault"
|
||||
v-model="formData.customerId"
|
||||
placeholder="请选择客户"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="电话" prop="telephone">
|
||||
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信" prop="wechat">
|
||||
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="QQ" prop="qq">
|
||||
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职位" prop="post">
|
||||
<el-input v-model="formData.post" placeholder="请输入职位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关键决策人" prop="master" style="width: 400px">
|
||||
<el-radio-group v-model="formData.master">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="formData.sex" placeholder="请选择" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="直属上级" prop="parentId">
|
||||
<el-select v-model="formData.parentId" placeholder="请选择直属上级" class="w-1/1">
|
||||
<el-option
|
||||
v-for="item in contactList"
|
||||
:key="item.id"
|
||||
:disabled="item.id == formData.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地址" prop="areaId">
|
||||
<el-cascader
|
||||
v-model="formData.areaId"
|
||||
:options="areaList"
|
||||
:props="defaultProps"
|
||||
class="w-1/1"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择城市"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="详细地址" prop="detailAddress">
|
||||
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="下次联系时间" prop="contactNextTime">
|
||||
<el-date-picker
|
||||
v-model="formData.contactNextTime"
|
||||
placeholder="选择下次联系时间"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
import { defaultProps } from '@/utils/tree'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const areaList = ref([]) // 地区列表
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
customerId: undefined,
|
||||
contactNextTime: undefined,
|
||||
ownerUserId: 0,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
qq: undefined,
|
||||
wechat: undefined,
|
||||
email: undefined,
|
||||
areaId: undefined,
|
||||
detailAddress: undefined,
|
||||
sex: undefined,
|
||||
master: false,
|
||||
post: undefined,
|
||||
parentId: undefined,
|
||||
remark: undefined,
|
||||
businessId: undefined,
|
||||
customerDefault: false
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
|
||||
customerId: [{ required: true, message: '客户不能为空', trigger: 'blur' }],
|
||||
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
||||
const contactList = ref<ContactApi.ContactVO[]>([]) // 联系人列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number, customerId?: number, businessId?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ContactApi.getContact(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
if (customerId) {
|
||||
formData.value.customerId = customerId
|
||||
formData.value.customerDefault = true // 默认客户的选择,不允许变
|
||||
}
|
||||
// 自动关联 businessId 商机编号
|
||||
if (businessId) {
|
||||
formData.value.businessId = businessId
|
||||
}
|
||||
}
|
||||
// 获得联系人列表
|
||||
contactList.value = await ContactApi.getSimpleContactList()
|
||||
// 获得客户列表
|
||||
customerList.value = await CustomerApi.getCustomerSimpleList()
|
||||
// 获得地区列表
|
||||
areaList.value = await AreaApi.getAreaTree()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 默认新建时选中自己
|
||||
if (formType.value === 'create') {
|
||||
formData.value.ownerUserId = useUserStore().getUser.id
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ContactApi.ContactVO
|
||||
if (formType.value === 'create') {
|
||||
await ContactApi.createContact(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ContactApi.updateContact(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
customerId: undefined,
|
||||
contactNextTime: undefined,
|
||||
ownerUserId: 0,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
qq: undefined,
|
||||
wechat: undefined,
|
||||
email: undefined,
|
||||
areaId: undefined,
|
||||
detailAddress: undefined,
|
||||
sex: undefined,
|
||||
master: false,
|
||||
post: undefined,
|
||||
parentId: undefined,
|
||||
remark: undefined,
|
||||
businessId: undefined,
|
||||
customerDefault: false
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,185 +0,0 @@
|
|||
<template>
|
||||
<!-- 操作栏 -->
|
||||
<el-row justify="end">
|
||||
<el-button @click="openForm">
|
||||
<Icon class="mr-5px" icon="system-uicons:contacts" />
|
||||
创建联系人
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="queryParams.businessId"
|
||||
v-hasPermi="['crm:contact:create-business']"
|
||||
@click="openBusinessModal"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:circle-plus" />
|
||||
关联
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="queryParams.businessId"
|
||||
v-hasPermi="['crm:contact:delete-business']"
|
||||
@click="deleteContactBusinessList"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:remove" />
|
||||
解除关联
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-table
|
||||
ref="contactRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
>
|
||||
<el-table-column v-if="queryParams.businessId" type="selection" width="55" />
|
||||
<el-table-column align="center" fixed="left" label="姓名" prop="name">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="手机号" prop="mobile" />
|
||||
<el-table-column align="center" label="职位" prop="post" />
|
||||
<el-table-column align="center" label="直属上级" prop="parentName" />
|
||||
<el-table-column align="center" label="是否关键决策人" min-width="100" prop="master">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加 -->
|
||||
<ContactForm ref="formRef" @success="getList" />
|
||||
<!-- 关联商机选择弹框 -->
|
||||
<ContactListModal
|
||||
v-if="customerId"
|
||||
ref="contactModalRef"
|
||||
:customer-id="customerId"
|
||||
@success="createContactBusinessList"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import ContactForm from './../ContactForm.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import ContactListModal from './ContactListModal.vue'
|
||||
|
||||
defineOptions({ name: 'CrmContactList' })
|
||||
const props = defineProps<{
|
||||
bizType: number // 业务类型
|
||||
bizId: number // 业务编号
|
||||
customerId?: number // 特殊:客户编号;在【商机】详情中,可以传递客户编号,默认新建的联系人关联到该客户
|
||||
businessId?: number // 特殊:商机编号;在【商机】详情中,可以传递商机编号,默认新建的联系人关联到该商机
|
||||
}>()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
customerId: undefined as unknown, // 允许 undefined + number
|
||||
businessId: undefined as unknown // 允许 undefined + number
|
||||
})
|
||||
const message = useMessage()
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 置空参数
|
||||
queryParams.customerId = undefined
|
||||
// 执行查询
|
||||
let data = { list: [], total: 0 }
|
||||
switch (props.bizType) {
|
||||
case BizTypeEnum.CRM_CUSTOMER:
|
||||
queryParams.customerId = props.bizId
|
||||
data = await ContactApi.getContactPageByCustomer(queryParams)
|
||||
break
|
||||
case BizTypeEnum.CRM_BUSINESS:
|
||||
queryParams.businessId = props.bizId
|
||||
data = await ContactApi.getContactPageByBusiness(queryParams)
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open('create', undefined, props.customerId, props.businessId)
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开联系人与商机的关联弹窗 */
|
||||
const contactModalRef = ref()
|
||||
const openBusinessModal = () => {
|
||||
contactModalRef.value.open()
|
||||
}
|
||||
const createContactBusinessList = async (contactIds: number[]) => {
|
||||
const data = {
|
||||
businessId: props.bizId,
|
||||
contactIds: contactIds
|
||||
} as ContactApi.ContactBusiness2ReqVO
|
||||
contactRef.value.getSelectionRows().forEach((row: ContactApi.ContactVO) => {
|
||||
data.contactIds.push(row.id)
|
||||
})
|
||||
await ContactApi.createContactBusinessList2(data)
|
||||
// 刷新列表
|
||||
message.success('关联联系人成功')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 解除联系人与商机的关联 */
|
||||
const contactRef = ref()
|
||||
const deleteContactBusinessList = async () => {
|
||||
const data = {
|
||||
businessId: props.bizId,
|
||||
contactIds: contactRef.value.getSelectionRows().map((row: ContactApi.ContactVO) => row.id)
|
||||
} as ContactApi.ContactBusiness2ReqVO
|
||||
if (data.contactIds.length === 0) {
|
||||
return message.error('未选择联系人')
|
||||
}
|
||||
await ContactApi.deleteContactBusinessList2(data)
|
||||
// 刷新列表
|
||||
message.success('取关联系人成功')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
|
||||
watch(
|
||||
() => [props.bizId, props.bizType],
|
||||
() => {
|
||||
handleQuery()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
</script>
|
|
@ -1,160 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="关联联系人">
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="联系人名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入联系人名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['crm:business:create']" type="primary" @click="openForm()">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-table
|
||||
ref="contactRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="center" fixed="left" label="姓名" prop="name">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="手机号" prop="mobile" />
|
||||
<el-table-column align="center" label="职位" prop="post" />
|
||||
<el-table-column align="center" label="直属上级" prop="parentName" />
|
||||
<el-table-column align="center" label="是否关键决策人" min-width="100" prop="master">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 表单弹窗:添加 -->
|
||||
<ContactForm ref="formRef" @success="getList" />
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import ContactForm from '../ContactForm.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const props = defineProps<{
|
||||
customerId: number
|
||||
}>()
|
||||
defineOptions({ name: 'ContactListModal' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
customerId: props.customerId
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
queryParams.customerId = props.customerId // 解决 props.customerId 没更新到 queryParams 上的问题
|
||||
await getList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ContactApi.getContactPageByCustomer(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open('create')
|
||||
}
|
||||
|
||||
/** 关联联系人提交 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const contactRef = ref()
|
||||
const submitForm = async () => {
|
||||
const contactIds = contactRef.value.getSelectionRows().map((row: ContactApi.ContactVO) => row.id)
|
||||
if (contactIds.length === 0) {
|
||||
return message.error('未选择联系人')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('success', contactIds, contactRef.value.getSelectionRows())
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
</script>
|
|
@ -1,33 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<el-col>
|
||||
<el-row>
|
||||
<span class="text-xl font-bold">{{ contact.name }}</span>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 右上:按钮 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-descriptions :column="5" direction="vertical">
|
||||
<el-descriptions-item label="客户名称">{{ contact.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职务">{{ contact.post }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ contact.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(contact.createTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
const { contact } = defineProps<{ contact: ContactApi.ContactVO }>()
|
||||
</script>
|
|
@ -1,69 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="basicInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">基本信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="姓名">{{ contact.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ contact.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ contact.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电话">{{ contact.telephone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ contact.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="QQ">{{ contact.qq }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信">{{ contact.wechat }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">
|
||||
{{ contact.areaName }} {{ contact.detailAddress }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="职务">{{ contact.post }}</el-descriptions-item>
|
||||
<el-descriptions-item label="直属上级">{{ contact.parentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关键决策人">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="contact.master" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下次联系时间">
|
||||
{{ formatDate(contact.contactNextTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ contact.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="systemInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">系统信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="负责人">{{ contact.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进记录">
|
||||
{{ contact.contactLastContent }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进时间">
|
||||
{{ formatDate(contact.contactLastTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ contact.creatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(contact.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(contact.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
const { contact } = defineProps<{
|
||||
contact: ContactApi.ContactVO
|
||||
}>()
|
||||
|
||||
// 展示的折叠面板
|
||||
const activeNames = ref(['basicInfo', 'systemInfo'])
|
||||
</script>
|
|
@ -1,121 +0,0 @@
|
|||
<template>
|
||||
<ContactDetailsHeader v-loading="loading" :contact="contact">
|
||||
<el-button v-if="permissionListRef?.validateWrite" @click="openForm('update', contact.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
|
||||
转移
|
||||
</el-button>
|
||||
</ContactDetailsHeader>
|
||||
<el-col>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="跟进记录">
|
||||
<FollowUpList :biz-id="contactId" :biz-type="BizTypeEnum.CRM_CONTACT" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="详细资料">
|
||||
<ContactDetailsInfo :contact="contact" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志">
|
||||
<OperateLogV2 :log-list="logList" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员">
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="contact.id!"
|
||||
:biz-type="BizTypeEnum.CRM_CONTACT"
|
||||
:show-action="true"
|
||||
@quit-team="close"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="商机" lazy>
|
||||
<BusinessList
|
||||
:biz-id="contact.id!"
|
||||
:biz-type="BizTypeEnum.CRM_CONTACT"
|
||||
:contact-id="contact.id"
|
||||
:customer-id="contact.customerId"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ContactForm ref="formRef" @success="getContact" />
|
||||
<CrmTransferForm ref="transferFormRef" :biz-type="BizTypeEnum.CRM_CONTACT" @success="close" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import ContactDetailsHeader from '@/views/crm/contact/detail/ContactDetailsHeader.vue'
|
||||
import ContactDetailsInfo from '@/views/crm/contact/detail/ContactDetailsInfo.vue'
|
||||
import BusinessList from '@/views/crm/business/components/BusinessList.vue' // 商机列表
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import ContactForm from '@/views/crm/contact/ContactForm.vue'
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
|
||||
defineOptions({ name: 'CrmContactDetail' })
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const contactId = ref(0) // 线索编号
|
||||
const loading = ref(true) // 加载中
|
||||
const contact = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) // 联系人详情
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
|
||||
|
||||
/** 获取详情 */
|
||||
const getContact = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
contact.value = await ContactApi.getContact(contactId.value)
|
||||
await getOperateLog(contactId.value)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 联系人转移 */
|
||||
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref
|
||||
const transfer = () => {
|
||||
transferFormRef.value?.open(contact.value.id)
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (contactId: number) => {
|
||||
if (!contactId) {
|
||||
return
|
||||
}
|
||||
const data = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_CONTACT,
|
||||
bizId: contactId
|
||||
})
|
||||
logList.value = data.list
|
||||
}
|
||||
|
||||
/** 关闭窗口 */
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const { params } = useRoute()
|
||||
onMounted(async () => {
|
||||
if (!params.id) {
|
||||
message.warning('参数错误,联系人不能为空!')
|
||||
close()
|
||||
return
|
||||
}
|
||||
contactId.value = params.id as unknown as number
|
||||
await getContact()
|
||||
})
|
||||
</script>
|
|
@ -1,332 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="客户" prop="customerId">
|
||||
<el-select
|
||||
v-model="queryParams.customerId"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
lable-key="name"
|
||||
placeholder="请选择客户"
|
||||
value-key="id"
|
||||
@keyup.enter="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入姓名"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input
|
||||
v-model="queryParams.mobile"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入手机号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="telephone">
|
||||
<el-input
|
||||
v-model="queryParams.telephone"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入电话"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信" prop="wechat">
|
||||
<el-input
|
||||
v-model="queryParams.wechat"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入微信"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="电子邮箱" prop="email">
|
||||
<el-input
|
||||
v-model="queryParams.email"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入电子邮箱"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['crm:contact:create']" type="primary" @click="openForm('create')">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:contact:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="我负责的" name="1" />
|
||||
<el-tab-pane label="我参与的" name="2" />
|
||||
<el-tab-pane label="下属负责的" name="3" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" fixed="left" label="联系人姓名" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="手机" prop="mobile" width="120" />
|
||||
<el-table-column align="center" label="电话" prop="telephone" width="130" />
|
||||
<el-table-column align="center" label="邮箱" prop="email" width="180" />
|
||||
<el-table-column align="center" label="职位" prop="post" width="120" />
|
||||
<el-table-column align="center" label="地址" prop="detailAddress" width="120" />
|
||||
<el-table-column align="center" label="关键决策人" prop="master" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="直属上级" prop="parentName" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.parentId)">
|
||||
{{ scope.row.parentName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="性别" prop="sex">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="备注" prop="remark" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
|
||||
<el-table-column align="center" fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['crm:contact:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:contact:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ContactForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import ContactForm from './ContactForm.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { TabsPaneContext } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CrmContact' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: '1', // 默认和 activeName 相等
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
email: undefined,
|
||||
customerId: undefined,
|
||||
name: undefined,
|
||||
wechat: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const activeName = ref('1') // 列表 tab
|
||||
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ContactApi.getContactPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName!.toString()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ContactApi.deleteContact(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ContactApi.exportContact(queryParams)
|
||||
download.excel(data, '联系人.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
customerList.value = await CustomerApi.getCustomerSimpleList()
|
||||
})
|
||||
</script>
|
|
@ -1,369 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="合同编号" prop="no">
|
||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="合同名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入合同名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select
|
||||
v-model="formData.ownerUserId"
|
||||
:disabled="formType !== 'create'"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="客户名称" prop="customerId">
|
||||
<el-select
|
||||
v-model="formData.customerId"
|
||||
placeholder="请选择客户"
|
||||
class="w-1/1"
|
||||
@change="handleCustomerChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="商机名称" prop="businessId">
|
||||
<el-select
|
||||
@change="handleBusinessChange"
|
||||
:disabled="!formData.customerId"
|
||||
v-model="formData.businessId"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getBusinessOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="下单日期" prop="orderDate">
|
||||
<el-date-picker
|
||||
v-model="formData.orderDate"
|
||||
placeholder="选择下单日期"
|
||||
type="date"
|
||||
value-format="x"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
v-model="formData.startTime"
|
||||
placeholder="选择开始时间"
|
||||
type="date"
|
||||
value-format="x"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
v-model="formData.endTime"
|
||||
placeholder="选择结束时间"
|
||||
type="date"
|
||||
value-format="x"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="公司签约人" prop="signUserId">
|
||||
<el-select v-model="formData.signUserId" class="w-1/1">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="客户签约人" prop="signContactId">
|
||||
<el-select
|
||||
v-model="formData.signContactId"
|
||||
:disabled="!formData.customerId"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getContactOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 子表的表单 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||
<el-tab-pane label="产品清单" name="product">
|
||||
<ContractProductForm
|
||||
ref="productFormRef"
|
||||
:products="formData.products"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="产品总金额" prop="totalProductPrice">
|
||||
<el-input
|
||||
disabled
|
||||
v-model="formData.totalProductPrice"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="整单折扣(%)" prop="discountPercent">
|
||||
<el-input-number
|
||||
v-model="formData.discountPercent"
|
||||
placeholder="请输入整单折扣"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="折扣后金额" prop="totalPrice">
|
||||
<el-input
|
||||
disabled
|
||||
v-model="formData.totalPrice"
|
||||
placeholder="请输入商机金额"
|
||||
:formatter="erpPriceTableColumnFormattere"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import * as BusinessApi from '@/api/crm/business'
|
||||
import { erpPriceMultiply, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import ContractProductForm from '@/views/crm/contract/components/ContractProductForm.vue'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
name: undefined,
|
||||
customerId: undefined,
|
||||
businessId: undefined,
|
||||
orderDate: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
signUserId: undefined,
|
||||
signContactId: undefined,
|
||||
ownerUserId: undefined,
|
||||
discountPercent: 0,
|
||||
totalProductPrice: undefined,
|
||||
remark: undefined,
|
||||
products: []
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '合同名称不能为空', trigger: 'blur' }],
|
||||
customerId: [{ required: true, message: '客户不能为空', trigger: 'blur' }],
|
||||
orderDate: [{ required: true, message: '下单日期不能为空', trigger: 'blur' }],
|
||||
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const customerList = ref([]) // 客户列表的数据
|
||||
const businessList = ref<BusinessApi.BusinessVO[]>([])
|
||||
const contactList = ref<ContactApi.ContactVO[]>([])
|
||||
|
||||
/** 子表的表单 */
|
||||
const subTabsName = ref('product')
|
||||
const productFormRef = ref()
|
||||
|
||||
/** 计算 discountPrice、totalPrice 价格 */
|
||||
watch(
|
||||
() => formData.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
const totalProductPrice = val.products.reduce((prev, curr) => prev + curr.totalPrice, 0)
|
||||
const discountPrice =
|
||||
val.discountPercent != null
|
||||
? erpPriceMultiply(totalProductPrice, val.discountPercent / 100.0)
|
||||
: 0
|
||||
const totalPrice = totalProductPrice - discountPrice
|
||||
// 赋值
|
||||
formData.value.totalProductPrice = totalProductPrice
|
||||
formData.value.totalPrice = totalPrice
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ContractApi.getContract(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得客户列表
|
||||
customerList.value = await CustomerApi.getCustomerSimpleList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 默认新建时选中自己
|
||||
if (formType.value === 'create') {
|
||||
formData.value.ownerUserId = useUserStore().getUser.id
|
||||
}
|
||||
// 获取联系人
|
||||
contactList.value = await ContactApi.getSimpleContactList()
|
||||
// 获得商机列表
|
||||
businessList.value = await BusinessApi.getSimpleBusinessList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
productFormRef.value.validate()
|
||||
try {
|
||||
const data = unref(formData.value) as unknown as ContractApi.ContractVO
|
||||
if (formType.value === 'create') {
|
||||
await ContractApi.createContract(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ContractApi.updateContract(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
name: undefined,
|
||||
customerId: undefined,
|
||||
businessId: undefined,
|
||||
orderDate: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
signUserId: undefined,
|
||||
signContactId: undefined,
|
||||
ownerUserId: undefined,
|
||||
discountPercent: 0,
|
||||
totalProductPrice: undefined,
|
||||
remark: undefined,
|
||||
products: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 处理切换客户 */
|
||||
const handleCustomerChange = () => {
|
||||
formData.value.businessId = undefined
|
||||
formData.value.signContactId = undefined
|
||||
formData.value.products = []
|
||||
}
|
||||
|
||||
/** 处理商机变化 */
|
||||
const handleBusinessChange = async (businessId: number) => {
|
||||
const business = await BusinessApi.getBusiness(businessId)
|
||||
business.products.forEach((item) => {
|
||||
item.contractPrice = item.businessPrice
|
||||
})
|
||||
formData.value.products = business.products
|
||||
}
|
||||
|
||||
/** 动态获取客户联系人 */
|
||||
const getContactOptions = computed(() =>
|
||||
contactList.value.filter((item) => item.customerId == formData.value.customerId)
|
||||
)
|
||||
/** 动态获取商机 */
|
||||
const getBusinessOptions = computed(() =>
|
||||
businessList.value.filter((item) => item.customerId == formData.value.customerId)
|
||||
)
|
||||
</script>
|
|
@ -1,136 +0,0 @@
|
|||
<template>
|
||||
<!-- 操作栏 -->
|
||||
<el-row justify="end">
|
||||
<el-button @click="openForm">
|
||||
<Icon class="mr-5px" icon="clarity:contract-line" />
|
||||
创建合同
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="合同名称" fixed="left" align="center" prop="name">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合同编号" align="center" prop="no" />
|
||||
<el-table-column label="客户名称" align="center" prop="customerName" />
|
||||
<el-table-column
|
||||
label="合同金额(元)"
|
||||
align="center"
|
||||
prop="totalPrice"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="开始时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="状态" prop="auditStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加 -->
|
||||
<ContractForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import ContractForm from './../ContractForm.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'CrmContractList' })
|
||||
const props = defineProps<{
|
||||
bizType: number // 业务类型
|
||||
bizId: number // 业务编号
|
||||
}>()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
customerId: undefined as unknown // 允许 undefined + number
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 置空参数
|
||||
queryParams.customerId = undefined
|
||||
// 执行查询
|
||||
let data = { list: [], total: 0 }
|
||||
switch (props.bizType) {
|
||||
case BizTypeEnum.CRM_CUSTOMER:
|
||||
queryParams.customerId = props.bizId
|
||||
data = await ContractApi.getContractPageByCustomer(queryParams)
|
||||
break
|
||||
case BizTypeEnum.CRM_BUSINESS:
|
||||
queryParams.businessId = props.bizId
|
||||
data = await ContractApi.getContractPageByBusiness(queryParams)
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open('create')
|
||||
}
|
||||
|
||||
/** 打开合同详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContractDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
|
||||
watch(
|
||||
() => [props.bizId, props.bizType],
|
||||
() => {
|
||||
handleQuery()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
</script>
|
|
@ -1,183 +0,0 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
v-loading="formLoading"
|
||||
label-width="0px"
|
||||
:inline-message="true"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<el-table :data="formData" class="-mt-10px">
|
||||
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||
<el-table-column label="产品名称" min-width="180">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
|
||||
<el-select
|
||||
v-model="row.productId"
|
||||
clearable
|
||||
filterable
|
||||
@change="onChangeProduct($event, row)"
|
||||
placeholder="请选择产品"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="条码" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-form-item class="mb-0px!">
|
||||
<el-input disabled v-model="row.productNo" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格(元)" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-form-item class="mb-0px!">
|
||||
<el-input disabled v-model="row.productPrice" :formatter="erpPriceInputFormatter" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价(元)" fixed="right" min-width="140">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.contractPrice`" class="mb-0px!">
|
||||
<el-input-number
|
||||
v-model="row.contractPrice"
|
||||
controls-position="right"
|
||||
:min="0.001"
|
||||
:precision="2"
|
||||
class="!w-100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" prop="count" fixed="right" min-width="120">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
|
||||
<el-input-number
|
||||
v-model="row.count"
|
||||
controls-position="right"
|
||||
:min="0.001"
|
||||
:precision="3"
|
||||
class="!w-100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合计" prop="totalPrice" fixed="right" min-width="140">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!">
|
||||
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="60">
|
||||
<template #default="{ $index }">
|
||||
<el-button @click="handleDelete($index)" link>—</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<el-row justify="center" class="mt-3" v-if="!disabled">
|
||||
<el-button @click="handleAdd" round>+ 添加产品</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ProductApi from '@/api/crm/product'
|
||||
import { erpPriceInputFormatter, erpPriceMultiply } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
const props = defineProps<{
|
||||
products: undefined
|
||||
disabled: false
|
||||
}>()
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
|
||||
contractPrice: [{ required: true, message: '合同价格不能为空', trigger: 'blur' }],
|
||||
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref([]) // 表单 Ref
|
||||
const productList = ref<ProductApi.ProductVO[]>([]) // 产品列表
|
||||
|
||||
/** 初始化设置产品项 */
|
||||
watch(
|
||||
() => props.products,
|
||||
async (val) => {
|
||||
formData.value = val
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 监听合同产品变化,计算合同产品总价 */
|
||||
watch(
|
||||
() => formData.value,
|
||||
(val) => {
|
||||
if (!val || val.length === 0) {
|
||||
return
|
||||
}
|
||||
// 循环处理
|
||||
val.forEach((item) => {
|
||||
if (item.contractPrice != null && item.count != null) {
|
||||
item.totalPrice = erpPriceMultiply(item.contractPrice, item.count)
|
||||
} else {
|
||||
item.totalPrice = undefined
|
||||
}
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
const row = {
|
||||
id: undefined,
|
||||
productId: undefined,
|
||||
productUnit: undefined, // 产品单位
|
||||
productNo: undefined, // 产品条码
|
||||
productPrice: undefined, // 产品价格
|
||||
contractPrice: undefined,
|
||||
count: 1
|
||||
}
|
||||
formData.value.push(row)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (index: number) => {
|
||||
formData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 处理产品变更 */
|
||||
const onChangeProduct = (productId, row) => {
|
||||
const product = productList.value.find((item) => item.id === productId)
|
||||
if (product) {
|
||||
row.productUnit = product.unit
|
||||
row.productNo = product.no
|
||||
row.productPrice = product.price
|
||||
row.contractPrice = product.price
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = () => {
|
||||
return formRef.value.validate()
|
||||
}
|
||||
defineExpose({ validate })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
productList.value = await ProductApi.getProductSimpleList()
|
||||
})
|
||||
</script>
|
|
@ -1,103 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【合同】合同管理、合同提醒" url="https://doc.iocoder.cn/crm/contract/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="160px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-card shadow="never">
|
||||
<!-- 操作 -->
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle title="合同配置设置" />
|
||||
<el-button type="primary" @click="onSubmit" v-hasPermi="['crm:contract-config:update']">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<el-form-item label="提前提醒设置" prop="notifyEnabled">
|
||||
<el-radio-group
|
||||
v-model="formData.notifyEnabled"
|
||||
@change="changeNotifyEnable"
|
||||
class="ml-4"
|
||||
>
|
||||
<el-radio :label="false" size="large">不提醒</el-radio>
|
||||
<el-radio :label="true" size="large">提醒</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="formData.notifyEnabled">
|
||||
<el-form-item>
|
||||
提前 <el-input-number class="mx-2" v-model="formData.notifyDays" /> 天提醒
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ContractConfigApi from '@/api/crm/contract/config'
|
||||
import { CardTitle } from '@/components/Card'
|
||||
|
||||
defineOptions({ name: 'CrmContractConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const formLoading = ref(false)
|
||||
const formData = ref({
|
||||
notifyEnabled: false,
|
||||
notifyDays: undefined
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 获取配置 */
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
formLoading.value = true
|
||||
const data = await ContractConfigApi.getContractConfig()
|
||||
if (data === null) {
|
||||
return
|
||||
}
|
||||
formData.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交配置 */
|
||||
const onSubmit = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as ContractConfigApi.ContractConfigVO
|
||||
await ContractConfigApi.saveContractConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
await getConfig()
|
||||
formLoading.value = false
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 更改提前提醒设置 */
|
||||
const changeNotifyEnable = () => {
|
||||
if (!formData.value.notifyEnabled) {
|
||||
formData.value.notifyDays = undefined
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getConfig()
|
||||
})
|
||||
</script>
|
|
@ -1,45 +0,0 @@
|
|||
<!-- 合同详情头部组件-->
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<el-col>
|
||||
<el-row>
|
||||
<span class="text-xl font-bold">{{ contract.name }}</span>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 右上:按钮 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-descriptions :column="5" direction="vertical">
|
||||
<el-descriptions-item label="客户名称">
|
||||
{{ contract.customerName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合同金额(元)">
|
||||
{{ erpPriceInputFormatter(contract.totalPrice) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下单时间">
|
||||
{{ formatDate(contract.orderDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回款金额(元)">
|
||||
{{ erpPriceInputFormatter(contract.totalReceivablePrice) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="负责人">
|
||||
{{ contract.ownerUserName }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { erpPriceInputFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'ContractDetailsHeader' })
|
||||
defineProps<{ contract: ContractApi.ContractVO }>()
|
||||
</script>
|
|
@ -1,76 +0,0 @@
|
|||
<!-- 合同详情组件 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="contractInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">基本信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="合同编号">{{ contract.no }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同名称">{{ contract.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ contract.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商机名称">{{ contract.businessName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同金额(元)">
|
||||
{{ erpPriceInputFormatter(contract.totalPrice) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下单时间">
|
||||
{{ formatDate(contract.orderDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合同开始时间">
|
||||
{{ formatDate(contract.startTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合同结束时间">
|
||||
{{ formatDate(contract.endTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户签约人">
|
||||
{{ contract.signContactName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司签约人">
|
||||
{{ contract.signUserName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">
|
||||
{{ contract.remark }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合同状态">
|
||||
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="contract.auditStatus" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="systemInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">系统信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="负责人">{{ contract.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进时间">
|
||||
{{ formatDate(contract.contactLastTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ contract.creatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(contract.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(contract.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { erpPriceInputFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'ContractDetailsInfo' })
|
||||
defineProps<{
|
||||
contract: ContractApi.ContractVO
|
||||
}>()
|
||||
|
||||
// 展示的折叠面板
|
||||
const activeNames = ref(['contractInfo', 'systemInfo'])
|
||||
</script>
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-table :data="contract.products" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="产品名称"
|
||||
fixed="left"
|
||||
prop="productName"
|
||||
min-width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.productName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品条码" align="center" prop="productNo" min-width="120" />
|
||||
<el-table-column align="center" label="产品单位" prop="productUnit" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="产品价格(元)"
|
||||
align="center"
|
||||
prop="productPrice"
|
||||
min-width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="合同价格(元)"
|
||||
align="center"
|
||||
prop="contractPrice"
|
||||
min-width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="数量"
|
||||
prop="count"
|
||||
min-width="100px"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="合计金额(元)"
|
||||
align="center"
|
||||
prop="totalPrice"
|
||||
min-width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-table>
|
||||
<el-row class="mt-10px" justify="end">
|
||||
<el-col :span="3"> 整单折扣:{{ erpPriceInputFormatter(contract.discountPercent) }}% </el-col>
|
||||
<el-col :span="4">
|
||||
产品总金额:{{ erpPriceInputFormatter(contract.totalProductPrice) }} 元
|
||||
</el-col>
|
||||
</el-row>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
const { contract } = defineProps<{
|
||||
contract: ContractApi.ContractVO
|
||||
}>()
|
||||
</script>
|
|
@ -1,139 +0,0 @@
|
|||
<!-- 合同详情页面组件-->
|
||||
<template>
|
||||
<ContractDetailsHeader v-loading="loading" :contract="contract">
|
||||
<el-button v-if="permissionListRef?.validateWrite" @click="openForm('update', contract.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transferContract">
|
||||
转移
|
||||
</el-button>
|
||||
</ContractDetailsHeader>
|
||||
<el-col>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="跟进记录">
|
||||
<FollowUpList :biz-id="contract.id" :biz-type="BizTypeEnum.CRM_CONTRACT" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="基本信息">
|
||||
<ContractDetailsInfo :contract="contract" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="产品">
|
||||
<ContractProductList :contract="contract" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="回款">
|
||||
<ReceivablePlanList
|
||||
:contract-id="contract.id!"
|
||||
:customer-id="contract.customerId"
|
||||
@create-receivable="createReceivable"
|
||||
/>
|
||||
<ReceivableList
|
||||
ref="receivableListRef"
|
||||
:contract-id="contract.id!"
|
||||
:customer-id="contract.customerId"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员">
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="contract.id!"
|
||||
:biz-type="BizTypeEnum.CRM_CONTRACT"
|
||||
:show-action="false"
|
||||
@quit-team="close"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志">
|
||||
<OperateLogV2 :log-list="logList" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ContractForm ref="formRef" @success="getContractData" />
|
||||
<CrmTransferForm ref="transferFormRef" :biz-type="BizTypeEnum.CRM_CONTRACT" @success="close" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import ContractDetailsInfo from './ContractDetailsInfo.vue'
|
||||
import ContractDetailsHeader from './ContractDetailsHeader.vue'
|
||||
import ContractProductList from './ContractProductList.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import ContractForm from '@/views/crm/contract/ContractForm.vue'
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue'
|
||||
import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue'
|
||||
|
||||
defineOptions({ name: 'CrmContractDetail' })
|
||||
const props = defineProps<{ id?: number }>()
|
||||
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const contractId = ref(0) // 编号
|
||||
const loading = ref(true) // 加载中
|
||||
const contract = ref<ContractApi.ContractVO>({} as ContractApi.ContractVO) // 详情
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
|
||||
|
||||
/** 编辑 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 获取详情 */
|
||||
const getContractData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
contract.value = await ContractApi.getContract(contractId.value)
|
||||
await getOperateLog(contractId.value)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (contractId: number) => {
|
||||
if (!contractId) {
|
||||
return
|
||||
}
|
||||
const data = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_CONTRACT,
|
||||
bizId: contractId
|
||||
})
|
||||
logList.value = data.list
|
||||
}
|
||||
|
||||
/** 从回款计划创建回款 */
|
||||
const receivableListRef = ref<InstanceType<typeof ReceivableList>>() // 回款列表 Ref
|
||||
const createReceivable = (planData: any) => {
|
||||
receivableListRef.value?.createReceivable(planData)
|
||||
}
|
||||
|
||||
/** 转移 */
|
||||
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 合同转移表单 ref
|
||||
const transferContract = () => {
|
||||
transferFormRef.value?.open(contract.value.id)
|
||||
}
|
||||
|
||||
/** 关闭 */
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
const id = props.id || route.params.id
|
||||
if (!id) {
|
||||
message.warning('参数错误,合同不能为空!')
|
||||
close()
|
||||
return
|
||||
}
|
||||
contractId.value = id as unknown as number
|
||||
await getContractData()
|
||||
})
|
||||
</script>
|
|
@ -1,398 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【合同】合同管理、合同提醒" url="https://doc.iocoder.cn/crm/contract/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="合同编号" prop="no">
|
||||
<el-input
|
||||
v-model="queryParams.no"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入合同编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入合同名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-form-item label="客户" prop="customerId">
|
||||
<el-select
|
||||
v-model="queryParams.customerId"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
lable-key="name"
|
||||
placeholder="请选择客户"
|
||||
value-key="id"
|
||||
@keyup.enter="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['crm:contract:create']" type="primary" @click="openForm('create')">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:contract:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="我负责的" name="1" />
|
||||
<el-tab-pane label="我参与的" name="2" />
|
||||
<el-tab-pane label="下属负责的" name="3" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" fixed="left" label="合同编号" prop="no" width="180" />
|
||||
<el-table-column align="center" fixed="left" label="合同名称" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户名称" prop="customerName" width="120">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openCustomerDetail(scope.row.customerId)"
|
||||
>
|
||||
{{ scope.row.customerName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="商机名称" prop="businessName" width="130">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openBusinessDetail(scope.row.businessId)"
|
||||
>
|
||||
{{ scope.row.businessName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同金额(元)"
|
||||
prop="totalPrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="下单时间"
|
||||
prop="orderDate"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同开始时间"
|
||||
prop="startTime"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="合同结束时间"
|
||||
prop="endTime"
|
||||
width="120"
|
||||
:formatter="dateFormatter2"
|
||||
/>
|
||||
<el-table-column align="center" label="客户签约人" prop="contactName" width="130">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="primary"
|
||||
@click="openContactDetail(scope.row.signContactId)"
|
||||
>
|
||||
{{ scope.row.signContactName }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="已回款金额(元)"
|
||||
prop="totalReceivablePrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="未回款金额(元)"
|
||||
prop="totalReceivablePrice"
|
||||
width="140"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.totalReceivablePrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
|
||||
<el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="250">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.auditStatus === 0"
|
||||
v-hasPermi="['crm:contract:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.auditStatus === 0"
|
||||
v-hasPermi="['crm:contract:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleSubmit(scope.row)"
|
||||
>
|
||||
提交审核
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
link
|
||||
v-hasPermi="['crm:contract:update']"
|
||||
type="primary"
|
||||
@click="handleProcessDetail(scope.row)"
|
||||
>
|
||||
查看审批
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:contract:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openDetail(scope.row.id)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:contract:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ContractForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import ContractForm from './ContractForm.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { TabsPaneContext } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CrmContract' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams: any = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: '1', // 默认和 activeName 相等
|
||||
name: null,
|
||||
customerId: null,
|
||||
orderDate: [],
|
||||
no: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const activeName = ref('1') // 列表 tab
|
||||
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
||||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName!.toString()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ContractApi.getContractPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ContractApi.deleteContract(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ContractApi.exportContract(queryParams)
|
||||
download.excel(data, '合同.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交审核 **/
|
||||
const handleSubmit = async (row: ContractApi.ContractVO) => {
|
||||
await message.confirm(`您确定提交【${row.name}】审核吗?`)
|
||||
await ContractApi.submitContract(row.id)
|
||||
message.success('提交审核成功!')
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 查看审批 */
|
||||
const handleProcessDetail = (row: ContractApi.ContractVO) => {
|
||||
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
|
||||
}
|
||||
|
||||
/** 打开合同详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContractDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const openCustomerDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开联系人详情 */
|
||||
const openContactDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 打开商机详情 */
|
||||
const openBusinessDetail = (id: number) => {
|
||||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
customerList.value = await CustomerApi.getCustomerSimpleList()
|
||||
})
|
||||
</script>
|
|
@ -1,259 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入客户名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户来源" prop="source">
|
||||
<el-select v-model="formData.source" placeholder="请选择客户来源" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select
|
||||
v-model="formData.ownerUserId"
|
||||
:disabled="formType !== 'create'"
|
||||
class="w-1/1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="电话" prop="telephone">
|
||||
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信" prop="wechat">
|
||||
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="QQ" prop="qq">
|
||||
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户行业" prop="industryId">
|
||||
<el-select v-model="formData.industryId" placeholder="请选择客户行业" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户级别" prop="level">
|
||||
<el-select v-model="formData.level" placeholder="请选择客户级别" class="w-1/1">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地址" prop="areaId">
|
||||
<el-cascader
|
||||
v-model="formData.areaId"
|
||||
:options="areaList"
|
||||
:props="defaultProps"
|
||||
class="w-1/1"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择城市"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="详细地址" prop="detailAddress">
|
||||
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="下次联系时间" prop="contactNextTime">
|
||||
<el-date-picker
|
||||
v-model="formData.contactNextTime"
|
||||
placeholder="选择下次联系时间"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
import { defaultProps } from '@/utils/tree'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const areaList = ref([]) // 地区列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
contactNextTime: undefined,
|
||||
ownerUserId: 0,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
qq: undefined,
|
||||
wechat: undefined,
|
||||
email: undefined,
|
||||
areaId: undefined,
|
||||
detailAddress: undefined,
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '客户名称不能为空', trigger: 'blur' }],
|
||||
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await CustomerApi.getCustomer(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得地区列表
|
||||
areaList.value = await AreaApi.getAreaTree()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 默认新建时选中自己
|
||||
if (formType.value === 'create') {
|
||||
formData.value.ownerUserId = useUserStore().getUser.id
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as CustomerApi.CustomerVO
|
||||
if (formType.value === 'create') {
|
||||
await CustomerApi.createCustomer(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CustomerApi.updateCustomer(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
contactNextTime: undefined,
|
||||
ownerUserId: 0,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
qq: undefined,
|
||||
wechat: undefined,
|
||||
email: undefined,
|
||||
areaId: undefined,
|
||||
detailAddress: undefined,
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,158 +0,0 @@
|
|||
<!-- 客户导入窗口 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="客户导入" width="400">
|
||||
<div class="flex items-center my-10px">
|
||||
<span class="mr-10px">负责人</span>
|
||||
<el-select v-model="ownerUserId" class="!w-240px" clearable>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:disabled="formLoading"
|
||||
:limit="1"
|
||||
:on-exceed="handleExceed"
|
||||
accept=".xlsx, .xls"
|
||||
action="none"
|
||||
drag
|
||||
>
|
||||
<Icon icon="ep:upload" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<div class="el-upload__tip">
|
||||
<el-checkbox v-model="updateSupport" />
|
||||
是否更新已经存在的客户数据(“客户名称”重复)
|
||||
</div>
|
||||
<span>仅允许导入 xls、xlsx 格式文件。</span>
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline"
|
||||
type="primary"
|
||||
@click="importTemplate"
|
||||
>
|
||||
下载模板
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import download from '@/utils/download'
|
||||
import type { UploadUserFile } from 'element-plus'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'SystemUserImportForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const uploadRef = ref()
|
||||
const fileList = ref<UploadUserFile[]>([]) // 文件列表
|
||||
const updateSupport = ref(false) // 是否更新已经存在的客户数据
|
||||
const ownerUserId = ref<undefined | number>() // 负责人编号
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
await resetForm()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
ownerUserId.value = useUserStore().getUser.id
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('updateSupport', String(updateSupport.value))
|
||||
formData.append('file', fileList.value[0].raw as Blob)
|
||||
formData.append('ownerUserId', String(ownerUserId.value))
|
||||
const res = await CustomerApi.handleImport(formData)
|
||||
submitFormSuccess(res)
|
||||
} catch {
|
||||
submitFormError()
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emits = defineEmits(['success'])
|
||||
const submitFormSuccess = (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
// 拼接提示语
|
||||
const data = response.data
|
||||
let text = '上传成功数量:' + data.createCustomerNames.length + ';'
|
||||
for (let customerName of data.createCustomerNames) {
|
||||
text += '< ' + customerName + ' >'
|
||||
}
|
||||
text += '更新成功数量:' + data.updateCustomerNames.length + ';'
|
||||
for (const customerName of data.updateCustomerNames) {
|
||||
text += '< ' + customerName + ' >'
|
||||
}
|
||||
text += '更新失败数量:' + Object.keys(data.failureCustomerNames).length + ';'
|
||||
for (const customerName in data.failureCustomerNames) {
|
||||
text += '< ' + customerName + ': ' + data.failureCustomerNames[customerName] + ' >'
|
||||
}
|
||||
message.alert(text)
|
||||
formLoading.value = false
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emits('success')
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = async () => {
|
||||
// 重置上传状态和文件
|
||||
fileList.value = []
|
||||
updateSupport.value = false
|
||||
ownerUserId.value = undefined
|
||||
await nextTick()
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
|
||||
/** 下载模板操作 */
|
||||
const importTemplate = async () => {
|
||||
const res = await CustomerApi.importCustomerTemplate()
|
||||
download.excel(res, '客户导入模版.xls')
|
||||
}
|
||||
</script>
|
|
@ -1,43 +0,0 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<!-- 左上:客户基本信息 -->
|
||||
<el-col>
|
||||
<el-row>
|
||||
<span class="text-xl font-bold">{{ customer.name }}</span>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 右上:按钮 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentWrap class="mt-10px">
|
||||
<el-descriptions :column="5" direction="vertical">
|
||||
<el-descriptions-item label="客户级别">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="成交状态">
|
||||
{{ customer.dealStatus ? '已成交' : '未成交' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="负责人">{{ customer.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(customer.createTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerDetailsHeader' })
|
||||
defineProps<{
|
||||
customer: CustomerApi.CustomerVO // 客户信息
|
||||
loading: boolean // 加载中
|
||||
}>()
|
||||
</script>
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<el-collapse v-model="activeNames" class="">
|
||||
<el-collapse-item name="basicInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">基本信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="客户名称">
|
||||
{{ customer.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户来源">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="customer.source" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ customer.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电话">{{ customer.telephone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ customer.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">
|
||||
{{ customer.areaName }} {{ customer.detailAddress }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="QQ">{{ customer.qq }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信">{{ customer.wechat }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户行业">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="customer.industryId" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户级别">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下次联系时间">
|
||||
{{ formatDate(customer.contactNextTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ customer.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="systemInfo">
|
||||
<template #title>
|
||||
<span class="text-base font-bold">系统信息</span>
|
||||
</template>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="负责人">{{ customer.ownerUserName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进记录">
|
||||
{{ customer.contactLastContent }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后跟进时间">
|
||||
{{ formatDate(customer.contactLastTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label=""> </el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ customer.creatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(customer.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(customer.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerDetailsInfo' })
|
||||
const { customer } = defineProps<{
|
||||
customer: CustomerApi.CustomerVO // 客户明细
|
||||
}>()
|
||||
|
||||
const activeNames = ref(['basicInfo', 'systemInfo']) // 展示的折叠面板
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
|
@ -1,222 +0,0 @@
|
|||
<template>
|
||||
<CustomerDetailsHeader :customer="customer" :loading="loading">
|
||||
<el-button
|
||||
v-if="permissionListRef?.validateWrite"
|
||||
v-hasPermi="['crm:customer:update']"
|
||||
type="primary"
|
||||
@click="openForm"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
|
||||
转移
|
||||
</el-button>
|
||||
<el-button v-if="permissionListRef?.validateWrite" @click="handleUpdateDealStatus">
|
||||
更改成交状态
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="customer.lockStatus && permissionListRef?.validateOwnerUser"
|
||||
@click="handleUnlock"
|
||||
>
|
||||
解锁
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!customer.lockStatus && permissionListRef?.validateOwnerUser"
|
||||
@click="handleLock"
|
||||
>
|
||||
锁定
|
||||
</el-button>
|
||||
<el-button v-if="!customer.ownerUserId" type="primary" @click="handleReceive"> 领取</el-button>
|
||||
<el-button v-if="!customer.ownerUserId" type="primary" @click="handleDistributeForm">
|
||||
分配
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="customer.ownerUserId && permissionListRef?.validateOwnerUser"
|
||||
@click="handlePutPool"
|
||||
>
|
||||
放入公海
|
||||
</el-button>
|
||||
</CustomerDetailsHeader>
|
||||
<el-col>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="跟进记录">
|
||||
<FollowUpList :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="基本信息">
|
||||
<CustomerDetailsInfo :customer="customer" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="联系人" lazy>
|
||||
<ContactList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员">
|
||||
<PermissionList
|
||||
ref="permissionListRef"
|
||||
:biz-id="customer.id!"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:show-action="!permissionListRef?.isPool || false"
|
||||
@quit-team="close"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="商机" lazy>
|
||||
<BusinessList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="合同" lazy>
|
||||
<ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="回款" lazy>
|
||||
<ReceivablePlanList :customer-id="customer.id!" @create-receivable="createReceivable" />
|
||||
<ReceivableList ref="receivableListRef" :customer-id="customer.id!" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志">
|
||||
<OperateLogV2 :log-list="logList" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CustomerForm ref="formRef" @success="getCustomer" />
|
||||
<CustomerDistributeForm ref="distributeForm" @success="getCustomer" />
|
||||
<CrmTransferForm ref="transferFormRef" :biz-type="BizTypeEnum.CRM_CUSTOMER" @success="close" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import CustomerForm from '@/views/crm/customer/CustomerForm.vue'
|
||||
import CustomerDetailsInfo from './CustomerDetailsInfo.vue' // 客户明细 - 详细信息
|
||||
import CustomerDetailsHeader from './CustomerDetailsHeader.vue' // 客户明细 - 头部
|
||||
import ContactList from '@/views/crm/contact/components/ContactList.vue' // 联系人列表
|
||||
import ContractList from '@/views/crm/contract/components/ContractList.vue' // 合同列表
|
||||
import BusinessList from '@/views/crm/business/components/BusinessList.vue' // 商机列表
|
||||
import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue' // 回款列表
|
||||
import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue' // 回款计划列表
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import type { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import CustomerDistributeForm from '@/views/crm/customer/pool/CustomerDistributeForm.vue'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerDetail' })
|
||||
|
||||
const customerId = ref(0) // 客户编号
|
||||
const loading = ref(true) // 加载中
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
|
||||
|
||||
/** 获取详情 */
|
||||
const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) // 客户详情
|
||||
const getCustomer = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
customer.value = await CustomerApi.getCustomer(customerId.value)
|
||||
await getOperateLog()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑客户 */
|
||||
const formRef = ref<InstanceType<typeof CustomerForm>>() // 客户表单 Ref
|
||||
const openForm = () => {
|
||||
formRef.value?.open('update', customerId.value)
|
||||
}
|
||||
|
||||
/** 更新成交状态操作 */
|
||||
const handleUpdateDealStatus = async () => {
|
||||
const dealStatus = !customer.value.dealStatus
|
||||
try {
|
||||
// 更新状态的二次确认
|
||||
await message.confirm(`确定更新成交状态为【${dealStatus ? '已成交' : '未成交'}】吗?`)
|
||||
// 发起更新
|
||||
await CustomerApi.updateCustomerDealStatus(customerId.value, dealStatus)
|
||||
message.success(`更新成交状态成功`)
|
||||
// 刷新数据
|
||||
await getCustomer()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 客户转移 */
|
||||
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 客户转移表单 ref
|
||||
const transfer = () => {
|
||||
transferFormRef.value?.open(customerId.value)
|
||||
}
|
||||
|
||||
/** 锁定客户 */
|
||||
const handleLock = async () => {
|
||||
await message.confirm(`确定锁定客户【${customer.value.name}】 吗?`)
|
||||
await CustomerApi.lockCustomer(unref(customerId.value), true)
|
||||
message.success(`锁定客户【${customer.value.name}】成功`)
|
||||
await getCustomer()
|
||||
}
|
||||
|
||||
/** 解锁客户 */
|
||||
const handleUnlock = async () => {
|
||||
await message.confirm(`确定解锁客户【${customer.value.name}】 吗?`)
|
||||
await CustomerApi.lockCustomer(unref(customerId.value), false)
|
||||
message.success(`解锁客户【${customer.value.name}】成功`)
|
||||
await getCustomer()
|
||||
}
|
||||
|
||||
/** 领取客户 */
|
||||
const handleReceive = async () => {
|
||||
await message.confirm(`确定领取客户【${customer.value.name}】 吗?`)
|
||||
await CustomerApi.receiveCustomer([unref(customerId.value)])
|
||||
message.success(`领取客户【${customer.value.name}】成功`)
|
||||
await getCustomer()
|
||||
}
|
||||
|
||||
/** 分配客户 */
|
||||
const distributeForm = ref<InstanceType<typeof CustomerDistributeForm>>() // 分配客户表单 Ref
|
||||
const handleDistributeForm = async () => {
|
||||
distributeForm.value?.open(customerId.value)
|
||||
}
|
||||
|
||||
/** 客户放入公海 */
|
||||
const handlePutPool = async () => {
|
||||
await message.confirm(`确定将客户【${customer.value.name}】放入公海吗?`)
|
||||
await CustomerApi.putCustomerPool(unref(customerId.value))
|
||||
message.success(`客户【${customer.value.name}】放入公海成功`)
|
||||
// 加载
|
||||
close()
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async () => {
|
||||
if (!customerId.value) {
|
||||
return
|
||||
}
|
||||
const data = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_CUSTOMER,
|
||||
bizId: customerId.value
|
||||
})
|
||||
logList.value = data.list
|
||||
}
|
||||
|
||||
/** 从回款计划创建回款 */
|
||||
const receivableListRef = ref<InstanceType<typeof ReceivableList>>() // 回款列表 Ref
|
||||
const createReceivable = (planData: any) => {
|
||||
receivableListRef.value?.createReceivable(planData)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
push({ name: 'CrmCustomer' })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const { params } = useRoute()
|
||||
onMounted(() => {
|
||||
if (!params.id) {
|
||||
message.warning('参数错误,客户不能为空!')
|
||||
close()
|
||||
return
|
||||
}
|
||||
customerId.value = params.id as unknown as number
|
||||
getCustomer()
|
||||
})
|
||||
</script>
|
|
@ -1,343 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="客户名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入客户名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="queryParams.mobile"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入手机"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属行业" prop="industryId">
|
||||
<el-select
|
||||
v-model="queryParams.industryId"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择所属行业"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户级别" prop="level">
|
||||
<el-select
|
||||
v-model="queryParams.level"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择客户级别"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户来源" prop="source">
|
||||
<el-select
|
||||
v-model="queryParams.source"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择客户来源"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['crm:customer:create']" type="primary" @click="openForm('create')">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['crm:customer:import']" plain type="warning" @click="handleImport">
|
||||
<Icon icon="ep:upload" />
|
||||
导入
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:customer:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="我负责的" name="1" />
|
||||
<el-tab-pane label="我参与的" name="2" />
|
||||
<el-tab-pane label="下属负责的" name="3" />
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" fixed="left" label="客户名称" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="手机" prop="mobile" width="120" />
|
||||
<el-table-column align="center" label="电话" prop="telephone" width="130" />
|
||||
<el-table-column align="center" label="邮箱" prop="email" width="180" />
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column align="center" label="锁定状态" prop="lockStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column align="center" label="地址" prop="detailAddress" width="180" />
|
||||
<el-table-column align="center" label="距离进入公海天数" prop="poolDay" width="140">
|
||||
<template #default="scope"> {{ scope.row.poolDay }} 天</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
<el-table-column align="center" fixed="right" label="操作" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['crm:customer:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:customer:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CustomerForm ref="formRef" @success="getList" />
|
||||
<CustomerImportForm ref="importFormRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import CustomerForm from './CustomerForm.vue'
|
||||
import CustomerImportForm from './CustomerImportForm.vue'
|
||||
import { TabsPaneContext } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CrmCustomer' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sceneType: '1', // 默认和 activeName 相等
|
||||
name: '',
|
||||
mobile: '',
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
pool: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const activeName = ref('1') // 列表 tab
|
||||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName as string
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerApi.getCustomerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const { currentRoute, push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await CustomerApi.deleteCustomer(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const importFormRef = ref<InstanceType<typeof CustomerImportForm>>()
|
||||
const handleImport = () => {
|
||||
importFormRef.value?.open()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await CustomerApi.exportCustomer(queryParams)
|
||||
download.excel(data, '客户.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听路由变化更新列表 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
getList()
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,150 +0,0 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="200px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="规则适用人群" prop="userIds">
|
||||
<el-select multiple filterable v-model="formData.userIds">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="规则适用部门" prop="deptIds">
|
||||
<el-tree-select
|
||||
v-model="formData.deptIds"
|
||||
:data="deptTree"
|
||||
:props="defaultProps"
|
||||
multiple
|
||||
filterable
|
||||
check-strictly
|
||||
node-key="id"
|
||||
placeholder="请选择规则适用部门"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="
|
||||
formData.type === LimitConfType.CUSTOMER_QUANTITY_LIMIT
|
||||
? '拥有客户数上限'
|
||||
: '锁定客户数上限'
|
||||
"
|
||||
prop="maxCount"
|
||||
>
|
||||
<el-input-number v-model="formData.maxCount" placeholder="请输入数量上限" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="成交客户是否占用拥有客户数"
|
||||
v-if="formData.type === LimitConfType.CUSTOMER_QUANTITY_LIMIT"
|
||||
prop="dealCountEnabled"
|
||||
>
|
||||
<el-switch v-model="formData.dealCountEnabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as CustomerLimitConfigApi from '@/api/crm/customer/limitConfig'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { LimitConfType } from '@/api/crm/customer/limitConfig'
|
||||
import { aw } from '../../../../../dist-prod/assets/index-9eac537b'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
type: LimitConfType.CUSTOMER_LOCK_LIMIT, // 给个默认值,避免 IDE 报错
|
||||
userIds: undefined,
|
||||
deptIds: undefined,
|
||||
maxCount: undefined,
|
||||
dealCountEnabled: false
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
|
||||
maxCount: [{ required: true, message: '数量上限不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const deptTree = ref() // 部门树形结构
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, limitConfType: LimitConfType, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await CustomerLimitConfigApi.getCustomerLimitConfig(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
formData.value.type = limitConfType
|
||||
}
|
||||
// 获得部门树
|
||||
deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
// 获得用户
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as CustomerLimitConfigApi.CustomerLimitConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await CustomerLimitConfigApi.createCustomerLimitConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CustomerLimitConfigApi.updateCustomerLimitConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
type: LimitConfType.CUSTOMER_LOCK_LIMIT,
|
||||
userIds: undefined,
|
||||
deptIds: undefined,
|
||||
maxCount: undefined,
|
||||
dealCountEnabled: false
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,150 +0,0 @@
|
|||
<template>
|
||||
<el-button plain @click="handleQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 刷新 </el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['crm:customer-limit-config:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
class="mt-4"
|
||||
>
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column
|
||||
label="规则适用人群"
|
||||
align="center"
|
||||
:formatter="(row) => row.users?.map((user: any) => user.nickname).join(',')"
|
||||
/>
|
||||
<el-table-column
|
||||
label="规则适用部门"
|
||||
align="center"
|
||||
:formatter="(row) => row.depts?.map((dept: any) => dept.name).join(',')"
|
||||
/>
|
||||
<el-table-column
|
||||
:label="
|
||||
confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT ? '拥有客户数上限' : '锁定客户数上限'
|
||||
"
|
||||
align="center"
|
||||
prop="maxCount"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT"
|
||||
label="成交客户是否占用拥有客户数"
|
||||
align="center"
|
||||
prop="dealCountEnabled"
|
||||
min-width="100"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealCountEnabled" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" min-width="110" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['crm:customer-limit-config:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['crm:customer-limit-config:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CustomerLimitConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as CustomerLimitConfigApi from '@/api/crm/customer/limitConfig'
|
||||
import CustomerLimitConfigForm from './CustomerLimitConfigForm.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { LimitConfType } from '@/api/crm/customer/limitConfig'
|
||||
|
||||
defineOptions({ name: 'CustomerLimitConfigList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const { confType } = defineProps<{ confType: LimitConfType }>()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: confType
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerLimitConfigApi.getCustomerLimitConfigPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, confType, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await CustomerLimitConfigApi.deleteCustomerLimitConfig(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,22 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="拥有客户数限制">
|
||||
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="锁定客户数限制">
|
||||
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import CustomerLimitConfigList from './CustomerLimitConfigList.vue'
|
||||
import { LimitConfType } from '@/api/crm/customer/limitConfig'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerLimitConfig' })
|
||||
</script>
|
|
@ -1,85 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="分配客户">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select v-model="formData.ownerUserId" class="w-1/1">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { distributeCustomer } from '@/api/crm/customer'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
ownerUserId: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await CustomerApi.distributeCustomer([formData.value.id], formData.value.ownerUserId)
|
||||
message.success('分配客户成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
ownerUserId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,270 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="客户名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入客户名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input
|
||||
v-model="queryParams.mobile"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入手机"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属行业" prop="industryId">
|
||||
<el-select
|
||||
v-model="queryParams.industryId"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择所属行业"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户级别" prop="level">
|
||||
<el-select
|
||||
v-model="queryParams.level"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择客户级别"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户来源" prop="source">
|
||||
<el-select
|
||||
v-model="queryParams.source"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择客户来源"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery()">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['crm:customer:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="客户名称" fixed="left" prop="name" width="160">
|
||||
<template #default="scope">
|
||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="下次联系时间"
|
||||
prop="contactNextTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="最后跟进时间"
|
||||
prop="contactLastTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="updateTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerPool' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
mobile: '',
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
sceneType: undefined,
|
||||
pool: true
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerApi.getCustomerPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
queryParams.value = {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
mobile: '',
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
sceneType: undefined,
|
||||
pool: true
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const { currentRoute, push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmCustomerDetail', params: { id } })
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await CustomerApi.exportCustomer(queryParams.value)
|
||||
download.excel(data, '客户公海.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听路由变化更新列表 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
getList()
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,136 +0,0 @@
|
|||
<template>
|
||||
<!-- <doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" /> -->
|
||||
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="160px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-card shadow="never">
|
||||
<!-- 操作 -->
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle title="客户公海规则设置" />
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="onSubmit"
|
||||
v-hasPermi="['crm:customer-pool-config:update']"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<el-form-item label="客户公海规则设置" prop="enabled">
|
||||
<el-radio-group v-model="formData.enabled" @change="changeEnable" class="ml-4">
|
||||
<el-radio :label="false" size="large">不启用</el-radio>
|
||||
<el-radio :label="true" size="large">启用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="formData.enabled">
|
||||
<el-form-item>
|
||||
<el-input-number class="mr-2" v-model="formData.contactExpireDays" />
|
||||
天不跟进或
|
||||
<el-input-number class="mx-2" v-model="formData.dealExpireDays" />
|
||||
天未成交
|
||||
</el-form-item>
|
||||
<el-form-item label="提前提醒设置" prop="notifyEnabled">
|
||||
<el-radio-group
|
||||
v-model="formData.notifyEnabled"
|
||||
@change="changeNotifyEnable"
|
||||
class="ml-4"
|
||||
>
|
||||
<el-radio :label="false" size="large">不提醒</el-radio>
|
||||
<el-radio :label="true" size="large">提醒</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="formData.notifyEnabled">
|
||||
<el-form-item>
|
||||
提前 <el-input-number class="mx-2" v-model="formData.notifyDays" /> 天提醒
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as CustomerPoolConfigApi from '@/api/crm/customer/poolConfig'
|
||||
import { CardTitle } from '@/components/Card'
|
||||
|
||||
defineOptions({ name: 'CrmCustomerPoolConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const formLoading = ref(false)
|
||||
const formData = ref({
|
||||
enabled: false,
|
||||
contactExpireDays: undefined,
|
||||
dealExpireDays: undefined,
|
||||
notifyEnabled: false,
|
||||
notifyDays: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
enabled: [{ required: true, message: '是否启用客户公海不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 获取配置 */
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
formLoading.value = true
|
||||
const data = await CustomerPoolConfigApi.getCustomerPoolConfig()
|
||||
if (data === null) {
|
||||
return
|
||||
}
|
||||
formData.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交配置 */
|
||||
const onSubmit = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as CustomerPoolConfigApi.CustomerPoolConfigVO
|
||||
await CustomerPoolConfigApi.saveCustomerPoolConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
await getConfig()
|
||||
formLoading.value = false
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 更改客户公海规则设置 */
|
||||
const changeEnable = () => {
|
||||
if (!formData.value.enabled) {
|
||||
formData.value.contactExpireDays = undefined
|
||||
formData.value.dealExpireDays = undefined
|
||||
formData.value.notifyEnabled = false
|
||||
formData.value.notifyDays = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 更改提前提醒设置 */
|
||||
const changeNotifyEnable = () => {
|
||||
if (!formData.value.notifyEnabled) {
|
||||
formData.value.notifyDays = undefined
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getConfig()
|
||||
})
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user