v1.0.2 更新内容
This commit is contained in:
@@ -175,4 +175,27 @@ export class manageService {
|
||||
url: '/common/preset/cache/clear'
|
||||
})
|
||||
}
|
||||
//公海客户分配
|
||||
static crmCustomerAllocation(params: any) {
|
||||
return request.get<Api.Common.AddResponse>({
|
||||
url: '/admin/crm/customer/cede',
|
||||
params
|
||||
// showErrorMessage: false // 不显示错误消息
|
||||
})
|
||||
}
|
||||
//公海客户跟进记录
|
||||
static crmCustomerFollow(params: any) {
|
||||
return request.get<any>({
|
||||
url: '/admin/crm/customer/follow',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
//公海客户删除
|
||||
static crmCustomerDelete(params: any) {
|
||||
return request.get<Api.Common.AddResponse>({
|
||||
url: '/admin/crm/customer/delete',
|
||||
params
|
||||
// showErrorMessage: false // 不显示错误消息
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,4 +176,19 @@ export class CrmService {
|
||||
// showErrorMessage: false // 不显示错误消息
|
||||
})
|
||||
}
|
||||
//所属员工
|
||||
static getCustomerUserList(params: Api.Common.PaginatingParams) {
|
||||
return request.get<Api.Common.CommonListData>({
|
||||
url: '/common/user/team',
|
||||
params
|
||||
})
|
||||
}
|
||||
//标记离职
|
||||
static markLeave(params: any) {
|
||||
return request.get<Api.Work.AddResponse>({
|
||||
url: '/common/user/resign',
|
||||
params
|
||||
// showErrorMessage: false // 不显示错误消息
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,9 +168,10 @@ export class systemService {
|
||||
})
|
||||
}
|
||||
//菜单列表
|
||||
static getMenutreeList() {
|
||||
static getMenutreeList(params: any) {
|
||||
return request.get<any>({
|
||||
url: '/admin/resources/list/tree'
|
||||
url: '/admin/resources/list/tree',
|
||||
params
|
||||
})
|
||||
}
|
||||
static getMenuList() {
|
||||
|
||||
+9
-2
@@ -7,7 +7,7 @@ export class UserService {
|
||||
url: '/common/preset/corps'
|
||||
})
|
||||
}
|
||||
//获取系统
|
||||
//获取系统
|
||||
static getsystemsList() {
|
||||
return request.get<Api.Brand.BrandInfo>({
|
||||
url: '/common/preset/systems'
|
||||
@@ -43,7 +43,7 @@ export class UserService {
|
||||
params
|
||||
})
|
||||
}
|
||||
//飞书凭证
|
||||
//飞书凭证
|
||||
static getfeishuscope() {
|
||||
return request.get<Api.Auth.Authfeishuscope>({
|
||||
url: '/common/auth/feishu/scope'
|
||||
@@ -78,4 +78,11 @@ export class UserService {
|
||||
url: '/common/preset/menus'
|
||||
})
|
||||
}
|
||||
//个人信息编辑
|
||||
static editUserInfo(params: any) {
|
||||
return request.post<Api.Work.AddResponse>({
|
||||
url: '/admin/user/edit',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
</div>
|
||||
</div> -->
|
||||
<ul class="user-menu" style="border: none">
|
||||
<li @click="goPage('/system/user-center')">
|
||||
<li @click="gouserInfo()">
|
||||
<i class="menu-icon iconfont-sys"></i>
|
||||
<span class="menu-txt">{{ $t('topBar.user.userCenter') }}</span>
|
||||
</li>
|
||||
@@ -223,7 +223,6 @@
|
||||
import { languageOptions } from '@/locales'
|
||||
const isWindows = navigator.userAgent.includes('Windows')
|
||||
const { locale } = useI18n()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
@@ -315,14 +314,22 @@
|
||||
const goPage = (path: string) => {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
const toDocs = () => {
|
||||
window.open(WEB_LINKS.DOCS)
|
||||
const { systemsid } = useUserStore()
|
||||
const gouserInfo = () => {
|
||||
if (systemsid == '1') {
|
||||
router.push('/admin/user/user_info')
|
||||
} else {
|
||||
router.push('/system/user-center')
|
||||
}
|
||||
}
|
||||
|
||||
const toGithub = () => {
|
||||
window.open(WEB_LINKS.GITHUB)
|
||||
}
|
||||
// const toDocs = () => {
|
||||
// window.open(WEB_LINKS.DOCS)
|
||||
// }
|
||||
|
||||
// const toGithub = () => {
|
||||
// window.open(WEB_LINKS.GITHUB)
|
||||
// }
|
||||
|
||||
const toHome = () => {
|
||||
router.push(home_page)
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div class="customer-page">
|
||||
<div class="customer-toolbar boxshadow">
|
||||
<div class="customer-search-group">
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxshadow">
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
@@ -15,6 +22,7 @@
|
||||
lass="customer-table"
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column prop="code" label="编号" align="center" />
|
||||
<el-table-column prop="contacts" label="客户名称" align="center" />
|
||||
<el-table-column prop="magnitude_txt" label="客户商机" align="center" />
|
||||
<el-table-column prop="channel_name" label="渠道" align="center" />
|
||||
@@ -83,7 +91,7 @@
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { manageService } from '@/api/adminApi'
|
||||
const { hasAuth } = useAuth()
|
||||
const selectValue = ref<string[]>([])
|
||||
const keyword = ref<string>('')
|
||||
const loading = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
const audetDialogVisible = ref(false)
|
||||
@@ -110,6 +118,10 @@
|
||||
getuserList()
|
||||
gettableList()
|
||||
})
|
||||
watch([keyword], () => {
|
||||
pagination.currentPage = 1
|
||||
gettableList()
|
||||
})
|
||||
const gettableList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -119,6 +131,9 @@
|
||||
size: pageSize,
|
||||
scene: 'audit'
|
||||
}
|
||||
if (keyword.value) {
|
||||
params.keyword = keyword.value
|
||||
}
|
||||
const { data, current, size, total } = await manageService.getcrmCustomerList(params)
|
||||
tableData.value = data.map((item: any) => ({
|
||||
...item
|
||||
|
||||
@@ -0,0 +1,655 @@
|
||||
<template>
|
||||
<div class="customer-page">
|
||||
<div class="customer-toolbar boxshadow">
|
||||
<div class="customer-search-group">
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxshadow">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="plzyDialogVisible = true"
|
||||
v-if="hasAuth('highseas@allocation')"
|
||||
:disabled="selectedRows.length === 0"
|
||||
>
|
||||
<i class="iconfont-sys"></i> 批量分配
|
||||
</el-button>
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
@selection-change="handleSelectionChange"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
lass="customer-table"
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column type="selection" width="55px" align="center"></el-table-column>
|
||||
<el-table-column prop="code" label="编号" align="center" />
|
||||
<el-table-column prop="contacts" label="客户名称" align="center" />
|
||||
<el-table-column prop="magnitude_txt" label="客户商机" align="center" />
|
||||
<el-table-column prop="channel_name" label="渠道" align="center" />
|
||||
<el-table-column prop="country_name" label="国家" align="center" />
|
||||
<el-table-column prop="phone" label="电话" align="center" />
|
||||
<el-table-column prop="WhatsApp" label="WhatsApp" align="center" />
|
||||
<el-table-column prop="email" label="邮箱" align="center" />
|
||||
<el-table-column prop="grade_txt" label="等级" align="center" />
|
||||
<el-table-column prop="follow" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<span :class="statusClass(scope.row.follow)">
|
||||
{{ scope.row.follow_txt }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="enter_user_name" label="录单人"></el-table-column>
|
||||
<!-- <el-table-column prop="follow_next" label="下次跟进"></el-table-column> -->
|
||||
<el-table-column prop="created_at" label="创建时间"></el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="200px" align="center">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
type="success"
|
||||
underline="never"
|
||||
style="margin-right: 8px"
|
||||
@click="onDetail(scope.row)"
|
||||
v-if="hasAuth('highseas@detail')"
|
||||
>
|
||||
<i class="iconfont-sys"> </i>详情
|
||||
</el-link>
|
||||
<el-link
|
||||
type="primary"
|
||||
underline="never"
|
||||
style="margin-right: 8px"
|
||||
@click="gotransfer(scope.row)"
|
||||
v-if="hasAuth('highseas@allocation')"
|
||||
>
|
||||
<i class="iconfont-sys"> </i>分配
|
||||
</el-link>
|
||||
<el-link
|
||||
type="warning"
|
||||
underline="never"
|
||||
style="margin-right: 8px"
|
||||
@click="gofollowdetail(scope.row)"
|
||||
v-if="hasAuth('highseas@follow_detail')"
|
||||
>
|
||||
<i class="iconfont-sys"> </i>跟进记录
|
||||
</el-link>
|
||||
<el-link
|
||||
type="danger"
|
||||
underline="never"
|
||||
style="margin-right: 8px"
|
||||
@click="onDelete(scope.row)"
|
||||
v-if="hasAuth('highseas@delete')"
|
||||
>
|
||||
<i class="iconfont-sys"></i>删除
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog v-model="detailDialogVisible" title="详情" width="800px">
|
||||
<div class="detail-info-table">
|
||||
<div class="row">
|
||||
<div>客户名称:{{ detailRow.contacts }}</div>
|
||||
<div>电话:{{ detailRow.phone }}</div>
|
||||
<div>国家:{{ detailRow.country_name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>渠道:{{ detailRow.channel_name }}</div>
|
||||
<div>邮箱:{{ detailRow.email }}</div>
|
||||
<div>WhatsApp:{{ detailRow.WhatsApp }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>发布人:{{ detailRow.enter_user_name }}</div>
|
||||
<div>录单时间:{{ detailRow.created_at }}</div>
|
||||
<div>
|
||||
等级:
|
||||
<span>
|
||||
{{ detailRow.magnitude_txt }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>
|
||||
状态:<span>{{ detailRow.grade_txt }}</span>
|
||||
</div>
|
||||
<div>
|
||||
跟进状态:<span>{{ detailRow.follow_txt }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtitle">详情内容</div>
|
||||
<div v-if="detailRow" v-html="detailRow.desc_collect" class="detils-content"></div>
|
||||
</el-dialog>
|
||||
<!-- 分配跟单对话框 -->
|
||||
<el-dialog v-model="zyDialogVisible" title="分配" width="360px">
|
||||
<el-form :model="zyForm" label-width="0">
|
||||
<el-form-item>
|
||||
<el-select
|
||||
v-model="zyForm.user_id"
|
||||
filterable
|
||||
placeholder="请选择分配人"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div style="display: flex; justify-content: space-around; margin-top: 24px">
|
||||
<el-button @click="zyDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handlezySubmit">确定</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<!-- 批量分配对话框 -->
|
||||
<el-dialog v-model="plzyDialogVisible" title="批量分配" width="360px">
|
||||
<el-form :model="plzyForm" label-width="0">
|
||||
<el-form-item>
|
||||
<el-select
|
||||
v-model="plzyForm.user_id"
|
||||
filterable
|
||||
placeholder="请选择分配人"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div style="display: flex; justify-content: space-around; margin-top: 24px">
|
||||
<el-button @click="plzyDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleplzySubmit">确定</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 跟进记录对话框 -->
|
||||
<el-dialog v-model="followRecordDialogVisible" title="跟进记录" width="800px">
|
||||
<!-- 跟进记录列表 -->
|
||||
<div class="follow-record-list">
|
||||
<ElScrollbar>
|
||||
<template v-if="followRecords.length > 0">
|
||||
<ElTimeline>
|
||||
<ElTimelineItem
|
||||
v-for="item in followRecords"
|
||||
:key="item.time"
|
||||
:placement="TIMELINE_PLACEMENT"
|
||||
:color="activecolor"
|
||||
:center="true"
|
||||
>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content-row">
|
||||
<div class="timeline-content">
|
||||
<p class="timeline-text"
|
||||
>{{ item.type_txt }}
|
||||
<span class="timeline-mom">{{ item.created_at }}</span></p
|
||||
>
|
||||
<p class="timeline-mom"
|
||||
><span v-if="item.type == 1">跟进描述</span><span v-else>拜访内容</span>
|
||||
{{ item.audio_text }}</p
|
||||
>
|
||||
</div>
|
||||
<img
|
||||
v-if="item.picture_full"
|
||||
:src="item.picture_full"
|
||||
class="timeline-img"
|
||||
alt="图片"
|
||||
@click="handleImageClick(item.picture_full)"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ElTimelineItem>
|
||||
</ElTimeline>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 使用 Element Plus 的空状态组件 -->
|
||||
<el-empty :image="emptyimg" description="暂无跟进记录" />
|
||||
</template>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-image-viewer
|
||||
v-if="imageViewerVisible"
|
||||
:url-list="imageList"
|
||||
:initial-index="imageViewerIndex"
|
||||
@close="closeImageViewer"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { manageService } from '@/api/adminApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import emptyimg from '@/assets/img/crm/empty.png'
|
||||
const TIMELINE_PLACEMENT = 'top'
|
||||
const activecolor = '#3471ff'
|
||||
const { hasAuth } = useAuth()
|
||||
const loading = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
const zyDialogVisible = ref(false)
|
||||
const plzyDialogVisible = ref(false)
|
||||
const followRecordDialogVisible = ref(false)
|
||||
const detailRow = ref<any>(null)
|
||||
const currentCustomer = ref<any>(null)
|
||||
const followRecords = ref<any[]>([])
|
||||
const keyword = ref<string>('')
|
||||
// 图片预览相关
|
||||
const imageViewerVisible = ref(false)
|
||||
const imageList = ref<string[]>([])
|
||||
const imageViewerIndex = ref(0)
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const plzyForm = reactive({
|
||||
customer_id: '',
|
||||
user_id: ''
|
||||
})
|
||||
const zyForm = reactive({
|
||||
customer_id: '',
|
||||
user_id: ''
|
||||
})
|
||||
const userOptions = ref<any>()
|
||||
const getuserList = async () => {
|
||||
// 这里调用获取用户列表的接口
|
||||
const res = await manageService.presetusers()
|
||||
userOptions.value = res || []
|
||||
// return res || []
|
||||
}
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
onMounted(() => {
|
||||
getuserList()
|
||||
gettableList()
|
||||
})
|
||||
watch([keyword], () => {
|
||||
pagination.currentPage = 1
|
||||
gettableList()
|
||||
})
|
||||
const gettableList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
const params: any = {
|
||||
current: currentPage,
|
||||
size: pageSize,
|
||||
scene: 'seas'
|
||||
}
|
||||
if (keyword.value) {
|
||||
params.keyword = keyword.value
|
||||
}
|
||||
const { data, current, size, total } = await manageService.getcrmCustomerList(params)
|
||||
tableData.value = data.map((item: any) => ({
|
||||
...item
|
||||
}))
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
gettableList()
|
||||
}
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
gettableList()
|
||||
}
|
||||
function statusClass(status: any) {
|
||||
if (status === 0) return 'status-following'
|
||||
if (status === 1) return 'status-dealed'
|
||||
if (status === 2) return 'status-lost'
|
||||
return ''
|
||||
}
|
||||
function processImages(html: string) {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(html, 'text/html')
|
||||
doc.querySelectorAll('img').forEach((img) => {
|
||||
img.style.maxWidth = '100%'
|
||||
img.style.height = 'auto'
|
||||
})
|
||||
return doc.body.innerHTML
|
||||
}
|
||||
function onDetail(row: any) {
|
||||
row.desc_collect = processImages(row.desc_collect) // 处理图片
|
||||
detailRow.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
const gotransfer = (row: any) => {
|
||||
zyForm.customer_id = row.id
|
||||
zyDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 跟进记录
|
||||
const gofollowdetail = async (row: any) => {
|
||||
currentCustomer.value = row
|
||||
followRecordDialogVisible.value = true
|
||||
await manageService.crmCustomerFollow({ id: row.id }).then((res) => {
|
||||
followRecords.value = res
|
||||
})
|
||||
}
|
||||
|
||||
// 图片预览相关函数
|
||||
const handleImageClick = (url: string) => {
|
||||
imageList.value = [url]
|
||||
imageViewerIndex.value = 0
|
||||
imageViewerVisible.value = true
|
||||
}
|
||||
|
||||
const closeImageViewer = () => {
|
||||
imageViewerVisible.value = false
|
||||
imageList.value = []
|
||||
imageViewerIndex.value = 0
|
||||
}
|
||||
// 分配
|
||||
const handlezySubmit = async () => {
|
||||
const res = await manageService.crmCustomerAllocation({
|
||||
customer_id: zyForm.customer_id,
|
||||
user_id: zyForm.user_id
|
||||
})
|
||||
ElMessage.success(res?.message || '分配成功!')
|
||||
zyDialogVisible.value = false
|
||||
gettableList()
|
||||
}
|
||||
// 选中的行数据
|
||||
const selectedRows = ref<any[]>([])
|
||||
// 处理表格行选择变化
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
// 批量分配
|
||||
const handleplzySubmit = async () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.warning('请至少选择一条记录')
|
||||
return
|
||||
}
|
||||
plzyForm.customer_id = selectedRows.value.map((item) => item.id).join(',')
|
||||
const res = await manageService.crmCustomerAllocation(plzyForm)
|
||||
ElMessage.success(res?.message || '批量分配成功!')
|
||||
plzyDialogVisible.value = false
|
||||
gettableList()
|
||||
}
|
||||
//删除
|
||||
const onDelete = async (row: any) => {
|
||||
try {
|
||||
const res = await manageService.crmCustomerDelete({ customer_id: row.id })
|
||||
ElMessage.success(res?.message || '删除成功!')
|
||||
gettableList()
|
||||
} catch (error) {
|
||||
ElMessage.error('删除失败,请稍后再试')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-badge) {
|
||||
--el-badge-size: 16px;
|
||||
}
|
||||
.itemtag {
|
||||
margin-top: -16px;
|
||||
margin-left: -13px;
|
||||
}
|
||||
:deep(.el-table__header th) {
|
||||
background: #f7f9fa !important;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
:deep(.el-table__empty-block) {
|
||||
height: 450px !important;
|
||||
}
|
||||
:deep(.el-table__body tr.custom-table-row td) {
|
||||
border-bottom: 1px solid #f7f9fa !important;
|
||||
border-right: 1px solid #f7f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr td:last-child) {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:last-child td) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th) {
|
||||
border-bottom: 1px solid #f7f9fa !important;
|
||||
border-right: 1px solid #f7f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th:last-child) {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
border: 1px solid #f7f9fa !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.customer-page {
|
||||
background: var(--art-main-bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.customer-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 20px 20px;
|
||||
background: var(--art-main-bg-color);
|
||||
}
|
||||
|
||||
.customer-search-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.customer-tags {
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
width: 120px;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-tags {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.customer-tabs {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.status-following {
|
||||
color: #00a43f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-dealed {
|
||||
color: #3471ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-lost {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.customer-table {
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.filter-more-btn {
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.tag-items {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
transition: background 0.2s;
|
||||
min-width: 100px;
|
||||
display: inline-block;
|
||||
margin-right: 30px;
|
||||
}
|
||||
.tag-item {
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.tag-itemall {
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.tag-active {
|
||||
background: #3471ff;
|
||||
color: #fff;
|
||||
}
|
||||
.tag-active :deep(.el-badge__content) {
|
||||
color: #3471ff !important; /* 文字颜色 */
|
||||
background-color: #fff !important; /* 背景色 */
|
||||
}
|
||||
.detail-info-table {
|
||||
padding-left: 15px;
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 6px;
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dtitle {
|
||||
line-height: 40px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.detils-content {
|
||||
padding-left: 15px;
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
.fenpei {
|
||||
color: #3471ff !important;
|
||||
}
|
||||
|
||||
.detil {
|
||||
color: #00a43f !important;
|
||||
}
|
||||
|
||||
.edit {
|
||||
color: #008477 !important;
|
||||
}
|
||||
|
||||
.delete {
|
||||
color: #f00 !important;
|
||||
}
|
||||
|
||||
.timeline-img {
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.follow-record-list {
|
||||
padding: 15px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
|
||||
.timeline-item {
|
||||
.timeline-content-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
|
||||
.timeline-content {
|
||||
flex: 1;
|
||||
|
||||
.timeline-text {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.timeline-mom {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-mom {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,17 +3,22 @@
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="customer-toolbar boxshadow">
|
||||
<div class="customer-search-group">
|
||||
<el-select
|
||||
v-model="selectValue"
|
||||
style="min-width: 120px"
|
||||
filterable
|
||||
multiple
|
||||
collapse-tags
|
||||
placeholder="请选择跟单人"
|
||||
>
|
||||
<el-option v-for="v in userOptions" :key="v.id" :label="v.name" :value="v.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跟单人" label-width="70px">
|
||||
<el-select
|
||||
v-model="selectValue"
|
||||
style="min-width: 120px"
|
||||
filterable
|
||||
multiple
|
||||
collapse-tags
|
||||
placeholder="请选择跟单人"
|
||||
>
|
||||
<el-option v-for="v in userOptions" :key="v.id" :label="v.name" :value="v.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-button type="primary" @click="exportfn" v-if="hasAuth('customer@export')">
|
||||
<i class="iconfont-sys"></i> 导出
|
||||
@@ -103,6 +108,7 @@
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column type="selection" width="55px" align="center"></el-table-column>
|
||||
<el-table-column prop="code" label="编号" align="center" />
|
||||
<el-table-column prop="contacts" label="客户名称" align="center" />
|
||||
<el-table-column prop="magnitude_txt" label="客户商机" align="center" />
|
||||
<el-table-column prop="channel_name" label="渠道" align="center" />
|
||||
@@ -142,6 +148,15 @@
|
||||
>
|
||||
<i class="iconfont-sys"> </i>转移
|
||||
</el-link>
|
||||
<el-link
|
||||
type="warning"
|
||||
underline="never"
|
||||
style="margin-right: 8px"
|
||||
@click="gofollowdetail(scope.row)"
|
||||
v-if="hasAuth('customer@follow_detail')"
|
||||
>
|
||||
<i class="iconfont-sys"> </i>跟进记录
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
@@ -232,6 +247,64 @@
|
||||
</div>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 跟进记录对话框 -->
|
||||
<el-dialog v-model="followRecordDialogVisible" title="跟进记录" width="800px">
|
||||
<!-- 跟进记录列表 -->
|
||||
<div class="follow-record-list">
|
||||
<ElScrollbar>
|
||||
<template v-if="followRecords.length > 0">
|
||||
<ElTimeline>
|
||||
<ElTimelineItem
|
||||
v-for="item in followRecords"
|
||||
:key="item.time"
|
||||
:placement="TIMELINE_PLACEMENT"
|
||||
:color="activecolor"
|
||||
:center="true"
|
||||
>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content-row">
|
||||
<div class="timeline-content">
|
||||
<p class="timeline-text"
|
||||
>{{ item.type_txt }}
|
||||
<span class="timeline-mom">{{ item.created_at }}</span></p
|
||||
>
|
||||
<p class="timeline-mom"
|
||||
><span v-if="item.type == 1">跟进描述</span><span v-else>拜访内容</span>
|
||||
{{ item.audio_text }}</p
|
||||
>
|
||||
</div>
|
||||
<img
|
||||
v-if="item.picture_full"
|
||||
:src="item.picture_full"
|
||||
class="timeline-img"
|
||||
alt="图片"
|
||||
@click="handleImageClick(item.picture_full)"
|
||||
style="cursor: pointer;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ElTimelineItem>
|
||||
</ElTimeline>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 使用 Element Plus 的空状态组件 -->
|
||||
<el-empty :image="emptyimg" description="暂无跟进记录" />
|
||||
<!-- <el-empty /> -->
|
||||
<!-- 或者自定义图片 -->
|
||||
<!-- <img src="你的空图片地址.png" alt="暂无数据" class="empty-img" /> -->
|
||||
</template>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-image-viewer
|
||||
v-if="imageViewerVisible"
|
||||
:url-list="imageList"
|
||||
:initial-index="imageViewerIndex"
|
||||
@close="closeImageViewer"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -239,6 +312,9 @@
|
||||
import { CrmService } from '@/api/crmApi'
|
||||
import { manageService } from '@/api/adminApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import emptyimg from '@/assets/img/crm/empty.png'
|
||||
const TIMELINE_PLACEMENT = 'top'
|
||||
const activecolor = '#3471ff'
|
||||
const { hasAuth } = useAuth()
|
||||
const selectValue = ref<string[]>([])
|
||||
const showMore = ref(false)
|
||||
@@ -246,7 +322,15 @@
|
||||
const detailDialogVisible = ref(false)
|
||||
const zyDialogVisible = ref(false)
|
||||
const plzyDialogVisible = ref(false)
|
||||
const followRecordDialogVisible = ref(false)
|
||||
const detailRow = ref<any>(null)
|
||||
const currentCustomer = ref<any>(null)
|
||||
const followRecords = ref<any[]>([])
|
||||
const keyword = ref<string>('')
|
||||
// 图片预览相关
|
||||
const imageViewerVisible = ref(false)
|
||||
const imageList = ref<string[]>([])
|
||||
const imageViewerIndex = ref(0)
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
@@ -294,7 +378,7 @@
|
||||
tagGrouplistfn()
|
||||
})
|
||||
// tabs 切换联动
|
||||
watch([selectValue], () => {
|
||||
watch([selectValue, keyword], () => {
|
||||
pagination.currentPage = 1
|
||||
gettableList()
|
||||
})
|
||||
@@ -309,6 +393,9 @@
|
||||
if (selectValue.value && selectValue.value.length) {
|
||||
params.user_id = selectValue.value.join(',')
|
||||
}
|
||||
if (keyword.value) {
|
||||
params.keyword = keyword.value
|
||||
}
|
||||
// 标签参数,所有选中的标签id组成数组
|
||||
const selectedTagIds = Object.values(tagActiveMap).filter(Boolean)
|
||||
if (selectedTagIds.length) {
|
||||
@@ -400,6 +487,27 @@
|
||||
zyForm.customer_id = row.id
|
||||
zyDialogVisible.value = true
|
||||
}
|
||||
// 跟进记录
|
||||
const gofollowdetail = async (row: any) => {
|
||||
currentCustomer.value = row
|
||||
followRecordDialogVisible.value = true
|
||||
await manageService.crmCustomerFollow({ id: row.id }).then((res) => {
|
||||
followRecords.value = res
|
||||
})
|
||||
}
|
||||
|
||||
// 图片预览相关函数
|
||||
const handleImageClick = (url: string) => {
|
||||
imageList.value = [url]
|
||||
imageViewerIndex.value = 0
|
||||
imageViewerVisible.value = true
|
||||
}
|
||||
|
||||
const closeImageViewer = () => {
|
||||
imageViewerVisible.value = false
|
||||
imageList.value = []
|
||||
imageViewerIndex.value = 0
|
||||
}
|
||||
// 转移
|
||||
const handlezySubmit = async () => {
|
||||
const res = await manageService.crmCustomerTransfer({
|
||||
@@ -431,6 +539,16 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.timeline-img {
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
:deep(.el-badge) {
|
||||
--el-badge-size: 16px;
|
||||
}
|
||||
@@ -616,4 +734,95 @@
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.follow-record-header {
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.customer-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.follow-record-list {
|
||||
padding: 15px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.record-type {
|
||||
font-size: 14px;
|
||||
color: #3471ff;
|
||||
font-weight: bold;
|
||||
background-color: #e6f7ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.record-user {
|
||||
font-size: 14px;
|
||||
color: #3471ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.record-content {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.record-footer {
|
||||
.next-follow {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-records {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ElRow>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton @click="showDialog('add')" v-ripple>新增</ElButton>
|
||||
<ElButton @click="showDialog('add')" v-ripple v-if="hasAuth('system@company_add')">新增</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ArtTable
|
||||
@@ -19,6 +19,7 @@
|
||||
<template #default>
|
||||
<ElTableColumn label="ID" prop="id" />
|
||||
<ElTableColumn label="公司名称" prop="name" />
|
||||
<ElTableColumn label="公司标识" prop="abbr" />
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status ? 'primary' : 'info'">
|
||||
@@ -30,8 +31,21 @@
|
||||
<ElTableColumn fixed="right" label="操作" width="200px">
|
||||
<template #default="scope">
|
||||
<ElRow>
|
||||
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
|
||||
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
|
||||
<el-button
|
||||
link
|
||||
@click="showDialog('edit', scope.row)"
|
||||
v-if="hasAuth('system@company_update')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="deleteRole(scope.row)"
|
||||
v-if="hasAuth('system@company_destroy')"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
@@ -43,10 +57,19 @@
|
||||
width="40%"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px" class="form">
|
||||
<ElFormItem label="公司名称" prop="name">
|
||||
<ElInput v-model="form.name" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="公司标识" prop="abbr">
|
||||
<ElInput v-model="form.abbr" />
|
||||
</ElFormItem>
|
||||
<!-- <ElFormItem label="飞书标识" prop="tenant_key">
|
||||
<ElInput v-model="form.tenant_key" />
|
||||
</ElFormItem> -->
|
||||
<ElFormItem label="状态">
|
||||
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
@@ -65,22 +88,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Ordertype} from '@/mock/temp/formData'
|
||||
import { systemService } from '@/api/systemApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
const { hasAuth } = useAuth()
|
||||
defineOptions({ name: 'Type ' })
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const typeList = ref<any[]>([])
|
||||
const formRef = ref<FormInstance>()
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 500)
|
||||
const rules = reactive<FormRules>({
|
||||
name: [{ required: true, message: '请输入公司名称', trigger: 'blur' }]
|
||||
name: [{ required: true, message: '请输入公司名称', trigger: 'blur' }],
|
||||
abbr: [{ required: true, message: '请输入公司简称', trigger: 'blur' }]
|
||||
// tenant_key: [{ required: true, message: '请输入飞书标识', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const form = reactive<Ordertype>({
|
||||
const form = reactive<any>({
|
||||
name: '',
|
||||
id:''
|
||||
id: '',
|
||||
abbr: '',
|
||||
status: 1
|
||||
})
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
@@ -127,18 +154,25 @@
|
||||
if (type === 'edit' && row) {
|
||||
form.name = row.name
|
||||
form.id = row.id
|
||||
form.status = row.status
|
||||
form.abbr = row.abbr
|
||||
// form.tenant_key = row.tenant_key
|
||||
} else {
|
||||
form.name = ''
|
||||
form.id = ''
|
||||
form.status = 1
|
||||
form.abbr = ''
|
||||
// form.tenant_key = ''
|
||||
}
|
||||
}
|
||||
const deleteRole = (row?: any) => {
|
||||
ElMessageBox.confirm('确定删除该分类吗?', '删除确认', {
|
||||
ElMessageBox.confirm('确定删除吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(async () => {
|
||||
const { message } = await systemService.companyDelete({
|
||||
id: row.id,
|
||||
id: row.id
|
||||
})
|
||||
ElMessage.success(message)
|
||||
getWorkList()
|
||||
@@ -148,17 +182,12 @@
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (dialogType.value === 'add') {
|
||||
const { message } = await systemService.companyAdd({
|
||||
name: form.name
|
||||
})
|
||||
const { message } = await systemService.companyAdd(form)
|
||||
ElMessage.success(message)
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
} else {
|
||||
const { message } = await systemService.companyEdit({
|
||||
name: form.name,
|
||||
id: form.id
|
||||
})
|
||||
const { message } = await systemService.companyEdit(form)
|
||||
ElMessage.success(message)
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div v-if="!isMobile">
|
||||
<ElRow>
|
||||
<!-- <ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput placeholder="名称"></ElInput>
|
||||
</ElCol> -->
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<!-- <ElButton v-ripple>搜索</ElButton> -->
|
||||
<ElButton @click="showDialog('add')" v-ripple>新增</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn label="ID" prop="id" />
|
||||
<ElTableColumn label="分类名称" prop="name" />
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status ? 'primary' : 'info'">
|
||||
{{ scope.row.status ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="created_at" />
|
||||
<ElTableColumn fixed="right" label="操作" width="200px">
|
||||
<template #default="scope">
|
||||
<ElRow>
|
||||
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
|
||||
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增' : '编辑'"
|
||||
width="40%"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
|
||||
<ElFormItem label="分类名称" prop="name">
|
||||
<ElInput v-model="form.name" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
<div v-else class="mobile-hide-tip">
|
||||
<p>该页面暂不支持移动端访问</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Ordertype} from '@/mock/temp/formData'
|
||||
import { systemService } from '@/api/systemApi'
|
||||
defineOptions({ name: 'Type ' })
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const typeList = ref<any[]>([])
|
||||
const formRef = ref<FormInstance>()
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 500)
|
||||
const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: '请输入分类名称', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const form = reactive<Ordertype>({
|
||||
name: '',
|
||||
id:''
|
||||
})
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const tableRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
getWorkList()
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getWorkList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
|
||||
const { records, current, size, total } = await systemService.getFeishuAppList({
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
})
|
||||
tableData.value = records.map((item: any, index: number) => ({
|
||||
...item
|
||||
}))
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const dialogType = ref('add')
|
||||
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.name = row.name
|
||||
form.id = row.id
|
||||
} else {
|
||||
form.name = ''
|
||||
}
|
||||
}
|
||||
const deleteRole = (row?: any) => {
|
||||
ElMessageBox.confirm('确定删除该分类吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(async () => {
|
||||
const { message } = await systemService.feishuAppDelete({
|
||||
id: row.id,
|
||||
})
|
||||
ElMessage.success(message)
|
||||
getWorkList()
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (dialogType.value === 'add') {
|
||||
const { message } = await systemService.feishuAppAdd({
|
||||
name: form.name
|
||||
})
|
||||
ElMessage.success(message)
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
} else {
|
||||
const { message } = await systemService.feishuAppEdit({
|
||||
name: form.name,
|
||||
id: form.id
|
||||
})
|
||||
ElMessage.success(message)
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__empty-block) {
|
||||
height: 100% !important;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.page-content {
|
||||
.svg-icon {
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
overflow: hidden;
|
||||
vertical-align: -8px;
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div v-if="!isMobile">
|
||||
<ElRow>
|
||||
<!-- <ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput placeholder="名称"></ElInput>
|
||||
</ElCol> -->
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<!-- <ElButton v-ripple>搜索</ElButton> -->
|
||||
<ElButton @click="showDialog('add')" v-if="hasAuth('system@feishu_app_add')" v-ripple
|
||||
>新增</ElButton
|
||||
>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn label="ID" prop="id" width="80" />
|
||||
<ElTableColumn label="公司" prop="corp_id" width="140">
|
||||
<template #default="scope">
|
||||
{{ scope.row.corpInfo.name }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="系统" prop="system_id" width="140">
|
||||
<template #default="scope">
|
||||
{{ scope.row.systemInfo.name }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="名称" prop="name" width="160" />
|
||||
<ElTableColumn label="应用ID" prop="app_id" width="180" show-overflow-tooltip />
|
||||
<ElTableColumn label="基础域" prop="base_uri" width="220" show-overflow-tooltip />
|
||||
<ElTableColumn label="授权地址" prop="r_url" width="260" show-overflow-tooltip />
|
||||
<ElTableColumn label="授权范围" prop="scopes" width="240" show-overflow-tooltip />
|
||||
<ElTableColumn label="携带参数" prop="state" width="140" show-overflow-tooltip />
|
||||
<ElTableColumn label="排序" prop="sort" width="90" />
|
||||
<ElTableColumn label="状态" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status ? 'primary' : 'info'">
|
||||
{{ scope.row.status ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="created_at" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="180px">
|
||||
<template #default="scope">
|
||||
<ElRow>
|
||||
<el-button
|
||||
link
|
||||
@click="showDialog('edit', scope.row)"
|
||||
v-if="hasAuth('system@feishu_app_update')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="deleteRole(scope.row)"
|
||||
v-if="hasAuth('system@feishu_app_destroy')"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增' : '编辑'"
|
||||
align-center
|
||||
>
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
style="padding: 5px 20px"
|
||||
label-width="100px"
|
||||
>
|
||||
<ElFormItem label="公司" prop="corp_id">
|
||||
<ElSelect v-model="form.corp_id" placeholder="请选择公司" filterable style="width: 100%">
|
||||
<ElOption v-for="item in companyList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系统" prop="system_id">
|
||||
<ElSelect v-model="form.system_id" placeholder="请选择系统" filterable style="width: 100%">
|
||||
<ElOption v-for="item in systemsList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="名称" prop="name">
|
||||
<ElInput v-model="form.name" placeholder="请输入名称" />
|
||||
</ElFormItem>
|
||||
<!-- <ElFormItem label="企业标识" prop="tenant_key">
|
||||
<ElInput v-model="form.tenant_key" placeholder="请输入企业标识" />
|
||||
</ElFormItem> -->
|
||||
<ElFormItem label="应用ID" prop="app_id">
|
||||
<ElInput v-model="form.app_id" placeholder="请输入应用ID" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="应用秘钥" prop="app_secret">
|
||||
<ElInput v-model="form.app_secret" placeholder="请输入应用秘钥" show-password />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="基础域" prop="base_uri">
|
||||
<ElInput v-model="form.base_uri" placeholder="请输入基础域" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="授权地址" prop="r_url">
|
||||
<ElInput v-model="form.r_url" placeholder="请输入授权地址" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="授权范围" prop="scopes">
|
||||
<ElInput v-model="form.scopes" type="textarea" :rows="3" placeholder="请输入授权范围" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="携带参数" prop="state">
|
||||
<ElInput v-model="form.state" placeholder="请输入携带参数" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="排序" prop="sort">
|
||||
<ElInputNumber v-model="form.sort" :min="0" placeholder="请输入排序" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态" prop="status">
|
||||
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
<div v-else class="mobile-hide-tip">
|
||||
<p>该页面暂不支持移动端访问</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { systemService } from '@/api/systemApi'
|
||||
import { UserService } from '@/api/usersApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
const { hasAuth } = useAuth()
|
||||
defineOptions({ name: 'Type ' })
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 500)
|
||||
const rules = reactive<FormRules>({
|
||||
corp_id: [{ required: true, message: '请选择公司', trigger: 'change' }],
|
||||
system_id: [{ required: true, message: '请选择系统', trigger: 'change' }],
|
||||
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
// app_id: [{ required: true, message: '请输入应用ID', trigger: 'blur' }],
|
||||
// app_secret: [{ required: true, message: '请输入应用秘钥', trigger: 'blur' }],
|
||||
// base_uri: [{ required: true, message: '请输入基础域', trigger: 'blur' }],
|
||||
// r_url: [{ required: true, message: '请输入授权地址', trigger: 'blur' }],
|
||||
// scopes: [{ required: true, message: '请输入授权范围', trigger: 'blur' }],
|
||||
// state: [{ required: true, message: '请输入携带参数', trigger: 'blur' }],
|
||||
// sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
|
||||
// status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
corp_id: '',
|
||||
system_id: '',
|
||||
name: '',
|
||||
// tenant_key: '',
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
base_uri: '',
|
||||
r_url: '',
|
||||
scopes: '',
|
||||
state: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
id: ''
|
||||
})
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const companyList = ref<any[]>([])
|
||||
const systemsList = ref<any[]>([])
|
||||
const getcompanyList = async () => {
|
||||
const response = await UserService.getCompanyList()
|
||||
const list = (response as any)?.data ?? (response as any)?.records ?? response
|
||||
companyList.value = Array.isArray(list) ? list : []
|
||||
}
|
||||
const getsystemsList = async () => {
|
||||
const response = await UserService.getsystemsList()
|
||||
const list = (response as any)?.data ?? (response as any)?.records ?? response
|
||||
systemsList.value = Array.isArray(list) ? list : []
|
||||
}
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const tableRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
getWorkList()
|
||||
getcompanyList()
|
||||
getsystemsList()
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getWorkList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
|
||||
const { records, current, size, total } = await systemService.getFeishuAppList({
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
})
|
||||
tableData.value = records.map((item: any, index: number) => ({
|
||||
...item
|
||||
}))
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const dialogType = ref('add')
|
||||
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
// 编辑模式,填充所有字段
|
||||
Object.assign(form, {
|
||||
corp_id: row.corp_id || '',
|
||||
system_id: row.system_id || '',
|
||||
name: row.name || '',
|
||||
// tenant_key: row.tenant_key || '',
|
||||
app_id: row.app_id || '',
|
||||
app_secret: row.app_secret || '',
|
||||
base_uri: row.base_uri || '',
|
||||
r_url: row.r_url || '',
|
||||
scopes: row.scopes || '',
|
||||
state: row.state || '',
|
||||
sort: row.sort || 0,
|
||||
status: row.status !== undefined ? row.status : 1,
|
||||
id: row.id || ''
|
||||
})
|
||||
} else {
|
||||
// 新增模式,重置所有字段
|
||||
Object.assign(form, {
|
||||
corp_id: '',
|
||||
system_id: '',
|
||||
name: '',
|
||||
// tenant_key: '',
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
base_uri: '',
|
||||
r_url: '',
|
||||
scopes: '',
|
||||
state: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
const deleteRole = (row?: any) => {
|
||||
ElMessageBox.confirm('确定删除吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(async () => {
|
||||
const { message } = await systemService.feishuAppDelete({
|
||||
id: row.id
|
||||
})
|
||||
ElMessage.success(message)
|
||||
getWorkList()
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
const { message } = await systemService.feishuAppAdd({
|
||||
corp_id: form.corp_id,
|
||||
system_id: form.system_id,
|
||||
name: form.name,
|
||||
app_id: form.app_id,
|
||||
app_secret: form.app_secret,
|
||||
base_uri: form.base_uri,
|
||||
r_url: form.r_url,
|
||||
scopes: form.scopes,
|
||||
state: form.state,
|
||||
sort: form.sort,
|
||||
status: form.status
|
||||
})
|
||||
ElMessage.success(message)
|
||||
} else {
|
||||
const { message } = await systemService.feishuAppEdit({
|
||||
id: form.id,
|
||||
corp_id: form.corp_id,
|
||||
system_id: form.system_id,
|
||||
name: form.name,
|
||||
app_id: form.app_id,
|
||||
app_secret: form.app_secret,
|
||||
base_uri: form.base_uri,
|
||||
r_url: form.r_url,
|
||||
scopes: form.scopes,
|
||||
state: form.state,
|
||||
sort: form.sort,
|
||||
status: form.status
|
||||
})
|
||||
ElMessage.success(message)
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__empty-block) {
|
||||
height: 100% !important;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.page-content {
|
||||
.svg-icon {
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
overflow: hidden;
|
||||
vertical-align: -8px;
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -14,8 +14,25 @@
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader :showZebra="false" v-model:columns="columnChecks" @refresh="handleRefresh">
|
||||
<template #left>
|
||||
<el-segmented
|
||||
v-model="yearVal"
|
||||
size="large"
|
||||
:options="segmentedOptions"
|
||||
@change="searchArticleByYear"
|
||||
>
|
||||
<!-- <template #default="scope">
|
||||
<div class="flex flex-col items-center gap-2 p-2">
|
||||
<div>{{ scope.item.label }}</div>
|
||||
</div>
|
||||
</template> -->
|
||||
</el-segmented>
|
||||
<!-- 按钮权限:后端控制模式,使用自定义指令 -->
|
||||
<ElButton v-auth="'system@menu_add'" @click="showModel('menu', null)" v-ripple>
|
||||
<ElButton
|
||||
v-auth="'system@menu_add'"
|
||||
@click="showModel('menu', null)"
|
||||
v-ripple
|
||||
style="margin-left: 20px"
|
||||
>
|
||||
添加菜单
|
||||
</ElButton>
|
||||
<ElButton @click="toggleExpand" v-ripple>
|
||||
@@ -224,7 +241,7 @@
|
||||
import { SearchFormItem } from '@/types'
|
||||
import { systemService } from '@/api/systemApi'
|
||||
import { UserService } from '@/api/usersApi'
|
||||
|
||||
const yearVal = ref<any>('')
|
||||
defineOptions({ name: 'Menus' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
@@ -233,6 +250,20 @@
|
||||
const menuList = ref<any[]>([])
|
||||
|
||||
const loading = ref(false)
|
||||
const systemsListtab = ref<any>([])
|
||||
const segmentedOptions = computed(() =>
|
||||
Array.isArray(systemsListtab.value)
|
||||
? systemsListtab.value.map((item: any) => ({ label: item.name, value: item.id }))
|
||||
: []
|
||||
)
|
||||
const getsystemsListtab = async () => {
|
||||
const response = await UserService.getsystemsList()
|
||||
const list = (response as any)?.data?.records ?? (response as any)?.records ?? response ?? []
|
||||
systemsListtab.value = Array.isArray(list) ? list : []
|
||||
if (!yearVal.value && systemsListtab.value.length > 0) {
|
||||
yearVal.value = systemsListtab.value[0].id
|
||||
}
|
||||
}
|
||||
//系统
|
||||
const systemsList = ref<any>()
|
||||
const getsystemsList = async () => {
|
||||
@@ -467,16 +498,37 @@
|
||||
const tableData = ref<AppRouteRecord[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
getsystemsListtab()
|
||||
getTableData()
|
||||
})
|
||||
|
||||
const searchArticleByYear = () => {
|
||||
getTableData()
|
||||
}
|
||||
// const handleSegmentedClick = (item: any) => {
|
||||
// // 若点击的是当前项也强制刷新
|
||||
// yearVal.value = item?.value ?? yearVal.value
|
||||
// getTableData()
|
||||
// }
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
// const { res } = await systemService.getMenutreeList()
|
||||
await systemService.getMenutreeList().then((res) => {
|
||||
console.log(res)
|
||||
menuList.value = res
|
||||
})
|
||||
// 清空旧数据,避免视觉上出现追加
|
||||
menuList.value = []
|
||||
tableData.value = []
|
||||
// 并发安全:本次请求标识
|
||||
requestCounter += 1
|
||||
const reqId = requestCounter
|
||||
const params: any = {
|
||||
system_id: yearVal.value || '1'
|
||||
}
|
||||
try {
|
||||
const res = await systemService.getMenutreeList(params)
|
||||
// 若在等待过程中又发起了新请求,则丢弃本次结果
|
||||
if (reqId !== requestCounter) return
|
||||
menuList.value = Array.isArray(res) ? res : []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
menuList.value = []
|
||||
}
|
||||
// console.log(res)
|
||||
// menuList.value = data
|
||||
|
||||
@@ -660,6 +712,9 @@
|
||||
const isExpanded = ref(false)
|
||||
const tableRef = ref()
|
||||
|
||||
// 数据请求并发保护计数器
|
||||
let requestCounter = 0
|
||||
|
||||
const toggleExpand = () => {
|
||||
isExpanded.value = !isExpanded.value
|
||||
nextTick(() => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple>搜索</ElButton>
|
||||
<ElButton @click="showDialog('add')" v-ripple>新增角色</ElButton>
|
||||
<ElButton @click="showDialog('add')" v-ripple v-if="hasAuth('system@role_add')">新增角色</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
@@ -36,9 +36,28 @@
|
||||
<ElTableColumn fixed="right" label="操作" width="200px">
|
||||
<template #default="scope">
|
||||
<ElRow>
|
||||
<el-button link @click="showPermissionDialog(scope.row)"> 菜单权限 </el-button>
|
||||
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
|
||||
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
|
||||
<el-button
|
||||
link
|
||||
@click="showPermissionDialog(scope.row)"
|
||||
v-if="hasAuth('system@role_perm')"
|
||||
>
|
||||
菜单权限
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
@click="showDialog('edit', scope.row)"
|
||||
v-if="hasAuth('system@role_update')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="deleteRole(scope.row)"
|
||||
v-if="hasAuth('system@role_destroy')"
|
||||
>删除
|
||||
</el-button>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
@@ -118,6 +137,8 @@
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { formatMenuTitle } from '@/router/utils/utils'
|
||||
import { systemService } from '@/api/systemApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
const { hasAuth } = useAuth()
|
||||
// import { ButtonMoreItem } from '@/components/core/forms/ArtButtonMore.vue'
|
||||
// import { Role, ROLE_LIST_DATA } from '@/mock/temp/formData'
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<!-- <ElButton v-ripple>搜索</ElButton> -->
|
||||
<ElButton @click="showDialog('add')" v-ripple>新增</ElButton>
|
||||
<ElButton @click="showDialog('add')" v-ripple v-if="hasAuth('system@systems_add')">新增</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
@@ -22,36 +22,114 @@
|
||||
:total="pagination.total"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn label="ID" prop="id" />
|
||||
<ElTableColumn label="分类名称" prop="name" />
|
||||
<ElTableColumn label="ID" prop="id" width="80" />
|
||||
<ElTableColumn label="系统编码" prop="code" />
|
||||
<ElTableColumn label="系统名称" prop="name" />
|
||||
<ElTableColumn label="系统类型" prop="type">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getTypeTagType(scope.row.type)">
|
||||
{{ getTypeText(scope.row.type) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="系统描述" prop="desc" show-overflow-tooltip />
|
||||
<ElTableColumn label="域名URL" prop="domain_url" show-overflow-tooltip />
|
||||
<ElTableColumn label="区分角色" prop="is_role">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.is_role ? 'success' : 'info'">
|
||||
{{ scope.row.is_role ? '是' : '否' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<!-- <ElTableColumn label="排序" prop="sort" width="80" /> -->
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status ? 'primary' : 'info'">
|
||||
{{ scope.row.status ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status ? 'primary' : 'info'">
|
||||
{{ scope.row.status ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="created_at" />
|
||||
<ElTableColumn fixed="right" label="操作" width="200px">
|
||||
<ElTableColumn fixed="right" label="操作">
|
||||
<template #default="scope">
|
||||
<ElRow>
|
||||
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
|
||||
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
|
||||
<el-button
|
||||
link
|
||||
@click="showDialog('edit', scope.row)"
|
||||
v-if="hasAuth('system@systems_update')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="deleteRole(scope.row)"
|
||||
v-if="hasAuth('system@systems_destroy')"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增' : '编辑'"
|
||||
width="40%"
|
||||
width="60%"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
|
||||
<ElFormItem label="分类名称" prop="name">
|
||||
<ElInput v-model="form.name" />
|
||||
<ElFormItem label="系统编码" prop="code">
|
||||
<ElInput v-model="form.code" placeholder="请输入系统编码" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系统名称" prop="name">
|
||||
<ElInput v-model="form.name" placeholder="请输入系统名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系统类型" prop="type">
|
||||
<ElSelect v-model="form.type" placeholder="请选择系统类型" style="width: 100%">
|
||||
<ElOption label="管理后台" value="admin" />
|
||||
<ElOption label="Web端" value="web" />
|
||||
<ElOption label="小程序" value="miniapp" />
|
||||
<ElOption label="工具" value="tool" />
|
||||
<ElOption label="其他" value="other" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系统描述" prop="desc">
|
||||
<ElInput v-model="form.desc" type="textarea" :rows="3" placeholder="请输入系统描述" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系统图标" prop="icon">
|
||||
<ElUpload
|
||||
v-model:file-list="iconFileList"
|
||||
:show-file-list="false"
|
||||
:headers="uploadHeaders"
|
||||
:action="actionImageUplode"
|
||||
:before-upload="beforeIconUpload"
|
||||
:on-success="handleIconSuccess"
|
||||
:on-error="handleIconError"
|
||||
accept="image/*"
|
||||
class="icon-uploader"
|
||||
>
|
||||
<div v-if="form.icon" class="icon-preview">
|
||||
<img :src="file_url" alt="系统图标" class="icon-image" />
|
||||
<div class="icon-overlay">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<el-icon v-else class="icon-uploader-icon"><Plus /></el-icon>
|
||||
</ElUpload>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="域名URL" prop="domain_url">
|
||||
<ElInput v-model="form.domain_url" placeholder="请输入域名URL" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="排序" prop="sort">
|
||||
<ElInputNumber v-model="form.sort" :min="0" placeholder="请输入排序" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="是否区分角色权限" prop="is_role">
|
||||
<ElSwitch v-model="form.is_role" :active-value="1" :inactive-value="0" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态" prop="status">
|
||||
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
@@ -70,25 +148,58 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Ordertype} from '@/mock/temp/formData'
|
||||
import { systemService } from '@/api/systemApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
const { hasAuth } = useAuth()
|
||||
defineOptions({ name: 'Type ' })
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const typeList = ref<any[]>([])
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const formRef = ref<FormInstance>()
|
||||
const iconFileList = ref<any[]>([])
|
||||
const typeList = ref<any[]>([])
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = computed(() => width.value < 500)
|
||||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||||
const { uploadHeaders } = useUploadHeaders()
|
||||
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload`
|
||||
const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: '请输入分类名称', trigger: 'blur' }
|
||||
]
|
||||
code: [{ required: true, message: '请输入系统编码', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入系统名称', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择系统类型', trigger: 'change' }]
|
||||
// desc: [
|
||||
// { required: true, message: '请输入系统描述', trigger: 'blur' }
|
||||
// ],
|
||||
// icon: [
|
||||
// { required: true, message: '请输入系统图标', trigger: 'blur' }
|
||||
// ],
|
||||
// domain_url: [
|
||||
// { required: true, message: '请输入域名URL', trigger: 'blur' }
|
||||
// ],
|
||||
// is_role: [
|
||||
// { required: true, message: '请选择是否区分角色权限', trigger: 'change' }
|
||||
// ],
|
||||
// sort: [
|
||||
// { required: true, message: '请输入排序', trigger: 'blur' }
|
||||
// ],
|
||||
// status: [
|
||||
// { required: true, message: '请选择状态', trigger: 'change' }
|
||||
// ]
|
||||
})
|
||||
|
||||
const form = reactive<Ordertype>({
|
||||
const file_url = ref('')
|
||||
const form = reactive({
|
||||
code: '',
|
||||
name: '',
|
||||
id:''
|
||||
type: '',
|
||||
desc: '',
|
||||
icon: '',
|
||||
domain_url: '',
|
||||
is_role: false,
|
||||
sort: 0,
|
||||
status: true,
|
||||
id: ''
|
||||
})
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
@@ -107,40 +218,86 @@
|
||||
|
||||
// 获取列表数据
|
||||
const getWorkList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
|
||||
const { records, current, size, total } = await systemService.getSystemList({
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
})
|
||||
tableData.value = records.map((item: any, index: number) => ({
|
||||
...item
|
||||
...item
|
||||
}))
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
} finally {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dialogType = ref('add')
|
||||
// 系统类型相关辅助函数
|
||||
const getTypeText = (type: string) => {
|
||||
const typeMap: { [key: string]: string } = {
|
||||
'admin': '管理后台',
|
||||
'web': 'Web端',
|
||||
'miniapp': '小程序',
|
||||
'tool': '工具',
|
||||
'other': '其他'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
const getTypeTagType = (type: string) => {
|
||||
const typeMap: { [key: string]: 'primary' | 'success' | 'warning' | 'info' | 'danger' } = {
|
||||
'admin': 'danger',
|
||||
'web': 'primary',
|
||||
'miniapp': 'success',
|
||||
'tool': 'warning',
|
||||
'other': 'info'
|
||||
}
|
||||
return typeMap[type] || 'info'
|
||||
}
|
||||
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
dialogType.value = type as 'add' | 'edit'
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.name = row.name
|
||||
form.id = row.id
|
||||
file_url.value = row.icon
|
||||
// 编辑模式,填充所有字段
|
||||
Object.assign(form, {
|
||||
code: row.code || '',
|
||||
name: row.name || '',
|
||||
type: row.type || 'web',
|
||||
desc: row.desc || '',
|
||||
icon: row.icon || row.icon_url || '', // 优先使用 icon,其次 icon_url
|
||||
domain_url: row.domain_url || '',
|
||||
is_role: row.is_role !== undefined ? row.is_role : 0,
|
||||
sort: row.sort || 0,
|
||||
status: row.status !== undefined ? row.status : 1,
|
||||
id: row.id || '' // 编辑时填充文件名,如果没有则使用 icon
|
||||
})
|
||||
} else {
|
||||
form.name = ''
|
||||
file_url.value = ''
|
||||
// 新增模式,重置所有字段
|
||||
Object.assign(form, {
|
||||
code: '',
|
||||
name: '',
|
||||
type: 'web',
|
||||
desc: '',
|
||||
icon: '',
|
||||
domain_url: '',
|
||||
is_role: 0,
|
||||
sort: 0,
|
||||
status: 1,
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
const deleteRole = (row?: any) => {
|
||||
ElMessageBox.confirm('确定删除该分类吗?', '删除确认', {
|
||||
ElMessageBox.confirm('确定删除吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
@@ -155,22 +312,72 @@
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (dialogType.value === 'add') {
|
||||
const { message } = await systemService.systemAdd({
|
||||
name: form.name
|
||||
})
|
||||
ElMessage.success(message)
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
} else {
|
||||
const { message } = await systemService.systemEdit({
|
||||
name: form.name,
|
||||
id:form.id
|
||||
})
|
||||
ElMessage.success(message)
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
const { message } = await systemService.systemAdd({
|
||||
code: form.code,
|
||||
name: form.name,
|
||||
type: form.type,
|
||||
desc: form.desc,
|
||||
icon: form.icon,
|
||||
domain_url: form.domain_url,
|
||||
is_role: form.is_role,
|
||||
sort: form.sort,
|
||||
status: form.status
|
||||
})
|
||||
ElMessage.success(message)
|
||||
} else {
|
||||
const { message } = await systemService.systemEdit({
|
||||
id: form.id,
|
||||
code: form.code,
|
||||
name: form.name,
|
||||
type: form.type,
|
||||
desc: form.desc,
|
||||
icon: form.icon,
|
||||
domain_url: form.domain_url,
|
||||
is_role: form.is_role,
|
||||
sort: form.sort,
|
||||
status: form.status
|
||||
})
|
||||
ElMessage.success(message)
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 图标上传相关函数
|
||||
const beforeIconUpload = (file: File) => {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件!')
|
||||
return false
|
||||
}
|
||||
if (!isLt2M) {
|
||||
ElMessage.error('图片大小不能超过 2MB!')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const handleIconSuccess = (response: any, file: any) => {
|
||||
// 接口返回 file_url 用于回显,file_name 用于传参
|
||||
if (response.data) {
|
||||
form.icon = response.data.file_url || response.data.file_name // 回显用 file_url
|
||||
file_url.value = response.data.file_url
|
||||
} else {
|
||||
form.icon = response.file_url || response.file_name
|
||||
file_url.value = response.file_url
|
||||
}
|
||||
ElMessage.success('图标上传成功')
|
||||
}
|
||||
|
||||
const handleIconError = () => {
|
||||
ElMessage.error('图标上传失败')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -191,4 +398,74 @@
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
.el-table {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.icon-uploader {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
text-align: center;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.icon-uploader-icon:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.icon-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.icon-overlay .el-icon {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.icon-preview:hover .icon-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -187,6 +187,23 @@
|
||||
<el-input v-model="addForm.phone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="上级" prop="report_to">
|
||||
<el-select
|
||||
v-model="addForm.report_to"
|
||||
filterable
|
||||
placeholder="请选择上级"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="addForm.status">
|
||||
@@ -348,6 +365,7 @@
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
report_to: '',
|
||||
status: ''
|
||||
})
|
||||
const roleForm = reactive({
|
||||
@@ -677,6 +695,7 @@
|
||||
addForm.email = row.email
|
||||
addForm.phone = row.phone
|
||||
addForm.status = row.status
|
||||
addForm.report_to = row.report_to || ''
|
||||
}
|
||||
const handleAddSubmit = async () => {
|
||||
const res = await manageService.userEdit(addForm)
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
<template>
|
||||
<div class="page-content user">
|
||||
<div class="content">
|
||||
<div class="left-wrap">
|
||||
<div class="user-wrap box-style">
|
||||
<!-- <img class="bg" src="@imgs/user/bg.webp" /> -->
|
||||
<img
|
||||
class="avatar"
|
||||
:src="userInfo.avatar != '' ? userInfo.avatar : '/src/assets/img/user/avatar.webp'"
|
||||
/>
|
||||
<h2 class="name">{{ userInfo.name }}</h2>
|
||||
<p class="des">SN:{{ userInfo.sn }}</p>
|
||||
<div class="outer-info">
|
||||
<!-- <div class="info-row">
|
||||
<span class="info-label">昵 称:</span>
|
||||
<span class="info-value">{{ userInfo.phone }}</span>
|
||||
</div> -->
|
||||
<div class="info-row">
|
||||
<span class="info-label">电 话:</span>
|
||||
<span class="info-value">{{ userInfo.phone }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">邮 箱:</span>
|
||||
<span class="info-value">{{ userInfo.email }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">公 司:</span>
|
||||
<span class="info-value">{{ userInfo.corp_name }}</span>
|
||||
</div>
|
||||
<!-- <div class="info-row">
|
||||
<span class="info-label">国 家:</span>
|
||||
<span class="info-value">{{ userInfo.country_txt }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-wrap">
|
||||
<div class="info box-style">
|
||||
<h1 class="title"><span class="border" style="margin-right: 5px"></span>基本信息</h1>
|
||||
|
||||
<ElForm
|
||||
:model="form"
|
||||
class="form"
|
||||
ref="ruleFormRef"
|
||||
label-width="86px"
|
||||
label-position="left"
|
||||
>
|
||||
<ElFormItem label="头像" prop="avatar">
|
||||
<el-upload
|
||||
class="qrcode-upload"
|
||||
:action="actionImageUplode"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleUploadavatar"
|
||||
:disabled="!isEdit"
|
||||
>
|
||||
<div class="qrcode-upload-box">
|
||||
<img
|
||||
class="avatar"
|
||||
:src="
|
||||
imageUrlavatar != '' ? imageUrlavatar : '/src/assets/img/user/avatar.webp'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</el-upload>
|
||||
</ElFormItem>
|
||||
<ElRow>
|
||||
<ElFormItem label="昵称" prop="name">
|
||||
<el-input v-model="form.name" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="性别" prop="sex" class="right-input">
|
||||
<ElSelect v-model="form.sex" placeholder="未知" :disabled="!isEdit">
|
||||
<ElOption
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElRow>
|
||||
|
||||
<ElRow>
|
||||
<ElFormItem label="联系方式" prop="phone">
|
||||
<ElInput v-model="form.phone" disabled />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="公司" prop="company" class="right-input">
|
||||
<ElInput v-model="form.corp_name" disabled />
|
||||
</ElFormItem>
|
||||
<!-- <ElFormItem label="职务" prop="position" class="right-input">
|
||||
<ElInput v-model="form.position" :disabled="!isEdit" />
|
||||
</ElFormItem> -->
|
||||
</ElRow>
|
||||
|
||||
<!-- <ElRow>
|
||||
<ElFormItem label="公司" prop="company">
|
||||
<ElInput v-model="form.corp_name" disabled />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="国家" prop="address" class="right-input">
|
||||
<el-select
|
||||
v-model="form.country_id"
|
||||
filterable
|
||||
placeholder="请选择国家"
|
||||
:disabled="!isEdit"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in countryOptions"
|
||||
:key="item.id"
|
||||
:label="item.cn_addr"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</ElFormItem>
|
||||
</ElRow> -->
|
||||
<ElRow>
|
||||
<ElFormItem label="邮箱" prop="email">
|
||||
<ElInput v-model="form.email" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
</ElRow>
|
||||
<!-- <ElRow>
|
||||
<ElFormItem
|
||||
label="二维码"
|
||||
prop="socialize"
|
||||
:class="isEdit ? 'qrcode-radio-group' : ''"
|
||||
>
|
||||
<el-radio-group v-model="form.socialize" v-if="isEdit">
|
||||
<el-radio label="微信">微信</el-radio>
|
||||
<el-radio label="WhatsApp">WhatsApp</el-radio>
|
||||
</el-radio-group>
|
||||
<template v-else>
|
||||
<img
|
||||
v-if="form.socialize_pic_full"
|
||||
:src="form.socialize_pic_full"
|
||||
class="qrcode-img"
|
||||
/>
|
||||
<span v-else class="qrcode-upload-text">暂无二维码</span>
|
||||
</template>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="isEdit" label="上传二维码" prop="address" class="right-input">
|
||||
<el-upload
|
||||
class="qrcode-upload"
|
||||
:action="actionImageUplode"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleUploadavatarqrcode"
|
||||
>
|
||||
<div class="qrcode-upload-box">
|
||||
<img
|
||||
v-if="form.socialize_pic_full"
|
||||
:src="socialize_pic_full"
|
||||
class="qrcode-img"
|
||||
/>
|
||||
<span v-else class="qrcode-upload-text">
|
||||
<img :src="imageUrls" class="qrcode-img" />
|
||||
</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
</ElFormItem>
|
||||
</ElRow> -->
|
||||
|
||||
<div class="el-form-item-right">
|
||||
<ElButton type="primary" style="width: 90px" v-ripple @click="edit">
|
||||
<i v-if="!isEdit" class="iconfont-sys"> </i>
|
||||
{{ isEdit ? '保存' : '编辑' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</div>
|
||||
|
||||
<div class="info box-style" style="margin-top: 20px">
|
||||
<h1 class="title">更改密码</h1>
|
||||
|
||||
<ElForm :model="pwdForm" class="form" label-width="86px" label-position="left">
|
||||
<ElFormItem label="当前密码" prop="password">
|
||||
<ElInput
|
||||
v-model="pwdForm.password"
|
||||
type="password"
|
||||
:disabled="!isEditPwd"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="新密码" prop="password_new">
|
||||
<ElInput
|
||||
v-model="pwdForm.password_new"
|
||||
type="password"
|
||||
:disabled="!isEditPwd"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="确认新密码" prop="confirmPassword">
|
||||
<ElInput
|
||||
v-model="pwdForm.confirmPassword"
|
||||
type="password"
|
||||
:disabled="!isEditPwd"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<div class="el-form-item-right">
|
||||
<ElButton type="primary" style="width: 90px" v-ripple @click="editPwd">
|
||||
<i v-if="!isEditPwd" class="iconfont-sys"> </i>
|
||||
{{ isEditPwd ? '保存' : '编辑' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElForm, FormInstance } from 'element-plus'
|
||||
// import imageUrl from '@/assets/img/crm/upimg.png'
|
||||
import { CrmService } from '@/api/crmApi'
|
||||
import { UserService } from '@/api/usersApi'
|
||||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
const { uploadHeaders } = useUploadHeaders()
|
||||
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload`
|
||||
defineOptions({ name: 'UserCenter' })
|
||||
// 定义客户详情类型
|
||||
interface CustomerInfo {
|
||||
[key: string]: any
|
||||
}
|
||||
// const imageUrls = ref(imageUrl)
|
||||
const imageUrlavatar = ref('')
|
||||
// const socialize_pic_full = ref('')
|
||||
// const countryOptions = ref<any>()
|
||||
const form = ref<CustomerInfo>({})
|
||||
//获取公司 getCompanyList
|
||||
// const companyList = ref<any>()
|
||||
//国家
|
||||
// const getarea = async () => {
|
||||
// try {
|
||||
// // 这里调用获取品牌列表的接口
|
||||
// const res = await CrmService.getCountry()
|
||||
// countryOptions.value = res||[]
|
||||
// } catch (error) { countryOptions.value = [] }
|
||||
// }
|
||||
const { getUserInfo } = useUserStore()
|
||||
const userInfo = ref<CustomerInfo>({})
|
||||
const getuserInfo = async () => {
|
||||
// const data = await CrmService.userget()
|
||||
userInfo.value = getUserInfo.user
|
||||
form.value = getUserInfo.user
|
||||
imageUrlavatar.value = getUserInfo.user.avatar
|
||||
// socialize_pic_full.value = data.socialize_pic_full
|
||||
}
|
||||
const isEdit = ref(false)
|
||||
const isEditPwd = ref(false)
|
||||
const pwdForm = reactive({
|
||||
password: '',
|
||||
password_new: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const ruleFormRef = ref<FormInstance>()
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '',
|
||||
label: '未知'
|
||||
},
|
||||
{
|
||||
value: '1',
|
||||
label: '男'
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '女'
|
||||
}
|
||||
]
|
||||
onMounted(async () => {
|
||||
// await getarea()
|
||||
await getuserInfo()
|
||||
})
|
||||
const edit = async () => {
|
||||
if (isEdit.value) {
|
||||
const res = await UserService.editUserInfo(form.value)
|
||||
ElMessage.success(res?.message || '操作成功')
|
||||
getuserInfo()
|
||||
}
|
||||
isEdit.value = !isEdit.value
|
||||
}
|
||||
|
||||
const editPwd = async () => {
|
||||
if (isEditPwd.value) {
|
||||
// 判断新密码和确认密码是否一致
|
||||
if (pwdForm.password_new !== pwdForm.confirmPassword) {
|
||||
ElMessage.error('新密码和确认密码不一致')
|
||||
return
|
||||
}
|
||||
const res = await CrmService.savePassword(pwdForm)
|
||||
ElMessage.success(res?.message || '操作成功')
|
||||
}
|
||||
isEditPwd.value = !isEditPwd.value
|
||||
}
|
||||
//头像上传
|
||||
function handleUploadavatar(response: any) {
|
||||
// 假设返回图片地址字段为 response.url 或 response.data.url
|
||||
imageUrlavatar.value = response.url || (response.data && response.data.file_url) || ''
|
||||
// 如需同步到 form.picture
|
||||
form.value.avatar = response.url || (response.data && response.data.file_name) || ''
|
||||
}
|
||||
//二维码上传
|
||||
// function handleUploadavatarqrcode(response: any) {
|
||||
// // 假设返回图片地址字段为 response.url 或 response.data.url
|
||||
// socialize_pic_full.value = response.url || (response.data && response.data.file_url) || ''
|
||||
// imageUrls.value = response.url || (response.data && response.data.file_url) || ''
|
||||
// // 如需同步到 form.picture
|
||||
// form.value.socialize_pic = response.url || (response.data && response.data.file_name) || ''
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.user {
|
||||
.icon {
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
overflow: hidden;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.qrcode-radio-group .el-form-item__content){
|
||||
height: 40px;
|
||||
}
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.qrcode-img{
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.page-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
$box-radius: calc(var(--custom-radius) + 4px);
|
||||
|
||||
.box-style {
|
||||
border: 1px solid var(--art-border-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
|
||||
.left-wrap {
|
||||
width: 350px;
|
||||
margin-right: 25px;
|
||||
|
||||
.user-wrap {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
padding: 35px 30px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
// background: var(--art-main-bg-color);
|
||||
background: linear-gradient(180deg, #E9F0FF 0%, #FFFFFF 67.9%);
|
||||
|
||||
border-radius: $box-radius;
|
||||
.avatar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
// margin-top: 120px;
|
||||
object-fit: cover;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-top: 20px;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.des {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.outer-info {
|
||||
// width: 260px;
|
||||
margin: 0 auto;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.info-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
color: #222;
|
||||
text-align: right;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.info-value {
|
||||
color: #333;
|
||||
margin-left: 8px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.lables {
|
||||
margin-top: 40px;
|
||||
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
|
||||
> div {
|
||||
padding: 3px 6px;
|
||||
margin: 0 10px 10px 0;
|
||||
font-size: 12px;
|
||||
background: var(--art-main-bg-color);
|
||||
border: 1px solid var(--art-border-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery {
|
||||
margin-top: 25px;
|
||||
border-radius: 10px;
|
||||
|
||||
.item {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-wrap {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: $box-radius;
|
||||
|
||||
.info {
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: $box-radius;
|
||||
|
||||
.title {
|
||||
padding: 15px 25px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: var(--art-text-gray-800);
|
||||
// border-bottom: 1px solid var(--art-border-color);
|
||||
}
|
||||
|
||||
.form {
|
||||
box-sizing: border-box;
|
||||
padding: 30px 25px;
|
||||
|
||||
> .el-row {
|
||||
.el-form-item {
|
||||
width: calc(50% - 10px);
|
||||
}
|
||||
|
||||
.el-input,
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.right-input {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.el-form-item-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
.el-button {
|
||||
width: 110px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $device-ipad-vertical) {
|
||||
.page-content {
|
||||
.content {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
|
||||
.left-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.right-wrap {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -36,11 +36,11 @@
|
||||
<div class="form">
|
||||
<!-- 登录方式切换 tab -->
|
||||
<div class="login-type-tabs">
|
||||
<span
|
||||
<!-- <span
|
||||
:class="{ active: activeLoginType === 'password' }"
|
||||
@click="activeLoginType = 'password'"
|
||||
>账号密码登录</span
|
||||
>
|
||||
> -->
|
||||
<span
|
||||
:class="{ active: activeLoginType === 'qrcode' }"
|
||||
@click="activeLoginType = 'qrcode'"
|
||||
|
||||
@@ -7,11 +7,25 @@
|
||||
添加
|
||||
</el-button>
|
||||
<div class="customer-search-group">
|
||||
<el-input v-model="searchValue" placeholder="请输入客户名称或电话" />
|
||||
<el-select v-model="selectValue">
|
||||
<el-option v-for="v in selectOptions" :key="v.value" :label="v.label" :value="v.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input v-model="searchValue" placeholder="请输入客户名称或电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="国家" label-width="70px">
|
||||
<el-select v-model="country_id" filterable placeholder="请选择国家">
|
||||
<el-option
|
||||
v-for="item in countryOptions"
|
||||
:key="item.id"
|
||||
:label="item.cn_addr"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间" label-width="70px">
|
||||
<el-select v-model="selectValue" placeholder="请选择时间" style="width: 120px">
|
||||
<el-option v-for="v in selectOptions" :key="v.value" :label="v.label" :value="v.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -96,6 +110,7 @@
|
||||
lass="customer-table"
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column prop="code" label="编号" align="center" />
|
||||
<el-table-column prop="contacts" label="客户名称" align="center" />
|
||||
<el-table-column prop="magnitude_txt" label="客户商机" align="center" />
|
||||
<el-table-column prop="channel_name" label="渠道" align="center" />
|
||||
@@ -175,6 +190,7 @@
|
||||
const searchValue = ref('')
|
||||
const selectValue = ref('')
|
||||
const activeTab = ref('')
|
||||
const country_id = ref('')
|
||||
const showMore = ref(false)
|
||||
const loading = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
@@ -190,6 +206,15 @@
|
||||
{ label: '近三天', value: '1' },
|
||||
{ label: '近七天', value: '2' }
|
||||
]
|
||||
const countryOptions = ref<any>()
|
||||
//国家
|
||||
const getarea = async () => {
|
||||
try {
|
||||
// 这里调用获取品牌列表的接口
|
||||
const res = await CrmService.getCountry()
|
||||
countryOptions.value = res||[]
|
||||
} catch (error) { countryOptions.value = [] }
|
||||
}
|
||||
// 获取列表数据
|
||||
const tagActiveMap = reactive<{ [groupId: string]: string }>({})
|
||||
//获取标签
|
||||
@@ -212,11 +237,12 @@
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
onMounted(() => {
|
||||
getarea()
|
||||
gettableList()
|
||||
tagGrouplistfn()
|
||||
})
|
||||
// tabs 切换联动
|
||||
watch([activeTab, searchValue, selectValue], () => {
|
||||
watch([activeTab, searchValue, selectValue, country_id], () => {
|
||||
pagination.currentPage = 1
|
||||
gettableList()
|
||||
})
|
||||
@@ -230,6 +256,7 @@
|
||||
}
|
||||
if (searchValue.value) params.keyword = searchValue.value
|
||||
if (selectValue.value) params.follow_last = selectValue.value
|
||||
if (country_id.value) params.country_id = country_id.value
|
||||
if (activeTab.value && activeTab.value !== '') params.follow = activeTab.value
|
||||
// 标签参数,所有选中的标签id组成数组
|
||||
const selectedTagIds = Object.values(tagActiveMap).filter(Boolean)
|
||||
|
||||
+19
-9
@@ -2,15 +2,24 @@
|
||||
<div class="customer-page">
|
||||
<div class="customer-toolbar boxshadow">
|
||||
<div class="customer-search-group">
|
||||
<el-input v-model="keyword" placeholder="请输入关键字" />
|
||||
<el-select
|
||||
v-model="selectValue"
|
||||
style="min-width: 120px"
|
||||
filterable
|
||||
placeholder="请选择用户"
|
||||
>
|
||||
<el-option v-for="v in userOptions" :key="v.id" :label="v.name" :value="v.id"></el-option>
|
||||
</el-select>
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input v-model="keyword" placeholder="请输入关键字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="组员:" label-width="70px">
|
||||
<el-select
|
||||
v-model="selectValue"
|
||||
style="min-width: 120px"
|
||||
filterable
|
||||
placeholder="请选择组员"
|
||||
>
|
||||
<el-option
|
||||
v-for="v in userOptions"
|
||||
:key="v.id"
|
||||
:label="v.name"
|
||||
:value="v.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table boxshadow">
|
||||
@@ -32,6 +41,7 @@
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column type="selection" width="55px" align="center"></el-table-column>
|
||||
<el-table-column prop="code" label="编号" align="center" />
|
||||
<el-table-column prop="contacts" label="客户名称"></el-table-column>
|
||||
<el-table-column prop="phone" label="电话"></el-table-column>
|
||||
<el-table-column prop="WhatsApp" label="WhatsApp"></el-table-column>
|
||||
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="customer-page">
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="customer-toolbar boxshadow">
|
||||
<div class="customer-search-group">
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" label-width="50px">
|
||||
<el-select v-model="selectValue" style="min-width: 120px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="正常" value="1" />
|
||||
<el-option label="离职" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxshadow">
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
lass="customer-table"
|
||||
>
|
||||
<template #default>
|
||||
<!-- <el-table-column prop="id" label="用户ID" align="center" /> -->
|
||||
<el-table-column prop="sn" label="SN" align="center" />
|
||||
<el-table-column prop="follow" label="头像" align="center">
|
||||
<template #default="scope">
|
||||
<img
|
||||
:src="scope.row.avatar"
|
||||
alt="头像"
|
||||
class="avatar"
|
||||
@click="handleAvatarClick(scope.row.avatar)"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名字" align="center" />
|
||||
<el-table-column prop="phone" label="电话" align="center" />
|
||||
<el-table-column prop="email" label="邮箱" align="center" />
|
||||
<!-- <el-table-column prop="corp_name" label="所属公司" align="center" /> -->
|
||||
<el-table-column prop="follow" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<span :class="statusClass(scope.row.status)">
|
||||
{{ scope.row.status == 1 ? '在职' : '离职' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="customer_count" label="客户数量" align="center" />
|
||||
<el-table-column fixed="right" label="操作" width="200px" align="center">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
type="warning"
|
||||
underline="never"
|
||||
style="margin-right: 8px"
|
||||
@click="statusfn(scope.row)"
|
||||
v-if="hasAuth('crm_customer@status') && scope.row.status == 1"
|
||||
>
|
||||
<i class="iconfont-sys"></i>标记离职
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</div>
|
||||
<!-- 图片预览组件 -->
|
||||
<ElImageViewer
|
||||
v-if="showImageViewer"
|
||||
:url-list="[currentImageUrl]"
|
||||
:hide-on-click-modal="true"
|
||||
@close="showImageViewer = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ElImageViewer } from 'element-plus'
|
||||
import { CrmService } from '@/api/crmApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
const { hasAuth } = useAuth()
|
||||
const selectValue = ref<string[]>([])
|
||||
const keyword = ref<string>('')
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
onMounted(() => {
|
||||
gettableList()
|
||||
})
|
||||
// tabs 切换联动
|
||||
watch([selectValue, keyword], () => {
|
||||
pagination.currentPage = 1
|
||||
gettableList()
|
||||
})
|
||||
const gettableList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
const params: any = {
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
}
|
||||
if (selectValue.value) {
|
||||
params.status = selectValue.value
|
||||
}
|
||||
if (keyword.value) {
|
||||
params.keyword = keyword.value
|
||||
}
|
||||
const { records, current, size, total } = await CrmService.getCustomerUserList(params)
|
||||
tableData.value = records.map((item: any) => ({
|
||||
...item
|
||||
}))
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
gettableList()
|
||||
}
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
gettableList()
|
||||
}
|
||||
function statusClass(status: any) {
|
||||
if (status === 1) return 'status-following'
|
||||
if (status === 0) return 'status-lost'
|
||||
return ''
|
||||
}
|
||||
// 图片预览相关
|
||||
const showImageViewer = ref(false)
|
||||
const currentImageUrl = ref('')
|
||||
|
||||
const handleAvatarClick = (avatarUrl: string) => {
|
||||
if (avatarUrl) {
|
||||
currentImageUrl.value = avatarUrl
|
||||
showImageViewer.value = true
|
||||
}
|
||||
}
|
||||
const statusfn = (row?: any) => {
|
||||
ElMessageBox.confirm('确定标记离职吗?', '标记确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(async () => {
|
||||
const { message } = await CrmService.markLeave({
|
||||
user_id: row.id
|
||||
})
|
||||
ElMessage.success(message)
|
||||
gettableList()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-badge) {
|
||||
--el-badge-size: 16px;
|
||||
}
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
transition: opacity 0.2s;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.itemtag {
|
||||
margin-top: -16px;
|
||||
margin-left: -13px;
|
||||
}
|
||||
:deep(.el-table__header th) {
|
||||
background: #f7f9fa !important;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
:deep(.el-table__empty-block) {
|
||||
height: 450px !important;
|
||||
}
|
||||
:deep(.el-table__body tr.custom-table-row td) {
|
||||
border-bottom: 1px solid #f7f9fa !important;
|
||||
border-right: 1px solid #f7f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr td:last-child) {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:last-child td) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th) {
|
||||
border-bottom: 1px solid #f7f9fa !important;
|
||||
border-right: 1px solid #f7f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th:last-child) {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
border: 1px solid #f7f9fa !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.customer-page {
|
||||
background: var(--art-main-bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.customer-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 20px 20px;
|
||||
background: var(--art-main-bg-color);
|
||||
}
|
||||
|
||||
.customer-search-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.customer-tags {
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
width: 120px;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-tags {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.customer-tabs {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.status-following {
|
||||
color: #00a43f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-dealed {
|
||||
color: #3471ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-lost {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.customer-table {
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.filter-more-btn {
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.form-submit-row {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="system-notification-demo">
|
||||
<h2>浏览器原生Notification API测试Demo</h2>
|
||||
<div class="permission-status">
|
||||
<p
|
||||
>当前通知权限状态: <strong>{{ permissionStatus }}</strong></p
|
||||
>
|
||||
<el-button v-if="permissionStatus !== 'granted'" @click="requestPermission" type="primary">
|
||||
请求通知权限
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<el-button @click="showBasicNotification">基本通知</el-button>
|
||||
<el-button @click="showIconNotification">带图标通知</el-button>
|
||||
<el-button @click="showPersistentNotification">持久化通知</el-button>
|
||||
<el-button @click="showDataNotification">带数据的通知</el-button>
|
||||
<el-button @click="showEventNotification">带事件监听的通知</el-button>
|
||||
<el-button @click="checkSupport">检查浏览器支持</el-button>
|
||||
</div>
|
||||
<div class="log-area">
|
||||
<h3>操作日志</h3>
|
||||
<ul>
|
||||
<li v-for="(log, index) in logs" :key="index">{{ log }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
// import { ElButton } from 'element-plus'
|
||||
|
||||
// 状态变量
|
||||
const permissionStatus = ref('default')
|
||||
const logs = ref([])
|
||||
const isSupported = ref(false)
|
||||
|
||||
// 添加日志
|
||||
const addLog = (message) => {
|
||||
const timestamp = new Date().toLocaleTimeString()
|
||||
logs.value.unshift(`[${timestamp}] ${message}`)
|
||||
// 限制日志数量
|
||||
if (logs.value.length > 10) {
|
||||
logs.value.pop()
|
||||
}
|
||||
}
|
||||
|
||||
// 检查浏览器支持情况
|
||||
const checkSupport = () => {
|
||||
if ('Notification' in window) {
|
||||
isSupported.value = true
|
||||
addLog('浏览器支持原生Notification API')
|
||||
addLog(`当前权限状态: ${Notification.permission}`)
|
||||
} else {
|
||||
isSupported.value = false
|
||||
addLog('⚠️ 浏览器不支持原生Notification API')
|
||||
}
|
||||
}
|
||||
|
||||
// 请求通知权限
|
||||
const requestPermission = async () => {
|
||||
if (!('Notification' in window)) {
|
||||
addLog('浏览器不支持原生Notification API')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const permission = await Notification.requestPermission()
|
||||
permissionStatus.value = permission
|
||||
if (permission === 'granted') {
|
||||
addLog('用户已授予通知权限')
|
||||
} else if (permission === 'denied') {
|
||||
addLog('用户拒绝了通知权限')
|
||||
} else {
|
||||
addLog('用户暂时未授予通知权限')
|
||||
}
|
||||
} catch (error) {
|
||||
addLog(`请求权限时出错: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示基本通知
|
||||
const showBasicNotification = () => {
|
||||
if (!('Notification' in window)) {
|
||||
addLog('浏览器不支持原生Notification API')
|
||||
return
|
||||
}
|
||||
if (Notification.permission !== 'granted') {
|
||||
addLog('请先授予通知权限')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const notification = new Notification('基本通知', {
|
||||
body: '这是一个使用浏览器原生API的系统通知'
|
||||
})
|
||||
notification.onclick = () => {
|
||||
addLog('用户点击了基本通知')
|
||||
notification.close()
|
||||
}
|
||||
addLog('已发送基本通知')
|
||||
} catch (error) {
|
||||
addLog(`发送通知失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示带图标通知
|
||||
const showIconNotification = () => {
|
||||
if (Notification.permission !== 'granted') {
|
||||
addLog('请先授予通知权限')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const notification = new Notification('带图标通知', {
|
||||
body: '这个通知包含自定义图标',
|
||||
icon: 'https://element-plus.org/images/element-plus-logo.svg', // 替换为实际图标URL
|
||||
badge: 'https://element-plus.org/images/element-plus-logo.svg' // 小图标(移动端)
|
||||
})
|
||||
addLog('已发送带图标通知')
|
||||
} catch (error) {
|
||||
addLog(`发送通知失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示持久化通知
|
||||
const showPersistentNotification = () => {
|
||||
if (Notification.permission !== 'granted') {
|
||||
addLog('请先授予通知权限')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const notification = new Notification('持久化通知', {
|
||||
body: '这个通知需要用户手动关闭',
|
||||
requireInteraction: true, // 持久化显示,直到用户交互
|
||||
icon: 'https://element-plus.org/images/element-plus-logo.svg'
|
||||
})
|
||||
notification.onclose = () => {
|
||||
addLog('用户关闭了持久化通知')
|
||||
}
|
||||
|
||||
addLog('已发送持久化通知')
|
||||
} catch (error) {
|
||||
addLog(`发送通知失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示带数据的通知
|
||||
const showDataNotification = () => {
|
||||
if (Notification.permission !== 'granted') {
|
||||
addLog('请先授予通知权限')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const notification = new Notification('带数据的通知', {
|
||||
body: '这个通知包含自定义数据',
|
||||
data: {
|
||||
url: 'https://example.com',
|
||||
id: 12345,
|
||||
message: '这是附加数据'
|
||||
},
|
||||
icon: 'https://element-plus.org/images/element-plus-logo.svg'
|
||||
})
|
||||
|
||||
notification.onclick = () => {
|
||||
addLog(`通知数据: ${JSON.stringify(notification.data)}`)
|
||||
window.open(notification.data.url)
|
||||
notification.close()
|
||||
}
|
||||
|
||||
addLog('已发送带数据的通知')
|
||||
} catch (error) {
|
||||
addLog(`发送通知失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示带事件监听的通知
|
||||
const showEventNotification = () => {
|
||||
if (Notification.permission !== 'granted') {
|
||||
addLog('请先授予通知权限')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const notification = new Notification('带事件监听的通知', {
|
||||
body: '这个通知演示各种事件监听',
|
||||
icon: 'https://element-plus.org/images/element-plus-logo.svg'
|
||||
})
|
||||
|
||||
notification.onshow = () => {
|
||||
addLog('通知显示事件触发')
|
||||
}
|
||||
|
||||
notification.onclick = () => {
|
||||
addLog('通知点击事件触发')
|
||||
notification.close()
|
||||
}
|
||||
|
||||
notification.onclose = () => {
|
||||
addLog('通知关闭事件触发')
|
||||
}
|
||||
|
||||
notification.onerror = (error) => {
|
||||
addLog(`通知错误事件: ${error.message}`)
|
||||
}
|
||||
|
||||
addLog('已发送带事件监听的通知')
|
||||
} catch (error) {
|
||||
addLog(`发送通知失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时检查权限状态
|
||||
onMounted(() => {
|
||||
addLog('页面加载完成,初始化Demo')
|
||||
|
||||
// 检查浏览器支持
|
||||
checkSupport()
|
||||
|
||||
// 检查当前权限状态
|
||||
if ('Notification' in window) {
|
||||
permissionStatus.value = Notification.permission
|
||||
addLog(`初始化权限状态: ${Notification.permission}`)
|
||||
|
||||
// 监听权限变化
|
||||
if (Notification.permissionChange) {
|
||||
Notification.permissionChange.then((permission) => {
|
||||
permissionStatus.value = permission
|
||||
addLog(`通知权限已变更为: ${permission}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.system-notification-demo {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.permission-status {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.log-area {
|
||||
margin-top: 30px;
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.log-area ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-area li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.log-area li:first-child {
|
||||
color: #409eff;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user