Browse Source

v 1.0

厅长信箱
wxc 1 year ago
parent
commit
fc20a5063f
  1. 4
      .gitignore
  2. 1
      package.json
  3. 26
      src/api/admin.js
  4. 8
      src/api/dept.js
  5. 5
      src/api/detail.js
  6. 15
      src/api/holiday.js
  7. 9
      src/api/mail.js
  8. 19
      src/api/mail12345.js
  9. 13
      src/api/user.js
  10. 77
      src/assets/style/element.scss
  11. 25
      src/assets/style/style.scss
  12. 384
      src/components/AddMail.vue
  13. 8
      src/components/Detail.vue
  14. 189
      src/components/HolidayList.vue
  15. 140
      src/components/LoginView.vue
  16. 301
      src/components/MailEtl.vue
  17. 275
      src/components/ManageMail.vue
  18. 281
      src/components/ManageUser.vue
  19. 63
      src/layout/Index.vue
  20. 8
      src/main.js
  21. 31
      src/router/index.js
  22. 2
      src/stores/useTokenStore.js
  23. 22
      src/util/cache.js
  24. 141
      src/util/request.js
  25. 10
      src/util/token.js
  26. 153
      src/views/Admin.vue
  27. 126
      src/views/Login.vue
  28. 325
      src/views/Mail12345.vue
  29. 302
      src/views/ManageMail.vue
  30. 152
      src/views/ManageUser.vue
  31. 2
      vite.config.js

4
.gitignore vendored

@ -6,4 +6,6 @@
node_modules
*.mjs
*.mjs
admin

1
package.json

@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"build:prod": "vite build --mode prod",
"preview": "vite preview"
},
"dependencies": {

26
src/api/admin.js

@ -0,0 +1,26 @@
import { get, post} from "@/util/request"
export function login(body) {
return post({
url: '/admin/login', body
})
}
export function self() {
return get({
url: '/admin/self'
})
}
export function adminList() {
return get({
url: '/admin'
})
}
export function addAdmin(body) {
return post({
url: '/admin', body
})
}

8
src/api/dept.js

@ -1,8 +1,8 @@
import { get, post} from "@/util/request"
import { get } from "@/util/request"
export function listSecond() {
return get('/system/dept/second/list')
return get({
url: '/system/dept/second/list'
})
}

5
src/api/detail.js

@ -1,4 +1,7 @@
import { get, post} from "@/util/request"
export function getdetail(id) {
return post('/mailbox/detail',id)
return post({
url: '/mailbox/detail?id=' + id
})
}

15
src/api/holiday.js

@ -1,10 +1,17 @@
import { get, post} from "@/util/request"
export function getholiday(search) {
return get('/outer/holiday/getholiday?search='+search)
return get({
url: '/outer/holiday/getholiday?search='+search
})
}
export function showholiday() {
return get('/outer/holiday/showholiday')
return get({
url: '/outer/holiday/show-holiday'
})
}
export function saveholiday(search) {
return post('/outer/holiday/saveholiday',search)
export function refreshHoliday(body) {
return post({
url: '/outer/holiday/refresh-holiday',
body
})
}

9
src/api/mail.js

@ -1,8 +1,7 @@
import { get, post} from "@/util/request"
export function addMail(body) {
return post('/mailbox/add', body)
export function listMail(query) {
return get({
url: '/mail/list', query
})
}

19
src/api/mail12345.js

@ -0,0 +1,19 @@
import { get, post } from "@/util/request"
export function mail12345List(query) {
return get({
url: '/mail12345', query
})
}
export function mail12345Import(body) {
return post({
url: '/mail12345/import', body
})
}
export function addMail12345(body) {
return post({
url: '/mail12345/add', body
})
}

13
src/api/user.js

@ -0,0 +1,13 @@
import { get, post} from "@/util/request"
export function listUser(query) {
return get({
url: '/user/list', query
})
}
export function delUser(id) {
return post({
url: '/user/del?id=' + id
})
}

77
src/assets/style/element.scss

@ -1,8 +1,79 @@
// @/styles/element/index.scss
@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"primary": (
"base": #162582,
)
),
'success': (
'base': #064D00,
),
'danger': (
'base': #F60000,
),
'warning': (
'base': #D05200,
),
)
);
);
.el-date-editor.el-input__wrapper {
--el-date-editor-daterange-width: 260px;
}
.el-select {
min-width: 80px;
}
.el-select-dropdown .el-select-dropdown__wrap {
max-height: 380px;
}
.el-radio {
color: #333;
}
.el-button--primary.is-link>span {
font-weight: bold;
}
div.el-table {
--el-table-header-bg-color: #EBEEFC;
--el-table-header-text-color: var(--primary-color);
.el-table__header .el-table__cell {
padding: 20px 0;
font-size: 15px;
}
.text-no-ellipsis .cell {
text-overflow: clip;
}
}
div.el-card {
border: none;
}
.el-dialog {
.el-dialog__header {
margin-right: 0;
.el-dialog__headerbtn {
font-size: 20px;
&:hover .el-dialog__close {
color: var(--danger-color);
}
}
}
&.dialog-header-nopadding {
&>.el-dialog__header {
padding: 0;
margin-right: 0;
}
&>.el-dialog__body {
padding-left: 0;
padding-right: 0;
}
}
}

25
src/assets/style/style.scss

