Commit f86c9c6930aa93078c9289dd5d97ca682cd50adc

Authored by “wangming”
1 parent 4b31ecb4

添加库存管理和学习班级管理功能

前端功能:
- 新增库存管理页面,包含库存列表、添加/编辑库存、库存详情等功能
- 新增库存使用记录管理,支持添加使用记录、查看使用记录列表等
- 新增学习班级管理页面,包含班级列表、创建班级、添加学员等功能
- 新增学习记录管理,支持添加学习记录、查看学员列表等
- 完善API接口调用和路由配置

后端功能:
- 完善LqInventoryService库存管理服务
- 完善LqInventoryUsageService库存使用记录服务
- 优化服务接口和业务逻辑

技术特点:
- 使用Vue 2 + Element UI构建现代化管理界面
- 支持分页查询、条件筛选、批量操作等功能
- 响应式设计,适配不同屏幕尺寸
- 统一的错误处理和用户提示
antis-ncc-admin/src/api/extend/lqInventory.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// ========== 库存管理接口 ==========
  4 +
  5 +// 创建库存
  6 +export function createInventory(data) {
  7 + return request({
  8 + url: '/api/Extend/LqInventory/Create',
  9 + method: 'post',
  10 + data
  11 + })
  12 +}
  13 +
  14 +// 更新库存
  15 +export function updateInventory(data) {
  16 + return request({
  17 + url: '/api/Extend/LqInventory/Update',
  18 + method: 'put',
  19 + data
  20 + })
  21 +}
  22 +
  23 +// 获取库存列表
  24 +export function getInventoryList(params) {
  25 + return request({
  26 + url: '/api/Extend/LqInventory/GetList',
  27 + method: 'get',
  28 + data: params
  29 + })
  30 +}
  31 +
  32 +// 获取库存详情
  33 +export function getInventoryInfo(id) {
  34 + return request({
  35 + url: '/api/Extend/LqInventory/GetInfo',
  36 + method: 'get',
  37 + data: { id }
  38 + })
  39 +}
  40 +
  41 +// ========== 库存使用记录接口 ==========
  42 +
  43 +// 添加库存使用记录
  44 +export function createInventoryUsage(data) {
  45 + return request({
  46 + url: '/api/Extend/LqInventoryUsage/Create',
  47 + method: 'post',
  48 + data
  49 + })
  50 +}
  51 +
  52 +// 作废库存使用记录
  53 +export function cancelInventoryUsage(id, remarks) {
  54 + return request({
  55 + url: '/api/Extend/LqInventoryUsage/Cancel',
  56 + method: 'put',
  57 + data: { id, remarks }
  58 + })
  59 +}
  60 +
  61 +// 获取使用记录列表
  62 +export function getInventoryUsageList(params) {
  63 + return request({
  64 + url: '/api/Extend/LqInventoryUsage/GetList',
  65 + method: 'get',
  66 + data: params
  67 + })
  68 +}
  69 +
  70 +// 获取产品使用统计
  71 +export function getProductUsageStatistics(data) {
  72 + return request({
  73 + url: '/api/Extend/LqInventoryUsage/GetProductUsageStatistics',
  74 + method: 'post',
  75 + data
  76 + })
  77 +}
  78 +
  79 +// 获取门店使用统计
  80 +export function getStoreUsageStatistics(data) {
  81 + return request({
  82 + url: '/api/Extend/LqInventoryUsage/GetStoreUsageStatistics',
  83 + method: 'post',
  84 + data
  85 + })
  86 +}
  87 +
  88 +// 获取使用趋势统计
  89 +export function getUsageTrendStatistics(data) {
  90 + return request({
  91 + url: '/api/Extend/LqInventoryUsage/GetUsageTrendStatistics',
  92 + method: 'post',
  93 + data
  94 + })
  95 +}
  96 +
  97 +// 获取产品使用排行榜
  98 +export function getProductUsageRanking(data) {
  99 + return request({
  100 + url: '/api/Extend/LqInventoryUsage/GetProductUsageRanking',
  101 + method: 'post',
  102 + data
  103 + })
  104 +}
... ...
antis-ncc-admin/src/api/extend/lqStudyClass.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 创建学习班级并添加学员
  4 +export function createClassWithStudents(data) {
  5 + return request({
  6 + url: '/api/Extend/LqStudyClass/CreateClassWithStudents',
  7 + method: 'post',
  8 + data
  9 + })
  10 +}
  11 +
  12 +// 向现有班级添加学员
  13 +export function addStudentsToClass(data) {
  14 + return request({
  15 + url: '/api/Extend/LqStudyClass/AddStudentsToClass',
  16 + method: 'post',
  17 + data
  18 + })
  19 +}
  20 +
  21 +// 获取所有班级列表
  22 +export function getClassList(params) {
  23 + return request({
  24 + url: '/api/Extend/LqStudyClass/GetClassList',
  25 + method: 'get',
  26 + data: params
  27 + })
  28 +}
  29 +
  30 +// 获取班级下所有学员信息
  31 +export function getStudentListByClassId(params) {
  32 + return request({
  33 + url: '/api/Extend/LqStudyClass/GetStudentListByClassId',
  34 + method: 'get',
  35 + data: params
  36 + })
  37 +}
  38 +
  39 +// 添加学习记录
  40 +export function addStudyRecord(data) {
  41 + return request({
  42 + url: '/api/Extend/LqStudyClass/AddStudyRecord',
  43 + method: 'post',
  44 + data
  45 + })
  46 +}
  47 +
  48 +// 获取学习记录列表
  49 +export function getStudyRecordList(params) {
  50 + return request({
  51 + url: '/api/Extend/LqStudyClass/GetStudyRecordList',
  52 + method: 'get',
  53 + data: params
  54 + })
  55 +}
  56 +
  57 +// 作废学习记录
  58 +export function cancelStudyRecord(id) {
  59 + return request({
  60 + url: '/api/Extend/LqStudyClass/CancelStudyRecord',
  61 + method: 'post',
  62 + data: id
  63 + })
  64 +}
  65 +
... ...
antis-ncc-admin/src/router/modules/base.js
... ... @@ -76,6 +76,17 @@ const baseRouter = [{
76 76 icon: 'icon-ym icon-ym-s-data',
77 77 }
78 78 },
  79 + {
  80 + path: '/lqInventory',
  81 + component: (resolve) => require(['@/views/lqInventory'], resolve),
  82 + name: 'lqInventory',
  83 + meta: {
  84 + title: 'lqInventory',
  85 + affix: false,
  86 + zhTitle: '库存管理',
  87 + icon: 'icon-ym icon-ym-box',
  88 + }
  89 + },
