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

chore: add wmpc

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