提交 bf829ccd authored 作者: 王鹏飞's avatar 王鹏飞

chore: add wmpc

上级 336b3c12
......@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="https://zws-imgs-pub.ezijing.com/pc/base/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PRP私享星球管理系统</title>
<title>WMPC管理系统</title>
<script src="https://webapp-pub.ezijing.com/plugins/tinymce@6/tinymce.min.js"></script>
</head>
<body>
......
差异被折叠。
......@@ -19,15 +19,17 @@
"axios": "^1.7.4",
"blueimp-md5": "^2.19.0",
"element-plus": "^2.8.0",
"pinia": "^2.2.1",
"file-saver": "^2.0.5",
"pinia": "^2.2.2",
"qs": "^6.13.0",
"vue": "^3.4.37",
"vue": "^3.4.38",
"vue-router": "^4.4.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@tsconfig/node18": "^18.2.4",
"@types/blueimp-md5": "^2.18.2",
"@types/file-saver": "^2.0.7",
"@types/node": "^18.19.44",
"@types/qs": "^6.9.15",
"@vitejs/plugin-vue": "^4.6.2",
......
差异被折叠。
......@@ -8,26 +8,36 @@ interface IRemoteProps {
callback?: any
}
const props = withDefaults(
defineProps<{
remote?: IRemoteProps
filters?: any[]
moreFilters?: any[]
columns: any[]
data?: any[]
hasPagination?: boolean
limit?: number
}>(),
{ hasPagination: true, limit: 10 }
)
interface Props {
remote?: IRemoteProps
filters?: any[]
filterForm?: any
columns?: any[]
data?: any[]
hasPagination?: boolean
limit?: number
}
const props = withDefaults(defineProps<Props>(), {
hasPagination: true,
limit: 10,
filters: () => [],
columns: () => [],
data: () => []
})
const filterFormRef = ref()
const loading = ref(false)
const tableRef = ref()
const dataList = ref([])
const dataList = ref<any[]>([])
const page = reactive({ total: 0, size: props.limit, currentPage: 1 })
const params = reactive({ page: page.currentPage, page_size: page.size, ...props.remote?.params })
const params = reactive({ ...props.remote?.params })
watchEffect(() => {
Object.assign(params, props.remote?.params)
})
watchEffect(() => {
dataList.value = props.data || []
})
// 获取数据
const fetchList = (isReset = false) => {
......@@ -51,31 +61,33 @@ const fetchList = (isReset = false) => {
if (beforeRequest) {
requestParams = beforeRequest(requestParams, isReset)
}
// for (const key in params) {
// if (params[key] === '' || params[key] === undefined || params[key] === undefined) {
// delete params[key]
// }
// }
for (const key in params) {
if (params[key] === '' || params[key] === undefined || params[key] === undefined) {
delete params[key]
}
}
loading.value = true
httpRequest(requestParams)
.then((res: any) => {
const { list = [], total = 0 } = callback ? callback(res.data) : res.data || {}
page.total = total
dataList.value = list
})
.catch(() => {
page.total = 0
dataList.value = []
})
.finally(() => {
loading.value = false
})
return (
httpRequest(requestParams)
.then((res: any) => {
const { list = [], total = 0 } = callback ? callback(res.data, requestParams) : res.data || {}
page.total = parseInt(total)
dataList.value = list
})
// .catch(() => {
// page.total = 0
// dataList.value = []
// })
.finally(() => {
loading.value = false
})
)
}
// 搜索
const search = () => {
page.currentPage = 1
fetchList()
return fetchList()
}
// 重置
......@@ -85,12 +97,12 @@ const reset = () => {
// 初始化页码
page.currentPage = 1
// 刷新列表
fetchList(true)
return fetchList(true)
}
// 刷新
const refetch = (isForce = false) => {
isForce ? reset() : fetchList()
return isForce ? reset() : fetchList()
}
// 页数改变
......@@ -104,7 +116,7 @@ onMounted(() => {
fetchList()
})
defineExpose({ refetch })
defineExpose({ refetch, tableRef })
</script>
<template>
......@@ -112,7 +124,7 @@ defineExpose({ refetch })
<div class="table-list-hd">
<!-- 筛选 -->
<div class="table-list-filter" v-if="filters && filters.length">
<el-form :inline="true" :model="params" ref="filterFormRef" @submit.prevent>
<el-form :inline="true" :model="params" v-bind="filterForm" ref="filterFormRef" @submit.prevent>
<template v-for="item in filters" :key="item.prop">
<el-form-item :label="item.label" :prop="item.prop">
<template v-if="item.slots">
......@@ -120,23 +132,13 @@ defineExpose({ refetch })
</template>
<template v-else>
<!-- input -->
<el-input
v-model="params[item.prop]"
v-bind="item"
clearable
@change="search"
v-if="item.type === 'input'" />
<el-input v-model="params[item.prop]" v-bind="item" clearable @change="search" v-if="item.type === 'input'" />
<!-- select -->
<el-select
v-model="params[item.prop]"
v-bind="item"
clearable
@change="search"
v-if="item.type === 'select'">
<el-select v-model="params[item.prop]" v-bind="item" filterable clearable @change="search" v-if="item.type === 'select'">
<el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
v-for="(option, index) in item.options"
:label="option[item.labelKey] || option.label || option"
:value="option[item.valueKey] || option.value || option"
:key="index" />
</el-select>
</template>
......@@ -150,12 +152,13 @@ defineExpose({ refetch })
</div>
<div class="table-list-hd-aside"><slot name="header-aside" /></div>
</div>
<div class="table-list-buttons"><slot name="header-buttons"></slot></div>
<slot></slot>
<!-- 主体 -->
<div class="table-list-bd">
<slot name="body" v-bind="{ data: dataList }">
<el-table :data="dataList" v-loading="loading" v-bind="$attrs" style="height: 100%" ref="tableRef">
<el-table-column v-bind="item" v-for="item in columns" :key="item.prop">
<el-table :data="dataList" v-loading="loading" v-bind="$attrs" ref="tableRef">
<el-table-column align="center" v-bind="item || {}" v-for="item in columns" :key="item.prop">
<template #default="scope" v-if="item.slots || item.computed">
<slot :name="item.slots" v-bind="scope" v-if="item.slots"></slot>
<div v-html="item.computed(scope)" v-if="item.computed"></div>
......@@ -173,7 +176,7 @@ defineExpose({ refetch })
class="table-list-pagination"
background
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[10, 20, 30, 50, 100]"
:page-sizes="[10, 20, 30, 50]"
:page-size="page.size"
:total="page.total"
v-model:currentPage="page.currentPage"
......@@ -187,22 +190,23 @@ defineExpose({ refetch })
</template>
<style lang="scss">
.table-list {
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.table-list-hd {
display: flex;
margin-bottom: 10px;
margin-bottom: 20px;
&:empty {
display: none;
}
}
.table-list-filter {
flex: 1;
}
.table-list-bd {
flex: 1;
.table-list-buttons {
margin-bottom: 20px;
&:empty {
display: none;
}
}
.table-list-ft {
padding: 10px 0;
display: flex;
......
......@@ -11,13 +11,13 @@ import {
School,
Picture,
Files,
VideoCamera,
Notebook,
Goods,
DishDot,
QuestionFilled,
Stamp,
Calendar
// VideoCamera,
Notebook
// Goods
// DishDot,
// QuestionFilled,
// Stamp,
// Calendar
} from '@element-plus/icons-vue'
const route = useRoute()
interface IMenuItem {
......@@ -31,14 +31,14 @@ const menuList: IMenuItem[] = [
{ name: '学员管理', path: '/student', icon: User },
{ name: '导师管理', path: '/lecturer', icon: School },
{ name: '广告管理', path: '/banner', icon: Picture },
{ name: '资料管理', path: '/doc', icon: Files },
{ name: '视频管理', path: '/video', icon: VideoCamera },
{ name: '课程管理', path: '/course', icon: Notebook },
{ name: '推荐课程', path: '/course/recommend', icon: Goods },
{ name: '团队管理', path: '/team', icon: DishDot },
{ name: '问答管理', path: '/qa', icon: QuestionFilled },
{ name: '审核管理', path: '/audit', icon: Stamp },
{ name: '知识日历', path: '/share', icon: Calendar }
{ name: '消息管理', path: '/doc', icon: Files }
// { name: '视频管理', path: '/video', icon: VideoCamera },
// { name: '推荐课程', path: '/course/recommend', icon: Goods }
// { name: '团队管理', path: '/team', icon: DishDot },
// { name: '问答管理', path: '/qa', icon: QuestionFilled },
// { name: '审核管理', path: '/audit', icon: Stamp },
// { name: '知识日历', path: '/share', icon: Calendar }
]
const defaultActive = computed(() => {
// 扁平菜单
......
......@@ -20,14 +20,14 @@ const listOptions = computed(() => {
{ label: '浏览量', prop: 'pv' },
{ label: '评论数', prop: 'comment_count' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 180, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 180 }
]
: [
{ label: 'ID', prop: 'id' },
{ label: '申请人', prop: 'user_info.name' },
{ label: '审核状态', prop: 'status_name' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 180, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 180 }
]
return {
remote: {
......
......@@ -18,7 +18,7 @@ const listOptions = {
{ label: '权重', prop: 'weight' },
{ label: '浏览量', prop: 'pv' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 160, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 160 }
]
}
......
......@@ -14,3 +14,8 @@ export function updateCourse(data: { id?: string; weight?: string; status?: stri
export function syncCourse() {
return httpRequest.get('/api/psp/backend/course/sync')
}
// 获取课程学员列表
export function getCourseStudentList(params: { course_id: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/backend-v2/course/students', { params })
}
......@@ -8,14 +8,16 @@ const props = defineProps<{ data: Record<string, any> }>()
const emit = defineEmits(['update:modelValue', 'success'])
const formRef = ref<FormInstance>()
const form = reactive<{ weight: string; status: string }>({ weight: '0', status: '2' })
const form = reactive({ weight: '0', status: '2', is_free: '0', times: '', category: '', prices: '', url: '' })
watchEffect(() => {
Object.assign(form, props.data)
})
const rules = {
weight: { required: true, message: '请输入权重', trigger: 'change' }
weight: { required: true, message: '请输入权重', trigger: 'change' },
times: { required: true, message: '请输入学时' },
category: { required: true, message: '请选择课程类型', trigger: 'change' }
}
// 提交
......@@ -31,25 +33,39 @@ function update() {
emit('success', res.data)
})
}
const categoryList = [
{ label: '财富管理基础知识', value: '1' },
{ label: '金融投资工具', value: '2' },
{ label: '财富管理务实应用', value: '3' }
]
</script>
<template>
<el-dialog
title="更新课程"
:close-on-click-modal="false"
width="400px"
@update:modelValue="$emit('update:modelValue')">
<el-form :model="form" :rules="rules" ref="formRef" label-width="60px">
<el-dialog title="更新课程" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="权重" prop="weight">
<el-input type="number" v-model="form.weight" placeholder="请输入内容" />
</el-form-item>
<el-form-item prop="status">
<el-switch
v-model="form.status"
active-text="显示"
active-value="2"
inactive-text="不显示"
inactive-value="1" />
<el-form-item label="学时" prop="times">
<el-input v-model="form.times" placeholder="请输入" />
</el-form-item>
<el-form-item label="课程类型" prop="category">
<el-radio-group v-model="form.category">
<el-radio v-for="item in categoryList" :key="item.value" :label="item.label" :value="item.value"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否显示" prop="status">
<el-switch v-model="form.status" active-text="显示" active-value="2" inactive-text="不显示" inactive-value="1" />
</el-form-item>
<el-form-item label="是否收费" prop="is_free">
<el-radio-group v-model="form.is_free">
<el-radio label="免费" value="1"></el-radio>
<el-radio label="付费" value="0"></el-radio>
</el-radio-group>
<el-input v-model="form.prices" placeholder="请输入金额" v-if="form.is_free == '0'" style="width: 200px; margin-left: 40px" />
</el-form-item>
<el-form-item label="跳转链接" prop="url">
<el-input v-model="form.url" placeholder="请输入" />
</el-form-item>
</el-form>
<template #footer>
......
<script setup lang="ts">
import { getCourseStudentList } from '../api'
const props = defineProps<{ data: Record<string, any> }>()
const listOptions = {
remote: {
httpRequest: getCourseStudentList,
params: { course_id: props.data.id },
callback(res: any) {
console.log(res)
return { list: res, total: res.length }
}
},
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '学员姓名', prop: 'name' },
{ label: '学员ID', prop: 'id' },
{ label: '最近一次学习时间', prop: 'last_time' },
{ label: '最近一次学习章节', prop: 'last_chapter' },
{ label: '学习进度', prop: 'percent' },
{ label: '成绩', prop: 'score' }
]
}
</script>
<template>
<el-dialog title="课程学员" :close-on-click-modal="false" width="800px" @update:modelValue="$emit('update:modelValue')">
<el-row justify="space-between" style="margin: 10px">
<div>课程名称:{{ data.course_name }}</div>
<div>课程类型:{{ data.category_name }}</div>
</el-row>
<AppList v-bind="listOptions" ref="appList"></AppList>
</el-dialog>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import DialogForm from '../components/DialogForm.vue'
import DialogStudent from '../components/DialogStudent.vue'
import { getCourseList, syncCourse, updateCourse } from '../api'
const appList = ref()
......@@ -15,10 +16,17 @@ const listOptions = {
{ label: '课程图片', slots: 'table-picture', width: 224 },
{ label: 'ID', prop: 'id', width: 200 },
{ label: '课程名称', prop: 'course_name' },
{
label: '授课教师',
prop: 'course_lectures',
computed({ row }: { row: any }) {
return row.course_lectures.map((item: any) => item.lecturer_name).join(',')
}
},
{ label: '课程描述', prop: 'course_represent', slots: 'table-desc' },
{ label: '权重', prop: 'weight', width: 80, align: 'center' },
{ label: '是否显示', prop: 'status', slots: 'table-status', width: 160, align: 'center' },
{ label: '操作', slots: 'table-operate', width: 100, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 160 }
]
}
......@@ -40,14 +48,20 @@ const onSyncCourse = () => {
// 更新
const dialogVisible = ref<boolean>(false)
const editData = ref<Record<string, any>>({})
const currentRow = ref<Record<string, any>>({})
const onEdit = (row: any) => {
editData.value = row
currentRow.value = row
dialogVisible.value = true
}
const onUpdateSuccess = () => {
appList.value?.refetch()
}
const dialogStudentVisible = ref<boolean>(false)
const handleViewStudent = (row: any) => {
currentRow.value = row
dialogStudentVisible.value = true
}
</script>
<template>
......@@ -69,18 +83,14 @@ const onUpdateSuccess = () => {
<div v-html="row.course_represent" style="max-height: 100px"></div>
</template>
<template #table-status="{ row }">
<el-switch
:model-value="row.status"
active-text="显示"
active-value="2"
inactive-text="不显示"
inactive-value="1"
@change="onChangeStatus(row)" />
<el-switch :model-value="row.status" active-text="显示" active-value="2" inactive-text="不显示" inactive-value="1" @change="onChangeStatus(row)" />
</template>
<template #table-operate="{ row }">
<el-button @click="onEdit(row)">更新</el-button>
<el-button @click="handleViewStudent(row)">学员</el-button>
</template>
</AppList>
</AppCard>
<DialogForm v-model="dialogVisible" :data="editData" @success="onUpdateSuccess"></DialogForm>
<DialogForm v-model="dialogVisible" :data="currentRow" @success="onUpdateSuccess"></DialogForm>
<DialogStudent v-model="dialogStudentVisible" :data="currentRow" v-if="dialogStudentVisible"></DialogStudent>
</template>
......@@ -18,7 +18,7 @@ const listOptions = {
{ label: '浏览量', prop: 'pv' },
{ label: '权重', prop: 'weight' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 160, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 160 }
]
}
......
......@@ -16,15 +16,15 @@ const listOptions = {
{ label: '类型', prop: 'type_name' },
{ label: '浏览量', prop: 'pv' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 160, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 160 }
]
}
const typeList = [
{ label: '全部', value: '' },
{ label: '入学指南', value: '1' },
{ label: '学习地图', value: '2' },
{ label: '考试攻略', value: '3' },
// { label: '入学指南', value: '1' },
// { label: '学习地图', value: '2' },
// { label: '考试攻略', value: '3' },
{ label: '消息', value: '4' },
{ label: '公告', value: '5' }
]
......
......@@ -17,9 +17,9 @@ const rules = {
desc: [{ required: true, message: '请输入正文', trigger: 'blur' }]
}
const typeList = [
{ label: '入学指南', value: '1' },
{ label: '学习地图', value: '2' },
{ label: '考试攻略', value: '3' },
// { label: '入学指南', value: '1' },
// { label: '学习地图', value: '2' },
// { label: '考试攻略', value: '3' },
{ label: '消息', value: '4' },
{ label: '公告', value: '5' }
]
......
......@@ -28,7 +28,7 @@ const listOptions = {
{ label: '类型', prop: 'type_name' },
{ label: '权重', prop: 'weight' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 160, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 160 }
]
}
......
......@@ -20,7 +20,7 @@ const listOptions = {
{ label: '发布时间', prop: 'created_time' },
{ label: '回答数量', prop: 'comment_count' },
{ label: '浏览量', prop: 'pv' },
{ label: '操作', slots: 'table-operate', align: 'right' }
{ label: '操作', slots: 'table-operate' }
]
}
const onRemove = (row: any) => {
......
......@@ -14,7 +14,7 @@ const listOptions = {
{ label: '标题', prop: 'title' },
{ label: '权重', prop: 'weight' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 160, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 160 }
]
}
......@@ -43,8 +43,7 @@ const onRemove = (row: any) => {
preview-teleported
lazy
fit="cover"
style="width: 200px; height: 100px"
/>
style="width: 200px; height: 100px" />
</template>
<template #table-operate="{ row }">
<el-space>
......
......@@ -10,14 +10,13 @@ export function getUser(params: { id: string }) {
return httpRequest.get('/api/psp/backend/user/view', { params })
}
// 添加学员
export function addUser(data: { name: string; mobile: string; gender: string; certificate_number: string; label?: string }) {
return httpRequest.post('/api/psp/backend-v2/user/create', data)
}
// 更新资料
export function updateUser(data: {
id: string
name: string
mobile: string
certificate_number: string
label?: string
}) {
export function updateUser(data: { id: string; name: string; mobile: string; certificate_number: string; label?: string }) {
return httpRequest.post('/api/psp/backend/user/update', data)
}
......@@ -38,5 +37,15 @@ export function syncUser() {
// 导出学员列表
export function exportUser(params?: { name?: string; mobile?: string }) {
return httpRequest.get('/api/psp/backend/user/download', { params })
return httpRequest.get('/api/psp/backend/user/download', { params, responseType: 'blob' })
}
// 学员关联课程时课程列表
export function getUserCourseList(params: { type: 'new' | 'old'; sso_id: string; category?: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/backend-v2/user/courses', { params })
}
// 学员关联课程时课程列表
export function updateUserCourse(data: { sso_id: string; course_id: string; type: 'add' | 'delete' }) {
return httpRequest.post('/api/psp/backend-v2/user/relation', data)
}
<script setup lang="ts">
import { getUserCourseList, updateUserCourse } from '../api'
import DialogCourseAdd from './DialogCourseAdd.vue'
const props = defineProps<{ data: Record<string, any> }>()
const appList = ref()
const listOptions = {
remote: { httpRequest: getUserCourseList, params: { sso_id: props.data.sso_id, type: 'old' } },
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '课程名称', prop: 'course_name' },
{ label: '课程类型', prop: 'category_name' },
{ label: '添加时间', prop: 'created_time' },
{
label: '添加方式',
prop: 'created_method',
computed() {
return '后台添加'
}
},
{ label: '学习进度', prop: 'percent' },
{ label: '操作', slots: 'table-x', width: 100 }
]
}
const dialogVisible = ref(false)
const handleRefresh = () => {
appList.value?.refetch()
}
const handleRemove = async (row: any) => {
await updateUserCourse({ sso_id: props.data.sso_id, course_id: row.id, type: 'delete' })
handleRefresh()
}
</script>
<template>
<el-dialog title="关联课程" :close-on-click-modal="false" width="800px" @update:modelValue="$emit('update:modelValue')">
<el-row justify="space-between" style="margin: 10px">
<div>姓名:{{ data.name }}</div>
<div>手机号:{{ data.mobile }}</div>
<el-button type="primary" @click="dialogVisible = true">添加课程</el-button>
</el-row>
<AppList v-bind="listOptions" ref="appList">
<template #table-x="{ row }">
<el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template>
</AppList>
<DialogCourseAdd v-model="dialogVisible" :data="data" @close="handleRefresh"></DialogCourseAdd>
</el-dialog>
</template>
<script setup lang="ts">
import { getUserCourseList, updateUserCourse } from '../api'
const props = defineProps<{ data: Record<string, any> }>()
const appList = ref()
const listOptions = {
remote: { httpRequest: getUserCourseList, params: { sso_id: props.data.sso_id, type: 'new' } },
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '课程名称', prop: 'course_name' },
{ label: '课程类型', prop: 'category_name' },
{ label: '操作', slots: 'table-x', width: 100 }
]
}
const handleAdd = async (row: any) => {
await updateUserCourse({ sso_id: props.data.sso_id, course_id: row.id, type: 'add' })
appList.value?.refetch()
}
</script>
<template>
<el-dialog title="用户添加课程" :close-on-click-modal="false" width="800px" @update:modelValue="$emit('update:modelValue')">
<el-row justify="space-evenly" style="margin: 10px">
<div>姓名:{{ data.name }}</div>
<div>手机号:{{ data.mobile }}</div>
</el-row>
<AppList v-bind="listOptions" ref="appList">
<template #table-x="{ row }">
<el-button type="primary" size="small" @click="handleAdd(row)">添加</el-button>
</template>
</AppList>
</el-dialog>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { addUser } from '../api'
const props = defineProps<{ data?: Record<string, any> }>()
const emit = defineEmits(['update:modelValue', 'success'])
const formRef = ref<FormInstance>()
const form = reactive({
name: '',
gender: '1',
mobile: '',
certificate_number: '',
star: '',
label: []
})
watchEffect(() => {
Object.assign(form, props.data)
})
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }]
}
// 提交
function onSubmit() {
if (!formRef.value) return
formRef.value.validate().then(update)
}
// 更新课程
function update() {
const params = { ...form, label: form.label.join(',') }
addUser(params).then(res => {
ElMessage.success('更新成功')
emit('update:modelValue', false)
emit('success', res.data)
})
}
</script>
<template>
<el-dialog title="添加学员" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio value="1" label="男"></el-radio>
<el-radio value="2" label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入" />
</el-form-item>
<el-form-item label="证书编号" prop="certificate_number">
<el-input v-model="form.certificate_number" placeholder="请输入" />
</el-form-item>
<el-form-item label="标签" prop="title">
<el-select v-model="form.label" multiple>
<el-option value="持证人"></el-option>
<el-option value="导师"></el-option>
</el-select>
</el-form-item>
<el-form-item label="星星数量" prop="star">
<el-input v-model="form.star" disabled />
</el-form-item>
</el-form>
<template #footer>
<el-button text @click="$emit('update:modelValue', false)">取消</el-button>
<el-button type="primary" @click="onSubmit">保存</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { saveAs } from 'file-saver'
import { ElMessage } from 'element-plus'
import { getUserList, syncUser } from '../api'
import qs from 'qs'
import { getUserList, syncUser, exportUser } from '../api'
// import qs from 'qs'
import DialogForm from '../components/DialogForm.vue'
import DialogCourse from '../components/DialogCourse.vue'
const listParams = reactive({ name: '', mobile: '' })
const appList = ref()
// 列表配置
const listOptions = {
remote: {
......@@ -24,7 +30,7 @@ const listOptions = {
{ label: '证书编号', prop: 'certificate_number' },
{ label: '星星数量', prop: 'star' },
{ label: '标签', prop: 'label', slots: 'table-label' },
{ label: '操作', slots: 'table-operate', width: 160, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 250 }
]
}
// 同步
......@@ -34,8 +40,26 @@ const onSyncUser = () => {
})
}
// 导出
function handleExport() {
window.open('/api/psp/backend/user/download?' + qs.stringify(listParams))
async function handleExport() {
const res: unknown = await exportUser(listParams)
saveAs(res as Blob, '学员列表.xlsx')
// window.open('/api/psp/backend/user/download?' + qs.stringify(listParams))
}
const currentRow = ref<any>()
// 更新
const dialogVisible = ref<boolean>(false)
const handleAdd = () => {
dialogVisible.value = true
}
const handleSuccess = () => {
appList.value?.refetch()
}
const dialogCourseVisible = ref<boolean>(false)
const handleCourse = (row: any) => {
currentRow.value = row
dialogCourseVisible.value = true
}
</script>
......@@ -43,7 +67,8 @@ function handleExport() {
<AppCard>
<AppList v-bind="listOptions" ref="appList">
<template #header-aside>
<el-button type="primary" @click="onSyncUser">同步</el-button>
<el-button type="primary" @click="onSyncUser" v-if="false">同步</el-button>
<el-button type="primary" @click="handleAdd">添加学员</el-button>
<el-button type="primary" @click="handleExport">导出</el-button>
</template>
<template #table-label="{ row }">
......@@ -51,6 +76,7 @@ function handleExport() {
</template>
<template #table-operate="{ row }">
<el-space>
<el-button plain @click="handleCourse(row)">关联课程</el-button>
<router-link :to="`/student/update/${row.id}`"><el-button plain>编辑</el-button></router-link>
<router-link :to="`/student/view/${row.id}`" target="_blank">
<el-button type="primary" plain>查看</el-button>
......@@ -58,5 +84,7 @@ function handleExport() {
</el-space>
</template>
</AppList>
<DialogForm v-model="dialogVisible" @success="handleSuccess" v-if="dialogVisible"></DialogForm>
<DialogCourse v-model="dialogCourseVisible" :data="currentRow" v-if="dialogCourseVisible"></DialogCourse>
</AppCard>
</template>
......@@ -25,7 +25,7 @@ const listOptions = {
{ label: '发布时间', prop: 'created_time' },
{ label: '回答数量', prop: 'comment_count' },
{ label: '浏览量', prop: 'pv' },
{ label: '操作', slots: 'table-operate', align: 'right' }
{ label: '操作', slots: 'table-operate' }
]
}
......
......@@ -23,7 +23,7 @@ const listOptions = {
{ label: '头像', prop: 'user_info.avatar', slots: 'table-logo', width: 74 },
{ label: '姓名', prop: 'user_info.name' },
{ label: '手机号', prop: 'user_info.mobile' },
{ label: '操作', slots: 'table-operate', width: 180, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 180 }
]
}
......
......@@ -25,7 +25,7 @@ const listOptions = {
{ label: '发布时间', prop: 'created_time' },
{ label: '回答数量', prop: 'comment_count' },
{ label: '浏览量', prop: 'pv' },
{ label: '操作', slots: 'table-operate', align: 'right' }
{ label: '操作', slots: 'table-operate' }
]
}
......
......@@ -34,7 +34,7 @@ const listOptions = {
{ label: '资料数量', prop: 'files_count' },
{ label: '问答数量', prop: 'questions_count' },
{ label: '审核状态', prop: 'status_name' },
{ label: '操作', slots: 'table-operate', width: 180, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 180 }
]
}
// 审核
......@@ -50,7 +50,13 @@ const onAudit = (row: any, status: 0 | 1) => {
<AppCard>
<AppList v-bind="listOptions" ref="appList">
<template #table-logo="{ row }">
<el-image :src="row.logo + '?x-oss-process=image/resize,m_fill,h_100,w_200'" :preview-src-list="[row.logo]" preview-teleported lazy fit="cover" style="width: 200px; height: 100px" />
<el-image
:src="row.logo + '?x-oss-process=image/resize,m_fill,h_100,w_200'"
:preview-src-list="[row.logo]"
preview-teleported
lazy
fit="cover"
style="width: 200px; height: 100px" />
</template>
<template #table-operate="{ row }">
<el-space>
......
......@@ -17,7 +17,7 @@ const listOptions = {
{ label: '类型', prop: 'type_name' },
{ label: '浏览量', prop: 'pv' },
{ label: '创建时间', prop: 'created_time' },
{ label: '操作', slots: 'table-operate', width: 230, align: 'right' }
{ label: '操作', slots: 'table-operate', width: 230 }
]
}
const typeList = [
......
......@@ -7,6 +7,7 @@ const httpRequest = axios.create({
timeout: 60000,
withCredentials: true,
headers: {
tenant: 'wmpc',
'Content-Type': 'application/x-www-form-urlencoded'
}
})
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论