79 90 {
80 91 path: '/salaryCalculation',
81 92 component: (resolve) => require(['@/views/salaryCalculation/index'], resolve),
... ...
antis-ncc-admin/src/views/lqInventory/AddUsageRecordForm.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="添加库存使用记录" :visible.sync="visible" width="600px" @close="closeDialog">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="100px">
  4 + <el-row :gutter="20">
  5 + <el-col :span="12">
  6 + <el-form-item label="产品" prop="productId">
  7 + <el-select v-model="form.productId" placeholder="请选择产品" style="width: 100%"
  8 + @change="onProductChange">
  9 + <el-option v-for="product in productList" :key="product.id" :label="product.productName"
  10 + :value="product.id">
  11 + <span style="float: left">{{ product.productName }}</span>
  12 + <span style="float: right; color: #8492a6; font-size: 13px">{{ product.productCategory
  13 + }}</span>
  14 + </el-option>
  15 + </el-select>
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="12">
  19 + <el-form-item label="使用门店" prop="storeId">
  20 + <userSelect v-model="form.storeId" placeholder="请选择使用门店" @change="onStoreChange" />
  21 + </el-form-item>
  22 + </el-col>
  23 + </el-row>
  24 +
  25 + <el-row :gutter="20">
  26 + <el-col :span="12">
  27 + <el-form-item label="使用数量" prop="usageQuantity">
  28 + <el-input-number v-model="form.usageQuantity" :min="1" style="width: 100%"
  29 + placeholder="请输入使用数量" />
  30 + </el-form-item>
  31 + </el-col>
  32 + <el-col :span="12">
  33 + <el-form-item label="使用时间" prop="usageTime">
  34 + <el-date-picker v-model="form.usageTime" type="date" placeholder="选择使用时间"
  35 + value-format="yyyy-MM-dd" style="width: 100%" />
  36 + </el-form-item>
  37 + </el-col>
  38 + </el-row>
  39 +
  40 + <el-form-item label="关联消费ID">
  41 + <el-input v-model="form.relatedConsumeId" placeholder="请输入关联消费ID(可选)" />
  42 + </el-form-item>
  43 +
  44 + <el-form-item label="备注">
  45 + <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
  46 + </el-form-item>
  47 +
  48 + <!-- 产品信息显示 -->
  49 + <div v-if="selectedProduct" class="product-info">
  50 + <el-divider content-position="left">产品信息</el-divider>
  51 + <el-row :gutter="20">
  52 + <el-col :span="8">
  53 + <div class="info-item">
  54 + <label>产品分类:</label>
  55 + <span>{{ selectedProduct.productCategory }}</span>
  56 + </div>
  57 + </el-col>
  58 + <el-col :span="8">
  59 + <div class="info-item">
  60 + <label>单价:</label>
  61 + <span class="price">{{ formatMoney(selectedProduct.price) }}</span>
  62 + </div>
  63 + </el-col>
  64 + <el-col :span="8">
  65 + <div class="info-item">
  66 + <label>当前库存:</label>
  67 + <span :class="selectedProduct.quantity <= 10 ? 'text-danger' : ''">{{
  68 + selectedProduct.quantity }}</span>
  69 + </div>
  70 + </el-col>
  71 + </el-row>
  72 + </div>
  73 + </el-form>
  74 +
  75 + <div slot="footer" class="dialog-footer">
  76 + <el-button @click="closeDialog">取消</el-button>
  77 + <el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
  78 + </div>
  79 + </el-dialog>
  80 +</template>
  81 +
  82 +<script>
  83 +import { createInventoryUsage, getInventoryList } from '@/api/extend/lqInventory'
  84 +
  85 +export default {
  86 + name: 'AddUsageRecordForm',
  87 + data() {
  88 + return {
  89 + visible: false,
  90 + submitLoading: false,
  91 + productId: '', // 特定产品ID
  92 + productName: '', // 特定产品名称
  93 + productList: [], // 产品列表
  94 + selectedProduct: null, // 选中的产品
  95 + form: {
  96 + productId: '',
  97 + storeId: '',
  98 + usageQuantity: 1,
  99 + usageTime: '',
  100 + relatedConsumeId: '',
  101 + remark: ''
  102 + },
  103 + rules: {
  104 + productId: [
  105 + { required: true, message: '请选择产品', trigger: 'change' }
  106 + ],
  107 + storeId: [
  108 + { required: true, message: '请选择使用门店', trigger: 'change' }
  109 + ],
  110 + usageQuantity: [
  111 + { required: true, message: '请输入使用数量', trigger: 'blur' }
  112 + ],
  113 + usageTime: [
  114 + { required: true, message: '请选择使用时间', trigger: 'change' }
  115 + ]
  116 + }
  117 + }
  118 + },
  119 + methods: {
  120 + init(productId = '', productName = '') {
  121 + this.visible = true
  122 + this.productId = productId
  123 + this.productName = productName
  124 + this.getProductList()
  125 + this.resetForm()
  126 + },
  127 +
  128 + // 获取产品列表
  129 + getProductList() {
  130 + const params = {
  131 + currentPage: 1,
  132 + pageSize: 1000, // 获取所有产品
  133 + isEffective: 1 // 只获取有效产品
  134 + }
  135 +
  136 + getInventoryList(params).then(response => {
  137 + if (response.code === 200) {
  138 + this.productList = response.data.list || []
  139 + // 如果指定了产品ID,自动选中
  140 + if (this.productId) {
  141 + this.form.productId = this.productId
  142 + this.onProductChange(this.productId)
  143 + }
  144 + } else {
  145 + this.$message.error(response.msg || '获取产品列表失败')
  146 + }
  147 + })
  148 + },
  149 +
  150 + // 产品选择变化
  151 + onProductChange(productId) {
  152 + this.selectedProduct = this.productList.find(product => product.id === productId)
  153 + },
  154 +
  155 + // 门店选择变化
  156 + onStoreChange(ids, users) {
  157 + // userSelect组件的回调处理
  158 + },
  159 +
  160 + resetForm() {
  161 + this.form = {
  162 + productId: this.productId || '',
  163 + storeId: '',
  164 + usageQuantity: 1,
  165 + usageTime: '',
  166 + relatedConsumeId: '',
  167 + remark: ''
  168 + }
  169 + this.selectedProduct = null
  170 + this.$nextTick(() => {
  171 + if (this.$refs.form) {
  172 + this.$refs.form.clearValidate()
  173 + }
  174 + })
  175 + },
  176 +
  177 + // 提交表单
  178 + submitForm() {
  179 + this.$refs.form.validate((valid) => {
  180 + if (valid) {
  181 + // 检查库存数量
  182 + if (this.selectedProduct && this.form.usageQuantity > this.selectedProduct.quantity) {
  183 + this.$message.error(`库存不足,当前库存:${this.selectedProduct.quantity},需要数量:${this.form.usageQuantity}`)
  184 + return
  185 + }
  186 +
  187 + this.submitLoading = true
  188 + createInventoryUsage(this.form).then(response => {
  189 + if (response.code === 200) {
  190 + this.$message.success('添加成功')
  191 + this.closeDialog()
  192 + this.$emit('refreshDataList')
  193 + } else {
  194 + this.$message.error(response.msg || '添加失败')
  195 + }
  196 + this.submitLoading = false
  197 + }).catch(() => {
  198 + this.submitLoading = false
  199 + })
  200 + }
  201 + })
  202 + },
  203 +
  204 + // 格式化金额
  205 + formatMoney(value) {
  206 + if (value === null || value === undefined || value === '') {
  207 + return '0.00'
  208 + }
  209 + return Number(value).toFixed(2)
  210 + },
  211 +
  212 + // 关闭弹窗
  213 + closeDialog() {
  214 + this.visible = false
  215 + this.resetForm()
  216 + }
  217 + }
  218 +}
  219 +</script>
  220 +
  221 +<style scoped>
  222 +.dialog-footer {
  223 + text-align: right;
  224 +}
  225 +
  226 +.product-info {
  227 + margin-top: 20px;
  228 + padding: 15px;
  229 + background: #f5f7fa;
  230 + border-radius: 4px;
  231 +}
  232 +
  233 +.info-item {
  234 + margin-bottom: 10px;
  235 + display: flex;
  236 + align-items: center;
  237 +}
  238 +
  239 +.info-item label {
  240 + font-weight: bold;
  241 + color: #606266;
  242 + min-width: 80px;
  243 + margin-right: 10px;
  244 +}
  245 +
  246 +.info-item span {
  247 + color: #303133;
  248 +}
  249 +
  250 +.price {
  251 + color: #e6a23c;
  252 + font-weight: bold;
  253 +}
  254 +
  255 +.text-danger {
  256 + color: #f56c6c;
  257 + font-weight: bold;
  258 +}
  259 +</style>
... ...
antis-ncc-admin/src/views/lqInventory/InventoryForm.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="isEdit ? '编辑库存' : '添加库存'" :visible.sync="visible" width="600px" @close="closeDialog">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="100px">
  4 + <el-row :gutter="20">
  5 + <el-col :span="12">
  6 + <el-form-item label="产品名称" prop="productName">
  7 + <el-input v-model="form.productName" placeholder="请输入产品名称" />
  8 + </el-form-item>
  9 + </el-col>
  10 + <el-col :span="12">
  11 + <el-form-item label="产品分类" prop="productCategory">
  12 + <el-input v-model="form.productCategory" placeholder="请输入产品分类" />
  13 + </el-form-item>
  14 + </el-col>
  15 + </el-row>
  16 +
  17 + <el-row :gutter="20">
  18 + <el-col :span="12">
  19 + <el-form-item label="单价" prop="price">
  20 + <el-input-number v-model="form.price" :min="0" :precision="2" style="width: 100%"
  21 + placeholder="请输入单价" />
  22 + </el-form-item>
  23 + </el-col>
  24 + <el-col :span="12">
  25 + <el-form-item label="库存数量" prop="quantity">
  26 + <el-input-number v-model="form.quantity" :min="0" style="width: 100%" placeholder="请输入库存数量" />
  27 + </el-form-item>
  28 + </el-col>
  29 + </el-row>
  30 +
  31 + <el-row :gutter="20">
  32 + <el-col :span="12">
  33 + <el-form-item label="标准单位" prop="standardUnit">
  34 + <el-input v-model="form.standardUnit" placeholder="请输入标准单位" />
  35 + </el-form-item>
  36 + </el-col>
  37 + <el-col :span="12">
  38 + <el-form-item label="负责部门" prop="departmentId">
  39 + <el-select v-model="form.departmentId" placeholder="请选择负责部门" style="width: 100%" clearable>
  40 + <el-option v-for="dept in departmentList" :key="dept.id" :label="dept.name"
  41 + :value="dept.id">
  42 + </el-option>
  43 + </el-select>
  44 + </el-form-item>
  45 + </el-col>
  46 + </el-row>
  47 +
  48 + <el-form-item label="备注">
  49 + <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
  50 + </el-form-item>
  51 + </el-form>
  52 +
  53 + <div slot="footer" class="dialog-footer">
  54 + <el-button @click="closeDialog">取消</el-button>
  55 + <el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
  56 + </div>
  57 + </el-dialog>
  58 +</template>
  59 +
  60 +<script>
  61 +import { createInventory, updateInventory, getInventoryInfo } from '@/api/extend/lqInventory'
  62 +import { getUserList } from '@/api/permission/user'
  63 +
  64 +export default {
  65 + name: 'InventoryForm',
  66 + data() {
  67 + return {
  68 + visible: false,
  69 + submitLoading: false,
  70 + isEdit: false,
  71 + departmentList: [], // 部门列表
  72 + form: {
  73 + id: '',
  74 + productName: '',
  75 + productCategory: '',
  76 + price: 0,
  77 + quantity: 0,
  78 + standardUnit: '',
  79 + departmentId: '',
  80 + departmentName: '',
  81 + remark: ''
  82 + },
  83 + rules: {
  84 + productName: [
  85 + { required: true, message: '请输入产品名称', trigger: 'blur' }
  86 + ],
  87 + productCategory: [
  88 + { required: true, message: '请输入产品分类', trigger: 'blur' }
  89 + ],
  90 + price: [
  91 + { required: true, message: '请输入单价', trigger: 'blur' }
  92 + ],
  93 + quantity: [
  94 + { required: true, message: '请输入库存数量', trigger: 'blur' }
  95 + ],
  96 + standardUnit: [
  97 + { required: true, message: '请输入标准单位', trigger: 'blur' }
  98 + ],
  99 + departmentId: [
  100 + { required: true, message: '请选择负责部门', trigger: 'change' }
  101 + ]
  102 + }
  103 + }
  104 + },
  105 + methods: {
  106 + init(id = '') {
  107 + this.visible = true
  108 + this.isEdit = !!id
  109 + this.getDepartmentList()
  110 + if (this.isEdit) {
  111 + this.getInventoryInfo(id)
  112 + } else {
  113 + this.resetForm()
  114 + }
  115 + },
  116 +
  117 + // 获取部门列表
  118 + getDepartmentList() {
  119 + const params = {
  120 + currentPage: 1,
  121 + pageSize: 1000, // 获取所有部门
  122 + enabledMark: 1 // 只获取启用的部门
  123 + }
  124 +
  125 + getUserList(params).then(response => {
  126 + if (response.code === 200) {
  127 + // 将用户列表转换为部门列表格式
  128 + this.departmentList = response.data.list.map(user => ({
  129 + id: user.id,
  130 + name: user.realName
  131 + }))
  132 + } else {
  133 + this.$message.error(response.msg || '获取部门列表失败')
  134 + }
  135 + }).catch(() => {
  136 + this.$message.error('获取部门列表失败')
  137 + })
  138 + },
  139 +
  140 + // 获取库存详情
  141 + getInventoryInfo(id) {
  142 + getInventoryInfo(id).then(response => {
  143 + if (response.code === 200) {
  144 + this.form = {
  145 + id: response.data.id,
  146 + productName: response.data.productName,
  147 + productCategory: response.data.productCategory,
  148 + price: response.data.price,
  149 + quantity: response.data.quantity,
  150 + standardUnit: response.data.standardUnit,
  151 + departmentId: response.data.departmentId,
  152 + departmentName: response.data.departmentName,
  153 + remark: response.data.remark || ''
  154 + }
  155 + } else {
  156 + this.$message.error(response.msg || '获取库存详情失败')
  157 + }
  158 + })
  159 + },
  160 +
  161 +
  162 + resetForm() {
  163 + this.form = {
  164 + id: '',
  165 + productName: '',
  166 + productCategory: '',
  167 + price: 0,
  168 + quantity: 0,
  169 + standardUnit: '',
  170 + departmentId: '',
  171 + departmentName: '',
  172 + remark: ''
  173 + }
  174 + this.$nextTick(() => {
  175 + if (this.$refs.form) {
  176 + this.$refs.form.clearValidate()
  177 + }
  178 + })
  179 + },
  180 +
  181 + // 提交表单
  182 + submitForm() {
  183 + this.$refs.form.validate((valid) => {
  184 + if (valid) {
  185 + this.submitLoading = true
  186 + const apiMethod = this.isEdit ? updateInventory : createInventory
  187 + apiMethod(this.form).then(response => {
  188 + if (response.code === 200) {
  189 + this.$message.success(this.isEdit ? '更新成功' : '创建成功')
  190 + this.closeDialog()
  191 + this.$emit('refreshDataList')
  192 + } else {
  193 + this.$message.error(response.msg || (this.isEdit ? '更新失败' : '创建失败'))
  194 + }
  195 + this.submitLoading = false
  196 + }).catch(() => {
  197 + this.submitLoading = false
  198 + })
  199 + }
  200 + })
  201 + },
  202 +
  203 + // 关闭弹窗
  204 + closeDialog() {
  205 + this.visible = false
  206 + this.resetForm()
  207 + }
  208 + }
  209 +}
  210 +</script>
  211 +
  212 +<style scoped>
  213 +.dialog-footer {
  214 + text-align: right;
  215 +}
  216 +</style>
... ...
antis-ncc-admin/src/views/lqInventory/InventoryInfoDialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="库存详情" :visible.sync="visible" width="800px" @close="closeDialog">
  3 + <div class="inventory-info-container">
  4 + <el-row :gutter="20">
  5 + <el-col :span="12">
  6 + <div class="info-item">
  7 + <label>产品名称:</label>
  8 + <span>{{ inventoryInfo.productName || '无' }}</span>
  9 + </div>
  10 + </el-col>
  11 + <el-col :span="12">
  12 + <div class="info-item">
  13 + <label>产品分类:</label>
  14 + <span>{{ inventoryInfo.productCategory || '无' }}</span>
  15 + </div>
  16 + </el-col>
  17 + </el-row>
  18 +
  19 + <el-row :gutter="20">
  20 + <el-col :span="12">
  21 + <div class="info-item">
  22 + <label>单价:</label>
  23 + <span class="price">{{ formatMoney(inventoryInfo.price) }}</span>
  24 + </div>
  25 + </el-col>
  26 + <el-col :span="12">
  27 + <div class="info-item">
  28 + <label>库存数量:</label>
  29 + <span :class="inventoryInfo.quantity <= 10 ? 'text-danger' : ''">{{ inventoryInfo.quantity || 0
  30 + }}</span>
  31 + </div>
  32 + </el-col>
  33 + </el-row>
  34 +
  35 + <el-row :gutter="20">
  36 + <el-col :span="12">
  37 + <div class="info-item">
  38 + <label>标准单位:</label>
  39 + <span>{{ inventoryInfo.standardUnit || '无' }}</span>
  40 + </div>
  41 + </el-col>
  42 + <el-col :span="12">
  43 + <div class="info-item">
  44 + <label>总价值:</label>
  45 + <span class="price">{{ formatMoney(inventoryInfo.totalValue) }}</span>
  46 + </div>
  47 + </el-col>
  48 + </el-row>
  49 +
  50 + <el-row :gutter="20">
  51 + <el-col :span="12">
  52 + <div class="info-item">
  53 + <label>负责部门:</label>
  54 + <span>{{ inventoryInfo.departmentName || '无' }}</span>
  55 + </div>
  56 + </el-col>
  57 + <el-col :span="12">
  58 + <div class="info-item">
  59 + <label>状态:</label>
  60 + <el-tag :type="inventoryInfo.isEffective === 1 ? 'success' : 'danger'">
  61 + {{ inventoryInfo.isEffective === 1 ? '有效' : '无效' }}
  62 + </el-tag>
  63 + </div>
  64 + </el-col>
  65 + </el-row>
  66 +
  67 + <el-row :gutter="20">
  68 + <el-col :span="12">
  69 + <div class="info-item">
  70 + <label>创建人:</label>
  71 + <span>{{ inventoryInfo.createUserName || '无' }}</span>
  72 + </div>
  73 + </el-col>
  74 + <el-col :span="12">
  75 + <div class="info-item">
  76 + <label>创建时间:</label>
  77 + <span>{{ inventoryInfo.createTime | dateTimeFormat }}</span>
  78 + </div>
  79 + </el-col>
  80 + </el-row>
  81 +
  82 + <el-row :gutter="20" v-if="inventoryInfo.updateTime">
  83 + <el-col :span="12">
  84 + <div class="info-item">
  85 + <label>更新人:</label>
  86 + <span>{{ inventoryInfo.updateUserName || '无' }}</span>
  87 + </div>
  88 + </el-col>
  89 + <el-col :span="12">
  90 + <div class="info-item">
  91 + <label>更新时间:</label>
  92 + <span>{{ inventoryInfo.updateTime | dateTimeFormat }}</span>
  93 + </div>
  94 + </el-col>
  95 + </el-row>
  96 +
  97 + <el-row v-if="inventoryInfo.remark">
  98 + <el-col :span="24">
  99 + <div class="info-item">
  100 + <label>备注:</label>
  101 + <span>{{ inventoryInfo.remark }}</span>
  102 + </div>
  103 + </el-col>
  104 + </el-row>
  105 + </div>
  106 +
  107 + <div slot="footer" class="dialog-footer">
  108 + <el-button @click="closeDialog">关闭</el-button>
  109 + </div>
  110 + </el-dialog>
  111 +</template>
  112 +
  113 +<script>
  114 +import { getInventoryInfo } from '@/api/extend/lqInventory'
  115 +
  116 +export default {
  117 + name: 'InventoryInfoDialog',
  118 + data() {
  119 + return {
  120 + visible: false,
  121 + inventoryInfo: {}
  122 + }
  123 + },
  124 + methods: {
  125 + init(id) {
  126 + this.visible = true
  127 + this.getInventoryInfo(id)
  128 + },
  129 +
  130 + // 获取库存详情
  131 + getInventoryInfo(id) {
  132 + getInventoryInfo(id).then(response => {
  133 + if (response.code === 200) {
  134 + this.inventoryInfo = response.data
  135 + } else {
  136 + this.$message.error(response.msg || '获取库存详情失败')
  137 + }
  138 + })
  139 + },
  140 +
  141 + // 格式化金额
  142 + formatMoney(value) {
  143 + if (value === null || value === undefined || value === '') {
  144 + return '0.00'
  145 + }
  146 + return Number(value).toFixed(2)
  147 + },
  148 +
  149 + // 关闭弹窗
  150 + closeDialog() {
  151 + this.visible = false
  152 + this.inventoryInfo = {}
  153 + }
  154 + }
  155 +}
  156 +</script>
  157 +
  158 +<style scoped>
  159 +.inventory-info-container {
  160 + padding: 20px;
  161 +}
  162 +
  163 +.info-item {
  164 + margin-bottom: 20px;
  165 + display: flex;
  166 + align-items: center;
  167 +}
  168 +
  169 +.info-item label {
  170 + font-weight: bold;
  171 + color: #606266;
  172 + min-width: 100px;
  173 + margin-right: 10px;
  174 +}
  175 +
  176 +.info-item span {
  177 + color: #303133;
  178 +}
  179 +
  180 +.price {
  181 + color: #e6a23c;
  182 + font-weight: bold;
  183 +}
  184 +
  185 +.text-danger {
  186 + color: #f56c6c;
  187 + font-weight: bold;
  188 +}
  189 +
  190 +.dialog-footer {
  191 + text-align: right;
  192 +}
  193 +</style>
... ...
antis-ncc-admin/src/views/lqInventory/UsageRecordDialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="库存使用记录管理" :visible.sync="visible" width="1400px" @close="closeDialog">
  3 + <div class="usage-record-container">
  4 + <!-- 搜索区域 -->
  5 + <div class="search-section">
  6 + <el-form :model="query" :inline="true" class="search-form">
  7 + <el-form-item label="产品名称">
  8 + <el-input v-model="query.productName" placeholder="请输入产品名称" clearable size="small" />
  9 + </el-form-item>
  10 + <el-form-item label="产品分类">
  11 + <el-input v-model="query.productCategory" placeholder="请输入产品分类" clearable size="small" />
  12 + </el-form-item>
  13 + <el-form-item label="门店">
  14 + <el-input v-model="query.storeName" placeholder="请输入门店名称" clearable size="small" />
  15 + </el-form-item>
  16 + <el-form-item label="使用时间">
  17 + <el-date-picker v-model="query.usageTimeRange" type="daterange" range-separator="至"
  18 + start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" size="small">
  19 + </el-date-picker>
  20 + </el-form-item>
  21 + <el-form-item>
  22 + <el-button type="primary" size="small" @click="search">搜索</el-button>
  23 + <el-button size="small" @click="reset">重置</el-button>
  24 + <el-button type="success" size="small" icon="el-icon-plus"
  25 + @click="addUsageRecord">添加使用记录</el-button>
  26 + </el-form-item>
  27 + </el-form>
  28 + </div>
  29 +
  30 + <!-- 使用记录列表 -->
  31 + <div class="table-section">
  32 + <el-table :data="usageList" v-loading="loading" border stripe style="width: 100%">
  33 + <el-table-column prop="productName" label="产品名称" width="150" fixed="left" />
  34 + <el-table-column prop="productCategory" label="产品分类" width="120" />
  35 + <el-table-column prop="productPrice" label="单价" width="100" align="right">
  36 + <template slot-scope="scope">
  37 + {{ formatMoney(scope.row.productPrice) }}
  38 + </template>
  39 + </el-table-column>
  40 + <el-table-column prop="usageQuantity" label="使用数量" width="100" align="right" />
  41 + <el-table-column prop="storeName" label="使用门店" width="120" />
  42 + <el-table-column prop="usageTime" label="使用时间" width="120">
  43 + <template slot-scope="scope">
  44 + {{ scope.row.usageTime | dateFormat }}
  45 + </template>
  46 + </el-table-column>
  47 + <el-table-column prop="relatedConsumeId" label="关联消费ID" width="120" />
  48 + <el-table-column prop="createUserName" label="创建人" width="100" />
  49 + <el-table-column prop="createTime" label="创建时间" width="160">
  50 + <template slot-scope="scope">
  51 + {{ scope.row.createTime | dateTimeFormat }}
  52 + </template>
  53 + </el-table-column>
  54 + <el-table-column prop="isEffective" label="状态" width="80" align="center">
  55 + <template slot-scope="scope">
  56 + <el-tag :type="scope.row.isEffective === 1 ? 'success' : 'danger'" size="small">
  57 + {{ scope.row.isEffective === 1 ? '有效' : '无效' }}
  58 + </el-tag>
  59 + </template>
  60 + </el-table-column>
  61 + <el-table-column label="操作" width="120" fixed="right">
  62 + <template slot-scope="scope">
  63 + <el-button v-if="scope.row.isEffective === 1" type="text" size="small"
  64 + @click="cancelUsageRecord(scope.row)">作废</el-button>
  65 + </template>
  66 + </el-table-column>
  67 + </el-table>
  68 +
  69 + <!-- 分页 -->
  70 + <div class="pagination-section">
  71 + <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
  72 + :current-page="pagination.pageIndex" :page-sizes="[10, 20, 50, 100]"
  73 + :page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper"
  74 + :total="pagination.total">
  75 + </el-pagination>
  76 + </div>
  77 + </div>
  78 + </div>
  79 +
  80 + <!-- 添加使用记录弹窗 -->
  81 + <AddUsageRecordForm v-if="addUsageRecordVisible" ref="AddUsageRecordForm" @refreshDataList="getUsageList" />
  82 +
  83 + <div slot="footer" class="dialog-footer">
  84 + <el-button @click="closeDialog">关闭</el-button>
  85 + </div>
  86 + </el-dialog>
  87 +</template>
  88 +
  89 +<script>
  90 +import { getInventoryUsageList, cancelInventoryUsage } from '@/api/extend/lqInventory'
  91 +import AddUsageRecordForm from './AddUsageRecordForm'
  92 +
  93 +export default {
  94 + name: 'UsageRecordDialog',
  95 + components: {
  96 + AddUsageRecordForm
  97 + },
  98 + data() {
  99 + return {
  100 + visible: false,
  101 + loading: false,
  102 + productId: '', // 特定产品ID,为空时显示所有
  103 + productName: '', // 特定产品名称
  104 + usageList: [],
  105 + query: {
  106 + productName: '',
  107 + productCategory: '',
  108 + storeName: '',
  109 + usageTimeRange: [],
  110 + productId: '',
  111 + storeId: '',
  112 + relatedConsumeId: '',
  113 + currentPage: 1,
  114 + pageSize: 20
  115 + },
  116 + pagination: {
  117 + pageIndex: 1,
  118 + pageSize: 20,
  119 + total: 0
  120 + },
  121 + addUsageRecordVisible: false
  122 + }
  123 + },
  124 + methods: {
  125 + init(productId = '', productName = '') {
  126 + this.visible = true
  127 + this.productId = productId
  128 + this.productName = productName
  129 + this.query.productId = productId
  130 + if (productName) {
  131 + this.query.productName = productName
  132 + }
  133 + this.getUsageList()
  134 + },
  135 +
  136 + // 获取使用记录列表
  137 + getUsageList() {
  138 + this.loading = true
  139 + const params = {
  140 + ...this.query,
  141 + currentPage: this.pagination.pageIndex,
  142 + pageSize: this.pagination.pageSize
  143 + }
  144 +
  145 + // 处理日期范围
  146 + if (this.query.usageTimeRange && this.query.usageTimeRange.length === 2) {
  147 + params.usageStartTime = this.query.usageTimeRange[0]
  148 + params.usageEndTime = this.query.usageTimeRange[1]
  149 + }
  150 +
  151 + getInventoryUsageList(params).then(response => {
  152 + if (response.code === 200) {
  153 + this.usageList = response.data.list || []
  154 + this.pagination.total = (response.data.pagination && response.data.pagination.total) || 0
  155 + } else {
  156 + this.$message.error(response.msg || '获取使用记录失败')
  157 + }
  158 + this.loading = false
  159 + }).catch(() => {
  160 + this.loading = false
  161 + })
  162 + },
  163 +
  164 + // 搜索
  165 + search() {
  166 + this.pagination.pageIndex = 1
  167 + this.getUsageList()
  168 + },
  169 +
  170 + // 重置
  171 + reset() {
  172 + this.query = {
  173 + productName: this.productName || '',
  174 + productCategory: '',
  175 + storeName: '',
  176 + usageTimeRange: [],
  177 + productId: this.productId,
  178 + storeId: '',
  179 + relatedConsumeId: '',
  180 + currentPage: 1,
  181 + pageSize: 20
  182 + }
  183 + this.pagination.pageIndex = 1
  184 + this.getUsageList()
  185 + },
  186 +
  187 + // 分页大小改变
  188 + handleSizeChange(val) {
  189 + this.pagination.pageSize = val
  190 + this.pagination.pageIndex = 1
  191 + this.getUsageList()
  192 + },
  193 +
  194 + // 当前页改变
  195 + handleCurrentChange(val) {
  196 + this.pagination.pageIndex = val
  197 + this.getUsageList()
  198 + },
  199 +
  200 + // 添加使用记录
  201 + addUsageRecord() {
  202 + this.addUsageRecordVisible = true
  203 + this.$nextTick(() => {
  204 + this.$refs.AddUsageRecordForm.init(this.productId, this.productName)
  205 + })
  206 + },
  207 +
  208 + // 作废使用记录
  209 + cancelUsageRecord(row) {
  210 + this.$confirm('确定要作废这条使用记录吗?作废后库存数量将恢复。', '提示', {
  211 + confirmButtonText: '确定',
  212 + cancelButtonText: '取消',
  213 + type: 'warning'
  214 + }).then(() => {
  215 + cancelInventoryUsage(row.Id).then(response => {
  216 + if (response.code === 200) {
  217 + this.$message.success('作废成功')
  218 + this.getUsageList()
  219 + } else {
  220 + this.$message.error(response.msg || '作废失败')
  221 + }
  222 + })
  223 + })
  224 + },
  225 +
  226 + // 格式化金额
  227 + formatMoney(value) {
  228 + if (value === null || value === undefined || value === '') {
  229 + return '0.00'
  230 + }
  231 + return Number(value).toFixed(2)
  232 + },
  233 +
  234 + // 关闭弹窗
  235 + closeDialog() {
  236 + this.visible = false
  237 + this.usageList = []
  238 + this.query = {
  239 + productName: '',
  240 + productCategory: '',
  241 + storeName: '',
  242 + usageTimeRange: [],
  243 + productId: '',
  244 + storeId: '',
  245 + relatedConsumeId: '',
  246 + currentPage: 1,
  247 + pageSize: 20
  248 + }
  249 + this.pagination = {
  250 + pageIndex: 1,
  251 + pageSize: 20,
  252 + total: 0
  253 + }
  254 + }
  255 + }
  256 +}
  257 +</script>
  258 +
  259 +<style scoped>
  260 +.usage-record-container {
  261 + padding: 20px;
  262 +}
  263 +
  264 +.search-section {
  265 + margin-bottom: 20px;
  266 + padding: 15px;
  267 + background: #f5f7fa;
  268 + border-radius: 4px;
  269 +}
  270 +
  271 +.search-form .el-form-item {
  272 + margin-bottom: 10px;
  273 +}
  274 +
  275 +.table-section {
  276 + margin-bottom: 20px;
  277 +}
  278 +
  279 +.pagination-section {
  280 + text-align: right;
  281 + margin-top: 20px;
  282 +}
  283 +
  284 +.dialog-footer {
  285 + text-align: right;
  286 +}
  287 +</style>
... ...
antis-ncc-admin/src/views/lqInventory/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 搜索区域 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="产品名称">
  9 + <el-input v-model="query.productName" placeholder="请输入产品名称" clearable />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="6">
  13 + <el-form-item label="产品分类">
  14 + <el-input v-model="query.productCategory" placeholder="请输入产品分类" clearable />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="6">
  18 + <el-form-item label="负责部门">
  19 + <el-select v-model="query.departmentId" placeholder="请选择负责部门" clearable style="width: 100%">
  20 + <el-option v-for="dept in departmentList" :key="dept.id" :label="dept.name"
  21 + :value="dept.id">
  22 + </el-option>
  23 + </el-select>
  24 + </el-form-item>
  25 + </el-col>
  26 + <el-col :span="6">
  27 + <el-form-item>
  28 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  29 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  30 + </el-form-item>
  31 + </el-col>
  32 + </el-form>
  33 + </el-row>
  34 +
  35 + <!-- 主要内容区域 -->
  36 + <div class="NCC-common-layout-main NCC-flex-main">
  37 + <!-- 操作按钮区域 -->
  38 + <div class="NCC-common-head">
  39 + <div>
  40 + <el-button type="primary" icon="el-icon-plus" @click="addInventoryHandle()">添加库存</el-button>
  41 + <el-button type="success" icon="el-icon-edit" @click="editInventoryHandle()"
  42 + :disabled="selectedIds.length !== 1">编辑库存</el-button>
  43 + <el-button type="warning" icon="el-icon-s-operation"
  44 + @click="usageRecordHandle()">使用记录</el-button>
  45 + <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button>
  46 + </div>
  47 + <div class="NCC-common-head-right">
  48 + <el-tooltip effect="dark" content="刷新" placement="top">
  49 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  50 + @click="reset()" />
  51 + </el-tooltip>
  52 + <screenfull isContainer />
  53 + </div>
  54 + </div>
  55 +
  56 + <!-- 库存列表表格 -->
  57 + <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
  58 + <el-table-column prop="productName" label="产品名称" align="left" show-overflow-tooltip />
  59 + <el-table-column prop="productCategory" label="产品分类" align="left" width="120" />
  60 + <el-table-column prop="price" label="单价" align="right" width="100">
  61 + <template slot-scope="scope">
  62 + {{ formatMoney(scope.row.price) }}
  63 + </template>
  64 + </el-table-column>
  65 + <el-table-column prop="quantity" label="库存数量" align="right" width="100">
  66 + <template slot-scope="scope">
  67 + <span :class="scope.row.quantity <= 10 ? 'text-danger' : ''">{{ scope.row.quantity }}</span>
  68 + </template>
  69 + </el-table-column>
  70 + <el-table-column prop="standardUnit" label="单位" align="center" width="80" />
  71 + <el-table-column prop="totalValue" label="总价值" align="right" width="120">
  72 + <template slot-scope="scope">
  73 + {{ formatMoney(scope.row.totalValue) }}
  74 + </template>
  75 + </el-table-column>
  76 + <el-table-column prop="departmentName" label="负责部门" align="left" width="120" />
  77 + <el-table-column prop="createTime" label="创建时间" align="center" width="160">
  78 + <template slot-scope="scope">
  79 + {{ scope.row.createTime | dateTimeFormat }}
  80 + </template>
  81 + </el-table-column>
  82 + <el-table-column label="操作" align="center" width="200" fixed="right">
  83 + <template slot-scope="scope">
  84 + <el-button type="text" @click="viewInventoryInfo(scope.row)">查看详情</el-button>
  85 + <el-button type="text" @click="editInventory(scope.row)">编辑</el-button>
  86 + <el-button type="text" @click="viewUsageRecords(scope.row)">使用记录</el-button>
  87 + </template>
  88 + </el-table-column>
  89 + </NCC-table>
  90 +
  91 + <!-- 分页组件 -->
  92 + <pagination v-show="total > 0" :total="total" :page.sync="query.currentPage"
  93 + :limit.sync="query.pageSize" @pagination="getList" />
  94 + </div>
  95 + </div>
  96 +
  97 + <!-- 添加/编辑库存弹窗 -->
  98 + <InventoryForm v-if="inventoryFormVisible" ref="InventoryForm" @refreshDataList="getList" />
  99 +
  100 + <!-- 库存详情弹窗 -->
  101 + <InventoryInfoDialog v-if="inventoryInfoVisible" ref="InventoryInfoDialog" />
  102 +
  103 + <!-- 使用记录弹窗 -->
  104 + <UsageRecordDialog v-if="usageRecordVisible" ref="UsageRecordDialog" />
  105 + </div>
  106 +</template>
  107 +
  108 +<script>
  109 +import { getInventoryList } from '@/api/extend/lqInventory'
  110 +import { getUserList } from '@/api/permission/user'
  111 +import InventoryForm from './InventoryForm'
  112 +import InventoryInfoDialog from './InventoryInfoDialog'
  113 +import UsageRecordDialog from './UsageRecordDialog'
  114 +import Pagination from '@/components/Pagination'
  115 +
  116 +export default {
  117 + name: 'LqInventory',
  118 + components: {
  119 + InventoryForm,
  120 + InventoryInfoDialog,
  121 + UsageRecordDialog,
  122 + Pagination
  123 + },
  124 + data() {
  125 + return {
  126 + query: {
  127 + productName: '',
  128 + productCategory: '',
  129 + departmentId: '',
  130 + currentPage: 1,
  131 + pageSize: 20
  132 + },
  133 + list: [],
  134 + total: 0,
  135 + listLoading: true,
  136 + inventoryFormVisible: false,
  137 + inventoryInfoVisible: false,
  138 + usageRecordVisible: false,
  139 + selectedIds: [],
  140 + departmentList: [] // 部门列表
  141 + }
  142 + },
  143 + created() {
  144 + this.getDepartmentList()
  145 + this.getList()
  146 + },
  147 + methods: {
  148 + // 获取部门列表
  149 + getDepartmentList() {
  150 + const params = {
  151 + currentPage: 1,
  152 + pageSize: 1000, // 获取所有部门
  153 + enabledMark: 1 // 只获取启用的部门
  154 + }
  155 +
  156 + getUserList(params).then(response => {
  157 + if (response.code === 200) {
  158 + // 将用户列表转换为部门列表格式
  159 + this.departmentList = response.data.list.map(user => ({
  160 + id: user.id,
  161 + name: user.realName
  162 + }))
  163 + } else {
  164 + this.$message.error(response.msg || '获取部门列表失败')
  165 + }
  166 + }).catch(() => {
  167 + this.$message.error('获取部门列表失败')
  168 + })
  169 + },
  170 +
  171 + // 获取库存列表
  172 + getList() {
  173 + this.listLoading = true
  174 + getInventoryList(this.query).then(response => {
  175 + if (response.code === 200) {
  176 + this.list = response.data.list || []
  177 + this.total = (response.data.pagination && response.data.pagination.total) || 0
  178 + } else {
  179 + this.$message.error(response.msg || '获取库存列表失败')
  180 + }
  181 + this.listLoading = false
  182 + }).catch(() => {
  183 + this.listLoading = false
  184 + })
  185 + },
  186 +
  187 + // 搜索
  188 + search() {
  189 + this.query.currentPage = 1
  190 + this.getList()
  191 + },
  192 +
  193 + // 重置
  194 + reset() {
  195 + this.query = {
  196 + productName: '',
  197 + productCategory: '',
  198 + departmentId: '',
  199 + currentPage: 1,
  200 + pageSize: 20
  201 + }
  202 + this.getList()
  203 + },
  204 +
  205 + // 添加库存
  206 + addInventoryHandle() {
  207 + this.inventoryFormVisible = true
  208 + this.$nextTick(() => {
  209 + this.$refs.InventoryForm.init()
  210 + })
  211 + },
  212 +
  213 + // 编辑库存
  214 + editInventoryHandle() {
  215 + if (this.selectedIds.length === 0) {
  216 + this.$message.warning('请选择一个库存记录')
  217 + return
  218 + }
  219 + if (this.selectedIds.length > 1) {
  220 + this.$message.warning('请只选择一个库存记录')
  221 + return
  222 + }
  223 +
  224 + const selectedInventory = this.list.find(item => item.id === this.selectedIds[0])
  225 + if (selectedInventory) {
  226 + this.inventoryFormVisible = true
  227 + this.$nextTick(() => {
  228 + this.$refs.InventoryForm.init(selectedInventory.id)
  229 + })
  230 + }
  231 + },
  232 +
  233 + // 编辑库存(从操作列)
  234 + editInventory(row) {
  235 + this.inventoryFormVisible = true
  236 + this.$nextTick(() => {
  237 + this.$refs.InventoryForm.init(row.id)
  238 + })
  239 + },
  240 +
  241 + // 查看库存详情
  242 + viewInventoryInfo(row) {
  243 + this.inventoryInfoVisible = true
  244 + this.$nextTick(() => {
  245 + this.$refs.InventoryInfoDialog.init(row.id)
  246 + })
  247 + },
  248 +
  249 + // 使用记录管理
  250 + usageRecordHandle() {
  251 + this.usageRecordVisible = true
  252 + this.$nextTick(() => {
  253 + this.$refs.UsageRecordDialog.init()
  254 + })
  255 + },
  256 +
  257 + // 查看使用记录
  258 + viewUsageRecords(row) {
  259 + this.usageRecordVisible = true
  260 + this.$nextTick(() => {
  261 + this.$refs.UsageRecordDialog.init(row.id, row.productName)
  262 + })
  263 + },
  264 +
  265 + // 多选
  266 + handleSelectionChange(selection) {
  267 + this.selectedIds = selection.map(item => item.id)
  268 + },
  269 +
  270 + // 格式化金额
  271 + formatMoney(value) {
  272 + if (value === null || value === undefined || value === '') {
  273 + return '0.00'
  274 + }
  275 + return Number(value).toFixed(2)
  276 + },
  277 +
  278 + // 导出
  279 + exportData() {
  280 + this.$message.info('导出功能开发中...')
  281 + }
  282 + }
  283 +}
  284 +</script>
  285 +
  286 +<style scoped>
  287 +.NCC-common-layout {
  288 + height: 100%;
  289 +}
  290 +
  291 +.text-danger {
  292 + color: #f56c6c;
  293 + font-weight: bold;
  294 +}
  295 +</style>
... ...
antis-ncc-admin/src/views/lqStudyClass/AddStudentForm.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="添加学员" :visible.sync="visible" width="800px" @close="closeDialog">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="100px">
  4 + <el-row :gutter="20">
  5 + <el-col :span="12">
  6 + <el-form-item label="班级名称">
  7 + <el-input v-model="className" placeholder="班级名称" readonly />
  8 + </el-form-item>
  9 + </el-col>
  10 + <el-col :span="12">
  11 + <el-form-item label="班级ID">
  12 + <el-input v-model="form.classId" placeholder="班级ID" readonly />
  13 + </el-form-item>
  14 + </el-col>
  15 + </el-row>
  16 +
  17 + <!-- 学员信息 -->
  18 + <el-form-item label="学员信息">
  19 + <div class="student-section">
  20 + <div class="student-header">
  21 + <span>学员列表</span>
  22 + <el-button type="primary" size="small" icon="el-icon-plus" @click="addStudent">添加学员</el-button>
  23 + </div>
  24 + <el-table :data="form.students" border style="width: 100%; margin-top: 10px;">
  25 + <el-table-column prop="employeeId" label="选择学员" width="200">
  26 + <template slot-scope="scope">
  27 + <userSelect v-model="scope.row.employeeId" placeholder="请选择学员"
  28 + @change="(ids, users) => onStudentChange(scope.$index, ids, users)" size="small" />
  29 + </template>
  30 + </el-table-column>
  31 + <el-table-column prop="employeeName" label="学员姓名" width="120">
  32 + <template slot-scope="scope">
  33 + <el-input v-model="scope.row.employeeName" placeholder="学员姓名" size="small" readonly />
  34 + </template>
  35 + </el-table-column>
  36 + <el-table-column prop="employeePhone" label="手机号" width="140">
  37 + <template slot-scope="scope">
  38 + <el-input v-model="scope.row.employeePhone" placeholder="请输入手机号" size="small" />
  39 + </template>
  40 + </el-table-column>
  41 + <el-table-column prop="admissionTime" label="入学时间" width="140">
  42 + <template slot-scope="scope">
  43 + <el-date-picker v-model="scope.row.admissionTime" type="date" placeholder="选择时间"
  44 + value-format="yyyy-MM-dd" size="small" style="width: 100%">
  45 + </el-date-picker>
  46 + </template>
  47 + </el-table-column>
  48 + <el-table-column prop="hrBelong" label="HR归属" width="120">
  49 + <template slot-scope="scope">
  50 + <el-input v-model="scope.row.hrBelong" placeholder="请输入HR归属" size="small" />
  51 + </template>
  52 + </el-table-column>
  53 + <el-table-column label="操作" width="80">
  54 + <template slot-scope="scope">
  55 + <el-button type="text" size="small" @click="removeStudent(scope.$index)">删除</el-button>
  56 + </template>
  57 + </el-table-column>
  58 + </el-table>
  59 + </div>
  60 + </el-form-item>
  61 + </el-form>
  62 +
  63 + <div slot="footer" class="dialog-footer">
  64 + <el-button @click="closeDialog">取消</el-button>
  65 + <el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
  66 + </div>
  67 + </el-dialog>
  68 +</template>
  69 +
  70 +<script>
  71 +import { addStudentsToClass } from '@/api/extend/lqStudyClass'
  72 +
  73 +export default {
  74 + name: 'AddStudentForm',
  75 + data() {
  76 + return {
  77 + visible: false,
  78 + submitLoading: false,
  79 + className: '',
  80 + form: {
  81 + classId: '',
  82 + students: []
  83 + },
  84 + rules: {
  85 + classId: [
  86 + { required: true, message: '班级ID不能为空', trigger: 'blur' }
  87 + ]
  88 + }
  89 + }
  90 + },
  91 + methods: {
  92 + init(classId = '', className = '') {
  93 + this.visible = true
  94 + this.className = className
  95 + this.form.classId = classId
  96 + this.form.students = []
  97 + },
  98 +
  99 + // 学员选择变化
  100 + onStudentChange(index, ids, users) {
  101 + if (users && users.length > 0) {
  102 + const user = users[0]
  103 + this.form.students[index].employeeName = user.fullName
  104 + // 手机号需要用户手动填写,因为用户选择组件没有提供手机号信息
  105 + } else {
  106 + this.form.students[index].employeeName = ''
  107 + }
  108 + },
  109 +
  110 + // 删除学员
  111 + removeStudent(index) {
  112 + this.form.students.splice(index, 1)
  113 + },
  114 +
  115 + // 提交表单
  116 + submitForm() {
  117 + this.$refs.form.validate((valid) => {
  118 + if (valid) {
  119 + if (this.form.students.length === 0) {
  120 + this.$message.warning('请至少添加一名学员')
  121 + return
  122 + }
  123 +
  124 + this.submitLoading = true
  125 + addStudentsToClass(this.form).then(response => {
  126 + if (response.code === 200) {
  127 + this.$message.success(response.message || '添加成功')
  128 + this.closeDialog()
  129 + this.$emit('refreshDataList')
  130 + } else {
  131 + this.$message.error(response.msg || '添加失败')
  132 + }
  133 + this.submitLoading = false
  134 + }).catch(() => {
  135 + this.submitLoading = false
  136 + })
  137 + }
  138 + })
  139 + },
  140 +
  141 + // 关闭弹窗
  142 + closeDialog() {
  143 + this.visible = false
  144 + this.className = ''
  145 + this.form = {
  146 + classId: '',
  147 + students: []
  148 + }
  149 + }
  150 + }
  151 +}
  152 +</script>
  153 +
  154 +<style scoped>
  155 +.student-section {
  156 + width: 100%;
  157 +}
  158 +
  159 +.student-header {
  160 + display: flex;
  161 + justify-content: space-between;
  162 + align-items: center;
  163 + font-weight: 600;
  164 + color: #303133;
  165 +}
  166 +</style>
... ...
antis-ncc-admin/src/views/lqStudyClass/CreateClassForm.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="创建学习班级" :visible.sync="visible" width="800px" @close="closeDialog">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="100px">
  4 + <el-row :gutter="20">
  5 + <el-col :span="12">
  6 + <el-form-item label="班级名称" prop="className">
  7 + <el-input v-model="form.className" placeholder="请输入班级名称" />
  8 + </el-form-item>
  9 + </el-col>
  10 + <el-col :span="12">
  11 + <el-form-item label="授课老师" prop="teacherId">
  12 + <userSelect v-model="form.teacherId" placeholder="请选择授课老师" @change="onTeacherChange" />
  13 + </el-form-item>
  14 + </el-col>
  15 + </el-row>
  16 +
  17 + <el-row :gutter="20">
  18 + <el-col :span="12">
  19 + <el-form-item label="开始时间" prop="startTime">
  20 + <el-date-picker v-model="form.startTime" type="datetime" placeholder="选择开始时间"
  21 + value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%">
  22 + </el-date-picker>
  23 + </el-form-item>
  24 + </el-col>
  25 + <el-col :span="12">
  26 + <el-form-item label="结束时间" prop="endTime">
  27 + <el-date-picker v-model="form.endTime" type="datetime" placeholder="选择结束时间"
  28 + value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%">
  29 + </el-date-picker>
  30 + </el-form-item>
  31 + </el-col>
  32 + </el-row>
  33 +
  34 + <el-form-item label="备注">
  35 + <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
  36 + </el-form-item>
  37 +
  38 + <!-- 学员信息 -->
  39 + <el-form-item label="学员信息">
  40 + <div class="student-section">
  41 + <div class="student-header">
  42 + <span>学员列表</span>
  43 + <el-button type="primary" size="small" icon="el-icon-plus" @click="addStudent">添加学员</el-button>
  44 + </div>
  45 + <el-table :data="form.students" border style="width: 100%; margin-top: 10px;">
  46 + <el-table-column prop="employeeId" label="选择学员" width="200">
  47 + <template slot-scope="scope">
  48 + <userSelect v-model="scope.row.employeeId" placeholder="请选择学员"
  49 + @change="(ids, users) => onStudentChange(scope.$index, ids, users)" size="small" />
  50 + </template>
  51 + </el-table-column>
  52 + <el-table-column prop="employeeName" label="学员姓名" width="120">
  53 + <template slot-scope="scope">
  54 + <el-input v-model="scope.row.employeeName" placeholder="学员姓名" size="small" readonly />
  55 + </template>
  56 + </el-table-column>
  57 + <el-table-column prop="employeePhone" label="手机号" width="140">
  58 + <template slot-scope="scope">
  59 + <el-input v-model="scope.row.employeePhone" placeholder="请输入手机号" size="small" />
  60 + </template>
  61 + </el-table-column>
  62 + <el-table-column prop="admissionTime" label="入学时间" width="140">
  63 + <template slot-scope="scope">
  64 + <el-date-picker v-model="scope.row.admissionTime" type="date" placeholder="选择时间"
  65 + value-format="yyyy-MM-dd" size="small" style="width: 100%">
  66 + </el-date-picker>
  67 + </template>
  68 + </el-table-column>
  69 + <el-table-column prop="hrBelong" label="HR归属" width="120">
  70 + <template slot-scope="scope">
  71 + <el-input v-model="scope.row.hrBelong" placeholder="请输入HR归属" size="small" />
  72 + </template>
  73 + </el-table-column>
  74 + <el-table-column label="操作" width="80">
  75 + <template slot-scope="scope">
  76 + <el-button type="text" size="small" @click="removeStudent(scope.$index)">删除</el-button>
  77 + </template>
  78 + </el-table-column>
  79 + </el-table>
  80 + </div>
  81 + </el-form-item>
  82 + </el-form>
  83 +
  84 + <div slot="footer" class="dialog-footer">
  85 + <el-button @click="closeDialog">取消</el-button>
  86 + <el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
  87 + </div>
  88 + </el-dialog>
  89 +</template>
  90 +
  91 +<script>
  92 +import { createClassWithStudents } from '@/api/extend/lqStudyClass'
  93 +
  94 +export default {
  95 + name: 'CreateClassForm',
  96 + data() {
  97 + return {
  98 + visible: false,
  99 + submitLoading: false,
  100 + form: {
  101 + className: '',
  102 + teacherId: '',
  103 + teacherName: '',
  104 + startTime: '',
  105 + endTime: '',
  106 + remark: '',
  107 + students: []
  108 + },
  109 + rules: {
  110 + className: [
  111 + { required: true, message: '请输入班级名称', trigger: 'blur' }
  112 + ],
  113 + teacherId: [
  114 + { required: true, message: '请选择授课老师', trigger: 'change' }
  115 + ],
  116 + startTime: [
  117 + { required: true, message: '请选择开始时间', trigger: 'change' }
  118 + ],
  119 + endTime: [
  120 + { required: true, message: '请选择结束时间', trigger: 'change' }
  121 + ]
  122 + }
  123 + }
  124 + },
  125 + methods: {
  126 + init() {
  127 + this.visible = true
  128 + this.resetForm()
  129 + },
  130 +
  131 + resetForm() {
  132 + this.form = {
  133 + className: '',
  134 + teacherId: '',
  135 + teacherName: '',
  136 + startTime: '',
  137 + endTime: '',
  138 + remark: '',
  139 + students: []
  140 + }
  141 + this.$nextTick(() => {
  142 + if (this.$refs.form) {
  143 + this.$refs.form.clearValidate()
  144 + }
  145 + })
  146 + },
  147 +
  148 + // 老师选择变化
  149 + onTeacherChange(ids, users) {
  150 + if (users && users.length > 0) {
  151 + this.form.teacherName = users[0].fullName
  152 + } else {
  153 + this.form.teacherName = ''
  154 + }
  155 + },
  156 +
  157 + // 学员选择变化
  158 + onStudentChange(index, ids, users) {
  159 + if (users && users.length > 0) {
  160 + const user = users[0]
  161 + this.form.students[index].employeeName = user.fullName
  162 + // 手机号需要用户手动填写,因为用户选择组件没有提供手机号信息
  163 + } else {
  164 + this.form.students[index].employeeName = ''
  165 + }
  166 + },
  167 +
  168 + // 添加学员
  169 + addStudent() {
  170 + this.form.students.push({
  171 + employeeName: '',
  172 + employeePhone: '',
  173 + employeeId: '',
  174 + admissionTime: '',
  175 + hrBelong: ''
  176 + })
  177 + },
  178 +
  179 + // 提交表单
  180 + submitForm() {
  181 + this.$refs.form.validate((valid) => {
  182 + if (valid) {
  183 + if (this.form.students.length === 0) {
  184 + this.$message.warning('请至少添加一名学员')
  185 + return
  186 + }
  187 +
  188 + this.submitLoading = true
  189 + createClassWithStudents(this.form).then(response => {
  190 + if (response.code === 200) {
  191 + this.$message.success(response.message || '创建成功')
  192 + this.closeDialog()
  193 + this.$emit('refreshDataList')
  194 + } else {
  195 + this.$message.error(response.msg || '创建失败')
  196 + }
  197 + this.submitLoading = false
  198 + }).catch(() => {
  199 + this.submitLoading = false
  200 + })
  201 + }
  202 + })
  203 + },
  204 +
  205 + // 关闭弹窗
  206 + closeDialog() {
  207 + this.visible = false
  208 + this.resetForm()
  209 + }
  210 + }
  211 +}
  212 +</script>
  213 +
  214 +<style scoped>
  215 +.student-section {
  216 + width: 100%;
  217 +}
  218 +
  219 +.student-header {
  220 + display: flex;
  221 + justify-content: space-between;
  222 + align-items: center;
  223 + font-weight: 600;
  224 + color: #303133;
  225 +}
  226 +</style>
... ...
antis-ncc-admin/src/views/lqStudyClass/StudentListDialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="班级学员列表" :visible.sync="visible" width="1000px" @close="closeDialog">
  3 + <div class="student-list-container">
  4 + <!-- 搜索区域 -->
  5 + <div class="search-section">
  6 + <el-form :model="query" :inline="true" class="search-form">
  7 + <el-form-item label="学员姓名">
  8 + <el-input v-model="query.employeeName" placeholder="请输入学员姓名" clearable size="small" />
  9 + </el-form-item>
  10 + <el-form-item label="手机号">
  11 + <el-input v-model="query.employeePhone" placeholder="请输入手机号" clearable size="small" />
  12 + </el-form-item>
  13 + <el-form-item label="员工ID">
  14 + <el-input v-model="query.employeeId" placeholder="请输入员工ID" clearable size="small" />
  15 + </el-form-item>
  16 + <el-form-item label="HR归属">
  17 + <el-input v-model="query.hrBelong" placeholder="请输入HR归属" clearable size="small" />
  18 + </el-form-item>
  19 + <el-form-item>
  20 + <el-button type="primary" size="small" @click="search">搜索</el-button>
  21 + <el-button size="small" @click="reset">重置</el-button>
  22 + </el-form-item>
  23 + </el-form>
  24 + </div>
  25 +
  26 + <!-- 学员列表 -->
  27 + <div class="table-section">
  28 + <el-table :data="studentList" v-loading="loading" border stripe style="width: 100%">
  29 + <el-table-column prop="employeeName" label="学员姓名" width="120" fixed="left" />
  30 + <el-table-column prop="employeePhone" label="手机号" width="140" />
  31 + <el-table-column prop="employeeId" label="员工ID" width="120" />
  32 + <el-table-column prop="admissionTime" label="入学时间" width="120">
  33 + <template slot-scope="scope">
  34 + {{ scope.row.admissionTime | dateFormat }}
  35 + </template>
  36 + </el-table-column>
  37 + <el-table-column prop="className" label="班级名称" width="150" />
  38 + <el-table-column prop="hrBelong" label="HR归属" width="120" />
  39 + <el-table-column label="操作" width="120" fixed="right">
  40 + <template slot-scope="scope">
  41 + <el-button type="text" size="small" @click="viewStudyRecords(scope.row)">学习记录</el-button>
  42 + </template>
  43 + </el-table-column>
  44 + </el-table>
  45 +
  46 + <!-- 分页 -->
  47 + <div class="pagination-section">
  48 + <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
  49 + :current-page="pagination.pageIndex" :page-sizes="[10, 20, 50, 100]"
  50 + :page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper"
  51 + :total="pagination.total">
  52 + </el-pagination>
  53 + </div>
  54 + </div>
  55 + </div>
  56 +
  57 + <div slot="footer" class="dialog-footer">
  58 + <el-button @click="closeDialog">关闭</el-button>
  59 + </div>
  60 + </el-dialog>
  61 +</template>
  62 +
  63 +<script>
  64 +import { getStudentListByClassId } from '@/api/extend/lqStudyClass'
  65 +
  66 +export default {
  67 + name: 'StudentListDialog',
  68 + data() {
  69 + return {
  70 + visible: false,
  71 + loading: false,
  72 + classId: '',
  73 + className: '',
  74 + studentList: [],
  75 + query: {
  76 + employeeName: '',
  77 + employeePhone: '',
  78 + employeeId: '',
  79 + hrBelong: '',
  80 + classId: ''
  81 + },
  82 + pagination: {
  83 + pageIndex: 1,
  84 + pageSize: 20,
  85 + total: 0
  86 + }
  87 + }
  88 + },
  89 + methods: {
  90 + init(classId, className) {
  91 + this.visible = true
  92 + this.classId = classId
  93 + this.className = className
  94 + this.query.classId = classId
  95 + this.getStudentList()
  96 + },
  97 +
  98 + // 获取学员列表
  99 + getStudentList() {
  100 + this.loading = true
  101 + const params = {
  102 + ...this.query,
  103 + currentPage: this.pagination.pageIndex,
  104 + pageSize: this.pagination.pageSize
  105 + }
  106 +
  107 + getStudentListByClassId(params).then(response => {
  108 + if (response.code === 200) {
  109 + this.studentList = response.data.list || []
  110 + this.pagination.total = (response.data.pagination && response.data.pagination.total) || 0
  111 + } else {
  112 + this.$message.error(response.msg || '获取学员列表失败')
  113 + }
  114 + this.loading = false
  115 + }).catch(() => {
  116 + this.loading = false
  117 + })
  118 + },
  119 +
  120 + // 搜索
  121 + search() {
  122 + this.pagination.pageIndex = 1
  123 + this.getStudentList()
  124 + },
  125 +
  126 + // 重置
  127 + reset() {
  128 + this.query = {
  129 + employeeName: '',
  130 + employeePhone: '',
  131 + employeeId: '',
  132 + hrBelong: '',
  133 + classId: this.classId
  134 + }
  135 + this.pagination.pageIndex = 1
  136 + this.getStudentList()
  137 + },
  138 +
  139 + // 分页大小改变
  140 + handleSizeChange(val) {
  141 + this.pagination.pageSize = val
  142 + this.pagination.pageIndex = 1
  143 + this.getStudentList()
  144 + },
  145 +
  146 + // 当前页改变
  147 + handleCurrentChange(val) {
  148 + this.pagination.pageIndex = val
  149 + this.getStudentList()
  150 + },
  151 +
  152 + // 查看学习记录
  153 + viewStudyRecords(row) {
  154 + this.$emit('viewStudyRecords', row)
  155 + },
  156 +
  157 + // 关闭弹窗
  158 + closeDialog() {
  159 + this.visible = false
  160 + this.studentList = []
  161 + this.query = {
  162 + employeeName: '',
  163 + employeePhone: '',
  164 + employeeId: '',
  165 + hrBelong: '',
  166 + classId: ''
  167 + }
  168 + this.pagination = {
  169 + pageIndex: 1,
  170 + pageSize: 20,
  171 + total: 0
  172 + }
  173 + }
  174 + }
  175 +}
  176 +</script>
  177 +
  178 +<style scoped>
  179 +.student-list-container {
  180 + padding: 20px;
  181 +}
  182 +
  183 +.search-section {
  184 + margin-bottom: 20px;
  185 + padding: 15px;
  186 + background: #f5f7fa;
  187 + border-radius: 4px;
  188 +}
  189 +
  190 +.search-form .el-form-item {
  191 + margin-bottom: 10px;
  192 +}
  193 +
  194 +.table-section {
  195 + margin-bottom: 20px;
  196 +}
  197 +
  198 +.pagination-section {
  199 + text-align: right;
  200 + margin-top: 20px;
  201 +}
  202 +</style>
... ...
antis-ncc-admin/src/views/lqStudyClass/StudyRecordDialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="学习记录管理" :visible.sync="visible" width="1200px" @close="closeDialog">
  3 + <div class="study-record-container">
  4 + <!-- 搜索区域 -->
  5 + <div class="search-section">
  6 + <el-form :model="query" :inline="true" class="search-form">
  7 + <el-form-item label="学员姓名">
  8 + <el-input v-model="query.employeeName" placeholder="请输入学员姓名" clearable size="small" />
  9 + </el-form-item>
  10 + <el-form-item label="员工ID">
  11 + <el-input v-model="query.employeeId" placeholder="请输入员工ID" clearable size="small" />
  12 + </el-form-item>
  13 + <el-form-item label="学习类型">
  14 + <el-input v-model="query.studyType" placeholder="请输入学习类型" clearable size="small" />
  15 + </el-form-item>
  16 + <el-form-item label="学习日期">
  17 + <el-date-picker v-model="query.studyDateRange" type="daterange" range-separator="至"
  18 + start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" size="small">
  19 + </el-date-picker>
  20 + </el-form-item>
  21 + <el-form-item label="日常状态">
  22 + <el-input v-model="query.dailyStatus" placeholder="请输入日常状态" clearable size="small" />
  23 + </el-form-item>
  24 + <el-form-item>
  25 + <el-button type="primary" size="small" @click="search">搜索</el-button>
  26 + <el-button size="small" @click="reset">重置</el-button>
  27 + <el-button type="success" size="small" icon="el-icon-plus" @click="addRecord">添加记录</el-button>
  28 + </el-form-item>
  29 + </el-form>
  30 + </div>
  31 +
  32 + <!-- 学习记录列表 -->
  33 + <div class="table-section">
  34 + <el-table :data="recordList" v-loading="loading" border stripe style="width: 100%">
  35 + <el-table-column prop="employeeName" label="学员姓名" width="100" fixed="left" />
  36 + <el-table-column prop="employeeId" label="员工ID" width="120" />
  37 + <el-table-column prop="studyType" label="学习类型" width="120" />
  38 + <el-table-column prop="transportFee" label="交通费" width="100" align="center">
  39 + <template slot-scope="scope">
  40 + {{ formatMoney(scope.row.transportFee) }}
  41 + </template>
  42 + </el-table-column>
  43 + <el-table-column prop="studyDate" label="学习日期" width="120">
  44 + <template slot-scope="scope">
  45 + {{ scope.row.studyDate | dateFormat }}
  46 + </template>
  47 + </el-table-column>
  48 + <el-table-column prop="dailyStatus" label="日常状态" width="120" />
  49 + <el-table-column prop="remark" label="备注" show-overflow-tooltip />
  50 + <el-table-column prop="isStoreAssist" label="下店协助" width="100" align="center">
  51 + <template slot-scope="scope">
  52 + <el-tag :type="scope.row.isStoreAssist === 1 ? 'success' : 'info'" size="mini">
  53 + {{ scope.row.isStoreAssist === 1 ? '是' : '否' }}
  54 + </el-tag>
  55 + </template>
  56 + </el-table-column>
  57 + <el-table-column prop="storeName" label="协助门店" width="120" />
  58 + <el-table-column prop="createUserName" label="创建人" width="100" />
  59 + <el-table-column prop="createTime" label="创建时间" width="160">
  60 + <template slot-scope="scope">
  61 + {{ scope.row.createTime | dateTimeFormat }}
  62 + </template>
  63 + </el-table-column>
  64 + <el-table-column label="操作" width="120" fixed="right">
  65 + <template slot-scope="scope">
  66 + <el-button type="text" size="small" @click="cancelRecord(scope.row)">作废</el-button>
  67 + </template>
  68 + </el-table-column>
  69 + </el-table>
  70 +
  71 + <!-- 分页 -->
  72 + <div class="pagination-section">
  73 + <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
  74 + :current-page="pagination.pageIndex" :page-sizes="[10, 20, 50, 100]"
  75 + :page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper"
  76 + :total="pagination.total">
  77 + </el-pagination>
  78 + </div>
  79 + </div>
  80 + </div>
  81 +
  82 + <div slot="footer" class="dialog-footer">
  83 + <el-button @click="closeDialog">关闭</el-button>
  84 + </div>
  85 +
  86 + <!-- 添加学习记录弹窗 -->
  87 + <el-dialog title="添加学习记录" :visible.sync="addRecordVisible" width="600px" append-to-body>
  88 + <el-form ref="recordForm" :model="recordForm" :rules="recordRules" label-width="100px">
  89 + <el-form-item label="学员姓名" prop="employeeName">
  90 + <el-input v-model="recordForm.employeeName" placeholder="请输入学员姓名" />
  91 + </el-form-item>
  92 + <el-form-item label="员工ID" prop="employeeId">
  93 + <el-input v-model="recordForm.employeeId" placeholder="请输入员工ID" />
  94 + </el-form-item>
  95 + <el-form-item label="学习类型" prop="studyType">
  96 + <el-input v-model="recordForm.studyType" placeholder="请输入学习类型" />
  97 + </el-form-item>
  98 + <el-form-item label="交通费">
  99 + <el-input-number v-model="recordForm.transportFee" :min="0" :precision="2" style="width: 100%" />
  100 + </el-form-item>
  101 + <el-form-item label="学习日期" prop="studyDate">
  102 + <el-date-picker v-model="recordForm.studyDate" type="date" placeholder="选择学习日期"
  103 + value-format="yyyy-MM-dd" style="width: 100%">
  104 + </el-date-picker>
  105 + </el-form-item>
  106 + <el-form-item label="日常状态" prop="dailyStatus">
  107 + <el-input v-model="recordForm.dailyStatus" placeholder="请输入日常状态" />
  108 + </el-form-item>
  109 + <el-form-item label="是否下店协助">
  110 + <el-switch v-model="recordForm.isStoreAssist" :active-value="1" :inactive-value="0" />
  111 + </el-form-item>
  112 + <el-form-item v-if="recordForm.isStoreAssist === 1" label="门店ID" prop="storeId">
  113 + <el-input v-model="recordForm.storeId" placeholder="请输入门店ID" />
  114 + </el-form-item>
  115 + <el-form-item v-if="recordForm.isStoreAssist === 1" label="门店名称" prop="storeName">
  116 + <el-input v-model="recordForm.storeName" placeholder="请输入门店名称" />
  117 + </el-form-item>
  118 + <el-form-item label="备注">
  119 + <el-input v-model="recordForm.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
  120 + </el-form-item>
  121 + </el-form>
  122 + <div slot="footer" class="dialog-footer">
  123 + <el-button @click="addRecordVisible = false">取消</el-button>
  124 + <el-button type="primary" @click="submitRecord" :loading="submitLoading">确定</el-button>
  125 + </div>
  126 + </el-dialog>
  127 + </el-dialog>
  128 +</template>
  129 +
  130 +<script>
  131 +import { getStudyRecordList, addStudyRecord, cancelStudyRecord, getStudentListByClassId } from '@/api/extend/lqStudyClass'
  132 +
  133 +export default {
  134 + name: 'StudyRecordDialog',
  135 + data() {
  136 + return {
  137 + visible: false,
  138 + loading: false,
  139 + submitLoading: false,
  140 + addRecordVisible: false,
  141 + classId: '',
  142 + className: '',
  143 + studentIds: [], // 班级学员ID列表
  144 + recordList: [],
  145 + query: {
  146 + employeeName: '',
  147 + employeeId: '',
  148 + studyType: '',
  149 + studyDateStart: '',
  150 + studyDateEnd: '',
  151 + dailyStatus: '',
  152 + studyDateRange: []
  153 + },
  154 + pagination: {
  155 + pageIndex: 1,
  156 + pageSize: 20,
  157 + total: 0
  158 + },
  159 + recordForm: {
  160 + employeeName: '',
  161 + employeeId: '',
  162 + studyType: '',
  163 + transportFee: 0,
  164 + studyDate: '',
  165 + dailyStatus: '',
  166 + remark: '',
  167 + isStoreAssist: 0,
  168 + storeId: '',
  169 + storeName: ''
  170 + },
  171 + recordRules: {
  172 + employeeName: [
  173 + { required: true, message: '请输入学员姓名', trigger: 'blur' }
  174 + ],
  175 + employeeId: [
  176 + { required: true, message: '请输入员工ID', trigger: 'blur' }
  177 + ],
  178 + studyType: [
  179 + { required: true, message: '请输入学习类型', trigger: 'blur' }
  180 + ],
  181 + studyDate: [
  182 + { required: true, message: '请选择学习日期', trigger: 'change' }
  183 + ],
  184 + dailyStatus: [
  185 + { required: true, message: '请输入日常状态', trigger: 'blur' }
  186 + ]
  187 + }
  188 + }
  189 + },
  190 + methods: {
  191 + init(classId, className) {
  192 + this.visible = true
  193 + this.classId = classId
  194 + this.className = className
  195 + this.getStudentIds(classId)
  196 + },
  197 +
  198 + // 获取班级下的学员ID列表
  199 + getStudentIds(classId) {
  200 + const params = {
  201 + classId: classId,
  202 + currentPage: 1,
  203 + pageSize: 1000 // 获取所有学员
  204 + }
  205 +
  206 + getStudentListByClassId(params).then(response => {
  207 + if (response.code === 200) {
  208 + const students = response.data.list || []
  209 + this.studentIds = students.map(student => student.employeeId).filter(id => id)
  210 + this.getRecordList()
  211 + } else {
  212 + this.$message.error(response.msg || '获取学员列表失败')
  213 + this.loading = false
  214 + }
  215 + }).catch(() => {
  216 + this.loading = false
  217 + })
  218 + },
  219 + // 获取学习记录列表
  220 + getRecordList() {
  221 + this.loading = true
  222 + const params = {
  223 + ...this.query,
  224 + currentPage: this.pagination.pageIndex,
  225 + pageSize: this.pagination.pageSize
  226 + }
  227 +
  228 + // 处理日期范围
  229 + if (this.query.studyDateRange && this.query.studyDateRange.length === 2) {
  230 + params.studyDateStart = this.query.studyDateRange[0]
  231 + params.studyDateEnd = this.query.studyDateRange[1]
  232 + }
  233 +
  234 + getStudyRecordList(params).then(response => {
  235 + if (response.code === 200) {
  236 + let recordList = response.data.list || []
  237 + // 按班级学员ID过滤学习记录
  238 + if (this.studentIds && this.studentIds.length > 0) {
  239 + recordList = recordList.filter(record => this.studentIds.includes(record.employeeId))
  240 + }
  241 + this.recordList = recordList
  242 + this.pagination.total = recordList.length
  243 + } else {
  244 + this.$message.error(response.msg || '获取学习记录失败')
  245 + }
  246 + this.loading = false
  247 + }).catch(() => {
  248 + this.loading = false
  249 + })
  250 + },
  251 +
  252 + // 搜索
  253 + search() {
  254 + this.pagination.pageIndex = 1
  255 + this.getRecordList()
  256 + },
  257 +
  258 + // 重置
  259 + reset() {
  260 + this.query = {
  261 + employeeName: '',
  262 + employeeId: '',
  263 + studyType: '',
  264 + studyDateStart: '',
  265 + studyDateEnd: '',
  266 + dailyStatus: '',
  267 + studyDateRange: []
  268 + }
  269 + this.pagination.pageIndex = 1
  270 + this.getRecordList()
  271 + },
  272 +
  273 + // 分页大小改变
  274 + handleSizeChange(val) {
  275 + this.pagination.pageSize = val
  276 + this.pagination.pageIndex = 1
  277 + this.getRecordList()
  278 + },
  279 +
  280 + // 当前页改变
  281 + handleCurrentChange(val) {
  282 + this.pagination.pageIndex = val
  283 + this.getRecordList()
  284 + },
  285 +
  286 + // 添加记录
  287 + addRecord() {
  288 + this.addRecordVisible = true
  289 + this.resetRecordForm()
  290 + },
  291 +
  292 + // 重置记录表单
  293 + resetRecordForm() {
  294 + this.recordForm = {
  295 + employeeName: '',
  296 + employeeId: '',
  297 + studyType: '',
  298 + transportFee: 0,
  299 + studyDate: '',
  300 + dailyStatus: '',
  301 + remark: '',
  302 + isStoreAssist: 0,
  303 + storeId: '',
  304 + storeName: ''
  305 + }
  306 + this.$nextTick(() => {
  307 + if (this.$refs.recordForm) {
  308 + this.$refs.recordForm.clearValidate()
  309 + }
  310 + })
  311 + },
  312 +
  313 + // 提交记录
  314 + submitRecord() {
  315 + this.$refs.recordForm.validate((valid) => {
  316 + if (valid) {
  317 + this.submitLoading = true
  318 + addStudyRecord(this.recordForm).then(response => {
  319 + if (response.code === 200) {
  320 + this.$message.success('添加成功')
  321 + this.addRecordVisible = false
  322 + this.getRecordList()
  323 + } else {
  324 + this.$message.error(response.msg || '添加失败')
  325 + }
  326 + this.submitLoading = false
  327 + }).catch(() => {
  328 + this.submitLoading = false
  329 + })
  330 + }
  331 + })
  332 + },
  333 +
  334 + // 作废记录
  335 + cancelRecord(row) {
  336 + this.$confirm('确定要作废该学习记录吗?', '提示', {
  337 + confirmButtonText: '确定',
  338 + cancelButtonText: '取消',
  339 + type: 'warning'
  340 + }).then(() => {
  341 + cancelStudyRecord(row.id).then(response => {
  342 + if (response.code === 200) {
  343 + this.$message.success('作废成功')
  344 + this.getRecordList()
  345 + } else {
  346 + this.$message.error(response.msg || '作废失败')
  347 + }
  348 + })
  349 + })
  350 + },
  351 +
  352 + // 格式化金额
  353 + formatMoney(value) {
  354 + if (value === null || value === undefined || value === '') {
  355 + return '0.00'
  356 + }
  357 + return Number(value).toFixed(2)
  358 + },
  359 +
  360 + // 关闭弹窗
  361 + closeDialog() {
  362 + this.visible = false
  363 + this.recordList = []
  364 + this.query = {
  365 + employeeName: '',
  366 + employeeId: '',
  367 + studyType: '',
  368 + studyDateStart: '',
  369 + studyDateEnd: '',
  370 + dailyStatus: '',
  371 + studyDateRange: []
  372 + }
  373 + this.pagination = {
  374 + pageIndex: 1,
  375 + pageSize: 20,
  376 + total: 0
  377 + }
  378 + }
  379 + }
  380 +}
  381 +</script>
  382 +
  383 +<style scoped>
  384 +.study-record-container {
  385 + padding: 20px;
  386 +}
  387 +
  388 +.search-section {
  389 + margin-bottom: 20px;
  390 + padding: 15px;
  391 + background: #f5f7fa;
  392 + border-radius: 4px;
  393 +}
  394 +
  395 +.search-form .el-form-item {
  396 + margin-bottom: 10px;
  397 +}
  398 +
  399 +.table-section {
  400 + margin-bottom: 20px;
  401 +}
  402 +
  403 +.pagination-section {
  404 + text-align: right;
  405 + margin-top: 20px;
  406 +}
  407 +</style>
... ...
antis-ncc-admin/src/views/lqStudyClass/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 搜索区域 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="班级名称">
  9 + <el-input v-model="query.className" placeholder="请输入班级名称" clearable />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="6">
  13 + <el-form-item label="授课老师">
  14 + <userSelect v-model="query.teacherId" placeholder="请选择授课老师" clearable />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="6">
  18 + <el-form-item label="开始时间">
  19 + <el-date-picker v-model="query.startTime" type="date" placeholder="选择开始时间"
  20 + value-format="yyyy-MM-dd" clearable>
  21 + </el-date-picker>
  22 + </el-form-item>
  23 + </el-col>
  24 + <el-col :span="6">
  25 + <el-form-item>
  26 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  27 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  28 + </el-form-item>
  29 + </el-col>
  30 + </el-form>
  31 + </el-row>
  32 +
  33 + <!-- 主要内容区域 -->
  34 + <div class="NCC-common-layout-main NCC-flex-main">
  35 + <!-- 操作按钮区域 -->
  36 + <div class="NCC-common-head">
  37 + <div>
  38 + <el-button type="primary" icon="el-icon-plus" @click="addClassHandle()">创建班级</el-button>
  39 + <el-button type="success" icon="el-icon-user"
  40 + @click="addStudentHandle()">添加学员(需选择班级)</el-button>
  41 + <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button>
  42 + </div>
  43 + <div class="NCC-common-head-right">
  44 + <el-tooltip effect="dark" content="刷新" placement="top">
  45 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  46 + @click="reset()" />
  47 + </el-tooltip>
  48 + <screenfull isContainer />
  49 + </div>
  50 + </div>
  51 +
  52 + <!-- 班级列表表格 -->
  53 + <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
  54 + <el-table-column prop="className" label="班级名称" align="left" show-overflow-tooltip />
  55 + <el-table-column prop="teacherName" label="授课老师" align="left" show-overflow-tooltip />
  56 + <el-table-column prop="startTime" label="开始时间" align="center" width="120">
  57 + <template slot-scope="scope">
  58 + {{ scope.row.startTime | dateFormat }}
  59 + </template>
  60 + </el-table-column>
  61 + <el-table-column prop="endTime" label="结束时间" align="center" width="120">
  62 + <template slot-scope="scope">
  63 + {{ scope.row.endTime | dateFormat }}
  64 + </template>
  65 + </el-table-column>
  66 + <el-table-column prop="remark" label="备注" align="left" show-overflow-tooltip />
  67 + <el-table-column label="操作" align="center" width="200" fixed="right">
  68 + <template slot-scope="scope">
  69 + <el-button type="text" @click="viewStudents(scope.row)">查看学员</el-button>
  70 + <el-button type="text" @click="addStudentsToClass(scope.row)">添加学员</el-button>
  71 + <el-button type="text" @click="viewStudyRecords(scope.row)">学习记录</el-button>
  72 + </template>
  73 + </el-table-column>
  74 + </NCC-table>
  75 +
  76 + <!-- 分页组件 -->
  77 + <pagination v-show="total > 0" :total="total" :page.sync="query.currentPage"
  78 + :limit.sync="query.pageSize" @pagination="getList" />
  79 + </div>
  80 + </div>
  81 +
  82 + <!-- 创建班级弹窗 -->
  83 + <CreateClassForm v-if="createClassVisible" ref="CreateClassForm" @refreshDataList="getList" />
  84 +
  85 + <!-- 添加学员弹窗 -->
  86 + <AddStudentForm v-if="addStudentVisible" ref="AddStudentForm" @refreshDataList="getList" />
  87 +
  88 + <!-- 学员列表弹窗 -->
  89 + <StudentListDialog v-if="studentListVisible" ref="StudentListDialog"
  90 + @viewStudyRecords="handleViewStudyRecords" />
  91 +
  92 + <!-- 学习记录弹窗 -->
  93 + <StudyRecordDialog v-if="studyRecordVisible" ref="StudyRecordDialog" />
  94 + </div>
  95 +</template>
  96 +
  97 +<script>
  98 +import { getClassList } from '@/api/extend/lqStudyClass'
  99 +import CreateClassForm from './CreateClassForm'
  100 +import AddStudentForm from './AddStudentForm'
  101 +import StudentListDialog from './StudentListDialog'
  102 +import StudyRecordDialog from './StudyRecordDialog'
  103 +import Pagination from '@/components/Pagination'
  104 +
  105 +export default {
  106 + name: 'LqStudyClass',
  107 + components: {
  108 + CreateClassForm,
  109 + AddStudentForm,
  110 + StudentListDialog,
  111 + StudyRecordDialog,
  112 + Pagination
  113 + },
  114 + data() {
  115 + return {
  116 + query: {
  117 + className: '',
  118 + teacherId: '',
  119 + startTime: '',
  120 + endTime: '',
  121 + currentPage: 1,
  122 + pageSize: 20
  123 + },
  124 + list: [],
  125 + total: 0,
  126 + listLoading: true,
  127 + createClassVisible: false,
  128 + addStudentVisible: false,
  129 + studentListVisible: false,
  130 + studyRecordVisible: false,
  131 + selectedIds: []
  132 + }
  133 + },
  134 + created() {
  135 + this.getList()
  136 + },
  137 + methods: {
  138 + // 获取班级列表
  139 + getList() {
  140 + this.listLoading = true
  141 + getClassList(this.query).then(response => {
  142 + if (response.code === 200) {
  143 + this.list = response.data.list || []
  144 + this.total = (response.data.pagination && response.data.pagination.total) || 0
  145 + } else {
  146 + this.$message.error(response.msg || '获取班级列表失败')
  147 + }
  148 + this.listLoading = false
  149 + }).catch(() => {
  150 + this.listLoading = false
  151 + })
  152 + },
  153 +
  154 + // 搜索
  155 + search() {
  156 + this.query.currentPage = 1
  157 + this.getList()
  158 + },
  159 +
  160 + // 重置
  161 + reset() {
  162 + this.query = {
  163 + className: '',
  164 + teacherId: '',
  165 + startTime: '',
  166 + endTime: '',
  167 + currentPage: 1,
  168 + pageSize: 20
  169 + }
  170 + this.getList()
  171 + },
  172 +
  173 + // 创建班级
  174 + addClassHandle() {
  175 + this.createClassVisible = true
  176 + this.$nextTick(() => {
  177 + this.$refs.CreateClassForm.init()
  178 + })
  179 + },
  180 +
  181 + // 添加学员
  182 + addStudentHandle() {
  183 + if (this.selectedIds.length === 0) {
  184 + this.$message.warning('请先选择一个班级')
  185 + return
  186 + }
  187 + if (this.selectedIds.length > 1) {
  188 + this.$message.warning('请只选择一个班级')
  189 + return
  190 + }
  191 +
  192 + // 找到选中的班级信息
  193 + const selectedClass = this.list.find(item => item.id === this.selectedIds[0])
  194 + if (selectedClass) {
  195 + this.addStudentVisible = true
  196 + this.$nextTick(() => {
  197 + this.$refs.AddStudentForm.init(selectedClass.id, selectedClass.className)
  198 + })
  199 + }
  200 + },
  201 +
  202 + // 向班级添加学员
  203 + addStudentsToClass(row) {
  204 + this.addStudentVisible = true
  205 + this.$nextTick(() => {
  206 + this.$refs.AddStudentForm.init(row.id, row.className)
  207 + })
  208 + },
  209 +
  210 + // 查看学员
  211 + viewStudents(row) {
  212 + this.studentListVisible = true
  213 + this.$nextTick(() => {
  214 + this.$refs.StudentListDialog.init(row.id, row.className)
  215 + })
  216 + },
  217 +
  218 + // 查看学习记录
  219 + viewStudyRecords(row) {
  220 + this.studyRecordVisible = true
  221 + this.$nextTick(() => {
  222 + this.$refs.StudyRecordDialog.init(row.id, row.className)
  223 + })
  224 + },
  225 +
  226 + // 多选
  227 + handleSelectionChange(selection) {
  228 + this.selectedIds = selection.map(item => item.id)
  229 + },
  230 +
  231 + // 处理学员列表中的学习记录查看
  232 + handleViewStudyRecords(row) {
  233 + this.studyRecordVisible = true
  234 + this.$nextTick(() => {
  235 + this.$refs.StudyRecordDialog.init(row.classId, row.className)
  236 + })
  237 + },
  238 + }
  239 +}
  240 +</script>
  241 +
  242 +<style scoped>
  243 +.NCC-common-layout {
  244 + height: 100%;
  245 +}
  246 +</style>
... ...
antis-ncc-admin/src/views/lqStudyClass/test.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 + <title>学习班级管理 - 功能测试</title>
  7 + <style>
  8 + body {
  9 + font-family: 'Microsoft YaHei', Arial, sans-serif;
  10 + margin: 0;
  11 + padding: 20px;
  12 + background-color: #f5f7fa;
  13 + }
  14 + .container {
  15 + max-width: 1200px;
  16 + margin: 0 auto;
  17 + background: white;
  18 + border-radius: 8px;
  19 + padding: 30px;
  20 + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  21 + }
  22 + .header {
  23 + text-align: center;
  24 + margin-bottom: 30px;
  25 + padding-bottom: 20px;
  26 + border-bottom: 2px solid #409EFF;
  27 + }
  28 + .header h1 {
  29 + color: #409EFF;
  30 + margin: 0;
  31 + font-size: 28px;
  32 + }
  33 + .header p {
  34 + color: #666;
  35 + margin: 10px 0 0 0;
  36 + font-size: 16px;
  37 + }
  38 + .feature-section {
  39 + margin-bottom: 30px;
  40 + }
  41 + .feature-section h2 {
  42 + color: #303133;
  43 + border-left: 4px solid #409EFF;
  44 + padding-left: 15px;
  45 + margin-bottom: 20px;
  46 + }
  47 + .feature-list {
  48 + list-style: none;
  49 + padding: 0;
  50 + }
  51 + .feature-list li {
  52 + padding: 10px 0;
  53 + border-bottom: 1px solid #ebeef5;
  54 + position: relative;
  55 + padding-left: 25px;
  56 + }
  57 + .feature-list li:before {
  58 + content: "✓";
  59 + position: absolute;
  60 + left: 0;
  61 + color: #67C23A;
  62 + font-weight: bold;
  63 + }
  64 + .api-info {
  65 + background: #f8f9fa;
  66 + padding: 20px;
  67 + border-radius: 6px;
  68 + margin: 20px 0;
  69 + }
  70 + .api-info h3 {
  71 + color: #409EFF;
  72 + margin-top: 0;
  73 + }
  74 + .code-block {
  75 + background: #2d3748;
  76 + color: #e2e8f0;
  77 + padding: 15px;
  78 + border-radius: 4px;
  79 + font-family: 'Courier New', monospace;
  80 + font-size: 14px;
  81 + overflow-x: auto;
  82 + margin: 10px 0;
  83 + }
  84 + .file-structure {
  85 + background: #f8f9fa;
  86 + padding: 15px;
  87 + border-radius: 4px;
  88 + font-family: 'Courier New', monospace;
  89 + font-size: 14px;
  90 + margin: 10px 0;
  91 + }
  92 + .status-badge {
  93 + display: inline-block;
  94 + padding: 4px 8px;
  95 + border-radius: 12px;
  96 + font-size: 12px;
  97 + font-weight: bold;
  98 + margin-left: 10px;
  99 + }
  100 + .status-completed {
  101 + background: #f0f9ff;
  102 + color: #27ae60;
  103 + }
  104 + .status-pending {
  105 + background: #fff7e6;
  106 + color: #e6a23c;
  107 + }
  108 + </style>
  109 +</head>
  110 +<body>
  111 + <div class="container">
  112 + <div class="header">
  113 + <h1>🎓 学习班级管理系统</h1>
  114 + <p>基于Vue 2.6 + Element UI + ASP.NET Core开发的学习班级管理功能</p>
  115 + </div>
  116 +
  117 + <div class="feature-section">
  118 + <h2>📋 已实现功能</h2>
  119 + <ul class="feature-list">
  120 + <li>学习班级列表展示(支持分页、搜索、筛选)</li>
  121 + <li>创建学习班级并添加学员(批量添加)</li>
  122 + <li>向现有班级添加学员</li>
  123 + <li>查看班级下所有学员信息(分页展示)</li>
  124 + <li>学习记录管理(添加、查看、作废)</li>
  125 + <li>学员信息搜索和筛选</li>
  126 + <li>学习记录搜索和筛选</li>
  127 + <li>响应式布局设计</li>
  128 + <li>表单验证和错误处理</li>
  129 + </ul>
  130 + </div>
  131 +
  132 + <div class="feature-section">
  133 + <h2>🔧 API接口配置</h2>
  134 + <div class="api-info">
  135 + <h3>接口地址:<code>/api/Extend/LqStudyClass</code></h3>
  136 + <p><strong>请求方法:</strong>GET, POST</p>
  137 + <p><strong>主要接口:</strong></p>
  138 + <div class="code-block">
  139 +POST /api/Extend/LqStudyClass/CreateClassWithStudents
  140 +GET /api/Extend/LqStudyClass/GetClassList
  141 +POST /api/Extend/LqStudyClass/AddStudentsToClass
  142 +GET /api/Extend/LqStudyClass/GetStudentListByClassId
  143 +POST /api/Extend/LqStudyClass/AddStudyRecord
  144 +GET /api/Extend/LqStudyClass/GetStudyRecordList
  145 +POST /api/Extend/LqStudyClass/CancelStudyRecord
  146 + </div>
  147 + </div>
  148 + </div>
  149 +
  150 + <div class="feature-section">
  151 + <h2>📁 文件结构</h2>
  152 + <div class="file-structure">
  153 +lqStudyClass/
  154 +├── index.vue # 主页面组件
  155 +├── CreateClassForm.vue # 创建班级表单组件
  156 +├── AddStudentForm.vue # 添加学员表单组件
  157 +├── StudentListDialog.vue # 学员列表弹窗组件
  158 +└── StudyRecordDialog.vue # 学习记录管理弹窗组件
  159 +
  160 +api/extend/
  161 +└── lqStudyClass.js # API接口定义
  162 + </div>
  163 + </div>
  164 +
  165 + <div class="feature-section">
  166 + <h2>🎯 核心功能说明</h2>
  167 + <div class="api-info">
  168 + <h3>1. 创建学习班级并添加学员</h3>
  169 + <p>支持一次性创建班级并批量添加学员,包含班级基本信息(名称、老师、时间、备注)和学员详细信息(姓名、手机、员工ID、入学时间、HR归属)。</p>
  170 +
  171 + <h3>2. 班级列表管理</h3>
  172 + <p>提供班级列表展示,支持按班级名称、授课老师、开始时间等条件搜索,支持分页显示。</p>
  173 +
  174 + <h3>3. 学员管理</h3>
  175 + <p>可以查看指定班级下的所有学员信息,支持学员信息搜索和筛选,支持向现有班级添加新学员。</p>
  176 +
  177 + <h3>4. 学习记录管理</h3>
  178 + <p>记录学员的学习情况,包括学习类型、交通费、学习日期、日常状态、是否下店协助等信息,支持记录的添加、查看和作废操作。</p>
  179 + </div>
  180 + </div>
  181 +
  182 + <div class="feature-section">
  183 + <h2>🚀 技术特点</h2>
  184 + <ul class="feature-list">
  185 + <li>采用Vue 2.6 + Element UI技术栈</li>
  186 + <li>组件化开发,模块清晰</li>
  187 + <li>统一的API调用方式</li>
  188 + <li>完善的表单验证</li>
  189 + <li>响应式设计</li>
  190 + <li>良好的用户体验</li>
  191 + <li>符合项目开发规范</li>
  192 + </ul>
  193 + </div>
  194 +
  195 + <div class="feature-section">
  196 + <h2>📊 数据流程</h2>
  197 + <div class="api-info">
  198 + <p><strong>创建班级流程:</strong></p>
  199 + <ol>
  200 + <li>填写班级基本信息(名称、老师、时间等)</li>
  201 + <li>添加学员信息(可批量添加)</li>
  202 + <li>提交表单,后端创建班级和学员记录</li>
  203 + <li>返回创建结果,刷新列表</li>
  204 + </ol>
  205 +
  206 + <p><strong>学员管理流程:</strong></p>
  207 + <ol>
  208 + <li>从班级列表选择班级</li>
  209 + <li>查看班级学员列表</li>
  210 + <li>支持搜索和筛选学员</li>
  211 + <li>可添加新学员到班级</li>
  212 + </ol>
  213 +
  214 + <p><strong>学习记录流程:</strong></p>
  215 + <ol>
  216 + <li>选择班级查看学习记录</li>
  217 + <li>添加新的学习记录</li>
  218 + <li>支持记录搜索和筛选</li>
  219 + <li>可作废无效记录</li>
  220 + </ol>
  221 + </div>
  222 + </div>
  223 +
  224 + <div class="feature-section">
  225 + <h2>✅ 开发状态</h2>
  226 + <p>
  227 + <strong>前端页面:</strong><span class="status-badge status-completed">已完成</span>
  228 + <strong>API接口:</strong><span class="status-badge status-completed">已对接</span>
  229 + <strong>路由配置:</strong><span class="status-badge status-completed">已配置</span>
  230 + <strong>功能测试:</strong><span class="status-badge status-pending">待测试</span>
  231 + </p>
  232 + </div>
  233 + </div>
  234 +</body>
  235 +</html>
  236 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
... ... @@ -171,20 +171,40 @@ namespace NCC.Extend
171 171 quantity = x.Quantity,
172 172 productCategory = x.ProductCategory,
173 173 departmentId = x.DepartmentId,
174   - departmentName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == x.DepartmentId).Select(u => u.RealName),
  174 + departmentName = "",