@ -1,8 +1,13 @@
@import 'element-plus/theme-chalk/el-message.css';
@import 'element-plus/theme-chalk/el-message-box.css';
:root:root {
--primary-color: #184DCF;
--primary-color: #162582;
--van-blue: var(--primary-color);
--van-tabs-bottom-bar-width: 60px;
--background-color: #ededed;
--header-height: 80px;
--aside-width: 7.3vw;
}
body {
@ -96,9 +101,12 @@ svg+span {
.text-wrap {
white-space: pre-wrap;
}
.text-danger {
color: red;
}
.container {
padding: 18px 36px;
padding: 20px;
}
.pointer:hover {
@ -137,6 +145,9 @@ svg+span {
margin-right: 20px;
}
.mt-4 {
margin-top: 4px;
}
.mt-8 {
margin-top: 8px;
}
@ -160,6 +171,9 @@ svg+span {
.mb-20 {
margin-bottom: 20px;
}
.mb-24 {
margin-bottom: 24px;
}
.mb-40 {
margin-bottom: 40px;
@ -208,4 +222,11 @@ svg+span {
height: 100vh;
background-color: var(--background-color);
overflow: auto;
}
.row {
display: flex;
.col-12 {
width: 50%;
}
}

384
src/components/AddMail.vue

@ -1,178 +1,158 @@
<template>
<el-dialog
width="50vw"
align-center
title="自建信件"
>
<el-form
ref="formRef"
:model="form"
label-width="200px"
:rules="rules"
style="width: 100%"
>
<el-divider />
<el-row class="title-label">
<el-col>
<span class="main-label">联系人信息</span>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item
label="姓名"
class="info-input"
prop="contactName"
required
>
<el-input
v-model="form.contactName"
placeholder="请输入姓名"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="性别"
class="info-input"
prop="contactSex"
>
<el-select
v-model="form.contactSex"
placeholder="请选择性别"
>
<el-option label="男" value="M"></el-option>
<el-option label="女" value="F"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item
label="证件号码"
class="info-input"
prop="contactIdCard"
>
<el-input
v-model="form.contactIdCard"
placeholder="请输入证件号码"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="来电电话"
class="info-input"
prop="contactPhone"
>
<el-input
v-model="form.contactPhone"
placeholder="请输入来电电话"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="来电时间">
<el-date-picker v-model="form.phoneTime" value-format="YYYY-MM-DD HH:mm:ss"
type="datetime" format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="来源"
class="info-input"
prop="source"
>
<el-input
v-model="form.source"
placeholder="请输入来源"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="话务类型"
class="info-input"
prop="contactType"
>
<el-input
v-model="form.contactType"
placeholder="请输入话务类型"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="案件类型"
class="info-input"
prop="caseType"
<el-dialog width="960px" title="新增12345投诉">
<el-form
ref="formRef"
:model="form"
label-width="200px"
:rules="rules"
style="width: 100%"
>
<el-divider />
<el-row class="title-label">
<el-col>
<span class="main-label">联系人信息</span>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item
label="姓名"
class="info-input"
prop="contactName"
required
>
<el-input
v-model="form.contactName"
placeholder="请输入姓名"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="性别"
class="info-input"
prop="contactSex"
>
<el-select
v-model="form.contactSex"
placeholder="请选择性别"
>
<el-input
v-model="form.caseType"
placeholder="请输入案件类型"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider />
<el-row class="title-label">
<el-col>
<span class="main-label">信件内容</span>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item
label="信件内容"
<el-option label="男" value="M"></el-option>
<el-option label="女" value="F"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item
label="证件号码"
class="info-input"
prop="contactIdCard"
>
<el-input
v-model="form.contactIdCard"
placeholder="请输入证件号码"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="来电电话"
class="info-input"
prop="contactPhone"
>
<el-input
v-model="form.contactPhone"
placeholder="请输入来电电话"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="来电时间">
<el-date-picker
v-model="form.phoneTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
prop="content"
>
<el-input
type="textarea"
v-model="form.content"
placeholder="请您尽量完整的描述您的信件内容,如发生事件、涉及单位、设计对象姓名、警号以及具体事项"
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- <el-row>
<el-form-item label="上传附件">
<Upload v-model="form.fileList" />
placeholder="请选择"
/>
</el-form-item>
</el-row> -->
<div
style="
width: 100%;
display: flex;
justify-content: flex-end;
"
>
<el-button
type="primary"
@click="handleSubmit"
style="height: 40px"
>提交信件</el-button
</el-col>
<el-col :span="12">
<el-form-item label="来源" class="info-input" prop="source">
<el-input
v-model="form.source"
placeholder="请输入来源"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="话务类型"
class="info-input"
prop="contactType"
>
</div>
</el-form>
<el-input
v-model="form.contactType"
placeholder="请输入话务类型"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="案件类型"
class="info-input"
prop="caseType"
>
<el-input
v-model="form.caseType"
placeholder="请输入案件类型"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider />
<el-row class="title-label">
<el-col>
<span class="main-label">信件内容</span>
</el-col>
</el-row>
<el-row class="mb-20">
<el-col>
<el-form-item
label="信件内容"
style="width: 100%"
prop="content"
>
<el-input
type="textarea"
v-model="form.content"
placeholder="请您尽量完整的描述您的信件内容,如发生事件、涉及单位、设计对象姓名、警号以及具体事项"
></el-input>
</el-form-item>
</el-col>
</el-row>
<div style="width: 100%; display: flex; justify-content: flex-end">
<el-button
type="primary"
@click="handleSubmit"
style="height: 40px"
>提交</el-button
>
</div>
</el-form>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { addMail12345 } from '@/api/mail12345'
import type { FormInstance, FormRules } from "element-plus";
import { listSecond } from "../api/dept";
import { addMail } from "../api/mail";
import { useTokenStore } from '../stores/useTokenStore'
const tokens = useTokenStore()
const accessToken = tokens.access_token;
import { depts } from "../stores/dept";
import { request } from '../util/axios_config'
import { ElMessage } from "element-plus";
interface FormData {
source: string;
contactName: string;
@ -185,104 +165,56 @@ interface FormData {
content: string;
fileList: any[];
attachments: String;
}
const { VITE_API_URL } = process.env;
const formRef = ref<FormInstance>();
const form = ref({
contactName: "",
contactSex: "",
contactIdCard: "",
contactPhone: "",
caseNumber: "",
involvedDeptId:'',
involvedDeptId: "",
involvedDeptName: "",
content: "",
attachments: "",
phoneTime:"",
phoneTime: "",
source: "",
contactType:"",
caseType:""
contactType: "",
caseType: "",
});
const rules = reactive<FormRules<FormData>>({
contactName: [
{ required: true, message: "请输入姓名" },
],
contactPhone: [
{ required: true, message: "请输入手机号码"},
],
contactIdCard: [
{ required: true, message: "请输入身份证"},
],
contactName: [{ required: true, message: "请输入姓名" }],
contactPhone: [{ required: true, message: "请输入手机号码" }],
content: [
{
required: true,
message:
"请您尽量完整的描述您的信件内容,如发生事件、涉及单位、设计对象姓名、警号以及具体事项",
trigger: "blur"
trigger: "blur",
},
],
});
// const { optionsData } = useDictOptions<{
// dept: any[];
// }>({
// dept: {
// api: listSecond,
// },
// });
const emit = defineEmits(["success", "close"]);
const selectMediaId = (depart)=>{
for (const i of depts) {
if(i.text == depart){
form.value.involvedDeptId=i.value;
form.value.involvedDeptName=i.text;
}
}
// console.log(mailData.value.involved_dept_id, 'select')
// console.log(depart, 'ID')
}
const handleSubmit = () => {
formRef.value.validate((valid: boolean) => {
if (valid) {
form.value.attachments = JSON.stringify(form.value.fileList);
console.log("accessToken"+accessToken)
// addMail({data:form.value,headers: { "Authorization": accessToken}}).then(() => {
// emit("success");
// emit("close");
// feedback.msgSuccess("");
// formRef.value.resetFields()
// form.value.fileList = []
// form.value.involvedDeptName = ''
// });
const url = VITE_API_URL +'/mailbox/add'
request({
url: url,
method: 'POST',
data: {MailBo:form.value} ,
headers: { 'Content-Type': 'application/json'}
}).then(() => {
addMail12345(form.value).then(() => {
emit("success");
emit("close");
formRef.value.resetFields()
form.value.fileList = []
form.value.involvedDeptName = ''
formRef.value.resetFields();
form.value.fileList = [];
form.value.involvedDeptName = "";
ElMessage.success("操作成功");
});
}
});
};
function handleDeptChange(val) {
const dept = optionsData.dept.find((item) => item.id === val);
form.value.involvedDeptName = dept.name;
}
</script>
<style lang="scss" scoped>
.main-label {

8
src/components/Detail.vue

@ -70,15 +70,10 @@
</el-col>
</el-row>
</main>
</el-dialog>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import router from '../router';
import { request } from '../util/axios_config'
const { VITE_API_URL } = process.env;
const emit = defineEmits(["success", "close"]);
const props = defineProps({
@ -104,9 +99,6 @@ const form = ref({
satisfaction: '',
});
onMounted(() => {
})
async function getDetail() {
const url = VITE_API_URL +'/mailbox/detail'
request({

189
src/components/HolidayList.vue

@ -1,25 +1,52 @@
<template>
<div class="app-container" v-loading="loading">
<el-form :inline="true" style="display: flex; justify-content: left;margin-top: 10px;margin-right: 70px; margin-left: 50px;">
<el-form
:inline="true"
style="
display: flex;
justify-content: left;
margin-top: 10px;
margin-right: 70px;
margin-left: 50px;
"
>
<el-form-item label="请选择年份">
<el-date-picker type="year" v-model="currenYear" placeholder="请选择年份" value-format="YYYY"></el-date-picker>
<el-date-picker
type="year"
v-model="currenYear"
placeholder="请选择年份"
value-format="YYYY"
></el-date-picker>
</el-form-item>
<el-button type="primary" @click="searchHoliday">查询节假日</el-button>
<el-button type="primary" @click="refreshHoliday">同步节假日</el-button>
<el-button type="primary" @click="searchHoliday"
>查询节假日</el-button
>
<el-button type="primary" @click="handleRefreshHoliday"
>同步节假日</el-button
>
</el-form>
<el-scrollbar class="scrollbar-container">
<el-row>
<el-col v-for="(item, index) in defaultCals " :key="item.cal" :span="7"
style="margin-right: 10px;margin-left: 50px;">
<el-card shadow="hover" style="margin-bottom: 20px;">
<el-calendar v-model="item.cal" class="holiday" style="pointer-events:none">
<el-col
v-for="(item, index) in defaultCals"
:key="item.cal"
:span="7"
style="margin-right: 10px; margin-left: 50px"
>
<el-card shadow="hover" style="margin-bottom: 20px">
<el-calendar
v-model="item.cal"
class="holiday"
style="pointer-events: none"
>
<!-- <el-calendar v-model="item.cal" class="holiday"> -->
<template #date-cell="{ data }">
<div class="holiday-cell" v-show="data.type === 'current-month'"
:id="index + '-' + data.day"
:class="{ 'is-holiday': ifHoliday(data.day) }, { 'is-adjust': ifAdjustDay(data.day) }">
{{ formatDay(data.day).split('-')[2] }}
<div
class="holiday-cell"
v-show="data.type === 'current-month'"
:active="dayType.findIndex(item => item.date === data.day && item.holidayFlag === 'Y') !== -1"
>
{{ formatDay(data.day).split("-")[2] }}
</div>
</template>
</el-calendar>
@ -31,46 +58,35 @@
</template>
<script setup>
import { ref, onMounted, onBeforeMount } from 'vue';
import { ElMessage } from 'element-plus'
import { request } from '../util/axios_config'
const { VITE_API_URL } = process.env;
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { showholiday, refreshHoliday } from "@/api/holiday";
const currenYear = ref('');
const currenYear = ref("");
const loading = ref(true);
const defaultCals = ref([]);
const dayType = ref({
date: '',
type: ''
});
const dayType = ref([]);
const getDateData = () => {
const url = VITE_API_URL +'/outer/holiday/show-holiday'
request({
url: url,
method: 'GET'
}).then(res => {
dayType.value = res.data;
showholiday().then((data) => {
console.log(data)
dayType.value = data;
loading.value = false;
}).catch(err => {
console.log(err);
});
}
};
onBeforeMount(() => {
getDateData();
})
const formatDay = (day) => {
// day "2023-09-05"
const [year, month, dayWithZero] = day.split('-');
// 使 parseInt 0 toString
const formattedDay = parseInt(dayWithZero, 10).toString();
// 0
// const formattedDay = dayWithZero.padStart(2, '0');
//
return `${year}-${month}-${formattedDay}`;
}
getDateData();
const formatDay = (day) => {
// day "2023-09-05"
const [year, month, dayWithZero] = day.split("-");
// 使 parseInt 0 toString
const formattedDay = parseInt(dayWithZero, 10).toString();
// 0
// const formattedDay = dayWithZero.padStart(2, '0');
//
return `${year}-${month}-${formattedDay}`;
};
onMounted(() => {
let nowYear = new Date().getFullYear();
initCalendar(nowYear);
@ -79,8 +95,7 @@ onMounted(() => {
const initCalendar = (year) => {
const months = [];
for (let i = 0; i < 12; i++)
months.push({ cal: new Date(year, i, 1) });
for (let i = 0; i < 12; i++) months.push({ cal: new Date(year, i, 1) });
defaultCals.value = months;
};
@ -93,73 +108,49 @@ const searchHoliday = () => {
loading.value = false;
}, 1000);
} else {
ElMessage.info('其他年份尚未拥有数据')
ElMessage.info("其他年份尚未拥有数据");
loading.value = false;
}
};
const refreshHoliday = () => {
console.log("currenYear.value"+currenYear.value);
const handleRefreshHoliday = () => {
if (currenYear.value == "") {
ElMessage.info('请输入年份')
ElMessage.info("请输入年份");
loading.value = false;
return;
return;
}
loading.value = true;
const url = VITE_API_URL +'/outer/holiday/refresh-holiday'
request({
url: url,
method: 'POST',
data: { year: currenYear.value },
headers: { 'Content-Type': 'application/json' }
}).then(res => {
dayType.value = res.data.holidayList;
refreshHoliday({ year: currenYear.value }).then((data) => {
dayType.value = data.holidayList;
setTimeout(() => {
loading.value = false;
}, 1000);
ElMessage.success('同步成功')
}).catch(err => {
ElMessage.error('同步失败')
console.log(err);
ElMessage.success("同步成功");
});
};
const ifHoliday = (day) => {
for (let i = 0; i < dayType.value.length; i++)
if (dayType.value[i].date === day && dayType.value[i].holidayFlag === 'Y')
if (
dayType.value[i].date === day &&
dayType.value[i].holidayFlag === "Y"
)
console.log(day)
return true;
console.log(false)
return false;
};
const ifAdjustDay = (day) => {
for (let i = 0; i < dayType.value.length; i++)
if (dayType.value[i].date === day && dayType.value[i].holidayFlag === 'N')
if (
dayType.value[i].date === day &&
dayType.value[i].holidayFlag === "N"
)
return true;
return false;
};
}
// const ifCurrentDay = (day, index) => {
// var today = new Date();
// //
// var year = today.getFullYear();
// var month = ('0' + (today.getMonth() + 1)).slice(-2); // 1
// var dayNumber = ('0' + today.getDate()).slice(-2); //
// // if (String(year) === currenYear && String(index + 1) === month && dayNumber == String(day))
// if (String(year) === currenYear && '02' === month && '11' == String(day))
// return true;
// else
// return false;
// };
const dayContent = (day) => {
// console.log('Comparing dayType.value.date:', dayType.value.date, 'with day:', day);
for (let i = 0; i < dayType.value.length; i++)
if (dayType.value[i].date === day)
return dayType.value[i].detail;
return '';
};
</script>
<style>
@ -175,7 +166,7 @@ const dayContent = (day) => {
padding: 1px;
width: 100%;
height: 40px;
background: #FFF;
background: #fff;
}
.select-month .el-calendar-day {
@ -220,26 +211,12 @@ const dayContent = (day) => {
white-space: nowrap;
}
.is-holiday {
.holiday-cell[active=true] {
border-radius: 50px 50px 50px 50px;
background: #298F17;
background: #298f17;
}
/* .is-adjust {
border-radius: 5px;
background: linear-gradient(145deg, #d9a2a2, #ffc1c1);
box-shadow: 5px 5px 10px #bc8c8c,
-5px -5px 10px #ffdcdc;
} */
.scrollbar-container {
height: calc(100vh - 150px);
}
/* .is-today {
border-radius: 5px;
background: linear-gradient(145deg, #d9a2a2, #ffc1c1);
box-shadow: 5px 5px 10px #bc8c8c,
-5px -5px 10px #ffdcdc;
} */
</style>

140
src/components/LoginView.vue

@ -1,140 +0,0 @@
<template>
<div class="wrapper">
<header class="text-center">
<img src="/imgs/login_logo1.png" alt="">
</header>
<div class="box flex v-center">
<div class="left text-center">
<img src="/imgs/pic.png" alt="" />
</div>
<div class="right mb-40">
<el-form :model="login" size="large">
<h1>用户登录</h1>
<el-form-item label="账号">
<el-input v-model="login.account" placeholder="请输入手机号" clearable @input="inputCheck" style="--el-input-height: 50px">
<template #prefix>
<icon name="el-icon-UserFilled" :size="33" color="#c0c5e2" />
</template>
</el-input>
</el-form-item>
<el-form-item label="密码">
<el-container>
<el-input v-model="login.captcha" placeholder="请输入密码" type="password" show-password clearable @input="inputCheck" style="--el-input-height: 50px">
<template #prefix>
<icon name="local-icon-lock-fill" :size="36" />
</template>
</el-input>
<!-- <el-button type="primary" @click="sendCaptcha">发送验证码</el-button> -->
</el-container>
</el-form-item>
<div class="mt-30 mb-10">
<el-button type="primary" @click="loginIn" style="width: 100%;">登录</el-button>
</div>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { request } from '../util/axios_config'
import router from '../router'
import { ElMessage } from 'element-plus'
import { useTokenStore } from '../stores/useTokenStore'
const { VITE_API_URL } = process.env;
const tokens = useTokenStore()
const login = reactive({
account: '',
captcha: ''
})
const inputCheck = () => {
login.account = login.account.replace(/[^\d]/g, '')
}
// const sendCaptcha = () => {
// request({
// url: '/api/captcha',
// method: 'POST',
// data: login,
// headers: { 'Content-Type': 'application/json' }
// }).then(res => {
// ElMessage.success('')
// login.captcha = res.data
// }).catch(err => {
// ElMessage.error('')
// console.log(err)
// })
// }
const loginIn = () => {
const url = VITE_API_URL +'/login'
request({
url: url,
method: 'POST',
data: login,
headers: { 'Content-Type': 'application/json' }
}).then(res => {
if (res.data.stateCode === 200) {
tokens.setAccessToken(res.data.accessToken)
tokens.setRefreshToken(res.data.refreshToken)
console.log("localStorage.getItem('user'):" + localStorage.getItem('user'))
ElMessage.success('登录成功')
router.push('/')
}else{
ElMessage.error('登录失败,账号或密码错误')
}
}).catch(err => {
ElMessage.error('登录失败')
console.log(err)
})
}
</script>
<style lang="scss" scoped>
.wrapper {
height: 100vh;
background-image: url("/imgs/bg.png");
background-size: cover;
header {
padding-top: 3.2vh;
margin-bottom: 8vh;
img {
height: 91px;
}
}
.box {
--login-box-width: 576px;
width: 80%;
margin: auto;
.left {
width: calc(100% - var(--login-box-width));
img {
width: 33vw;
}
}
.right {
background-color: #fff;
border: 18px solid #4169ea;
width: var(--login-box-width);
padding: 40px;
box-sizing: border-box;
h1 {
color: var(--primary-color);
font-size: 32px;
margin-top: 0;
margin-bottom: 32px;
}
}
}
}
</style>

301
src/components/MailEtl.vue

@ -1,216 +1,239 @@
<template>
<div style="width: 90vw;margin: 0 auto;">
<el-form :model="form" label-width="150px" style="margin-top: 20px; margin-right: -50px;;">
<div class="container">
<el-form :model="form" label-width="150px">
<el-row>
<el-col :span="6">
<el-form-item label="数据库名">
<el-select
v-model="MailTab"
placeholder="Select"
size="small"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="etl编号">
<el-input v-model="form.id" placeholder="请输入etl编号" max-length="200px"></el-input>
<el-form-item label="数据库名">
<el-select
v-model="MailTab"
placeholder="Select"
size="small"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="编号">
<el-input
v-model="form.id"
placeholder="请输入编号"
max-length="200px"
></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="信件编号">
<el-input v-model="form.mailId" placeholder="请输入信件编号"></el-input>
<el-input
v-model="form.mailId"
placeholder="请输入信件编号"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<el-form-item label="是否成功">
<el-input v-model="form.success" placeholder=""></el-input>
<el-input
v-model="form.success"
placeholder=""
></el-input>
</el-form-item>
</el-col>
<el-col :span="3"></el-col>
<el-col :span="14" :style="{ display: 'inline-flex', alignItems: 'center' }">
<el-row>
<el-col :span="6"></el-col>
<el-col :span="6">
<el-button type="primary" @click="search" class="under-btn">搜索</el-button></el-col>
<el-col :span="6">
<el-button type="default" style="position: relative; left: 100px;" @click="reset" class="under-btn">重置</el-button></el-col>
</el-row>
</el-col>
</el-row>
<div class="flex end mb-24">
<el-button type="primary" @click="search" class="under-btn"
>搜索</el-button
>
<el-button
type="default"
@click="reset"
class="under-btn"
>重置</el-button
>
</div>
</el-form>
<div class="table-box" ref="tableBoxHeight" v-loading="loading">
<el-table :data="tableData" border :height="tableHeight" table-layout="fixed"
:header-cell-style="{ 'background-color': '#EBEEFC', 'color': '#1D2C86' }">
<el-table-column fixed="left" prop="id" label="etl编号" width="200px">
<el-table
:data="tableData"
border
:height="tableHeight"
table-layout="fixed"
:header-cell-style="{
'background-color': '#EBEEFC',
color: '#1D2C86',
}"
>
<el-table-column
fixed="left"
prop="id"
label="编号"
width="80px"
>
</el-table-column>
<el-table-column fixed="left" prop="mailId" label="信件编号" width="200px">
<el-table-column
fixed="left"
prop="mailId"
label="信件编号"
width="200px"
>
</el-table-column>
<el-table-column prop="success" label="是否成功" width="100px">
<template #default="{ row }">
<span>{{ row.success ? "成功" : "失败" }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建日期" width="120px">
<el-table-column
prop="createTime"
label="创建日期"
width="120px"
>
</el-table-column>
<el-table-column prop="errMsg" label="错误信息" width="800px">
<el-table-column prop="errMsg" label="错误信息">
</el-table-column>
</el-table>
</div>
<div style="display: flex; justify-content: center;position: relative;top: 20px;">
<el-pagination background @size-change="handleSizeChange" @current-change="handlePageChange"
:current-page="pageData.currentPage" :page-sizes="[4, 10, 20, 40, 50]" :page-size="pageData.pageSize"
layout="total,sizes, prev, pager, next, jumper" :total="pageData.totalSize">
</el-pagination>
<div class="flex end mt-10">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handlePageChange"
:current-page="pageData.currentPage"
:page-sizes="[4, 10, 20, 40, 50]"
:page-size="pageData.pageSize"
layout="total,sizes, prev, pager, next, jumper"
:total="pageData.totalSize"
>
</el-pagination>
</div>
</div>
</template>
<script setup>
import { request } from '../util/axios_config'
import { onMounted, ref } from 'vue';
import router from '../router';
const MailTab = ref('mail_etl')
import { request } from "../util/axios_config";
import { onMounted, ref } from "vue";
import router from "../router";
const MailTab = ref("mail_etl");
const { VITE_API_URL } = process.env;
const options = [
{
value: 'mail_etl',
label: 'mail_etl',
},
{
value: 'mail_evaluate_etl',
label: 'mail_evaluate_etl',
},
]
{
value: "mail_etl",
label: "mail_etl",
},
{
value: "mail_evaluate_etl",
label: "mail_evaluate_etl",
},
];
const loading = ref(true);
const form = ref({
id: '',
mailId: '',
success: '',
})
id: "",
mailId: "",
success: "",
});
// tableDataaxios
const tableData = ref([])
const tableData = ref([]);
const pageData = ref({
currentPage: 1,
pageSize: 8,
totalSize: 0
})
totalSize: 0,
});
const tableBoxHeight = ref(null)
const tableHeight = ref('100%')
const tableBoxHeight = ref(null);
const tableHeight = ref("100%");
const flexColumnWidth = (label, prop) => {
const width = Math.max(label.length * 12, 120)
return `${width}px`
}
const width = Math.max(label.length * 12, 120);
return `${width}px`;
};
const handleResponse = (response) => {
tableData.value=[];
if(MailTab.value=='mail_etl'){
console.log("aaaa")
tableData.value = [];
if (MailTab.value == "mail_etl") {
console.log("aaaa");
tableData.value = response.data.mails;
}else{
console.log("bbbb")
} else {
console.log("bbbb");
tableData.value = response.data.evaMails;
}
tableData.value.forEach(item => {
item.createTime = item.createTime.split('T')[0]
})
tableData.value.forEach((item) => {
item.createTime = item.createTime.split("T")[0];
});
pageData.value.totalSize = response.data.pageSet.totalSize;
loading.value = false;
}
};
const makeRequest = (requestData, callback) => {
const data = JSON.stringify(requestData)
const url = VITE_API_URL +'/mailetl/list-submit'
const data = JSON.stringify(requestData);
const url = VITE_API_URL + "/mailetl/list-submit";
request({
url: url,
method: 'POST',
method: "POST",
data: data,
headers: { 'Content-Type': 'application/json' }
}).then(callback)
.catch(function (error) { console.log(error) })
}
headers: { "Content-Type": "application/json" },
})
.then(callback)
.catch(function (error) {
console.log(error);
});
};
const updateData = (requestData) => {
makeRequest(requestData, function (response) {
handleResponse(response)
})
}
handleResponse(response);
});
};
onMounted(() => {
const requestData = {
formData: form.value,
selectData:MailTab.value,
pageData: pageData.value
}
updateData(requestData)
})
selectData: MailTab.value,
pageData: pageData.value,
};
updateData(requestData);
});
const handleSizeChange = (size) => {
const requestData = {
formData: form.value,
selectData:MailTab.value,
pageData: pageData.value
}
pageData.value.pageSize = size
updateData(requestData)
}
selectData: MailTab.value,
pageData: pageData.value,
};
pageData.value.pageSize = size;
updateData(requestData);
};
const handlePageChange = (currentPage) => {
const requestData = {
formData: form.value,
selectData:MailTab.value,
pageData: pageData.value
}
pageData.value.currentPage = currentPage
updateData(requestData)
}
selectData: MailTab.value,
pageData: pageData.value,
};
pageData.value.currentPage = currentPage;
updateData(requestData);
};
const search = () => {
const requestData = {
formData: form.value,
selectData:MailTab.value,
pageData: pageData.value
}
updateData(requestData)
}
selectData: MailTab.value,
pageData: pageData.value,
};
updateData(requestData);
};
const reset = () => {
form.value = {
id: '',
mailId: '',
success: '',
}
}
</script>
<style scoped>
.under-btn {
margin-right: 30px;
margin-bottom: 5px;
}
.table-box {
/* 全屏时顶部元素总和316px */
height: calc(100vh - 336px);
}
</style>
id: "",
mailId: "",
success: "",
};
};
</script>

