学生导入

This commit is contained in:
Cool 2024-10-23 22:19:31 +08:00
parent 347fb88477
commit d0a33280d3
6 changed files with 690 additions and 228 deletions

View File

@ -0,0 +1,72 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from "/@/hooks/web/useMessage";
const { createConfirm } = useMessage();
enum Api {
list = '/com/cet4Major/list',
save='/com/cet4Major/add',
edit='/com/cet4Major/edit',
deleteOne = '/com/cet4Major/delete',
deleteBatch = '/com/cet4Major/deleteBatch',
importExcel = '/com/cet4Major/importExcel',
exportXls = '/com/cet4Major/exportXls',
}
/**
* api
* @param params
*/
export const getExportUrl = Api.exportXls;
/**
* api
*/
export const getImportUrl = Api.importExcel;
/**
*
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
*
* @param params
* @param handleSuccess
*/
export const deleteOne = (params,handleSuccess) => {
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
handleSuccess();
});
}
/**
*
* @param params
* @param handleSuccess
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
handleSuccess();
});
}
});
}
/**
*
* @param params
* @param isUpdate
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params }, { isTransformResponse: false });
}

View File

@ -0,0 +1,103 @@
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
import { rules } from '/@/utils/helper/validator';
import { render } from '/@/utils/common/renderUtils';
//列表数据
export const columns: BasicColumn[] = [
{
title: '年级',
dataIndex: 'entrydate',
key: 'entrydate',
align: 'center',
},
{
title: '学生人数',
dataIndex: 'studentNumber',
key: 'studentNumber',
align: 'center',
},
];
//查询数据
export const searchFormSchema: FormSchema[] = [];
//表单数据
export const formSchema: FormSchema[] = [
{
label: 'name',
field: 'name',
component: 'Input',
},
{
label: 'college',
field: 'college',
component: 'Input',
},
{
label: 'majorId',
field: 'majorId',
component: 'Input',
},
{
label: 'majorname',
field: 'majorname',
component: 'Input',
},
{
label: 'className',
field: 'className',
component: 'Input',
},
{
label: 'educate',
field: 'educate',
component: 'Input',
},
{
label: 'entrydate',
field: 'entrydate',
component: 'Input',
},
{
label: 'campus',
field: 'campus',
component: 'Input',
},
{
label: 'state',
field: 'state',
component: 'Input',
},
{
label: 'level',
field: 'level',
component: 'Input',
},
{
label: 'category',
field: 'category',
component: 'Input',
},
// TODO 主键隐藏字段目前写死为ID
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
];
// 高级查询数据
export const superQuerySchema = {
name: { title: 'name', order: 0, view: 'text', type: 'string' },
college: { title: 'college', order: 1, view: 'text', type: 'string' },
majorId: { title: 'majorId', order: 2, view: 'text', type: 'string' },
majorname: { title: 'majorname', order: 3, view: 'text', type: 'string' },
className: { title: 'className', order: 4, view: 'text', type: 'string' },
educate: { title: 'educate', order: 5, view: 'text', type: 'string' },
entrydate: { title: 'entrydate', order: 6, view: 'text', type: 'string' },
campus: { title: 'campus', order: 7, view: 'text', type: 'string' },
state: { title: 'state', order: 8, view: 'text', type: 'string' },
level: { title: 'level', order: 9, view: 'text', type: 'string' },
category: { title: 'category', order: 10, view: 'text', type: 'string' },
};

View File

@ -0,0 +1,247 @@
<template>
<div class="p-2">
<a-card title="英语四级数据导入" :bordered="false">
<a-row :gutter="2">
<a-col :xl="24" :style="{ marginBottom: '24px' }">
<div class="clearfix">
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<!--<div class="clearfix">
<a-button type="primary" preIcon="ant-design:import-outlined" @click="asyncData">同步数据</a-button>
</div>-->
<!--<div class="clearfix">
<a-button preIcon="ant-design:export-outlined" @click="downloadTemplate"> 下载模板</a-button>
</div>-->
</div>
</a-col>
</a-row>
<a-row :gutter="10">
<a-col :xl="24">
<a-table :dataSource="dataSourceCet4" :columns="columns" :pagination="false" :loading="loading" bordered class="custom-table" />
</a-col>
</a-row>
</a-card>
<!--查询区域-->
<!--<div class="jeecg-basic-table-form-container">
<a-form ref="formRef" @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-row :gutter="24" />
</a-form>
</div>-->
<!--引用表格-->
<!-- 表单区域 -->
<Cet4MajorModal ref="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="com-cet4Major" setup>
import { ref, reactive, onMounted } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, superQuerySchema } from './Cet4Major.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './Cet4Major.api';
import { downloadFile } from '/@/utils/common/renderUtils';
import Cet4MajorModal from './components/Cet4MajorModal.vue';
import { useUserStore } from '/@/store/modules/user';
import { defHttp } from '/@/utils/http/axios';
const url = {
downLoadTemplate: '/cetDataImport/downloadTemplate',
importCet4Data: '/cetDataImport/dbfImport',
loadImportData: '/com/cet4Major/loadTable',
asyncData: '/com/cet4Major/asyncData',
};
const asyncData = async () => {
const res = await defHttp.post({ url: url.asyncData });
message.success('正在同步');
};
const loading = ref(false);
//
const dataSourceCet4 = ref([]);
const formRef = ref();
const queryParam = reactive<any>({});
const toggleSearchStatus = ref<boolean>(false);
const registerModal = ref();
const userStore = useUserStore();
//table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: 'cet4_major',
api: list,
columns,
canResize: false,
useSearchForm: false,
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: (params) => {
return Object.assign(params, queryParam);
},
},
exportConfig: {
name: 'cet4_major',
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
//
const fetchData = async () => {
loading.value = true; //
try {
const response = await defHttp.post({ url: url.loadImportData });
console.log(response, 'response');
dataSourceCet4.value = response.data; //
} catch (error) {
message.error('Failed to load data.');
} finally {
loading.value = false; //
}
};
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] =
tableContext;
const labelCol = reactive({
xs: 24,
sm: 4,
xl: 6,
xxl: 4,
});
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
//
const superQueryConfig = reactive(superQuerySchema);
/**
* 高级查询事件
*/
function handleSuperQuery(params) {
Object.keys(params).map((k) => {
queryParam[k] = params[k];
});
searchQuery();
}
/**
* 新增事件
*/
function handleAdd() {
registerModal.value.disableSubmit = false;
registerModal.value.add();
}
/**
* 编辑事件
*/
function handleEdit(record: Recordable) {
registerModal.value.disableSubmit = false;
registerModal.value.edit(record);
}
/**
* 详情
*/
function handleDetail(record: Recordable) {
registerModal.value.disableSubmit = true;
registerModal.value.edit(record);
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
},
];
}
/**
* 查询
*/
function searchQuery() {
reload();
}
/**
* 重置
*/
function searchReset() {
formRef.value.resetFields();
selectedRowKeys.value = [];
//
reload();
}
onMounted(fetchData);
</script>
<style lang="less" scoped>
.jeecg-basic-table-form-container {
padding: 0;
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
.query-group-cust {
min-width: 100px !important;
}
.query-group-split-cust {
width: 30px;
display: inline-block;
text-align: center;
}
}
.clearfix {
display: flex;
padding-left: 10px;
}
</style>

