This commit is contained in:
陈红丽 2024-08-24 09:57:13 +08:00
parent 0c9cb50548
commit bfa6f73975
13 changed files with 331 additions and 191 deletions

View File

@ -62,7 +62,18 @@ export function changePassword(data) {
}
);
}
/**
* @description:
*/
export function sendSms(data) {
return http.request(
{
url: `/sms/sendSms`,
method: 'POST',
data
}
);
}
/**
* @description:
*/
@ -142,6 +153,16 @@ export function userBatchDelete(data:any) {
data
});
}
/**
* @description:
*/
export function userImport(data) {
return http.request({
url: '/user/import',
method: 'POST',
data
});
}
/**
* @description:
*/
@ -153,6 +174,16 @@ export function userExport() {
isTransformResponse: false,
});
}
/**
* @description:
*/
export function getTemplateByCode(code:any) {
return http.request({
url: '/file/template/getTemplateByCode/'+code,
method: 'GET'
});
}
/**
* @description:
*/

View File

@ -8,6 +8,7 @@
:layout="layout"
:total="pager.count"
:hide-on-single-page="false"
background
@size-change="sizeChange"
@current-change="pageChange"
></el-pagination>

View File

@ -1,7 +1,7 @@
<template>
<PageWrapper>
<el-row :gutter="10" class="mt-3">
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="mb-4">
<el-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7" class="mb-4">
<el-card shadow="hover" class="border-0">
<template #header>
<el-row>
@ -15,36 +15,40 @@
</el-input>
</el-col>
<el-col :span="4" style="text-align: right;">
<el-button type="primary" @click="reloadTable"> 查询 </el-button>
<el-button type="primary" @click="pager.page=1;loadDataTable()"> 查询 </el-button>
</el-col>
</el-row>
<div style="margin-top:15px;">
<el-button type="primary" @click="addConfig">新建</el-button>
<el-button type="danger" :disabled="!selectionData.length" @click="handleDelete()">删除</el-button>
<el-button type="primary" icon="Plus" @click="addConfig" v-perm="['sys:config:add']">新建</el-button>
<el-button type="warning" icon="Edit" @click="handleEdit" v-perm="['sys:config:edit']">编辑</el-button>
<el-button type="danger" icon="Delete" @click="handleDelete()" v-perm="['sys:config:delete']">删除</el-button>
</div>
</template>
<BasicTable :columns="columns" :showTableSetting="false" :request="loadDataTable" :row-key="(row) => row.id"
ref="tableRef" :actionColumn="actionColumn" @selection-change="onSelectionChange" highlight-current-row
@row-click="onCheckedRow" />
<div :style="{ height: fwbHeight + 'px' }" class="dict-list-box">
<div v-for="(item, index) in configDataList" :key="index" @click="onCheckedRow(item)" class="dict-item"
:class="item.id == configId ? 'active' : ''">
<span class="t1">{{ item.name }}</span>
</div>
</div>
<pagination style="justify-content: flex-end" class="mt-10 flex" @change="loadDataTable" v-model="pager" layout="total, jumper">
</pagination>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16" class="mb-4">
<el-col :xs="24" :sm="24" :md="17" :lg="17" :xl="17" class="mb-4">
<el-card shadow="hover" class="mb-4 border-0 proCard">
<configItem :configId="configId" v-if="configItemShow"></configItem>
</el-card>
</el-col>
</el-row>
<editDialog v-if="editVisible" :configId="configId" v-model:visible="editVisible" @success="reloadTable">
<editDialog v-if="editVisible" :configId="configId" v-model:visible="editVisible" @success="loadDataTable()">
</editDialog>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, reactive, h, nextTick, defineAsyncComponent } from 'vue';
import { ref,nextTick, defineAsyncComponent,onMounted } from 'vue';
import { SearchOutlined } from '@vicons/antd';
import { TableAction } from '@/components/Table';
import { getConfigList, configDelete, configBatchDelete } from '@/api/data/config';
import { columns } from './columns';
import { getConfigList, configDelete } from '@/api/data/config';
import configItem from './configItem.vue';
import { message, confirm } from "@/utils/auth";
const editDialog = defineAsyncComponent(() =>
@ -54,86 +58,52 @@ const configId = ref(0)
const configItemShow = ref(false)
const tableRef = ref();
const editVisible = ref(false)
const selectionData = ref([])
const params = ref({
name: '',
});
const actionColumn = reactive({
lable: 150,
title: '操作',
prop: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'text',
actions: [
{
label: '编辑',
icon:'Edit',
type: 'warning',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
icon:'Delete',
type: 'danger',
onClick: handleDelete.bind(null, record),
}
],
});
},
const configDataList = ref([])
const pager = ref({
page: 1,
size: 10,
count: configDataList.value.length
});
const fwbHeight = document.body.clientHeight - 390
//
const addConfig = async () => {
configId.value=0
await nextTick();
editVisible.value=true
};
//
const handleEdit = async (record: Recordable) => {
configId.value=record.row.id
await nextTick();
const handleEdit = () => {
editVisible.value=true
};
//
function onCheckedRow(row) {
configId.value = row.id
}
//
function reloadTable() {
tableRef.value.reload({ pageNo: 1 });
}
//;
const loadDataTable = async (res) => {
const result = await getConfigList({ ...params.value, ...res });
const loadDataTable = async () => {
const result = await getConfigList({ ...params.value, pageNo:pager.value.page,pageSize:pager.value.size });
configId.value = result?.records[0]?.id
configItemShow.value = true
nextTick(() => {
const tables = tableRef.value.getTableRef();
tables.setCurrentRow(result?.records[0])
})
return result;
configDataList.value = result.records
pager.value.count = result.total
};
//
async function handleDelete(record: Recordable) {
let ids = []
if (!record) {
ids = selectionData.value.map(({ id }) => id);
}
async function handleDelete() {
await confirm('确定要删除?');
record ? await configDelete(record.row.id) : await configBatchDelete(ids);
await configDelete(configId.value);
message("删除成功");
reloadTable()
}
function onSelectionChange(value) {
selectionData.value = value
pager.value.page = 1
loadDataTable()
}
onMounted(() => {
loadDataTable()
})
</script>
<style lang="scss" scoped>
.card-header {
@ -141,4 +111,42 @@ function onSelectionChange(value) {
justify-content: space-between;
align-items: center;
}
.dict-list-box {
.dict-item {
height: 40px;
line-height: 40px;
padding: 0 6px;
cursor: pointer;
display: flex;
justify-content: space-between;
.t1 {
font-size: 14px;
display: block;
font-weight: 700;
.t2 {
font-size: 12px;
font-weight: normal;
}
}
&.active {
background-color: #e8f1ff;
border-radius: 3px;
.t1 {
color: #1677ff;
}
.t2 {
color:rgb(22, 119, 255,.8)
}
}
.el-badge__content.is-fixed {
top: 20px;
right: calc(-10px + var(--el-badge-size)/ 2);
}
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<PageWrapper>
<el-row :gutter="10" class="mt-3">
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="mb-4">
<el-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7" class="mb-4">
<el-card shadow="hover" class="border-0">
<template #header>
<el-row>
@ -15,37 +15,43 @@
</el-input>
</el-col>
<el-col :span="4" style="text-align: right;">
<el-button type="primary" @click="reloadTable"> 查询 </el-button>
<el-button type="primary" @click="pager.page=1;loadDataTable()"> 查询 </el-button>
</el-col>
</el-row>
<el-button type="primary" icon="RefreshRight" @click="dictRefresh" style="margin-top:15px;"
v-perm="['sys:dict:cache']">刷新缓存</el-button>
<div style="margin-top:15px;">
<el-button type="primary" @click="dictRefresh" v-perm="['sys:dict:cache']">刷新缓存</el-button>
<el-button type="primary" @click="addDict" v-perm="['sys:dict:add']">新建</el-button>
<el-button type="danger" v-perm="['sys:dict:delete']" :disabled="!selectionData.length" @click="handleDelete()">删除</el-button>
<el-button type="primary" icon="Plus" @click="addDict" v-perm="['sys:dict:add']">新建</el-button>
<el-button type="warning" icon="Edit" @click="handleEdit" v-perm="['sys:dict:edit']">编辑</el-button>
<el-button type="danger" icon="Delete" v-perm="['sys:dict:delete']"
@click="handleDelete()">删除</el-button>
</div>
</template>
<BasicTable :columns="columns" :showTableSetting="false" :request="loadDataTable" :row-key="(row) => row.id"
ref="tableRef" :actionColumn="actionColumn" @selection-change="onSelectionChange" highlight-current-row
@row-click="onCheckedRow" />
<div :style="{ height: fwbHeight + 'px' }" class="dict-list-box">
<div v-for="(item, index) in dictDataList" :key="index" @click="onCheckedRow(item)" class="dict-item"
:class="item.id == dictId ? 'active' : ''">
<span class="t1">{{ item.name }}<span class="t2">({{ item.code }})</span></span>
</div>
</div>
<pagination style="justify-content: flex-end" class="mt-10 flex" @change="loadDataTable" v-model="pager" layout="total, jumper">
</pagination>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16" class="mb-4">
<el-col :xs="24" :sm="24" :md="17" :lg="17" :xl="17" class="mb-4">
<el-card shadow="hover" class="mb-4 border-0 proCard">
<dictItem :dictId="dictId" v-if="dictItemShow"></dictItem>
</el-card>
</el-col>
</el-row>
<editDialog v-if="editVisible" :dictId="dictId" v-model:visible="editVisible" @success="reloadTable">
<editDialog v-if="editVisible" :dictId="dictId" v-model:visible="editVisible" @success="loadDataTable()">
</editDialog>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, reactive, h, nextTick, defineAsyncComponent } from 'vue';
import { onMounted, ref, nextTick, defineAsyncComponent } from 'vue';
import { SearchOutlined } from '@vicons/antd';
import { TableAction } from '@/components/Table';
import { getDictList, refreshCache, dictDelete, dictBatchDelete } from '@/api/data/dictionary';
import { columns } from './columns';
import { getDictList, refreshCache, dictDelete } from '@/api/data/dictionary';
import dictItem from './dictItem.vue';
import { message, confirm } from "@/utils/auth";
const editDialog = defineAsyncComponent(() =>
@ -53,75 +59,40 @@ const editDialog = defineAsyncComponent(() =>
)
const dictId = ref(0)
const dictItemShow = ref(false)
const tableRef = ref();
const selectedKey = ref('');
const rowKeysName = ref('');
const currentRow = ref();
const dictDataList = ref([])
const editVisible = ref(false)
const selectionData = ref([])
const params = ref({
name: '',
});
const actionColumn = reactive({
lable: 150,
title: '操作',
prop: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'text',
actions: [
{
label: '编辑',
icon:'Edit',
type: 'warning',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
icon:'Delete',
type: 'danger',
onClick: handleDelete.bind(null, record),
}
],
});
},
const pager = ref({
page: 1,
size: 10,
count: dictDataList.value.length
});
const fwbHeight = document.body.clientHeight - 440
//
const addDict = async () => {
dictId.value=0
dictId.value = 0
await nextTick();
editVisible.value=true
editVisible.value = true
};
//
const handleEdit = async (record: Recordable) => {
dictId.value=record.row.id
await nextTick();
editVisible.value=true
const handleEdit = () => {
editVisible.value = true
};
//
function onCheckedRow(row) {
dictId.value = row.id
}
//
function reloadTable() {
tableRef.value.reload({ pageNo: 1 });
}
//;
const loadDataTable = async (res) => {
const result = await getDictList({ ...params.value, ...res });
const loadDataTable = async () => {
const result = await getDictList({ ...params.value, pageNo:pager.value.page,pageSize:pager.value.size });
dictId.value = result?.records[0]?.id
dictItemShow.value = true
nextTick(() => {
const tables = tableRef.value.getTableRef();
tables.setCurrentRow(result?.records[0])
})
return result;
dictDataList.value = result.records
pager.value.count = result.total
};
//
@ -130,19 +101,17 @@ async function dictRefresh() {
message("刷新成功");
}
//
async function handleDelete(record: Recordable) {
let ids = []
if (!record) {
ids = selectionData.value.map(({ id }) => id);
}
async function handleDelete() {
await confirm('确定要删除?');
record ? await dictDelete(record.row.id) : await dictBatchDelete(ids);
await dictDelete(dictId.value);
message("删除成功");
reloadTable()
}
function onSelectionChange(value) {
selectionData.value = value
pager.value.page = 1
loadDataTable()
}
onMounted(() => {
loadDataTable()
})
</script>
<style lang="scss" scoped>
.card-header {
@ -150,4 +119,42 @@ function onSelectionChange(value) {
justify-content: space-between;
align-items: center;
}
.dict-list-box {
.dict-item {
height: 40px;
line-height: 40px;
padding: 0 6px;
cursor: pointer;
display: flex;
justify-content: space-between;
.t1 {
font-size: 14px;
display: block;
font-weight: 700;
.t2 {
font-size: 12px;
font-weight: normal;
}
}
&.active {
background-color: #e8f1ff;
border-radius: 3px;
.t1 {
color: #1677ff;
}
.t2 {
color:rgb(22, 119, 255,.8)
}
}
.el-badge__content.is-fixed {
top: 20px;
right: calc(-10px + var(--el-badge-size)/ 2);
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="props.visible" :title="dialogTitle" :append-to-body="true" width="600" :close-on-click-modal="false"
<el-dialog v-model="props.visible" :title="dialogTitle" width="600" :close-on-click-modal="false"
:before-close="dialogClose">
<el-form ref="formRef" :model="formData" label-width="90px">
<el-form-item

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="props.visible" :title="dialogTitle" :append-to-body="true" width="600" :close-on-click-modal="false"
<el-dialog v-model="props.visible" :title="dialogTitle" width="600" :close-on-click-modal="false"
:before-close="dialogClose">
<el-form ref="formRef" :model="formData" label-width="90px">
<el-form-item

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="props.visible" :title="dialogTitle" :append-to-body="true" width="600" :close-on-click-modal="false"
<el-dialog v-model="props.visible" :title="dialogTitle" width="600" :close-on-click-modal="false"
:before-close="dialogClose">
<el-form ref="formRef" :model="formData" label-width="90px">
<el-form-item

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="props.visible" :title="dialogTitle" :append-to-body="true" width="600" :close-on-click-modal="false"
<el-dialog v-model="props.visible" :title="dialogTitle" width="600" :close-on-click-modal="false"
:before-close="dialogClose">
<el-form ref="formRef" :model="formData" label-width="90px">
<el-form-item

View File

@ -42,11 +42,11 @@ export const columns = [
},
{
label: '模板编号',
prop: 'number',
prop: 'code',
},
{
label: '接收人手机',
prop: 'receiveMobile',
label: '接收人邮箱',
prop: 'receiveEmail',
},
{
label: '接收人类型',

View File

@ -10,12 +10,22 @@
<el-descriptions column="2" border>
<el-descriptions-item label="日志标题:" label-class-name="des-width">{{formData.title}}</el-descriptions-item>
<el-descriptions-item label="日志类型:" label-class-name="des-width">{{getTyepText(formData.type)}}</el-descriptions-item>
<el-descriptions-item label="模板编号:">{{formData.number}}</el-descriptions-item>
<el-descriptions-item label="接收人手机:">{{formData.receiveMobile}}</el-descriptions-item>
<el-descriptions-item label="模板编号:">{{formData.code}}</el-descriptions-item>
<el-descriptions-item label="接收人邮箱:">{{formData.receiveEmail}}</el-descriptions-item>
<el-descriptions-item label="接收人类型:">{{getReviceType(formData.receiveType)}}</el-descriptions-item>
<el-descriptions-item label="请求耗时:">{{formData.consumeTime}}s</el-descriptions-item>
<el-descriptions-item label="日志状态:">{{formData.status==1?'已读':'未读'}}</el-descriptions-item>
</el-descriptions>
<el-descriptions class="margin-top" :column="1" border>
<el-descriptions-item label="请求参数" label-class-name="des-width">
{{formData.param}}
</el-descriptions-item>
</el-descriptions>
<el-descriptions class="margin-top" :column="1" border>
<el-descriptions-item label="返回结果" label-class-name="des-width">
{{formData.result}}
</el-descriptions-item>
</el-descriptions>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogClose">关闭</el-button>

View File

@ -37,7 +37,7 @@ import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@/store/modules/user';
import { ElMessage } from 'element-plus';
import { ResultEnum } from '@/enums/httpEnum';
import { getInfoCaptcha } from '@/api/system/user';
import { getInfoCaptcha,sendSms } from '@/api/system/user';
import { PageEnum } from '@/enums/pageEnum';
import { SafetyCertificateOutlined } from '@vicons/antd';
const captchaImg = ref('')
@ -56,7 +56,6 @@ const loading = ref(false);
const codeMsg: any = ref('获取验证码');
const isGetCode = ref(false);
const autoLogin = ref(true);
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
const formInline = reactive({
@ -73,11 +72,12 @@ const userStore = useUserStore();
const router = useRouter();
const route = useRoute();
function getCode() {
async function getCode() {
if (!formInline.mobile) {
formRef.value.validateField('mobile')
return
}
await sendSms({mobile:formInline.mobile})
codeMsg.value = 60;
isGetCode.value = true;
let time = setInterval(() => {

View File

@ -37,7 +37,15 @@
</template>
删除
</el-button>
<el-upload
<el-button type="primary" @click="importVisible=true" v-perm="['sys:user:import']">
<template #icon>
<el-icon class="el-input__icon">
<Upload />
</el-icon>
</template>
导入
</el-button>
<!-- <el-upload
ref="upload"
action="/api/user/import"
:headers="uploadHeaders"
@ -56,7 +64,7 @@
</template>
导入
</el-button>
</el-upload>
</el-upload> -->
<el-button type="primary" @click="handleExport" :loading="exportLoading" :disabled="exportLoading" v-perm="['sys:user:export']">
<template #icon>
<el-icon class="el-input__icon">
@ -76,31 +84,30 @@
@success="reloadTable('noRefresh')"
>
</editDialog>
<userUpload v-if="importVisible" v-model:visible="importVisible" @success="reloadTable()"/>
</PageWrapper>
</template>
<script lang="ts" setup>
import { h, nextTick, reactive, ref ,defineAsyncComponent} from 'vue';
import { ColProps, UploadInstance } from 'element-plus';
import { ColProps } from 'element-plus';
import { TableAction } from '@/components/Table';
import { useForm } from '@/components/Form/index';
import { getUserList,userDelete,userBatchDelete,userExport,resetPwd } from '@/api/system/user';
import {message,confirm,loading, closeLoading} from "@/utils/auth";
import {message,confirm} from "@/utils/auth";
import { columns } from './columns';
import { schemas } from './querySchemas';
import { useUserStore } from '@/store/modules/user';
import {downloadByData} from '@/utils/file/download';
const userId = ref(0);
const tableRef = ref();
const editVisible = ref(false)
const importVisible = ref(false)
const selectionData = ref([])
const upload = ref<UploadInstance>();
const exportLoading=ref(false)
const editDialog = defineAsyncComponent(() =>
import('./edit.vue'))
const uploadHeaders = reactive({
authorization:useUserStore().getToken
});
const userUpload = defineAsyncComponent(() =>
import('./userUpload.vue'))
const formParams = reactive({
realname: '',
role:'',
@ -216,34 +223,7 @@
userId.value =0
editVisible.value = true
}
const beforeUpload = (file: UploadFile) => {
const isLt2M = file.size / 1024 / 1024 < 200;
if (!isLt2M) {
message("大小不能超过200MB!", "error");
return false;
}
if (!/\.(xlsx|xls|XLSX|XLS)$/.test(file.name)) {
message("请上传.xlsx .xls", "error");
return false;
}
loading("上传中");
return true;
};
const onSuccess = (file: UploadFile) => {
upload.value!.clearFiles();
closeLoading();
if (file.code == 0) {
message("导入成功");
reloadTable()
} else {
message(file.msg ? file.msg : "导入失败", "error");
}
};
const onError = () => {
upload.value!.clearFiles();
closeLoading();
message("导入失败", "error");
};
//
const handleExport = async()=>{
exportLoading.value=true

View File

@ -0,0 +1,103 @@
<template>
<el-dialog v-model="props.visible" :title="dialogTitle" width="450" :close-on-click-modal="false"
:before-close="dialogClose">
<el-upload
style="width:400px"
drag
ref="upload"
action="/api/user/import"
:headers="uploadHeaders"
:on-error="onError"
:on-success="onSuccess"
:before-upload="beforeUpload"
:show-file-list="false"
:limit="1"
v-perm="['sys:user:import']"
>
<el-icon class="el-icon--upload" style="color:#165DFF;"><upload-filled /></el-icon>
<div class="el-upload__text">
点击或将文件拖拽到这里上传
</div>
</el-upload>
<div style="margin-top:20px;">
<span>只能上传 xlsxlsx 文件,</span>
<el-button type="primary" link @click="handleDownload">下载模板
</el-button>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { getTemplateByCode } from '@/api/system/user';
import { computed,shallowRef,ref,reactive } from "vue";
import { UploadInstance } from 'element-plus';
import { FormInstance } from "element-plus";
import { message,loading,closeLoading } from "@/utils/auth";
const formRef = shallowRef<FormInstance>();
import { useUserStore } from '@/store/modules/user';
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false
},
id: {
type: Number,
required: true,
default: 0
}
});
const upload = ref<UploadInstance>();
const uploadHeaders = reactive({
authorization:useUserStore().getToken
});
const emit = defineEmits(["success", "update:visible"]);
const dialogTitle = computed(() => {
return '导入用户';
});
const dialogClose = () => {
emit("update:visible", false);
};
const beforeUpload = (file: UploadFile) => {
const isLt2M = file.size / 1024 / 1024 < 200;
if (!isLt2M) {
message("大小不能超过200MB!", "error");
return false;
}
if (!/\.(xlsx|xls|XLSX|XLS)$/.test(file.name)) {
message("请上传.xlsx .xls", "error");
return false;
}
loading("上传中");
return true;
};
const onSuccess = (file: UploadFile) => {
upload.value!.clearFiles();
closeLoading();
if (file.code == 0) {
message("导入成功");
emit("update:visible", false);
emit("success");
} else {
message(file.msg ? file.msg : "导入失败", "error");
}
};
const onError = () => {
upload.value!.clearFiles();
closeLoading();
message("导入失败", "error");
};
const handleDownload = async()=>{
const res =await getTemplateByCode('user_import')
window.open(res.filePath)
}
</script>