275
src/components/ManageMail.vue

@ -1,275 +0,0 @@
<template>
<div style="width: 90vw;margin: 0 auto;">
<el-form :model="form" label-width="150px" style="margin-top: 20px; margin-right: -50px;;">
<el-row>
<el-col :span="6">
<el-form-item label="群众姓名">
<el-input v-model="form.contactName" placeholder="请输入群众姓名" max-length="200px"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="群众手机号">
<el-input v-model="form.contactPhone" placeholder="请输入群众手机号"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="群众身份证号">
<el-input v-model="form.contactIdCard" placeholder="请输入群众身份证号"></el-input>
</el-form-item>
</el-col>
<el-col :span="3"></el-col>
</el-row>
<el-row>
<el-col :span="6">
<el-form-item label="案件编号">
<el-input v-model="form.id" placeholder="请输入案件编号"></el-input>
</el-form-item>
</el-col>
<el-col :span="6" :style="{ flex: '1' }">
<el-form-item label="信件内容">
<el-input v-model="form.content" type="textarea" placeholder="请输入信件内容"
:autosize="{ minRows: 1, maxRows: 6 }" resize="none" style="width: 100%;"></el-input>
</el-form-item></el-col>
<el-col :span="6" :style="{ flex: '1' }">
<el-form-item label="评价结果">
<el-input v-model="form.evaluate" type="textarea" placeholder="请输入评价结果"
:autosize="{ minRows: 1, maxRows: 6 }" resize="none" style="width: 100%;"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex">
<el-col :span="8">
<el-form-item label="来信时间">
<el-date-picker v-model="form.date" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="14">
</el-col>
<el-col :span="8" :style="{ display: 'inline-flex', alignItems: 'center' }">
<el-row>
<el-col :span="6">
<el-button type="primary" @click="createMail" class="under-btn">自建信件</el-button>
</el-col>
<el-col :span="6">
<el-button type="primary" @click="search" class="under-btn">搜索</el-button></el-col>
<el-col :span="6">
<el-button type="default" @click="reset" class="under-btn">重置</el-button></el-col>
<el-col :span="6">
<el-button type="primary" @click="out" class="under-btn">导出信件</el-button></el-col>
</el-row>
</el-col>
</el-row>
</el-form>
<div class="table-box" ref="tableBoxHeight" v-loading="loading">
<el-table :data="tableData" border :height="tableHeight" table-layout="fixed"
:header-cell-style="{ 'background-color': '#EBEEFC', 'color': '#1D2C86' }">
<el-table-column fixed="left" prop="id" label="案件编号" width="200px">
</el-table-column>
<el-table-column prop="createTime" label="来信日期" width="120px">
</el-table-column>
<el-table-column prop="contactName" label="联系人姓名" width="100px">
</el-table-column>
<!-- todo 联系人身份证号码和联系人手机号码的需要脱敏 -->
<el-table-column prop="contactIdCard" label="联系人身份证号码" width="300px">
</el-table-column>
<el-table-column prop="contactPhone" label="联系人手机号码" width="200px">
</el-table-column>
<el-table-column prop="content" label="信件内容" width="400px" :show-overflow-tooltip="true">
</el-table-column>
<el-table-column prop="satisfaction" label="评价结果" width="100px">
</el-table-column>
<el-table-column fixed="right" label="详情" width="100">
<template v-slot="scope">
<el-button @click="handleDetail(scope.$index + 1)">详情</el-button>
</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: center;">
<el-pagination background @size-change="handleSizeChange" @current-change="handlePageChange"
:current-page="pageData.currentPage" :page-sizes="[4, 10, 20, 40, 50]" :page-size="pageData.pageSize"
layout="total,sizes, prev, pager, next, jumper" :total="pageData.totalSize">
</el-pagination>
</div>
</div>
</div>
<AddMail
v-model="addMailShow"
@close="addMailShow = false"
@success="search"
/>
<Detail
v-model="detailShow"
:hotId="activehotId"
@close="detailShow = false"
@success="search"
/>
</template>
<script setup>
import { request } from '../util/axios_config'
import { onMounted, ref } from 'vue';
import router from '../router';
import AddMail from "../components/AddMail.vue";
import Detail from "../components/Detail.vue";
const loading = ref(true);
const { VITE_API_URL } = process.env;
const activehotId = ref("");
const form = ref({
date: '',
contactName: '',
contactPhone: '',
contactIdCard: '',
id: '',
content: '',
evaluate: ''
})
const addMailShow = ref(false);
const detailShow = ref(false);
// tableDataaxios
const tableData = ref([])
const createMail = () => {
addMailShow.value = true;
};
const pageData = ref({
currentPage: 1,
pageSize: 4,
totalSize: 0
})
const tableBoxHeight = ref(null)
const tableHeight = ref('100%')
const flexColumnWidth = (label, prop) => {
const width = Math.max(label.length * 12, 120)
return `${width}px`
}
const handleResponse = (response) => {
tableData.value = response.data.mails;
tableData.value.forEach(item => {
item.createTime = item.createTime.split('T')[0]
})
pageData.value.totalSize = response.data.pageSet.totalSize;
loading.value = false;
}
const makeRequest = (requestData, callback) => {
const data = JSON.stringify(requestData)
const url = VITE_API_URL +'/mailbox/list-submit'
request({
url: url,
method: 'POST',
data: data,
headers: { 'Content-Type': 'application/json' }
}).then(callback)
.catch(function (error) { console.log(error) })
}
const updateData = (requestData) => {
makeRequest(requestData, function (response) {
handleResponse(response)
})
}
onMounted(() => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
updateData(requestData)
})
const handleDetail = (index) => {
detailShow.value = true;
activehotId.value = tableData.value[index - 1].id;
}
const handleSizeChange = (size) => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
pageData.value.pageSize = size
updateData(requestData)
}
const handlePageChange = (currentPage) => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
pageData.value.currentPage = currentPage
updateData(requestData)
}
const search = () => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
updateData(requestData)
}
const reset = () => {
form.value = {
date: '',
name: '',
phone: '',
id_card: '',
mail_id: '',
mail_context: '',
mail_appraise: ''
}
}
const out = () => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
const data = JSON.stringify(requestData)
const url = VITE_API_URL +'/mailbox/exportexcel'
request({
url: url,
method: 'POST',
data: data,
headers: { 'Content-Type': 'application/json' },
responseType: 'blob'
}).then(function (res) {
var blob = new Blob([res.data], { type: 'application/octet-stream;charset=UTF-8' })
var contentDisposition = res.headers['content-disposition']
var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*')
var result = patt.exec(contentDisposition)
var filename = result[1]
var downloadElement = document.createElement('a')
var href = window.URL.createObjectURL(blob) //
var reg = /^["](.*)["]$/g
downloadElement.style.display = 'none'
downloadElement.href = href
downloadElement.download = decodeURI(filename.replace(reg, '$1')) //
document.body.appendChild(downloadElement)
downloadElement.click() //
document.body.removeChild(downloadElement) //
window.URL.revokeObjectURL(href)
}).catch(function (error) { console.log(error) })
}
</script>
<style scoped>
.under-btn {
margin-right: 30px;
margin-bottom: 5px;
}
.table-box {
/* 全屏时顶部元素总和316px */
height: calc(100vh - 336px);
}
</style>

