【招生平台用户端】 新增# 子账号管理:对接新增账号接口

main
kaeery 2025-02-26 21:48:36 +08:00
parent 00a6296aa9
commit 935d73277b
13 changed files with 262 additions and 278 deletions

View File

@ -0,0 +1,25 @@
import request from '@/utils/request'
// 组织列表
export function organzationLists(params?: any) {
return request.get({ url: '/organization/list', params })
}
// 添加组织
export function organzationtAdd(params: any) {
return request.post({ url: '/organization/add', params })
}
// 编辑组织
export function organzationEdit(params: any) {
return request.post({ url: '/organization/edit', params })
}
// 删除组织
export function organzationDelete(params: any) {
return request.post({ url: '/organization/del', params })
}
// 组织详情
export function organzationDetail(params: any) {
return request.get({ url: '/organization/detail', params })
}

View File

@ -0,0 +1,30 @@
import request from '@/utils/request'
// 岗位列表
export function postLists(params?: any) {
return request.get({ url: '/system/post/list', params })
}
// 岗位列表
export function postAll(params?: any) {
return request.get({ url: '/system/post/all', params })
}
// 添加岗位
export function postAdd(params: any) {
return request.post({ url: '/system/post/add', params })
}
// 编辑岗位
export function postEdit(params: any) {
return request.post({ url: '/system/post/edit', params })
}
// 删除岗位
export function postDelete(params: any) {
return request.post({ url: '/system/post/del', params })
}
// 岗位详情
export function postDetail(params: any) {
return request.get({ url: '/system/post/detail', params })
}

View File

@ -0,0 +1,18 @@
import request from '@/utils/request'
// 新增子账号
export function subAccountAdd(params: Record<string, any>) {
return request.post({ url: '/user/sonAdd', params })
}
// 编辑子账号
export function subAccountEdit(params: Record<string, any>) {
return request.post({ url: '/user/sonEdit', params })
}
// 子账号列表
export function subAccountList(params: Record<string, any>) {
return request.get({ url: '/user/selectOrganizationAllUser', params })
}
// 账号名额
export function subAccountNumber() {
return request.get({ url: '/user/validUserCount' })
}

View File

@ -0,0 +1,8 @@
export enum StatusEnum {
Normal = 1,
Stop = 0
}
export enum DataFlowEnum {
DEFAULT = 0,
SPECIFIC = 1
}

View File

