@@ -1,9 +1,67 @@
export const RedirectName = ''
export const asyncRoutes = {
- ErrorPage: () => import('@/views/exception/404.vue'),
- Layout: () => import('@/layout/index.vue'),
- ParentLayout: () => import('@/layout/parentLayout.vue'),
- // setMenu: () => import('@/views/menu-manage/index'),
- homeData: () => import('@/views/test/index'),
- menuManage: () => import('@/views/menu-manage/index') // 菜单管理
+ ErrorPage: () => import('@/views/exception/404.vue'),
+ Layout: () => import('@/layout/index.vue'),
+ ParentLayout: () => import('@/layout/parentLayout.vue'),
+ // setMenu: () => import('@/views/menu-manage/index'),
+ home: () => import('@/views/test/index'),
+ loginLog: () => import('@/views/login-log/index'), // 登录日志
+ interfaceLog: () => import('@/views/login-log/interface-log'), // 操作日志
+ cityManage: () => import('@/views/city-manage/index'), // 城市管理
+ menuManage: () => import('@/views/system-manage/menu-manage/index'), // 菜单管理
+ systemApply: () => import('@/views/system-manage/system-apply/index'), // 系统应用
+ roleManage: () => import('@/views/system-manage/role-mange/index'), // 角色管理
+ stationManage: () => import('@/views/system-manage/station-manage/index'), // 岗位管理
+ employeeManage: () => import('@/views/system-manage/employee-manage/employee-tab'), // 员工管理
+ protocolManage: () => import('@/views/system-manage/protocol-manage/index'), // 协议管理
+ paramSettings: () => import('@/views/system-manage/param-settings/index'), // 参数设置
+ songManage: () => import('@/views/system-manage/song-manage/index'), // 声部管理
+ songSetting: () => import('@/views/system-manage/song-setting/index'), // 声部基础配置
+ appVersion: () => import('@/views/system-manage/app-version/index'), // app版本控制
+ systemHoliday: () => import('@/views/system-manage/system-holiday/index'), // 节假日设置
+ platformSuggestion: () => import('@/views/system-manage/platform-suggestion/index'), // 平台建议
+ // schoolManage: () => import('@/views/school-manage/index'), // 学校管理
+ // schoolOperation: () => import('@/views/school-manage/component/school-operation'), // 添加学校
+ // schoolDetail: () => import('@/views/school-manage/school-detail/index'), // 学校详情
+ contentFlash: () => import('@/views/content-manage/content-flash/index'), // 闪页管理
+ contentNotice: () => import('@/views/content-manage/content-notice/index'), // 公告管理
+ contentInformation: () => import('@/views/content-manage/content-information/index'), // 资讯管理
+ contentAppButton: () => import('@/views/content-manage/content-app-button/index'), // app 按钮管理
+ contentAd: () => import('@/views/content-manage/content-ad/index'), // 广告管理
+ helpCenter: () => import('@/views/content-manage/help-center/index'), // 帮助中心
+ musicManage: () => import('@/views/content-manage/music-manage/index'), // 曲谱管理
+ // musicgroupManage: () => import('@/views/music-group-manage/index'), // 乐团列表
+ // singleMusicGroupManage: () => import('@/views/music-group-manage/single-index'), // 乐团列表 - single
+ // studyGuidanceManage: () => import('@/views/study-guidance-manage/index'), // 伴学老师管理
+ // studyGuidanceManage: () => import('@/views/study-guidance-manage/index-catagory'), // 伴学老师管理
+ // studyGuidanceDetail: () => import('@/views/study-guidance-manage/study-guidance-detail'), // 伴学老师详情
+ // studyGuidanceStatistics: () => import('@/views/study-guidance-manage/statistics'), //
+ // teacherEvaluationRecord: () => import('@/views/study-guidance-manage/teacher-evaluation-record'), // 伴学老师评测详情
+ // teacherRecordInfo: () => import('@/views/study-guidance-manage/teacher-evaluation-record-info'),
+ // createGroup: () => import('@/views/music-group-manage/createGroup'),
+ educationalManage: () => import('@/views/educational-manage/index'), // 素材管理
+ knowledgeManage: () => import('@/views/knowledge-manage/knowledge-list/index'), //知识点管理
+ knowledgeDetail: () => import('@/views/knowledge-manage/knowledge-detail'), //知识点详情
+ afterClassTrainingManage: () => import('@/views/teaching-manage/after-class-training/index'), //课后训练
+ afterClassTrainingDetail: () => import('@/views/teaching-manage/after-class-training-detail/index'), //课后训练详情
+ // groupDetail: () => import('@/views/music-group-manage/groupDetail'), // 乐团详情
+ coursewareManage: () => import('@/views/teaching-manage/courseware-manage/index'), // 课件列表
+ teachingPlan: () => import('@/views/teaching-manage/teaching-plan/index'), // 教学计划列表
+ coursewareDetail: () => import('@/views/teaching-manage/courseware-manage/detail'), // 课件创建和配置
+ planDetail: () => import('@/views/teaching-manage/teaching-plan/plan-detail'), // 教学计划关联课件
+ // musicCategrory: () => import('@/views/music-categrory/index'),
+ // auditCenter: () => import('@/views/audit-center/index'), // 审核中心
+ // orderManage: () => import('@/views/order-manage/index'), // 交易管理
+ // studentEvaluationRecord: () => import('@/views/student-manage/student-evaluation-record'), // 学员管理评测详情
+ // studentManage: () => import('@/views/student-manage/index-catagory'), // 学员管理
+ // studentDetail: () => import('@/views/student-manage/student-detail'),
+ // operationManual: () => import('@/views/operation-manual/index'),
+ unitExamination: () => import('@/views/teaching-manage/unit-test/index'),
+ unitTestCreate: () => import('@/views/teaching-manage/unit-test/unit-test-index/editAndUpdate'),
+ // subsidyManage: () => import('@/views/subsidy-manage/index'),
+ // subsidyDetail: () => import('@/views/subsidy-manage/subsidyDetail'),
+ // practiceManage: () => import('@/views/practice-manage/index'),
+ deviceNumManage: () => import('@/views/system-manage/device-num-manage/index'),
+ smsCodeMessage: () => import('@/views/sms-code-message/index'), // 短信验证码
+
}
@@ -3,3 +3,230 @@ export const schoolNature = {
PRIVATE: '民办',
PUBLIC: '公办'
+// 岗位
+export const position = {
+ IM_SERVICE: 'IM客服',
+ REPAIR: '维修工',
+ STAFF: '职员'
+}
+// 系统员工状态
+export const employee = {
+ // CANCEL: '注销',
+ LOCKED: '冻结',
+ ACTIVATION: '正常'
+// 学年制
+export const schoolSystem = {
+ fiveYearSystem: '五年制',
+ sixYearSystem: '六年制'
+// 支付服务提供方
+export const openType = {
+ ORIGINAL: '原生微信支付宝',
+ ADAPAY: '汇付',
+ OTHER: '其它'
+// 客户端类型
+export const clientType = {
+ BACKEND: '后台',
+ SCHOOL: '管理端',
+ TEACHER: '老师端',
+ STUDENT: '学生端',
+ REPAIR: '维修端'
+// 协议类型
+export const protocolType = {
+ BUY_ORDER: '产品与服务协议',
+ WITHDRAW: '三方协议',
+ REGISTER: '使用协议(学生端)',
+ REGISTER_TEACHER: '使用协议(伴学端/管理端)',
+ LABOR_TEACHER: '劳务协议'
+// 消息组
+export const messageGroup = {
+ SYSTEM: '系统消息',
+ COURSE: '课程信息'
+// 平台建议
+export const suggestionType = {
+ APP: '软件反馈',
+ SMART_PRACTICE: '智能陪练'
+// 内容分类
+export const contentCategory = {
+ HOT_CONSULTATION: '热门资讯',
+ OPEN_SCREEN_AD: '开屏广告',
+ FLASH_PAGE: '闪页管理',
+ ROTATION_CHART: '轮播图管',
+ MUSIC: '乐理章节'
+export const musicType = {
+ DELIVERY: '交付团',
+ PROMOTION: '晋升团'
+// 交付团类型
+export const orchestraTypes = {
+ SINGLE_DELIVERY: '单交付团',
+ MULTIPLE_DELIVERY: '多交付常规团',
+ MULTIPLE_DELIVERY_SCHOOL: '多交付校团'
+// 乐团类型
+export const orchestraType = {
+// 乐团状态
+export const musicStatus = {
+ // SUBJECT_CONFIG: '声部配置',
+ INITIATION_SURVEY: '启蒙调查',
+ PRE_REGISTER: '家长会调查',
+ REGISTER: '乐团注册',
+ DOING: '乐团交付',
+ DONE: '进行中',
+ CLOSE: '关闭'
+// 播放类型
+export const audioType = {
+ MP3: 'MP3',
+ MIDI: 'MIDI'
+// 伴奏类型
+export const accompanimentType = {
+ HOMEMODE: '自制伴奏',
+ COMMON: '普通伴奏'
+// 课程类型
+export const courseEmnu = {
+ PERCUSSION_SINGLE: '打击乐',
+ FLUTE_SINGLE: '长笛',
+ SAX_SINGLE: '萨克斯',
+ CLARINET_SINGLE: '单簧管',
+ TRUMPET_SINGLE: '小号',
+ TROMBONE_SINGLE: '长号',
+ HORN_SINGLE: '圆号',
+ BARITONE_TUBA_SINGLE: '上低音号-大号',
+ MUSIC_THEORY: '乐理',
+ INSTRUMENTAL_ENSEMBLE: '合奏',
+ EUPHONIUM_SINGLE: '上低音号',
+ TUBA_SINGLE: '大号'
+ // TEST_CLARINET: '测试竖笛'
+// 学生状态
+export const studentStatusEmnu = {
+ REGISTER: '报名',
+ LEARNING: '在读',
+ OUTOF_ORCHESTRA: '退团'
+// 订单状态
+export const orderStatus = {
+ WAIT_PAY: '待支付',
+ PAYING: '支付中',
+ PAID: '已付款',
+ TIMEOUT: '订单超时',
+ FAIL: '支付失败',
+ CLOSED: '订单关闭',
+ REFUNDING: '退款中',
+ REFUNDED: '已退款'
+// 订单类型
+export const orderType = {
+ VIP: '开通会员',
+ ORCHESTRA: '乐团注册'
+// 交易状态
+export const reportStatus = {
+ SUCCESSED: '交易成功',
+ PENDDING: '退费中',
+ FAILED: '交易失败'
+export const tradeStatus = {
+ PENDDING: '交易中',
+// 审核状态
+export const refundStatus = {
+ PASS: '审核通过',
+ REJECT: '审核拒绝',
+ ING: '审核中',
+ CLOSED: '已关闭'
+// 课程状态
+export const courseStatus = {
+ NOT_START: '未开始',
+ ING: '进行中',
+ COMPLETE: '已结束'
+// 学生考勤状态
+export const attendanceStatus = {
+ LATE: '迟到',
+ NORMAL: '正常',
+ LEAVE: '请假',
+ TRUANCY: '旷课',
+ UNCALLED: '未点名'
+// 老师老师考勤
+export const teacherAttendanceStatus = {
+ EXCEPTION: '异常',
+ EARLY: '早退',
+ NO_SIGN: '未签到',
+ NO_SIGN_OUT: '未签退',
+ UNCALLED: '未点名',
+ LOCATION_EXCEPTION: '定位异常'
+export const authStatus = {
+ DOING: '审核中',
+ PASS: '通过',
+ UNPASS: '不通过'
+// 结算状态
+export const withdrawalStatus = {
+ WAIT: '待结算',
+ SETTLED: '已结算',
+ PART_SETTLED: '部分结算',
+ SETTLE_FAIL: '结算失败',
+ SETTLEING: '结算中'
+// 补助类型
+export const salaryType = {
+ MANAGE: '管理补助',
+ COURSE: '课程补助',
+ TRAINING: '练习补助'
+} as any
+export const heardLevelType = {
+ BEGINNER: '入门级',
+ ADVANCED: '进阶级',
+ PERFORMER: '大师级'
@@ -1,10 +1,287 @@
import * as constant from './constant'
// 岗位管理
-export const filterPosition = (key: 'PRIVATE' | 'PUBLIC' | '') => {
+export const filterPosition = (key: 'IM_SERVICE' | 'REPAIR' | 'STAFF' | '') => {
+ if (key && constant.position && constant.position[key]) {
+ return constant.position[key]
+ } else {
+ return key
+ }
+// 员工状态
+export const filterEmployee = (key: 'LOCKED' | 'ACTIVATION' | '') => {
+ if (key && constant.employee && constant.employee[key]) {
+ return constant.employee[key]
+// 办学性质
+export const filterSchoolNature = (key: 'PRIVATE' | 'PUBLIC' | '') => {
if (key && constant.schoolNature && constant.schoolNature[key]) {
return constant.schoolNature[key]
} else {
return key
+export const filterSchoolSystem = (key: 'fiveYearSystem' | 'sixYearSystem' | '') => {
+ if (key && constant.schoolSystem && constant.schoolSystem[key]) {
+ return constant.schoolSystem[key]
+export const filterOpenType = (key: 'ORIGINAL' | 'ADAPAY' | 'OTHER' | '') => {
+ if (key && constant.openType && constant.openType[key]) {
+ return constant.openType[key]
+export const filterClientType = (
+ key: 'BACKEND' | 'SCHOOL' | 'TEACHER' | 'STUDENT' | 'REPAIR' | ''
+) => {
+ if (key && constant.clientType && constant.clientType[key]) {
+ return constant.clientType[key]
+export const filterProtocolType = (key: 'BUY_ORDER' | 'WITHDRAW' | 'REGISTER' | '') => {
+ if (key && constant.protocolType && constant.protocolType[key]) {
+ return constant.protocolType[key]
+export const filterMessageGroup = (key: 'SYSTEM' | 'COURSE' | '') => {
+ if (key && constant.messageGroup && constant.messageGroup[key]) {
+ return constant.messageGroup[key]
+export const filterSuggestionType = (key: 'APP' | 'SMART_PRACTICE' | '') => {
+ if (key && constant.suggestionType && constant.suggestionType[key]) {
+ return constant.suggestionType[key]
+export const filterContentCategory = (
+ key: 'HOT_CONSULTATION' | 'OPEN_SCREEN_AD' | 'FLASH_PAGE' | 'ROTATION_CHART' | 'MUSIC' | ''
+ if (key && constant.contentCategory && constant.contentCategory[key]) {
+ return constant.contentCategory[key]
+// 交付团类型 filterGroupType
+export const filterGroupType = (
+ key: 'SINGLE_DELIVERY' | 'MULTIPLE_DELIVERY' | 'MULTIPLE_DELIVERY_SCHOOL'
+ if (key && constant.orchestraTypes && constant.orchestraTypes[key]) {
+ return constant.orchestraTypes[key]
+export const filterOrchestraType = (key: 'DELIVERY' | 'PROMOTION') => {
+ if (key && constant.orchestraType && constant.orchestraType[key]) {
+ return constant.orchestraType[key]
+// 乐团状态 SUBJECT_CONFIG
+export const filterGroupStatus = (
+ key: 'INITIATION_SURVEY' | 'PRE_REGISTER' | 'REGISTER' | 'DOING' | 'DONE' | 'CLOSE'
+ if (key && constant.musicStatus && constant.musicStatus[key]) {
+ return constant.musicStatus[key]
+export const filterAudioType = (key: 'MP3' | 'MIDI' | '') => {
+ if (key && constant.audioType && constant.audioType[key]) {
+ return constant.audioType[key]
+export const filterAccompanimentType = (key: 'HOMEMODE' | 'COMMON' | '') => {
+ if (key && constant.accompanimentType && constant.accompanimentType[key]) {
+ return constant.accompanimentType[key]
+export const filterCourseEmnu = (
+ key:
+ | 'PERCUSSION_SINGLE'
+ | 'FLUTE_SINGLE'
+ | 'SAX_SINGLE'
+ | 'CLARINET_SINGLE'
+ | 'TRUMPET_SINGLE'
+ | 'TROMBONE_SINGLE'
+ | 'HORN_SINGLE'
+ | 'BARITONE_TUBA_SINGLE'
+ | 'EUPHONIUM_SINGLE'
+ | 'TUBA_SINGLE'
+ | 'MUSIC_THEORY'
+ | 'INSTRUMENTAL_ENSEMBLE'
+ | ''
+ if (key && constant.courseEmnu && constant.courseEmnu[key]) {
+ return constant.courseEmnu[key]
+// 课程状态 courseStatus
+export const filterCourseStatus = (key: 'NOT_START' | 'ING' | 'COMPLETE') => {
+ if (key && constant.courseStatus && constant.courseStatus[key]) {
+ return constant.courseStatus[key]
+export const filterStudentStatusEmnu = (key: 'REGISTER' | 'LEARNING' | 'OUTOF_ORCHESTRA') => {
+ if (key && constant.studentStatusEmnu && constant.studentStatusEmnu[key]) {
+ return constant.studentStatusEmnu[key]
+export const filterOrderStatus = (
+ | 'WAIT_PAY'
+ | 'PAYING'
+ | 'PAID'
+ | 'TIMEOUT'
+ | 'FAIL'
+ | 'CLOSED'
+ | 'REFUNDING'
+ | 'REFUNDED'
+ if (key && constant.orderStatus && constant.orderStatus[key]) {
+ return constant.orderStatus[key]
+export const filterOrderType = (key: 'VIP' | 'ORCHESTRA' | '') => {
+ if (key && constant.orderType && constant.orderType[key]) {
+ return constant.orderType[key]
+// 审核交易状态
+export const filterReportStatus = (key: 'SUCCESSED' | 'FAILED' | 'PENDDING' | '') => {
+ if (key && constant.reportStatus && constant.reportStatus[key]) {
+ return constant.reportStatus[key]
+export const filterTradeStatus = (key: 'SUCCESSED' | 'FAILED' | 'PENDDING' | '') => {
+ if (key && constant.tradeStatus && constant.tradeStatus[key]) {
+ return constant.tradeStatus[key]
+export const filterRefundStatus = (key: 'PASS' | 'REJECT' | 'ING' | 'CLOSED' | '') => {
+ if (key && constant.refundStatus && constant.refundStatus[key]) {
+ return constant.refundStatus[key]
+// 学生考勤类型 attendanceStatus
+export const filterAttendanceStatus = (
+ key: 'LATE' | 'NORMAL' | 'LEAVE' | 'TRUANCY' | 'UNCALLED' | ''
+ if (key && constant.attendanceStatus && constant.attendanceStatus[key]) {
+ return constant.attendanceStatus[key]
+// 老师考勤类型
+export const filterTeacherAttendanceStatus = (
+ key: 'NORMAL' | 'EXCEPTION' | 'LATE' | 'EARLY' | 'TRUANCY' | 'NO_SIGN' | 'LOCATION_EXCEPTION' | ''
+ if (key && constant.teacherAttendanceStatus && constant.teacherAttendanceStatus[key]) {
+ return constant.teacherAttendanceStatus[key]
+export const filterAuthStatus = (key: 'DOING' | 'PASS' | 'UNPASS') => {
+ if (key && constant.authStatus && constant.authStatus[key]) {
+ return constant.authStatus[key]
+export const filterWithdrawalStatus = (
+ key: 'WAIT' | 'SETTLED' | 'PART_SETTLED' | 'SETTLE_FAIL'
+ if (key && constant.withdrawalStatus && constant.withdrawalStatus[key]) {
+ return constant.withdrawalStatus[key]
+// 补助类型 salaryType
+export const filterSalaryType = (key: 'MANAGE' | 'COURSE' | 'TRAINING') => {
+ if (key && constant.salaryType && constant.salaryType[key]) {
+ return constant.salaryType[key]
@@ -11,5 +11,81 @@ export function getValueForKey(obj: any) {
return arr
-/** 办学性质 */
+// 岗位管理
+export const positionArray = getValueForKey(constant.position)
+export const employeeArray = getValueForKey(constant.employee)
export const schoolNatureArray = getValueForKey(constant.schoolNature)
+export const schoolSystemArray = getValueForKey(constant.schoolSystem)
+export const openTypeArray = getValueForKey(constant.openType)
+export const clientTypeArray = getValueForKey(constant.clientType)
+export const protocolTypeArray = getValueForKey(constant.protocolType)
+export const messageGroupArray = getValueForKey(constant.messageGroup)
+export const suggestionTypeArray = getValueForKey(constant.suggestionType)
+export const contentCategoryArray = getValueForKey(constant.contentCategory)
+export const musicTypeArray = getValueForKey(constant.musicType)
+export const orchestraTypesArray = getValueForKey(constant.orchestraTypes)
+export const musicStatusArray = getValueForKey(constant.musicStatus)
+export const audioTypeArray = getValueForKey(constant.audioType)
+export const accompanimentTypeArray = getValueForKey(constant.accompanimentType)
+export const courseEmnuArray = getValueForKey(constant.courseEmnu)
+export const courseStatusArray = getValueForKey(constant.courseStatus)
+export const studentStatusEmnuArray = getValueForKey(constant.studentStatusEmnu)
+export const orderStatusArray = getValueForKey(constant.orderStatus)
+export const orderTypeArray = getValueForKey(constant.orderType)
+export const reportStatusArray = getValueForKey(constant.reportStatus)
+export const tradeStatusArray = getValueForKey(constant.tradeStatus)
+export const refundStatusArray = getValueForKey(constant.refundStatus)
+export const attendanceStatusArray = getValueForKey(constant.attendanceStatus)
+// 老师考勤状态 teacherAttendanceStatus
+export const ateacherAttendanceStatusArray = getValueForKey(constant.teacherAttendanceStatus)
+export const authStatusArray = getValueForKey(constant.authStatus)
+export const withdrawalStatusArray = getValueForKey(constant.withdrawalStatus)
@@ -0,0 +1,111 @@
+import request from '@/utils/request/index'
+/**
+ * @description: 乐团补助标准列表
+ */
+export const orchestraSubsidyStandardPage = (params: object) => {
+ return request({
+ url: '/cbs-app/orchestraSubsidyStandard/page',
+ method: 'post',
+ data: params,
+ } as any)
+ * @description: 乐团补助标准添加
+export const orchestraSubsidyStandardSave = (params: object) => {
+ url: '/cbs-app/orchestraSubsidyStandard/save',
+ * @description: 乐团补助标准修改
+export const orchestraSubsidyStandardUpdate = (params: object) => {
+ url: '/cbs-app/orchestraSubsidyStandard/update',
+ * @description: 城市收费设置列表
+export const cityFeeSettingPage = (params: object) => {
+ url: '/cbs-app/cityFeeSetting/page',
+ * @description: 城市收费设置添加
+export const cityFeeSettingSave = (params: object) => {
+ url: '/cbs-app/cityFeeSetting/save',
+ * @description: 城市收费设置修改
+export const cityFeeSettingUpdate = (params: object) => {
+ url: '/cbs-app/cityFeeSetting/update',
+ * @description: 获取区域表
+export const sysAreaPage = (params: object) => {
+ url: '/cbs-app/sysArea/page',
+ })
+ * @description: 获取区域详情
+export const sysAreaDetail = (id: number) => {
+ url: `/cbs-app//sysArea/detail/${id}`,
+ method: 'get'
+ * @description: 根据code获取区域详情
+export const sysAreaDetailCode = (id: number | string) => {
+ url: `/cbs-app/sysArea/queryByCode/${id}`,
+ method: 'get',
+ requestType: 'form'
+ * @description: 查询全部区域
+export const queryAllProvince = () => {
+ url: `/cbs-app/sysArea/queryAllProvince`,
@@ -0,0 +1,308 @@
+import SaveForm from '@/components/save-form'
+import Pagination from '@/components/pagination'
+import {
+ NButton,
+ NCascader,
+ NDataTable,
+ NDescriptions,
+ NDescriptionsItem,
+ NFormItem,
+ NInput,
+ NModal,
+ NSpace,
+ NTreeSelect
+} from 'naive-ui'
+import type { TreeSelectOption } from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import CityOperation from '../modal/city-operation'
+import { cityFeeSettingPage, sysAreaPage, sysAreaDetailCode, queryAllProvince } from '../api'
+import numeral from 'numeral'
+export default defineComponent({
+ name: 'city-list',
+ setup() {
+ const state = reactive({
+ loading: false,
+ pagination: {
+ page: 1,
+ rows: 10,
+ pageTotal: 0
+ },
+ searchForm: {
+ cityCode: ''
+ dataList: [] as any,
+ visiableCity: false,
+ cityType: 'add',
+ cityData: {} as any,
+ areaList: [] as any
+ const columns = () => {
+ return [
+ {
+ title: '城市信息',
+ key: 'id',
+ render(row: any) {
+ return (
+ <NDescriptions labelPlacement="left" column={1}>
+ <NDescriptionsItem label="城市名称">{row.cityName}</NDescriptionsItem>
+ <NDescriptionsItem label="城市编号">{row.id}</NDescriptionsItem>
+ {/* <NDescriptionsItem label="补助标准">
+ {row.orchestraSubsidyStandardName}
+ </NDescriptionsItem> */}
+ </NDescriptions>
+ )
+ title: '团练宝零售价',
+ key: 'vipPrice',
+ <NDescriptionsItem label="团练宝零售价">
+ {numeral(row.vipPrice).format('0,0.00')}元/6个月
+ </NDescriptionsItem>
+ <NDescriptionsItem label="团练宝团购价">
+ {' '}
+ {numeral(row.vipGroupPrice).format('0,0.00')}元/6个月
+ <NDescriptionsItem label="团练宝原价">
+ {numeral(row.vipOriginalPrice).format('0,0.00')}元/6个月
+ // render(row: any) {
+ // return `${numeral(row.vipPrice).format('0,0.00')}元/6个月`
+ // }
+ // {
+ // title: '团练宝团购价',
+ // key: 'vipGroupPrice',
+ // return `${numeral(row.vipGroupPrice).format('0,0.00')}元/6个月`
+ // },
+ // title: '团练宝原价',
+ // key: 'vipOriginalPrice',
+ // return `${numeral(row.vipOriginalPrice).format('0,0.00')}元/6个月`
+ // title: '补助标准',
+ // key: 'orchestraSubsidyStandardName'
+ title: '更新人',
+ key: 'operatorName',
+ <NDescriptionsItem label="更新人">{row.operatorName}</NDescriptionsItem>
+ <NDescriptionsItem label="更新时间">{row.updateTime}</NDescriptionsItem>
+ // title: '更新时间',
+ // key: 'updateTime'
+ title: '操作',
+ key: 'operation',
+ <NSpace>
+ <NButton
+ type="primary"
+ size="small"
+ text
+ //v-auth="cityFeeSetting/update1597887290159779842"
+ onClick={() => {
+ state.visiableCity = true
+ state.cityType = 'edit'
+ state.cityData = row
+ }}
+ >
+ 修改
+ </NButton>
+ </NSpace>
+ ]
+ const saveForm = ref()
+ const onSubmit = () => {
+ state.pagination.page = 1
+ getList()
+ const onSearch = () => {
+ saveForm.value?.submit()
+ const onBtnReset = () => {
+ saveForm.value?.reset()
+ const getList = async () => {
+ try {
+ state.loading = true
+ const { data } = await cityFeeSettingPage({ ...state.pagination, ...state.searchForm })
+ state.loading = false
+ state.pagination.pageTotal = Number(data.total)
+ state.dataList = data.rows || []
+ } catch {
+ // 获取所在城市
+ const getAreaAll = async () => {
+ const { data } = await queryAllProvince()
+ ;(data || []).forEach((child: any) => {
+ child.isLeaf = false
+ child.areas.forEach((city: any) => {
+ city.isLeaf = true
+ city.areas = null
+ state.areaList = data
+ } catch {}
+ // 加载列表
+ const handleLoad = async (option: TreeSelectOption) => {
+ const res = await sysAreaPage({
+ rows: 999,
+ parentId: option.id
+ const data = res.data.rows || []
+ const tempList = [] as any
+ data.forEach((item: any) => {
+ tempList.push({ key: item.code, label: item.name, id: item.id, isLeaf: true })
+ option.children = tempList
+ //
+ onMounted(async () => {
+ getAreaAll()
+ if (state.searchForm.cityCode) {
+ const codeDetail = await sysAreaDetailCode(state.searchForm.cityCode)
+ parentId: codeDetail.data.parentOrganId
+ state.areaList.forEach((area: any) => {
+ if (area.id === codeDetail.data.parentOrganId) {
+ area.children = tempList
+ return () => (
+ <>
+ <SaveForm
+ ref={saveForm}
+ model={state.searchForm}
+ onSubmit={onSubmit}
+ onSetModel={(val: any) => (state.searchForm = val)}
+ <NFormItem path="cityCode">
+ <NCascader
+ placeholder="请选择城市"
+ v-model:value={state.searchForm.cityCode}
+ options={state.areaList}
+ checkStrategy="child"
+ childrenField="areas"
+ expandTrigger="hover"
+ valueField="code"
+ labelField="name"
+ clearable
+ filterable
+ />
+ </NFormItem>
+ <NFormItem>
+ <NButton type="primary" onClick={onSearch}>
+ 搜索
+ <NButton type="default" onClick={onBtnReset}>
+ 重置
+ </SaveForm>
+ <NSpace style={{ paddingBottom: '12px' }}>
+ //v-auth="cityFeeSetting/save1597887194890358786"
+ state.cityType = 'add'
+ state.cityData = {}
+ 添加城市
+ <NDataTable loading={state.loading} columns={columns()} data={state.dataList}></NDataTable>
+ <Pagination
+ v-model:page={state.pagination.page}
+ v-model:pageSize={state.pagination.rows}
+ v-model:pageTotal={state.pagination.pageTotal}
+ onList={getList}
+ sync
+ saveKey="city-list"
+ ></Pagination>
+ <NModal
+ v-model:show={state.visiableCity}
+ preset="dialog"
+ showIcon={false}
+ title={state.cityType === 'add' ? '添加城市' : '修改城市'}
+ style={{ width: '500px' }}
+ <CityOperation
+ type={state.cityType}
+ data={state.cityData}
+ areaList={state.areaList}
+ onGetList={getList}
+ onClose={() => (state.visiableCity = false)}
+ </NModal>
+ </>
+})
@@ -0,0 +1,126 @@
+import { NButton, NDataTable, NFormItem, NInput, NModal, NSpace } from 'naive-ui'
+import { defineComponent, reactive, onMounted } from 'vue'
+import SubsidyOperation from '../modal/subsidy-operation'
+import { orchestraSubsidyStandardPage } from '../api'
+ name: 'subsidy-list',
+ visiableSubsidy: false,
+ subsidyOperation: 'add',
+ subsidyData: {} as any
+ title: '编号',
+ key: 'id'
+ title: '补助标准名称',
+ key: 'subsidyStandardName'
+ key: 'operatorName'
+ title: '更新时间',
+ key: 'updateTime'
+ //v-auth="orchestraSubsidyStandard/update1597887579789053953"
+ state.visiableSubsidy = true
+ state.subsidyOperation = 'edit'
+ state.subsidyData = row
+ const { data } = await orchestraSubsidyStandardPage({ ...state.pagination })
+ onMounted(() => {
+ //v-auth="orchestraSubsidyStandard/save1597887486016999426"
+ state.subsidyOperation = 'add'
+ state.subsidyData = {}
+ 添加补助标准
+ saveKey="subsidy-list"
+ v-model:show={state.visiableSubsidy}
+ title={state.subsidyOperation === 'add' ? '新增补助标准' : '修改补助标准'}
+ style={{ width: '800px' }}
+ <SubsidyOperation
+ type={state.subsidyOperation}
+ data={state.subsidyData}
+ onClose={() => (state.visiableSubsidy = false)}
@@ -0,0 +1,56 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, h, reactive, resolveDynamicComponent } from 'vue'
+import CityList from './component/city-list'
+import SubsidyList from './component/subsidy-list'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+import { useRoute } from 'vue-router'
+ name: 'city-manage',
+ tabName: 'CityList' as 'CityList' | 'SubsidyList'
+ const route = useRoute()
+ getTabsCache((val: any) => {
+ if (val.form.tabName) {
+ state.tabName = val.form.tabName
+ const setTabs = (val: any) => {
+ setTabsCaches(val, 'tabName', route)
+ return () => {
+ // const Component = resolveDynamicComponent(state.componentName)
+ <div class="system-menu-container">
+ <h2>城市管理</h2>
+ <div class={['section-container']} style="padding-top: 20px">
+ <CityList />
+ {/* <NTabs
+ type="line"
+ size="large"
+ v-model:value={state.tabName}
+ onUpdate:value={(val: any) => setTabs(val)}
+ <NTabPane
+ name="CityList"
+ tab="城市列表"
+ v-auth="cityFeeSetting/page1597885815002091522"
+ </NTabPane>
+ name="SubsidyList"
+ tab="补助标准"
+ v-auth="orchestraSubsidyStandard/page1597886618878201858"
+ <SubsidyList />
+ </NTabs> */}
+ </div>
@@ -0,0 +1,249 @@
+import { NForm, NFormItem, NInput, NSelect, NSpace, NButton, useMessage, NCascader } from 'naive-ui'
+import { defineComponent, onMounted, PropType, reactive, ref } from 'vue'
+import { cityFeeSettingSave, cityFeeSettingUpdate, orchestraSubsidyStandardPage } from '../api'
+ name: 'city-operation',
+ props: {
+ type: {
+ type: String,
+ default: 'add'
+ data: {
+ type: Object as PropType<any>,
+ default: () => {}
+ areaList: {
+ type: Array as PropType<Array<TreeSelectOption>>,
+ default: () => []
+ emits: ['close', 'getList'],
+ setup(props, { slots, attrs, emit }) {
+ const forms = reactive({
+ cityCode: null,
+ provinceCode: null, // 省号
+ vipPrice: null,
+ vipGroupPrice: null,
+ vipOriginalPrice: null,
+ orchestraSubsidyStandardId: null,
+ vipDurationMonth: 6 // 固定半年
+ const btnLoading = ref(false)
+ const formsRef = ref()
+ const subsidyList = ref<any>([])
+ const message = useMessage()
+ // 获取补助标准列表
+ const getSubsidy = async () => {
+ const res = await orchestraSubsidyStandardPage({
+ rows: 100
+ tempList.push({ label: item.subsidyStandardName, value: item.id })
+ subsidyList.value = tempList
+ // 提交记录
+ const onSubmit = async () => {
+ formsRef.value.validate(async (error: any) => {
+ if (error) return
+ console.log(forms, 'forms')
+ const ids = formatParentAreaId(forms.cityCode, props.areaList)
+ forms.provinceCode = ids[0]
+ btnLoading.value = true
+ if (props.type === 'add') {
+ await cityFeeSettingSave({ ...forms })
+ message.success('添加成功')
+ } else if (props.type === 'edit') {
+ await cityFeeSettingUpdate({ ...forms, id: props.data.id })
+ message.success('修改成功')
+ emit('getList')
+ emit('close')
+ setTimeout(() => {
+ btnLoading.value = false
+ }, 100)
+ // const formatParentId = (id: any, list: any, ids = [] as any) => {
+ // for (const item of list) {
+ // if (item.children && item.children.length > 0) {
+ // const cIds: any = formatParentId(id, item.children, [...ids, item.code])
+ // if (cIds.includes(id)) {
+ // return cIds
+ // if (item.code === id) {
+ // return [...ids, id]
+ // return ids
+ const formatParentAreaId = (id: any, list: any, ids = [] as any) => {
+ for (const item of list) {
+ if (item.areas && item.areas.length > 0) {
+ const cIds: any = formatParentAreaId(id, item.areas, [...ids, item.code])
+ if (cIds.includes(id)) {
+ return cIds
+ if (item.code === id) {
+ return [...ids, id]
+ return ids
+ getSubsidy()
+ // 初始化数据
+ if (props.type === 'edit') {
+ console.log(props.data, 'data')
+ forms.cityCode = props.data.cityCode
+ forms.provinceCode = props.data.provinceCode
+ forms.vipPrice = props.data.vipPrice.toString()
+ forms.vipGroupPrice = props.data.vipGroupPrice.toString()
+ forms.vipOriginalPrice = props.data.vipOriginalPrice.toString()
+ forms.orchestraSubsidyStandardId = props.data.orchestraSubsidyStandardId
+ // 只能输入数字
+ const onlyAllowNumber = (value: string) =>
+ !value || /^(?:0.\d{0,3}|[0-9][0-9]{0,12}|[0-9]{1,10}.\d{0,3})$/.test(value)
+ <div style="background: #fff; padding-top: 12px">
+ <NForm model={forms} ref={formsRef} label-placement="left" label-width="auto">
+ <NFormItem
+ label="城市"
+ path="cityCode"
+ rule={[
+ required: true,
+ message: '请输入城市'
+ ]}
+ v-model:value={forms.cityCode}
+ options={props.areaList}
+ label="团练宝零售价"
+ path="vipPrice"
+ message: '请输入团练宝零售价'
+ <NInput
+ v-model:value={forms.vipPrice}
+ placeholder="请输入团练宝零售价"
+ allowInput={onlyAllowNumber}
+ {{ suffix: () => '元/6个月' }}
+ </NInput>
+ label="团练宝团购价"
+ path="vipGroupPrice"
+ message: '请输入团练宝团购价'
+ v-model:value={forms.vipGroupPrice}
+ placeholder="请输入团练宝团购价"
+ label="团练宝原价"
+ path="vipOriginalPrice"
+ message: '请输入团练宝原价'
+ v-model:value={forms.vipOriginalPrice}
+ placeholder="请输入团练宝原价"
+ {/* <NFormItem
+ label="补助标准"
+ path="orchestraSubsidyStandardId"
+ message: '请选择补助标准'
+ <NSelect
+ v-model:value={forms.orchestraSubsidyStandardId}
+ options={subsidyList.value}
+ placeholder="请选择补助标准"
+ ></NSelect>
+ </NFormItem> */}
+ </NForm>
+ <NSpace justify="end">
+ <NButton type="default" onClick={() => emit('close')}>
+ 取消
+ onClick={() => onSubmit()}
+ loading={btnLoading.value}
+ disabled={btnLoading.value}
+ 保存
@@ -0,0 +1,11 @@
+.section {
+ background: #f7f7f7;
+ padding: 12px 12px 0;
+ border-radius: 6px;
+ margin-bottom: 12px;
+ &:last-child {
+ margin-bottom: 0;
@@ -0,0 +1,421 @@
+import { AddOutline, TrashOutline } from '@vicons/ionicons5'
+ NForm,
+ NGrid,
+ NGi,
+ NInputGroup,
+ NIcon,
+ useDialog,
+ useMessage
+import { defineComponent, PropType, reactive, ref } from 'vue'
+import styles from './index.module.less'
+import { orchestraSubsidyStandardSave, orchestraSubsidyStandardUpdate } from '../api'
+ default: () => {
+ return {}
+ const baseConfig = {
+ minStudentNum: null as any,
+ maxStudentNum: null,
+ singleSubsidy: null,
+ ensembleSubsidy: null,
+ musicTheorySubsidy: null,
+ manageSubsidy: null
+ subsidyStandardName: '',
+ config: [{ ...baseConfig }]
+ const dialog = useDialog()
+ forms.subsidyStandardName = props.data.subsidyStandardName
+ forms.config = JSON.parse(props.data.configJson)
+ const onlyAllowNumber = (value: string) => !value || /^\d+$/.test(value)
+ const minValidator = (rule: any, value: string) => {
+ const t = rule.field.split(']')
+ const index = t[0].split('[')[1]
+ const prevMinStudentNum = forms.config[index - 1]?.maxStudentNum
+ const maxStudentNum = forms.config[index]?.maxStudentNum
+ return new Promise<void>((resolve, reject) => {
+ if (!value) {
+ reject(Error('请输入最小值'))
+ } else if (
+ ((Number(prevMinStudentNum) || 0) >= Number(value) ||
+ (Number(value) > Number(maxStudentNum) && maxStudentNum)) &&
+ index != '0'
+ ) {
+ reject(Error('数值输入有误'))
+ } else if (value == '0') {
+ reject(Error('数值必须大于0'))
+ resolve()
+ const maxValidator = (rule: any, value: any) => {
+ // const prevMinStudentNum = forms.config[index - 1]?.maxStudentNum
+ const minStudentNum = forms.config[index]?.minStudentNum
+ reject(Error('请输入最大值'))
+ } else if ((Number(minStudentNum) || 0) > Number(value)) {
+ // 删除
+ const onRemoveItem = (index: number) => {
+ dialog.warning({
+ title: '提示',
+ content: '是否确定删除?',
+ positiveText: '确定',
+ negativeText: '取消',
+ onPositiveClick: () => {
+ forms.config.splice(index, 1)
+ formsRef.value?.validate(async (error: any) => {
+ // console.log(error)
+ let checkNumber = true
+ forms.config.forEach((config: any, index: number) => {
+ // 从第二条数据开始判断
+ const currentMaxNumber = Number(config.maxStudentNum)
+ const nextConfig = forms.config[index + 1]
+ console.log(nextConfig)
+ // 判断是否有下一个
+ if (nextConfig) {
+ const nextMinNumber = Number(nextConfig.minStudentNum)
+ console.log(currentMaxNumber, nextMinNumber)
+ checkNumber = currentMaxNumber + 1 === nextMinNumber
+ if (!checkNumber) {
+ message.error('阶梯配置有误')
+ return
+ await orchestraSubsidyStandardSave({
+ subsidyStandardName: forms.subsidyStandardName,
+ configJson: JSON.stringify(forms.config)
+ await orchestraSubsidyStandardUpdate({
+ id: props.data.id,
+ <NForm
+ model={forms}
+ ref={formsRef}
+ label-placement="top"
+ label-width="auto"
+ // requireMarkPlacement="left"
+ <NGrid cols={3} xGap={12}>
+ <NGi>
+ label="补助标准名称"
+ path="subsidyStandardName"
+ message: '请输入补助标准名称',
+ trigger: ['input', 'blur']
+ v-model:value={forms.subsidyStandardName}
+ placeholder="请输入补助标准名称"
+ ></NInput>
+ </NGi>
+ </NGrid>
+ {forms.config &&
+ forms.config.map((item: any, index: number) => (
+ <div class={styles.section}>
+ <NGrid cols={1}>
+ <NSpace justify="space-between" style="width: 100%; padding-bottom: 8px;">
+ <div style={{ fontSize: '16px', fontWeight: 'bold' }}>阶梯{index + 1}</div>
+ quaternary
+ circle
+ disabled={index === 0 ? true : false}
+ onClick={() => onRemoveItem(index)}
+ {{
+ icon: () => (
+ <NIcon size={20}>
+ <TrashOutline />
+ </NIcon>
+ <NInputGroup>
+ label="学生人数范围"
+ path={`config[${index}].minStudentNum`}
+ trigger: ['input', 'blur'],
+ validator: minValidator
+ v-model:value={item.minStudentNum}
+ placeholder="最小值"
+ disabled={index > 0 ? true : false}
+ {{ suffix: () => '人' }}
+ <span style="padding: 0 8px;display: flex;align-items: center;">至</span>
+ path={`config[${index}].maxStudentNum`}
+ validator: maxValidator
+ v-model:value={item.maxStudentNum}
+ placeholder="最大值"
+ onUpdateValue={(val: any) => {
+ const config = forms.config[index + 1]
+ if (config) {
+ config.minStudentNum = val ? (Number(val) + 1).toString() : null
+ </NInputGroup>
+ label="单技训练补助"
+ path={`config[${index}].singleSubsidy`}
+ message: '请输入单技训练补助',
+ v-model:value={item.singleSubsidy}
+ placeholder="请输入单技训练补助"
+ {{ suffix: () => '元/次' }}
+ label="合奏训练补助"
+ path={`config[${index}].ensembleSubsidy`}
+ message: '请输入合奏训练补助',
+ v-model:value={item.ensembleSubsidy}
+ placeholder="请输入合奏训练补助"
+ label="乐理训练补助"
+ path={`config[${index}].musicTheorySubsidy`}
+ message: '请输入乐理训练补助',
+ v-model:value={item.musicTheorySubsidy}
+ placeholder="请输入乐理训练补助"
+ label="管理补助"
+ path={`config[${index}].manageSubsidy`}
+ message: '请输入管理补助',
+ v-model:value={item.manageSubsidy}
+ placeholder="请输入管理补助"
+ label="练习奖励"
+ path={`config[${index}].trainingSubsidy`}
+ message: '请输入练习奖励',
+ v-model:value={item.trainingSubsidy}
+ placeholder="请输入练习奖励"
+ {{ suffix: () => '元/人' }}
+ ))}
+ <div style={{ margin: '12px 0' }}>
+ block
+ type="default"
+ let minNumber = null as any
+ if (forms.config.length > 0) {
+ minNumber = forms.config[forms.config.length - 1].maxStudentNum
+ forms.config.push({
+ ...baseConfig,
+ minStudentNum: minNumber ? (Number(minNumber) + 1).toString() : null
+ {{ default: () => '添加标准', icon: () => <AddOutline /> }}
+ onClick={onSubmit}
@@ -0,0 +1,373 @@
+ * @description: 帮助中心列表
+export const helpCenterContentPage = (params: object) => {
+ url: '/cbs-app/helpCenterContent/page',
+ data: params
+ * @description: 帮助中心-启用/停用
+export const helpCenterContentStatus = (params: any) => {
+ url: '/cbs-app/helpCenterContent/status/' + params.id,
+ method: 'post'
+ * @description: 帮助中心添加
+export const helpCenterContentSave = (params: object) => {
+ url: '/cbs-app/helpCenterContent/save',
+ * @description: 帮助中心修改
+export const helpCenterContentUpdate = (params: object) => {
+ url: '/cbs-app/helpCenterContent/update',
+ * @description: 帮助中心删除
+export const helpCenterContentRemove = (params: object) => {
+ url: '/cbs-app/helpCenterContent/remove',
+ * @description: 帮助中心分类列表
+export const helpCenterCatalogPage = (params: object) => {
+ url: '/cbs-app/helpCenterCatalog/page',
+ * @description: 帮助中心分类添加
+export const helpCenterCatalogSave = (params: object) => {
+ url: '/cbs-app/helpCenterCatalog/save',
+ * @description: 帮助中心分类更新
+export const helpCenterCatalogUpdate = (params: object) => {
+ url: '/cbs-app/helpCenterCatalog/update',
+ * @description: 帮助中心分类删除
+export const helpCenterCatalogRemove = (params: object) => {
+ url: '/cbs-app/helpCenterCatalog/remove',
+ * @description: 资讯列表
+export const sysNewsInformationPage = (params: object) => {
+ url: '/cbs-app/sysNewsInformation/page',
+ * @description: 资讯添加
+export const sysNewsInformationSave = (params: object) => {
+ url: '/cbs-app/sysNewsInformation/save',
+ * @description: 资讯更新
+export const sysNewsInformationUpdate = (params: object) => {
+ url: '/cbs-app/sysNewsInformation/update',
+ * @description: 资讯删除
+export const sysNewsInformationRemove = (params: object) => {
+ url: '/cbs-app/sysNewsInformation/remove',
+ * @description: 公告列表
+export const sysNoticePage = (params: object) => {
+ url: '/cbs-app/sysNotice/page',
+ * @description: 公告添加
+export const sysNoticeSave = (params: object) => {
+ url: '/cbs-app/sysNotice/save',
+ * @description: 公告更新
+export const sysNoticeUpdate = (params: object) => {
+ url: '/cbs-app/sysNotice/update',
+ * @description: 公告删除
+export const sysNoticeRemove = (params: object) => {
+ url: '/cbs-app/sysNotice/remove',
+ * @description: 公告启用/停用
+export const sysNoticeStatus = (params?: any) => {
+ url: '/cbs-app/sysNotice/status/' + params.id,
+ * @description: APP按钮管理列表
+export const sysMenuButtonPage = (params: object) => {
+ url: '/cbs-app/sysMenuButton/page',
+ * @description: APP按钮管理添加
+export const sysMenuButtonSave = (params: object) => {
+ url: '/cbs-app/sysMenuButton/save',
+ * @description: APP按钮管理更新
+export const sysMenuButtonUpdate = (params: object) => {
+ url: '/cbs-app/sysMenuButton/update',
+ * @description: APP按钮管理删除
+export const sysMenuButtonRemove = (params: object) => {
+ url: '/cbs-app/sysMenuButton/remove',
+ * @description: 乐谱标签列表
+export const musicTagPage = (params: object) => {
+ url: '/cbs-app/musicTag/page',
+ * @description: 乐谱标签添加
+export const musicTagSave = (params: object) => {
+ url: '/cbs-app/musicTag/save',
+ * @description: 乐谱标签更新
+export const musicTagUpdate = (params: object) => {
+ url: '/cbs-app/musicTag/update',
+ * @description: 乐谱标签删除
+export const musicTagRemove = (params: object) => {
+ url: '/cbs-app/musicTag/remove',
+ * @description: 乐谱标签启用/停用
+export const musicTagState = (params: any) => {
+ url: '/cbs-app/musicTag/state/' + params.id,
+ * @description: 乐谱列表
+export const musicSheetPage = (params: object) => {
+ url: '/cbs-app/musicSheet/page',
+ * @description: 乐谱添加
+export const musicSheetSave = (params: object) => {
+ url: '/cbs-app/musicSheet/save',
+ * @description: 乐谱更新
+export const musicSheetUpdate = (params: object) => {
+ url: '/cbs-app/musicSheet/update',
+ * @description: 乐谱删除
+export const musicSheetRemove = (params: object) => {
+ url: '/cbs-app/musicSheet/remove',
+ * @description: 乐谱详情
+export const musicSheetDetail = (params?: any) => {
+ url: '/cbs-app/musicSheet/detail/' + params.id,
+ * @description: 乐谱-启用-停用
+export const musicSheetStatus = (params?: any) => {
+ url: '/cbs-app/musicSheet/status',
+ requestType: 'form',
@@ -0,0 +1,236 @@
+ NSelect,
+ useMessage,
+ NDatePicker
+import { defineComponent, onMounted, PropType, provide, reactive, ref } from 'vue'
+import UploadFile from '@/components/upload-file'
+import { sysNewsInformationSave, sysNewsInformationUpdate } from '../api'
+import dayjs from 'dayjs'
+import { getTimes } from '@/utils/dateUtil'
+ name: 'ad-operation',
+ clientTypeList: {
+ type: 'OPEN_SCREEN_AD',
+ clientType: null,
+ title: null,
+ coverImage: null,
+ linkUrl: null,
+ showTime: null,
+ operationTime: null as any,
+ onlineTime: null,
+ offlineTime: null,
+ status: false
+ const { operationTime, ...res } = forms
+ let onlineTime = '',
+ offlineTime = ''
+ if (operationTime) {
+ onlineTime = dayjs(operationTime[0]).format('YYYY-MM-DD HH:mm:ss')
+ offlineTime = dayjs(operationTime[1]).format('YYYY-MM-DD HH:mm:ss')
+ await sysNewsInformationSave({
+ ...res,
+ ...getTimes(operationTime, ['onlineTime', 'offlineTime'], 'YYYY-MM-DD')
+ await sysNewsInformationUpdate({
+ ...getTimes(operationTime, ['onlineTime', 'offlineTime'], 'YYYY-MM-DD'),
+ id: props.data.id
+ const data = props.data
+ forms.title = data.title
+ forms.coverImage = data.coverImage
+ forms.linkUrl = data.linkUrl
+ forms.showTime = data.showTime
+ if (data.onlineTime && data.offlineTime) {
+ forms.operationTime = [
+ dayjs(data.onlineTime).valueOf(),
+ dayjs(data.offlineTime).valueOf()
+ forms.clientType = data.clientType
+ forms.status = data.status
+ label="客户端"
+ path="clientType"
+ message: '请选择客户端'
+ options={props.clientTypeList}
+ placeholder="请选择客户端"
+ v-model:value={forms.clientType}
+ label="广告标题"
+ path="title"
+ message: '请输入广告标题'
+ v-model:value={forms.title}
+ placeholder="请输入广告标题"
+ showCount
+ maxlength={15}
+ label="显示时长"
+ path="showTime"
+ message: '请输入显示时长'
+ v-model:value={forms.showTime}
+ placeholder="请输入显示时长"
+ {{ suffix: () => '秒' }}
+ label="生效时间"
+ path="operationTime"
+ message: '请输入生效时间'
+ <NDatePicker
+ v-model:value={forms.operationTime}
+ type="daterange"
+ startPlaceholder="生效开始时间"
+ endPlaceholder="生效结束时间"
+ isDateDisabled={(ts: number) => {
+ return ts < dayjs(dayjs().format('YYYY-MM-DD')).valueOf()
+ style={{ width: '100%', textAlign: 'left' }}
+ <NFormItem label="跳转链接" path="linkUrl">
+ <NInput v-model:value={forms.linkUrl} placeholder="请输入跳转链接" clearable />
+ label="广告图片"
+ path="coverImage"
+ message: '请输入广告图片',
+ <UploadFile
+ v-model:fileList={forms.coverImage}
+ accept=".jpg,.jpeg,.png"
+ cropper
+ bucketName="news-info"
+ options={{
+ enlarge: 2,
+ autoCropWidth: 258, //默认生成截图框宽度
+ autoCropHeight: 355 //默认生成截图框高度
+ tips="请上传尺寸为516*710,大小1M以内的JPG、PNG图片"
@@ -0,0 +1,332 @@
+ NImage,
+ NTag,
+ NTooltip,
+import { sysNewsInformationPage, sysNewsInformationRemove, sysNewsInformationUpdate } from '../api'
+import AdOperation from './ad-operation'
+import { clientTypeArray } from '@/utils/searchArray'
+import { filterClientType } from '@/utils/filters'
+import TheTooltip from '@/components/TheTooltip'
+// import StationOperation from './station-operation'
+ name: 'content-ad',
+ pageTotal: 0,
+ type: 'OPEN_SCREEN_AD'
+ status: null,
+ keyword: null
+ clientTypeList: [] as any,
+ visiableAd: false,
+ adOperation: 'add',
+ adData: {} as any
+ render: (row: any) => (
+ <NDescriptionsItem label="广告标题">
+ <TheTooltip content={row.title} />{' '}
+ <NDescriptionsItem label="广告编号">{row.id}</NDescriptionsItem>
+ // title: '广告标题',
+ // key: 'title'
+ title: '广告图/视频',
+ key: 'coverImage',
+ width: 120,
+ return <NImage width={70} height={70} src={row.coverImage} />
+ title: '广告链接',
+ key: 'linkUrl',
+ return <TheTooltip content={row.linkUrl} />
+ title: '生效时间',
+ key: 'onlineTime',
+ <p>{row.onlineTime}</p>
+ <p>{row.offlineTime}</p>
+ title: '状态',
+ key: 'status',
+ <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+ title: '客户端',
+ key: 'clientType',
+ return filterClientType(row.clientType)
+ title: '操作人',
+ key: 'updateUser'
+ //v-auth="sysNewsInformation/update1599958605834342402"
+ state.visiableAd = true
+ state.adOperation = 'edit'
+ state.adData = row
+ //v-auth="sysNewsInformation/update1599958777301684226"
+ onClick={() => onChangeStatus(row)}
+ {row.status ? '停用' : '启用'}
+ {!row.status && (
+ onClick={() => onRmove(row)}
+ //v-auth="sysNewsInformation/remove1599958687598104577"
+ 删除
+ )}
+ const onRmove = (row: any): void => {
+ title: '警告',
+ content: `删除"${row.roleName}",是否继续?`,
+ onPositiveClick: async () => {
+ await sysNewsInformationRemove({ id: row.id })
+ message.success('删除成功')
+ const onChangeStatus = (row: any) => {
+ const statusStr = row.status ? '停用' : '启用'
+ content: `是否${statusStr}?`,
+ id: row.id,
+ status: row.status ? false : true,
+ title: row.title
+ message.success(`${statusStr}成功`)
+ const { data } = await sysNewsInformationPage({ ...state.pagination, ...state.searchForm })
+ clientTypeArray.forEach((item: any) => {
+ if (item.value != 'BACKEND') {
+ state.clientTypeList.push(item)
+ <h2>广告管理</h2>
+ label-width=""
+ <NFormItem label="标题" path="keyword">
+ <NInput clearable v-model:value={state.searchForm.keyword} placeholder="请输入标题" />
+ <NFormItem label="状态" path="status">
+ v-model:value={state.searchForm.status}
+ placeholder="请选择状态"
+ options={
+ [
+ label: '启用',
+ value: 1
+ label: '停用',
+ value: 0
+ ] as any
+ <NFormItem label="客户端" path="cityCode">
+ v-model={[state.searchForm.clientType, 'value']}
+ options={state.clientTypeList}
+ <div class={['section-container']}>
+ //v-auth="sysNewsInformation/save1599958516768296961"
+ state.adOperation = 'add'
+ state.adData = {}
+ 添加广告
+ <NDataTable
+ loading={state.loading}
+ columns={columns()}
+ data={state.dataList}
+ ></NDataTable>
+ v-model:show={state.visiableAd}
+ title={state.adOperation === 'add' ? '新增广告' : '修改广告'}
+ style={{ width: '600px' }}
+ <AdOperation
+ type={state.adOperation}
+ data={state.adData}
+ clientTypeList={state.clientTypeList}
+ onClose={() => (state.visiableAd = false)}
@@ -0,0 +1,346 @@
+import { sysMenuButtonPage, sysMenuButtonRemove, sysMenuButtonUpdate } from '../../api'
+import AppButtonOperation from '../modal/app-button-operation'
+ name: 'content-flash',
+ clientType: 'SCHOOL',
+ keyword: null,
+ auditFlag: null,
+ order: null as any
+ visiableApp: false,
+ appOperation: 'add',
+ appData: {} as any
+ const orderRef = reactive({
+ title: '排序值',
+ key: 'order'
+ // sorter: true,
+ // sortOrder: false
+ title: '按钮名称',
+ key: 'title'
+ title: '按钮图',
+ key: 'buttonImage',
+ return <NImage width={44} height={44} src={row.buttonImage} />
+ title: '跳转链接',
+ <NTooltip style="max-width: 400px;">
+ default: () => row.linkUrl,
+ trigger: () => (
+ <span style="overflow: hidden;display: inline-block;max-width: 200px;white-space: nowrap;text-overflow: ellipsis;">
+ {row.linkUrl}
+ </span>
+ </NTooltip>
+ orderRef,
+ title: '审核版本',
+ key: 'auditFlag',
+ <NTag type={row.auditFlag ? 'primary' : 'default'}>
+ {row.auditFlag ? '是' : '否'}
+ </NTag>
+ //v-auth="sysMenuButton/update1600386947867840521"
+ state.visiableApp = true
+ state.appOperation = 'edit'
+ state.appData = row
+ //v-auth="sysMenuButton/update1600386947867840523"
+ //v-auth="sysMenuButton/remove1600386947867840522"
+ // const handleSorterChange = (sroter: any) => {
+ // if (!sroter.order) {
+ // state.searchForm.order = '' as string
+ // orderRef.sortOrder = false
+ // } else {
+ // orderRef.sortOrder = sroter.order
+ // state.searchForm.order = sroter.order == 'ascend' ? 'ASC' : 'DESC'
+ // getList()
+ content: `删除"${row.title}",是否继续?`,
+ await sysMenuButtonRemove({ id: row.id })
+ await sysMenuButtonUpdate({
+ status: row.status ? false : true
+ const { data } = await sysMenuButtonPage({ ...state.pagination, ...state.searchForm })
+ saveKey="app-button-school"
+ <NFormItem label="关键字" path="keyword">
+ placeholder="请输入按钮名称"
+ v-model:value={state.searchForm.keyword}
+ <NFormItem label="审核版本" path="auditFlag">
+ v-model:value={state.searchForm.auditFlag}
+ label: '是',
+ label: '否',
+ placeholder="请选择审核版本"
+ //v-auth="sysMenuButton/save1600386947867840520"
+ state.appOperation = 'add'
+ state.appData = {}
+ 添加按钮
+ v-model:show={state.visiableApp}
+ title={state.appOperation === 'add' ? '新增按钮' : '修改按钮'}
+ style={{ width: '550px' }}
+ <AppButtonOperation
+ type={state.appOperation}
+ data={state.appData}
+ clientType="SCHOOL"
+ onClose={() => (state.visiableApp = false)}
+ clientType: 'STUDENT',
+ auditFlag: null
+ //v-auth="sysMenuButton/update1600386770473947138"
+ //v-auth="sysMenuButton/update1600386947867840513"
+ //v-auth="sysMenuButton/remove1600386859657433090"
+ saveKey="app-button-student"
+ //v-auth="sysMenuButton/save1600386656187551746"
+ clientType="STUDENT"
+ clientType: 'TEACHER',
+ //v-auth="sysMenuButton/update1600386947867840517"
+ //v-auth="sysMenuButton/update1600386947867840519"
+ //v-auth="sysMenuButton/remove1600386947867840518"
+ saveKey="app-button-teacher"
+ //v-auth="sysMenuButton/save1600386947867840516"
+ clientType="TEACHER"
+import Student from './component/student'
+import Teacher from './component/teacher'
+import School from './component/school'
+ tabName: 'Student' as 'Student' | 'Teacher' | 'School'
+ <h2>APP按钮管理</h2>
+ <div class={['section-container']} style="padding-top: 0">
+ <NTabs
+ <NTabPane name="Student" tab="学生端"
+ //v-auth="sysMenuButton/page1600386298522472450"
+ <Student />
+ <NTabPane name="Teacher" tab="老师端"
+ //v-auth="sysMenuButton/page1600386947867840514"
+ <Teacher />
+ <NTabPane name="School" tab="管理端"
+ //v-auth="sysMenuButton/page1600386947867840515"
+ <School />
+ </NTabs>
@@ -0,0 +1,336 @@
+ NInputNumber,
+ NTooltip
+import { sysMenuButtonSave, sysMenuButtonUpdate } from '../../api'
+import { QuestionCircleFilled, QuestionCircleOutlined } from '@vicons/antd'
+ clientType: {
+ default: 'STUDENT'
+ order: null,
+ buttonImage: null,
+ type: 'APP',
+ homePage: null as any, // 是否首页展示
+ clientType: props.clientType || null,
+ auditFlag: null as any,
+ defaultButton: null as any, // 是否默认按钮(用于添加管理员默认添加按钮)
+ lockFlag: null as any, // 是否锁定-主要是管理端使用
+ manageButton: null as any // 辅助管理-主要是管理端使用
+ await sysMenuButtonSave({ ...forms })
+ await sysMenuButtonUpdate({ ...forms, id: props.data.id })
+ forms.order = data.order
+ forms.buttonImage = data.buttonImage
+ forms.auditFlag = data.auditFlag ? 1 : 0
+ forms.homePage = data.homePage ? 1 : 0
+ forms.defaultButton = data.defaultButton ? 1 : 0
+ forms.lockFlag = data.lockFlag ? 1 : 0
+ forms.manageButton = data.manageButton ? 1 : 0
+ label="标题"
+ message: '请输入标题',
+ placeholder="请输入标题"
+ maxlength={14}
+ label="排序值"
+ path="order"
+ message: '请输入排序值'
+ <NInputNumber
+ v-model:value={forms.order}
+ placeholder="请输入排序值"
+ min={0}
+ style={{ width: '100%' }}
+ label="是否审核"
+ path="auditFlag"
+ message: '请选择是否审核'
+ v-model:value={forms.auditFlag}
+ placeholder="请选择是否审核"
+ options={[
+ label="首页展示"
+ path="homePage"
+ message: '请选择首页展示'
+ v-model:value={forms.homePage}
+ placeholder="请选择首页展示"
+ label="是否默认"
+ path="defaultButton"
+ message: '请选择是否默认'
+ {/* 刘俊驰要求是是0 否是1 */}
+ v-model:value={forms.defaultButton}
+ placeholder="请选择是否默认"
+ disabled={forms.manageButton === 1}
+ label="是否锁定"
+ path="lockFlag"
+ message: '请选择是否锁定'
+ v-model:value={forms.lockFlag}
+ placeholder="请选择是否锁定"
+ // label="辅助管理"
+ path="manageButton"
+ message: '请选择辅助管理'
+ label: () => (
+ <span style={{ display: 'flex', alignItems: 'center' }}>
+ 管理配置
+ <NTooltip>
+ <NIcon size={16} color="#FF8057">
+ <QuestionCircleFilled />
+ ),
+ default: () => '总管理员是否可将该按钮权限配置给辅助管理员'
+ default: () => (
+ v-model:value={forms.manageButton}
+ placeholder="请选择管理配置"
+ // 5737 优化:app按钮管理--管理配置选【否】时,强制 是否默认值 为【否】,如果管理配置选【是】时,则不限制
+ if (val === 1) {
+ forms.defaultButton = 0
+ label="按钮图"
+ path="buttonImage"
+ message: '请上传按钮图',
+ v-model:fileList={forms.buttonImage}
+ enlarge: 1, // 图片放大倍数
+ autoCropWidth: 88, //默认生成截图框宽度
+ autoCropHeight: 88 //默认生成截图框高度
+ size={1}
+ tips="请上传请上传尺寸为88*88大小1M以内的JPG、PNG图片"
@@ -0,0 +1,213 @@
+ name: 'flash-operation',
+ type: 'FLASH_PAGE',
+ label="闪页标题"
+ message: '请输入闪页标题'
+ placeholder="请输入闪页标题"
+ <NFormItem label="跳转连接" path="linkUrl">
+ <NInput v-model:value={forms.linkUrl} placeholder="请输入跳转连接" clearable />
+ label="闪页图片"
+ message: '请输入闪页图片',
@@ -0,0 +1,331 @@
+import FlashOperation from './flash-operation'
+ visiableFlash: false,
+ flashOperation: 'add',
+ flashData: {} as any
+ title: '基本信息',
+ <NDescriptionsItem label="闪页标题">
+ <NDescriptionsItem label="闪页编号">{row.id}</NDescriptionsItem>
+ // title: '闪页标题',
+ title: '闪页图',
+ title: '跳转连接',
+ //v-auth="sysNewsInformation/update1599695301404631042"
+ state.visiableFlash = true
+ state.flashOperation = 'edit'
+ state.flashData = row
+ //v-auth="sysNewsInformation/remove1599695493080129537"
+ <h2>闪页管理</h2>
+ <NFormItem label="客户端" path="clientType">
+ //v-auth="sysNewsInformation/save1599695226544693250"
+ state.flashOperation = 'add'
+ state.flashData = {}
+ 添加闪页
+ v-model:show={state.visiableFlash}
+ title={state.flashOperation === 'add' ? '新增闪页' : '修改闪页'}
+ <FlashOperation
+ type={state.flashOperation}
+ data={state.flashData}
+ onClose={() => (state.visiableFlash = false)}
+import InformationOperation from './information-operation'
+import { sysEmployeePage } from '@/views/system-manage/api'
+ type: 'HOT_CONSULTATION',
+ updateBy: null,
+ clientType: null
+ visiableInfo: false,
+ infoOperation: 'add',
+ infoData: {} as any,
+ staffList: [] as any
+ <NDescriptionsItem label="资讯标题">
+ <NDescriptionsItem label="资讯编号">{row.id}</NDescriptionsItem>
+ // title: '资讯标题',
+ title: '资讯封面',
+ return <NImage width={87} height={60} src={row.coverImage} />
+ title: '创建时间',
+ key: 'createTime'
+ state.visiableInfo = true
+ state.infoOperation = 'edit'
+ state.infoData = row
+ content: `是否${statusStr}"${row.title}"?`,
+ // 获取操作人
+ const getOperationList = async () => {
+ const { data } = await sysEmployeePage({
+ rows: 999
+ // jobType: 'STAFF'
+ if (Array.isArray(data.rows)) {
+ state.staffList = data.rows.map((n: any) => ({ label: n.nickname, value: n.id }))
+ if (item.value != 'BACKEND' && item.value != 'SCHOOL' && item.value != 'REPAIR') {
+ getOperationList()
+ <h2>资讯管理</h2>
+ v-model={[state.searchForm.keyword, 'value']}
+ placeholder="资讯标题"
+ v-model={[state.searchForm.status, 'value']}
+ value: true
+ value: false
+ <NFormItem label="操作人" path="clientType">
+ v-model={[state.searchForm.updateBy, 'value']}
+ placeholder="请选择操作人"
+ options={state.staffList}
+ state.infoOperation = 'add'
+ state.infoData = {}
+ 添加资讯
+ v-model:show={state.visiableInfo}
+ title={state.infoOperation === 'add' ? '新增资讯' : '修改资讯'}
+ style={{ width: '850px' }}
+ <InformationOperation
+ type={state.infoOperation}
+ data={state.infoData}
+ onClose={() => (state.visiableInfo = false)}
@@ -0,0 +1,241 @@
+ NDatePicker,
+ NFormItemGi,
+ NRadioGroup,
+ NRadio
+import Editor from '@/components/editor'
+import UEditor from '@/components/u-editor'
+ linkType: null as any,
+ content: null,
+ summary: null,
+ await sysNewsInformationSave({ ...forms })
+ await sysNewsInformationUpdate({ ...forms, id: props.data.id })
+ forms.linkType = data.linkType
+ forms.content = data.content
+ forms.summary = data.summary
+ <NForm model={forms} ref={formsRef} label-placement="left" label-width="100">
+ <NGrid cols={2}>
+ <NFormItemGi
+ </NFormItemGi>
+ label="跳转方式"
+ path="linkType"
+ message: '请输入跳转方式'
+ <NRadioGroup v-model:value={forms.linkType}>
+ <NRadio value={'IN'}>内容跳转</NRadio>
+ <NRadio value={'OUT'}>外部链接</NRadio>
+ </NRadioGroup>
+ label="资讯标题"
+ message: '请输入资讯标题'
+ placeholder="请输入资讯标题"
+ {forms.linkType === 'OUT' && (
+ label="跳转链接"
+ path="linkUrl"
+ message: '请输入跳转链接'
+ pattern:
+ /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/,
+ message: '跳转链接输入有误'
+ label="资讯封面"
+ message: '请输入资讯封面',
+ size={5}
+ autoCropWidth: 208, //默认生成截图框宽度
+ autoCropHeight: 144 //默认生成截图框高度
+ tips="请上传尺寸为208*144,JPG、PNG图片"
+ <NFormItemGi label="描述" path="summary">
+ placeholder="请输入描述"
+ v-model:value={forms.summary}
+ type="textarea"
+ rows={3}
+ {forms.linkType === 'IN' && (
+ label="内容"
+ path="content"
+ message: '请输入内容'
+ <UEditor v-model:modelValue={forms.content} bucketName="news-info"></UEditor>
+ {/* <Editor v-model:value={forms.content} height={300} bucketName="news-info" /> */}
+import { sysNoticePage, sysNoticeRemove, sysNoticeStatus } from '../api'
+import NoticeOperation from './notice-operation'
+ catalogType: null,
+ status: null
+ <NDescriptionsItem label="公告标题">
+ <NDescriptionsItem label="公告编号">{row.id}</NDescriptionsItem>
+ // title: '公告标题',
+ // title: '发布时间',
+ // key: 'releaseTime'
+ key: 'catalogType',
+ return filterClientType(row.catalogType)
+ //v-auth="sysNotice/update1599958936240640001"
+ //v-auth="sysNotice/status1599959101026455553"
+ //v-auth="sysNotice/remove1599959008667881473"
+ await sysNoticeRemove({ id: row.id })
+ await sysNoticeStatus({
+ id: row.id
+ const { data } = await sysNoticePage({ ...state.pagination, ...state.searchForm })
+ <h2>公告管理</h2>
+ <NInput v-model={[state.searchForm.keyword, 'value']} placeholder="公告编号/标题" />
+ <NFormItem label="客户端" path="catalogType">
+ v-model={[state.searchForm.catalogType, 'value']}
+ //v-auth="sysNotice/save1599958862773211137"
+ 添加公告
+ title={state.adOperation === 'add' ? '新增公告' : '修改公告'}
+ style={{ width: '750px' }}
+ <NoticeOperation
@@ -0,0 +1,166 @@
+import { sysNoticeSave, sysNoticeUpdate } from '../api'
+ // releaseTime: null as any,
+ let { ...res } = forms
+ await sysNoticeSave({ ...res })
+ await sysNoticeUpdate({ ...res, id: props.data.id })
+ // onMounted(async () => {
+ forms.catalogType = data.catalogType
+ // forms.releaseTime = data.releaseTime ? dayjs(data.releaseTime).valueOf() : null
+ // })
+ path="catalogType"
+ v-model:value={forms.catalogType}
+ label="公告标题"
+ message: '请输入公告标题'
+ placeholder="请输入公告标题"
+ {/* <NFormItem label="发布时间" path="releaseTime">
+ v-model:value={forms.releaseTime}
+ type="datetime"
+ placeholder="请选择发布时间"
+ message: '请输入内容',
+ trigger: ['change', 'blur']
+ <Editor v-model:value={forms.content} bucketName="news-info" />
@@ -0,0 +1,151 @@
+import { NButton, NDataTable, NModal, NSpace, useDialog, useMessage } from 'naive-ui'
+import { helpCenterCatalogPage, helpCenterCatalogRemove } from '../../api'
+import HelpCenterCategoryOperation from '../modal/help-center-category-operation'
+ visiableCategory: false,
+ categoryOperation: 'add',
+ categoryData: {} as any
+ title: '名称',
+ key: 'name'
+ //v-auth="helpCenterCatalog/update1599694974123089922"
+ state.visiableCategory = true
+ state.categoryOperation = 'edit'
+ state.categoryData = row
+ //v-auth="helpCenterCatalog/remove1599695049448595458"
+ console.log(row, 'row')
+ content: `是否删除该数据?`,
+ await helpCenterCatalogRemove({ id: row.id })
+ const { data } = await helpCenterCatalogPage({ ...state.pagination })
+ //v-auth="helpCenterCatalog/save1599694895257591809"
+ state.categoryOperation = 'add'
+ state.categoryData = {}
+ 添加分类
+ saveKey="help-center-category"
+ v-model:show={state.visiableCategory}
+ title={state.categoryOperation === 'add' ? '新增帮助分类' : '修改帮助分类'}
+ <HelpCenterCategoryOperation
+ type={state.categoryOperation}
+ data={state.categoryData}
+ onClose={() => (state.visiableCategory = false)}
@@ -0,0 +1,318 @@
+ helpCenterCatalogPage,
+ helpCenterContentPage,
+ helpCenterContentRemove,
+ helpCenterContentStatus
+} from '../../api'
+import HelpCenterOperation from '../modal/help-center-operation'
+ catalogId: null,
+ catalogList: [] as any,
+ visiableHelp: false,
+ helpOperation: 'add',
+ helpData: {} as any
+ title: '标题',
+ key: 'title',
+ return <TheTooltip content={row.title} />
+ title: '分类',
+ key: 'catalogName',
+ return <TheTooltip content={row.catalogName} />
+ <NTag type={row.status ? 'info' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+ //v-auth="helpCenterContent/update1599694657725767681"
+ state.visiableHelp = true
+ state.helpOperation = 'edit'
+ state.helpData = row
+ //v-auth="helpCenterContent/status1611349854722027521"
+ //v-auth="helpCenterContent/remove1599694768375701505"
+ await helpCenterContentStatus({
+ await helpCenterContentRemove({ id: row.id })
+ const { data } = await helpCenterContentPage({ ...state.pagination, ...state.searchForm })
+ const getHelpCenterCategory = async () => {
+ const { data } = await helpCenterCatalogPage({ page: 1, rows: 999 })
+ state.catalogList = data.rows || []
+ getHelpCenterCategory()
+ <NFormItem label="分类" path="catalogId">
+ v-model:value={state.searchForm.catalogId}
+ placeholder="请选择分类"
+ options={state.catalogList}
+ valueField="id"
+ //v-auth="helpCenterContent/save1599694573994876929"
+ state.helpOperation = 'add'
+ state.helpData = {}
+ 添加帮助
+ saveKey="help-center"
+ v-model:show={state.visiableHelp}
+ title={state.helpOperation === 'add' ? '新增帮助' : '修改帮助'}
+ <HelpCenterOperation
+ type={state.helpOperation}
+ data={state.helpData}
+ catalogList={state.catalogList}
+ onClose={() => (state.visiableHelp = false)}
@@ -0,0 +1,55 @@
+import HelpCenter from './component/help-center'
+import HelpCenterCategory from './component/help-center-category'
+ tabName: 'HelpCenter' as 'HelpCenter' | 'HelpCenterCategory'
+ <h2>帮助中心</h2>
+ name="HelpCenter"
+ tab="帮助列表"
+ //v-auth="helpCenterContent/page1599594492578836481"
+ <HelpCenter />
+ name="HelpCenterCategory"
+ tab="帮助分类"
+ //v-auth="helpCenterCatalog/page1599594656550957058"
+ <HelpCenterCategory />
@@ -0,0 +1,102 @@
+import { helpCenterCatalogSave, helpCenterCatalogUpdate } from '../../api'
+ name: null,
+ parentId: 0
+ await helpCenterCatalogSave({ ...forms })
+ await helpCenterCatalogUpdate({ ...forms, id: props.data.id })
+ forms.name = data.name
+ forms.parentId = data.parentId
+ label="分类名称"
+ path="name"
+ message: '请输入分类名称'
+ <NInput v-model:value={forms.name} placeholder="请输入分类名称" clearable></NInput>
@@ -0,0 +1,163 @@
+import { helpCenterContentSave, helpCenterContentUpdate } from '../../api'
+ catalogList: {
+ type: Array as PropType<Array<string>>,
+ catalogType: null
+ await helpCenterContentSave({ ...forms })
+ await helpCenterContentUpdate({ ...forms, id: props.data.id })
+ forms.catalogId = data.catalogId
+ maxlength={30}
+ label="分类"
+ path="catalogId"
+ message: '请选择分类',
+ v-model:value={forms.catalogId}
+ options={props.catalogList}
+ message: '请选择客户端',
+ options={props.clientTypeList as any}
@@ -0,0 +1,466 @@
+ NDescriptionsItem
+import { defineComponent, onMounted, reactive, ref, watch } from 'vue'
+import { musicSheetPage, musicSheetRemove, musicSheetStatus, musicTagPage } from '../../api'
+import { getMusicSheetCategories } from '@/views/music-categrory/api'
+import MusicOperation from '../modal/music-operation'
+import { subjectBasicConfigPage } from '@/views/system-manage/api'
+import { accompanimentTypeArray, audioTypeArray } from '@/utils/searchArray'
+import MusicPreView from '../modal/musicPreView'
+import { filterPointCategory } from '@/views/teaching-manage/unit-test'
+ props: ['searchId'],
+ setup(props, { emit }) {
+ musicTag: null,
+ musicSubject: null,
+ audioType: null,
+ accompanimentType: null,
+ topFlag: null,
+ musicSheetCategoriesId: props.searchId
+ subjectList: [] as any,
+ tagList: [] as any,
+ visiableMusic: false,
+ musicOperation: 'add',
+ musicData: {} as any,
+ musicSheetCategories: [] as any,
+ musicPreview: false,
+ musicScore: null as any
+ const columns = (): any => {
+ title: '曲目名称',
+ <NDescriptionsItem label="曲目名称">
+ <TheTooltip content={row.musicSheetName} />{' '}
+ <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+ title: '曲目封面',
+ key: 'titleImg',
+ return <NImage width={60} height={60} src={row.titleImg} />
+ title: '曲目信息',
+ key: 'musicSheetCategoriesName',
+ <NDescriptionsItem label="曲目分类">
+ <TheTooltip content={row.musicSheetCategoriesName} />{' '}
+ <NDescriptionsItem label="伴奏类型">
+ {row.accompanimentType === 'HOMEMODE' ? '自制伴奏' : '普通伴奏'}
+ title: '可用声部',
+ key: 'musicSubject',
+ return <NTag type="primary">{row.musicSheetType === 'CONCERT' ? '合奏' :row.musicSubject}</NTag>
+ title: '播放类型',
+ key: 'audioType',
+ {row.audioType === 'MP3' && <NTag type="primary">MP3</NTag>}
+ {row.audioType === 'MIDI' && <NTag type="default">MIDI</NTag>}
+ title: '能否转简谱',
+ key: 'notation',
+ <NTag type={row.notation ? 'primary' : 'default'}>{row.notation ? '是' : '否'}</NTag>
+ title: '排序',
+ key: 'sortNumber'
+ fixed: 'right',
+ state.musicPreview = true
+ state.musicScore = row
+ 预览
+ //v-auth="musicSheet/status1612431726029942786"
+ //v-auth="musicSheet/update1602302618558099458"
+ state.visiableMusic = true
+ state.musicOperation = 'edit'
+ state.musicData = row
+ //v-auth="musicSheet/remove1602302689404088321"
+ {/* <NButton
+ v-auth="musicSheet/remove1599694768375701505"
+ {row.topFlag ? '取消置顶' : '置顶'}
+ </NButton> */}
+ await musicSheetStatus({
+ content: `删除"${row.musicSheetName}",是否继续?`,
+ await musicSheetRemove({ id: row.id })
+ const { data } = await musicSheetPage({ ...state.pagination, ...state.searchForm })
+ // 获取标签
+ const getTagList = async () => {
+ const { data } = await musicTagPage({ page: 1, rows: 999 })
+ const tempList = data.rows || []
+ tempList.forEach((item: any) => {
+ item.label = item.name
+ item.value = item.id
+ state.tagList = tempList
+ // 获取分类
+ const getMusicSheetCategorieList = async () => {
+ const { data } = await getMusicSheetCategories({ enable: true })
+ state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList')
+ } catch (e) {}
+ // 获取声部
+ const getSubjectList = async () => {
+ const { data } = await subjectBasicConfigPage({ page: 1, rows: 999 })
+ item.label = item.subjectName
+ item.value = item.subjectId
+ state.subjectList = tempList
+ watch(
+ () => props.searchId,
+ (val) => {
+ console.log(val, 'searchId')
+ // getTagList()
+ console.log()
+ if (props.searchId) {
+ state.searchForm.musicSheetCategoriesId = props.searchId || null
+ getSubjectList()
+ getMusicSheetCategorieList()
+ saveKey="music-list"
+ <NFormItem label="关键词" path="keyword">
+ placeholder="曲目编号/名称"
+ {/* <NFormItem label="标签" path="musicTag">
+ placeholder="请选择标签"
+ v-model:value={state.searchForm.musicTag}
+ options={state.tagList}
+ <NFormItem label="分类" path="musicSheetCategoriesId">
+ children-field="musicSheetCategoriesList"
+ v-model:value={state.searchForm.musicSheetCategoriesId}
+ options={state.musicSheetCategories}
+ <NFormItem label="声部" path="musicSubject">
+ placeholder="请选择声部"
+ v-model:value={state.searchForm.musicSubject}
+ options={state.subjectList}
+ <NFormItem label="播放类型" path="audioType">
+ placeholder="请选择播放类型"
+ v-model:value={state.searchForm.audioType}
+ options={audioTypeArray}
+ {/* <NFormItem label="伴奏类型" path="accompanimentType">
+ placeholder="请选择伴奏类型"
+ v-model:value={state.searchForm.accompanimentType}
+ options={accompanimentTypeArray}
+ //v-auth="musicSheet/save1602302550719426561"
+ state.musicOperation = 'add'
+ state.musicData = {}
+ 添加曲目
+ v-model:show={state.visiableMusic}
+ title={state.musicOperation === 'add' ? '新增曲目' : '修改曲目'}
+ style={{ width: '950px' }}
+ <MusicOperation
+ type={state.musicOperation}
+ data={state.musicData}
+ subjectList={state.subjectList}
+ musicSheetCategories={state.musicSheetCategories}
+ // tagList={state.tagList}
+ onClose={() => (state.visiableMusic = false)}
+ blockScroll={true}
+ v-model:show={state.musicPreview}
+ title={'曲目预览'}
+ style={{ width: 'auto' }}
+ <MusicPreView item={state.musicScore} />
@@ -0,0 +1,262 @@
+import { musicTagPage, musicTagRemove, musicTagState } from '../../api'
+import TagOperation from '../modal/tag-operation'
+ visiableTag: false,
+ tagOperation: 'add',
+ tagData: {} as any
+ // title: '平台乐谱启用/总数',
+ // key: 'enablePlatformMusicSheetNum',
+ // return `${row.enablePlatformMusicSheetNum}/${row.musicPlatformSheetNum}`
+ // title: '老师乐谱启用/总数',
+ // key: 'enableTeacherMusicSheetNum',
+ // return `${row.enableTeacherMusicSheetNum}/${row.musicTeacherSheetNum}`
+ key: 'state',
+ <NTag type={row.state ? 'primary' : 'default'}>{row.state ? '启用' : '停用'}</NTag>
+ onClick={() => onChangeState(row)}
+ //v-auth="musicTag/state1602302067191672833"
+ {row.state ? '停用' : '启用'}
+ //v-auth="musicTag/update1602301883418243073"
+ state.visiableTag = true
+ state.tagOperation = 'edit'
+ state.tagData = row
+ //v-auth="musicTag/remove1602301979954343937"
+ const onChangeState = (row: any) => {
+ // musicTag/state
+ const statusStr = row.state ? '停用' : '启用'
+ await musicTagState({
+ content: `删除"${row.name}",是否继续?`,
+ await musicTagRemove({ id: row.id })
+ const { data } = await musicTagPage({ ...state.pagination, ...state.searchForm })
+ saveKey="tag-list"
+ <NFormItem label="名称" path="keyword">
+ <NInput placeholder="请输入名称" v-model:value={state.searchForm.keyword} clearable />
+ //v-auth="musicTag/save1602301806310158338"
+ state.tagOperation = 'add'
+ state.tagData = {}
+ 添加曲目标签
+ v-model:show={state.visiableTag}
+ title={state.tagOperation === 'add' ? '新增曲目标签' : '修改曲目标签'}
+ <TagOperation
+ type={state.tagOperation}
+ data={state.tagData}
+ onClose={() => (state.visiableTag = false)}
@@ -0,0 +1,62 @@
+import MusicList from './component/music-list'
+import TagList from './component/tag-list'
+import CategroryList from '@/views/music-categrory/index'
+ tabName: 'MusicList' as 'MusicList' | 'TagList' | 'CategroryList',
+ searchId: null
+ const setTabName = (val: any) => {
+ console.log('setTabName', val)
+ state.tabName = val.tabName
+ state.searchId = val.id
+ <h2>乐谱管理</h2>
+ <NTabPane name="MusicList" tab="曲目管理"
+ //v-auth="musicSheet/page1602301588206350338"
+ <MusicList searchId={state.searchId} />
+ {/* <NTabPane name="TagList" tab="曲目标签管理" v-auth="musicTag/page1602301689389740033">
+ <TagList />
+ </NTabPane> */}
+ name="CategroryList"
+ tab="曲目分类管理"
+ //v-auth="/musicCategrory1607664813521346561"
+ <CategroryList onSetTabName={setTabName} />
@@ -0,0 +1,20 @@
+.audioSection {
+ position: relative;
+ padding-right: 20px;
+ border-radius: 12px;
+ padding-top: 24px;
+ .btnRemove {
+ position: absolute;
+ bottom: 12px;
+ right: 20px;
+.formContainer{
+ max-height: 80vh;
+ overflow-y: auto;
+ padding: 0 10px;
@@ -0,0 +1,976 @@
+ NRadio,
+ NAlert,
+ NInputGroupLabel
+import type { SelectOption } from 'naive-ui'
+import { musicSheetDetail, musicSheetSave, musicSheetUpdate } from '../../api'
+import deepClone from '@/utils/deep.clone'
+import axios from 'axios'
+ * 获取指定元素下一个Note元素
+ * @param ele 指定元素
+ * @param selectors 选择器
+const getNextNote = (ele: any, selectors: any) => {
+ let index = 0
+ const parentEle = ele.closest(selectors)
+ let pointer = parentEle
+ const measure = parentEle?.closest('measure')
+ let siblingNote = null
+ // 查找到相邻的第一个note元素
+ while (!siblingNote && index < (measure?.childNodes.length || 50)) {
+ index++
+ if (pointer?.nextElementSibling?.tagName === 'note') {
+ siblingNote = pointer?.nextElementSibling
+ pointer = pointer?.nextElementSibling
+ return siblingNote
+export const onlyVisible = (xml: any, partIndex: any) => {
+ if (!xml) return ''
+ const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+ const partList =
+ xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
+ const parts = xmlParse.getElementsByTagName('part')
+ const visiblePartInfo = partList[partIndex]
+ if (visiblePartInfo) {
+ const id = visiblePartInfo.getAttribute('id')
+ Array.from(parts).forEach((part) => {
+ if (part && part.getAttribute('id') !== id) {
+ part.parentNode?.removeChild(part)
+ // 不等于第一行才添加避免重复添加
+ // 最后一个小节的结束线元素不在最后 调整
+ if (part && part.getAttribute('id') === id) {
+ const barlines = part.getElementsByTagName('barline')
+ const lastParent = barlines[barlines.length - 1]?.parentElement
+ if (lastParent?.lastElementChild?.tagName !== 'barline') {
+ const children: any[] = (lastParent?.children as any) || []
+ for (let el of children) {
+ if (el.tagName === 'barline') {
+ // 将结束线元素放到最后
+ lastParent?.appendChild(el)
+ break
+ Array.from(partList).forEach((part) => {
+ // 处理装饰音问题
+ const notes = xmlParse.getElementsByTagName('note')
+ const getNextvNoteDuration = (i: any) => {
+ let nextNote = notes[i + 1]
+ // 可能存在多个装饰音问题,取下一个非装饰音时值
+ for (let index = i; index < notes.length; index++) {
+ const note = notes[index]
+ if (!note.getElementsByTagName('grace')?.length) {
+ nextNote = note
+ const nextNoteDuration = nextNote?.getElementsByTagName('duration')[0]
+ return nextNoteDuration
+ Array.from(notes).forEach((note, i) => {
+ const graces = note.getElementsByTagName('grace')
+ if (graces && graces.length) {
+ note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
+ return new XMLSerializer().serializeToString(xmlParse)
+const speedInfo = {
+ 'rall.': 1.333333333,
+ 'poco rit.': 1.333333333,
+ 'rit.': 1.333333333,
+ 'molto rit.': 1.333333333,
+ 'molto rall': 1.333333333,
+ molto: 1.333333333,
+ lentando: 1.333333333,
+ allargando: 1.333333333,
+ morendo: 1.333333333,
+ 'accel.': 0.8,
+ calando: 2,
+ 'poco accel.': 0.8,
+ 'gradually slowing': 1.333333333,
+ slowing: 1.333333333,
+ slow: 1.333333333,
+ slowly: 1.333333333,
+ faster: 1.333333333
+ * 按照xml进行减慢速度的计算
+ * @param xml 始终按照第一分谱进行减慢速度的计算
+export function getGradualLengthByXml(xml: string) {
+ const firstPartXml = onlyVisible(xml, 0)
+ const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
+ const measures = Array.from(xmlParse.querySelectorAll('measure'))
+ const notes = Array.from(xmlParse.querySelectorAll('note'))
+ const words = Array.from(xmlParse.querySelectorAll('words'))
+ const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
+ const eles = []
+ for (const ele of [...words, ...metronomes]) {
+ const note = getNextNote(ele, 'direction')
+ // console.log(ele, note)
+ if (note) {
+ const measure = note?.closest('measure')
+ const measureNotes = Array.from(measure.querySelectorAll('note'))
+ const noteInMeasureIndex = Array.from(measure.childNodes)
+ .filter((item: any) => item.nodeName === 'note')
+ .findIndex((item) => item === note)
+ let allDuration = 0
+ let leftDuration = 0
+ for (let i = 0; i < measureNotes.length; i++) {
+ const n: any = measureNotes[i]
+ const duration = +(n.querySelector('duration')?.textContent || '0')
+ allDuration += duration
+ if (i < noteInMeasureIndex) {
+ leftDuration = allDuration
+ eles.push({
+ ele,
+ index: notes.indexOf(note),
+ noteInMeasureIndex,
+ textContent: ele.textContent,
+ measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number')
+ type: ele.tagName,
+ allDuration,
+ leftDuration
+ // 结尾处手动插入一个音符节点
+ ele: notes[notes.length - 1],
+ index: notes.length,
+ noteInMeasureIndex: 0,
+ textContent: '',
+ type: 'metronome',
+ allDuration: 1,
+ leftDuration: 1,
+ measureIndex: measures.length
+ const gradualNotes: any[] = []
+ eles.sort((a, b) => a.index - b.index)
+ const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase())
+ let isLastNoteAndNotClosed = false
+ for (const ele of eles) {
+ const textContent: any = ele.textContent?.toLocaleLowerCase().trim()
+ if (ele === eles[eles.length - 1]) {
+ if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
+ isLastNoteAndNotClosed = true
+ const isKeyWork = keys.find((k) => {
+ const ks = k.split(' ')
+ return textContent && ks.includes(textContent)
+ if (
+ ele.type === 'metronome' ||
+ (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) ||
+ isLastNoteAndNotClosed
+ const indexOf = gradualNotes.findIndex((item) => item.length === 1)
+ if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
+ gradualNotes[indexOf][1] = {
+ start: ele.index,
+ measureIndex: ele.measureIndex,
+ noteInMeasureIndex: ele.noteInMeasureIndex,
+ allDuration: ele.allDuration,
+ leftDuration: ele.leftDuration,
+ type: textContent
+ if (ele.type === 'words' && isKeyWork) {
+ gradualNotes.push([
+ ])
+ return gradualNotes
+ tagList: {
+ type: Array as PropType<Array<SelectOption>>,
+ subjectList: {
+ musicSheetCategories: {
+ graduals: {} as any, // 渐变速度
+ audioType: 'MP3', // 播放类型
+ mp3Type: 'MP3', // 是否含节拍器
+ xmlFileUrl: null, // XML
+ midiUrl: null, // mid
+ metronomeUrl: null, // 伴奏 根据mp3Type 是否是为包含节拍器
+ musicSheetName: null, // 曲目名称
+ musicTag: [] as any, // 曲目标签
+ composer: null, // 音乐人
+ playSpeed: null, // 曲谱速度
+ showFingering: null as any, // 是否显示指法
+ canEvaluate: null as any, // 是否评测
+ musicSubject: null as any, // 可用声部
+ notation: null as any, // 能否转和简谱
+ auditVersion: null as any, // 审核版本
+ accompanimentType: null, // 伴奏类型
+ sortNumber: null, // 排序
+ titleImg: null, // 曲谱封面
+ remark: null, // 曲谱描述
+ background: [] as any, // 原音
+ musicSheetCategoriesId: null,
+ status: false,
+ musicSheetType: 'SINGLE' as 'SINGLE' | 'CONCERT'
+ tagList: [...props.tagList] as any, // 标签列表
+ xmlFirstSpeed: null as any, // 第一个音轨速度
+ partListNames: [] as any, // 所有音轨声部列表
+ musicSheetCategories: [...props.musicSheetCategories] as any
+ const gradualData = reactive({
+ list: [] as any[],
+ gradualRefs: [] as any[]
+ console.log(error, 'error')
+ if (error) {
+ message.error(error[0]?.[0]?.message)
+ const obj = {
+ ...forms,
+ musicTag: '-1',
+ extConfigJson: JSON.stringify({ gradualTimes: forms.graduals })
+ if (forms.audioType == 'MIDI') {
+ obj.background = []
+ await musicSheetSave({ ...obj })
+ await musicSheetUpdate({ ...obj, id: props.data.id })
+ } catch (e) {
+ console.log(e)
+ // 上传XML,初始化音轨 音轨速度
+ const readFileInputEventAsArrayBuffer = (file: any) => {
+ const xmlRead = new FileReader()
+ xmlRead.onload = (res) => {
+ gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter(
+ (item: any) => item.length === 2
+ } catch (error) {}
+ state.partListNames = getPartListNames(res?.target?.result as any) as any
+ // 这里是如果没有当前音轨就重新写
+ for (let j = 0; j < state.partListNames.length; j++) {
+ if (!forms.background[j]) {
+ forms.background.push({ audioFileUrl: null, track: null })
+ forms.background[j].track = state.partListNames[j].value
+ // 循环添加所在音轨的原音
+ for (let index = forms.background.length; index < state.partListNames.length; index++) {
+ const part = state.partListNames[index].value
+ const sysData = {
+ ...forms.background[0],
+ track: part
+ if (!sysData.speed) {
+ sysData.speed = state.xmlFirstSpeed
+ createSys(sysData)
+ if (forms.background.length == 0) {
+ forms.background.push({ audioFileUrl: '', track: '' })
+ xmlRead.readAsText(file)
+ // 获取xml中所有轨道
+ const getPartListNames = (xml: any) => {
+ if (!xml) return []
+ const partListNames = Array.from(partList).map((item) => {
+ const part = item.getElementsByTagName('part-name')?.[0].textContent || ''
+ return {
+ value: part,
+ label: part
+ if (partListNames.length > 0) {
+ forms.background = forms.background.slice(0, partListNames.length)
+ state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
+ if (!forms.playSpeed) {
+ forms.playSpeed = state.xmlFirstSpeed
+ return partListNames
+ // 判断选择的音轨是否在选中
+ const initPartsListStatus = (track: string): any => {
+ const _names = state.partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
+ const partListNames = deepClone(_names) || []
+ partListNames.forEach((item: any) => {
+ const index = forms.background.findIndex((ground: any) => item.value == ground.track)
+ if (index > -1 && track != item.value) {
+ item.disabled = true
+ item.disabled = false
+ return partListNames || []
+ // 添加原音
+ const createSys = (initData?: any) => {
+ forms.background.push({
+ audioFileUrl: null, // 原音
+ track: null, // 轨道
+ ...initData
+ // 删除原音
+ const removeSys = (index: number) => {
+ content: `是否确认删除此原音?`,
+ forms.background.splice(index, 1)
+ const detail = props.data
+ const { data } = await musicSheetDetail({ id: detail.id })
+ forms.audioType = data.audioType
+ forms.mp3Type = data.mp3Type
+ forms.xmlFileUrl = data.xmlFileUrl
+ forms.midiUrl = data.midiUrl
+ forms.metronomeUrl = data.metronomeUrl
+ forms.musicSheetName = data.musicSheetName
+ forms.musicTag = data.musicTag?.split(',')
+ forms.composer = data.composer
+ forms.playSpeed = data.playSpeed
+ forms.showFingering = Number(data.showFingering)
+ forms.canEvaluate = Number(data.canEvaluate)
+ forms.musicSubject = data.musicSubject ? Number(data.musicSubject) : null
+ forms.notation = Number(data.notation)
+ forms.auditVersion = Number(data.auditVersion)
+ forms.accompanimentType = data.accompanimentType
+ forms.sortNumber = data.sortNumber
+ forms.titleImg = data.titleImg
+ forms.remark = data.remark
+ forms.musicSheetCategoriesId = data.musicSheetCategoriesId
+ forms.background = data.background || []
+ forms.musicSheetType = data.musicSheetType || "SINGLE"
+ // 获取渐变 和 是否多声部
+ const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
+ forms.graduals = extConfigJson.gradualTimes || {}
+ axios.get(data.xmlFileUrl).then((res: any) => {
+ if (res?.data) {
+ gradualData.list = getGradualLengthByXml(res?.data as any).filter(
+ state.partListNames = getPartListNames(res?.data as any) as any
+ } catch (error) {
+ console.log(error)
+ class={styles.formContainer}
+ label-placement="left"
+ label-width="130"
+ label="播放类型"
+ path="audioType"
+ message: '请选择播放类型'
+ <NRadioGroup
+ v-model:value={forms.audioType}
+ onUpdateValue={(value: string | number | boolean) => {
+ console.log(value, 'value')
+ if (value === 'MP3') {
+ forms.mp3Type = 'MP3'
+ forms.mp3Type = ''
+ <NRadio value="MP3">MP3</NRadio>
+ <NRadio value="MIDI">MIDI</NRadio>
+ label="曲谱类型"
+ path="musicSheetType"
+ message: '请选择曲谱类型'
+ <NRadioGroup v-model:value={forms.musicSheetType}>
+ <NRadio value="SINGLE">单曲</NRadio>
+ <NRadio value="CONCERT">合奏</NRadio>
+ {/* 只有mp3 才有节拍器 */}
+ {forms.audioType === 'MP3' && (
+ label="是否含节拍器"
+ path="mp3Type"
+ message: '请选择是否含节拍器'
+ <NRadioGroup v-model:value={forms.mp3Type}>
+ <NRadio value="MP3">不含节拍器</NRadio>
+ <NRadio value="MP3_METRONOME">含节拍器</NRadio>
+ label="上传XML"
+ path="xmlFileUrl"
+ message: '请选择上传XML'
+ size={10}
+ v-model:fileList={forms.xmlFileUrl}
+ tips="仅支持上传.xml格式文件"
+ listType="image"
+ accept=".xml"
+ bucketName="cloud-coach"
+ text="点击上传XML"
+ onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
+ {forms.audioType === 'MIDI' && (
+ label="上传mid"
+ path="midiUrl"
+ message: '请选择上传mid'
+ v-model:fileList={forms.midiUrl}
+ tips="仅支持上传.mid格式文件"
+ accept=".mid"
+ text="点击上传mid"
+ {/* 只有播放类型为mp3时才会有伴奏 */}
+ label={`伴奏(${forms.mp3Type === 'MP3' ? '不' : ''}含节拍器)`}
+ path="metronomeUrl"
+ message: '请选择上传伴奏'
+ v-model:fileList={forms.metronomeUrl}
+ tips="仅支持上传.mp3/.aac格式文件"
+ accept=".mp3,.aac"
+ text={`点击上传伴奏(${forms.mp3Type === 'MP3' ? '不' : ''}含节拍器)`}
+ label="曲目名称"
+ path="musicSheetName"
+ message: '请输入曲目名称'
+ <NInput v-model:value={forms.musicSheetName} placeholder="请输入曲目名称" />
+ {/* <NFormItemGi
+ label="曲目标签"
+ path="musicTag"
+ message: '请选择曲目标签'
+ placeholder="请选择曲目标签"
+ v-model:value={forms.musicTag}
+ multiple
+ maxTagCount="responsive"
+ onUpdate:value={(value: any) => {
+ // 判断条数
+ if (value.length === 3) {
+ state.tagList.forEach((item: any) => {
+ forms.musicTag &&
+ typeof value === 'object' &&
+ value.includes(item.value)
+ </NFormItemGi> */}
+ label="曲目分类"
+ path="musicSheetCategoriesId"
+ message: '请选择曲目分类'
+ v-model:value={forms.musicSheetCategoriesId}
+ <NFormItemGi label="音乐人" path="composer">
+ <NInput v-model:value={forms.composer} placeholder="请输入音乐人姓名" />
+ {forms.musicSheetType != 'CONCERT' && <NFormItemGi
+ label="可用声部"
+ path="musicSubject"
+ message: '请选择可用声部'
+ v-model:value={forms.musicSubject}
+ options={props.subjectList}
+ placeholder="请选择可用声部"
+ </NFormItemGi>}
+ label="指法展示"
+ path="showFingering"
+ message: '请选择指法展示'
+ <NRadioGroup v-model:value={forms.showFingering}>
+ <NRadio value={1}>展示</NRadio>
+ <NRadio value={0}>不展示</NRadio>
+ label="是否评测"
+ path="canEvaluate"
+ message: '请选择是否评测'
+ <NRadioGroup v-model:value={forms.canEvaluate}>
+ <NRadio value={1}>是</NRadio>
+ <NRadio value={0}>否</NRadio>
+ label="能否转简谱"
+ path="notation"
+ message: '请选择能否转简谱'
+ <NRadioGroup v-model:value={forms.notation}>
+ label="伴奏类型"
+ path="accompanimentType"
+ message: '请选择伴奏类型'
+ <NRadioGroup v-model:value={forms.accompanimentType}>
+ <NRadio value="HOMEMODE">自制伴奏</NRadio>
+ <NRadio value="COMMON">普通伴奏</NRadio>
+ path="sortNumber"
+ v-model:value={forms.sortNumber}
+ <NFormItemGi label="曲谱封面" path="titleImg">
+ tips="请上传大小2M以内的JPG、PNG图片"
+ v-model:fileList={forms.titleImg}
+ autoCrop: true, //是否默认生成截图框
+ enlarge: 2, // 图片放大倍数
+ autoCropWidth: 200, //默框高度
+ fixedBox: true, //是否固定截图框大认生成截图框宽度
+ autoCropHeight: 200, //默认生成截图小 不允许改变
+ previewsCircle: false, //预览图是否是原圆形
+ title: '曲谱封面'
+ <NFormItemGi label="曲谱描述" path="remark">
+ placeholder="请输入曲谱描述"
+ rows={4}
+ maxlength={200}
+ v-model:value={forms.remark}
+ {!!gradualData.list.length && (
+ <NAlert showIcon={false} type="info">
+ 识别到共1处渐变速度,请输入Dorico对应小节时间信息
+ </NAlert>
+ <NFormItem label="rit." required style={{ marginTop: '10px' }}>
+ <NSpace vertical>
+ {gradualData.list.map((n: any, ni: number) => (
+ path={`graduals.${n[0].measureIndex}`}
+ { required: true, message: '请输入合奏曲目时间' },
+ pattern: /^((\d{2}):?){2,3}$/,
+ message: '请输入正确的曲目时间',
+ trigger: 'blur'
+ <NInputGroupLabel>{n[0].measureIndex}小节开始</NInputGroupLabel>
+ placeholder="00:00:00"
+ v-model:value={forms.graduals[n[0].measureIndex]}
+ <div style={{ lineHeight: '30px', padding: '0 4px' }}>~</div>
+ path={`graduals.${n[1].measureIndex}`}
+ v-model:value={forms.graduals[n[1].measureIndex]}
+ <NInputGroupLabel>{n[1].measureIndex}小节结束</NInputGroupLabel>
+ <NFormItem label="曲谱速度">
+ <NInputNumber v-model:value={forms.playSpeed} showButton={false} />
+ {/* 只有播放类型为mp3时才会有原音 */}
+ {forms.background.map((item: any, index: number) => (
+ {item.track?.toLocaleUpperCase?.() != 'COMMON' && <NGrid class={styles.audioSection}>
+ span={12}
+ label="原音"
+ path={`background[${index}].audioFileUrl`}
+ message: `请上传${
+ item.track ? item.track + '的' : '第' + (index + 1) + '个'
+ }原音`
+ v-model:fileList={item.audioFileUrl}
+ {state.partListNames.length > 1 && (
+ label="所属轨道"
+ path={`background[${index}].track`}
+ message: '请选择所属轨道'
+ placeholder="请选择所属轨道"
+ v-model:value={item.track}
+ options={initPartsListStatus(item.track)}
+ <NGi class={styles.btnRemove}>
+ disabled={forms.background.length === 1}
+ onClick={() => removeSys(index)}
+ </NGrid>}
+ dashed
+ disabled={state.partListNames.length <= forms.background.length}
+ style={{
+ marginBottom: '24px'
+ onClick={createSys}
+ 添加原音
@@ -0,0 +1,23 @@
+import { defineComponent } from 'vue'
+import { useUserStore } from '@/store/modules/user'
+ name: 'musicPreView',
+ item: {
+ type: Object,
+ const userStore = useUserStore()
+ const token = userStore.getToken
+ const prefix = /(localhost|192)/.test(location.host) ? 'https://ponline.colexiu.com' : location.origin
+ const src = prefix + `/orchestra-music-score/?_t=${Date.now()}&id=${props.item.id}&modelType=practice&modeType=json&Authorization=${token}`
+ <div>
+ <iframe width={'667px'} height={'375px'} frameborder="0" src={src}></iframe>
@@ -0,0 +1,125 @@
+ NInputNumber
+import { musicTagSave, musicTagUpdate } from '../../api'
+ sortNumber: null
+ await musicTagSave({ ...forms })
+ await musicTagUpdate({ ...forms, id: props.data.id })
+ label="名称"
+ message: '请输入名称'
+ v-model:value={forms.name}
+ placeholder="请输入名称"
+ maxlength={6}
+ <NFormItem label="排序" path="sortNumber">
+ placeholder="请输入排序"
@@ -0,0 +1,131 @@
+import { clearEmtryData } from '@/utils'
+ * 获取素材列表
+export const fetchMaterialList = (data: object) => {
+ url: '/cbs-app/material/page',
+ data
+ * 素材分类分页
+export const fetchCategoryList: any = (data: any) => {
+ return new Promise((resolve) => {
+ request({
+ url: '/cbs-app/materialCategory/page',
+ data:{
+ ...data,
+ rows: 1000
+ }).then((res) => {
+ if (res?.data){
+ res.data.rows = clearEmtryData(res.data.rows, 'subMaterialCategoryList')
+ resolve(res)
+ .catch(() => {
+ resolve({})
+ * 添加素材
+export const materialSave = (data: any) => {
+ url: '/cbs-app/material/save',
+ * 删除素材
+export const deleteMaterial = (data: any) => {
+ url: '/cbs-app/material/remove?id=' + data,
+ * 素材详情
+export const fetchMaterailDetail = (data: any) => {
+ url: `/cbs-app/material/detail/${data}`,
+ * 修改素材
+export const updateMaterailData = (data: any) => {
+ url: `/cbs-app/material/update`,
+ * 教学素材分类分页
+export const materialCategoryPage = (data: any) => {
+ url: `/cbs-app/materialCategory/page`,
+ * 教学素材分类详情
+export const materialCategoryDetail = (data: any) => {
+ url: `/cbs-app/materialCategory/detail/${data}`,
+ * 教学素材分类新增
+ export const materialCategorySave = (data: any) => {
+ url: `/cbs-app/materialCategory/save`,
+ * 教学素材分类删除
+ export const materialCategoryRemove = (data: any) => {
+ url: `/cbs-app/materialCategory/remove?id=` + data,
+ * 教学素材分类修改
+ export const materialCategoryUpdate = (data: any) => {
+ url: `/cbs-app/materialCategory/update`,
@@ -0,0 +1,269 @@
+ DataTableColumn,
+import { materialCategoryPage, materialCategoryRemove } from '../api'
+import styles from '../index.module.less'
+import AddCategory from '../model/addCategory'
+ name: 'category-list',
+ setup(props) {
+ rows: 10000
+ materail: null as any
+ const searchForm = reactive({
+ keyword: '',
+ times: null as any,
+ operatorId: '' //创建人
+ const columns = (): DataTableColumn[] => {
+ title: '分类名称',
+ key: 'name',
+ fixed: 'left'
+ title: '分类描述',
+ key: 'desc'
+ title: '上传时间',
+ key: 'updateTime',
+ return row.updateTime || row.createTime
+ width: 180,
+ //v-auth="materialCategory/save1599962104022290433"
+ state.materail = {
+ parentId: row.id
+ 添加子分类
+ //v-auth="materialCategory/update1599962354053140482"
+ state.cityType = 'update'
+ state.materail = row
+ //v-auth="materialCategory/remove1599962245085122562"
+ onClick={() => handleDelete(row)}
+ const { times, ...reset } = searchForm
+ const body = {
+ ...reset,
+ ...getTimes(times, ['startTime', 'endTime']),
+ page: state.pagination.page,
+ rows: state.pagination.rows
+ const { data } = await materialCategoryPage(body)
+ const handleDelete = async (row: any) => {
+ content: '是否确认删除此分类?',
+ const res = await materialCategoryRemove(row.id)
+ if (res.data) {
+ onSubmit()
+ model={searchForm}
+ onSetModel={(val: any) => Object.assign(searchForm, val)}
+ saveKey="category-key"
+ <NFormItem path="keyword" label="分类名称">
+ placeholder="分类名称"
+ v-model:value={searchForm.keyword}
+ onKeydown={(e) => {
+ if (e.code === 'Enter') {
+ {/* <NFormItem path="keyword">
+ placeholder="更新人"
+ v-model:value={searchForm.operatorId}
+ <NFormItem path="times" label="时间">
+ class={styles.datepicker}
+ value-format="yyyy.MM.dd"
+ v-model:value={searchForm.times}
+ state.materail = null
+ disabled={state.loading}
+ 新增分类
+ scroll-x="1300"
+ children-key="subMaterialCategoryList"
+ default-expand-all={false}
+ row-key={(row: any) => row.id}
+ title={state.cityType === 'add' ? '新增分类' : '修改分类'}
+ style={{ width: '400px' }}
+ onClose={() => {
+ <AddCategory
+ item={state.materail}
+ list={state.dataList}
+ isAdd={state.cityType === 'add' ? true : false}
+ state.visiableCity = false
+ onHandleSuccess={() => {
+ if (!state.materail) {
@@ -0,0 +1,450 @@
+import { defineComponent, inject, onMounted, reactive, ref, provide } from 'vue'
+import { fetchCategoryList, fetchMaterialList, deleteMaterial, updateMaterailData } from '../api'
+import { InternalRowData } from 'naive-ui/es/data-table/src/interface'
+import AddMaterial from '../model/addMaterial'
+import { getLessonType, lessonType } from '@/views/knowledge-manage/knowledgeTypeData'
+import { filterTimes, getTimes } from '@/utils/dateUtil'
+import router from '@/router'
+const classType: { [_: string]: any } = {
+ VIDEO: '视频',
+ IMG: '图片',
+ SONG: '曲目'
+const classTypeList = Object.keys(classType).map((key: string) => {
+ return { label: classType[key], value: key }
+ name: 'material-list',
+ pageTotal: 10
+ materail: null,
+ isLook: false,
+ employeeList: [] as any
+ keyword: null as any,
+ time: null as any,
+ courseTypeCode: null,
+ materialCategoryId: null,
+ type: null
+ // 获取分类列表
+ const category = reactive({
+ list: [] as any,
+ index: -1
+ const getCategoryList = async () => {
+ const { data } = await fetchCategoryList({ page: 1 })
+ category.list = data?.rows || []
+ provide('categoryList', category)
+ // 声部
+ const subjects = reactive({
+ list: [] as any
+ const getSubjects = async () => {
+ const { data } = await subjectBasicConfigPage({ page: 1, row: 1000 })
+ subjects.list = data?.rows || []
+ provide('subjects', subjects)
+ console.log(route.query, 'route.query')
+ if (route.query.id) {
+ searchForm.keyword = route.query.id as string
+ getCategoryList()
+ getSubjects()
+ title: '素材名称',
+ <NDescriptionsItem label="素材名称">{row.name}</NDescriptionsItem>
+ <NDescriptionsItem label="素材编号">{row.id}</NDescriptionsItem>
+ <NDescriptionsItem label="素材分类">{row.materialCategoryName}</NDescriptionsItem>
+ // title: '编号',
+ // key: 'id'
+ // title: '素材分类',
+ // key: 'materialCategoryName'
+ // }, 分段编号
+ title: '素材信息',
+ key: 'sn',
+ <NDescriptionsItem label="分段编号">{row.sn}</NDescriptionsItem>
+ <NDescriptionsItem label="素材类型">
+ {classType[row.type as string]}
+ {/* <NDescriptionsItem label="课程类型">{row.courseTypeName}</NDescriptionsItem> */}
+ title: '课程信息',
+ key: 'type',
+ <NDescriptionsItem label="课程类型">{row.courseTypeName}</NDescriptionsItem>
+ {/*
+ <NDescriptionsItem label="建议时长">
+ {row.adviseStudyTimeSecond ? row.adviseStudyTimeSecond + '秒' : ''}
+ // title: '课程类型',
+ // key: 'courseTypeName',
+ // width: 200,
+ // ellipsis: true,
+ // render(row: InternalRowData) {
+ // return (
+ // <NTooltip placement="top-start">
+ // {{
+ // default: () => row.courseTypeName,
+ // trigger: () => row.courseTypeName
+ // }}
+ // </NTooltip>
+ // )
+ // title: '建议学习时长',
+ // key: 'adviseStudyTimeSecond',
+ // return row.adviseStudyTimeSecond ? row.adviseStudyTimeSecond + '秒' : ''
+ title: '操作信息',
+ <NDescriptionsItem label="上传人">{row.operatorName}</NDescriptionsItem>
+ <NDescriptionsItem label="上传时间">
+ {row.updateTime ? row.updateTime : '--'}
+ // title: '上传人',
+ // key: 'operatorName'
+ key: 'delFlag',
+ render(row: InternalRowData) {
+ <NTag type={row.enableFlag ? 'success' : 'warning'}>
+ {row.enableFlag ? '启用' : '停用'}
+ width: 160,
+ type={row.enableFlag ? 'error' : 'success'}
+ v-auth="material/update1599948375910109186"
+ onClick={() => handleChange(row)}
+ {row.enableFlag ? '停用' : '启用'}
+ state.isLook = true
+ 查看
+ //v-auth="material/update1599948375910109186"
+ disabled={row.enableFlag}
+ //v-auth="material/remove1599948195949301762"
+ const { time, ...reset } = searchForm
+ ...filterTimes(time, ['startTime', 'endTime']),
+ const { data } = await fetchMaterialList(body)
+ content: '是否确认删除此素材?',
+ const res = await deleteMaterial(row.id)
+ // 启用,停用
+ const handleChange = (row: any) => {
+ content: row.enableFlag ? '是否停用此素材?' : '是否启用此素材?',
+ await updateMaterailData({ id: row.id, enableFlag: row.enableFlag ? 0 : 1 })
+ saveKey="material-key"
+ <NFormItem label="搜索条件" path="keyword">
+ placeholder={'素材名称/编号'}
+ <NFormItem label="素材分类" path="materialCategoryId">
+ v-model:value={searchForm.materialCategoryId}
+ options={category.list}
+ childrenField="subMaterialCategoryList"
+ <NFormItem label="素材类型" path="type">
+ <NSelect clearable v-model:value={searchForm.type} options={classTypeList} />
+ <NFormItem label="课程类型" path="courseTypeCode">
+ <NSelect clearable v-model:value={searchForm.courseTypeCode} options={lessonType} />
+ <NFormItem label="搜索时间" path="time">
+ v-model:value={searchForm.time}
+ //v-auth="material/save1599948016487616513"
+ 添加素材
+ title={state.cityType === 'add' ? '添加素材' : '修改素材'}
+ style={{ width: '700px' }}
+ state.isLook = false
+ {state.visiableCity && (
+ <AddMaterial
+ isLook={state.isLook}
@@ -0,0 +1,5 @@
+export enum materialType {
+ 视频 = 'VIDEO',
+ 图片 = 'IMG',
+ 曲目 = 'SONG'
@@ -0,0 +1,8 @@
+.datepicker {
+ width: 240px !important;
+ :global {
+ .n-input {
+ width: auto !important;
@@ -0,0 +1,54 @@
+import { defineComponent, reactive } from 'vue'
+import CategoryList from './component/category-list'
+import MaterialList from './component/material-list'
+ name: 'educational-manage',
+ tabName: 'MaterialList' as 'MaterialList' | 'CategoryList'
+ name="MaterialList"
+ tab="素材列表"
+ //v-auth="cityFeeSetting/page1597885815002091522"
+ <MaterialList />
+ name="CategoryList"
+ tab="素材分类"
+ //v-auth="orchestraSubsidyStandard/page1597886618878201858"
+ <CategoryList />
@@ -0,0 +1,107 @@
+ FormInst,
+ FormItemRule,
+ FormRules,
+ NSpin,
+import { materialCategorySave, materialCategoryUpdate } from '../api'
+ name: 'addCategory',
+ emits: ['handleSuccess', 'close'],
+ props: ['item', 'list', 'isAdd'],
+ const loading = ref(false)
+ const formRef = ref<FormInst | null>(null)
+ const parentList = ref([
+ { name: '顶级分类', id: '0' },
+ ...filterPointCategory(props.list, 'subMaterialCategoryList')
+ ] as any)
+ const saveModel = reactive({
+ id: props?.item?.id ? props?.item?.id : null,
+ name: props?.item?.name || '',
+ desc: props?.item?.desc || '', //描述
+ parentId: props?.item?.parentId || '0'
+ const rules: FormRules = {
+ name: [{ required: true, message: '请填写素材名称', trigger: ['blur', 'change'] }],
+ desc: [{ required: true, message: '请填写素材描述', trigger: ['blur', 'change'] }]
+ const submit = () => {
+ formRef.value?.validate(async (err) => {
+ if (!err) {
+ const params: any = {
+ ...saveModel
+ let res: any = null
+ console.log(params)
+ if (saveModel.id) {
+ res = await materialCategoryUpdate(params)
+ res = await materialCategorySave(params)
+ if (res?.code == 200) {
+ message.success('保存成功')
+ emit('handleSuccess')
+ message.warning('保存失败')
+ <NSpin show={loading.value}>
+ ref={formRef}
+ onSubmit={submit}
+ labelPlacement="top"
+ model={saveModel}
+ rules={rules}
+ require-mark-placement="left"
+ {props.isAdd && (
+ <NFormItem label="父级分类" path="parentId">
+ placeholder="请选择父级分类"
+ v-model:value={saveModel.parentId}
+ options={parentList.value}
+ checkStrategy="all"
+ <NFormItem label="素材名称" path="name">
+ <NInput v-model:value={saveModel.name} />
+ <NFormItem label="分类描述" path="desc">
+ <NInput v-model:value={saveModel.desc} />
+ <NButton onClick={() => emit('close')}>取消</NButton>
+ <NButton type="primary" onClick={submit}>
+ </NSpin>
@@ -0,0 +1,285 @@
+ NDrawer,
+ NDrawerContent,
+ NSwitch,
+ NUpload,
+import { defineComponent, inject, onMounted, reactive, ref, watch } from 'vue'
+import { fetchMaterailDetail, materialSave, updateMaterailData } from '../api'
+import { materialType } from '../educationalData'
+import { lessonType } from '@/views/knowledge-manage/knowledgeTypeData'
+import SelectMusicSheet from './selectMusicSheet'
+ name: 'addMaterial',
+ props: ['item', 'isLook'],
+ const formContentRef = ref<any>()
+ name: '',
+ sn: '', //序号
+ materialCategoryId: '', //素材分类
+ // adviseStudyTimeSecond: null,
+ type: materialType.视频,
+ content: '', // 视频、图片链接或者是曲目编号
+ courseTypeCode: [], // 课程类型
+ enableFlag: true //启用状态
+ if (props?.item?.id) {
+ loading.value = true
+ const res: any = await fetchMaterailDetail(props.item.id)
+ Object.keys(saveModel).forEach((key: any) => {
+ if (res?.data?.[key]) {
+ if (key === 'adviseStudyTimeSecond') (saveModel as any)[key] = res.data[key] + ''
+ else if (key === 'courseTypeCode')
+ (saveModel as any)[key] = res.data[key]?.split(',') || []
+ else (saveModel as any)[key] = res.data[key]
+ if (key === 'enableFlag') (saveModel as any)[key] = res.data[key]
+ if (saveModel.type === 'SONG') {
+ musicOpentions.music = {
+ musicSheetName: res.data.contentDesc
+ console.log('🚀 ~ saveModel', saveModel)
+ loading.value = false
+ function validateContent(rule: FormItemRule, value: string) {
+ return saveModel.type === materialType.视频
+ ? new Error('请上传视频')
+ : saveModel.type === materialType.图片
+ ? new Error('请上传图片')
+ : new Error('请选择曲目')
+ return true
+ function validateAdviseStudyTimeSecond(rule: FormItemRule, value: string) {
+ return new Error('请填写时间')
+ if (!/^\+?[1-9]\d*$/.test(value)) {
+ return new Error('请填写正确的时间')
+ function validateCourseTypeCode(rule: FormItemRule, value: []) {
+ if (Array.isArray(value) && !value.length) {
+ return new Error('请选择课程类型')
+ sn: [{ required: true, message: '分段编号', trigger: ['blur', 'change'] }],
+ materialCategoryId: [
+ { required: true, message: '请选择素材分类', trigger: ['blur', 'change'] }
+ ],
+ adviseStudyTimeSecond: [
+ validator: validateAdviseStudyTimeSecond,
+ trigger: ['change']
+ content: [{ validator: validateContent, trigger: ['blur', 'change', 'input'] }],
+ courseTypeCode: [
+ validator: validateCourseTypeCode,
+ message: '请选择课程类型',
+ trigger: ['blur', 'change']
+ const categoryList = inject('categoryList', { list: [] }).list || []
+ ...saveModel,
+ courseTypeCode: saveModel.courseTypeCode?.join(',') || ''
+ params.id = props.item.id
+ res = await updateMaterailData(params)
+ res = await materialSave(params)
+ // 改变素材类型
+ const changeType = () => {
+ saveModel.content = ''
+ musicOpentions.music = ''
+ // 添加曲谱
+ const musicOpentions = reactive({
+ show: false,
+ music: '' as any
+ const hanldeSelectMusic = (musicItem: any) => {
+ musicOpentions.music = musicItem
+ saveModel.content = musicItem.id
+ musicOpentions.show = false
+ formContentRef.value?.restoreValidation()
+ // console.log('🚀 ~ musicItem', musicItem)
+ disabled={props.isLook ? true : false}
+ <NSpace justify="space-between" item-style={{ flex: 1 }}>
+ <NFormItem label="分段编号" path="sn">
+ <NInput v-model:value={saveModel.sn} />
+ v-model:value={saveModel.materialCategoryId}
+ options={categoryList}
+ {/* <NFormItem label="建议学习时长" path="adviseStudyTimeSecond" required>
+ v-model:value={saveModel.adviseStudyTimeSecond}
+ v-slots={{
+ suffix: () => '秒'
+ disabled={props?.item ? true : false}
+ value={saveModel.courseTypeCode}
+ saveModel.courseTypeCode = val
+ options={lessonType}
+ <NFormItem label="是否启用">
+ <NSwitch v-model:value={saveModel.enableFlag} />
+ <NFormItem label="素材类型" required labelPlacement="left" path="type">
+ <NRadioGroup v-model:value={saveModel.type} onUpdateValue={() => changeType()}>
+ <NRadio value={materialType.视频}>视频</NRadio>
+ <NRadio value={materialType.图片}>图片</NRadio>
+ <NRadio value={materialType.曲目}>曲目</NRadio>
+ <NFormItem label="上传素材" path="content" ref={formContentRef} required>
+ {saveModel.type === materialType.曲目 ? (
+ disabled={props.isLook}
+ onClick={() => (musicOpentions.show = true)}
+ 选择曲谱
+ {musicOpentions.music && (
+ <NInput readonly value={musicOpentions.music?.musicSheetName}></NInput>
+ ) : (
+ accept={
+ saveModel.type === materialType.视频
+ ? 'video/*'
+ ? 'image/*'
+ : ''
+ path="courseware/"
+ listType={saveModel.type === materialType.图片 ? 'image-card' : 'image'}
+ v-model:fileList={saveModel.content}
+ size={1024}
+ onReadFileInputEventAsArrayBuffer={() => {
+ ></UploadFile>
+ {props.isLook ? (
+ ''
+ <NDrawer width="80vw" height="100vh" v-model:show={musicOpentions.show}>
+ <NDrawerContent title="选择曲谱" closable>
+ <SelectMusicSheet onSelect={(row: any) => hanldeSelectMusic(row)} />
+ </NDrawerContent>
+ </NDrawer>
+.selectMusicSheet{
+ :global{
+ .section-container.section-save-form{
+ padding: 0;
+ margin: 0;
+ .n-form.n-form--inline .n-form-item{
+ width: 30%;
@@ -0,0 +1,245 @@
+import { musicSheetPage, musicTagPage } from '@/views/content-manage/api'
+import { NButton, NImage, NSpace, NTag, NDataTable, NFormItem, NInput, NSelect } from 'naive-ui'
+ name: 'selectMusicSheet',
+ emits: ['select'],
+ topFlag: null
+ musicData: {} as any
+ return `${row.musicSheetName}(${row.id})`
+ return <NImage width={35} height={35} src={row.titleImg} />
+ title: '伴奏类型',
+ key: 'accompanimentType',
+ return row.accompanimentType === 'HOMEMODE' ? '自制伴奏' : '普通伴奏'
+ return <NTag type="primary">{row.musicSubject}</NTag>
+ // title: '排序',
+ // key: 'sortNumber'
+ <NButton type="success" size="small" text onClick={() => emit('select', row)}>
+ 添加
+ const { data } = await musicSheetPage({ page: state.pagination.page, rows: state.pagination.rows, ...state.searchForm,status: 1 })
+ getTagList()
+ <div class={styles.selectMusicSheet}>
+ saveKey="select-music-list"
+ <NFormItem label="标签" path="musicTag">
+ <NFormItem label="伴奏类型" path="accompanimentType">
+ scrollX={1200}
@@ -0,0 +1,114 @@
+ * 获取知识点列表
+export const fetchKnowledgeList = (data: object) => {
+ url: '/cbs-app/knowledgePoint/page',
+ data: data
+ * 新增知识点
+export const knowledgePointSave = (data: object) => {
+ url: '/cbs-app/knowledgePoint/save',
+// 删除知识点
+export const fetchDelKnowledge = (data: object) => {
+ url: '/cbs-app/knowledgePoint/remove?id=' + data,
+ * 知识点详情
+export const knowledgePointDetail = (data: any) => {
+ url: `/cbs-app/knowledgePoint/detail/${data}`,
+ * 知识点修改
+export const knowledgePointUpdate = (data: any) => {
+ url: `/cbs-app/knowledgePoint/update`,
+ * 素材知识点关联列表
+export const knowledgePointMaterialRelationPage = (data: any) => {
+ url: `/cbs-app/knowledgePointMaterialRelation/page`,
+ * 素材知识点移动数据存储
+export const knowledgePointMaterialRelationUpdate = (data: any) => {
+ url: `/cbs-app/knowledgePointMaterialRelation/update`,
+ * 知识点删除素材关联
+export const knowledgePointMaterialRelationRemove = (data: any) => {
+ url: `/cbs-app/knowledgePointMaterialRelation/remove`,
+ * 知识点素材分页查询
+export const knowledgePointMaterialRelationMaterialPage = (data: any) => {
+ url: `/cbs-app/knowledgePointMaterialRelation/materialPage`,
+ * 素材关联知识点
+export const knowledgePointMaterialRelationSave = (data: any) => {
+ url: `/cbs-app/knowledgePointMaterialRelation/save`,
+ * 知识点启用停用
+export const knowledgePointStatus = (data: any) => {
+ url: `/cbs-app/knowledgePoint/status?id=${data}`,
@@ -0,0 +1,18 @@
+:global {
+ .n-cascader-submenu {
+ width: 260px !important;
+.loading {
+ animation: rotate 1s infinite linear;
+ color: #2d8cf0;
+@keyframes rotate {
+ 50% {
+ transform: rotate(180deg);
+ 100% {
+ transform: rotate(360deg);
@@ -0,0 +1,232 @@
+import TheLink from '@/components/TheLink'
+ NCard,
+ NPageHeader,
+import { defineComponent, onMounted, reactive } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+ knowledgePointMaterialRelationPage,
+ knowledgePointMaterialRelationRemove,
+ knowledgePointMaterialRelationUpdate
+} from '../api'
+import { knowledgeTypeData } from '../knowledgeTypeData'
+import AddMaterialKnowledge from './model/addMaterialKnowledge'
+ name: 'knowledgeDetail',
+ const router = useRouter()
+ const parentData = reactive<{ name: string; id: any; courseTypeCode: any }>({
+ name: route.query.name as string,
+ id: route.query.id,
+ courseTypeCode: route.query.courseTypeCode as any
+ checkList: [] as string[],
+ isMover: false,
+ visiableKnowledge: false,
+ modalType: 'add',
+ materail: null
+ type: 'selection'
+ // key: 'relOrder'
+ key: 'materialName',
+ <TheLink
+ to={{ path: '/educationalManage/educationalManage', query: { id: row.materialId } }}
+ {row.materialName}
+ </TheLink>
+ title: '素材分类',
+ key: 'materialCategoryName'
+ title: '分段编号',
+ return <TheLink to={{ path: '/educationalManage/educationalManage' }}>{row.sn}</TheLink>
+ title: '课程类型',
+ key: 'courseTypeName'
+ render(row: InternalRowData, rowIndex: number) {
+ disabled={rowIndex === 0}
+ onClick={() => handleRowMove('up', rowIndex)}
+ 上移
+ disabled={rowIndex === state.dataList.length - 1}
+ onClick={() => handleRowMove('down', rowIndex)}
+ 下移
+ const { data } = await knowledgePointMaterialRelationPage({
+ knowledgePointId: parentData.id,
+ rows: 9999
+ if (Array.isArray(data?.rows)) {
+ state.dataList = data.rows.map((n: any, i: number) => ({ ...n, relOrder: i + 1 }))
+ // 批量删除
+ const hanldeDelete = () => {
+ const d = dialog.warning({
+ content: '是否确认删除这些素材知识点关联?',
+ d.loading = true
+ const res: any = await knowledgePointMaterialRelationRemove(state.checkList)
+ d.loading = false
+ state.checkList = []
+ // 表格行移动
+ const handleRowMove = (type: 'up' | 'down', index: number) => {
+ if (type === 'up') {
+ if (index != 0) {
+ state.dataList[index] = state.dataList.splice(index - 1, 1, state.dataList[index])[0]
+ state.dataList.push(state.dataList.shift())
+ if (index != state.dataList.length - 1) {
+ state.dataList[index] = state.dataList.splice(index + 1, 1, state.dataList[index])[0]
+ state.dataList.unshift(state.dataList.splice(index, 1)[0])
+ state.isMover = true
+ handleSaveRow()
+ // 记录排序后的位置
+ const handleSaveRow = () => {
+ const body = state.dataList.map((n: any, i: number) => {
+ materialId: n.materialId,
+ relOrder: i + 1,
+ id: n.id
+ knowledgePointMaterialRelationUpdate(body)
+ <div class="section-container">
+ <NPageHeader
+ on-back={() => router.push('/educationalManage/knowledgeManage')}
+ title={parentData.name}
+ ></NPageHeader>
+ <NSpace style={{ padding: '15px 0' }}>
+ //v-auth="knowledgePointMaterialRelation/save1604770193787420673"
+ onClick={() => (state.visiableKnowledge = true)}
+ 新增素材
+ //v-auth="knowledgePointMaterialRelation/remove1604770313480273922"
+ type="error"
+ disabled={state.checkList.length ? false : true}
+ onClick={() => hanldeDelete()}
+ 批量删除
+ {/* <NButton>保存移动的排序</NButton> */}
+ maxHeight="calc(100vh - 290px)"
+ rowKey={(row: any) => row.id}
+ onUpdateCheckedRowKeys={(value: any) => {
+ state.checkList = value
+ <NDrawer width="80vw" v-model:show={state.visiableKnowledge}>
+ <NDrawerContent title="新增素材关联" closable>
+ <AddMaterialKnowledge
+ parentData={parentData}
+ state.visiableKnowledge = false
@@ -0,0 +1,288 @@
+ NCol,
+ NRow,
+ NEmpty,
+ NIcon
+import { fetchCategoryList } from '@/views/educational-manage/api'
+ knowledgePointMaterialRelationMaterialPage,
+ knowledgePointMaterialRelationSave
+ name: 'addMaterialKnowledge',
+ parentData: {
+ emits: ['close'],
+ console.log('🚀 ~ props', props.parentData)
+ saveLoading: false,
+ checkList: [] as string[]
+ materialCategoryId: null
+ index: -1,
+ loading: false
+ category.loading = true
+ category.loading = false
+ key: 'sn'
+ title: '素材类型',
+ return classType[row.type as string]
+ const getData = async () => {
+ knowledgePointId: props.parentData.id,
+ ...searchForm,
+ courseTypeCode: props.parentData.courseTypeCode,
+ return (await knowledgePointMaterialRelationMaterialPage(body)) as any
+ const { data } = await getData()
+ // 素材关联知识点
+ const handleSave = async () => {
+ state.saveLoading = true
+ materialId: state.checkList.join(',')
+ await knowledgePointMaterialRelationSave(body)
+ state.saveLoading = false
+ style={{ padding: 0, boxShadow: 'none' }}
+ saveKey="addMaterialKnowledge-key"
+ <NRow gutter={14}>
+ <NCol span={6}>
+ <NFormItem label="素材名称/编号" path="keyword">
+ </NCol>
+ empty: () =>
+ category.loading ? (
+ <NEmpty
+ description="加载中"
+ <NIcon size="30" class={styles.loading}>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ viewBox="0 0 1024 1024"
+ <path
+ d="M988 548c-19.9 0-36-16.1-36-36c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9a437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7c26.7 63.1 40.2 130.2 40.2 199.3c.1 19.9-16 36-35.9 36z"
+ fill="currentColor"
+ ></path>
+ </svg>
+ ></NEmpty>
+ ) : null
+ {/* <NCol span={6}>
+ </NCol> */}
+ </NRow>
+ maxHeight="calc(100vh - 270px)"
+ rowKey={(row: any) => row.materialId}
+ v-model:checkedRowKeys={state.checkList}
+ loading={state.saveLoading}
+ onClick={() => handleSave()}
+ 保 存
+import { SelectMixedOption } from 'naive-ui/es/select/src/interface'
+import { knowledgePointSave, knowledgePointUpdate } from '../../api'
+import { knowledgeTypeData } from '../../knowledgeTypeData'
+ name: 'KnowledgeModel',
+ props: ['item'],
+ courseTypeCode: props?.item?.courseTypeCode?.split(',') || [], //课程类型
+ autoPlayFlag: props?.item?.autoPlayFlag == true ? 1 : 0,
+ totalMaterialTimeSecond: props?.item?.totalMaterialTimeSecond,
+ parentId: props?.item?.parentId || 0 // 默认第一层级
+ name: [{ required: true, message: '请输入知识点名称', trigger: ['blur', 'change'] }],
+ const handleOptions = () => {
+ let _obj: SelectMixedOption[] = []
+ Object.keys(knowledgeTypeData).map((key) => {
+ _obj.push({
+ label: knowledgeTypeData[key],
+ value: key
+ return _obj
+ const hanldeAdd = () => {
+ console.log('🚀 ~ err', err)
+ let isCreate = ''
+ res = await knowledgePointUpdate(params)
+ res = await knowledgePointSave(params)
+ isCreate = res.data
+ emit('handleSuccess', isCreate)
+ <NFormItem label="知识点名称" path="name">
+ v-model:value={saveModel.courseTypeCode}
+ options={handleOptions()}
+ label="建议学习时长"
+ path="totalMaterialTimeSecond"
+ type: 'number',
+ min: 0,
+ message: '请输入正确建议学习时长',
+ placeholder="请输入建议学习时长"
+ showButton={false}
+ v-model:value={saveModel.totalMaterialTimeSecond}
+ suffix: () => 's(秒)'
+ <NFormItem label="自动播放下一知识点" path="autoPlayFlag">
+ <NRadioGroup v-model:value={saveModel.autoPlayFlag}>
+ <NButton type="primary" onClick={hanldeAdd}>
@@ -0,0 +1,396 @@
+import { defineComponent, onMounted, reactive, ref, provide } from 'vue'
+import { employeeArray } from '@/utils/searchArray'
+import { fetchKnowledgeList, fetchDelKnowledge, knowledgePointStatus } from '../api'
+import { knowledgeTypeData, lessonType } from '../knowledgeTypeData'
+import KnowledgeModel from './component/knowledgeModel'
+import { useRouter } from 'vue-router'
+ name: 'knowledge-manage',
+ time: null,
+ operatorId: null,
+ enableFlag: null
+ // 获取知识点列表
+ const knowledge = reactive({
+ const { time, ...resset } = searchForm
+ const { data } = await fetchKnowledgeList({
+ ...resset,
+ ...filterTimes(time, ['startUpdateTime', 'endUpdateTime']),
+ // 打开详情
+ const openDetail = (row: any) => {
+ router.push({
+ path: '/knowledgeDetail',
+ query: {
+ name: row.name,
+ courseTypeCode: row.courseTypeCode
+ title: '知识点',
+ <div style="display: inline-flex;vertical-align: middle;">
+ <NDescriptionsItem label="知识点名称">{row.name}</NDescriptionsItem>
+ <NDescriptionsItem label="知识点编号">{row.id}</NDescriptionsItem>
+ key: 'courseTypeName',
+ <NDescriptionsItem label="自动播放">
+ {row.autoPlayFlag == 1 ? '是' : '否'}
+ title: '学习信息',
+ key: 'totalMaterialNum',
+ <NDescriptionsItem label="素材数量">{row.totalMaterialNum}</NDescriptionsItem>
+ {(row.totalMaterialTimeSecond || 0) + 's'}
+ title: '上传信息',
+ <NDescriptionsItem label="修改人">{row.operatorName}</NDescriptionsItem>
+ <NDescriptionsItem label="修改时间">{row.updateTime}</NDescriptionsItem>
+ key: 'enableFlag',
+ {row.enableFlag ? '已启用' : '未启用'}
+ //v-auth="knowledgePoint/status1605123728664281090"
+ onClick={() => changeState(row)}
+ {/* 一级分类下是否有子项 */}
+ {!(row.children && row.children.length > 0) && (
+ <NButton type="primary" size="small" text onClick={() => openDetail(row)}>
+ 详情
+ {/* 是否是一级分类,一级分类下面是否有素材 */}
+ {row.parentId <= 0 && !row.materialFlag && (
+ //v-auth="knowledgePoint/update1604688172914417665"
+ state.modalType = 'add-child'
+ state.visiableKnowledge = true
+ parentId: row.id,
+ 添加子项
+ state.modalType = 'update'
+ disabled={row.enableFlag ? true : false}
+ //v-auth="knowledgePoint/remove1604688068350418946"
+ handleDelete(row)
+ // 启用停用
+ const changeState = async (row: any, open = true) => {
+ content: `是否${row.enableFlag ? '停用' : '启用'}此知识点?`,
+ console.log('row', row)
+ const res = await knowledgePointStatus(row.id)
+ content: '是否确认删除此知识点?',
+ const res = await fetchDelKnowledge(row.id)
+ <h2>知识点管理</h2>
+ <NFormItem path="search" label="关键字">
+ <NInput v-model:value={searchForm.keyword} placeholder="知识组名称/编号" clearable />
+ <NFormItem path="time" label="时间">
+ <NDatePicker v-model:value={searchForm.time} type="daterange" clearable></NDatePicker>
+ <NFormItem label="状态" path="enableFlag">
+ v-model:value={searchForm.enableFlag}
+ { label: '已启用', value: 1 },
+ { label: '未启用', value: 0 }
+ {/* <NFormItem path="status">
+ placeholder="上传人"
+ options={employeeArray}
+ //v-auth="knowledgePoint/save1604687872153460738"
+ state.modalType = 'add'
+ 新增知识点
+ children-key="children"
+ v-model:show={state.visiableKnowledge}
+ title={state.modalType !== 'update' ? '新增知识点' : '修改知识点'}
+ <KnowledgeModel
+ onHandleSuccess={(val: any) => {
+ if (val) {
+ openDetail(val)
+ console.log(val)
+ ></KnowledgeModel>
+interface knowledgeType {
+ [key: string]: string
+export const knowledgeTypeData: knowledgeType = {
+ INSTRUMENTAL_ENSEMBLE: '合奏'
+export const lessonType = Object.keys(knowledgeTypeData).map((key: string) => {
+ return { label: knowledgeTypeData[key], value: key }
+export const getLessonType = (key: any) => {
+ return knowledgeTypeData[key] || ''
@@ -0,0 +1,46 @@
+ * @description: 获取登录日志
+export const sysUserLoginLogPage = (params: object) => {
+ url: '/cbs-app/sysUserLoginLog/page',
+ * @description: 获取登录统计
+export const sysUserLoginPage = (params: object) => {
+ url: '/cbs-app/sysUserLogin/page',
+ * @description: 获取登录设备
+export const sysUserDevicePage = (params: object) => {
+ url: '/cbs-app/sysUserDevice/page',
+ * @description: 获取操作日志
+export const sysUserAuditLogPage = (params: object) => {
+ url: '/cbs-app/sysUserAuditLog/page',
@@ -0,0 +1,87 @@
+import { NDataTable, NIcon, NTooltip } from 'naive-ui'
+import { HelpCircleOutline } from '@vicons/ionicons5'
+import { sysUserDevicePage } from '../api'
+ name: 'login-device',
+ name: ''
+ visiableCity: false
+ title: '用户名称',
+ key: 'nickname'
+ title: '设备型号',
+ key: 'userAgent',
+ width: '400'
+ title: '绑定时间',
+ key: 'bindTime'
+ title: '登录时间',
+ const { data } = await sysUserDevicePage({
+ ...state.pagination
+ saveKey="login-device"
@@ -0,0 +1,93 @@
+import { sysUserLoginLogPage } from '../api'
+ name: 'login-log',
+ // type: 'expand',
+ // renderExpand: (rowData: any) => {
+ // return `请求参数:${rowData.params} `
+ title: '登录用户',
+ title: '登录IP',
+ key: 'loginIp'
+ key: 'loginTime'
+ title: '登录地点',
+ key: 'loginRegion'
+ const { data } = await sysUserLoginLogPage({
+ saveKey="login-log"
@@ -0,0 +1,110 @@
+import { sysUserLoginPage } from '../api'
+ name: 'login-statistics',
+ title: '登录总次数',
+ key: 'loginCount'
+ title: '最后登录IP',
+ key: 'lastLoginIp'
+ title: '最后登录时间',
+ key: 'lastLoginTime'
+ title: '锁定日期',
+ key: 'lockDate'
+ title(column: any) {
+ 锁定时间
+ <NIcon size={18}>
+ <HelpCircleOutline />
+ default: () => '超过锁定时间自动解锁'
+ key: 'lockTime'
+ const { data } = await sysUserLoginPage({
+ saveKey="login-statistics"
@@ -0,0 +1,63 @@
+import LoginLog from './component/login-log'
+import LoginStatistics from './component/login-statistics'
+import LoginDevice from './component/login-device'
+ tabName: 'LoginLog' as 'LoginLog' | 'LoginStatistics' | 'LoginDevice'
+ <h2>登录日志</h2>
+ name="LoginLog"
+ tab="登录日志"
+ //v-auth="sysUserLoginLog/page1597883965544714242"
+ <LoginLog />
+ name="LoginStatistics"
+ tab="登录统计"
+ //v-auth="sysUserLogin/page1597884090153291778"
+ <LoginStatistics />
+ name="LoginDevice"
+ tab="登录设备"
+ //v-auth="sysUserDevice/page1597884219157499905"
+ <LoginDevice />
@@ -0,0 +1,138 @@
+import { NDataTable, NDescriptions, NDescriptionsItem, NIcon, NTooltip } from 'naive-ui'
+import type { EllipsisProps } from 'naive-ui'
+import { sysUserAuditLogPage } from './api'
+ name: 'interface-log',
+ type: 'expand',
+ renderExpand: (rowData: any) => {
+ return `请求参数:${rowData.params} `
+ <NDescriptionsItem label="日志编号">{row.id}</NDescriptionsItem>
+ <NDescriptionsItem label="操作用户">{row.nickname}</NDescriptionsItem>
+ <NDescriptionsItem label="操作时间">{row.startTime}</NDescriptionsItem>
+ // title: '操作用户',
+ // key: 'nickname'
+ title: '接口信息',
+ key: 'requestUri',
+ <NDescriptionsItem label="接口地址">
+ <TheTooltip content={row.requestUri} />{' '}
+ <NDescriptionsItem label="接口描述">
+ <TheTooltip content={row.description} />{' '}
+ // title: '接口描述',
+ // key: 'description'
+ title: '接口类路径',
+ key: 'classPath',
+ width: 230
+ // title: '操作时间',
+ // key: 'startTime'
+ title: '调用时长',
+ key: 'consumingTime',
+ return `${row.consumingTime}ms`
+ const { data } = await sysUserAuditLogPage({
+ <h2>接口操作日志</h2>
@@ -96,7 +96,7 @@ export default defineComponent({
type="primary"
text
size="small"
- v-auth="sysMenu/update1597877909171064833"
+ //v-auth="sysMenu/update1597877909171064833"
onClick={() => {
state.visiableMenu = true
state.menuType = 'edit'
@@ -109,7 +109,7 @@ export default defineComponent({
- v-auth="sysMenu/remove1597878074728632322"
+ //v-auth="sysMenu/remove1597878074728632322"
onClick={() => onRmove(row)}
>
删除
@@ -224,7 +224,7 @@ export default defineComponent({
<NSpace style={{ paddingBottom: '12px' }} justify="space-between">
<NButton
- v-auth="sysMenu/save1597877997041733633"
+ //v-auth="sysMenu/save1597877997041733633"
state.menuType = 'add'
state.applyData = []
+ * 曲谱分类列表
+export const getMusicSheetCategories = (params: object) => {
+ url: '/cbs-app/musicSheetCategories/queryTree',
+ params
+ * 新增曲谱分类 musicSheetCategories/save
+export const saveMusicSheetCategories = (params: object) => {
+ url: '/cbs-app/musicSheetCategories/save',
+ * 停用启用
+export const musicSheetCategoriesSwitching = (params: object) => {
+ url: `/cbs-app/musicSheetCategories/switching/${params}`,
+ * 修改
+export const resetMusicSheetCategories = (params: object) => {
+ url: '/cbs-app/musicSheetCategories/update',
@@ -0,0 +1,152 @@
+ NCascader
+import { saveMusicSheetCategories, resetMusicSheetCategories } from '../api'
+ props: ['actvieRow', 'isAdd', 'list'],
+ name: 'eidt-categroy',
+ const orchestraId = route.query.orchestraId || ''
+ const formRef = ref()
+ { name: '顶级分类', id: 0 },
+ ...filterPointCategory(props.list, 'musicSheetCategoriesList')
+ btnLoading: false
+ id: '',
+ lessonTargetDesc: '',
+ sortNo: '',
+ semesterNo: '',
+ enableFlag: false,
+ formRef.value?.validate(async (errors: any) => {
+ if (!errors) {
+ if (!props.isAdd) {
+ const { data } = await resetMusicSheetCategories({ ...forms })
+ const { data } = await saveMusicSheetCategories({ ...forms })
+ // console.log(errors)
+ // message.error(errors)
+ if (props?.actvieRow?.id && !props.isAdd) {
+ for (let key in props?.actvieRow) {
+ forms[key] = props?.actvieRow[key]
+ } else if (props?.actvieRow?.id && props.isAdd) {
+ forms.parentId = props?.actvieRow.id + ''
+ <NForm ref={formRef} model={forms}>
+ v-model:value={forms.parentId}
+ childrenField="musicSheetCategoriesList"
+ label="教材名称"
+ message: '请输入教材名称',
+ trigger: ['blur', 'input']
+ placeholder="请输入教材名称"
+ maxlength={500}
+ label="教材封面"
+ path="coverImg"
+ message: '请上教材封面',
+ v-model:fileList={forms.coverImg}
+ autoCropWidth: 349, //默认生成截图框宽度
+ autoCropHeight: 141 //默认生成截图框高度
+ tips="请上传请上传尺寸为349*141大小1M以内的JPG、PNG图片"
+ loading={state.btnLoading}
+ disabled={state.btnLoading}
+ 确认
@@ -0,0 +1,340 @@
+ NModal
+import { getMusicSheetCategories, musicSheetCategoriesSwitching } from './api'
+import { knowledgeTypeData, lessonType } from '@/views/knowledge-manage/knowledgeTypeData'
+import EidtCategroy from './components/eidt-categroy'
+type RowData = {
+ id: string
+ name: string
+ coverImg: string
+ musicSheetNum: string
+ updateTime: string
+ enable: boolean
+ musicSheetCategoriesList?: RowData[]
+ emits: ['setTabName'],
+ name: 'music-categrory',
+ visiablePlan: false,
+ actvieRow: null as any,
+ isAdd: true
+ const searchForm = reactive<any>({
+ name: '', // 计划名称
+ keyword: '', // 关键字匹配
+ enable: null // 是否启用
+ rows: state.pagination.rows,
+ ...searchForm
+ const { data } = await getMusicSheetCategories(body)
+ // state.pagination.pageTotal = Number(data.total)
+ state.dataList = (data as RowData[]) || []
+ //启用停用
+ const handleChangeState = (row: any, type: boolean) => {
+ content: type ? `是否确认启用${row.name}?` : `是否确认停用${row.name}?`,
+ const res = await musicSheetCategoriesSwitching(row.id)
+ message.success('操作成功')
+ title: '教材编号',
+ title: '教材名称',
+ title: '教材封面',
+ key: 'coverImg',
+ <img src={row.coverImg} style={{ width: '175px', height: '70px' }} alt="" />
+ title: '曲目数量',
+ key: 'musicSheetNum'
+ // title: '使用音源',
+ // key: 'soundResource'
+ // // render(row) {
+ // // return `第${row.semesterNo}学期`
+ // // }
+ title: '最后更新时间',
+ title: '教材状态',
+ key: 'enable',
+ render(row) {
+ return row.enable ? '已启用' : '未启用'
+ {row.enable ? (
+ //v-auth="musicSheetCategories/switching1608067671668359169"
+ onClick={() => handleChangeState(row, false)}
+ 停用
+ onClick={() => handleChangeState(row, true)}
+ 启用
+ gotoDetail(row)
+ resetPlan(row)
+ //v-auth="musicSheetCategories/update1608067835686617090"
+ {row.currentLevel < 4 ? (
+ onClick={() => handleAdd(row)}
+ //v-auth="musicSheetCategories/save1608067543331045378"
+ ) : null}
+ content: '是否确认删除此计划?',
+ // const res = await lessonPlanRemove(row.id)
+ // if (res.data) {
+ // onSubmit()
+ // message.success('删除成功')
+ // 修改
+ const resetPlan = (row: any) => {
+ state.actvieRow = row
+ state.isAdd = false
+ state.visiablePlan = true
+ // 曲目新增
+ const handleAdd = (row: any) => {
+ state.isAdd = true
+ // 详情
+ const gotoDetail = (row: any) => {
+ console.log('gotoDetail')
+ emit('setTabName', {
+ tabName: 'MusicList'
+ // router.push({ path: '/planDetail', query: { name: row.name, id: row.id } })
+ saveKey="music-categrory"
+ {/* <NFormItem path="keyword" label="计划名称/编号">
+ <NInput v-model:value={searchForm.keyword} placeholder="计划名称/编号" clearable />
+ <NFormItem path="enable" label="教材状态">
+ placeholder="教材状态"
+ v-model:value={searchForm.enable}
+ state.actvieRow = null
+ scrollX={1000}
+ childrenKey="musicSheetCategoriesList"
+ v-model:show={state.visiablePlan}
+ title={state.actvieRow ? '修改曲谱分类' : '新增曲谱分类'}
+ <EidtCategroy
+ onGetList={() => {
+ state.visiablePlan = false
+ isAdd={state.isAdd}
+ actvieRow={state.actvieRow}
+ ></EidtCategroy>
@@ -0,0 +1,9 @@
+// 查询列表
+export const sysMessagePage = (params: object) => {
+ url: '/cbs-app/sysMessage/page',
@@ -0,0 +1,210 @@
+import { sysMessagePage } from './api'
+import { sysParamConfigSingle } from '../system-manage/api'
+export const smsType = {
+ SMS_VERIFY_CODE_LOGIN: '验证码登录',
+ SMS_VERIFY_CODE_LOGOFF: '用户注销',
+ SMS_VERIFY_CODE_REGISTER: '验证码注册',
+ SMS_VERIFY_CODE_UPDATE_PSW: '密码修改',
+ SMS_VERIFY_CODE_UPDATE_PHONE: '修改手机号',
+ SMS_VERIFY_CODE: '验证码',
+ SMS_VERIFY_CODE_BANK_CARD: '银行卡验证'
+ name: 'subsidy-manage',
+ verify_code_expire_time: 0,
+ messageType: '',
+ receiver: null,
+ times: null // 结算时间
+ dataList: [] as any
+ const columns = (): DataTableColumn[] => [
+ title: '手机号',
+ key: 'receiver',
+ ellipsis: {
+ tooltip: true
+ title: '验证码',
+ key: 'verityCode',
+ title: '验证码类型',
+ key: 'messageType',
+ return smsType[row.messageType]
+ title: '发送时间',
+ const { times, messageType, ...result } = state.searchForm
+ console.log(state, 'state.searchForm')
+ let type = messageType
+ // 判断是否选择的全部
+ if (!messageType) {
+ type =
+ 'SMS_VERIFY_CODE_LOGIN,SMS_VERIFY_CODE_LOGOFF,SMS_VERIFY_CODE_REGISTER,SMS_VERIFY_CODE_UPDATE_PSW,SMS_VERIFY_CODE_UPDATE_PHONE,SMS_VERIFY_CODE,SMS_VERIFY_CODE_BANK_CARD'
+ let obj = {
+ type: 'SMS',
+ messageType: type,
+ ...result,
+ ...getTimes(times, ['startTime', 'endTime'], 'YYYY-MM-DD'),
+ const { data } = await sysMessagePage({ ...obj })
+ const getSystemList = async () => {
+ const { data } = await sysParamConfigSingle({ paramName: 'verify_code_expire_time' })
+ state.verify_code_expire_time = data.paramValue || 0
+ getSystemList()
+ <h2>短信验证码</h2>
+ saveKey="sms-code-message"
+ onSetModel={(val: any) => {
+ console.log(val, 'val')
+ state.searchForm = val
+ <NFormItem label="手机号" path="receiver">
+ v-model:value={state.searchForm.receiver}
+ placeholder="请输入手机号"
+ <NFormItem label="验证码类型" path="messageType">
+ v-model:value={state.searchForm.messageType}
+ { value: 'SMS_VERIFY_CODE_LOGIN', label: '验证码登录' },
+ { value: 'SMS_VERIFY_CODE_LOGOFF', label: '用户注销' },
+ { value: 'SMS_VERIFY_CODE_REGISTER', label: '验证码注册' },
+ { value: 'SMS_VERIFY_CODE_UPDATE_PSW', label: '密码修改' },
+ { value: 'SMS_VERIFY_CODE_UPDATE_PHONE', label: '修改手机号' },
+ { value: 'SMS_VERIFY_CODE', label: '验证码' },
+ { value: 'SMS_VERIFY_CODE_BANK_CARD', label: '银行卡验证' }
+ placeholder="请选择验证码类型"
+ <NFormItem label="发送时间" path="times">
+ v-model:value={state.searchForm.times}
+ startPlaceholder="开始时间"
+ endPlaceholder="结束时间"
+ <p style={{ paddingBottom: '10px' }}>
+ 验证码有效期为<span style={{ color: 'red' }}>{state.verify_code_expire_time}</span>
+ 分钟
+ </p>
@@ -0,0 +1,737 @@
+ * @description: 系统应用信息列表
+export const sysApplicationPage = (params: object) => {
+ url: '/cbs-app/sysApplication/page',
+ * @description: 系统应用信息添加
+export const sysApplicationSave = (params: object) => {
+ url: '/cbs-app/sysApplication/save',
+export const sysApplicationUpdate = (params: object) => {
+ url: '/cbs-app/sysApplication/update',
+ * @description: 系统应用信息删除
+export const sysApplicationRemove = (params: object) => {
+ url: '/cbs-app/sysApplication/remove',
+ * @description: 系统应用信息详情
+export const sysApplicationDetail = (params: any) => {
+ url: '/cbs-app/sysApplication/detail/' + params.id,
+ * @description: 菜单权限列表
+export const sysMenuPage = (params: object) => {
+ url: '/cbs-app/sysMenu/page',
+ * @description: 菜单权限添加
+export const sysMenuSave = (params: object) => {
+ url: '/cbs-app/sysMenu/save',
+ * @description: 菜单权限修改
+export const sysMenuUpdate = (params: object) => {
+ url: '/cbs-app/sysMenu/update',
+ * @description: 菜单权限删除
+export const sysMenuRemove = (params: object) => {
+ url: '/cbs-app/sysMenu/remove',
+ * @description: 系统角色列表
+export const sysRolePage = (params: object) => {
+ url: '/cbs-app/sysRole/page',
+ * @description: 系统角色添加
+export const sysRoleSave = (params: object) => {
+ url: '/cbs-app/sysRole/save',
+ * @description: 系统角色修改
+export const sysRoleUpdate = (params: object) => {
+ url: '/cbs-app/sysRole/update',
+ * @description: 系统角色删除
+export const sysRoleRemove = (params: object) => {
+ url: '/cbs-app/sysRole/remove',
+ * @description: 系统角色详情
+export const sysRoleDetail = (params: any) => {
+ url: '/cbs-app/sysRole/detail/' + params.id,
+ * @description: 岗位列表
+export const sysPositionPage = (params: object) => {
+ url: '/cbs-app/sysPosition/page',
+ * @description: 岗位添加
+export const sysPositionSave = (params: object) => {
+ url: '/cbs-app/sysPosition/save',
+ * @description: 岗位修改
+export const sysPositionUpdate = (params: object) => {
+ url: '/cbs-app/sysPosition/update',
+ * @description: 岗位删除
+export const sysPositionRemove = (params: object) => {
+ url: '/cbs-app/sysPosition/remove',
+ * @description: 员工列表
+export const sysEmployeePage = (params: object) => {
+ url: '/cbs-app/sysEmployee/page',
+ * @description: 员工添加
+export const sysEmployeeSave = (params: object) => {
+ url: '/cbs-app/sysEmployee/save',
+ * @description: 员工修改
+export const sysEmployeeUpdate = (params: object) => {
+ url: '/cbs-app/sysEmployee/update',
+ * @description: 员工删除
+export const sysEmployeeRemove = (params: object) => {
+ url: '/cbs-app/sysEmployee/remove',
+ * @description: 员工 激活-冻结
+export const sysEmployeeStatus = (params: object) => {
+ url: '/cbs-app/sysEmployee/status',
+ * @description: 员工 详情
+export const sysEmployeeDetail = (params: any) => {
+ url: '/cbs-app/sysEmployee/detail/' + params.id,
+ params: params,
+ * @description: 员工 重置密码
+export const resetPassword = (params: object) => {
+ url: '/cbs-app/user/resetPassword',
+ * @description: 系统配置 列表
+export const sysParamConfigPage = (params?: object) => {
+ url: '/cbs-app/sysParamConfig/page',
+export const sysParamConfigSingle = (params?: object) => {
+ url: '/cbs-app/sysParamConfig/queryByParamName',
+ * @description: 系统配置 更新
+export const sysParamConfigUpdate = (params?: object) => {
+ url: '/cbs-app/sysParamConfig/update',
+ * @description: 支付服务配置参数
+export const sysPaymentConfigPage = (params?: object) => {
+ url: '/cbs-app/sysPaymentConfig/page',
+ * @description: 支付服务配置参数-新增
+export const sysPaymentConfigSave = (params?: object) => {
+ url: '/cbs-app/sysPaymentConfig/save',
+ * @description: 支付服务配置参数-修改
+export const sysPaymentConfigUpdate = (params?: object) => {
+ url: '/cbs-app/sysPaymentConfig/update',
+ * @description: 支付服务配置参数-删除
+export const sysPaymentConfigRemove = (params?: object) => {
+ url: '/cbs-app/sysPaymentConfig/remove',
+ * @description: 消息配置
+export const sysMessageConfigPage = (params?: object) => {
+ url: '/cbs-app/sysMessageConfig/page',
+ * @description: 消息配置-新增
+export const sysMessageConfigSave = (params?: object) => {
+ url: '/cbs-app/sysMessageConfig/save',
+ * @description: 消息配置-修改
+export const sysMessageConfigUpdate = (params?: object) => {
+ url: '/cbs-app/sysMessageConfig/update',
+ * @description: 消息配置-删除
+export const sysMessageConfigRemove = (params?: object) => {
+ url: '/cbs-app/sysMessageConfig/remove',
+ * @description: 协议
+export const schoolContractTemplatePage = (params?: object) => {
+ url: '/cbs-app/schoolContractTemplate/page',
+ * @description: 协议-新增
+export const schoolContractTemplateSave = (params?: object) => {
+ url: '/cbs-app/schoolContractTemplate/save',
+ * @description: 协议-修改
+export const schoolContractTemplateUpdate = (params?: object) => {
+ url: '/cbs-app/schoolContractTemplate/update',
+ * @description: 协议-启用协议
+export const schoolContractTemplateUpdateStatus = (params?: object) => {
+ url: '/cbs-app/schoolContractTemplate/updateStatus',
+ * @description: 协议-详情
+export const schoolContractTemplateDetail = (params?: any) => {
+ url: '/cbs-app/schoolContractTemplate/detail/' + params.id,
+ * @description: 声部基础配置表-列表
+export const subjectBasicConfigPage = (params?: object) => {
+ url: '/cbs-app/subjectBasicConfig/page',
+ * @description: 声部基础配置表-新增
+export const subjectBasicConfigSave = (params?: object) => {
+ url: '/cbs-app/subjectBasicConfig/save',
+ * @description: 声部基础配置表-修改
+export const subjectBasicConfigUpdate = (params?: object) => {
+ url: '/cbs-app/subjectBasicConfig/update',
+ * @description: 声部-修改
+export const subjectBasicConfigUpdateConfigStatus = (params?: object) => {
+ url: '/cbs-app/subjectBasicConfig/updateConfigStatus',
+ * @description: 声部-列表
+export const subjectPage = (params?: object) => {
+ url: '/cbs-app/subject/page',
+ * @description: 声部-添加
+export const subjectSave = (params?: object) => {
+ url: '/cbs-app/subject/save',
+export const subjectUpdate = (params?: object) => {
+ url: '/cbs-app/subject/update',
+ * @description: 商品列表
+export const productList = (params?: object) => {
+ url: '/cbs-app/open/mall/productSearch',
+ noToken: true
+ * @description: 商品-详情
+export const productDetail = (params?: any) => {
+ url: '/cbs-app/open/mall/product/detail/' + params.id,
+ * @description: APP版本信息管理
+export const appVersionInfoPage = (params?: any) => {
+ url: '/cbs-app/appVersionInfo/page',
+ * @description: APP版本信息管理-添加
+export const appVersionInfoSave = (params?: object) => {
+ url: '/cbs-app/appVersionInfo/save',
+ * @description: APP版本信息管理-修改
+export const appVersionInfoUpdate = (params?: object) => {
+ url: '/cbs-app/appVersionInfo/update',
+ * @description: 平台建议
+export const sysSuggestionPage = (params?: any) => {
+ url: '/cbs-app/sysSuggestion/page',
+ * @description: 节假日表-按年查询节假日
+export const holidaysFestivalsQuery = (params?: any) => {
+ url: '/cbs-app/holidaysFestivals/query/' + params.year,
+ * @description: 节假日表-添加
+export const holidaysFestivalsSave = (params?: object) => {
+ url: '/cbs-app/holidaysFestivals/save',
+ * @description: 节假日表-修改
+export const holidaysFestivalsUpdate = (params?: object) => {
+ url: '/cbs-app/holidaysFestivals/update',
+ * @description: 请假类别-列表
+export const leaveCategoryPage = (params?: object) => {
+ url: '/cbs-app/leaveCategory/page',
+ * @description: 请假类别-添加
+export const leaveCategorySave = (params?: object) => {
+ url: '/cbs-app/leaveCategory/save',
+ * @description: 请假类别-修改
+export const leaveCategoryUpdate = (params?: object) => {
+ url: '/cbs-app/leaveCategory/update',
+ * @description: 请假类别-删除
+export const leaveCategoryRemove = (params?: object) => {
+ url: '/cbs-app/leaveCategory/remove',
+ * @description: 获取会员-原价
+export const getVipPriceSettings = (params?: object) => {
+ url: '/cbs-app/vipPriceSettings/page',
+ * @description: 修改会员-售价
+export const setVipPriceSettings = (params?: object) => {
+ url: '/cbs-app/vipPriceSettings/update',
+ * @description: 批量修改
+export const vipPriceSettingsBatchUpdate = (params?: object) => {
+ url: '/cbs-app/vipPriceSettings/batchUpdate',
+ * @description: 设备号管理列表
+export const sysUserDevicePage = (params?: object) => {
+ * @description: 设备号管理-删除
+export const sysUserDeviceRemove = (params?: object) => {
+ url: '/cbs-app/sysUserDevice/removeIds',
@@ -0,0 +1,221 @@
+import { appVersionInfoPage } from '../api'
+import VersionOperation from './version-operation'
+import { filterPlatform, filterStatus, platformArr, statusArr } from './operation'
+ name: 'app-version',
+ platform: null
+ key: 'platform',
+ return filterPlatform(row.platform)
+ title: '版本号',
+ key: 'version'
+ title: '强制更新',
+ key: 'isForceUpdate',
+ <NTag type={row.isForceUpdate ? 'primary' : 'default'}>
+ {row.isForceUpdate ? '是' : '否'}
+ return filterStatus(row.status)
+ title: '描述',
+ key: 'description'
+ title: '下载连接',
+ key: 'downloadUrl',
+ width: 250
+ //v-auth="appVersionInfo/update1598525730761052161"
+ const { data } = await appVersionInfoPage({ ...state.pagination, ...state.searchForm })
+ // console.log('搜索', { ...state.form })
+ <h2>APP版本控制</h2>
+ <NFormItem label="客户端" path="platform">
+ v-model:value={state.searchForm.platform}
+ options={platformArr}
+ options={statusArr}
+ {/* platform version */}
+ //v-auth="appVersionInfo/save1598525667141849090"
+ 添加APP版本
+ title={state.appOperation === 'add' ? '新增APP版本' : '修改APP版本'}
+ <VersionOperation
+ v-if={state.visiableApp}
+const platform = {
+ 'ios-school': '苹果-管理端',
+ 'ios-teacher': '苹果-老师端',
+ 'ios-student': '苹果-学生端',
+ 'android-school': '安卓-管理端',
+ 'android-teacher': '安卓-老师端',
+ 'android-student': '安卓-学生端'
+const status = {
+ newest: '最新',
+ history: '历史',
+ auditing: '审核中'
+export const platformArr = [
+ { value: 'ios-school', label: '苹果-管理端' },
+ { value: 'ios-teacher', label: '苹果-老师端' },
+ { value: 'ios-student', label: '苹果-学生端' },
+ { value: 'android-school', label: '安卓-管理端' },
+ { value: 'android-teacher', label: '安卓-老师端' },
+ { value: 'android-student', label: '安卓-学生端' }
+]
+export const statusArr = [
+ { value: 'newest', label: '最新' },
+ { value: 'history', label: '历史' },
+ { value: 'auditing', label: '审核中' }
+export const filterStatus = (key: 'newest' | 'history' | 'auditing' | '') => {
+ if (key && status && status[key]) {
+ return status[key]
+export const filterPlatform = (
+ | 'ios-school'
+ | 'ios-teacher'
+ | 'ios-student'
+ | 'android-school'
+ | 'android-teacher'
+ | 'android-student'
+ if (key && platform && platform[key]) {
+ return platform[key]
+ NRadioGroup
+import { appVersionInfoSave, appVersionInfoUpdate } from '../api'
+import { platformArr, statusArr } from './operation'
+ name: 'role-operation',
+ applyList: {
+ type: Array as PropType<any>,
+ platform: null,
+ version: null,
+ isForceUpdate: null,
+ downloadUrl: null,
+ description: null
+ if (error) return false
+ await appVersionInfoSave({ ...forms })
+ await appVersionInfoUpdate({
+ const changeApp = () => {
+ forms.version = null
+ forms.isForceUpdate = null
+ forms.status = null
+ forms.downloadUrl = null
+ forms.description = null
+ forms.platform = data.platform
+ forms.version = data.version
+ forms.isForceUpdate = data.isForceUpdate
+ forms.downloadUrl = data.downloadUrl
+ forms.description = data.description
+ label="APP客户端"
+ path="platform"
+ message: '请选择APP客户端'
+ v-model:value={forms.platform}
+ placeholder="请选择APP客户端"
+ disabled={props.type == 'add' ? false : true}
+ onChange={() => changeApp()}
+ label="版本号"
+ path="version"
+ message: '请输入版本号'
+ v-model:value={forms.version}
+ placeholder="请输入版本号"
+ maxlength={100}
+ label="强制更新"
+ path="isForceUpdate"
+ message: '请选择强制更新'
+ <NRadioGroup v-model:value={forms.isForceUpdate}>
+ <NRadio value={true}>是</NRadio>
+ <NRadio value={false}>否</NRadio>
+ label="状态"
+ path="status"
+ message: '请选择状态'
+ <NSelect options={statusArr} v-model:value={forms.status} placeholder="请选择状态" />
+ {(forms.platform === 'android-student' ||
+ forms.platform === 'android-teacher' ||
+ forms.platform === 'android-school') && (
+ label="上传文件"
+ path="downloadUrl"
+ message: '请上传文件'
+ size={200}
+ accept=".apk,.war"
+ v-model:fileList={forms.downloadUrl}
+ bucketName="appstore"
+ // accept=".apk"
+ label="上传链接"
+ message: '请输入链接地址'
+ v-model:value={forms.downloadUrl}
+ placeholder="请输入链接地址"
+ <NFormItem label="描述" path="description">
+ v-model:value={forms.description}
+ rows={2}
+ <NButton type="primary" onClick={() => onSubmit()} loading={btnLoading.value}>
@@ -0,0 +1,290 @@
+ DataTableRowKey,
+ sysApplicationPage,
+ sysRolePage,
+ sysRoleRemove,
+ sysUserDevicePage,
+ sysUserDeviceRemove
+import { formatDataList } from '@/utils/urlUtils'
+ createTime: null as any,
+ deviceNum: null
+ applyList: [] as any,
+ visiableRole: false,
+ roleOperation: 'add',
+ roleData: {} as any
+ const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+ const handleCheck = (rowKeys: DataTableRowKey[]) => {
+ console.log('🚀 ~ rowKeys', rowKeys)
+ checkedRowKeysRef.value = rowKeys
+ const columns: any = () => {
+ title: '用户姓名',
+ key: 'phone'
+ title: '设备号',
+ key: 'deviceNum'
+ //v-auth="sysUserDevice/removeIds1633324668827324417"
+ onRemove(row)
+ 解除绑定
+ const onRemove = (row: any): void => {
+ content: `是否确定解除绑定?`,
+ await sysUserDeviceRemove([row.id])
+ message.success('解除绑定成功')
+ const { createTime, ...res } = state.searchForm
+ ...state.pagination,
+ ...getTimes(createTime, ['bindStartTime', 'bindEndTime'], 'YYYY-MM-DD')
+ const getApplyList = async (parentId = 0) => {
+ const { data } = await sysApplicationPage({
+ leafQuery: true,
+ parentId
+ state.applyList = formatDataList(data.rows || [], 'bizApps')
+ const handleDeletes = () => {
+ console.log(checkedRowKeysRef.value)
+ content: `是否确认批量解除绑定?`,
+ const res = await sysUserDeviceRemove(checkedRowKeysRef.value)
+ checkedRowKeysRef.value = []
+ } catch (e: any) {}
+ getApplyList()
+ <h2>设备号管理</h2>
+ <NFormItem label="搜索" path="appId">
+ placeholder="请输入姓名、手机号"
+ <NFormItem label="设备号" path="roleName">
+ v-model:value={state.searchForm.deviceNum}
+ placeholder="请输入设备号"
+ <NFormItem path="createTime" label="绑定时间">
+ v-model:formattedValue={state.searchForm.createTime}
+ valueFormat="yyyy-MM-dd"
+ startPlaceholder="绑定开始日期"
+ endPlaceholder="绑定结束日期"
+ //v-auth="sysUserDevice/removeIds1631499811567173634"
+ onClick={handleDeletes}
+ 批量解除绑定
+ v-model:checkedRowKeys={checkedRowKeysRef.value}
+ onUpdate:checked-row-keys={handleCheck}
@@ -0,0 +1,485 @@
+import { queryAllProvince, sysAreaPage } from '@/views/city-manage/api'
+ NAlert
+ defineComponent,
+ nextTick,
+ onMounted,
+ PropType,
+ reactive,
+ ref,
+ shallowReactive,
+ shallowRef
+} from 'vue'
+ sysEmployeeDetail,
+ sysEmployeeSave,
+ sysEmployeeUpdate,
+ sysPositionPage,
+ sysRolePage
+ nickname: null,
+ phone: null,
+ gender: null,
+ entryDate: null,
+ roleList: [],
+ positionList: [],
+ employeeCityList: [],
+ userReceiveAddress: {
+ phoneNumber: null,
+ address: null, //临时
+ province: null,
+ city: null,
+ region: null,
+ detailAddress: null
+ } as any
+ const state = shallowReactive({
+ selectRole: [],
+ selectPosition: [],
+ selectCity: [] as any,
+ selectCityEmployee: [] as any
+ console.log(forms.userReceiveAddress.address, '1212', state.selectCity)
+ const selectArea =
+ formatParentAreaId(forms.userReceiveAddress.address, state.selectCity) || []
+ // if (selectArea.length > 0) {
+ console.log(selectArea, 'selectArea')
+ forms.userReceiveAddress.province = selectArea[0] || null
+ forms.userReceiveAddress.city = selectArea[1] || null
+ forms.userReceiveAddress.region = selectArea[2] || selectArea[1] || null
+ const { roleList, positionList, entryDate, employeeCityList, ...res } = forms
+ console.log(roleList, positionList, employeeCityList)
+ const tempRoleList: any = []
+ if (roleList && roleList.length > 0) {
+ roleList.forEach((item: any) => {
+ tempRoleList.push({
+ roleName: ' ',
+ id: item
+ const tempPositionList: any = []
+ if (positionList) {
+ tempPositionList.push({
+ id: positionList
+ const tempEmployeeList: any = []
+ if (employeeCityList && employeeCityList.length > 0) {
+ employeeCityList.forEach((item: any) => {
+ const itemCode = formatParentAreaId(item, state.selectCity)
+ tempEmployeeList.push({
+ cityCode: itemCode[1],
+ provinceCode: itemCode[0]
+ await sysEmployeeSave({
+ roleList: tempRoleList,
+ positionList: tempPositionList,
+ employeeCityList: tempEmployeeList,
+ entryDate: dayjs(entryDate).format('YYYY-MM-DD HH:mm:ss')
+ await sysEmployeeUpdate({
+ entryDate: dayjs(entryDate).format('YYYY-MM-DD HH:mm:ss'),
+ const formatParentId = (id: any, list: any, ids = [] as any) => {
+ if (item.children && item.children.length > 0) {
+ const cIds: any = formatParentId(id, item.children, [...ids, item.code])
+ const getPositionList = async () => {
+ const { data } = await sysPositionPage({ page: 1, rows: 999 })
+ state.selectPosition = data.rows || []
+ const getRoleList = async () => {
+ const { data } = await sysRolePage({ page: 1, rows: 999, enable: true })
+ state.selectRole = data.rows || []
+ const phoneValidater = (rule: any, value: string) => {
+ !/^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\d{8}$/.test(
+ value
+ return new Error('请输入正确的手机号')
+ const getAreaList = async (parentId = 0) => {
+ const tempList = data || []
+ state.selectCity = deepClone(tempList)
+ state.selectCityEmployee = tempList.map((item: any) => {
+ item.isLeaf = false
+ item.areas.map((area: any) => {
+ area.isLeaf = true
+ area.areas = null
+ return item
+ getPositionList()
+ getRoleList()
+ getAreaList()
+ const res = await sysEmployeeDetail({ id: data.id })
+ const detail = res.data || {}
+ forms.nickname = detail.nickname
+ forms.phone = detail.phone
+ forms.gender = detail.gender
+ forms.entryDate = detail.entryDate && new Date(detail.entryDate).getTime()
+ forms.roleList =
+ detail.roleList &&
+ detail.roleList.map((position: any) => {
+ return position.id
+ forms.positionList =
+ (detail.positionList && detail.positionList.length > 0 && detail.positionList[0].id) ||
+ null
+ forms.employeeCityList =
+ detail.employeeCityList &&
+ detail.employeeCityList.map((city: any) => {
+ return Number(city.cityCode)
+ const userReceiveAddress = detail.userReceiveAddress || {}
+ forms.userReceiveAddress = {
+ id: userReceiveAddress.id,
+ name: userReceiveAddress.name,
+ phoneNumber: userReceiveAddress.phoneNumber,
+ address: Number(userReceiveAddress.region || userReceiveAddress.city) || null, //临时
+ province: userReceiveAddress.province,
+ city: userReceiveAddress.city,
+ region: userReceiveAddress.region,
+ detailAddress: userReceiveAddress.detailAddress
+ <NAlert title="基本信息" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ label="员工姓名"
+ path="nickname"
+ message: '请输入员工姓名'
+ v-model:value={forms.nickname}
+ placeholder="请输入员工姓名"
+ label="手机号"
+ path="phone"
+ message: '请输入手机号'
+ /^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\d{8}$/,
+ message: '请输入正确的手机号',
+ v-model:value={forms.phone}
+ maxlength={11}
+ label="性别"
+ path="gender"
+ message: '请选择性别'
+ <NRadioGroup v-model:value={forms.gender}>
+ <NRadio value={1}>男</NRadio>
+ <NRadio value={0}>女</NRadio>
+ label="入职日期"
+ path="entryDate"
+ message: '请选择入职日期'
+ v-model:value={forms.entryDate}
+ type="date"
+ // valueFormat="yyyy-MM-dd mm:hh:ss"
+ style="width: 100%"
+ label="角色"
+ path="roleList"
+ message: '请选择角色'
+ v-model:value={forms.roleList}
+ options={state.selectRole}
+ labelField="roleName"
+ placeholder="请选择角色"
+ maxTagCount={'responsive'}
+ label="岗位"
+ path="positionList"
+ v-model:value={forms.positionList}
+ options={state.selectPosition}
+ placeholder="请选择岗位"
+ label="负责城市"
+ path="employeeCityList"
+ message: '请选择负责城市'
+ v-model:value={forms.employeeCityList}
+ options={state.selectCityEmployee}
+ showPath={false}
+ placeholder="请选择负责城市"
+ <NAlert title="维修地址" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ <NFormItemGi label="员工姓名" path="userReceiveAddress.name">
+ v-model:value={forms.userReceiveAddress.name}
+ path="userReceiveAddress.phoneNumber"
+ validator: phoneValidater,
+ v-model:value={forms.userReceiveAddress.phoneNumber}
+ style="width: 100%;"
+ <NFormItem label="省市区" path="userReceiveAddress.address">
+ v-model:value={forms.userReceiveAddress.address}
+ options={state.selectCity}
+ showPath={true}
+ placeholder="请选择省市区"
+ <NFormItem label="详情地址" path="userReceiveAddress.detailAddress">
+ v-model:value={forms.userReceiveAddress.detailAddress}
+ maxlength={180}
+ placeholder="请输入详情地址"
@@ -0,0 +1,53 @@
+import EmployeeManage from './index'
+import RoleManage from '@/views/system-manage/role-mange/index'
+import StationManage from '@/views/system-manage/station-manage/index'
+ name: 'employee-tab',
+ <h2>员工管理</h2>
+ <NTabPane name="CityList" tab="员工管理" //v-auth="sysEmployee/page1596444091629301762"
+ <EmployeeManage></EmployeeManage>
+ tab="岗位管理"
+ //v-auth="sysPosition/page1596107770146689025"
+ <StationManage></StationManage>
+import { resetPassword, sysEmployeePage, sysEmployeeRemove, sysEmployeeStatus } from '../api'
+import EmployeeOperation from './employee-operation'
+import { filterEmployee } from '@/utils/filters'
+ visiableEmployee: false,
+ employeeOperation: 'add',
+ employeeData: {} as any
+ title: '姓名',
+ title: '角色',
+ key: 'roleList',
+ // ellipsis: {
+ // tooltip: true
+ {row.roleList &&
+ row.roleList.map((role: any) => <NTag type="info">{role.roleName}</NTag>)}
+ title: '岗位',
+ key: 'positionList',
+ {row.positionList &&
+ row.positionList.map((role: any) => <NTag type="info">{role.roleName}</NTag>)}
+ return filterEmployee(row.status)
+ //v-auth="sysEmployee/update1597891779411431425"
+ state.visiableEmployee = true
+ state.employeeOperation = 'edit'
+ state.employeeData = row
+ //v-auth="user/resetPassword1597892728867639297"
+ onClick={() => onResetPassword(row)}
+ 重置密码
+ {row.status !== 'CANCEL' && (
+ //v-auth="sysEmployee/status1597892902956421121"
+ {row.status === 'LOCKED' ? '解冻' : '冻结'}
+ // 重置密码
+ const onResetPassword = (row: any): void => {
+ content: `重置"${row.nickname}"的密码,是否继续?`,
+ await resetPassword({
+ userId: row.id,
+ password: 'gyt' + row.phone.substr(7),
+ clientType: row.clientType
+ message.success('重置成功')
+ const statuStr = row.status === 'LOCKED' ? '解冻' : '冻结'
+ content: `是否${statuStr}"${row.nickname}"?`,
+ await sysEmployeeStatus({ id: row.id })
+ message.success(statuStr + '成功')
+ const { data } = await sysEmployeePage({ ...state.pagination, ...state.searchForm })
+ {/* <h2>员工管理</h2> */}
+ saveKey="EmployeeManage"
+ <NFormItem label="关键字" path="search">
+ placeholder="员工编号/姓名/手机号"
+ //v-auth="sysEmployee/save1597891616592744449"
+ state.employeeOperation = 'add'
+ state.employeeData = {}
+ 添加员工
+ v-model:show={state.visiableEmployee}
+ title={state.employeeOperation === 'add' ? '新增员工' : '修改员工'}
+ <EmployeeOperation
+ type={state.employeeOperation}
+ data={state.employeeData}
+ onClose={() => (state.visiableEmployee = false)}
@@ -0,0 +1,273 @@
+import { sysApplicationPage, sysMenuPage, sysMenuRemove } from '../api'
+import MenuOperation from './menu-operation'
+/*
+parentId 要结点
+path 路径
+name 名称
+component 组件名
+icon 图标
+type 菜单 | 按钮
+permission 权限
+parentPermission 高亮路径
+sort 菜单排序
+hidden 是否隐藏
+ name: 'menu-manage',
+ visiableMenu: false,
+ menuType: 'add',
+ applyData: {} as any,
+ expandedRowKeys: [],
+ appId: null
+ const columns = (): any => [
+ title: '菜单名称',
+ width: 330,
+ return `${row.name} - ${row.id}`
+ title: '应用名称',
+ key: 'appName'
+ title: '组件名',
+ key: 'component'
+ title: '路由路径',
+ key: 'path'
+ title: '权限标识',
+ key: 'permission'
+ key: 'sort'
+ title: '类型',
+ return row.type === '0' ? (
+ <NTag type="primary">菜单</NTag>
+ <NTag type="default">按钮</NTag>
+ state.visiableMenu = true
+ state.menuType = 'edit'
+ state.applyData = row
+ 编辑
+ await sysMenuRemove({ id: row.id })
+ state.expandedRowKeys = []
+ const { data } = await sysMenuPage({
+ ...state.searchForm,
+ parentId: 0,
+ delFlag: false
+ ;(data || []).forEach((item: any) => {
+ item.isLeaf = item.children ? false : true
+ tempList.push(item)
+ state.dataList = formatDataList(tempList)
+ <h2>菜单管理</h2>
+ <NFormItem label="应用" path="name">
+ v-model:value={state.searchForm.appId}
+ options={state.applyList}
+ allowCheckingNotLoaded={false}
+ labelField="appName"
+ childrenField="bizApps"
+ placeholder="请选择应用"
+ ></NCascader>
+ <NSpace style={{ paddingBottom: '12px' }} justify="space-between">
+ state.menuType = 'add'
+ state.applyData = []
+ 添加菜单
+ allowCheckingNotLoaded
+ cascade={false}
+ virtualScroll
+ maxHeight={400}
+ expandedRowKeys={state.expandedRowKeys}
+ onUpdateExpandedRowKeys={(keys: any) => {
+ state.expandedRowKeys = keys
+ v-model:show={state.visiableMenu}
+ title={state.menuType === 'add' ? '添加菜单' : '修改菜单'}
+ <MenuOperation
+ type={state.menuType}
+ data={state.applyData}
+ menuList={state.dataList}
+ applyList={state.applyList}
+ onClose={() => (state.visiableMenu = false)}
@@ -0,0 +1,315 @@
+ NTreeSelect,
+import { sysApplicationDetail, sysApplicationPage, sysMenuSave, sysMenuUpdate } from '../api'
+ menuList: {
+ type: Array,
+ // parentId 上级菜单
+ // path 路径
+ // name 名称
+ // component 组件名
+ // icon 图标
+ // type 菜单 | 按钮
+ // permission 权限
+ // parentPermission 高亮路径
+ // sort 菜单排序
+ // hidden 是否隐藏
+ // linkPath 外链地址
+ appId: null,
+ parentId: null,
+ type: 0,
+ path: null,
+ icon: null,
+ component: null,
+ permission: null,
+ parentPermission: null,
+ linkPath: null,
+ sort: 0,
+ hidden: 0
+ const menuList = ref<any[]>([{ id: '0', name: '根结点', isLeaf: true }, ...props.menuList])
+ await sysMenuSave({
+ ...forms
+ await sysMenuUpdate({ ...forms, id: props.data.id })
+ forms.appId = data.appId
+ forms.type = Number(data.type)
+ forms.path = data.path
+ forms.icon = data.icon
+ forms.component = data.component
+ forms.permission = data.permission
+ forms.parentPermission = data.parentPermission
+ forms.linkPath = data.linkPath
+ forms.sort = data.sort
+ forms.hidden = data.hidden
+ label="应用分类"
+ path="parentId"
+ message: '请选择应用分类'
+ v-model:value={forms.appId}
+ options={props.applyList}
+ placeholder="请选择应用分类"
+ label="上级菜单"
+ message: '请选择上级菜单'
+ <NTreeSelect
+ placeholder="请选择上级菜单"
+ checkStrategy="parent"
+ options={menuList.value}
+ keyField="id"
+ {/* <NCascader
+ ></NCascader> */}
+ <NFormItemGi label="菜单类型" path="type">
+ <NRadioGroup v-model:value={forms.type}>
+ <NRadio value={0}>菜单</NRadio>
+ <NRadio value={1}>按钮</NRadio>
+ label="菜单名称"
+ message: '请输入菜单名称',
+ placeholder="请输入菜单名称"
+ {forms.type === 0 && (
+ label="路由路径"
+ path="path"
+ message: '请输入路由路径',
+ v-model:value={forms.path}
+ placeholder="请输入路由路径"
+ <NFormItemGi label="菜单图标" path="icon">
+ v-model:value={forms.icon}
+ placeholder="请输入菜单图标"
+ label="组件名称"
+ path="component"
+ message: '请输入组件名称',
+ v-model:value={forms.component}
+ placeholder="请输入组件名称"
+ <NFormItemGi label="链接地址" path="linkPath">
+ v-model:value={forms.linkPath}
+ placeholder="外链/内嵌时链接地址(http://www.colexiu.com)"
+ <NFormItemGi label="高亮路径" path="parentPermission">
+ v-model:value={forms.parentPermission}
+ placeholder="子页面高亮路径"
+ label="权限标识"
+ path="permission"
+ message: '请输入权限标识',
+ v-model:value={forms.permission}
+ placeholder="请输入权限标识"
+ <NFormItemGi label="菜单排序" path="sort">
+ v-model:value={forms.sort}
+ placeholder="请输入菜单排序"
+ ></NInputNumber>
+ <NFormItemGi label="是否隐藏" path="hidden">
+ <NRadioGroup v-model:value={forms.hidden}>
@@ -0,0 +1,752 @@
+ addFormMinute,
+ addFormMinuteAddS,
+ reduceFormMinute,
+ reduceFormMinuteAddS,
+ reduceFormMinuteMS
+} from '@/utils/dateUtil'
+ NGridItem,
+import { defineComponent, reactive, ref, shallowRef } from 'vue'
+import { sysParamConfigPage, sysParamConfigUpdate } from '../../api'
+ name: 'attendance-rule',
+ const selectUnit = [
+ label: '元',
+ value: 'MONEY'
+ label: '%',
+ value: 'PERCENTAGE'
+ const forms = reactive({}) as any
+ const beforeData: any = shallowRef({}) // 储存原始数据
+ const { data } = await sysParamConfigPage({ page: 1, rows: 999, group: 'ATTENDANCE' })
+ const rows = data.rows || []
+ rows.forEach((row: any) => {
+ forms[row.paramName] = row.paramValue
+ beforeData.value = { ...forms }
+ // 数组进行对比
+ const dataCompare = (beforeData: any, afterData: any) => {
+ const changeDate: any = []
+ for (let key in beforeData) {
+ if (beforeData[key] != afterData[key]) {
+ changeDate.push({
+ paramName: key,
+ paramValue: afterData[key]
+ return changeDate || []
+ formsRef.value.validate(async (errors: any) => {
+ if (errors) return
+ const submitData = dataCompare(beforeData.value, forms)
+ if (submitData && submitData.length > 0) {
+ await sysParamConfigUpdate({ configs: [...submitData], group: 'ATTENDANCE' })
+ <NForm labelPlacement="left" model={forms} requireMarkPlacement="left" ref={formsRef}>
+ <NAlert
+ title="考勤定位范围"
+ bordered={false}
+ style="margin-bottom: 12px;"
+ <h3>考勤定位范围</h3>
+ <NGrid class={styles.pl13} cols={1}>
+ label="签到、签退GPS定位须在学校定位点"
+ path="scope_of_attendance"
+ pattern: /^\d+$/,
+ message: '请输入定位距离',
+ class={[styles.w140, styles.mr14]}
+ v-model:value={forms['scope_of_attendance']}
+ {{ suffix: () => '米' }}
+ 以内
+ <h3>签到签退定位异常</h3>
+ <span class={styles.red}>签到</span>地点未在教学点范围内,扣减课酬
+ path="sign_in_attendance"
+ <NInputGroup class={[styles.mr14, styles.w140]}>
+ <NInput class={styles.w80} v-model:value={forms['sign_in_attendance']}></NInput>
+ class={styles.w60}
+ options={selectUnit}
+ v-model:value={forms['sign_in_attendance_type']}
+ defaultValue={'MONEY'}
+ <span class={styles.red}>签退</span>地点未在教学点范围内,扣减课酬
+ path="sign_out_attendance"
+ message: '请输入金额',
+ <NInput class={styles.w80} v-model:value={forms['sign_out_attendance']}></NInput>
+ v-model:value={forms['sign_out_attendance_type']}
+ <NAlert title="签到规则" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ <h3>正常签到</h3>
+ <NGridItem class={styles.inlineFlex}>
+ label="课程开始前"
+ path="normal_sign_in_start"
+ message: '请输入分钟数',
+ v-model:value={forms['normal_sign_in_start']}
+ {{ suffix: () => '分钟' }}
+ label="至课程开始前"
+ showRequireMark={false}
+ path="normal_sign_in_end"
+ v-model:value={forms['normal_sign_in_end']}
+ forms['abnormal_sign_in'] = val
+ </NGridItem>
+ <NGridItem>
+ <div class={styles.tips}>
+ 例: <span class={styles.red}>12:00:00</span> 开始的课程,正常签到时间范围:
+ <span class={styles.red}>
+ {reduceFormMinute('12:00:00', forms['normal_sign_in_start'])} ~
+ {reduceFormMinute('12:00:00', forms['normal_sign_in_end'])}
+ </span>{' '}
+ 且最早
+ {reduceFormMinute('12:00:00', forms['normal_sign_in_start'])}
+ 可以进行签到。
+ {forms['normal_sign_in_end'] && (
+ <h3>异常签到</h3>
+ label="未在课程开始前"
+ path="abnormal_sign_in"
+ disabled
+ v-model:value={forms['abnormal_sign_in']}
+ label="前签到,扣减当日训练补助"
+ path="abnormal_sign_in_fee"
+ class={styles.w80}
+ v-model:value={forms['abnormal_sign_in_fee']}
+ v-model:value={forms['abnormal_sign_in_fee_type']}
+ 例: <span class={styles.red}>12:00:00</span> 开始的课程,在
+ {reduceFormMinuteAddS('12:00:00', forms['abnormal_sign_in'])} ~ 12:00:00
+ 时间段内签到为
+ <span class={styles.red}>异常签到</span>。
+ <h3>迟到</h3>
+ label="课程开始后"
+ path="late_sign_in"
+ v-model:value={forms['late_sign_in']}
+ forms['absence_sign_in'] = val
+ label="以内签到,扣减当日训练补助"
+ path="late_sign_in_fee"
+ <NInput class={styles.w80} v-model:value={forms['late_sign_in_fee']}></NInput>
+ v-model:value={forms['late_sign_in_fee_type']}
+ {addFormMinuteAddS('12:00:00', 0)} ~{' '}
+ {addFormMinute('12:00:00', forms['late_sign_in'])}
+ <span class={styles.red}>迟到</span>。
+ {forms['late_sign_in'] && (
+ <h3>旷课</h3>
+ label="课程开始"
+ path="absence_sign_in"
+ v-model:value={forms['absence_sign_in']}
+ label="后签到,扣减当日训练补助"
+ path="absence_sign_in_fee"
+ v-model:value={forms['absence_sign_in_fee']}
+ v-model:value={forms['absence_sign_in_fee_type']}
+ {addFormMinute('12:00:00', forms['absence_sign_in'])}
+ 后签到(含
+ )为
+ <span class={styles.red}>旷课</span>。
+ <h3>未签到</h3>
+ label="扣减当日训练补助"
+ path="not_sign_in_fee"
+ <NInput class={styles.w80} v-model:value={forms['not_sign_in_fee']}></NInput>
+ v-model:value={forms['not_sign_in_fee_type']}
+ <NAlert title="签退规则" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ <h3>正常签退</h3>
+ label="当日课程结束"
+ path="normal_sign_out"
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['normal_sign_out']}>
+ 内签退
+ 例: <span class={styles.red}>13:00:00</span> 当日课程结束,可在
+ 13:00:00 ~ {addFormMinute('13:00:00', forms['normal_sign_out'])}{' '}
+ 时间段内完成签退。
+ <h3>异常签退</h3>
+ label="课程结束前"
+ path="abnormal_sign_out"
+ v-model:value={forms['abnormal_sign_out']}
+ forms['premise_sign_out'] = val
+ label="至结束时间段内签退,扣减当日训练补助"
+ path="abnormal_sign_out_fee"
+ v-model:value={forms['abnormal_sign_out_fee']}
+ v-model:value={forms['abnormal_sign_out_fee_type']}
+ 例: <span class={styles.red}>17:00:00</span> 当日课程结束,在
+ {reduceFormMinute('17:00:00', forms['abnormal_sign_out'])} ~{' '}
+ {reduceFormMinuteMS('17:00:00', 0)}
+ 时间段内签退为
+ {forms['abnormal_sign_out'] && (
+ <h3>早退</h3>
+ path="premise_sign_out"
+ v-model:value={forms['premise_sign_out']}
+ label="以上签退,扣减当日训练补助"
+ path="premise_sign_out_fee"
+ v-model:value={forms['premise_sign_out_fee']}
+ v-model:value={forms['premise_sign_out_fee_type']}
+ {reduceFormMinute('17:00:00', forms['premise_sign_out'])}
+ 前签退(含
+ <span class={styles.red}>早退</span>。
+ {/* <h3>旷课</h3>
+ path="absence_sign_out"
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['absence_sign_out']}>
+ path="absence_sign_out_fee"
+ <NInput class={styles.w80} v-model:value={forms['absence_sign_out_fee']}></NInput>
+ v-model:value={forms['absence_sign_out_fee_type']}
+ <span class={styles.red}>12:30:01 ~ 12:00:00</span> 以后签到为
+ <span class={styles.red}>旷课</span>
+ </NGrid> */}
+ <h3>未签退</h3>
+ label="课程未签退,扣减当日训练补助"
+ path="not_sign_out_fee"
+ <NInput class={styles.w80} v-model:value={forms['not_sign_out_fee']}></NInput>
+ v-model:value={forms['not_sign_out_fee_type']}
+ <h3>签退提醒</h3>
+ label="当日训练结束后"
+ path="sign_out_remind"
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['sign_out_remind']}>
+ 对当日存在未签退课程的老师发送短信、推送、公众号提醒
+ //v-auth="sysParamConfig/update1597903049401421825"
+ 保存设置
@@ -0,0 +1,209 @@
+import { NAlert, NButton, NForm, NFormItemGi, NGrid, NInput, useMessage } from 'naive-ui'
+ name: 'finance-rule',
+ const { data } = await sysParamConfigPage({ page: 1, rows: 999, group: 'FINANCE' })
+ if (row.paramName === 'refund_reason') {
+ forms[row.paramName] = row.paramValue.trim()
+ await sysParamConfigUpdate({ configs: [...submitData], group: 'FINANCE' })
+ <NForm ref={formsRef} labelPlacement="left" model={forms} requireMarkPlacement="left">
+ <NAlert title="训练补助" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ label="训练结束"
+ path="training_end"
+ message: '请输入训练结束天数',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['training_end']}>
+ {{ suffix: () => '天' }}
+ 后,发放训练补助
+ label="训练补助结算扣除"
+ path="subsidy_tax_rate"
+ message: '请输入训练补助结算百分比',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['subsidy_tax_rate']}>
+ {{ suffix: () => '‰' }}
+ 手续费
+ label="申请提现后"
+ path="subsidy_withdraw"
+ message: '请输入申请提现的工作日',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['subsidy_withdraw']}>
+ {{ suffix: () => '个工作日' }}
+ 内打款
+ <NAlert title="交易退费" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ label="交易完成"
+ path="transaction_complete"
+ message: '请输入交易完成天数',
+ v-model:value={forms['transaction_complete']}
+ 内可申请退费
+ label="退费扣除"
+ path="transaction_complete_tax_rate"
+ message: '请输入退费扣除百分比',
+ v-model:value={forms['transaction_complete_tax_rate']}
+ label="退费原因"
+ path="refund_reason"
+ message: '请输入退费原因',
+ class={[styles.mr14]}
+ style={'width: 400px !important;'}
+ v-model:value={forms['refund_reason']}
+ //v-auth="sysParamConfig/update1597903120771698689"
@@ -0,0 +1,42 @@
+.mr14 {
+ margin-right: 14px;
+.inlineFlex {
+ display: inline-flex !important;
+.w140 {
+ width: 140px !important;
+.w80 {
+ width: 80px !important;
+.w60 {
+ width: 60px !important;
+.red {
+ color: red;
+h3 {
+ padding-left: 13px;
+.pl13 {
+.tips {
+ font-size: 13px;
+ color: #333;
+ margin-bottom: 20px;
+ padding: 6px 12px;
+ border-radius: 4px;
+ max-width: 650px;
@@ -0,0 +1,130 @@
+import { clientTypeArray, openTypeArray } from '@/utils/searchArray'
+ NSelect
+import { leaveCategorySave, leaveCategoryUpdate } from '../../api'
+ remark: null
+ await leaveCategorySave({ ...forms })
+ await leaveCategoryUpdate({ ...forms, id: props.data.id })
+ } catch (e: any) {
+ console.log(e, 'e')
+ label="客户端类型"
+ message: '请输入客户端类型'
+ placeholder="请输入客户端类型"
+ options={clientTypeArray}
+ label="类型名称"
+ message: '请输入类型名称'
+ placeholder="请输入类型名称"
+ <NFormItem label="备注" path="remark">
+ placeholder="请输入备注"
+ <NButton type="primary" onClick={onSubmit} loading={btnLoading.value}>
@@ -0,0 +1,161 @@
+import { NButton, NDataTable, NModal, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
+import { leaveCategoryPage, leaveCategoryRemove } from '../../api'
+import { filterClientType, filterMessageGroup } from '@/utils/filters'
+import LeaveCategoryOperation from './leave-category-operation'
+ visiableLeave: false,
+ leaveOperation: 'add',
+ leaveData: {} as any
+ title: '客户端类型',
+ title: '类型名称',
+ return <TheTooltip content={row.name} />
+ title: '备注',
+ key: 'remark',
+ return <TheTooltip content={row.remark} />
+ //v-auth="leaveCategory/update1610189620435570689"
+ state.visiableLeave = true
+ state.leaveOperation = 'edit'
+ state.leaveData = row
+ onClick={() => onRemove(row)}
+ //v-auth="leaveCategory/remove1610189694385344513"
+ await leaveCategoryRemove({ id: row.id })
+ const { data } = await leaveCategoryPage({ ...state.pagination })
+ <NSpace style="padding-bottom: 12px">
+ //v-auth="leaveCategory/save1610189531537297410"
+ state.leaveOperation = 'add'
+ state.leaveData = []
+ 新增
+ v-model:show={state.visiableLeave}
+ title={state.leaveOperation === 'add' ? '新增请假类型' : '修改请假类型'}
+ <LeaveCategoryOperation
+ type={state.leaveOperation}
+ data={state.leaveData}
+ onClose={() => (state.visiableLeave = false)}
+import { getVipPriceSettings, setVipPriceSettings } from '../../api'
+ name: 'member-fee',
+ const forms = reactive({ id: 0, originalPrice: 0, salePrice: 0 }) as any
+ const { data } = await getVipPriceSettings({ page: 1, rows: 999, cardTypes: ['VIP'] })
+ const rows = data.rows[0]
+ forms.id = rows.id
+ forms.originalPrice = rows.originalPrice
+ forms.salePrice = rows.salePrice
+ console.log(forms)
+ // beforeData.value = { ...forms }
+ // // 数组进行对比
+ // const dataCompare = (beforeData: any, afterData: any) => {
+ // const changeDate: any = []
+ // for (let key in beforeData) {
+ // if (beforeData[key] != afterData[key]) {
+ // changeDate.push({
+ // id: key,
+ // paramValue: afterData[key]
+ // return changeDate || []
+ const res = await setVipPriceSettings({ ...forms })
+ <NAlert title="会员价格" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ label="会员原价"
+ path="originalPrice"
+ pattern: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/,
+ message: '请输正确的会员原价',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms.originalPrice}>
+ {{ suffix: () => '元' }}
+ label="会员售价"
+ path="salePrice"
+ message: '请输正确的入会员售价',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms.salePrice}>
+ //v-auth="vipPriceSettings/update1623234797500751874"
@@ -0,0 +1,255 @@
+import { getMenus } from '@/api/system/menu'
+import { clientTypeArray, messageGroupArray, openTypeArray } from '@/utils/searchArray'
+import { SearchOutline } from '@vicons/ionicons5'
+ NGrid
+import { sysMessageConfigSave, sysMessageConfigUpdate } from '../../api'
+const messageTypeArray = [
+ { label: 'CUSTOME_MESSAGE_PUSH', value: 'CUSTOME_MESSAGE_PUSH' },
+ { label: 'SMS_VERIFY_CODE_LOGIN', value: 'SMS_VERIFY_CODE_LOGIN' },
+ { label: 'SMS_VERIFY_CODE_LOGOFF', value: 'SMS_VERIFY_CODE_LOGOFF' },
+ { label: 'SMS_VERIFY_CODE_REGISTER', value: 'SMS_VERIFY_CODE_REGISTER' },
+ { label: 'SMS_VERIFY_CODE_UPDATE_PSW', value: 'SMS_VERIFY_CODE_UPDATE_PSW' },
+ { label: 'SMS_VERIFY_CODE_UPDATE_PHONE', value: 'SMS_VERIFY_CODE_UPDATE_PHONE' },
+ { label: 'SMS_VERIFY_CODE', value: 'SMS_VERIFY_CODE' },
+ { label: 'CAPTCHA_SESSION_KEY', value: 'CAPTCHA_SESSION_KEY' },
+ { label: 'SMS_VERIFY_CODE_BANK_CARD', value: 'SMS_VERIFY_CODE_BANK_CARD' },
+ { label: 'STUDENT_JOIN_FANSGROUP', value: 'STUDENT_JOIN_FANSGROUP' },
+ { label: 'FANSGROUP_APPLY_SUCCESS', value: 'FANSGROUP_APPLY_SUCCESS' }
+const subTypeArray = [
+ { label: 'TEACHER_AUTH', value: 'TEACHER_AUTH' },
+ { label: 'PRACTICE', value: 'PRACTICE' },
+ { label: 'MUSIC_SHEET', value: 'MUSIC_SHEET' },
+ { label: 'COURSE', value: 'COURSE' },
+ { label: 'INCOME', value: 'INCOME' },
+ { label: 'HOMEWORK', value: 'HOMEWORK' },
+ { label: 'EVALUATE', value: 'EVALUATE' },
+ { label: 'GROUP_CHAT', value: 'GROUP_CHAT' },
+ { label: 'VIP', value: 'VIP' }
+const actionArray = [
+ { label: 'h5', value: 'h5' },
+ { label: 'app', value: 'app' }
+ sendFlag: null,
+ clientId: null,
+ actionUrl: null,
+ description: null,
+ params: null,
+ messageType: null,
+ action: null,
+ subType: null,
+ group: null
+ }) as any
+ console.log('date', '1212')
+ await sysMessageConfigSave({ ...forms })
+ await sysMessageConfigUpdate({ ...forms, id: props.data.id })
+ forms.sendFlag = data.sendFlag
+ forms.clientId = data.clientId
+ forms.actionUrl = data.actionUrl
+ forms.params = data.params
+ forms.messageType = data.messageType
+ forms.action = data.action
+ forms.subType = data.subType
+ forms.group = data.group
+ <NFormItemGi label="图标地址" path="icon">
+ <UploadFile size={5} listType="image-card" v-model:fileList={forms.icon} />
+ label="是否发送"
+ path="sendFlag"
+ message: '请选择是否发送'
+ <NRadioGroup v-model:value={forms.sendFlag}>
+ <NRadio value={1}>发送</NRadio>
+ <NRadio value={0}>不发送</NRadio>
+ path="clientId"
+ v-model:value={forms.clientId}
+ label="消息类型"
+ path="messageType"
+ message: '请输入消息类型'
+ placeholder="请输入消息类型"
+ v-model:value={forms.messageType}
+ options={messageTypeArray}
+ label="消息组"
+ path="group"
+ message: '请输入消息组'
+ placeholder="请输入消息组"
+ v-model:value={forms.group}
+ options={messageGroupArray}
+ <NFormItemGi label="二级分类" path="subType">
+ placeholder="请输入二级分类"
+ v-model:value={forms.subType}
+ options={subTypeArray}
+ <NFormItemGi label="跳转类型" path="action">
+ placeholder="请输入跳转类型"
+ v-model:value={forms.action}
+ options={actionArray}
+ <NFormItemGi label="跳转连接" path="actionUrl">
+ v-model:value={forms.actionUrl}
+ placeholder="请输入跳转连接"
+ <NFormItemGi label="其它参数" path="params">
+ v-model:value={forms.params}
+ placeholder="请输入其它参数"
+ <NFormItemGi label="消息内容" path="content">
+ v-model:value={forms.content}
+ placeholder="请输入消息内容"
+ <NFormItemGi label="描述" path="description">
@@ -0,0 +1,180 @@
+import { sysMessageConfigPage, sysMessageConfigRemove } from '../../api'
+import MessageOperation from './message-operation'
+ visiablePayment: false,
+ paymentOperation: 'add',
+ paymentData: {} as any
+ key: 'clientId',
+ return filterClientType(row.clientId)
+ title: '消息类型',
+ key: 'messageType'
+ title: '消息跳转类型',
+ key: 'action'
+ title: '二级分类',
+ key: 'subType'
+ title: '消息组',
+ key: 'group',
+ return filterMessageGroup(row.group)
+ title: '消息是否发送',
+ key: 'sendFlag',
+ return row.sendFlag === 1 ? (
+ <NTag type="primary">发送</NTag>
+ <NTag type="default">不发送</NTag>
+ //v-auth="sysMessageConfig/update1597903931924926465"
+ state.visiablePayment = true
+ state.paymentOperation = 'edit'
+ state.paymentData = row
+ //v-auth="sysMessageConfig/remove1597904021058080770"
+ content: `是否删除?`,
+ await sysMessageConfigRemove({ id: row.id })
+ const { data } = await sysMessageConfigPage({ ...state.pagination })
+ //v-auth="sysMessageConfig/save1597903839524409345"
+ state.paymentOperation = 'add'
+ state.paymentData = []
+ v-model:show={state.visiablePayment}
+ title={state.paymentOperation === 'add' ? '新增消息通知' : '修改消息通知'}
+ <MessageOperation
+ type={state.paymentOperation}
+ data={state.paymentData}
+ onClose={() => (state.visiablePayment = false)}
@@ -0,0 +1,401 @@
+import { deepClone } from '@/layout/components/Header/imkit/utils/utils'
+import { NAlert, NButton, NForm, NFormItemGi, NGrid, NInput, NSelect, useMessage } from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref, shallowRef } from 'vue'
+ name: 'other-rule',
+ const beforeData: any = ref({}) // 储存原始数据
+ const { data } = await sysParamConfigPage({ page: 1, rows: 999, group: 'OTHER' })
+ await sysParamConfigUpdate({ configs: [...submitData], group: 'OTHER' })
+ <NAlert title="设备限制" showIcon={false} bordered={false} style="margin-bottom: 12px;" />
+ label="教务端每个账号可登录"
+ path="school_device_num"
+ message: '请输入设备数',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['school_device_num']}>
+ {{ suffix: () => '台' }}
+ 设备
+ label="学生端每个账号可登录"
+ path="student_device_num"
+ v-model:value={forms['student_device_num']}
+ label="老师端每个账号可登录"
+ path="teacher_device_num"
+ v-model:value={forms['teacher_device_num']}
+ label="后端账号登录异常"
+ path="backend_login_locked"
+ message: '请输入次数',
+ v-model:value={forms['backend_login_locked']}
+ {{ suffix: () => '次' }}
+ 锁定
+ label="教务端账号登录异常"
+ path="school_login_locked"
+ v-model:value={forms['school_login_locked']}
+ label="学生端账号登录异常"
+ path="student_login_locked"
+ v-model:value={forms['student_login_locked']}
+ label="老师端账号登录异常"
+ path="teacher_login_locked"
+ v-model:value={forms['teacher_login_locked']}
+ {/* <NAlert
+ title="学期开始时间"
+ label="上学期开始时间"
+ path="last_term_time"
+ message: '请输入上学期开始时间',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['last_term_time']}></NInput>
+ label="下学期开始时间"
+ path="next_term_time"
+ message: '请输入下学期开始时间',
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['next_term_time']}></NInput>
+ title="课程最早开始和最晚结束时间"
+ label="最早开始时间"
+ path="course_start_time"
+ message: '请输入最早开始时间',
+ v-model:value={forms['course_start_time']}
+ label="最晚结束时间"
+ path="course_end_time"
+ message: '请输入最晚结束时间',
+ v-model:value={forms['course_end_time']}
+ title="二维码有效期"
+ label="二维码过期时间"
+ path="qr_code_expire_hours"
+ message: '请输入二维码过期时间',
+ v-model:value={forms['qr_code_expire_hours']}
+ {{ suffix: () => '小时' }}
+ title="酷乐秀按钮设置"
+ label="是否开启酷乐秀"
+ path="clx_enable"
+ message: '请输入天数',
+ class={styles.w140}
+ value: '1'
+ value: '0'
+ v-model:value={forms['clx_enable']}
+ label="开启酷乐秀按钮后,学生上课"
+ path="clx_hidden_day"
+ <NInput class={[styles.w140, styles.mr14]} v-model:value={forms['clx_hidden_day']}>
+ 后展示按钮
+ //v-auth="sysParamConfig/update1597903200849350657"
@@ -0,0 +1,168 @@
+import { sysPaymentConfigSave, sysPaymentConfigUpdate } from '../../api'
+ paramName: null,
+ paramValue: null,
+ openType: null,
+ await sysPaymentConfigSave({ ...forms })
+ await sysPaymentConfigUpdate({ ...forms, id: props.data.id })
+ forms.paramName = data.paramName
+ forms.paramValue = data.paramValue
+ forms.openType = data.openType
+ label="提供方"
+ path="openType"
+ message: '请输入提供方'
+ placeholder="请输入提供方"
+ v-model:value={forms.openType}
+ options={openTypeArray}
+ label="参数名称"
+ path="paramName"
+ message: '请输入参数名称'
+ v-model:value={forms.paramName}
+ placeholder="请输入参数名称"
+ label="参数值"
+ path="paramValue"
+ message: '请输入参数值'
+ v-model:value={forms.paramValue}
+ placeholder="请输入参数值"
+ <NFormItem label="备注" path="description">
@@ -0,0 +1,167 @@
+import { sysPaymentConfigPage, sysPaymentConfigRemove } from '../../api'
+import { filterClientType, filterOpenType } from '@/utils/filters'
+import PaymentOperation from './payment-operation'
+ title: '提供方',
+ key: 'openType',
+ return filterOpenType(row.openType)
+ title: '参数名称',
+ key: 'paramName'
+ title: '参数值',
+ key: 'paramValue'
+ //v-auth="sysPaymentConfig/update1597903527669518338"
+ //v-auth="sysPaymentConfig/remove1597903613933768705"
+ await sysPaymentConfigRemove({ id: row.id })
+ const { data } = await sysPaymentConfigPage({ ...state.pagination })
+ //v-auth="sysPaymentConfig/save1597903459457552386"
+ saveKey="payemnt-setting"
+ title={state.paymentOperation === 'add' ? '新增支付参数' : '修改支付参数'}
+ <PaymentOperation
@@ -0,0 +1,91 @@
+import AttendanceRule from './component/attendance-rule'
+import FinanceRule from './component/finance-rule'
+import LeaveCategory from './component/leave-category'
+import MessageSetting from './component/message-setting'
+import OtherRule from './component/other-rule'
+import PaymentSetting from './component/payment-setting'
+import Memberfee from './component/member-fee'
+ name: 'param-settings',
+ tabName: 'attendance' as 'attendance' | 'finance' | 'other' | 'payment' | 'message' | 'leave'
+ <h2>参数设置</h2>
+ name="attendance"
+ tab="考勤规则"
+ //v-auth="sysParamConfig/page1597901894499815425"
+ <AttendanceRule />
+ name="finance"
+ tab="财务规则"
+ //v-auth="sysParamConfig/page1597902460701495297"
+ <FinanceRule />
+ <NTabPane name="other" tab="其它参数" //v-auth="sysParamConfig/page1597902586396397570"
+ <OtherRule />
+ {/* <NTabPane
+ name="payment"
+ tab="支付参数"
+ v-auth="sysPaymentConfig/page1597902712028385281"
+ <PaymentSetting />
+ name="message"
+ tab="消息通知设置"
+ //v-auth="sysMessageConfig/page1597902847743479810"
+ <MessageSetting />
+ name="leave"
+ tab="请假类型"
+ <LeaveCategory />
+ name="member"
+ tab="会员价格配置"
+ //v-auth="vipPriceSettings/page1623234661835988994"
+ <Memberfee />
+import { NDataTable } from 'naive-ui'
+import { sysSuggestionPage } from '../api'
+import { filterClientType, filterSuggestionType } from '@/utils/filters'
+ title: '建议类型',
+ width: 100,
+ return filterSuggestionType(row.type)
+ title: '学校名称',
+ key: 'schoolName',
+ return <TheTooltip content={row.schoolName} />
+ title: '反馈时间',
+ title: '内容',
+ key: 'content',
+ return <TheTooltip content={row.content} />
+ title: '用户',
+ key: 'mobileNo'
+ width: '300'
+ const { data } = await sysSuggestionPage({ ...state.pagination, ...state.searchForm })
+ <h2>意见反馈</h2>
@@ -0,0 +1,226 @@
+import { protocolTypeArray } from '@/utils/searchArray'
+import { NForm, NFormItem, NInput, NSelect, NButton, NSpace, NModal, useMessage } from 'naive-ui'
+import { schoolContractTemplateSave } from '../api'
+ name: 'add-protocol',
+ const visiable = ref<boolean>(false)
+ type: null,
+ contractNo: null,
+ origanalFileUrl: null,
+ contractTemplateContent: null,
+ timeFlag: null,
+ time: null
+ await schoolContractTemplateSave({ ...forms, id: props.data.id })
+ const displayResult = (result: any) => {
+ let html = result.value
+ let newHTML = html
+ .replace(//g, '')
+ .replace('<h1>', '<h2 style="font-size: 16px;font-weight: bold; padding-top: 15px;">')
+ forms.contractTemplateContent = newHTML
+ let reader = new FileReader()
+ reader.onload = function (loadEvent: any) {
+ let arrayBuffer = loadEvent.target.result //arrayBuffer
+ // @ts-ignore
+ mammoth
+ .convertToHtml({ arrayBuffer: arrayBuffer })
+ .then(function (result: any) {
+ displayResult(result)
+ .catch(function (e: any) {
+ forms.origanalFileUrl = null
+ message.error('上传文件有误,请重新上传')
+ .done()
+ reader.readAsArrayBuffer(file)
+ <NForm model={forms} ref={formsRef} label-placement="left" label-width="110px">
+ label="协议名称"
+ message: '请输入协议名称'
+ <NInput placeholder="请输入协议名称" v-model:value={forms.name} />
+ label="协议类型"
+ path="type"
+ message: '请选择协议类型'
+ placeholder="请选择协议类型"
+ options={protocolTypeArray}
+ v-model:value={forms.type}
+ label="协议号"
+ path="contractNo"
+ message: '请输入协议号'
+ <NInput placeholder="请输入协议号" v-model:value={forms.contractNo} />
+ label="签署时长状态"
+ path="timeFlag"
+ message: '请输入签署时长状态'
+ placeholder="请选择签署时长"
+ { label: '显示', value: true },
+ { label: '隐藏', value: false }
+ v-model:value={forms.timeFlag}
+ {/* 显示才要输入时长 */}
+ {forms.timeFlag && (
+ label="签署时长"
+ path="time"
+ message: '请输入签署时长'
+ placeholder="请输入签署时长(年)"
+ v-model:value={forms.time}
+ maxlength={2}
+ label="上传协议"
+ path="origanalFileUrl"
+ message: '请上传协议'
+ tips="仅支持上传 docx 格式文件"
+ accept=".docx"
+ v-model:fileList={forms.origanalFileUrl}
+ onRemove={() => {
+ forms.contractTemplateContent = null
+ visiable.value = true
+ 下一步
+ v-model:show={visiable.value}
+ title="查看协议"
+ <div
+ style="max-height: 450px; overflow-y: auto;"
+ v-html={forms.contractTemplateContent}
+ ></div>
+ <NSpace justify="end" style="padding-top: 16px">
+ <NButton type="default" onClick={() => (visiable.value = false)}>
+ <NButton type="primary" onClick={onSubmit}>
+ 确定
@@ -0,0 +1,266 @@
+import AddProtocol from './add-protocol'
+import { filterProtocolType } from '@/utils/filters'
+ schoolContractTemplateDetail,
+ schoolContractTemplatePage,
+ schoolContractTemplateUpdateStatus
+ type: '',
+ status: ''
+ visiableProtocol: false,
+ protocolOperation: 'add',
+ protocolData: {},
+ visiableLook: false
+ title: '协议编号',
+ title: '协议名称',
+ title: '协议号',
+ key: 'contractNo'
+ title: '协议类型',
+ return filterProtocolType(row.type)
+ key: 'username'
+ return row.status ? <NTag type="primary">启用</NTag> : <NTag type="default">停用</NTag>
+ //v-auth="schoolContractTemplate/detail1599978249005670402"
+ onClick={() => lookDetails(row)}
+ onClick={() => onUpdateState(row)}
+ //v-auth="schoolContractTemplate/updateStatus1597901264288862209"
+ const onUpdateState = async (row: any) => {
+ await schoolContractTemplateUpdateStatus({ id: row.id })
+ message.success('启用成功')
+ const lookDetails = async (row: any) => {
+ const { data } = await schoolContractTemplateDetail({ id: row.id })
+ state.visiableLook = true
+ state.contractTemplateContent = data.contractTemplateContent
+ const { data } = await schoolContractTemplatePage({
+ ...state.searchForm
+ state.pagination.pageTotal = data.total || 0
+ <h2>协议管理</h2>
+ placeholder="请输入协议名称/协议号"
+ <NFormItem label="协议类型" path="type">
+ <NSelect options={protocolTypeArray} v-model:value={state.searchForm.type} clearable />
+ <NFormItem label="协议状态" path="status">
+ { label: '启用', value: 1 },
+ { label: '停用', value: 0 }
+ type="success"
+ //v-auth="schoolContractTemplate/save1597901145271291905"
+ state.visiableProtocol = true
+ 添加协议
+ v-model:show={state.visiableProtocol}
+ title="新增协议"
+ <AddProtocol
+ type={state.protocolOperation}
+ data={state.protocolData}
+ onClose={() => (state.visiableProtocol = false)}
+ v-model:show={state.visiableLook}
+ v-html={state.contractTemplateContent}
+ <NButton type="primary" onClick={() => (state.visiableLook = false)}>
@@ -0,0 +1,251 @@
+import { sysApplicationPage, sysRolePage, sysRoleRemove } from '../api'
+import RoleOperation from './role-operation'
+ roleName: null
+ title: '应用名',
+ title: '角色名称',
+ key: 'roleName'
+ title: '角色状态',
+ return row.enable ? <NTag type="primary">开启</NTag> : <NTag type="default">关闭</NTag>
+ title: '角色描述',
+ key: 'roleDesc'
+ //v-auth="sysRole/update1597890274151223298"
+ state.visiableRole = true
+ state.roleOperation = 'edit'
+ state.roleData = row
+ //v-auth="sysRole/remove1597890339439759362"
+ await sysRoleRemove({ id: row.id })
+ const { data } = await sysRolePage({ ...state.pagination, ...state.searchForm })
+ <h2>角色管理</h2>
+ <NFormItem label="应用分类" path="appId">
+ <NFormItem label="角色名称" path="roleName">
+ <NInput v-model:value={state.searchForm.roleName} placeholder="请输入角色名称" />
+ //v-auth="sysRole/save1597890178735001601"
+ state.roleData = {}
+ state.roleOperation = 'add'
+ 添加角色
+ v-model:show={state.visiableRole}
+ title={state.roleOperation === 'add' ? '新增角色' : '修改角色'}
+ <RoleOperation
+ type={state.roleOperation}
+ data={state.roleData}
+ onClose={() => (state.visiableRole = false)}
@@ -0,0 +1,299 @@
+ NTree,
+ NCheckbox
+import { defineComponent, onMounted, PropType, reactive, ref, shallowRef } from 'vue'
+import { sysRoleSave, sysRoleUpdate, sysRoleDetail } from '../api'
+ roleName: null,
+ menuId: [] as any,
+ enable: false,
+ roleDesc: null
+ const menuList = shallowRef<any[]>([])
+ const pattern = ref('')
+ const isChecked = ref(false)
+ const idsList = ref<any[]>([]) // 所有结点
+ const indeterminate = ref(false)
+ const indeterminateKeys = ref<any[]>([])
+ const roleDetail = ref<any>({})
+ const treeRef = ref()
+ const res = treeRef.value.getCheckedData()
+ const inRes = treeRef.value.getIndeterminateData()
+ const menuId = [...res.keys, ...inRes.keys]
+ // return
+ const { ...res } = forms
+ const roleIds = [...new Set(menuId)]
+ await sysRoleSave({ ...res, menuId: roleIds })
+ await sysRoleUpdate({ ...res, menuId: roleIds, id: props.data.id })
+ // 选中结点
+ const updateCheckedKeys = (options: any) => {
+ // 判断选中条件
+ if (options.length >= idsList.value.length) {
+ isChecked.value = true
+ indeterminate.value = false
+ } else if (options.length > 0) {
+ indeterminate.value = true
+ isChecked.value = false
+ forms.menuId = options
+ // 由于手动添加选中的值,不会触发update事件,所以需要手动去设置按钮状态
+ const onCheckAll = (obj: boolean) => {
+ isChecked.value = obj
+ if (obj) {
+ forms.menuId = [...idsList.value]
+ forms.menuId = []
+ indeterminateKeys.value = []
+ const getMenuList = async () => {
+ const menus = await getMenus({ delFlag: false })
+ menuList.value = menus.data
+ getMenuLength(menus.data)
+ // 获取菜单id数量
+ const getMenuLength = async (menu: any) => {
+ menu.map((m: any) => {
+ idsList.value.push(m.id)
+ if (m.children && m.children.length > 0) {
+ getMenuLength(m.children)
+ // 获取角色详情
+ const getRoleDetail = async () => {
+ const res = await sysRoleDetail({ id: data.id })
+ roleDetail.value = res.data || {}
+ await getMenuList()
+ await getRoleDetail()
+ forms.roleName = data.roleName
+ forms.enable = data.enable
+ forms.roleDesc = data.roleDesc
+ forms.menuId = roleDetail.value.menuLeafId
+ if (roleDetail.value.menuId.length >= idsList.value.length) {
+ } else if (forms.menuId.length > 0) {
+ path="appId"
+ label="角色名称"
+ path="roleName"
+ message: '请输入角色名称'
+ v-model:value={forms.roleName}
+ placeholder="请输入角色名称"
+ label="角色状态"
+ path="enable"
+ message: '请选择角色状态'
+ <NRadioGroup v-model:value={forms.enable}>
+ <NRadio value={true}>启用</NRadio>
+ <NRadio value={false}>停用</NRadio>
+ <NFormItem label="备注" path="roleDesc">
+ v-model:value={forms.roleDesc}
+ <NFormItem label="权限" path="status">
+ <NSpace vertical style={{ width: '100%' }}>
+ placeholder="输入菜单名搜索"
+ v-model:value={pattern.value}
+ suffix: () => (
+ <NIcon>
+ <SearchOutline />
+ <NCheckbox
+ indeterminate={indeterminate.value}
+ checked={isChecked.value}
+ onUpdateChecked={onCheckAll}
+ 全选
+ </NCheckbox>
+ <NTree
+ width: '100%',
+ border: '1px solid #e0e0e6',
+ height: '250px',
+ borderRadius: '3px'
+ ref={treeRef}
+ virtual-scroll
+ cascade
+ checkable
+ showIrrelevantNodes={false}
+ pattern={pattern.value}
+ data={menuList.value}
+ checkedKeys={forms.menuId}
+ // indeterminateKeys={indeterminateKeys.value}
+ onUpdate:checkedKeys={updateCheckedKeys}
+ // onUpdateIndeterminateKeys={updateIndeterminateKeys}
+ childrenField="children"
@@ -0,0 +1,174 @@
+import { NButton, NDataTable, NImage, NModal, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
+import { subjectPage, subjectUpdate } from '../api'
+import SongOperation from './song-operation'
+ visiableSong: false,
+ songOperation: 'add',
+ songData: {} as any
+ title: '图片',
+ key: 'img',
+ return <NImage width={70} src={row.img} />
+ title: '乐器名称',
+ title: '乐器编码',
+ key: 'code'
+ // title: '状态',
+ // key: 'delFlag',
+ // return !row.delFlag ? <NTag type="primary">启用</NTag> : <NTag type="error">停用</NTag>
+ //v-auth="subject/update1598205405598932994"
+ state.visiableSong = true
+ state.songOperation = 'edit'
+ state.songData = row
+ v-auth="subject/update1668958388100288514"
+ onClick={() => onOperation(row)}
+ {row.delFlag ? '启用' : '停用'}
+ // const onOperation = async (row: any) => {
+ // const content = row.delFlag ? `您是否启用“${row.name}”?` : `您是否停用“${row.name}”?`
+ // dialog.warning({
+ // title: '提示',
+ // content,
+ // positiveText: '确定',
+ // negativeText: '取消',
+ // onPositiveClick: async () => {
+ // try {
+ // await subjectUpdate({ id: row.id, delFlag: !row.delFlag })
+ // message.success(`${row.delFlag ? '启用' : '停用'}成功`)
+ // } catch {}
+ const { data } = await subjectPage({ ...state.pagination, ...state.searchForm })
+ <h2>声部管理</h2>
+ //v-auth="subject/save1598205342327857154"
+ state.songOperation = 'add'
+ state.songData = {}
+ 添加声部
+ v-model:show={state.visiableSong}
+ title={state.songOperation === 'add' ? '新增声部' : '修改声部'}
+ <SongOperation
+ type={state.songOperation}
+ data={state.songData}
+ onClose={() => (state.visiableSong = false)}
@@ -0,0 +1,122 @@
+import { NForm, NInput, NSpace, NButton, useMessage, NFormItem } from 'naive-ui'
+import { subjectSave, subjectUpdate } from '../api'
+ code: null,
+ img: null,
+ await subjectSave({ ...forms })
+ await subjectUpdate({
+ forms.img = data.img
+ forms.code = data.code
+ label="声部图片"
+ path="img"
+ message: '请输入声部图片'
+ size={2}
+ v-model:fileList={forms.img}
+ bucketName="gyt"
+ path="basic/"
+ tips="图片大小2M以内"
+ label="声部名称"
+ message: '请输入声部名称'
+ placeholder="请输入声部名称"
+ <NFormItem label="声部编码" path="code">
+ <NInput v-model:value={forms.code} placeholder="请输入声部编码" clearable></NInput>
@@ -0,0 +1,281 @@
+import { subjectBasicConfigPage, subjectBasicConfigUpdateConfigStatus } from '../api'
+ title: '声部名称',
+ key: 'subjectName'
+ title: '招生人数限制',
+ key: 'studentEnrollmentUpperLimit',
+ return row.studentEnrollmentUpperLimit ? row.studentEnrollmentUpperLimit : '无限制'
+ title: '开团乐器',
+ key: 'instrumentsName'
+ title: '乐器团购价',
+ key: 'instrumentsGroupPrice',
+ return numeral(row.instrumentsGroupPrice).format('0,0.00') + '元'
+ title: '乐器原价',
+ key: 'instrumentsOriginalPrice',
+ return numeral(row.instrumentsOriginalPrice).format('0,0.00') + '元'
+ title: '教材',
+ key: 'textbookName'
+ title: '教材团购价',
+ key: 'textbookGroupPrice',
+ return numeral(row.textbookGroupPrice).format('0,0.00') + '元'
+ title: '教材原价',
+ key: 'textbookOriginalPrice',
+ return numeral(row.textbookOriginalPrice).format('0,0.00') + '元'
+ return row.enableFlag ? (
+ <NTag type="primary">启用</NTag>
+ <NTag type="error">停用</NTag>
+ //v-auth="subjectBasicConfig/update1598501736003452929"
+ //v-auth="subjectBasicConfig/updateConfigStatus1668958388100288514"
+ {!row.enableFlag ? '启用' : '停用'}
+ const onOperation = async (row: any) => {
+ const content = !row.enableFlag
+ ? `您是否启用“${row.subjectName}”?`
+ : `您是否停用“${row.subjectName}”?`
+ content,
+ await subjectBasicConfigUpdateConfigStatus({
+ enableFlag: row.enableFlag ? false : true
+ message.success(`${!row.enableFlag ? '启用' : '停用'}成功`)
+ const { data } = await subjectBasicConfigPage({ ...state.pagination, ...state.searchForm })
+ <h2>声部参数</h2>
+ {/* <SaveForm
+ </SaveForm> */}
+ //v-auth="subjectBasicConfig/save1598501676989595649"
+ 添加声部参数
+ title={state.songOperation === 'add' ? '新增声部参数' : '修改声部参数'}
@@ -0,0 +1,389 @@
+import { NForm, NInput, NSpace, NButton, useMessage, NGrid, NFormItemGi, NSelect } from 'naive-ui'
+import { defineComponent, nextTick, onMounted, PropType, reactive, ref, shallowReactive } from 'vue'
+ productDetail,
+ productList,
+ subjectBasicConfigSave,
+ subjectBasicConfigUpdate,
+ subjectPage
+ subjectId: null,
+ studentEnrollmentUpperLimit: null,
+ instrumentsId: null,
+ instrumentsSkuId: null,
+ instrumentsOriginalPrice: null,
+ instrumentsGroupPrice: null,
+ textbookId: null,
+ textbookSkuId: null,
+ textbookOriginalPrice: null,
+ textbookGroupPrice: null
+ selectSubject: [],
+ selectProduct: [],
+ selectSkuProduct: [],
+ selectSkuTextbook: []
+ await subjectBasicConfigSave({ ...forms })
+ await subjectBasicConfigUpdate({
+ const { data } = await subjectPage({ page: 1, row: 999 })
+ state.selectSubject = data.rows || []
+ const getProductList = async () => {
+ const { data } = await productList({ pageNum: 1, pageSize: 999 })
+ const tempList = data.list || []
+ item.value = Number(item.id)
+ state.selectProduct = tempList
+ const getProductDetail = async (id: string | number, type = 'instrument') => {
+ const { data } = await productDetail({ id })
+ if (type == 'instrument') {
+ state.selectSkuProduct = skuStockList(data)
+ state.selectSkuTextbook = skuStockList(data)
+ const skuStockList = (data: any) => {
+ // 处理规格
+ const skuStockList = data.skuStockList || []
+ skuStockList.forEach((item: any) => {
+ if (item.spData) {
+ const spData = JSON.parse(item.spData)
+ item.label = spData.reduce((spDataJson: any, value: any) => {
+ spDataJson += value.value
+ return spDataJson
+ }, '')
+ item.label = '默认'
+ return skuStockList
+ getProductList()
+ getProductDetail(data.instrumentsId)
+ getProductDetail(data.textbookId, 'textbook')
+ forms.subjectId = data.subjectId
+ forms.studentEnrollmentUpperLimit = data.studentEnrollmentUpperLimit
+ forms.instrumentsId = data.instrumentsId
+ forms.instrumentsSkuId = data.instrumentsSkuId
+ forms.instrumentsOriginalPrice = data.instrumentsOriginalPrice
+ forms.instrumentsGroupPrice = data.instrumentsGroupPrice
+ forms.textbookId = data.textbookId
+ forms.textbookSkuId = data.textbookSkuId
+ forms.textbookOriginalPrice = data.textbookOriginalPrice
+ forms.textbookGroupPrice = data.textbookGroupPrice
+ <NGrid cols={2} xGap="12">
+ label="声部"
+ path="subjectId"
+ message: '请选择声部'
+ v-model:value={forms.subjectId}
+ options={state.selectSubject}
+ label="招生人数限制"
+ path="studentEnrollmentUpperLimit"
+ // rule={[
+ // required: true,
+ // message: '请输入招生人数限制'
+ // ]}
+ v-model:value={forms.studentEnrollmentUpperLimit}
+ placeholder="请输入招生人数限制"
+ maxlength={8}
+ label="开团乐器"
+ path="instrumentsId"
+ message: '请选择开团乐器'
+ v-model:value={forms.instrumentsId}
+ options={state.selectProduct}
+ placeholder="请选择开团乐器"
+ onUpdate:value={(value: any, options: any) => {
+ forms.instrumentsSkuId = null
+ forms.instrumentsGroupPrice = null
+ // forms.instrumentsOriginalPrice = null
+ // console.log(value, options)
+ forms.instrumentsOriginalPrice = options.originalPrice || 0
+ getProductDetail(value)
+ label="乐器规格"
+ path="instrumentsSkuId"
+ message: '请选择乐器规格'
+ v-model:value={forms.instrumentsSkuId}
+ options={state.selectSkuProduct}
+ placeholder="请选择乐器规格"
+ // forms.instrumentsOriginalPrice = options.originalPrice || 0
+ label="乐器原价"
+ path="instrumentsOriginalPrice"
+ message: '请输入乐器原价'
+ v-model:value={forms.instrumentsOriginalPrice}
+ placeholder="请输入乐器原价"
+ label="乐器开团价"
+ path="instrumentsGroupPrice"
+ message: '请选择输入开团价'
+ pattern: /^(?:0.\d{0,3}|[0-9][0-9]{0,12}|[0-9]{1,10}.\d{0,3})$/,
+ message: '输入乐器开团价有误',
+ trigger: ['blur']
+ v-model:value={forms.instrumentsGroupPrice}
+ placeholder="请输入乐器开团价"
+ label="教材"
+ path="textbookId"
+ message: '请选择教材'
+ v-model:value={forms.textbookId}
+ placeholder="请选择教材"
+ forms.textbookSkuId = null
+ forms.textbookGroupPrice = null
+ // forms.textbookOriginalPrice = null
+ forms.textbookOriginalPrice = options.originalPrice || 0
+ getProductDetail(value, 'textbook')
+ label="教材规格"
+ path="textbookSkuId"
+ message: '请选择教材规格'
+ v-model:value={forms.textbookSkuId}
+ options={state.selectSkuTextbook}
+ placeholder="请选择教材规格"
+ // forms.textbookOriginalPrice = options.originalPrice || 0
+ label="教材原价"
+ path="textbookOriginalPrice"
+ message: '请输入教材原价'
+ v-model:value={forms.textbookOriginalPrice}
+ placeholder="请输入教材原价"
+ label="教材团购价"
+ path="textbookGroupPrice"
+ message: '请输入教材团购价'
+ v-model:value={forms.textbookGroupPrice}
+ placeholder="请输入教材团购价"
@@ -0,0 +1,169 @@
+import { sysPositionPage, sysPositionRemove } from '../api'
+import { filterPosition } from '@/utils/filters'
+import StationOperation from './station-operation'
+ title: '岗位名称',
+ title: '岗位类型',
+ key: 'jobType',
+ return filterPosition(row.jobType)
+ //v-auth="sysPosition/update1597891012130623489"
+ //v-auth="sysPosition/remove1597891111267192834"
+ await sysPositionRemove({ id: row.id })
+ const { data } = await sysPositionPage({ ...state.pagination })
+ {/* <h2>岗位管理</h2> */}
+ //v-auth="sysPosition/save1597890897835839489"
+ 添加岗位
+ saveKey="StationManage"
+ title={state.roleOperation === 'add' ? '新增岗位' : '修改岗位'}
+ <StationOperation
@@ -0,0 +1,113 @@
+import { positionArray } from '@/utils/searchArray'
+import { NForm, NFormItem, NInput, NSelect, NSpace, NButton, useMessage } from 'naive-ui'
+import { sysPositionSave, sysPositionUpdate } from '../api'
+ jobType: null
+ const applyList = ref<any[]>([])
+ await sysPositionSave({
+ await sysPositionUpdate({ ...forms, id: props.data.id })
+ forms.jobType = data.jobType
+ label="岗位名称"
+ message: '请输入岗位名称'
+ placeholder="请输入岗位名称"
+ label="岗位类型"
+ path="jobType"
+ message: '岗位类型'
+ v-model:value={forms.jobType}
+ placeholder="请选择岗位类型"
+ options={positionArray}
+ options-line
@@ -0,0 +1,263 @@
+import { sysApplicationPage, sysApplicationSave, sysApplicationUpdate } from '../api'
+ parentId: {
+ type: Number,
+ default: 0
+ applyType: props.parentId == 0 ? 0 : 1, // 顶级分类,子级分类
+ parentId: props.parentId,
+ appName: null,
+ clientIds: null,
+ permissionFlag: true,
+ console.log(option)
+ parentId: option.key
+ ;(data.rows || []).forEach((item: any) => {
+ tempList.push({
+ key: item.id,
+ label: item.appName,
+ isLeaf: item.number > 0 ? false : true
+ option.children = [...tempList]
+ return tempList
+ await sysApplicationSave({
+ appName: forms.appName,
+ clientIds: forms.clientIds,
+ parentId: forms.parentId,
+ permissionFlag: forms.permissionFlag,
+ remark: forms.remark
+ await sysApplicationUpdate({ ...forms, id: props.data.id })
+ applyList.value = await getApplyList()
+ console.log(props.parentId)
+ const tempList = await getApplyList(props.parentId)
+ applyList.value.forEach((apply: any) => {
+ if (apply.key == props.parentId) {
+ apply.children = [...tempList]
+ forms.appName = data.appName
+ forms.clientIds = data.clientIds
+ forms.permissionFlag = data.permissionFlag
+ {props.type === 'add' && (
+ label="应用类型"
+ path="applyType"
+ message: '请选择应用类型'
+ <NRadioGroup v-model:value={forms.applyType}>
+ <NRadio value={0}>顶级应用</NRadio>
+ <NRadio value={1}>子级应用</NRadio>
+ {forms.applyType > 0 && props.type === 'add' && (
+ options={applyList.value}
+ // checkStrategy={'child'}
+ onLoad={handleLoad}
+ ></NTreeSelect>
+ label="应用名称"
+ path="appName"
+ message: '请输入应用名称'
+ v-model:value={forms.appName}
+ placeholder="请输入应用名称"
+ label="权限客户端"
+ path="clientIds"
+ message: '请输入权限客户端'
+ v-model:value={forms.clientIds}
+ placeholder="请输入权限客户端"
+ label="权限认证"
+ path="permissionFlag"
+ message: '请选择权限认证'
+ <NRadioGroup v-model:value={forms.permissionFlag}>
+ label="备注"
+ path="remark"
+ message: '备注'