281
src/components/ManageUser.vue

@ -1,281 +0,0 @@
<template>
<div style="width: 90vw; margin: 0 auto;">
<el-form :model="form" label-width="150px" style="margin-top: 20px; margin-right: -50px;;">
<el-row>
<el-col :span="6">
<el-form-item label="群众姓名">
<el-input v-model="form.realName" placeholder="请输入群众姓名" max-length="200px"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="群众手机号">
<el-input v-model="form.phone" placeholder="请输入群众手机号"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="群众身份证号">
<el-input v-model="form.idCard" placeholder="请输入群众身份证号"></el-input>
</el-form-item>
</el-col>
<el-col :span="3"></el-col>
</el-row>
<el-row type="flex">
<el-col :span="8">
<el-form-item label="创建时间">
<el-date-picker v-model="form.date" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="14">
</el-col>
<el-col :span="8" :style="{ display: 'inline-flex', alignItems: 'center' }">
<el-row>
<el-col :span="6"></el-col>
<el-col :span="6">
<el-button type="primary" @click="search" class="under-btn">搜索</el-button></el-col>
<el-col :span="6">
<el-button type="default" @click="reset" class="under-btn">重置</el-button></el-col>
<el-col :span="6">
<el-button type="primary" @click="newUser = true" class="under-btn">新增用户</el-button></el-col>
<el-dialog v-model="newUser" title="新增用户" width="40%">
<el-form :model="newForm" label-width="150px">
<el-form-item label="新管理员姓名">
<el-input v-model="newForm.realName" placeholder="请输入新管理员姓名"
max-length="200px"></el-input>
</el-form-item>
<el-form-item label="新管理员手机号">
<el-input v-model="newForm.phone" placeholder="请输入新管理员手机号"></el-input>
</el-form-item>
<el-form-item label="新管理员身份证号">
<el-input v-model="newForm.idCard" placeholder="请输入新管理员身份证号"></el-input>
</el-form-item></el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancleNewUser">取消</el-button>
<el-button type="primary" @click="submitNewUser">
提交
</el-button>
</span>
</template>
</el-dialog>
</el-row>
</el-col>
</el-row>
</el-form>
<div class="table-box" ref="tableBoxHeight" v-loading="loading">
<el-table :data="tableData" border :height="tableHeight" table-layout="fixed"
:header-cell-style="{ 'background-color': '#EBEEFC', 'color': '#1D2C86' }">
<el-table-column prop="createTime" label="用户创建日期" width="120px">
</el-table-column>
<el-table-column prop="realName" label="联系人姓名" width="100px">
</el-table-column>
<!-- todo 联系人身份证号码和联系人手机号码的需要脱敏 -->
<el-table-column prop="idCard" label="联系人身份证号码" width="300px">
</el-table-column>
<el-table-column prop="phone" label="联系人手机号码" width="200px">
</el-table-column>
<el-table-column fixed="right" label="详情" width="100">
<template v-slot="scope">
<el-popconfirm title="你确定要删除这个管理员吗?" @confirm="handleDelete(scope.$index + 1)">
<template #reference>
<el-button type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: center;">
<el-pagination background @size-change="handleSizeChange" @current-change="handlePageChange"
:current-page="pageData.currentPage" :page-sizes="[4, 10, 20, 40, 50]" :page-size="pageData.pageSize"
layout="total,sizes, prev, pager, next, jumper" :total="pageData.totalSize">
</el-pagination>
</div>
</div>
</div>
</template>
<script setup>
import { request } from '../util/axios_config'
import { onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus'
const { VITE_API_URL } = process.env;
const loading = ref(true);
const form = ref({
date: '',
realName: '',
phone: '',
idCard: '',
})
const newForm = ref({
realName: '',
phone: '',
idCard: '',
})
// tableDataaxios
const tableData = ref([])
const pageData = ref({
currentPage: 1,
pageSize: 4,
totalSize: 0
})
const getData = (data) => {
const url = VITE_API_URL +'/user/list-submit'
request({
url: url,
method: 'POST',
data: data,
headers: { 'Content-Type': 'application/json' }
}).then(function (response) {
tableData.value = response.data.users;
tableData.value.forEach(item => {
item.createTime = item.createTime.split('T')[0]
})
pageData.value.totalSize = response.data.pageSet.totalSize;
loading.value = false;
})
.catch(function (error) { console.log(error) })
}
onMounted(() => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
const data = JSON.stringify(requestData)
getData(data)
})
const tableBoxHeight = ref(null)
const tableHeight = ref('100%')
const flexColumnWidth = (label, prop) => {
const width = Math.max(label.length * 12, 120)
return `${width}px`
}
const handleDelete = (index) => {
const url = VITE_API_URL +'/user/delete-user'
request({
url: url,
method: 'POST',
data: { id: tableData.value[index - 1].id },
}).then(function (response) {
if (response.status === 200 && response.data === 'success') {
search()
successMessage('删除用户成功')
}
})
.catch(function (error) {
errorMessage('删除用户失败');
console.log(error)
})
}
const handleSizeChange = (size) => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
pageData.value.pageSize = size
const data = JSON.stringify(requestData)
getData(data)
}
const handlePageChange = (currentPage) => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
pageData.value.currentPage = currentPage
const data = JSON.stringify(requestData)
getData(data)
}
const search = () => {
const requestData = {
formData: form.value,
pageData: pageData.value
}
const data = JSON.stringify(requestData)
getData(data)
}
const reset = () => {
form.value = {
date: '',
name: '',
phone: '',
id_card: '',
mail_id: '',
mail_context: '',
mail_appraise: ''
}
}
const newUser = ref(false)
const submitNewUser = () => {
const requestData = newForm.value
const data = JSON.stringify(requestData)
const url = VITE_API_URL +'/user/add-user'
request({
url: url,
method: 'POST',
data: data,
headers: { 'Content-Type': 'application/json' }
}).then(function (response) {
if (response.status === 200 && response.data === 'success') {
newUser.value = false;
newFormReset();
search();
successMessage('新增用户成功')
}
})
.catch(function (error) {
errorMessage('新增用户失败');
console.log(error)
})
}
const cancleNewUser = () => {
newUser.value = false;
newFormReset();
}
const newFormReset = () => {
newForm.value = {
realName: '',
phone: '',
idCard: '',
}
}
const successMessage = (msg) => {
ElMessage.success(msg)
}
const errorMessage = (msg) => {
ElMessage.error(msg)
}
</script>
<style scoped>
.under-btn {
margin-right: 30px;
margin-bottom: 5px;
}
.table-box {
/* 全屏时顶部元素总和286px */
height: calc(100vh - 286px);
}
</style>