175 175 standardUnit = x.StandardUnit,
176 176 totalValue = x.Price * x.Quantity,
177 177 createUser = x.CreateUser,
178   - createUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == x.CreateUser).Select(u => u.RealName),
  178 + createUserName = "",
179 179 createTime = x.CreateTime,
180 180 updateUser = x.UpdateUser,
181   - updateUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == x.UpdateUser).Select(u => u.RealName),
  181 + updateUserName = "",
182 182 updateTime = x.UpdateTime,
183 183 isEffective = x.IsEffective
184 184 })
185 185 .MergeTable()
186 186 .OrderBy(sidx + " " + input.sort)
187 187 .ToPagedListAsync(input.currentPage, input.pageSize);
  188 +
  189 + // 补充用户名称信息
  190 + foreach (var item in data.list)
  191 + {
  192 + if (!string.IsNullOrEmpty(item.departmentId))
  193 + {
  194 + var deptUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.departmentId).FirstAsync();
  195 + item.departmentName = deptUser?.RealName ?? "";
  196 + }
  197 + if (!string.IsNullOrEmpty(item.createUser))
  198 + {
  199 + var createUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.createUser).FirstAsync();
  200 + item.createUserName = createUser?.RealName ?? "";
  201 + }
  202 + if (!string.IsNullOrEmpty(item.updateUser))
  203 + {
  204 + var updateUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.updateUser).FirstAsync();
  205 + item.updateUserName = updateUser?.RealName ?? "";
  206 + }
  207 + }
