Browse Source

Merge branch 'master' of http://gitlab.frkj.cc/php/zhjwxt

master
李双庆 10 months ago
parent
commit
17632268db
  1. 10
      admin/src/app/api/school_approval/process.ts
  2. 13
      admin/src/app/api/service_logs.js
  3. 13
      admin/src/app/api/sys.ts
  4. 141
      admin/src/app/views/customer_resources/components/UserProfile.vue
  5. 15
      admin/src/app/views/customer_resources/customer_resources.vue
  6. 467
      admin/src/app/views/school_approval/config/index.vue
  7. 182
      admin/src/app/views/school_approval/process/index.vue
  8. 165
      admin/src/app/views/service_logs/service_logs.vue
  9. 76
      admin/src/app/views/student_course_usage/student_course_usage.vue
  10. 37
      admin/src/utils/request.ts
  11. 14
      niucloud/app/adminapi/controller/person_course_schedule/PersonCourseSchedule.php
  12. 20
      niucloud/app/adminapi/controller/school_approval/Process.php
  13. 39
      niucloud/app/adminapi/controller/service_logs/ServiceLogs.php
  14. 6
      niucloud/app/adminapi/controller/student_course_usage/StudentCourseUsage.php
  15. 15
      niucloud/app/adminapi/controller/sys/System.php
  16. 31
      niucloud/app/adminapi/route/service_logs.php
  17. 5
      niucloud/app/adminapi/route/sys.php
  18. 76
      niucloud/app/model/person_course_schedule/PersonCourseSchedule.php
  19. 44
      niucloud/app/model/service_logs/ServiceLogs.php
  20. 12
      niucloud/app/service/admin/auth/AuthService.php
  21. 4
      niucloud/app/service/admin/customer_resources/CustomerResourcesService.php
  22. 60
      niucloud/app/service/admin/service_logs/ServiceLogsService.php
  23. 26
      niucloud/app/service/admin/student_course_usage/StudentCourseUsageService.php
  24. 18
      niucloud/app/service/admin/sys/SystemService.php
  25. 3
      niucloud/app/service/school_approval/SchoolApprovalConfigService.php
  26. 26
      niucloud/app/service/school_approval/SchoolApprovalProcessService.php

10
admin/src/app/api/school_approval/process.ts

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @param params * @param params
*/ */
export function getProcessList(params?: Record<string, any>) { export function getProcessList(params?: Record<string, any>) {
return request.get({ url: '/school_approval/process/lists', params }) return request.get('/school_approval/process/lists', { params })
} }
/** /**
@ -13,7 +13,7 @@ export function getProcessList(params?: Record<string, any>) {
* @param params * @param params
*/ */
export function getProcessInfo(params: { id: number }) { export function getProcessInfo(params: { id: number }) {
return request.get({ url: '/school_approval/process/info', params }) return request.get('/school_approval/process/info', { params })
} }
/** /**
@ -21,7 +21,7 @@ export function getProcessInfo(params: { id: number }) {
* @param params * @param params
*/ */
export function createProcess(params: any) { export function createProcess(params: any) {
return request.post({ url: '/school_approval/process/create', data: params }) return request.post('/school_approval/process/create', params)
} }
/** /**
@ -29,7 +29,7 @@ export function createProcess(params: any) {
* @param params * @param params
*/ */
export function approveProcess(params: { process_id: number; status: string; remarks?: string }) { export function approveProcess(params: { process_id: number; status: string; remarks?: string }) {
return request.post({ url: '/school_approval/process/approve', data: params }) return request.post('/school_approval/process/approve', params)
} }
/** /**
@ -37,5 +37,5 @@ export function approveProcess(params: { process_id: number; status: string; rem
* @param params * @param params
*/ */
export function cancelProcess(params: { process_id: number }) { export function cancelProcess(params: { process_id: number }) {
return request.post({ url: '/school_approval/process/cancel', data: params }) return request.post('/school_approval/process/cancel', params)
} }

13
admin/src/app/api/service_logs.js

@ -0,0 +1,13 @@
import request from '@/utils/request'
// USER_CODE_BEGIN -- service_logs
/**
*
* @param params
* @returns
*/
export function getServiceLogsList(params) {
return request.get(`service_logs/service_logs`, {params})
}

13
admin/src/app/api/sys.ts

@ -796,3 +796,16 @@ export function setDocument(params: Record<string, any>) {
export function getScsjtj(params: Record<string, any>) { export function getScsjtj(params: Record<string, any>) {
return request.post('sys/scsjtj', params) return request.post('sys/scsjtj', params)
} }
export function person_all() {
return request.get('sys/person_all')
}
export function role_all() {
return request.get('sys/role_all')
}
export function departments_all() {
return request.get('sys/departments_all')
}

141
admin/src/app/views/customer_resources/components/UserProfile.vue

@ -0,0 +1,141 @@
<template>
<el-dialog
v-model="showDialog"
title="客户详情"
width="80%"
class="user-profile p-6"
:destroy-on-close="true"
>
<!-- 顶部用户信息 -->
<el-card class="mb-4">
<div class="flex items-center">
<el-avatar :size="60" class="mr-4" />
<div>
<h2 class="text-xl font-bold">{{ user.name }}</h2>
<p>ID: {{ user.id }}</p>
<p>电话: {{ user.phone }}</p>
</div>
</div>
</el-card>
<!-- 标签导航 -->
<el-tabs v-model="activeTab" class="mb-4">
<el-tab-pane label="六要素" name="six-elements" />
<el-tab-pane label="修改日志" name="log" />
<el-tab-pane label="学生情况" name="student" />
<el-tab-pane label="订单列表" name="orders" />
<el-tab-pane label="课程列表" name="courses" />
</el-tabs>
<!-- 六要素信息卡片 -->
<el-card v-if="activeTab === 'six-elements'">
<h3 class="text-lg font-bold mb-4">六要素信息</h3>
<el-row :gutter="20" class="mb-2">
<el-col :span="8">需求购买力<el-tag>{{ info.purchasePower }}</el-tag></el-col>
<el-col :span="8">认知理念<el-tag>{{ info.knowledge }}</el-tag></el-col>
<el-col :span="8">距离<span>{{ info.distance }}</span></el-col>
</el-row>
<el-row :gutter="20" class="mb-2">
<el-col :span="8">可谈上课时间<span>{{ info.canTalkDate }}</span></el-col>
<el-col :span="8">承诺到访时间<span>{{ info.promisedVisitDate }}</span></el-col>
<el-col :span="8">实际到访时间<span>{{ info.actualVisitDate }}</span></el-col>
</el-row>
<el-row :gutter="20" class="mb-2">
<el-col :span="8">电话后的意向程度<el-tag>{{ info.phoneIntention }}</el-tag></el-col>
<el-col :span="8">是否关单
<el-tag :type="info.isClosed ? 'success' : 'danger'">{{ info.isClosed ? '是' : '否' }}</el-tag>
</el-col>
</el-row>
<!-- 备注与访问情况 -->
<el-form label-position="top" class="mt-4">
<el-form-item label="沟通备注">
<el-input v-model="info.notes" placeholder="填写备注" disabled/>
</el-form-item>
<el-form-item label="一访情况">
<el-input v-model="info.visit1" placeholder="填写一访情况" disabled/>
</el-form-item>
<el-form-item label="二访情况">
<el-input v-model="info.visit2" placeholder="填写二访情况" disabled/>
</el-form-item>
</el-form>
</el-card>
</el-dialog>
</template>
<script setup>
import { useDictionary } from '@/app/api/dict'
import { ref, reactive, computed, watch } from 'vue'
let showDialog = ref(false)
const activeTab = ref('six-elements')
const user = reactive({
name: '王五1',
id: 'U001',
phone: '13914556707'
})
const info = reactive({
purchasePower: '',
knowledge: '',
distance: '',
canTalkDate: '',
promisedVisitDate: '',
actualVisitDate: '',
phoneIntention: '',
isClosed: '',
notes: '',
visit1: '',
visit2: ''
})
let call_intentList = ref([])
let purchase_powerList = ref([])
let concept_awarenessList = ref([])
const setFormData = async (row) => {
call_intentList.value = await (
await useDictionary('preliminarycustomerintention')
).data.dictionary
purchase_powerList.value = await (
await useDictionary('customer_purchasing_power')
).data.dictionary
concept_awarenessList.value = await (
await useDictionary('cognitive_concept')
).data.dictionary
user.name = row.name;
user.id = row.id;
user.phone = row.phone_number;
info.purchasePower = purchase_powerList.value.flat().find(item => item.value == row.six.purchase_power)?.name || '';
info.knowledge = concept_awarenessList.value.flat().find(item => item.value == row.six.concept_awareness)?.name || '';
info.distance = row.six.distance;
info.canTalkDate = row.six.preferred_class_time;
info.promisedVisitDate = row.six.promised_visit_time;
info.actualVisitDate = row.six.actual_visit_time;
info.phoneIntention = call_intentList.value.flat().find(item => item.value == row.six.call_intent)?.name || '';
info.isClosed = row.six.is_closed == 1 ? true : false;
info.notes = row.six.communication;
info.visit1 = row.six.first_visit_status;
info.visit2 = row.six.second_visit_status;
console.log(call_intentList.value);
}
defineExpose({
showDialog,
setFormData
})
</script>
<style scoped>
.user-profile {
margin: 0 auto;
}
</style>

15
admin/src/app/views/customer_resources/customer_resources.vue

@ -127,6 +127,9 @@
<el-dropdown-item command="resourceChanges"> <el-dropdown-item command="resourceChanges">
客户信息修改记录 客户信息修改记录
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item command="UserProfile">
客户详情
</el-dropdown-item>
<el-dropdown-item command="editEvent"> <el-dropdown-item command="editEvent">
{{ t('edit') }} {{ t('edit') }}
</el-dropdown-item> </el-dropdown-item>
@ -180,6 +183,7 @@
<tc ref="TcCustomerResourcesDialog" @complete="loadCustomerResourcesList" /> <tc ref="TcCustomerResourcesDialog" @complete="loadCustomerResourcesList" />
<user ref="UserProfileDialog" @complete="loadCustomerResourcesList" />
</el-card> </el-card>
</div> </div>
</template> </template>
@ -195,6 +199,8 @@
import Fp from '@/app/views/customer_resources/components/fp.vue' import Fp from '@/app/views/customer_resources/components/fp.vue'
import Order from '@/app/views/order_table/components/order-table-edit.vue' import Order from '@/app/views/order_table/components/order-table-edit.vue'
import Tc from '@/app/views/tc_dialog/tc_dialog.vue' import Tc from '@/app/views/tc_dialog/tc_dialog.vue'
import User from '@/app/views/customer_resources/components/UserProfile.vue'
import { ArrowDown } from '@element-plus/icons-vue' import { ArrowDown } from '@element-plus/icons-vue'
import { getMemberLabelAll } from '@/app/api/member' import { getMemberLabelAll } from '@/app/api/member'
@ -244,6 +250,9 @@
case 'deleteEvent': case 'deleteEvent':
deleteEvent(row.id) deleteEvent(row.id)
break break
case 'UserProfile':
UserProfile(row)
break
} }
} }
@ -278,6 +287,7 @@
} }
const TcCustomerResourcesDialog : Record<string, any> | null = ref(null) const TcCustomerResourcesDialog : Record<string, any> | null = ref(null)
const tcEvent = (row : any) => { const tcEvent = (row : any) => {
TcCustomerResourcesDialog.value.setFormData(row) TcCustomerResourcesDialog.value.setFormData(row)
@ -290,7 +300,12 @@
} }
const UserProfileDialog : Record<string, any> | null = ref(null)
const UserProfile = (row : any) => {
UserProfileDialog.value.setFormData(row)
UserProfileDialog.value.showDialog = true
}
const searchFormRef = ref<FormInstance>() const searchFormRef = ref<FormInstance>()