63
src/layout/Index.vue

@ -1,43 +1,58 @@
<template>
<header class="flex v-center between">
<header class="flex v-center between header">
<div style="display: inline-flex;align-items: center;justify-content: center;height: 100%;">
<el-image src="/admin/police-icon.png" style="width: 40px;height: 40px;margin-right: 10px;"></el-image>
<div class="title"><a href="">局长信箱即接即办管理端</a></div>
<div class="title"><a href="">局长信箱 即接即办管理端</a></div>
</div>
<div>
<el-button type="primary" @click="logout">退出</el-button>
<el-button type="primary" @click="logout" style="--el-button-bg-color: #283AAC;--el-button-border-color: #586EFF;">
<template #icon>
<el-icon style="transform: rotate(-90deg);" :size="18"><Download /></el-icon>
</template>
退出
</el-button>
</div>
</header>
<div class="flex">
<aside>
<nav>
<a class="flex v-center center wrap pointer" @click="router.push('/mailbox')">
<a class="flex v-center center wrap pointer" @click="go('/')" :active="activeUrl === '/'">
<el-icon><Message /></el-icon>
<span>信件管理</span>
<span>12345投诉</span>
</a>
<a class="flex v-center center wrap pointer" @click="router.push('/holiday')">
<a class="flex v-center center wrap pointer" @click="go('/mail')" :active="activeUrl === '/mail'">
<el-icon><Message /></el-icon>
<span>信件查询</span>
</a>
<a class="flex v-center center wrap pointer" @click="go('/holiday')" :active="activeUrl === '/holiday'">
<el-icon><Calendar /></el-icon>
<span>节假日管理</span>
</a>
<a class="flex v-center center wrap pointer" @click="router.push('/user')">
<a class="flex v-center center wrap pointer" @click="go('/admin')" :active="activeUrl === '/admin'">
<el-icon>
<User />
</el-icon>
<span>人员管理</span>
<span>管理员列表</span>
</a>
<a class="flex v-center center wrap pointer" @click="router.push('/mail_etl')">
<a class="flex v-center center wrap pointer" @click="go('/user')" :active="activeUrl === '/user'">
<el-icon>
<User />
</el-icon>
<span>用户管理</span>
</a>
<!-- <a class="flex v-center center wrap pointer" @click="router.push('/mail_etl')">
<el-icon><ChatDotSquare /></el-icon>
<span>信件推送查询</span>
</a>
</a> -->
</nav>
</aside>
<main style="width: 100%;"><router-view /></main>
<main><router-view /></main>
</div>
</template>
<script setup>
import { HomeFilled, Platform, Menu, User,Message,Calendar,ChatDotSquare } from '@element-plus/icons-vue'
import { HomeFilled, Platform, Menu, User,Message,Calendar,ChatDotSquare, Download } from '@element-plus/icons-vue'
import { useRouter } from "vue-router";
import { useTokenStore } from '../stores/useTokenStore';
import { request } from '../util/axios_config';
@ -62,17 +77,19 @@ const logout = () => {
router.push('/login');
}
const activeUrl = ref('/');
function go(url) {
activeUrl.value = url
router.push(url)
}
</script>
<style lang="scss" scoped>
:root {
--header-height: 80px;
}
header {
.header {
background-color: #162582;
color: #fff;
height: var(--header-height);
padding: 0 20px;
box-sizing: border-box;
.title {
font-size: 28px;
@ -87,21 +104,21 @@ header {
aside {
background-color: #071254;
color: #fff;
width: 100px;
width: var(--aside-width);
height: calc(100vh - var(--header-height));
nav {
a {
height: 100px;
color: #707ab6;
&:hover {
color: #fff;
}
&[active=true] {
color: #fff;
}
.el-icon {
font-size: 50px;
}
span {
@ -111,4 +128,8 @@ aside {
}
}
}
main {
height: calc(100vh - var(--header-height));
width: calc(100vw - var(--aside-width));
}
</style>

8
src/main.js

@ -10,9 +10,11 @@ import piniaPersist from 'pinia-plugin-persist'
import './assets/style/style.scss'
import 'element-plus/theme-chalk/src/message.scss'
createApp(App)
.use(router)
const app = createApp(App);
const p = createPinia().use(piniaPersist)
app.use(router)
.use(ElementPlus, { locale: zhCn })
.use(createPinia().use(piniaPersist))
.use(p)
.component('Icon', IconComponent)
.mount('#app')

31
src/router/index.js

@ -2,7 +2,6 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import { getActivePinia } from 'pinia';
import { useTokenStore } from "@/stores/useTokenStore";
import Home from '@/views/Home.vue'
import Layout from '@/layout/Index.vue'
import NotFound from '@/views/error/404.vue'
@ -16,33 +15,29 @@ const constantRoutes = [
children: [
{
path: '/',
component: () => import('@/components/ManageMail.vue'),
component: () => import('@/views/Mail12345.vue'),
},
{
path: '/mailbox',
component: () => import('@/components/ManageMail.vue'),
path: '/mail',
component: () => import('@/views/ManageMail.vue')
},
{
path: '/mailbox/detail/:id',
component: () => import('@/components/MailDetail.vue')
path: '/admin',
component: () => import('@/views/Admin.vue')
},
{
path: '/user',
component: () => import('@/components/ManageUser.vue')
component: () => import('@/views/ManageUser.vue')
},
{
path: '/holiday',
component: () => import('@/components/HolidayList.vue')
},
{
path: '/mail_etl',
component: () => import('@/components/MailEtl.vue')
}
]
},
{
path: '/login',
component: () => import('@/components/LoginView.vue')
component: () => import('@/views/Login.vue')
},
{
path: '/:catchAll(.*)',
@ -59,14 +54,6 @@ const router = createRouter({
});
router.beforeEach((to, from, next) => {
const data = sessionStorage.getItem('token');
console.log(data);
const tokens = useTokenStore(getActivePinia());
const refreshToken = tokens.refresh_token;
if (!refreshToken && to.path !== '/login') {
next('/login');
} else {
next();
}
next();
});
export default router;
export default router;

2
src/stores/useTokenStore.js

@ -22,7 +22,7 @@ export const useTokenStore = defineStore('token', {
strategies: [
{
storage: localStorage,
key: 'token'
key: 'admin_token'
}
]
}

22
src/util/cache.js

@ -0,0 +1,22 @@
const cache = {
//设置缓存(expire为缓存时效)
set(key, value) {
try {
window.localStorage.setItem(key, value)
} catch (e) {
}
},
get(key) {
try {
return window.localStorage.getItem(key)
} catch (e) {
return null
}
},
remove(key) {
window.localStorage.removeItem(key)
}
}
export default cache

141
src/util/request.js

@ -1,54 +1,97 @@
const basePath = '/api'
export function post(url, data) {
return new Promise((resolve, reject) => {
fetch(`${basePath}${url}`, {
method: 'POST',
body: data ? JSON.stringify(data) : '',
})
// .then(res => {
// return res.json();
// }).then(res => {
// if (res.ok()) {
// resolve(res)
// } else {
// reject(res)
// }
// })
.then(res => {
resolve(res);
}).catch(e => {
console.error("请求失败了,详细信息:" + JSON.stringify(e));
reject(e);
})
})
import {
ElMessage,
ElMessageBox
} from 'element-plus'
import { getToken } from './token'
const BASE_PATH = '/admin-api'
export function get(options) {
options.method = 'GET';
return ajax(options.url, options)
}
export function post(options) {
options.method = 'POST';
return ajax(options.url, options)
}
function put(options) {
options.method = 'PUT';
return ajax(options.url, options)
}
function del(options) {
options.method = 'DELETE';
return ajax(options.url, options)
}
export function get(url) {
let isRelogin = false;
function ajax(url, options) {
const headers = {
"Content-Type": "application/json",
"Authorization": getToken()
};
let body;
if (options?.params && Object.keys(options.params).length > 0) {
if (options.method === 'GET') {
options.query = options.params;
} else {
body = JSON.stringify(options.params);
}
}
if (options?.query) {
url += (url.indexOf('?') > -1 ? '' : '?') + new URLSearchParams(options.query).toString();
}
if (options?.body) {
if (options.body === 'string' || options.body instanceof FormData) {
body = options.body;
} else {
if (Object.keys(options.body).length > 0) {
body = JSON.stringify(options.body);
}
}
}
return new Promise((resolve, reject) => {
fetch(`${basePath}${url}`, {
method: 'GET',
fetch(`${BASE_PATH}${url}`, {
method: options.method,
body: body,
headers: { ...headers, ...options.headers }
}).then(response => {
if (response.status === 413) {
return;
}
return response.json();
}).then(res => {
if (res.code === 200) {
resolve(res.data)
} else {
let message = res.msg;
if (res.code === 401) {
if (url === '/admin/self') {
feedback.msgError('登录状态已过期')
reject(res)
return
}
if (!isRelogin) {
isRelogin = true
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '温馨提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin = false;
// 调整登录
location.href = process.env.VITE_BASE + "#/login";
}).catch(() => {
isRelogin = false;
});
}
reject(res)
return
}
ElMessage.error(message || '系统异常')
reject(res)
}
})
.then(res => {
let data = res.text();//转成字符串判断
return data.then(r => {
if (r.length === 0) return null;
else return JSON.parse(r);
})
})
// .then(res => {
// return res.json();
// })
// .then(res => {
// if (res.ok()) {
// resolve(res)
// } else {
// reject(res)
// }
// })
.then(res => {
resolve(res);
}).catch(e => {
console.error("请求失败了,详细信息:" + JSON.stringify(e));
reject(e);
})
})
}

10
src/util/token.js

@ -0,0 +1,10 @@
import cache from './cache'
const TOKEN_KEY = 'token';
export function getToken() {
return cache.get(TOKEN_KEY)
}
export function setToken(val) {
return cache.set(TOKEN_KEY, val)
}

153
src/views/Admin.vue

@ -0,0 +1,153 @@
<template>
<div class="container">
<el-form :model="form" label-width="120px" style="margin-top: 20px">
<el-row>
<el-col :span="6">
<el-form-item label="姓名">
<el-input
v-model="form.nickname"
placeholder="请输入姓名"
max-length="200px"
></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="手机号">
<el-input
v-model="form.username"
placeholder="请输入手机号"
></el-input>
</el-form-item>
</el-col>
</el-row>
<div class="flex between mb-24">
<el-button type="primary" @click="show = true"
>新增用户</el-button
>
<div>
<el-button type="primary" @click="getList">搜索</el-button>
<el-button type="default" @click="reset">重置</el-button>
</div>
</div>
</el-form>
<div class="table-box" v-loading="loading">
<el-table :data="list">
<el-table-column prop="id" label="编号" />
<el-table-column prop="nickname" label="姓名" />
<el-table-column prop="username" label="手机号/用户名" />
<el-table-column prop="createTime" label="创建日期" />
</el-table>
<div class="flex end mt-10">
<el-pagination
:total="total"
>
</el-pagination>
</div>
</div>
</div>
<el-dialog v-model="show" title="新增用户" width="600px">
<el-form :model="newForm" label-width="150px" ref="formRef" :rules="rules">
<el-form-item label="管理员姓名" prop="nickname">
<el-input
v-model="newForm.nickname"
placeholder="请输入新管理员姓名"
max-length="200px"
></el-input>
</el-form-item>
<el-form-item label="管理员手机号" prop="username">
<el-input
v-model="newForm.username"
placeholder="请输入管理员手机号"
></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
type="password"
v-model="newForm.password"
placeholder="请输入密码"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="show = false">取消</el-button>
<el-button type="primary" @click="handleAdd">
提交
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from "element-plus";
import { adminList, addAdmin } from "@/api/admin";
const loading = ref(true);
const form = ref({
current: 1,
pageSize: 10
});
const list = ref([]);
const total = ref(0)
const getList = () => {
loading.value = true;
adminList(form.value).then((data) => {
list.value = data.records;
total.value = data.total
loading.value = false;
});
};
getList();
const reset = () => {
form.value = {};
getList();
};
const successMessage = (msg) => {
ElMessage.success(msg);
};
const show = ref(false)
const newForm = ref({})
const formRef = ref()
const rules = {
username: [{ validator: validatorPhone }],
password: [
{
required: true,
message: "请输入密码(长度不能少于6位)",
min: 6
}
],
};
function handleAdd() {
formRef.value.validate((valid) => {
if (valid) {
addAdmin(newForm.value).then(() => {
show.value = false
newForm.value = {}
getList()
})
}
})
}
function validatorPhone(rule, phonenumber, callback) {
if (!phonenumber) {
return callback(new Error('请输入手机号码'))
}
if (phonenumber.length !== 11) {
return callback(new Error('请输入正确的手机号码'))
}
if (!/^1[3456789]\d{9}/.test(phonenumber)) {
return callback(new Error('请输入正确的手机号码'))
}
callback()
}
</script>

126
src/views/Login.vue

@ -0,0 +1,126 @@
<template>
<div class="wrapper">
<header class="text-center">
<img src="/imgs/login_logo1.png" alt="" />
</header>
<div class="box flex v-center">
<div class="left text-center">
<img src="/imgs/pic.png" alt="" />
</div>
<div class="right mb-40">
<el-form :model="form" ref="formRef" :rules="rules">
<h1>用户登录</h1>
<el-form-item prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户名/手机号"
clearable
style="--el-input-height: 50px"
>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-container>
<el-input
v-model="form.password"
placeholder="请输入密码"
type="password"
show-password
clearable
style="--el-input-height: 50px"
>
</el-input>
</el-container>
</el-form-item>
<div class="mt-30 mb-10">
<el-button
type="primary"
@click="handleLogin"
size="large"
style="
width: 100%;
--el-button-size: 54px;
--el-font-size-base: 18px;
"
v-loading="loading"
>登录</el-button
>
</div>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { login } from '@/api/admin'
import { setToken } from '@/util/token'
import { useRouter } from 'vue-router'
import cache from '@/util/cache'
const form = reactive({
username: cache.get('username')
})
const formRef = ref()
const router = useRouter()
const rules = {
username: [{ required: true, message: "请输入用户名/手机号" }],
password: [{ required: true, message: "请输入密码" }]
}
const loading = ref(false)
function handleLogin() {
formRef.value.validate((valid) => {
if (valid) {
loading.value = true
cache.set('username', form.username)
login(form).then(data => {
setToken(data.token)
router.push('/')
loading.value = false
}).catch(() => {
loading.value = false
})
}
})
}
</script>
<style lang="scss" scoped>
.wrapper {
height: 100vh;
background-image: url("/imgs/bg.png");
background-size: cover;
header {
padding-top: 3.2vh;
margin-bottom: 8vh;
img {
height: 91px;
}
}
.box {
--login-box-width: 576px;
width: 80%;
margin: auto;
.left {
width: calc(100% - var(--login-box-width));
img {
width: 33vw;
}
}
.right {
background-color: #fff;
border: 18px solid #4169ea;
width: var(--login-box-width);
padding: 40px;
box-sizing: border-box;
h1 {
color: var(--primary-color);
font-size: 32px;
margin-top: 0;
margin-bottom: 32px;
}
}
}
}
</style>

325
src/views/Mail12345.vue

@ -0,0 +1,325 @@
<template>
<div class="container">
<header class="mb-24">
<el-form :label-width="120">
<el-row>
<el-col :span="6">
<el-form-item label="编号">
<el-input placeholder="请输入" v-model="query.id" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来源">
<el-input
placeholder="请输入"
v-model="query.source"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来电时间">
<el-date-picker
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
time-format="A hh:mm:ss"
v-model="query.phoneTime"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="市民姓名">
<el-input
placeholder="请输入"
v-model="query.contactName"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<el-form-item label="来电电话">
<el-input
placeholder="请输入"
v-model="query.contactPhone"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="证件号码">
<el-input
placeholder="请输入"
v-model="query.contactIdCard"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="话务类型">
<el-input
placeholder="请输入"
v-model="query.contactType"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="案件类型">
<el-input
placeholder="请输入"
v-model="query.caseType"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="flex between">
<div>
<el-button type="primary" @click="addMailShow = true"
>新增12345投诉</el-button
>
<el-button type="primary" @click="importShow = true"
>信件导入</el-button
>
</div>
<div>
<el-button type="primary" @click="getList">查询</el-button>
<el-button @click="reset">重置</el-button>
</div>
</div>
</header>
<div>
<el-table :data="list">
<el-table-column prop="id" label="编号" width="220" />
<el-table-column prop="contactName" label="市民姓名" width="120" />
<el-table-column prop="contactPhone" label="来电电话" width="120" />
<el-table-column prop="contactIdCard" label="证件号码" width="180" />
<el-table-column
prop="content"
label="来话内容"
show-overflow-tooltip
/>
<el-table-column prop="contactType" label="话务类型" width="120" />
<el-table-column prop="source" label="来源" width="150" />
<el-table-column prop="caseType" label="案件类型" width="120" />
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button
type="primary"
link
@click="handleMail(row)"
size="small"
>详情</el-button
>
</template>
</el-table-column>
</el-table>
<div class="flex mt-4 end">
<el-pagination
@size-change="getList"
@current-change="getList"
:current-page="query.current"
:page-sizes="[10, 20, 50]"
:page-size="query.size"
v-model:current-page="query.current"
:total="totalSize.total"
layout="total, sizes, prev, pager, next"
>
</el-pagination>
</div>
</div>
</div>
<AddMail
v-model="addMailShow"
@close="addMailShow = false"
@success="getList"
/>
<el-dialog width="1000px" title="12345投诉导入" v-model="importShow">
<header class="mb-20 flex gap">
<el-upload
:action="`${VITE_API_URL}/mail12345/analyzeExcel`"
:headers="{ Authorization: getToken() }"
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
:on-success="onAnalyzeExcelSuccess"
:show-file-list="false"
>
<template #trigger>
<el-button type="primary" plain>选择文件</el-button>
</template>
</el-upload>
<el-button type="primary" @click="downloadTemplate" plain
>下载模版</el-button
>
</header>
<el-table :data="importList" ref="importTable">
<el-table-column type="selection" width="55" />
<el-table-column prop="contactName" label="市民姓名" width="100" />
<el-table-column prop="contactPhone" label="来电电话" width="120" />
<el-table-column
prop="contactIdCard"
label="证件号码"
width="180"
/>
<el-table-column
prop="content"
label="来话内容"
show-overflow-tooltip
/>
<el-table-column prop="phoneTime" label="来电时间" />
<el-table-column label="数据是否正确" align="center">
<template #default="{ row }">
<span v-if="row.flag">正确</span>
<el-tooltip
class="box-item"
effect="dark"
:content="row.errMsg"
placement="top"
:raw-content="true"
v-else
>
<span class="text-danger">错误</span>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<footer class="flex end mt-20">
<el-button
type="primary"
@click="handleImport"
size="large"
:disabled="disabled"
>确认导入</el-button
>
</footer>
</el-dialog>
<el-dialog title="信件详情" v-model="showDetail">
<el-scrollbar max-height="600px" class="dialog-content">
<div class="row">
<div class="col-12 form-item">
<label>姓名</label>
<div>{{ activeRow.contactName }}</div>
</div>
</div>
<div class="row">
<div class="col-12 form-item">
<label>手机号</label>
<div>{{ activeRow.contactPhone }}</div>
</div>
<div class="col-12 form-item">
<label>身份证号码</label>
<div>{{ activeRow.contactIdCard }}</div>
</div>
</div>
<div class="form-item">
<label>内容</label>
<div class="content text-wrap">{{ activeRow.content }}</div>
</div>
</el-scrollbar>
<footer class="flex end mt-20">
<el-button @click="showDetail = false">关闭窗口</el-button>
</footer>
</el-dialog>
</template>
<script setup>
import AddMail from "@/components/AddMail.vue";
import { mail12345List, mail12345Import } from "@/api/mail12345";
import { getToken } from "@/util/token";
import { ElMessage } from "element-plus";
const { VITE_API_URL } = process.env;
const query = ref({
current: 1,
size: 10,
});
const totalSize = ref({
total: 0,
});
const list = ref([]);
const addMailShow = ref(false);
const importShow = ref(false);
function getList() {
if (query.value.phoneTime && query.value.phoneTime.length === 2) {
query.value.phoneTimeBegin = query.value.phoneTime[0];
query.value.phoneTimeEnd = query.value.phoneTime[1];
} else {
query.value.phoneTimeBegin = "";
query.value.phoneTimeEnd = "";
}
mail12345List(query.value).then((data) => {
list.value = data.records;
totalSize.value.total = data.total;
});
}
function reset() {
query.value = {
current: 1,
size: 10,
};
getList();
}
getList();
const importList = ref([]);
const importTable = ref();
function onAnalyzeExcelSuccess(response) {
console.log(response);
importList.value = response.data;
}
function downloadTemplate() {
location.href = `${VITE_API_URL}/file/download/template/12345投诉模版.xlsx`;
}
const disabled = ref(false);
function handleImport() {
const rows = importTable.value.getSelectionRows();
console.log(rows);
if (!rows.length) {
ElMessage.warning("请选择至少一条数据");
return;
}
disabled.value = true;
mail12345Import(rows).then(() => {
disabled.value = false;
ElMessage.success("导入成功");
getList();
importList.value = [];
importShow.value = false;
});
}
const showDetail = ref(false);
const activeRow = ref({})
function handleMail(row) {
showDetail.value = true;
activeRow.value = row
}
</script>
<style lang="scss" scoped>
.form-item {
display: flex;
line-height: 22px;
margin: 10px 0;
gap: 20px;
label {
width: 137px;
text-align: right;
}
div {
color: #333;
max-width: calc(100% - 137px);
}
.content {
background: #EFF0F5;
padding: 0 20px;
}
}
</style>

302
src/views/ManageMail.vue

@ -0,0 +1,302 @@
<template>
<div class="container">
<header class="mb-24">
<el-form :label-width="120">
<el-row>
<el-col :span="6">
<el-form-item label="来信时间">
<el-date-picker
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
time-format="A hh:mm:ss"
v-model="query.createTime"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来信人姓名">
<el-input
placeholder="请输入"
v-model="query.contactName"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来信人电话">
<el-input
placeholder="请输入"
v-model="query.contactPhone"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来信人证件号码">
<el-input
placeholder="请输入"
v-model="query.contactIdCard"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<el-form-item label="信件内容">
<el-input
placeholder="请输入"
v-model="query.content"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="评价结果">
<el-select v-model="query.satisfactionSms">
<el-option value="satisfied" label="非常满意" />
<el-option
value="basically_satisfied"
label="基本满意"
/>
<el-option
value="not_satisfied"
label="不满意"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="flex end">
<div>
<el-button type="primary" @click="getList">查询</el-button>
<el-button @click="reset">重置</el-button>
</div>
</div>
</header>
<div>
<el-table :data="list">
<el-table-column prop="id" label="编号" width="220" />
<el-table-column
prop="createTime"
label="来信时间"
width="180"
/>
<el-table-column
prop="contactName"
label="来信人姓名"
width="120"
/>
<el-table-column
prop="contactPhone"
label="来信人电话"
width="120"
/>
<el-table-column
prop="contactIdCard"
label="来信人证件号码"
width="180"
/>
<el-table-column
prop="content"
label="信件内容"
show-overflow-tooltip
/>
<el-table-column
prop="caseNumber"
label="案件编号"
width="120"
/>
<el-table-column
prop="involvedDeptName"
label="被投诉/涉及单位"
width="150"
/>
<el-table-column
label="评价结果"
width="150"
>
<template #default="{ row }">
<span>{{ getSatisfactionLabel(row.satisfactionSms) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
width="150"
>
<template #default="{ row }">
<el-button
type="primary"
link
@click="handleMail(row)"
size="small"
>详情</el-button
>
</template>
</el-table-column>
</el-table>
<div class="flex mt-4 end">
<el-pagination
@size-change="getList"
@current-change="getList"
:current-page="query.current"
:page-sizes="[10, 20, 50]"
:page-size="query.size"
v-model:current-page="query.current"
:total="totalSize.total"
layout="total, sizes, prev, pager, next"
>
</el-pagination>
</div>
</div>
</div>
<el-dialog title="信件详情" v-model="show">
<div class="row">
<div class="col">
<label>信件编号</label>
<span>{{ activeRow.id }}</span>
</div>
<div class="col">
<label>来信时间</label>
<span>{{ activeRow.createTime }}</span>
</div>
</div>
<div class="row">
<div class="col">
<label>来信人姓名</label>
<span>{{ activeRow.contactName }}</span>
</div>
<div class="col">
<label>来信人电话</label>
<span>{{ activeRow.contactPhone }}</span>
</div>
</div>
<div class="row">
<div class="col">
<label>证件号码</label>
<span>{{ activeRow.contactIdCard }}</span>
</div>
</div>
<div class="row">
<div class="col" style="width: 100%">
<label>信件内容</label>
<div class="content">{{ activeRow.content }}</div>
</div>
</div>
<div class="row">
<div class="col">
<label>案件编号</label>
<span>{{ activeRow.caseNumber }}</span>
</div>
<div class="col">
<label>被投诉/举报单位</label>
<span>{{ activeRow.involvedDeptName }}</span>
</div>
</div>
<div class="row" v-if="activeRow.attachments">
<div class="col" style="width: 100%">
<label>附件</label>
<div class="flex gap wrap attachments" v-if="JSON.parse(activeRow.attachments).length">
<img v-for="(item, index) in JSON.parse(activeRow.attachments)" :key="index" :src="`https://jzxx.hncsga.cn/api/file/stream/${item.filepath}`" alt="" @click="open(`https://jzxx.hncsga.cn/api/file/stream/${item.filepath}`)">
</div>
<el-empty description="无附件" v-else />
</div>
</div>
<footer></footer>
</el-dialog>
</template>
<script setup>
import { listMail } from "@/api/mail";
const query = ref({
current: 1,
size: 10,
});
const totalSize = ref({
total: 0,
});
const list = ref([]);
function getList() {
listMail(query.value).then((data) => {
list.value = data.records;
totalSize.value.total = data.total;
});
}
function reset() {
query.value = {
current: 1,
size: 10,
};
getList();
}
getList();
function getSatisfactionLabel(satisfaction) {
if (satisfaction === "satisfied") {
return "非常满意";
} else if (satisfaction === "not_satisfied") {
return "不满意";
} else if (satisfaction === "basically_satisfied") {
return "基本满意";
} else {
return "暂无评价";
}
}
const show = ref(false)
const activeRow = ref({})
function handleMail(row) {
activeRow.value = row
show.value = true
}
function open(url) {
window.open(url)
}
</script>
<style lang="scss" scoped>
.row {
display: flex;
padding: 10px 0;
.col {
width: 50%;
display: flex;
gap: 10px;
label {
width: 160px;
text-align: right;
color: #666;
+ * {
width: calc(100% - 170px);
}
}
span {
color: #333;
}
.content {
background: #EFF0F5;
padding: 8px 20px;
white-space: pre-wrap;
}
}
}
.attachments {
img {
max-width: 160px;
max-height: 300px;
border: 1px solid transparent;
&:hover {
cursor: pointer;
border-color: red;
}
}
}
footer {
min-height: 20px;
}
</style>

152
src/views/ManageUser.vue

@ -0,0 +1,152 @@
<template>
<div class="container">
<header class="mb-24">
<el-form :label-width="120">
<el-row>
<el-col :span="6">
<el-form-item label="来信时间">
<el-date-picker
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
time-format="A hh:mm:ss"
v-model="query.createTime"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="真实姓名">
<el-input
placeholder="请输入"
v-model="query.realName"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="电话">
<el-input
placeholder="请输入"
v-model="query.phone"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="证件号码">
<el-input
placeholder="请输入"
v-model="query.idCard"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="flex end">
<div>
<el-button type="primary" @click="getList">查询</el-button>
<el-button @click="reset">重置</el-button>
</div>
</div>
</header>
<div>
<el-table :data="list">
<el-table-column
prop="id"
label="编号"
width="80"
align="center"
/>
<el-table-column prop="openid" label="openid" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column prop="realName" label="真实姓名" width="120" />
<el-table-column prop="phone" label="电话" width="120" />
<el-table-column prop="idCard" label="证件号码" />
<el-table-column prop="faceAuthTime" label="认证时间" />
<el-table-column label="操作" width="160">
<template #default="{ row }">
<el-popconfirm title="确认删除该用户?" @confirm="handleDel(row.id)">
<template #reference>
<el-button
type="danger"
link
size="small"
>删除用户</el-button
>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="flex mt-4 end">
<el-pagination
@size-change="getList"
@current-change="getList"
:current-page="query.current"
:page-sizes="[10, 20, 50]"
:page-size="query.size"
v-model:current-page="query.current"
:total="totalSize.total"
layout="total, sizes, prev, pager, next"
>
</el-pagination>
</div>
</div>
</div>
</template>
<script setup>
import { listUser, delUser } from "@/api/user";
import { ElMessage } from "element-plus";
const query = ref({
current: 1,
size: 10,
});
const totalSize = ref({
total: 0,
});
const list = ref([]);
function getList() {
listUser(query.value).then((data) => {
list.value = data.records;
totalSize.value.total = data.total;
});
}
function reset() {
query.value = {
current: 1,
size: 10,
};
getList();
}
getList();
function handleDel(id) {
delUser(id).then(() => {
ElMessage.success('删除成功')
getList()
})
}
</script>
<style lang="scss" scoped>
.form-item {
display: flex;
line-height: 22px;
margin: 10px 0;
gap: 20px;
label {
width: 137px;
text-align: right;
}
div {
color: #333;
max-width: calc(100% - 137px);
}
.content {
background: #eff0f5;
padding: 0 20px;
}
}
</style>

2
vite.config.js

@ -51,6 +51,8 @@ export default ({ mode }) => {
host: '0.0.0.0',
proxy: {
'/admin-api': {
// http://118.253.151.67:8989/admin-api
// http://127.0.0.1:8083
target: 'http://127.0.0.1:8083',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/admin-api/, '')

Loading…
Cancel
Save