188 208 return PageResult<LqInventoryListOutput>.SqlSugarPageResult(data);
189 209 }
190 210 catch (Exception ex)
... ... @@ -210,12 +230,14 @@ namespace NCC.Extend
210 230 {
211 231 throw NCCException.Oh("库存ID不能为空");
212 232 }
  233 + // 查询库存信息
213 234 var inventory = await _db.Queryable<LqInventoryEntity>().Where(x => x.Id == id).FirstAsync();
214 235  
215 236 if (inventory == null)
216 237 {
217 238 throw NCCException.Oh("库存记录不存在");
218 239 }
  240 +
219 241 var result = new LqInventoryInfoOutput
220 242 {
221 243 id = inventory.Id,
... ... @@ -224,18 +246,35 @@ namespace NCC.Extend
224 246 quantity = inventory.Quantity,
225 247 productCategory = inventory.ProductCategory,
226 248 departmentId = inventory.DepartmentId,
227   - departmentName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == inventory.DepartmentId).Select(u => u.RealName),
  249 + departmentName = "",
228 250 standardUnit = inventory.StandardUnit,
229 251 totalValue = inventory.Price * inventory.Quantity,
230 252 createUser = inventory.CreateUser,
231   - createUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == inventory.CreateUser).Select(u => u.RealName),
  253 + createUserName = "",