View File

@ -0,0 +1,193 @@
<template>
<a-spin :spinning="confirmLoading">
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-row>
<a-col :span="24">
<a-form-item label="name" v-bind="validateInfos.name">
<a-input v-model:value="formData.name" placeholder="请输入name" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="college" v-bind="validateInfos.college">
<a-input v-model:value="formData.college" placeholder="请输入college" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="majorId" v-bind="validateInfos.majorId">
<a-input v-model:value="formData.majorId" placeholder="请输入majorId" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="majorname" v-bind="validateInfos.majorname">
<a-input v-model:value="formData.majorname" placeholder="请输入majorname" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="className" v-bind="validateInfos.className">
<a-input v-model:value="formData.className" placeholder="请输入className" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="educate" v-bind="validateInfos.educate">
<a-input v-model:value="formData.educate" placeholder="请输入educate" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="entrydate" v-bind="validateInfos.entrydate">
<a-input v-model:value="formData.entrydate" placeholder="请输入entrydate" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="campus" v-bind="validateInfos.campus">
<a-input v-model:value="formData.campus" placeholder="请输入campus" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="state" v-bind="validateInfos.state">
<a-input v-model:value="formData.state" placeholder="请输入state" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="level" v-bind="validateInfos.level">
<a-input v-model:value="formData.level" placeholder="请输入level" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="category" v-bind="validateInfos.category">
<a-input v-model:value="formData.category" placeholder="请输入category" :disabled="disabled"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { getValueType } from '/@/utils';
import { saveOrUpdate } from '../Cet4Major.api';
import { Form } from 'ant-design-vue';
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
});
const formRef = ref();
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
id: '',
name: '',
college: '',
majorId: '',
majorname: '',
className: '',
educate: '',
entrydate: '',
campus: '',
state: '',
level: '',
category: '',
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = {
};
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
//
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
/**
* 新增
*/
function add() {
edit({});
}
/**
* 编辑
*/
function edit(record) {
nextTick(() => {
resetFields();
const tmpData = {};
Object.keys(formData).forEach((key) => {
if(record.hasOwnProperty(key)){
tmpData[key] = record[key]
}
})
//
Object.assign(formData, tmpData);
});
}
/**
* 提交数据
*/
async function submitForm() {
//
await validate();
confirmLoading.value = true;
const isUpdate = ref<boolean>(false);
//
let model = formData;
if (model.id) {
isUpdate.value = true;
}
//
for (let data in model) {
//
if (model[data] instanceof Array) {
let valueType = getValueType(formRef.value.getProps, data);
//
if (valueType === 'string') {
model[data] = model[data].join(',');
}
}
}
await saveOrUpdate(model, isUpdate.value)
.then((res) => {
if (res.success) {
createMessage.success(res.message);
emit('ok');
} else {
createMessage.warning(res.message);
}
})
.finally(() => {
confirmLoading.value = false;
});
}
defineExpose({
add,
edit,
submitForm,
});
</script>
<style lang="less" scoped>
.antd-modal-form {
height: 500px !important;
overflow-y: auto;
padding: 14px;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<Cet4MajorForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></Cet4MajorForm>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import Cet4MajorForm from './Cet4MajorForm.vue'
const title = ref<string>('');
const width = ref<number>(800);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
/**
* 新增
*/
function add() {
title.value = '新增';
visible.value = true;
nextTick(() => {
registerForm.value.add();
});
}
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = disableSubmit.value ? '详情' : '编辑';
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
emit('success');
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
visible.value = false;
}
defineExpose({
add,
edit,
disableSubmit,
});
</script>
<style>
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>

