新增会员界面
This commit is contained in:
parent
6032036f40
commit
dbf0710653
92
src/views/vip/components/AddMemberDialog.vue
Normal file
92
src/views/vip/components/AddMemberDialog.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="新增会员" width="500px" @close="handleClose">
|
||||
<el-form :model="form" label-width="100px" :rules="rules" ref="formRef">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineEmits, defineProps, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const dialogVisible = ref(props.visible)
|
||||
const formRef = ref(null)
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
phone: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入姓名', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(() => props.visible, (val) => {
|
||||
dialogVisible.value = val
|
||||
})
|
||||
|
||||
// 监听dialogVisible变化
|
||||
watch(dialogVisible, (val) => {
|
||||
emit('update:visible', val)
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
form.value = {
|
||||
name: '',
|
||||
phone: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
// 生成一个随机的会员数据
|
||||
const newMember = {
|
||||
id: Date.now(),
|
||||
name: form.value.name,
|
||||
phone: form.value.phone,
|
||||
vipStatus: false,
|
||||
vipExpireDate: ''
|
||||
}
|
||||
emit('success', newMember)
|
||||
handleClose()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
</style>
|
111
src/views/vip/components/PasswordVerifyDialog.vue
Normal file
111
src/views/vip/components/PasswordVerifyDialog.vue
Normal file
@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="密码验证"
|
||||
width="400px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="formData.password"
|
||||
type="password"
|
||||
placeholder="请输入当前登录密码"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="loading">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'success', 'cancel'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
const formData = reactive({
|
||||
password: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
dialogVisible.value = val
|
||||
}
|
||||
)
|
||||
|
||||
// 监听dialogVisible变化
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(val) => {
|
||||
emit('update:visible', val)
|
||||
}
|
||||
)
|
||||
|
||||
const handleClose = () => {
|
||||
formData.password = ''
|
||||
formRef.value?.resetFields()
|
||||
dialogVisible.value = false
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
|
||||
// TODO: 这里需要调用后端API验证密码
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 假设密码验证成功
|
||||
emit('success', formData.password)
|
||||
handleClose()
|
||||
loading.value = false
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
137
src/views/vip/components/RechargeDialog.vue
Normal file
137
src/views/vip/components/RechargeDialog.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="会员续费" width="600px" @close="handleClose">
|
||||
<div class="package-grid">
|
||||
<div
|
||||
v-for="pkg in packages"
|
||||
:key="pkg.id"
|
||||
class="package-card"
|
||||
:class="{ active: selectedPackage === pkg }"
|
||||
@click="selectedPackage = pkg"
|
||||
>
|
||||
<div class="package-name">{{ pkg.name }}</div>
|
||||
<div class="package-price">
|
||||
<span class="currency">¥</span>
|
||||
<span class="amount">{{ pkg.price }}</span>
|
||||
</div>
|
||||
<div class="package-duration">{{ pkg.duration }}个月</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineEmits, defineProps, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
member: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const dialogVisible = ref(props.visible)
|
||||
const selectedPackage = ref(null)
|
||||
|
||||
const packages = [
|
||||
{ id: 1, name: '月度会员', price: 30, duration: 1 },
|
||||
{ id: 2, name: '季度会员', price: 80, duration: 3 },
|
||||
{ id: 3, name: '年度会员', price: 298, duration: 12 }
|
||||
]
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(() => props.visible, (val) => {
|
||||
dialogVisible.value = val
|
||||
})
|
||||
|
||||
// 监听dialogVisible变化
|
||||
watch(dialogVisible, (val) => {
|
||||
emit('update:visible', val)
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
selectedPackage.value = null
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!selectedPackage.value) {
|
||||
ElMessage.warning('请选择会员套餐')
|
||||
return
|
||||
}
|
||||
|
||||
// 计算新的到期时间
|
||||
const currentDate = props.member.vipExpireDate ? new Date(props.member.vipExpireDate) : new Date()
|
||||
const newExpireDate = new Date(currentDate.setMonth(currentDate.getMonth() + selectedPackage.value.duration))
|
||||
|
||||
emit('success', {
|
||||
memberId: props.member.id,
|
||||
packageId: selectedPackage.value.id,
|
||||
expireDate: newExpireDate.toISOString().split('T')[0]
|
||||
})
|
||||
handleClose()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.package-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
border-color: #409eff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.package-card.active {
|
||||
border-color: #409eff;
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.package-price {
|
||||
color: #f56c6c;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.package-price .currency {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.package-price .amount {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.package-duration {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
300
src/views/vip/index.vue
Normal file
300
src/views/vip/index.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="会员姓名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入会员姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="会员状态" prop="vipStatus">
|
||||
<el-select
|
||||
v-model="queryParams.vipStatus"
|
||||
placeholder="请选择会员状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option label="已开通" :value="true" />
|
||||
<el-option label="未开通" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="memberList"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<el-table-column label="姓名" align="center" prop="name" />
|
||||
<el-table-column label="手机号" align="center" prop="phone" />
|
||||
<el-table-column label="VIP状态" align="center" prop="vipStatus" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.vipStatus ? 'success' : 'info'">
|
||||
{{ scope.row.vipStatus ? '已开通' : '未开通' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="地址" align="center" prop="address" />
|
||||
<el-table-column label="到期时间" align="center" prop="vipExpireDate" >
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.vipStatus" class="text-primary">{{ scope.row.vipExpireDate }}</span>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center justify-center">
|
||||
<el-button
|
||||
v-if="!scope.row.vipStatus"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="handleRecharge(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:video-play" />开通会员
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="handleRecharge(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:refresh" />续费
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.vipStatus"
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:close" />取消会员
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 续费弹窗 -->
|
||||
<RechargeDialog
|
||||
v-model:visible="rechargeDialogVisible"
|
||||
:member="currentMember"
|
||||
@success="handleRechargeSuccess"
|
||||
/>
|
||||
|
||||
<!-- 密码验证弹窗 -->
|
||||
<PasswordVerifyDialog
|
||||
v-model:visible="passwordVerifyVisible"
|
||||
@success="handlePasswordVerifySuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import RechargeDialog from './components/RechargeDialog.vue'
|
||||
import PasswordVerifyDialog from './components/PasswordVerifyDialog.vue'
|
||||
|
||||
// 会员列表数据
|
||||
const memberList = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
vipStatus: undefined
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
|
||||
// 对话框控制
|
||||
const rechargeDialogVisible = ref(false)
|
||||
const passwordVerifyVisible = ref(false)
|
||||
|
||||
// 当前选中的会员
|
||||
const currentMember = ref(null)
|
||||
|
||||
// 模拟测试数据
|
||||
const mockMembers = [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
phone: '13800138000',
|
||||
vipStatus: true,
|
||||
vipExpireDate: '2024-12-31'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
phone: '13800138001',
|
||||
vipStatus: false,
|
||||
vipExpireDate: ''
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
phone: '13800138002',
|
||||
vipStatus: true,
|
||||
vipExpireDate: '2024-06-30'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取会员列表
|
||||
const getList = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
// 模拟搜索过滤
|
||||
let filteredList = [...mockMembers]
|
||||
if (queryParams.name) {
|
||||
filteredList = filteredList.filter(m => m.name.includes(queryParams.name))
|
||||
}
|
||||
if (queryParams.vipStatus !== undefined) {
|
||||
filteredList = filteredList.filter(m => m.vipStatus === queryParams.vipStatus)
|
||||
}
|
||||
|
||||
// 模拟分页
|
||||
const start = (queryParams.pageNo - 1) * queryParams.pageSize
|
||||
const end = start + queryParams.pageSize
|
||||
memberList.value = filteredList.slice(start, end)
|
||||
total.value = filteredList.length
|
||||
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 搜索按钮操作
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置按钮操作
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 续费/开通会员
|
||||
const handleRecharge = (member) => {
|
||||
currentMember.value = member
|
||||
rechargeDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 续费成功
|
||||
const handleRechargeSuccess = (data) => {
|
||||
const member = mockMembers.find(m => m.id === data.memberId)
|
||||
if (member) {
|
||||
member.vipStatus = true
|
||||
member.vipExpireDate = data.expireDate
|
||||
getList()
|
||||
ElMessage.success('操作成功')
|
||||
}
|
||||
}
|
||||
|
||||
// 取消会员
|
||||
const handleCancel = (member) => {
|
||||
currentMember.value = member
|
||||
passwordVerifyVisible.value = true
|
||||
}
|
||||
|
||||
// 密码验证成功
|
||||
const handlePasswordVerifySuccess = (password) => {
|
||||
// TODO: 这里需要调用后端API验证密码并取消会员
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
const index = mockMembers.findIndex(m => m.id === currentMember.value.id)
|
||||
if (index !== -1) {
|
||||
mockMembers[index] = {
|
||||
...currentMember.value,
|
||||
vipStatus: false,
|
||||
vipExpireDate: ''
|
||||
}
|
||||
getList()
|
||||
ElMessage.success('已取消会员资格')
|
||||
// 关闭密码验证弹窗
|
||||
passwordVerifyVisible.value = false
|
||||
// 清空当前选中的会员
|
||||
currentMember.value = null
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-primary {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
--el-table-border-color: var(--el-border-color-lighter);
|
||||
--el-table-border: 1px solid var(--el-table-border-color);
|
||||
--el-table-text-color: var(--el-text-color-regular);
|
||||
--el-table-header-text-color: var(--el-text-color-secondary);
|
||||
--el-table-row-hover-bg-color: var(--el-fill-color-light);
|
||||
--el-table-current-row-bg-color: var(--el-color-primary-light-9);
|
||||
--el-table-header-height: 50px;
|
||||
--el-table-row-height: 60px;
|
||||
}
|
||||
|
||||
:deep(.el-table__cell) {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-button--link) {
|
||||
padding: 0 6px;
|
||||
height: 26px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-button--link .el-icon) {
|
||||
margin-right: 2px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
90
src/views/vip/iphone/components/VisitDetail.vue
Normal file
90
src/views/vip/iphone/components/VisitDetail.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="回访详情"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="会员姓名">{{ visit?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ visit?.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="回访状态">
|
||||
<el-tag :type="visit?.visitStatus ? 'success' : 'warning'">
|
||||
{{ visit?.visitStatus ? '已回访' : '未回访' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回访时间">{{ visit?.visitTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="回访内容">{{ visit?.content || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="回访结果">
|
||||
<el-tag :type="getResultType(visit?.result)">
|
||||
{{ visit?.result || '-' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下次回访">{{ visit?.nextVisitTime || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
visit: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 获取回访结果类型
|
||||
const getResultType = (result) => {
|
||||
const types = {
|
||||
'满意': 'success',
|
||||
'一般': 'warning',
|
||||
'不满意': 'danger'
|
||||
}
|
||||
return types[result] || 'info'
|
||||
}
|
||||
|
||||
// 监听visible变化
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
dialogVisible.value = val
|
||||
}
|
||||
)
|
||||
|
||||
// 监听dialogVisible变化
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(val) => {
|
||||
emit('update:visible', val)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
169
src/views/vip/iphone/components/VisitDialog.vue
Normal file
169
src/views/vip/iphone/components/VisitDialog.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="visit?.visitStatus ? '编辑回访记录' : '新增回访记录'"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="会员姓名" prop="name">
|
||||
<el-input v-model="formData.name" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="formData.phone" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="回访时间" prop="visitTime">
|
||||
<el-date-picker
|
||||
v-model="formData.visitTime"
|
||||
type="datetime"
|
||||
placeholder="选择回访时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访内容" prop="content">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入回访内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访结果" prop="result">
|
||||
<el-select v-model="formData.result" placeholder="请选择回访结果" style="width: 100%">
|
||||
<el-option label="满意" value="满意" />
|
||||
<el-option label="一般" value="一般" />
|
||||
<el-option label="不满意" value="不满意" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="下次回访" prop="nextVisitTime">
|
||||
<el-date-picker
|
||||
v-model="formData.nextVisitTime"
|
||||
type="date"
|
||||
placeholder="选择下次回访时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {Icon} from '@/components/Icon'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
visit: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: undefined,
|
||||
name: '',
|
||||
phone: '',
|
||||
visitTime: '',
|
||||
content: '',
|
||||
result: '',
|
||||
nextVisitTime: ''
|
||||
})
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
visitTime: [{ required: true, message: '请选择回访时间', trigger: 'change' }],
|
||||
content: [{ required: true, message: '请输入回访内容', trigger: 'blur' }],
|
||||
result: [{ required: true, message: '请选择回访结果', trigger: 'change' }],
|
||||
nextVisitTime: [{ required: true, message: '请选择下次回访时间', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
Object.assign(formData, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
phone: '',
|
||||
visitTime: '',
|
||||
content: '',
|
||||
result: '',
|
||||
nextVisitTime: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 监听visible变化
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
dialogVisible.value = val
|
||||
}
|
||||
)
|
||||
|
||||
// 监听dialogVisible变化
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(val) => {
|
||||
emit('update:visible', val)
|
||||
}
|
||||
)
|
||||
|
||||
// 监听visit变化
|
||||
watch(
|
||||
() => props.visit,
|
||||
(val) => {
|
||||
if (val) {
|
||||
Object.assign(formData, val)
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
// 模拟提交
|
||||
setTimeout(() => {
|
||||
emit('success', {
|
||||
...formData,
|
||||
visitStatus: true
|
||||
})
|
||||
dialogVisible.value = false
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
297
src/views/vip/iphone/index.vue
Normal file
297
src/views/vip/iphone/index.vue
Normal file
@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="会员姓名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入会员姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访状态" prop="visitStatus">
|
||||
<el-select
|
||||
v-model="queryParams.visitStatus"
|
||||
placeholder="请选择回访状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option label="已回访" :value="true" />
|
||||
<el-option label="未回访" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访结果" prop="result">
|
||||
<el-select
|
||||
v-model="queryParams.result"
|
||||
placeholder="请选择回访结果"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option label="满意" value="满意" />
|
||||
<el-option label="一般" value="一般" />
|
||||
<el-option label="不满意" value="不满意" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="visitList"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<el-table-column label="姓名" align="center" prop="name" width="160" />
|
||||
<el-table-column label="手机号" align="center" prop="phone" />
|
||||
<el-table-column label="回访状态" align="center" prop="visitStatus" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.visitStatus ? 'success' : 'warning'">
|
||||
{{ scope.row.visitStatus ? '已回访' : '未回访' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回访时间" align="center" prop="visitTime" />
|
||||
<el-table-column label="回访结果" align="center" prop="result" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="getResultType(scope.row.result)">
|
||||
{{ scope.row.result || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下次回访" align="center" prop="nextVisitTime" />
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center justify-center">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="default"
|
||||
@click="handleVisit(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" />{{ scope.row.visitStatus ? '编辑' : '回访' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.visitStatus"
|
||||
type="info"
|
||||
link
|
||||
size="default"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" />详情
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 回访弹窗 -->
|
||||
<VisitDialog
|
||||
v-model:visible="visitDialogVisible"
|
||||
:visit="currentVisit"
|
||||
@success="handleVisitSuccess"
|
||||
/>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<VisitDetail
|
||||
v-model:visible="detailDialogVisible"
|
||||
:visit="currentVisit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import VisitDialog from './components/VisitDialog.vue'
|
||||
import VisitDetail from './components/VisitDetail.vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
// 回访列表数据
|
||||
const visitList = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
visitStatus: undefined,
|
||||
result: undefined
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
|
||||
// 对话框控制
|
||||
const visitDialogVisible = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
|
||||
// 当前选中的回访记录
|
||||
const currentVisit = ref(null)
|
||||
|
||||
// 获取回访结果类型
|
||||
const getResultType = (result) => {
|
||||
const types = {
|
||||
'满意': 'success',
|
||||
'一般': 'warning',
|
||||
'不满意': 'danger'
|
||||
}
|
||||
return types[result] || 'info'
|
||||
}
|
||||
|
||||
// 模拟测试数据
|
||||
const mockVisits = [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
phone: '13800138000',
|
||||
visitStatus: true,
|
||||
visitTime: '2024-03-20 14:30:00',
|
||||
content: '客户对产品使用体验非常满意,建议增加更多功能',
|
||||
result: '满意',
|
||||
nextVisitTime: '2024-04-20'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
phone: '13800138001',
|
||||
visitStatus: false,
|
||||
visitTime: '',
|
||||
content: '',
|
||||
result: '',
|
||||
nextVisitTime: ''
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
phone: '13800138002',
|
||||
visitStatus: true,
|
||||
visitTime: '2024-03-19 10:00:00',
|
||||
content: '客户反映产品偶尔会出现卡顿现象',
|
||||
result: '一般',
|
||||
nextVisitTime: '2024-04-19'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取回访列表
|
||||
const getList = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
// 模拟搜索过滤
|
||||
let filteredList = [...mockVisits]
|
||||
if (queryParams.name) {
|
||||
filteredList = filteredList.filter(m => m.name.includes(queryParams.name))
|
||||
}
|
||||
if (queryParams.visitStatus !== undefined) {
|
||||
filteredList = filteredList.filter(m => m.visitStatus === queryParams.visitStatus)
|
||||
}
|
||||
if (queryParams.result) {
|
||||
filteredList = filteredList.filter(m => m.result === queryParams.result)
|
||||
}
|
||||
|
||||
// 模拟分页
|
||||
const start = (queryParams.pageNo - 1) * queryParams.pageSize
|
||||
const end = start + queryParams.pageSize
|
||||
visitList.value = filteredList.slice(start, end)
|
||||
total.value = filteredList.length
|
||||
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 搜索按钮操作
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置按钮操作
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 回访/编辑
|
||||
const handleVisit = (visit) => {
|
||||
currentVisit.value = visit
|
||||
visitDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleDetail = (visit) => {
|
||||
currentVisit.value = visit
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 回访成功
|
||||
const handleVisitSuccess = (data) => {
|
||||
const index = mockVisits.findIndex(m => m.id === data.id)
|
||||
if (index !== -1) {
|
||||
mockVisits[index] = data
|
||||
getList()
|
||||
ElMessage.success('操作成功')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-table) {
|
||||
--el-table-border-color: var(--el-border-color-lighter);
|
||||
--el-table-border: 1px solid var(--el-table-border-color);
|
||||
--el-table-text-color: var(--el-text-color-regular);
|
||||
--el-table-header-text-color: var(--el-text-color-secondary);
|
||||
--el-table-row-hover-bg-color: var(--el-fill-color-light);
|
||||
--el-table-current-row-bg-color: var(--el-color-primary-light-9);
|
||||
--el-table-header-height: 50px;
|
||||
--el-table-row-height: 60px;
|
||||
}
|
||||
|
||||
:deep(.el-table__cell) {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-button--link) {
|
||||
padding: 0 6px;
|
||||
height: 26px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-button--link .el-icon) {
|
||||
margin-right: 2px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user