232 254 createTime = inventory.CreateTime,
233 255 updateUser = inventory.UpdateUser,
234   - updateUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == inventory.UpdateUser).Select(u => u.RealName),
  256 + updateUserName = "",
235 257 updateTime = inventory.UpdateTime,
236 258 isEffective = inventory.IsEffective
237 259 };
238 260  
  261 + // 补充用户名称信息
  262 + if (!string.IsNullOrEmpty(result.departmentId))
  263 + {
  264 + var deptUser = await _db.Queryable<UserEntity>().Where(u => u.Id == result.departmentId).FirstAsync();
  265 + result.departmentName = deptUser?.RealName ?? "";
  266 + }
  267 + if (!string.IsNullOrEmpty(result.createUser))
  268 + {
  269 + var createUser = await _db.Queryable<UserEntity>().Where(u => u.Id == result.createUser).FirstAsync();
  270 + result.createUserName = createUser?.RealName ?? "";
  271 + }
  272 + if (!string.IsNullOrEmpty(result.updateUser))
  273 + {
  274 + var updateUser = await _db.Queryable<UserEntity>().Where(u => u.Id == result.updateUser).FirstAsync();
  275 + result.updateUserName = updateUser?.RealName ?? "";
  276 + }
  277 +
239 278 return result;
240 279 }
241 280 catch (Exception ex)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
... ... @@ -186,28 +186,70 @@ namespace NCC.Extend
186 186 {
187 187 Id = x.Id,
188 188 ProductId = x.ProductId,
189   - ProductName = SqlFunc.Subqueryable<LqInventoryEntity>().Where(p => p.Id == x.ProductId).Select(p => p.ProductName),
190   - ProductCategory = SqlFunc.Subqueryable<LqInventoryEntity>().Where(p => p.Id == x.ProductId).Select(p => p.ProductCategory),
191   - ProductPrice = SqlFunc.Subqueryable<LqInventoryEntity>().Where(p => p.Id == x.ProductId).Select(p => p.Price),
  189 + ProductName = "",
  190 + ProductCategory = "",
  191 + ProductPrice = 0,
192 192 StoreId = x.StoreId,
193   - StoreName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == x.StoreId).Select(u => u.RealName),
  193 + StoreName = "",