View File

@ -1,228 +0,0 @@
<template>
<div style="background: #ececec; padding-top: 0px; padding-left: 20px; padding-right: 20px; padding-bottom: 20px">
<a-card title="英语四级数据导入" :bordered="false">
<a-row :gutter="2">
<a-col :xl="24" :style="{ marginBottom: '24px' }">
<div class="clearfix">
<div class="clearfix">
<!-- accept=".dbf" 属性限制文件类型 -->
<a-upload
:file-list="fileList"
accept=".dbf"
:max-count="1"
:customRequest="handleUpload"
:before-upload="beforeUpload"
@remove="handleRemove"
>
<a-button type="primary">
<upload-outlined />
导入文件
</a-button>
</a-upload>
</div>
<!--<div class="clearfix">
<a-button preIcon="ant-design:export-outlined" @click="downloadTemplate"> 下载模板</a-button>
</div>-->
<div class="clearfix">
<a-button type="primary" :disabled="!canUpload" :loading="uploading" @click="handleUpload">
{{ uploading ? 'Uploading' : '确认上传文件' }}
</a-button>
</div>
</div>
</a-col>
</a-row>
<a-row :gutter="10">
<a-col :xl="24">
<a-table :dataSource="dataSourceCet4" :columns="columns" :pagination="false" :loading="loading" bordered class="custom-table" />
</a-col>
</a-row>
</a-card>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { defHttp } from '/@/utils/http/axios';
import { downloadTemplateExcel } from './dataImportApi';
import { conversionFileDownload } from '@/utils/download';
const loading = ref(false);
//
const month = ref(null);
const url = {
downLoadTemplate: '/cetDataImport/downloadTemplate',
importCet4Data: '/cetDataImport/dbfImport',
loadImportData: '/cet_4/loadImportDataList',
};
//
const fileList: any = ref([]);
const uploading = ref(false);
//
const canUpload = computed(() => fileList.value.length > 0 && month.value !== null);
//
const handleRemove = (file) => {
const index = fileList.value.indexOf(file);
const newFileList = fileList.value.slice();
newFileList.splice(index, 1);
fileList.value = newFileList;
};
//
const beforeUpload = (file) => {
console.log(file);
fileList.value = [];
fileList.value = [...fileList.value, file];
let fileType = file.name.split('.').pop().toLowerCase();
if (fileType !== 'dbf') {
message.error('请上传.dbf文件');
fileList.value = fileList.value.filter((item) => item.uid !== file.uid);
}
return false; //
};
//
const handleUpload = async () => {
//
if (!fileList.value || fileList.value.length === 0) {
message.error('没有选择文件');
return;
}
const file = fileList.value[0];
if (!file) {
message.error('没有选择文件');
return;
}
// Base64
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async () => {
const base64File = reader.result as string;
//
const payload = {
fileName: file.name,
fileContent: base64File,
batch: '',
};
// batch
let batch: any = month.value;
if (batch) {
batch = batch.format('YYYY-MM-DD');
batch = setDayToFirst(batch); //
payload.batch = batch;
}
try {
uploading.value = true;
// POST
const response: any = await defHttp.post(
{
url: url.importCet4Data,
data: payload,
headers: { 'Content-Type': 'application/json' },
},
{
isTransformResponse: false,
}
);
//
fileList.value = [];
//
if (response.success) {
message.success('上传成功');
} else {
message.error(response.message || '上传失败');
}
} catch (error) {
console.error('上传失败:', error);
message.error('上传失败');
} finally {
uploading.value = false;
}
};
reader.onerror = (error) => {
console.error('文件读取错误:', error);
message.error('文件读取失败');
};
};
/**
* 'yyyy-MM-dd' 格式的日期字符串的天部分设置为 '01'
* @param {string} dateStr - 原始日期字符串格式为 'yyyy-MM-dd'
* @returns {string} 修改后的日期字符串格式为 'yyyy-MM-01'
*/
const setDayToFirst = (dateStr) => {
// 使
const regex = /^(\d{4})-(\d{2})-(\d{2})$/;
const match = dateStr.match(regex);
if (!match) {
throw new Error("日期格式不正确,应为 'yyyy-MM-dd'");
}
const year = match[1];
const month = match[2];
// '01'
return `${year}-${month}-01`;
};
//
const dataSourceCet4 = ref([]);
//
const columns = [
{
title: '年级',
dataIndex: 'entrydate',
key: 'entrydate',
align: 'center',
},
{
title: '学生人数',
dataIndex: 'studentNumber',
key: 'studentNumber',
align: 'center',
},
];
//
const fetchData = async () => {
loading.value = true; //
try {
const response = await defHttp.post({ url: url.loadImportData });
console.log(response, 'response');
dataSourceCet4.value = response.data; //
} catch (error) {
message.error('Failed to load data.');
} finally {
loading.value = false; //
}
};
//const downloadTemplate = async () => {
// downloadTemplateExcel()
// .then((response: any) => {
// console.log(response, '123');
// conversionFileDownload(response);
// })
// .catch((error) => {
// console.error(error);
// });
//};
// fetchData
onMounted(fetchData);
</script>
<style scoped>
.clearfix {
display: flex;
padding-left: 10px;
}
</style>