@ -30,7 +30,7 @@ export function isExternal(path: string) {
export function validateContact(rule: any, value: any, callback: any) {
if (value) {
if (!mobileReg.test(value)) {
callback(new Error('请输入正确的手机号'))
callback(new Error('请输入正确的联系电话'))
} else {
callback()
}

View File

@ -1,15 +1,8 @@
<template>
<div class="edit-popup">
<popup
ref="popupRef"
:title="popupTitle"
:async="true"
width="550px"
@confirm="handleSubmit"
@close="handleClose"
>
<popup ref="popupRef" :title="popupTitle" :async="true" width="450px" @confirm="handleSubmit" @close="handleClose">
<el-form ref="formRef" :model="formData" label-width="84px" :rules="formRules">
<el-form-item label="上级部门" prop="pid" v-if="formData.pid !== 0">
<el-form-item label="上级组织" prop="pid" v-if="formData.pid !== 0">
<el-tree-select
class="flex-1"
v-model="formData.pid"
@ -22,36 +15,14 @@
}"
check-strictly
:default-expand-all="true"
placeholder="请选择上级部门"
placeholder="请选择上级组织"
/>
</el-form-item>
<el-form-item label="部门名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入部门名称"
clearable
:maxlength="100"
/>
<el-form-item label="组织名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入组织名称" clearable :maxlength="100" />
</el-form-item>
<el-form-item label="负责人" prop="duty">
<el-input
v-model="formData.duty"
placeholder="请输入负责人姓名"
clearable
:maxlength="30"
/>
</el-form-item>
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入联系电话" clearable />
</el-form-item>
<el-form-item label="排序" prop="sort">
<div>
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
<div class="form-tips">默认为0 数值越大越排前</div>
</div>
</el-form-item>
<el-form-item label="部门状态" prop="isStop">
<el-switch v-model="formData.isStop" :active-value="0" :inactive-value="1" />
<el-form-item label="组织状态" prop="status">
<el-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
</popup>
@ -59,10 +30,11 @@
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { deptLists, deptEdit, deptAdd, deptDetail } from '@/api/org/department'
import Popup from '@/components/popup/index.vue'
import { useDictOptions } from '@/hooks/useDictOptions'
import feedback from '@/utils/feedback'
import { organzationDetail, organzationEdit, organzationLists, organzationtAdd } from '@/api/account_center/organization'
const emit = defineEmits(['success', 'close'])
const formRef = shallowRef<FormInstance>()
const popupRef = shallowRef<InstanceType<typeof Popup>>()
@ -74,50 +46,20 @@ const formData = reactive({
id: '',
pid: '' as string | number,
name: '',
duty: '',
mobile: '',
sort: 0,
isStop: 0
status: 1
})
const checkMobile = (rule: any, value: any, callback: any) => {
if (!value) {
return callback()
} else {
const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
console.log(reg.test(value))
if (reg.test(value)) {
callback()
} else {
return callback(new Error('请输入正确的手机号'))
}
}
}
const formRules = {
pid: [
{
required: true,
message: '请选择上级部门',
message: '请选择上级组织',
trigger: ['change']
}
],
name: [
{
required: true,
message: '请输入部门名称',
trigger: ['blur']
}
],
duty: [
{
required: true,
message: '请输入负责人',
trigger: ['blur']
}
],
mobile: [
{
required: true,
message: '请输入联系电话',
message: '请输入组织名称',
trigger: ['blur']
}
]
@ -127,13 +69,13 @@ const { optionsData } = useDictOptions<{
dept: any[]
}>({
dept: {
api: deptLists
api: organzationLists
}
})
const handleSubmit = async () => {
await formRef.value?.validate()
mode.value == 'edit' ? await deptEdit(formData) : await deptAdd(formData)
mode.value == 'edit' ? await organzationEdit(formData) : await organzationtAdd(formData)
popupRef.value?.close()
feedback.msgSuccess('操作成功')
emit('success')
@ -154,7 +96,7 @@ const setFormData = (data: Record<any, any>) => {
}
const getDetail = async (row: Record<string, any>) => {
const data = await deptDetail({
const data = await organzationDetail({
id: row.id
})
setFormData(data)

View File

@ -2,16 +2,11 @@
<div class="dept-lists">
<el-card class="!border-none" shadow="never">
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
<el-form-item label="部门名称" prop="name">
<el-input
class="w-[280px]"
v-model="queryParams.name"
clearable
@keyup.enter="getLists"
/>
<el-form-item label="组织名称" prop="name">
<el-input class="w-[280px]" v-model="queryParams.name" clearable placeholder="请输入" @keyup.enter="getLists" />
</el-form-item>
<el-form-item label="部门状态" prop="isStop">
<el-select class="w-[280px]" v-model="queryParams.isStop">
<el-form-item label="组织状态" prop="status">
<el-select class="w-[280px]" v-model="queryParams.status">
<el-option label="全部" value />
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
@ -31,7 +26,6 @@
</template>
新增
</el-button>
<el-button @click="handleExpand"> / </el-button>
</div>
<el-table
ref="tableRef"
@ -42,46 +36,20 @@
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column
label="部门名称"
prop="name"
min-width="150"
show-overflow-tooltip
/>
<el-table-column label="部门状态" prop="isStop" min-width="100">
<el-table-column label="组织名称" prop="name" min-width="150" show-overflow-tooltip />
<el-table-column label="组织状态" prop="status" min-width="100">
<template #default="{ row }">
<el-tag class="ml-2" :type="row.isStop ? 'danger' : ''">
{{ row.isStop ? '停用' : '正常' }}
<el-tag class="ml-2" :type="tagType(row.status)">
{{ parseStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" min-width="100" />
<el-table-column label="更新时间" prop="updateTime" min-width="180" />
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['system:dept:add']"
type="primary"
link
@click="handleAdd(row.id)"
>
新增
</el-button>
<el-button
v-perms="['system:dept:edit']"
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-if="row.pid !== 0"
v-perms="['system:dept:del']"
type="danger"
link
@click="handleDelete(row.id)"
>
<el-button v-perms="['system:dept:add']" type="primary" link @click="handleAdd(row.id)"></el-button>
<el-button v-perms="['system:dept:edit']" type="primary" link @click="handleEdit(row)"></el-button>
<el-button v-if="row.pid !== 0" v-perms="['system:dept:del']" type="danger" link @click="handleDelete(row.id)">
删除
</el-button>
</template>
@ -94,8 +62,10 @@
<script lang="ts" setup name="department">
import type { ElTable, FormInstance } from 'element-plus'
import EditPopup from './edit.vue'
import { deptDelete, deptLists } from '@/api/org/department'
import feedback from '@/utils/feedback'
import { organzationDelete, organzationLists } from '@/api/account_center/organization'
import { StatusEnum } from '@/enums'
const tableRef = shallowRef<InstanceType<typeof ElTable>>()
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
const formRef = shallowRef<FormInstance>()
@ -103,13 +73,19 @@ let isExpand = false
const loading = ref(false)
const lists = ref<any[]>([])
const queryParams = reactive({
isStop: '',
status: '',
name: ''
})
const showEdit = ref(false)
const statusMap: Record<StatusEnum, string> = {
[StatusEnum.Normal]: '启用',
[StatusEnum.Stop]: '停用'
}
const parseStatusText = computed(() => (status: StatusEnum) => statusMap[status])
const tagType = computed(() => (status: StatusEnum) => status == StatusEnum.Normal ? 'primary' : 'danger')
const getLists = async () => {
loading.value = true
lists.value = await deptLists(queryParams)
lists.value = await organzationLists(queryParams)
loading.value = false
}
@ -138,7 +114,7 @@ const handleEdit = async (data: any) => {
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除?')
await deptDelete({ id })
await organzationDelete({ id })
feedback.msgSuccess('删除成功')
getLists()
}

View File

@ -1,9 +1,9 @@
<template>
<ProDialog ref="proDialogRef" @handle-cancel="handleCancel" @handle-confirm="handleConfirm">
<el-form :model="form" label-position="right" label-width="120">
<el-form-item label="所属组织">
<el-form ref="formRef" :rules="rules" :model="form" label-position="right" label-width="120">
<el-form-item label="所属组织" prop="organizationId">
<el-tree-select
v-model="form.organization"
v-model="form.organizationId"
:data="data"
:props="defaultProps"
:render-after-expand="false"
@ -11,21 +11,21 @@
style="width: 100%"
/>
</el-form-item>
<el-form-item label="账号名称">
<el-input v-model="form.accountName" autocomplete="off" placeholder="请输入" maxlength="20" show-word-limit />
<el-form-item label="账号名称" prop="username">
<el-input v-model="form.username" autocomplete="off" placeholder="请输入" maxlength="20" show-word-limit />
</el-form-item>
<el-form-item label="联系电话">
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="form.mobile" autocomplete="off" placeholder="请输入" maxlength="11" show-word-limit />
</el-form-item>
<el-form-item label="岗位">
<el-checkbox-group v-model="form.position">
<el-checkbox v-for="option in positionOptions" :key="option.value" :label="option.value" name="type">
{{ option.label }}
<el-form-item label="岗位" prop="postIds">
<el-checkbox-group v-model="form.postIds">
<el-checkbox v-for="option in positionOptions" :key="option.id" :label="option.id" name="type">
{{ option.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<template v-if="form.position.includes(1)">
<el-form-item>
<template v-if="form.postIds.includes(5)">
<el-form-item prop="dataFlow">
<template #label>
<div class="flex items-center gap-[6px] cursor-pointer">
<span>有效数据流向</span>
@ -43,10 +43,10 @@
</el-tooltip>
</div>
</template>
<el-radio-group v-model="form.channel">
<el-radio-group v-model="form.dataFlow">
<el-radio v-for="option in channelOptions" :key="option.value" :label="option.value">{{ option.label }}</el-radio>
</el-radio-group>
<template v-if="form.channel == 'specific'">
<template v-if="form.dataFlow == 1">
<el-select class="mt-[10px]" v-model="form.teacher" placeholder="请选择指定招生老师">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
@ -54,8 +54,8 @@
</template>
</el-form-item>
</template>
<el-form-item label="账号状态">
<el-radio-group v-model="form.accountStatus">
<el-form-item label="账号状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="option in accountStatusOptions" :key="option.value" :label="option.value">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
@ -64,62 +64,37 @@
</template>
<script setup lang="ts">
import { organzationLists } from '@/api/account_center/organization'
import { postLists } from '@/api/account_center/postion'
import { subAccountAdd } from '@/api/account_center/sub_account'
import ProDialog, { type IParams } from '@/components/ProDialog/index.vue'
import { DataFlowEnum } from '@/enums'
import { validateContact } from '@/utils/validate'
import type { FormInstance, FormRules } from 'element-plus'
export interface IAccount {
organizationId: number | string
username: string
mobile: string
postId: number[]
dataFlow: number
accountStatus: number
teacher: string
}
const proDialogRef = ref<InstanceType<typeof ProDialog>>()
const emit = defineEmits(['fetchTableList'])
const data = ref([
{
id: 1,
label: '合创云公司',
disabled: true,
children: [
{
id: 11,
label: '湛江团队',
disabled: true,
children: [
{
id: 111,
label: 'A组'
},
{
id: 112,
label: 'B组'
},
{
id: 113,
label: 'C组'
}
]
},
{
id: 12,
label: '广州团队',
disabled: true,
children: [
{
id: 121,
label: 'A组'
}
]
}
]
}
])
const data = ref([])
const defaultProps = {
label: 'label', //
label: 'name', //
value: 'id', //
children: 'children', //
disabled: 'disabled'
}
const positionOptions = ref([
{ label: '电销老师', value: 1 },
{ label: '招生老师', value: 2 }
])
const positionOptions = ref()
const channelOptions = ref([
{ label: '默认组织', value: 'default' },
{ label: '组织指定', value: 'specific' }
{ label: '默认组织', value: DataFlowEnum.DEFAULT },
{ label: '组织指定', value: DataFlowEnum.SPECIFIC }
])
const accountStatusOptions = ref([
{ label: '启用', value: 1 },
@ -127,23 +102,74 @@ const accountStatusOptions = ref([
])
const channelTooltips = ['选择默认组织则数据流向到所属组织下的所有招生老师;', '选择组织指定则可选择将数据流向到所属组织下的指定招生老师;']
const form = ref({
organization: '',
accountName: '',
organizationId: '',
username: '',
mobile: '',
position: [],
channel: '',
accountStatus: '',
postIds: [],
dataFlow: 0,
status: 1,
teacher: ''
})
const fetchPostionData = async () => {
try {
const result = await postLists()
positionOptions.value = result.lists
} catch (error) {}
}
fetchPostionData()
const fetchOrganizationData = async () => {
try {
const result = await organzationLists()
setDsiabled(result)
data.value = result
} catch (error) {}
}
//
const setDsiabled = (nodes: any[]) => {
nodes.forEach(node => {
const ancestorArray = node.ancestors ? node.ancestors.split(',') : []
const level = ancestorArray.length - 1
node.disabled = level < 2
if (node.children && node.children.length > 0) {
setDsiabled(node.children)
}
})
}
const openDialog = (params: IParams) => {
fetchOrganizationData()
proDialogRef.value?.openDialog(params)
}
const handleCancel = (callback: () => void) => {
resetForm()
callback()
}
const formRef = ref<FormInstance>()
const rules = reactive({
organizationId: { required: true, message: '请选择所属组织', trigger: 'change' },
username: { required: true, message: '请输入账号名称', trigger: 'change' },
mobile: [
{ required: true, message: '请输入联系电话', trigger: 'change' },
{ validator: validateContact, trigger: 'change' }
],
postIds: { required: true, message: '请选择岗位', trigger: 'change' }
})
const handleConfirm = (callback: () => void) => {
formRef.value?.validate(async valid => {
if (!valid) return
try {
const data = {
...form.value,
postIds: form.value.postIds.join(',')
}
await subAccountAdd(data)
callback()
emit('fetchTableList')
resetForm()
} catch (error) {}
})
}
const resetForm = () => {
formRef.value?.resetFields()
}
defineExpose({
openDialog

View File

@ -63,7 +63,7 @@ const props = defineProps({
const emit = defineEmits(['setGroupLeader'])
const defaultProps = {
children: 'children',
label: 'label',
label: 'name',
class: 'custom-tree'
}
const parent = inject(ORGANIZATION_INJECTION_KEY)

View File

@ -28,7 +28,7 @@ watch(
)
const accountListRef = ref<InstanceType<typeof accountList>>()
const fetchTableList = () => {
accountListRef.value?.fetchTableList()
accountListRef.value?.fetchTableList(selectedNode.value?.id)
}
</script>
<style scoped></style>

View File

@ -1,7 +1,7 @@
<template>
<div class="flex flex-col flex-1">
<div class="h-[50px] flex items-center border-b-solid-light2 px-[20px] text-[20px] font-bold">
{{ curOrganization.label }}
{{ curOrganization.name }}
</div>
<div class="px-[20px] py-[16px] flex justify-between">
<el-space>
@ -38,7 +38,7 @@
<el-switch v-model="row.accountStatus" :active-value="1" :inactive-value="0" :before-change="() => handleStatusChange(row)" />
</template>
<template #operation="{ row }">
<el-button link type="primary">详情</el-button>
<el-button link type="primary" @click="handleEdit(row)"></el-button>
<el-button link type="primary" @click="setGroupLeader(row)">{{ btnText(row.groupLeader) }}</el-button>
<el-button link type="primary" @click="handleDelete(row)"></el-button>
</template>
@ -53,8 +53,9 @@ import feedback from '@/utils/feedback'
import { Plus, Delete } from '@element-plus/icons-vue'
import accountDialog from '../components/account-list/account-dialog.vue'
import { useDebounceFn } from '@vueuse/core'
import { subAccountList } from '@/api/account_center/sub_account'
defineProps({
const props = defineProps({
curOrganization: {
type: Object,
default: () => ({})
@ -76,50 +77,26 @@ const proTableRef = ref()
const tableData = ref<any[]>([])
const columns = reactive([
{ type: 'selection', fixed: 'left', width: 70, reserveSelection: true },
{ prop: 'accountName', label: '账号名称', width: 180 },
{ prop: 'username', label: '账号名称', width: 180 },
{ prop: 'mobile', label: '联系电话', width: 180 },
{ prop: 'organization', label: '所属组织', width: 180 },
{ prop: 'position', label: '岗位', width: 180 },
{ prop: 'accountStatus', label: '账号状态', width: 180 },
{ prop: 'organizationName', label: '所属组织', width: 180 },
{ prop: 'postName', label: '岗位', width: 180 },
{ prop: 'status', label: '账号状态', width: 180 },
{ prop: 'operation', label: '操作', fixed: 'right', width: 250 }
])
const isGroupLeader = computed(() => (groupLeader: number) => groupLeader == 1)
const btnText = computed(() => (groupLeader: number) => isGroupLeader.value(groupLeader) ? '取消设为组长' : '设置组长')
const fetchTableList = () => {
const fetchTableList = async (nodeId?: number) => {
loading.value = true
setTimeout(() => {
tableData.value = [
{
id: 1,
accountName: '张三',
mobile: '18138952909',
organization: '广州团队-A组',
position: '电销老师',
accountStatus: 1,
groupLeader: 1
},
{
id: 2,
accountName: '李四',
mobile: '18138952909',
organization: '广州团队-A组',
position: '电销老师',
accountStatus: 1,
groupLeader: 0
},
{
id: 3,
accountName: '王五',
mobile: '18138952909',
organization: '广州团队-A组',
position: '招生老师',
accountStatus: 0,
groupLeader: 0
try {
const params = {
organizationId: nodeId ?? props.curOrganization.id
}
]
const result = await subAccountList(params)
tableData.value = result ?? []
} catch (error) {}
loading.value = false
}, 500)
}
const handleStatusChange = row => {
console.log(row)
@ -151,13 +128,16 @@ const deleteCommon = async (row?: any) => {
}
proTableRef.value.tableRef.clearSelection()
}
const handleEdit = row => {
showAccountDialog(row)
}
defineExpose({
fetchTableList
})
const accountDialogRef = ref<InstanceType<typeof accountDialog>>()
const showAccountDialog = () => {
const showAccountDialog = (row?: any) => {
accountDialogRef.value?.openDialog({
title: '新建账号',
title: row?.id ? '编辑账号' : '新建账号',
width: 400,
data: {}
})

View File

@ -1,14 +1,27 @@
<template>
<div class="bg-primary-light-10 py-[10px] pl-[16px]">
当前您有
<span class="text-primary">49</span>
<span class="text-primary">{{ accoutnInfo.quota }}</span>
个可用的子账号名额已使用
<span class="text-primary">0</span>
<span class="text-primary">{{ accoutnInfo.quantity }}</span>
剩余
<span class="text-primary">49</span>
<span class="text-primary">{{ parseUnuseNumber }}</span>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { subAccountNumber } from '@/api/account_center/sub_account'
const accoutnInfo = ref()
const parseUnuseNumber = computed(() => accoutnInfo.value?.quota - accoutnInfo.value?.quantity)
const fetchSubAccountNumber = async () => {
try {
const result = await subAccountNumber()
accoutnInfo.value = result
} catch (error) {}
}
fetchSubAccountNumber()
</script>
<style scoped></style>

View File

@ -12,6 +12,7 @@ import organizationTree, { type Tree } from '../components/organization/organiza
import groupLeaderDialog from '../components/organization/groupLeader-dialog.vue'
import { ORGANIZATION_INJECTION_KEY } from '@/config/symbol'
import type { PropType } from 'vue'
import { organzationLists } from '@/api/account_center/organization'
defineProps({
curSelectedNode: {
@ -25,49 +26,14 @@ const loading = ref(false)
const setSelectedNode = (data: Tree) => {
emit('setSelectedNode', data)
}
const fetchOrganizationList = () => {
console.log('fetchOrganizationList')
const fetchOrganizationList = async () => {
loading.value = true
setTimeout(() => {
data.value = [
{
id: 1,
label: '合创云公司',
children: [
{
id: 11,
label: '湛江团队',
children: [
{
id: 111,
label: 'A组'
},
{
id: 112,
label: 'B组'
},
{
id: 113,
label: 'C组'
}
]
},
{
id: 12,
label: '广州团队',
children: [
{
id: 121,
label: 'A组'
}
]
}
]
}
]
try {
const result = await organzationLists()
data.value = result
loading.value = false
data.value.length > 0 && setSelectedNode(data.value[0])
}, 500)
} catch (error) {}
}
provide(ORGANIZATION_INJECTION_KEY, {
fetchOrganizationList,