194 194 UsageTime = x.UsageTime,
195 195 UsageQuantity = x.UsageQuantity,
196 196 RelatedConsumeId = x.RelatedConsumeId,
197 197 CreateUser = x.CreateUser,
198   - CreateUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == x.CreateUser).Select(u => u.RealName),
  198 + CreateUserName = "",
199 199 CreateTime = x.CreateTime,
200 200 UpdateUser = x.UpdateUser,
201   - UpdateUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == x.UpdateUser).Select(u => u.RealName),
  201 + UpdateUserName = "",
202 202 UpdateTime = x.UpdateTime,
203 203 IsEffective = x.IsEffective
204 204 })
205 205 .MergeTable()
206   - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), x => x.ProductName.Contains(input.ProductName))
207   - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), x => x.ProductCategory.Contains(input.ProductCategory))
208   - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreName), x => x.StoreName.Contains(input.StoreName))
209 206 .OrderBy(sidx + " " + input.sort)
210 207 .ToPagedListAsync(input.currentPage, input.pageSize);
  208 +
  209 + // 补充产品信息和用户信息
  210 + foreach (var item in data.list)
  211 + {
  212 + if (!string.IsNullOrEmpty(item.ProductId))
  213 + {
  214 + var product = await _db.Queryable<LqInventoryEntity>().Where(p => p.Id == item.ProductId).FirstAsync();
  215 + if (product != null)
  216 + {
  217 + item.ProductName = product.ProductName;
  218 + item.ProductCategory = product.ProductCategory;
  219 + item.ProductPrice = product.Price;
  220 + }
  221 + }
  222 + if (!string.IsNullOrEmpty(item.StoreId))
  223 + {
  224 + var store = await _db.Queryable<UserEntity>().Where(u => u.Id == item.StoreId).FirstAsync();
  225 + item.StoreName = store?.RealName ?? "";
  226 + }
  227 + if (!string.IsNullOrEmpty(item.CreateUser))
  228 + {
  229 + var createUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.CreateUser).FirstAsync();
  230 + item.CreateUserName = createUser?.RealName ?? "";
  231 + }
  232 + if (!string.IsNullOrEmpty(item.UpdateUser))
  233 + {
  234 + var updateUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.UpdateUser).FirstAsync();
  235 + item.UpdateUserName = updateUser?.RealName ?? "";
  236 + }
  237 + }
  238 +
  239 + // 应用产品名称和分类的过滤条件
  240 + if (!string.IsNullOrWhiteSpace(input.ProductName) || !string.IsNullOrWhiteSpace(input.ProductCategory))
  241 + {
  242 + data.list = data.list.Where(x =>
  243 + (string.IsNullOrWhiteSpace(input.ProductName) || x.ProductName.Contains(input.ProductName)) &&
  244 + (string.IsNullOrWhiteSpace(input.ProductCategory) || x.ProductCategory.Contains(input.ProductCategory))
  245 + ).ToList();
  246 + }
  247 +
  248 + // 应用门店名称的过滤条件
  249 + if (!string.IsNullOrWhiteSpace(input.StoreName))
  250 + {
  251 + data.list = data.list.Where(x => x.StoreName.Contains(input.StoreName)).ToList();
  252 + }
211 253 return PageResult<LqInventoryUsageListOutput>.SqlSugarPageResult(data);
212 254 }
213 255 catch (Exception ex)
... ...