467
admin/src/app/views/school_approval/config/index.vue

@ -9,21 +9,10 @@
</el-button> </el-button>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<el-input <el-input v-model="state.searchParams.config_name" class="w-200px mr-15px" placeholder="请输入配置名称"
v-model="state.searchParams.config_name" clearable @keyup.enter="handleSearch" @clear="handleSearch" />
class="w-200px mr-15px" <el-select v-model="state.searchParams.status" class="w-150px mr-15px" placeholder="请选择状态" clearable
placeholder="请输入配置名称" @change="handleSearch">
clearable
@keyup.enter="handleSearch"
@clear="handleSearch"
/>
<el-select
v-model="state.searchParams.status"
class="w-150px mr-15px"
placeholder="请选择状态"
clearable
@change="handleSearch"
>
<el-option label="启用" :value="1" /> <el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" /> <el-option label="禁用" :value="0" />
</el-select> </el-select>
@ -38,23 +27,15 @@
</div> </div>
</div> </div>
<el-table <el-table v-loading="state.loading" class="mt-15px" :data="state.configList"
v-loading="state.loading" :header-cell-style="{ background: '#fafafa', color: '#606266' }">
class="mt-15px"
:data="state.configList"
:header-cell-style="{ background: '#fafafa', color: '#606266' }"
>
<el-table-column label="ID" prop="id" width="80" /> <el-table-column label="ID" prop="id" width="80" />
<el-table-column label="配置名称" prop="config_name" min-width="180" /> <el-table-column label="配置名称" prop="config_name" min-width="180" />
<el-table-column label="配置描述" prop="description" min-width="200" show-overflow-tooltip /> <el-table-column label="配置描述" prop="description" min-width="200" show-overflow-tooltip />
<el-table-column label="状态" prop="status" width="100"> <el-table-column label="状态" prop="status" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-switch <el-switch v-model="row.status" :active-value="1" :inactive-value="0"
v-model="row.status" @change="handleStatusChange(row)" />
:active-value="1"
:inactive-value="0"
@change="handleStatusChange(row)"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建时间" prop="created_at" width="180" /> <el-table-column label="创建时间" prop="created_at" width="180" />
@ -74,132 +55,100 @@
</el-table> </el-table>
<div class="flex justify-end mt-15px"> <div class="flex justify-end mt-15px">
<el-pagination <el-pagination v-model:current-page="state.searchParams.page"
v-model:current-page="state.searchParams.page" v-model:page-size="state.searchParams.limit" :page-sizes="[10, 20, 50, 100]" :total="state.total"
v-model:page-size="state.searchParams.limit" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
:page-sizes="[10, 20, 50, 100]" @current-change="handleCurrentChange" />
:total="state.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div> </div>
</el-card> </el-card>
<!-- 新增/编辑弹窗 --> <!-- 新增/编辑弹窗 -->
<el-dialog <el-dialog v-model="state.dialog.visible" :title="state.dialog.title" width="800px"
v-model="state.dialog.visible" :close-on-click-modal="false" :destroy-on-close="true">
:title="state.dialog.title" <el-form ref="formRef" :model="state.dialog.form" :rules="state.dialog.rules" label-width="120px">
width="800px"
:close-on-click-modal="false"
:destroy-on-close="true"
>
<el-form
ref="formRef"
:model="state.dialog.form"
:rules="state.dialog.rules"
label-width="120px"
>
<el-form-item label="配置名称" prop="config_name"> <el-form-item label="配置名称" prop="config_name">
<el-input v-model="state.dialog.form.config_name" placeholder="请输入配置名称" /> <el-input v-model="state.dialog.form.config_name" placeholder="请输入配置名称" />
</el-form-item> </el-form-item>
<el-form-item label="配置描述" prop="description"> <el-form-item label="配置描述" prop="description">
<el-input <el-input v-model="state.dialog.form.description" type="textarea" :rows="3" placeholder="请输入配置描述" />
v-model="state.dialog.form.description"
type="textarea"
:rows="3"
placeholder="请输入配置描述"
/>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-switch <el-switch v-model="state.dialog.form.status" :active-value="1" :inactive-value="0" />
v-model="state.dialog.form.status"
:active-value="1"
:inactive-value="0"
/>
</el-form-item> </el-form-item>
<el-form-item label="审批节点" prop="nodes"> <el-form-item label="审批节点" prop="nodes">
<div class="mb-10px"> <!-- 添加节点按钮 -->
<el-button type="primary" @click="handleAddNode"> <div class="mb-4">
<icon name="add" class="mr-5px" /> <el-button type="primary" @click="handleAddNode" size="small">
<icon name="add" class="mr-1" />
添加节点 添加节点
</el-button> </el-button>
</div> </div>
<div v-if="state.dialog.form.nodes.length === 0" class="text-gray-400 text-center py-20px border border-dashed rounded">
<!-- 无节点提示 -->
<div v-if="state.dialog.form.nodes.length === 0"
class="text-gray-400 text-center py-6 border border-dashed rounded bg-gray-50">
暂无审批节点请点击添加 暂无审批节点请点击添加
</div> </div>
<div v-else class="node-list">
<div <!-- 节点列表 -->
v-for="(element, index) in state.dialog.form.nodes" <div v-else class="space-y-4">
:key="index" <div v-for="(element, index) in state.dialog.form.nodes" :key="index"
class="node-item p-15px mb-15px border border-gray-200 rounded" class="p-4 border border-gray-300 rounded-md bg-white shadow-sm">
> <!-- 节点头部 -->
<div class="flex justify-between items-center mb-10px"> <div class="flex justify-between items-center mb-4">
<div class="flex items-center"> <div class="flex items-center text-gray-800 font-medium">
<icon name="rank" class="drag-handle mr-5px cursor-move" /> <icon name="rank" class="drag-handle mr-2 text-gray-500 cursor-move" />
<span class="font-bold">节点 {{ index + 1 }}</span> 节点 {{ index + 1 }}
</div> </div>
<el-button type="danger" link @click="handleRemoveNode(index)"> <el-button type="danger" link @click="handleRemoveNode(index)">
删除 删除
</el-button> </el-button>
</div> </div>
<div class="flex items-center mb-10px">
<span class="w-100px">节点名称</span> <!-- 节点名称 -->
<el-input <div class="flex items-center mb-3">
v-model="element.node_name" <span class="w-28 text-gray-600">节点名称</span>
placeholder="请输入节点名称" <el-input v-model="element.node_name" placeholder="请输入节点名称" size="small" style="width: 73%;"/>
/>
</div> </div>
<div class="flex items-center mb-10px">
<span class="w-100px">审批人类型</span> <!-- 审批人类型 -->
<el-select <div class="flex items-center mb-3">
v-model="element.approver_type" <span class="w-28 text-gray-600">审批人类型</span>
class="flex-1" <el-select v-model="element.approver_type" class="flex-1" placeholder="请选择审批人类型"
placeholder="请选择审批人类型" size="small">
>
<el-option label="指定用户" value="user" /> <el-option label="指定用户" value="user" />
<el-option label="指定角色" value="role" /> <el-option label="指定角色" value="role" />
<el-option label="指定部门" value="department" /> <el-option label="指定部门" value="department" />
</el-select> </el-select>
</div> </div>
<div class="flex items-center mb-10px">
<span class="w-100px">审批人</span> <!-- 审批 -->
<el-select <div class="flex items-center mb-3">
v-model="element.approver_ids" <span class="w-28 text-gray-600">审批</span>
class="flex-1" <el-select v-model="element.approver_ids" class="flex-1" multiple placeholder="请选择审批"
multiple size="small">
placeholder="请选择审批人" <!-- 用户 -->
>
<!-- 这里根据 approver_type 不同显示不同的选项 -->
<template v-if="element.approver_type === 'user'"> <template v-if="element.approver_type === 'user'">
<el-option <el-option v-for="item in state.userOptions" :key="item.id" :label="item.name"
v-for="item in state.userOptions" :value="String(item.id)" />
:key="item.value"
:label="item.label"
:value="item.value"
/>
</template> </template>
<!-- 角色 -->
<template v-else-if="element.approver_type === 'role'"> <template v-else-if="element.approver_type === 'role'">
<el-option <el-option v-for="item in state.roleOptions" :key="item.role_id"
v-for="item in state.roleOptions" :label="item.role_name" :value="String(item.role_id)" />
:key="item.value"
:label="item.label"
:value="item.value"
/>
</template> </template>
<!-- 部门 -->
<template v-else-if="element.approver_type === 'department'"> <template v-else-if="element.approver_type === 'department'">
<el-option <el-option v-for="item in state.departmentOptions" :key="item.id"
v-for="item in state.departmentOptions" :label="item.department_name" :value="String(item.id)" />
:key="item.value"
:label="item.label"
:value="item.value"
/>
</template> </template>
</el-select> </el-select>
</div> </div>
<!-- 审批类型 -->
<div class="flex items-center"> <div class="flex items-center">
<span class="w-100px">审批类型</span> <span class="w-28 text-gray-600">审批类型</span>
<el-radio-group v-model="element.sign_type"> <el-radio-group v-model="element.sign_type" size="small">
<el-radio label="or_sign">或签一人通过即可</el-radio> <el-radio label="or_sign">或签一人通过即可</el-radio>
<el-radio label="and_sign">会签需全部通过</el-radio> <el-radio label="and_sign">会签需全部通过</el-radio>
</el-radio-group> </el-radio-group>
@ -207,6 +156,7 @@
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="state.dialog.visible = false">取消</el-button> <el-button @click="state.dialog.visible = false">取消</el-button>
@ -217,12 +167,7 @@
</el-dialog> </el-dialog>
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<el-dialog <el-dialog v-model="state.detailDialog.visible" title="审批流配置详情" width="800px" :destroy-on-close="true">
v-model="state.detailDialog.visible"
title="审批流配置详情"
width="800px"
:destroy-on-close="true"
>
<el-descriptions :column="1" border> <el-descriptions :column="1" border>
<el-descriptions-item label="ID"> <el-descriptions-item label="ID">
{{ state.detailDialog.info?.id || '-' }} {{ state.detailDialog.info?.id || '-' }}
@ -246,19 +191,17 @@
<div class="mt-20px"> <div class="mt-20px">
<div class="font-bold text-16px mb-10px">审批节点列表</div> <div class="font-bold text-16px mb-10px">审批节点列表</div>
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item v-for="(node, index) in state.detailDialog.info?.nodes || []" :key="index"
v-for="(node, index) in state.detailDialog.info?.nodes || []" :type="getNodeType(node.sign_type)" :color="getNodeColor(node.sign_type)">
:key="index"
:type="getNodeType(node.sign_type)"
:color="getNodeColor(node.sign_type)"
>
<div class="font-bold mb-5px">{{ node.node_name }}</div> <div class="font-bold mb-5px">{{ node.node_name }}</div>
<div class="text-gray-500"> <div class="text-gray-500">
审批人类型{{ getApproverTypeText(node.approver_type) }} 审批人类型{{ getApproverTypeText(node.approver_type) }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
审批人{{ node.approver_ids }} 审批人{{ getApproverNames(node.approver_ids,node.approver_type)}}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
审批类型{{ node.sign_type === 'or_sign' ? '或签' : '会签' }} 审批类型{{ node.sign_type === 'or_sign' ? '或签' : '会签' }}
</div> </div>
@ -270,45 +213,51 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue' import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus' import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { getConfigList, getConfigInfo, addConfig, editConfig, deleteConfig, changeConfigStatus } from '@/app/api/school_approval/config' import { getConfigList, getConfigInfo, addConfig, editConfig, deleteConfig, changeConfigStatus } from '@/app/api/school_approval/config'
// draggable使 import {
// import draggable from 'vuedraggable' person_all,
// i18n role_all,
// import { useI18n } from 'vue-i18n' departments_all
// const { t } = useI18n() } from '@/app/api/sys'
// // draggable使
const formRef = ref<FormInstance>() // import draggable from 'vuedraggable'
// i18n
// // import { useI18n } from 'vue-i18n'
interface DetailInfo { // const { t } = useI18n()
id?: number;
config_name?: string; //
description?: string; const formRef = ref<FormInstance>()
status?: number;
created_at?: string; //
nodes?: Array<{ interface DetailInfo {
node_name: string; id ?: number;
approver_type: string; config_name ?: string;
approver_ids: string; description ?: string;
sign_type: string; status ?: number;
created_at ?: string;
nodes ?: Array<{
node_name : string;
approver_type : string;
approver_ids : string;
sign_type : string;
}>; }>;
} }
// //
interface ApprovalNode { interface ApprovalNode {
key: string; key : string;
node_name: string; node_name : string;
approver_type: string; approver_type : string;
approver_ids: string[]; approver_ids : string[];
sign_type: string; sign_type : string;
} }
// //
const state = reactive({ const state = reactive({
loading: false, loading: false,
configList: [], configList: [],
total: 0, total: 0,
@ -319,19 +268,9 @@ const state = reactive({
status: '' status: ''
}, },
// API // API
userOptions: [ userOptions: [],
{ label: '用户1', value: '1' }, roleOptions: [],
{ label: '用户2', value: '2' }, departmentOptions: [],
{ label: '用户3', value: '3' }
],
roleOptions: [
{ label: '角色1', value: '1' },
{ label: '角色2', value: '2' }
],
departmentOptions: [
{ label: '部门1', value: '1' },
{ label: '部门2', value: '2' }
],
dialog: { dialog: {
visible: false, visible: false,
title: '', title: '',
@ -357,10 +296,48 @@ const state = reactive({
visible: false, visible: false,
info: {} as DetailInfo info: {} as DetailInfo
} }
}) })
const setPersonIdList = async () => {
state.userOptions = await (await person_all()).data
}
setPersonIdList()
const setRoleIdList = async () => {
state.roleOptions = await (await role_all()).data
}
setRoleIdList()
const setDepartmentsIdList = async () => {
state.departmentOptions = await (await departments_all()).data
}
setDepartmentsIdList()
const getApproverNames = (ids : string | string[], type : string) => {
if (!ids) return '';
const idArray = typeof ids === 'string' ? ids.split(',') : ids;
let options = [];
if (type === 'user') {
options = state.userOptions;
} else if (type === 'role') {
options = state.roleOptions;
} else if (type === 'department') {
options = state.departmentOptions;
}
console.log(options);
// return idArray
function validateNodes(rule: any, value: any, callback: any) { .map(id => {
const match = options.find(item => String(item.id || item.role_id) === String(id));
return match ? (match.name || match.role_name || match.department_name) : '';
})
.filter(name => name !== '')
.join(', ');
};
//
function validateNodes(rule : any, value : any, callback : any) {
if (!value || value.length === 0) { if (!value || value.length === 0) {
callback(new Error('请至少添加一个审批节点')) callback(new Error('请至少添加一个审批节点'))
} else { } else {
@ -380,10 +357,10 @@ function validateNodes(rule: any, value: any, callback: any) {
} }
callback() callback()
} }
} }
// //
async function getList() { async function getList() {
state.loading = true state.loading = true
try { try {
const res = await getConfigList(state.searchParams) const res = await getConfigList(state.searchParams)
@ -394,16 +371,16 @@ async function getList() {
} finally { } finally {
state.loading = false state.loading = false
} }
} }
// //
function handleSearch() { function handleSearch() {
state.searchParams.page = 1 state.searchParams.page = 1
getList() getList()
} }
// //
function handleReset() { function handleReset() {
state.searchParams = { state.searchParams = {
page: 1, page: 1,
limit: 10, limit: 10,
@ -411,23 +388,23 @@ function handleReset() {
status: '' status: ''
} }
getList() getList()
} }
// //
function handleCurrentChange(page: number) { function handleCurrentChange(page : number) {
state.searchParams.page = page state.searchParams.page = page
getList() getList()
} }
// //
function handleSizeChange(size: number) { function handleSizeChange(size : number) {
state.searchParams.limit = size state.searchParams.limit = size
state.searchParams.page = 1 state.searchParams.page = 1
getList() getList()
} }
// //
function handleAdd() { function handleAdd() {
state.dialog.type = 'add' state.dialog.type = 'add'
state.dialog.title = '添加审批流配置' state.dialog.title = '添加审批流配置'
state.dialog.form = { state.dialog.form = {
@ -438,10 +415,10 @@ function handleAdd() {
nodes: [] nodes: []
} }
state.dialog.visible = true state.dialog.visible = true
} }
// //
async function handleEdit(row: any) { async function handleEdit(row : any) {
state.dialog.type = 'edit' state.dialog.type = 'edit'
state.dialog.title = '编辑审批流配置' state.dialog.title = '编辑审批流配置'
state.dialog.loading = true state.dialog.loading = true
@ -450,7 +427,7 @@ async function handleEdit(row: any) {
state.dialog.form = { ...res.data } state.dialog.form = { ...res.data }
// key // key
if (state.dialog.form.nodes && Array.isArray(state.dialog.form.nodes)) { if (state.dialog.form.nodes && Array.isArray(state.dialog.form.nodes)) {
state.dialog.form.nodes = state.dialog.form.nodes.map((node: any, index: number) => { state.dialog.form.nodes = state.dialog.form.nodes.map((node : any, index : number) => {
// approver_idssplit // approver_idssplit
const approverIds = typeof node.approver_ids === 'string' const approverIds = typeof node.approver_ids === 'string'
? node.approver_ids.split(',') ? node.approver_ids.split(',')
@ -472,10 +449,10 @@ async function handleEdit(row: any) {
} finally { } finally {
state.dialog.loading = false state.dialog.loading = false
} }
} }
// //
async function handleDetail(row: any) { async function handleDetail(row : any) {
try { try {
const res = await getConfigInfo({ id: row.id }) const res = await getConfigInfo({ id: row.id })
state.detailDialog.info = res.data state.detailDialog.info = res.data
@ -483,10 +460,10 @@ async function handleDetail(row: any) {
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
} }
// //
function handleDelete(row: any) { function handleDelete(row : any) {
ElMessageBox.confirm('确定要删除此项吗?', '警告', { ElMessageBox.confirm('确定要删除此项吗?', '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -501,11 +478,11 @@ function handleDelete(row: any) {
console.error(error) console.error(error)
} }
}) })
.catch(() => {}) .catch(() => { })
} }
// //
async function handleStatusChange(row: any) { async function handleStatusChange(row : any) {
try { try {
await changeConfigStatus({ id: row.id, status: row.status }) await changeConfigStatus({ id: row.id, status: row.status })
ElMessage.success( ElMessage.success(
@ -517,10 +494,10 @@ async function handleStatusChange(row: any) {
console.error(error) console.error(error)
row.status = row.status ? 0 : 1 // row.status = row.status ? 0 : 1 //
} }
} }
// //
function handleAddNode() { function handleAddNode() {
state.dialog.form.nodes.push({ state.dialog.form.nodes.push({
key: `node_${state.dialog.form.nodes.length}_${Date.now()}`, key: `node_${state.dialog.form.nodes.length}_${Date.now()}`,
node_name: '', node_name: '',
@ -528,15 +505,15 @@ function handleAddNode() {
approver_ids: [], approver_ids: [],
sign_type: 'or_sign' sign_type: 'or_sign'
}) })
} }
// //
function handleRemoveNode(index: number) { function handleRemoveNode(index : number) {
state.dialog.form.nodes.splice(index, 1) state.dialog.form.nodes.splice(index, 1)
} }
// //
async function handleSubmit() { async function handleSubmit() {
if (!formRef.value) return if (!formRef.value) return
await formRef.value.validate(async (valid) => { await formRef.value.validate(async (valid) => {
@ -548,7 +525,7 @@ async function handleSubmit() {
const formData = { ...state.dialog.form } const formData = { ...state.dialog.form }
// //
formData.nodes = formData.nodes.map((node: any, index: number) => { formData.nodes = formData.nodes.map((node : any, index : number) => {
// keyAPI // keyAPI
const { key, ...nodeWithoutKey } = node; const { key, ...nodeWithoutKey } = node;
@ -585,54 +562,64 @@ async function handleSubmit() {
state.dialog.loading = false state.dialog.loading = false
} }
}) })
} }
// //
function getNodeType(signType: string) { function getNodeType(signType : string) {
return signType === 'or_sign' ? 'primary' : 'success' return signType === 'or_sign' ? 'primary' : 'success'
} }
// //
function getNodeColor(signType: string) { function getNodeColor(signType : string) {
return signType === 'or_sign' ? '#409EFF' : '#67C23A' return signType === 'or_sign' ? '#409EFF' : '#67C23A'
} }
// //
function getApproverTypeText(type: string) { function getApproverTypeText(type : string) {
const map: Record<string, string> = { const map : Record<string, string> = {
user: '指定用户', user: '指定用户',
role: '指定角色', role: '指定角色',
department: '指定部门' department: '指定部门'
} }
return map[type] || type return map[type] || type
} }
onMounted(() => { onMounted(() => {
getList() getList()
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.node-list {
margin-left: 10px;
}
.node-item { .node-item {
background-color: #f9f9f9; background-color: #f9f9f9;
transition: all 0.3s; transition: all 0.3s;
padding: 10px;
margin-top: 10px;
.node-item {
background-color: #f9f9f9;
transition: all 0.3s;
&:hover { &:hover {
background-color: #f2f2f2; background-color: #f2f2f2;
} }
} }
.ghost { .ghost {
opacity: 0.5; opacity: 0.5;
background: #c8ebfb; background: #c8ebfb;
} }
.drag-handle { .drag-handle {
cursor: move; cursor: move;
color: #909399; color: #909399;
&:hover { &:hover {
color: #409eff; color: #409eff;
} }
}
} }
</style> </style>

182
admin/src/app/views/school_approval/process/index.vue

@ -5,14 +5,14 @@
<div> <div>
<el-button type="primary" @click="handleCreate"> <el-button type="primary" @click="handleCreate">
<icon name="add" class="mr-5px" /> <icon name="add" class="mr-5px" />
{{ $t('approval.process.create') }} {{ '创建(测试暂留)' }}
</el-button> </el-button>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<el-input <el-input
v-model="state.searchParams.process_name" v-model="state.searchParams.process_name"
class="w-200px mr-15px" class="w-200 mr-15"
:placeholder="$t('approval.process.searchPlaceholder')" :placeholder="'搜索'"
clearable clearable
@keyup.enter="handleSearch" @keyup.enter="handleSearch"
@clear="handleSearch" @clear="handleSearch"
@ -20,29 +20,29 @@
<el-select <el-select
v-model="state.searchParams.approval_status" v-model="state.searchParams.approval_status"
class="w-150px mr-15px" class="w-150px mr-15px"
:placeholder="$t('approval.process.statusPlaceholder')" :placeholder="'状态'"
clearable clearable
@change="handleSearch" @change="handleSearch"
> >
<el-option :label="$t('approval.process.pending')" value="pending" /> <el-option :label="'待审批'" value="pending" />
<el-option :label="$t('approval.process.approved')" value="approved" /> <el-option :label="'已审批'" value="approved" />
<el-option :label="$t('approval.process.rejected')" value="rejected" /> <el-option :label="'已拒绝'" value="rejected" />
</el-select> </el-select>
<el-button type="primary" @click="handleSearch"> <el-button type="primary" @click="handleSearch">
<icon name="search" class="mr-5px" /> <icon name="search" class="mr-5px" />
{{ $t('common.search') }} {{ '搜索' }}
</el-button> </el-button>
<el-button @click="handleReset"> <el-button @click="handleReset">
<icon name="refresh-right" class="mr-5px" /> <icon name="refresh-right" class="mr-5px" />
{{ $t('common.reset') }} {{ '重置' }}
</el-button> </el-button>
</div> </div>
</div> </div>
<el-tabs v-model="state.activeTab" class="mt-15px" @tab-click="handleTabChange"> <el-tabs v-model="state.activeTab" class="mt-15px" @tab-click="handleTabChange">
<el-tab-pane :label="$t('approval.process.all')" name="all" /> <el-tab-pane :label="'全部'" name="all" />
<el-tab-pane :label="$t('approval.process.myCreate')" name="myCreate" /> <el-tab-pane :label="'我创建的'" name="myCreate" />
<el-tab-pane :label="$t('approval.process.myApproval')" name="myApproval" /> <el-tab-pane :label="'我审批的'" name="myApproval" />
</el-tabs> </el-tabs>
<el-table <el-table
@ -50,22 +50,22 @@
:data="state.processList" :data="state.processList"
:header-cell-style="{ background: '#fafafa', color: '#606266' }" :header-cell-style="{ background: '#fafafa', color: '#606266' }"
> >
<el-table-column :label="$t('approval.process.id')" prop="id" width="80" /> <el-table-column :label="'ID'" prop="id" width="80" />
<el-table-column :label="$t('approval.process.processName')" prop="process_name" min-width="180" /> <el-table-column :label="'流程名称'" prop="process_name" min-width="180" />
<el-table-column :label="$t('approval.process.applicantId')" prop="applicant_id" width="100" /> <el-table-column :label="'申请人'" prop="applicant_name" width="100" />
<el-table-column :label="$t('approval.process.applicationTime')" prop="application_time" width="180" /> <el-table-column :label="'申请时间'" prop="application_time" width="180" />
<el-table-column :label="$t('approval.process.currentApproverId')" prop="current_approver_id" width="100" /> <el-table-column :label="'当前审批人'" prop="current_approver_name" width="100" />
<el-table-column :label="$t('approval.process.approvalStatus')" prop="approval_status" width="100"> <el-table-column :label="'审批状态'" prop="approval_status" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="getStatusType(row.approval_status)"> <el-tag :type="getStatusType(row.approval_status)">
{{ getStatusText(row.approval_status) }} {{ getStatusText(row.approval_status) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('common.action')" width="180" fixed="right"> <el-table-column :label="'操作'" width="180" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="handleDetail(row)"> <el-button type="primary" link @click="handleDetail(row)">
{{ $t('common.detail') }} {{ '详情' }}
</el-button> </el-button>
<el-button <el-button
v-if="row.approval_status === 'pending' && row.applicant_id === state.userInfo.uid" v-if="row.approval_status === 'pending' && row.applicant_id === state.userInfo.uid"
@ -73,7 +73,7 @@
link link
@click="handleCancel(row)" @click="handleCancel(row)"
> >
{{ $t('approval.process.cancel') }} {{ '取消' }}
</el-button> </el-button>
<el-button <el-button
v-if="row.approval_status === 'pending' && row.current_approver_id === state.userInfo.uid" v-if="row.approval_status === 'pending' && row.current_approver_id === state.userInfo.uid"
@ -81,7 +81,7 @@
link link
@click="handleApprove(row)" @click="handleApprove(row)"
> >
{{ $t('approval.process.approve') }} {{ '审批' }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -103,7 +103,7 @@
<!-- 创建审批弹窗 --> <!-- 创建审批弹窗 -->
<el-dialog <el-dialog
v-model="state.createDialog.visible" v-model="state.createDialog.visible"
:title="$t('approval.process.create')" :title="'创建审批'"
width="600px" width="600px"
:close-on-click-modal="false" :close-on-click-modal="false"
:destroy-on-close="true" :destroy-on-close="true"
@ -114,17 +114,17 @@
:rules="state.createDialog.rules" :rules="state.createDialog.rules"
label-width="120px" label-width="120px"
> >
<el-form-item :label="$t('approval.process.processName')" prop="process_name"> <el-form-item :label="'流程名称'" prop="process_name">
<el-input <el-input
v-model="state.createDialog.form.process_name" v-model="state.createDialog.form.process_name"
:placeholder="$t('approval.process.processNamePlaceholder')" :placeholder="'请输入流程名称'"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="$t('approval.process.configId')" prop="config_id"> <el-form-item :label="'审批流配置'" prop="config_id">
<el-select <el-select
v-model="state.createDialog.form.config_id" v-model="state.createDialog.form.config_id"
class="w-full" class="w-full"
:placeholder="$t('approval.process.configIdPlaceholder')" :placeholder="'请选择审批流配置'"
> >
<el-option <el-option
v-for="item in state.configOptions" v-for="item in state.configOptions"
@ -134,19 +134,19 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('approval.process.remarks')" prop="remarks"> <el-form-item :label="'备注'" prop="remarks">
<el-input <el-input
v-model="state.createDialog.form.remarks" v-model="state.createDialog.form.remarks"
type="textarea" type="textarea"
:rows="3" :rows="3"
:placeholder="$t('approval.process.remarksPlaceholder')" :placeholder="'请输入备注'"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="state.createDialog.visible = false">{{ $t('common.cancel') }}</el-button> <el-button @click="state.createDialog.visible = false">{{ '取消' }}</el-button>
<el-button type="primary" :loading="state.createDialog.loading" @click="handleCreateSubmit"> <el-button type="primary" :loading="state.createDialog.loading" @click="handleCreateSubmit">
{{ $t('common.confirm') }} {{ '确认' }}
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
@ -154,7 +154,7 @@
<!-- 审批弹窗 --> <!-- 审批弹窗 -->
<el-dialog <el-dialog
v-model="state.approveDialog.visible" v-model="state.approveDialog.visible"
:title="$t('approval.process.approve')" :title="'审批'"
width="500px" width="500px"
:close-on-click-modal="false" :close-on-click-modal="false"
:destroy-on-close="true" :destroy-on-close="true"
@ -165,25 +165,25 @@
:rules="state.approveDialog.rules" :rules="state.approveDialog.rules"
label-width="80px" label-width="80px"
> >
<el-form-item :label="$t('approval.process.status')" prop="status"> <el-form-item :label="'审批状态'" prop="status">
<el-radio-group v-model="state.approveDialog.form.status"> <el-radio-group v-model="state.approveDialog.form.status">
<el-radio label="approved">{{ $t('approval.process.approved') }}</el-radio> <el-radio label="approved">{{ '已审批' }}</el-radio>
<el-radio label="rejected">{{ $t('approval.process.rejected') }}</el-radio> <el-radio label="rejected">{{ '已拒绝' }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="$t('approval.process.remarks')" prop="remarks"> <el-form-item :label="'备注'" prop="remarks">
<el-input <el-input
v-model="state.approveDialog.form.remarks" v-model="state.approveDialog.form.remarks"
type="textarea" type="textarea"
:rows="3" :rows="3"
:placeholder="$t('approval.process.remarksPlaceholder')" :placeholder="'请输入备注'"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="state.approveDialog.visible = false">{{ $t('common.cancel') }}</el-button> <el-button @click="state.approveDialog.visible = false">{{ '取消' }}</el-button>
<el-button type="primary" :loading="state.approveDialog.loading" @click="handleApproveSubmit"> <el-button type="primary" :loading="state.approveDialog.loading" @click="handleApproveSubmit">
{{ $t('common.confirm') }} {{ '确认' }}
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
@ -191,41 +191,41 @@
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<el-dialog <el-dialog
v-model="state.detailDialog.visible" v-model="state.detailDialog.visible"
:title="$t('approval.process.detail')" :title="'审批详情'"
width="800px" width="800px"
:destroy-on-close="true" :destroy-on-close="true"
> >
<el-descriptions :column="1" border> <el-descriptions :column="1" border>
<el-descriptions-item :label="$t('approval.process.id')"> <el-descriptions-item :label="'ID'">
{{ state.detailDialog.info.id }} {{ state.detailDialog.info.id }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.processName')"> <el-descriptions-item :label="'流程名称'">
{{ state.detailDialog.info.process_name }} {{ state.detailDialog.info.process_name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.applicantId')"> <el-descriptions-item :label="'申请人'">
{{ state.detailDialog.info.applicant_id }} {{ state.detailDialog.info.applicant_name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.applicationTime')"> <el-descriptions-item :label="'申请时间'">
{{ state.detailDialog.info.application_time }} {{ state.detailDialog.info.application_time }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.currentApproverId')"> <el-descriptions-item :label="'当前审批人'">
{{ state.detailDialog.info.current_approver_id }} {{ state.detailDialog.info.current_approver_name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.approvalStatus')"> <el-descriptions-item :label="'审批状态'">
<el-tag :type="getStatusType(state.detailDialog.info.approval_status)"> <el-tag :type="getStatusType(state.detailDialog.info.approval_status)">
{{ getStatusText(state.detailDialog.info.approval_status) }} {{ getStatusText(state.detailDialog.info.approval_status) }}
</el-tag> </el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.approvalTime')"> <el-descriptions-item :label="'审批时间'">
{{ state.detailDialog.info.approval_time || '-' }} {{ state.detailDialog.info.approval_time || '-' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.remarks')"> <el-descriptions-item :label="'备注'">
{{ state.detailDialog.info.remarks || '-' }} {{ state.detailDialog.info.remarks || '-' }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<div class="mt-20px"> <div class="mt-20px">
<div class="font-bold text-16px mb-10px">{{ $t('approval.process.participants') }}</div> <div class="font-bold text-16px mb-10px">{{ '参与者' }}</div>
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item
v-for="(node, index) in state.detailDialog.info.participants" v-for="(node, index) in state.detailDialog.info.participants"
@ -234,22 +234,22 @@
:color="getNodeColor(node.status)" :color="getNodeColor(node.status)"
> >
<div class="font-bold mb-5px"> <div class="font-bold mb-5px">
{{ $t('approval.process.participantLabel', { index: index + 1 }) }} {{ '参与者' + (index + 1) }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.participantId') }}{{ node.participant_id }} {{ '参与者' }}{{ node.name }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.sequence') }}{{ node.sequence }} {{ '顺序' }}{{ node.sequence }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.status') }}{{ getStatusText(node.status) }} {{ '状态' }}{{ getStatusText(node.status) }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.signType') }}{{ node.sign_type === 'or_sign' ? $t('approval.process.orSign') : $t('approval.process.andSign') }} {{ '签名类型' }}{{ node.sign_type === 'or_sign' ? '或签名' : '和签名' }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.remarks') }}{{ node.remarks || '-' }} {{ '备注' }}{{ node.remarks || '-' }}
</div> </div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
@ -263,23 +263,39 @@ import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus' import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { getProcessList, getProcessInfo, createProcess, approveProcess, cancelProcess } from '@/app/api/school_approval/process' import { getProcessList, getProcessInfo, createProcess, approveProcess, cancelProcess } from '@/app/api/school_approval/process'
import { getConfigList } from '@/app/api/school_approval/config' import { getConfigList } from '@/app/api/school_approval/config'
import { useI18n } from 'vue-i18n' // import { useI18n } from 'vue-i18n'
// import { useUserInfo } from '@/stores/userInfo' // import { useUserInfo } from '@/stores/userInfo'
const { t } = useI18n() // const { t } = useI18n()
// const userInfo = useUserInfo() // const userInfo = useUserInfo()
// //
const createFormRef = ref<FormInstance>() const createFormRef = ref<FormInstance>()
const approveFormRef = ref<FormInstance>() const approveFormRef = ref<FormInstance>()
// script
interface ConfigOption {
label: string;
value: number;
}
interface Participant {
participant_id: number;
sequence: number;
status: string;
sign_type: string;
remarks?: string;
}
// //
const state = reactive({ const state = reactive({
loading: false, loading: false,
processList: [], processList: [],
total: 0, total: 0,
activeTab: 'all', activeTab: 'all',
userInfo: null, userInfo: {
uid: 1 // IDstore
},
searchParams: { searchParams: {
page: 1, page: 1,
limit: 10, limit: 10,
@ -288,7 +304,7 @@ const state = reactive({
applicant_id: 0, applicant_id: 0,
approver_id: 0 approver_id: 0
}, },
configOptions: [], // configOptions: [] as ConfigOption[], //
createDialog: { createDialog: {
visible: false, visible: false,
loading: false, loading: false,
@ -299,10 +315,10 @@ const state = reactive({
}, },
rules: { rules: {
process_name: [ process_name: [
{ required: true, message: t('approval.process.processNameRequired'), trigger: 'blur' } { required: true, message: '请输入流程名称', trigger: 'blur' }
], ],
config_id: [ config_id: [
{ required: true, message: t('approval.process.configIdRequired'), trigger: 'change' } { required: true, message: '请选择审批流配置', trigger: 'change' }
] ]
} }
}, },
@ -316,13 +332,23 @@ const state = reactive({
}, },
rules: { rules: {
status: [ status: [
{ required: true, message: t('approval.process.statusRequired'), trigger: 'change' } { required: true, message: '请选择审批状态', trigger: 'change' }
] ]
} }
}, },
detailDialog: { detailDialog: {
visible: false, visible: false,
info: {} info: {
id: 0,
process_name: '',
applicant_id: 0,
application_time: '',
current_approver_id: 0,
approval_status: '',
approval_time: '',
remarks: '',
participants: [] as Participant[] //
}
} }
}) })
@ -394,11 +420,11 @@ function handleTabChange() {
async function getConfigOptions() { async function getConfigOptions() {
try { try {
const res = await getConfigList({ status: 1 }) const res = await getConfigList({ status: 1 })
state.configOptions = res.data.list.map((item: any) => { state.configOptions = (res.data.list || []).map((item: any) => {
return { return {
label: item.config_name, label: item.config_name || '',
value: item.id value: item.id || 0
} } as ConfigOption
}) })
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -425,7 +451,7 @@ async function handleCreateSubmit() {
state.createDialog.loading = true state.createDialog.loading = true
try { try {
await createProcess(state.createDialog.form) await createProcess(state.createDialog.form)
ElMessage.success(t('approval.process.createSuccess')) ElMessage.success('创建成功')
state.createDialog.visible = false state.createDialog.visible = false
getList() getList()
} catch (error) { } catch (error) {
@ -460,7 +486,7 @@ async function handleApproveSubmit() {
status: state.approveDialog.form.status, status: state.approveDialog.form.status,
remarks: state.approveDialog.form.remarks remarks: state.approveDialog.form.remarks
}) })
ElMessage.success(t('approval.process.approveSuccess')) ElMessage.success('审批成功')
state.approveDialog.visible = false state.approveDialog.visible = false
getList() getList()
} catch (error) { } catch (error) {
@ -473,15 +499,15 @@ async function handleApproveSubmit() {
// //
function handleCancel(row: any) { function handleCancel(row: any) {
ElMessageBox.confirm(t('approval.process.confirmCancel'), t('common.warning'), { ElMessageBox.confirm('确认取消审批?', '警告', {
confirmButtonText: t('common.confirm'), confirmButtonText: '确认',
cancelButtonText: t('common.cancel'), cancelButtonText: '取消',
type: 'warning' type: 'warning'
}) })
.then(async () => { .then(async () => {
try { try {
await cancelProcess({ process_id: row.id }) await cancelProcess({ process_id: row.id })
ElMessage.success(t('approval.process.cancelSuccess')) ElMessage.success('取消成功')
getList() getList()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -514,9 +540,9 @@ function getStatusType(status: string) {
// //
function getStatusText(status: string) { function getStatusText(status: string) {
const map: Record<string, string> = { const map: Record<string, string> = {
pending: t('approval.process.pending'), pending: '待审批',
approved: t('approval.process.approved'), approved: '已审批',
rejected: t('approval.process.rejected') rejected: '已拒绝'
} }
return map[status] || status return map[status] || status
} }

165
admin/src/app/views/service_logs/service_logs.vue

@ -0,0 +1,165 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{ pageName }}</span>
</div>
<el-card
class="box-card !border-none my-[10px] table-search-wrap"
shadow="never"
>
<el-form
:inline="true"
:model="studentCourseUsageTable.searchParam"
ref="searchFormRef"
>
<el-form-item label="家长姓名" prop="name">
<el-input
v-model="studentCourseUsageTable.searchParam.name"
placeholder="请输入家长姓名"
/>
</el-form-item>
<el-form-item label="评分" prop="score">
<el-input
v-model="studentCourseUsageTable.searchParam.score"
placeholder="请输入评分"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadServiceLogsList()">{{
t('search')
}}</el-button>
<el-button @click="resetForm(searchFormRef)">{{
t('reset')
}}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table
:data="studentCourseUsageTable.data"
size="large"
v-loading="studentCourseUsageTable.loading"
>
<template #empty>
<span>{{
!studentCourseUsageTable.loading ? t('emptyData') : ''
}}</span>
</template>
<el-table-column prop="name" label="家长姓名" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="staff_name" label="教练姓名" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="service_name" label="服务内容" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="customer_confirmation" label="是否需要反馈" min-width="120" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-if="row.customer_confirmation == 1"></div>
<div v-if="row.customer_confirmation == 0"></div>
</template>
</el-table-column>
<el-table-column prop="score" label="反馈分数" min-width="120" :show-overflow-tooltip="true"/>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination
v-model:current-page="studentCourseUsageTable.page"
v-model:page-size="studentCourseUsageTable.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="studentCourseUsageTable.total"
@size-change="loadServiceLogsList()"
@current-change="loadServiceLogsList"
/>
</div>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import {
getServiceLogsList,
} from '@/app/api/service_logs'
import { img } from '@/utils/common'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
let studentCourseUsageTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
name: '',
score: '',
},
})
const searchFormRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
/**
* 获取学员课时消费记录列表
*/
const loadServiceLogsList = (page: number = 1) => {
studentCourseUsageTable.loading = true
studentCourseUsageTable.page = page
getServiceLogsList({
page: studentCourseUsageTable.page,
limit: studentCourseUsageTable.limit,
...studentCourseUsageTable.searchParam,
})
.then((res) => {
studentCourseUsageTable.loading = false
studentCourseUsageTable.data = res.data.data
studentCourseUsageTable.total = res.data.total
})
.catch(() => {
studentCourseUsageTable.loading = false
})
}
loadServiceLogsList()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

76
admin/src/app/views/student_course_usage/student_course_usage.vue

@ -3,9 +3,6 @@
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-lg">{{ pageName }}</span> <span class="text-lg">{{ pageName }}</span>
<el-button type="primary" @click="addEvent">
{{ t('addStudentCourseUsage') }}
</el-button>
</div> </div>
<el-card <el-card
@ -17,22 +14,25 @@
:model="studentCourseUsageTable.searchParam" :model="studentCourseUsageTable.searchParam"
ref="searchFormRef" ref="searchFormRef"
> >
<el-form-item :label="t('studentCourseId')" prop="student_course_id"> <el-form-item label="家长名称" prop="emergency_contact">
<el-input <el-input
v-model="studentCourseUsageTable.searchParam.student_course_id" v-model="studentCourseUsageTable.searchParam.emergency_contact"
:placeholder="t('studentCourseIdPlaceholder')" placeholder="请输入家长名称"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('usedHours')" prop="used_hours">
<el-form-item label="学生名称" prop="student_name">
<el-input <el-input
v-model="studentCourseUsageTable.searchParam.used_hours" v-model="studentCourseUsageTable.searchParam.student_name"
:placeholder="t('usedHoursPlaceholder')" placeholder="请输入学生名称"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('usageDate')" prop="usage_date">
<el-form-item label="手机号" prop="contact_phone">
<el-input <el-input
v-model="studentCourseUsageTable.searchParam.usage_date" v-model="studentCourseUsageTable.searchParam.contact_phone"
:placeholder="t('usageDatePlaceholder')" placeholder="请输入手机号"
/> />
</el-form-item> </el-form-item>
@ -58,41 +58,23 @@
!studentCourseUsageTable.loading ? t('emptyData') : '' !studentCourseUsageTable.loading ? t('emptyData') : ''
}}</span> }}</span>
</template> </template>
<el-table-column
prop="student_course_id"
:label="t('studentCourseId')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column <el-table-column prop="course_name" label="课程名称" min-width="120" :show-overflow-tooltip="true"/>
prop="used_hours"
:label="t('usedHours')" <el-table-column prop="student_name" label="学员姓名" min-width="120" :show-overflow-tooltip="true"/>
min-width="120"
:show-overflow-tooltip="true" <el-table-column prop="emergency_contact" label="家长称呼" min-width="120" :show-overflow-tooltip="true"/>
/>
<el-table-column prop="total_hours" label="课时数量" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="used_hours" label="消课数量" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="sy_time" label="剩余课时" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="gift_hours" label="赠送课时" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column
prop="usage_date"
:label="t('usageDate')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column
:label="t('operation')"
fixed="right"
min-width="120"
>
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{
t('edit')
}}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{
t('delete')
}}</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<div class="mt-[16px] flex justify-end"> <div class="mt-[16px] flex justify-end">
<el-pagination <el-pagination
@ -136,9 +118,9 @@ let studentCourseUsageTable = reactive({
loading: true, loading: true,
data: [], data: [],
searchParam: { searchParam: {
student_course_id: '', emergency_contact: '',
used_hours: '', student_name: '',
usage_date: '', contact_phone: '',
}, },
}) })

37
admin/src/utils/request.ts

@ -8,7 +8,6 @@ import type {
import { getToken, isUrl } from './common' import { getToken, isUrl } from './common'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import type { MessageParams } from 'element-plus' import type { MessageParams } from 'element-plus'
import { t } from '@/lang'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage' import storage from '@/utils/storage'
@ -172,13 +171,13 @@ class Request {
const errStatus = err.response.status const errStatus = err.response.status
switch (errStatus) { switch (errStatus) {
case 400: case 400:
errMessage = t('axios.400') errMessage = '请求错误'
break break
case 401: case 401:
errMessage = t('axios.401') errMessage = '未授权,请登录'
break break
case 403: case 403:
errMessage = t('axios.403') errMessage = '拒绝访问'
break break
case 404: case 404:
let baseURL = ''; let baseURL = '';
@ -191,38 +190,38 @@ class Request {
} catch (e) { } catch (e) {
baseURL = location.origin; baseURL = location.origin;
} }
errMessage = baseURL + t('axios.baseUrlError') errMessage = baseURL + '请求地址出错'
break break
case 405: case 405:
errMessage = t('axios.405') errMessage = '请求方法未允许'
break break
case 408: case 408:
errMessage = t('axios.408') errMessage = '请求超时'
break break
case 409: case 409:
errMessage = t('axios.409') errMessage = '资源冲突'
break break
case 500: case 500:
errMessage = t('axios.500') errMessage = '服务器内部错误'
break break
case 501: case 501:
errMessage = t('axios.501') errMessage = '服务未实现'
break break
case 502: case 502:
errMessage = t('axios.502') errMessage = '网关错误'
break break
case 503: case 503:
errMessage = t('axios.503') errMessage = '服务不可用'
break break
case 504: case 504:
errMessage = t('axios.504') errMessage = '网关超时'
break break
case 505: case 505:
errMessage = t('axios.505') errMessage = 'HTTP版本不受支持'
break break
} }
} }
err.message.includes('timeout') && (errMessage = t('axios.timeout')) err.message.includes('timeout') && (errMessage = '请求超时')
if (err.code == 'ERR_NETWORK') { if (err.code == 'ERR_NETWORK') {
let baseURL = ''; let baseURL = '';
try { try {
@ -234,7 +233,7 @@ class Request {
} catch (e) { } catch (e) {
baseURL = location.origin; baseURL = location.origin;
} }
errMessage = baseURL + t('axios.baseUrlError') errMessage = baseURL + '请求地址出错'
} }
errMessage && errMessage &&
this.showElMessage({ this.showElMessage({
@ -256,7 +255,11 @@ class Request {
private messageCache = new Map() private messageCache = new Map()
private showElMessage(options: MessageParams) { private showElMessage(options: MessageParams) {
const cacheKey = options.message // 处理类型问题,安全地获取消息
const message = typeof options === 'string' ? options : (options as any).message;
if (!message) return;
const cacheKey = message;
const cachedMessage = this.messageCache.get(cacheKey) const cachedMessage = this.messageCache.get(cacheKey)
if (!cachedMessage || Date.now() - cachedMessage.timestamp > 5000) { if (!cachedMessage || Date.now() - cachedMessage.timestamp > 5000) {

14
niucloud/app/adminapi/controller/person_course_schedule/PersonCourseSchedule.php

@ -50,14 +50,11 @@ class PersonCourseSchedule extends BaseAdminController
*/ */
public function add(){ public function add(){
$data = $this->request->params([ $data = $this->request->params([
["person_id",0], ["resources_id",[]],
["person_type",""],
["schedule_id",0], ["schedule_id",0],
["course_date","2025-05-29 17:03:27"], ["student_ids",[]],
["time_slot",""],
]); ]);
$this->validate($data, 'app\validate\person_course_schedule\PersonCourseSchedule.add');
$id = (new PersonCourseScheduleService())->add($data); $id = (new PersonCourseScheduleService())->add($data);
return success('ADD_SUCCESS', ['id' => $id]); return success('ADD_SUCCESS', ['id' => $id]);
} }
@ -69,14 +66,11 @@ class PersonCourseSchedule extends BaseAdminController
*/ */
public function edit(int $id){ public function edit(int $id){
$data = $this->request->params([ $data = $this->request->params([
["person_id",0], ["resources_id",[]],
["person_type",""],
["schedule_id",0], ["schedule_id",0],
["course_date","2025-05-29 17:03:27"], ["student_ids",[]],
["time_slot",""],
]); ]);
$this->validate($data, 'app\validate\person_course_schedule\PersonCourseSchedule.edit');
(new PersonCourseScheduleService())->edit($id, $data); (new PersonCourseScheduleService())->edit($id, $data);
return success('EDIT_SUCCESS'); return success('EDIT_SUCCESS');
} }

20
niucloud/app/adminapi/controller/school_approval/Process.php

@ -37,28 +37,28 @@ class Process extends BaseAdminController
$where = []; $where = [];
if ($status !== '') { if ($status !== '') {
$where[] = ['approval_status', '=', $status]; $where[] = ['a.approval_status', '=', $status];
} }
$process_name = input('process_name', ''); $process_name = input('process_name', '');
if (!empty($process_name)) { if (!empty($process_name)) {
$where[] = ['process_name', 'like', "%{$process_name}%"]; $where[] = ['a.process_name', 'like', "%{$process_name}%"];
} }
// 我发起的审批 // 我发起的审批
$applicant_id = input('applicant_id', 0); $applicant_id = input('applicant_id', 0);
if (!empty($applicant_id)) { if (!empty($applicant_id)) {
$where[] = ['applicant_id', '=', $applicant_id]; $where[] = ['a.applicant_id', '=', $applicant_id];
} }
// 待我审批的 // 待我审批的
$approver_id = input('approver_id', 0); $approver_id = input('approver_id', 0);
if (!empty($approver_id)) { if (!empty($approver_id)) {
$where[] = ['current_approver_id', '=', $approver_id]; $where[] = ['a.current_approver_id', '=', $approver_id];
$where[] = ['approval_status', '=', 'pending']; $where[] = ['a.approval_status', '=', 'pending'];
} }
$data = $this->service->getList($where, $page, $limit); $data = $this->service->getList($where, (int)$page, (int)$limit);
return success($data); return success($data);
} }
@ -73,7 +73,7 @@ class Process extends BaseAdminController
return fail('参数错误'); return fail('参数错误');
} }
$info = $this->service->getInfo($id); $info = $this->service->getInfo((int)$id);
if (empty($info)) { if (empty($info)) {
return fail('审批流程不存在'); return fail('审批流程不存在');
} }
@ -99,7 +99,7 @@ class Process extends BaseAdminController
} }
// 设置申请人ID // 设置申请人ID
$data['applicant_id'] = $this->user_info['uid']; $data['applicant_id'] = $this->request->uid();
try { try {
$process_id = $this->service->create($data, $config_id); $process_id = $this->service->create($data, $config_id);
@ -127,7 +127,7 @@ class Process extends BaseAdminController
} }
try { try {
$result = $this->service->approve($process_id, $this->user_info['uid'], $status, $remarks); $result = $this->service->approve((int)$process_id, $this->request->uid(), $status, $remarks);
return success($result); return success($result);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail($e->getMessage()); return fail($e->getMessage());
@ -146,7 +146,7 @@ class Process extends BaseAdminController
} }
try { try {
$result = $this->service->cancel($process_id, $this->user_info['uid']); $result = $this->service->cancel($process_id, $this->request->uid());
return success($result); return success($result);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail($e->getMessage()); return fail($e->getMessage());

39
niucloud/app/adminapi/controller/service_logs/ServiceLogs.php

@ -0,0 +1,39 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\adminapi\controller\service_logs;
use app\service\admin\service_logs\ServiceLogsService;
use core\base\BaseAdminController;
use app\service\admin\service\ServiceService;
/**
* 服务控制器
* Class Service
* @package app\adminapi\controller\service
*/
class ServiceLogs extends BaseAdminController
{
/**
* 获取服务列表
* @return \think\Response
*/
public function lists(){
$data = $this->request->params([
["name",""],
["score",""]
]);
return success((new ServiceLogsService())->getPage($data));
}
}

6
niucloud/app/adminapi/controller/student_course_usage/StudentCourseUsage.php

@ -28,9 +28,9 @@ class StudentCourseUsage extends BaseAdminController
*/ */
public function lists(){ public function lists(){
$data = $this->request->params([ $data = $this->request->params([
["student_course_id",""], ["emergency_contact",""],
["used_hours",""], ["student_name",""],
["usage_date",""] ["contact_phone",""]
]); ]);
return success((new StudentCourseUsageService())->getPage($data)); return success((new StudentCourseUsageService())->getPage($data));
} }

15
niucloud/app/adminapi/controller/sys/System.php

@ -169,4 +169,19 @@ class System extends BaseAdminController
} }
//全部人员
public function person_all(){
return success(data: (new SystemService())->person_all());
}
public function role_all(){
return success(data: (new SystemService())->role_all());
}
public function departments_all(){
return success(data: (new SystemService())->departments_all());
}
} }

31
niucloud/app/adminapi/route/service_logs.php

@ -0,0 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use think\facade\Route;
use app\adminapi\middleware\AdminCheckRole;
use app\adminapi\middleware\AdminCheckToken;
use app\adminapi\middleware\AdminLog;
// USER_CODE_BEGIN -- service
Route::group('service_logs', function () {
Route::get('service_logs', 'service_logs.ServiceLogs/lists');
})->middleware([
AdminCheckToken::class,
AdminCheckRole::class,
AdminLog::class
]);
// USER_CODE_END -- service

5
niucloud/app/adminapi/route/sys.php

@ -351,6 +351,11 @@ Route::group('sys', function() {
//系统环境(不效验登录状态) //系统环境(不效验登录状态)
Route::group('sys', function() { Route::group('sys', function() {
Route::get('person_all', 'sys.System/person_all');
Route::get('role_all', 'sys.System/role_all');
Route::get('departments_all', 'sys.System/departments_all');
Route::get('web/website', 'sys.Config/getWebsite'); Route::get('web/website', 'sys.Config/getWebsite');
// 获取版权信息 // 获取版权信息
Route::get('web/copyright', 'sys.Config/getCopyright'); Route::get('web/copyright', 'sys.Config/getCopyright');

76
niucloud/app/model/person_course_schedule/PersonCourseSchedule.php

@ -11,6 +11,8 @@
namespace app\model\person_course_schedule; namespace app\model\person_course_schedule;
use app\model\personnel\Personnel;
use app\model\student\Student;
use core\base\BaseModel; use core\base\BaseModel;
use think\model\relation\HasMany; use think\model\relation\HasMany;
use think\model\relation\HasOne; use think\model\relation\HasOne;
@ -40,7 +42,19 @@ class PersonCourseSchedule extends BaseModel
/** /**
* 搜索器:人员与课程安排关系资源 * 搜索器:人员与课程安排关系关系编号
* @param $value
* @param $data
*/
public function searchIdAttr($query, $value, $data)
{
if ($value) {
$query->where("id", $value);
}
}
/**
* 搜索器:人员与课程安排关系人员或资源ID
* @param $value * @param $value
* @param $data * @param $data
*/ */
@ -51,13 +65,71 @@ class PersonCourseSchedule extends BaseModel
} }
} }
/**
* 搜索器:人员与课程安排关系人员类型: student-正式学员, customer_resource-客户资源
* @param $value
* @param $data
*/
public function searchPersonTypeAttr($query, $value, $data)
{
if ($value) {
$query->where("person_type", $value);
}
}
/**
* 搜索器:人员与课程安排关系课程安排ID
* @param $value
* @param $data
*/
public function searchScheduleIdAttr($query, $value, $data)
{
if ($value) {
$query->where("schedule_id", $value);
}
}
/**
* 搜索器:人员与课程安排关系上课日期
* @param $value
* @param $data
*/
public function searchCourseDateAttr($query, $value, $data)
{
if ($value) {
$query->where("course_date", $value);
}
}
/**
* 搜索器:人员与课程安排关系上课时段
* @param $value
* @param $data
*/
public function searchTimeSlotAttr($query, $value, $data)
{
if ($value) {
$query->where("time_slot", $value);
}
}
public function person()
{
return $this->hasOne(Personnel::class, 'id', 'person_id');
}
public function student()
{
return $this->hasOne(Student::class, 'id', 'student_id');
}
public function resources()
{
return $this->hasOne(CustomerResources::class, 'id', 'resources_id');
}
public function customerResources(){ public function customerResources(){
return $this->hasOne(CustomerResources::class, 'id', 'person_id')->joinType('left')->withField('name,id')->bind(['person_id_name'=>'name']); return $this->hasOne(CustomerResources::class, 'id', 'person_id')->joinType('left')->withField('name,id')->bind(['person_id_name'=>'name']);
} }
} }

44
niucloud/app/model/service_logs/ServiceLogs.php

@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\model\service_logs;
use core\base\BaseModel;
use think\model\concern\SoftDelete;
use think\model\relation\HasMany;
use think\model\relation\HasOne;
/**
* 服务模型
* Class Service
* @package app\model\service
*/
class ServiceLogs extends BaseModel
{
/**
* 数据表主键
* @var string
*/
protected $pk = 'id';
/**
* 模型名称
* @var string
*/
protected $name = 'service_logs';
}

12
niucloud/app/service/admin/auth/AuthService.php

@ -48,7 +48,10 @@ class AuthService extends BaseAdminService
if (!in_array($rule, $method_menu_list)) if (!in_array($rule, $method_menu_list))
return true; return true;
$auth_role_list = $this->getAuthApiList(); $auth_role_list = $this->getAuthApiList();
if (!empty($auth_role_list[ $method ]) && in_array($rule, $auth_role_list[ $method ])) if (!empty($auth_role_list[ $method ]) && in_array($rule, $auth_role_list[ $method ]))
return true; return true;
@ -72,7 +75,14 @@ class AuthService extends BaseAdminService
//获取站点信息 //获取站点信息
return ( new MenuService() )->getAllApiList(1); return ( new MenuService() )->getAllApiList(1);
} else { } else {
$user_role_ids = $user_info[ 'role_ids' ];
$per = new Personnel();
$CampusPersonRole = new CampusPersonRole();
$per_id = $per->where(['sys_user_id' => $this->uid])->column('id');
$user_role_ids = $CampusPersonRole->where(['person_id' => $per_id])->column('role_id');
// $user_role_ids = $user_info[ 'role_ids' ];
$role_service = new RoleService(); $role_service = new RoleService();
$menu_keys = $role_service->getMenuKeysByRoleIds($user_role_ids ?? []); $menu_keys = $role_service->getMenuKeysByRoleIds($user_role_ids ?? []);
return $menu_service->getApiListByMenuKeys($menu_keys); return $menu_service->getApiListByMenuKeys($menu_keys);

4
niucloud/app/service/admin/customer_resources/CustomerResourcesService.php

@ -95,6 +95,10 @@ class CustomerResourcesService extends BaseAdminService
return $this->pageQuery($search_model, function ($item, $key) { return $this->pageQuery($search_model, function ($item, $key) {
$item = $this->makeUp($item); $item = $this->makeUp($item);
$sixSpeed = new SixSpeed();
$data = $sixSpeed->where(['resource_id' => $item['id']])->findOrEmpty()->toArray();
$item['six'] = $data;
}); });
} }

60
niucloud/app/service/admin/service_logs/ServiceLogsService.php

@ -0,0 +1,60 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\service\admin\service_logs;
use app\model\service_logs\ServiceLogs;
use core\base\BaseAdminService;
/**
* 学员课时消费记录服务层
* Class StudentCourseUsageService
* @package app\service\admin\student_course_usage
*/
class ServiceLogsService extends BaseAdminService
{
public function __construct()
{
parent::__construct();
$this->model = new ServiceLogs();
}
/**
* 获取学员课时消费记录列表
* @return array
*/
public function getPage(array $data = [])
{
$where = [];
if($data['name']){
$where[] = ['b.name','like','%'.$data['name'].'%'];
}
if($data['score']){
$where[] = ['a.score','=',$data['score']];
}
$search_model = $this->model
->alias("a")
->join(['school_customer_resources' => 'b'],'a.resource_id = b.id','left')
->join(['school_personnel' => 'c'],'a.staff_id = c.id','left')
->join(['school_service' => 'd'],'a.service_id = d.id','left')
->where($where)
->field("a.*,b.name,c.name as staff_name,d.service_name,d.customer_confirmation")
->order("a.id desc");
$list = $this->pageQuery($search_model);
return $list;
}
}

26
niucloud/app/service/admin/student_course_usage/StudentCourseUsageService.php

@ -31,15 +31,31 @@ class StudentCourseUsageService extends BaseAdminService
/** /**
* 获取学员课时消费记录列表 * 获取学员课时消费记录列表
* @param array $where
* @return array * @return array
*/ */
public function getPage(array $where = []) public function getPage(array $data = [])
{ {
$field = 'id,student_course_id,used_hours,usage_date,created_at,updated_at'; $where = [];
$order = 'id desc'; if($data['emergency_contact']){
$where[] = ['c.emergency_contact','like','%'.$data['emergency_contact'].'%'];
}
if($data['student_name']){
$where[] = ['c.name','like','%'.$data['student_name'].'%'];
}
if($data['contact_phone']){
$where[] = ['c.contact_phone','=',$data['contact_phone']];
}
$search_model = $this->model->withSearch(["id","student_course_id","used_hours","usage_date"], $where)->field($field)->order($order); $search_model = $this->model
->alias("a")
->join(['school_student_courses' => 'b'],'a.student_course_id = b.id','left')
->join(['school_student' => 'c'],'b.student_id = c.id','left')
->join(['school_course' => 'd'],'b.course_id = d.id','left')
->where($where)
->field("a.*,c.name as student_name,c.emergency_contact,d.course_name,b.total_hours,ROUND(b.total_hours - b.use_total_hours) as sy_time,ROUND(b.gift_hours - b.use_gift_hours) as gift_hours")
->order("a.id desc");
$list = $this->pageQuery($search_model); $list = $this->pageQuery($search_model);
return $list; return $list;
} }

18
niucloud/app/service/admin/sys/SystemService.php

@ -17,10 +17,12 @@ use app\model\campus\Campus;
use app\model\campus_person_role\CampusPersonRole; use app\model\campus_person_role\CampusPersonRole;
use app\model\communication_records\CommunicationRecords; use app\model\communication_records\CommunicationRecords;
use app\model\customer_resources\CustomerResources; use app\model\customer_resources\CustomerResources;
use app\model\departments\Departments;
use app\model\market_performance\MarketPerformance; use app\model\market_performance\MarketPerformance;
use app\model\personnel\Personnel; use app\model\personnel\Personnel;
use app\model\student\Student; use app\model\student\Student;
use app\model\sys\SysConfig; use app\model\sys\SysConfig;
use app\model\sys\SysRole;
use app\service\core\sys\CoreSysConfigService; use app\service\core\sys\CoreSysConfigService;
use core\base\BaseAdminService; use core\base\BaseAdminService;
use think\facade\Db; use think\facade\Db;
@ -342,4 +344,20 @@ class SystemService extends BaseAdminService
} }
return $data; return $data;
} }
public function person_all(){
$Personnel = new Personnel();
return $Personnel->select()->toArray();
}
public function role_all(){
$sysRole = new SysRole();
return $sysRole->select()->toArray();
}
public function departments_all(){
$departments = new Departments();
return $departments->select()->toArray();
}
} }

3
niucloud/app/service/school_approval/SchoolApprovalConfigService.php

@ -51,10 +51,11 @@ class SchoolApprovalConfigService
public function getInfo(int $id): array public function getInfo(int $id): array
{ {
$info = (new SchoolApprovalConfig())->with(['nodes'])->where(['id' => $id])->find(); $info = (new SchoolApprovalConfig())->with(['nodes'])->where(['id' => $id])->find();
if (empty($info)) { if (empty($info)) {
return []; return [];
} }
return $info->toArray(); return $info->toArray();
} }

26
niucloud/app/service/school_approval/SchoolApprovalProcessService.php

@ -3,6 +3,7 @@ declare(strict_types=1);
namespace app\service\school_approval; namespace app\service\school_approval;
use app\model\personnel\Personnel;
use app\model\school_approval\SchoolApprovalConfig; use app\model\school_approval\SchoolApprovalConfig;
use app\model\school_approval\SchoolApprovalConfigNode; use app\model\school_approval\SchoolApprovalConfigNode;
use app\model\school_approval\SchoolApprovalParticipants; use app\model\school_approval\SchoolApprovalParticipants;
@ -26,10 +27,13 @@ class SchoolApprovalProcessService
*/ */
public function getList(array $where = [], int $page = 1, int $limit = 10): array public function getList(array $where = [], int $page = 1, int $limit = 10): array
{ {
$field = 'id, process_name, applicant_id, application_time, current_approver_id, approval_status, approval_time, remarks, created_at, updated_at'; $field = 'a.*,b.name as applicant_name,c.name as current_approver_name';
$order = 'id desc'; $order = 'a.id desc';
$list = (new SchoolApprovalProcess()) $list = (new SchoolApprovalProcess())
->alias("a")
->join(['school_personnel' => 'b'],'a.applicant_id = b.id','left')
->join(['school_personnel' => 'c'],'a.current_approver_id = c.id','left')
->where($where) ->where($where)
->field($field) ->field($field)
->order($order) ->order($order)
@ -52,12 +56,24 @@ class SchoolApprovalProcessService
*/ */
public function getInfo(int $id): array public function getInfo(int $id): array
{ {
$info = (new SchoolApprovalProcess())->with(['participants'])->where(['id' => $id])->find(); $info = (new SchoolApprovalProcess())
->alias("a")
->join(['school_personnel' => 'b'],'a.applicant_id = b.id','left')
->join(['school_personnel' => 'c'],'a.current_approver_id = c.id','left')
->with(['participants'])
->where(['a.id' => $id])
->field('a.*,b.name as applicant_name,c.name as current_approver_name')
->find();
if (empty($info)) { if (empty($info)) {
return []; return [];
} }
$info = $info->toArray();
$Personnel = new Personnel();
foreach ($info['participants'] as $key => $value) {
$info['participants'][$key]['name'] = $Personnel->where(['id' => $value['participant_id']])->value('name');
}
return $info->toArray(); return $info;
} }
/** /**
@ -81,7 +97,7 @@ class SchoolApprovalProcessService
$process = [ $process = [
'process_name' => $data['process_name'], 'process_name' => $data['process_name'],
'applicant_id' => $data['applicant_id'], 'applicant_id' => $data['applicant_id'],
'application_time' => time(), 'application_time' => date("Y-m-d H:i:s"),
'current_approver_id' => 0, // 初始时为0,后面会更新 'current_approver_id' => 0, // 初始时为0,后面会更新
'approval_status' => SchoolApprovalProcess::STATUS_PENDING, 'approval_status' => SchoolApprovalProcess::STATUS_PENDING,
'remarks' => $data['remarks'] ?? '' 'remarks' => $data['remarks'] ?? ''

Loading…
Cancel
Save