xy 1 year ago
parent 6bc62282b4
commit 903751043f

@ -26,3 +26,11 @@ export function statistics(params,isLoading= true) {
isLoading
})
}
export function preDistance(params) {
return request({
method: 'get',
url: '/api/oa/attendance/pre-distance',
params
})
}

@ -0,0 +1,44 @@
import request from "@/utils/request";
export function index(params) {
return request({
method: 'get',
url: '/api/oa/meeting/index',
params
})
}
export function show(params) {
return request({
method: 'get',
url: '/api/oa/meeting/show',
params
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/oa/meeting/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/meeting/destroy',
params,
isLoading
})
}
export function check(params,isLoading=true) {
return request({
method: 'get',
url: '/api/oa/meeting/check-time',
params,
isLoading
})
}

@ -0,0 +1,34 @@
import request from "@/utils/request";
export function index(params) {
return request({
method: 'get',
url: '/api/oa/meeting-room/index',
params
})
}
export function show(params) {
return request({
method: 'get',
url: '/api/oa/meeting-room/show',
params
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/oa/meeting-room/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/meeting-room/destroy',
params,
isLoading
})
}

@ -0,0 +1,34 @@
import request from "@/utils/request";
export function index(params) {
return request({
method: 'get',
url: '/api/oa/notice/index',
params
})
}
export function show(params) {
return request({
method: 'get',
url: '/api/oa/notice/show',
params
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/oa/notice/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/notice/destroy',
params,
isLoading
})
}

@ -0,0 +1 @@
<svg t="1729565029927" class="icon" viewBox="0 0 1137 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5133" width="200" height="200"><path d="M568.465067 0C254.103885 0 0 203.510494 0 454.772053c0 160.875614 104.029107 301.85495 260.925466 382.861223l26.717858 175.939938a12.221999 12.221999 0 0 0 20.464742 7.105813l133.305058-122.504222A704.043985 704.043985 0 0 0 568.465067 909.544107c312.655787 0 568.465067-203.510494 568.465066-454.772054S881.120853 0 568.465067 0z m-229.94412 507.923537a47.751066 47.751066 0 1 1 48.035298-47.751066 47.751066 47.751066 0 0 1-48.035298 47.751066z m229.091422 0a47.751066 47.751066 0 1 1 47.751066-47.751066A48.035298 48.035298 0 0 1 568.465067 507.923537z m228.807189 0a47.751066 47.751066 0 1 1 47.751066-47.751066 47.751066 47.751066 0 0 1-48.319531 47.751066z" p-id="5134"></path></svg>

After

Width:  |  Height:  |  Size: 845 B

@ -241,6 +241,7 @@ export default function formBuilder(device, info, h, row, pWrite = false,pReadab
placeholder: info.help_text,
multiple: !!info.multiple,
'multiple-limit': info.multiple,
filterable: true
},
style: {
width: "100%",
@ -449,6 +450,7 @@ export default function formBuilder(device, info, h, row, pWrite = false,pReadab
placeholder: info.help_text,
multiple: !!info.multiple,
'multiple-limit': info.multiple,
filterable: true
},
attrs: {
placeholder: info.help_text,
@ -483,6 +485,7 @@ export default function formBuilder(device, info, h, row, pWrite = false,pReadab
clearable: true,
placeholder: info.help_text,
multiple: true,
filterable: true
},
attrs: {
placeholder: info.help_text,
@ -709,7 +712,7 @@ export default function formBuilder(device, info, h, row, pWrite = false,pReadab
color: "#333",
},
},
info.multiple ? this.form[info.name]?.split('|').map(j => {
info.multiple ? this.form[info.name]?.toString()?.split('|')?.map(j => {
return options.find((i) =>
typeof i === "object"
? i.id == j
@ -726,7 +729,7 @@ export default function formBuilder(device, info, h, row, pWrite = false,pReadab
color: "#333",
},
},
this.form[info.name]?.split('|').map(j => {
this.form[info.name]?.toString()?.split('|').map(j => {
return options.find((i) => i.id == j)?.name;
})?.toString()
);
@ -744,7 +747,7 @@ export default function formBuilder(device, info, h, row, pWrite = false,pReadab
color: "#333",
},
},
info.multiple ? this.form[info.name]?.split('|').map(j => {
info.multiple ? this.form[info.name]?.toString()?.split('|').map(j => {
return options.find((i) =>
typeof i === "object"
? i.id == j

@ -0,0 +1,74 @@
<template>
<div>
<vxe-modal
:value="isShow"
show-footer
title="会议室"
show-confirm-button
:width="600"
:height="400"
esc-closable
:fullscreen="$store.getters.device === 'mobile'"
@input="e => $emit('update:isShow',e)"
>
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="名字" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="排序" prop="myindex">
<el-input-number v-model="form.myindex" controls-position="right" :precision="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { save } from '@/api/meetingRoom'
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
name: '',
myindex: 0
},
rules: {}
}
},
computed: {},
methods: {
submit() {
this.$refs['elForm'].validate(async valid => {
if (valid) {
this.loading = true
try {
await save(this.form)
this.$message.success('新增成功')
this.$emit('refresh')
this.$emit('update:isShow', false)
this.loading = false
this.$refs['elForm'].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,190 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-toolbar>
<vxe-table
ref="table"
stripe
style="margin-top: 10px;"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:align="allAlign"
:data="tableData"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column field="name" width="170" title="名字" :edit-render="{ name: 'input', attrs: { type: 'text' } }" />
<vxe-column field="myindex" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }" />
<vxe-column field="operate" title="操作" min-width="220">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px;"
:current-page="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="e => {
select.page_size = e;
select.page = 1;
getList();
}"
@current-change="e => {
select.page = e;
getList();
}"
/>
</card-container>
<add-meeting-room ref="AddMeetingRoom" :is-show.sync="isShowAdd" @refresh="getList" />
</div>
</template>
<script>
import { deepCopy } from '@/utils'
import { index, save, destroy } from '@/api/meetingRoom'
import AddMeetingRoom from './components/AddMeetingRoom.vue'
export default {
components: {
AddMeetingRoom
},
data() {
return {
isShowAdd: false,
loading: false,
select: {
page: 1,
page_size: 20
},
total: 0,
allAlign: null,
tableData: [],
validRules: {},
form: {
id: '',
name: '',
myindex: ''
}
}
},
computed: {
isActiveStatus() {
return function(row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
created() {
this.getList()
},
methods: {
editRowEvent(row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent(row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async getList() {
this.loading = true
try {
const res = await index(this.select)
this.tableData = res.data
this.total = res.total
this.loading = false
} catch (err) {
console.error(err)
this.loading = false
}
},
async saveRowEvent(row) {
try {
const errMap = await this.$refs['table'].validate()
if (errMap) {
throw new Error(errMap)
}
await this.$confirm('确认保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
await this.$refs['table'].clearEdit()
const form = deepCopy(this.form)
for (const key in form) {
form[key] = row[key]
}
if (!form.password) {
delete form.password
}
this.loading = true
await save(form)
await this.getList()
this.loading = false
} catch (err) {
this.loading = false
}
},
async destroyRowEvent(row) {
try {
await this.$confirm('确认删除?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
this.loading = true
if (row.id) {
await destroy({
id: row.id
})
await this.getList()
} else {
console.log(row)
this.tableData.splice(this.tableData.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY), 1)
}
this.loading = false
} catch (err) {
this.loading = false
}
}
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -5,23 +5,29 @@
<button class="sign-btn" @click="clockIn">
<span>打卡</span>
</button>
<div class="sign-statsu">
<div>打卡状态 <el-tag size="small" effect="dark" type="primary">{{ isGetLocation ? '可打卡' : '不可打卡' }}</el-tag></div>
<div>当前位置 <span v-if="isGetLocation">
<div class="sign-info">
<div class="sign-statue">
<div>打卡状态 <el-tag size="small" effect="dark" type="primary">{{ isGetLocation ? isOutSign ? '外勤打卡' : '可打卡' : '不可打卡' }}</el-tag></div>
<div>当前位置 <span v-if="isGetLocation">
<el-tag size="small" type="primary" effect="dark">{{pos.address}}</el-tag>
{{pos.lng}},{{pos.lat}}</span></div>
<div>当前距离</div>
<div>最大打卡范围</div>
<div>当前距离{{ nowDistance }}</div>
<div>最大打卡范围{{ maxDistance }}</div>
</div>
<div class="sign-log">
</div>
</div>
</div>
<MonthStatics></MonthStatics>
<MonthStatics ref="MonthStatics"></MonthStatics>
</CardContainer>
</div>
</template>
<script>
import { sign } from '@/api/attendance'
import { sign, preDistance } from '@/api/attendance'
import { throttle } from '@/utils'
import MonthStatics from './components/MonthStatics'
import axios from "axios";
@ -32,21 +38,28 @@ export default {
data() {
return {
isGetLocation: false,
isOutSign: false,
pos: {
lng: '',
lat: '',
address: ''
}
},
maxDistance: '',
nowDistance: '',
}
},
computed: {},
methods: {
clockIn: throttle(async function() {
try {
this.getLocation()
if(!this.isGetLocation) return
const res = await sign({
location: `${this.pos.lng},${this.pos.lat}`,
address: this.pos.address
})
this.$message.success('打卡成功')
await this.$refs['MonthStatics'].getData()
console.log(res)
} catch (err) {
console.error(err)
@ -63,19 +76,11 @@ export default {
},
async pos2Address(lat, lng) {
try {
// const res = await axios.get('https://apis.map.qq.com/ws/geocoder/v1/',{
// params: {
// location: lat + "," + lng,
// key: "D5EBZ-C3BWP-HZIDG-VO6BE-P2MN5-ESFZO",
// output: "jsonp"
// },
// })
const res = await this.$jsonp('https://apis.map.qq.com/ws/geocoder/v1/',{
location: lat + "," + lng,
key: "D5EBZ-C3BWP-HZIDG-VO6BE-P2MN5-ESFZO",
output: "jsonp"
})
console.log(res)
if(!res.status) {
this.pos.address = res.result.address
}
@ -92,6 +97,13 @@ export default {
this.pos.lat = pos.coords.latitude
if(this.pos.lat && this.pos.lng && this.isGetLocation) {
this.pos2Address(this.pos.lat, this.pos.lng)
preDistance({
location: `${this.pos.lng},${this.pos.lat}`
}).then(res => {
this.isOutSign = res.distance > Number(res.max_distance)
this.maxDistance = res.max_distance
this.nowDistance = res.distance
})
}
}, (error) => {
console.log(error)
@ -142,7 +154,7 @@ export default {
});
},
},
created() {
mounted() {
this.isAuthPermission()
}
}
@ -185,7 +197,7 @@ export default {
bottom: 0;
}
}
.sign-statsu {
.sign-statue {
margin-top: 20px;
line-height: 2;
text-align: center;

@ -741,6 +741,7 @@ $btn-colors: linear-gradient(90deg, #d4bbfd 0%, #af7bff 100%),
margin: 10px 10px 0 0;
// overflow: hidden;
position: relative;
z-index: 3;
&::before {
content: "";
@ -754,6 +755,7 @@ $btn-colors: linear-gradient(90deg, #d4bbfd 0%, #af7bff 100%),
#0000 44%
)
no-repeat;
z-index: -1;
position: absolute;
left: -33%;
bottom: -64%;
@ -770,6 +772,7 @@ $btn-colors: linear-gradient(90deg, #d4bbfd 0%, #af7bff 100%),
#0000 44%
)
no-repeat;
z-index: -1;
position: absolute;
right: -56%;
bottom: -79%;

@ -115,7 +115,7 @@ export default {
beforeUpload(file) {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
this.$message.error('上传头像图片大小不能超过 10MB!')
this.$message.error('上传文件大小不能超过 10MB!')
}
return isLt10M
},

@ -66,7 +66,7 @@ export default {
request,
async validate() {
return await this.$refs["elForm"].validate();
},
}
},
computed: {},
watch: {

@ -4,6 +4,7 @@ import moment from "moment/moment";
import MobileMultipleSelect from "@/components/MobileMultipleSelect/index.vue";
import {deepCopy} from "@/utils";
import {PopupManager} from "element-ui/lib/utils/popup";
import request from '@/utils/request'
export default {
components: {
MobileMultipleSelect
@ -86,7 +87,9 @@ export default {
}
}
},
methods: {},
methods: {
request
},
computed: {},
watch: {
info(newVal) {
@ -167,7 +170,8 @@ export default {
},[
h('van-datetime-picker',{
props: {
type: 'datetime'
type: 'datetime',
title: '选择时间'
},
on: {
confirm: time => {

@ -274,6 +274,7 @@ export default {
},
data() {
return {
printKey: 0,
isShowRollback: false,
isShowForward: false,
isShowAssign: false,
@ -335,14 +336,85 @@ export default {
}
},
async print(isLog=false) {
let customModelId = this.config.customModel.id || this.$route.query.module_id
const modelRes = await fieldConfig(customModelId,true)
let pickTemplate = 0
const printTemplates = [{
id: 0,
name: '基础模版',
print_format: modelRes.customModel.print_format
},...modelRes.customModel.print_formats]
const h = this.$createElement;
await this.$msgbox({
title: '打印模版选择',
message: h('div',{
class: 'print-template-radios',
key: this.printKey++
},[
h('div',{
},printTemplates.map(i => h('div',{
style: {
display: 'flex',
'align-items': 'center',
'margin-top': '4px',
}
},[
h('span',{
class: 'el-radio__input'
},[
h('span', {
class: 'el-radio__inner custom-cursor-on-hover'
}),
h('input', {
style: {
cursor: 'pointer',
opacity: 0,
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
margin: 0
},
attrs: {
//
type: "radio",
id: `print-radio-${i.id}`,
name: "Radio",
value: i.id,
checked: pickTemplate === i.id,
},
on: {
change: () => {
pickTemplate = i.id
}
}
})
]),
h('label', {
style: {
flex: 1
},
attrs: {
for: `print-radio-${i.id}`
},
class: 'el-radio__label',
}, i.name)
])))
]),
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
})
const printText = printTemplates.find(i => i.id === pickTemplate)?.print_format
if(isLog) {
const res = await this.$refs['table'].exportData({
type: 'html',
download: false
})
print(this.config.customModel.print_format, isLog, res.content)
print(printText, isLog, res.content)
} else {
print(this.config.customModel.print_format, isLog)
print(printText, isLog)
}
},
@ -361,7 +433,7 @@ export default {
default:
return {
validator: (myRule, value, callback) => {
if (validation.get(rule).test(value)) {
if (validation.get(rule).test(value) || value === '') {
callback();
} else {
callback(
@ -728,4 +800,11 @@ export default {
height: 0;
}
}
.print-template-radios .el-radio__input:has(input[type=radio]:checked) .el-radio__inner {
border-color: var(--theme-color);
background: var(--theme-color);
&::after {
transform: translate(-50%,-50%) scale(1);
}
}
</style>

@ -0,0 +1,193 @@
<template>
<div>
<vxe-modal
:value="isShow"
show-footer
:z-index="zIndex"
title="会议使用审核"
show-confirm-button
:width="640"
:height="580"
esc-closable
:fullscreen="$store.getters.device === 'mobile'"
@input="(e) => $emit('update:isShow', e)"
>
<el-form
ref="elForm"
:model="form"
:rules="rules"
label-position="top"
label-width="100"
>
<el-form-item label="会议/活动名称" prop="title">
<el-input v-model="form.title" />
</el-form-item>
<el-form-item label="开始时间" prop="start_time">
<el-date-picker
style="width: 100%;"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="form.start_time"
></el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="end_time">
<el-date-picker
style="width: 100%;"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="form.end_time"
></el-date-picker>
</el-form-item>
<el-form-item label="会议室" prop="room_id">
<el-select
style="width: 100%;"
v-model="form.room_id"
@change="
(e) =>
(form.address = rooms.find((i) => i.id === e)
? rooms.find((i) => i.id === e).name
: '')
"
>
<el-option
v-for="item in rooms"
:key="item.id"
:value="item.id"
:label="item.name"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="会议/活动地址" prop="address">
<el-input v-model="form.address" />
</el-form-item>
<el-form-item label="主要议程" prop="content">
<el-input
v-model="form.content"
type="textarea"
:autosize="{ minRows: 2 }"
/>
</el-form-item>
<el-form-item label="附件上传">
<el-upload
:action="action"
:headers="{
Authorization: `Bearer ${getToken()}`,
}"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
multiple
:file-list="fileList"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">上传文件不超过10Mb</div>
</el-upload>
</el-form-item>
<el-form-item label="用户选择" prop="user_ids">
<el-transfer
filterable
:titles="['未选用户', '已选用户']"
filter-placeholder="请输入关键字"
:value="form.user_ids.map(i => i.user_id)"
:props="{
key: 'id',
label: 'name'
}"
:data="users"
@input="e => form.user_ids = e.map(i => ({ user_id: i }))">
</el-transfer>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"
>确认</el-button
>
</template>
</vxe-modal>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import { save } from "@/api/meeting";
import { PopupManager } from "element-ui/lib/utils/popup";
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
rooms: {
type: Array,
default: () => [],
},
users: {
type: Array,
default: () => [],
}
},
data() {
return {
zIndex: PopupManager.nextZIndex(),
loading: false,
form: {
title: "",
start_time: "",
end_time: "",
address: "",
room_id: "",
content: "",
meeting_status: 0,
files: [],
user_ids: [],
},
action: process.env.VUE_APP_UPLOAD_API,
fileList: [],
rules: {},
};
},
computed: {},
watch: {
isShow(newVal) {
if(newVal) {
this.zIndex = PopupManager.nextZIndex()
}
}
},
methods: {
beforeUpload(file) {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
this.$message.error('上传文件大小不能超过 10MB!')
}
return isLt10M
},
uploadSuccess(response, file, fileList) {
this.fileList = fileList
},
getToken,
submit() {
this.$refs["elForm"].validate(async (valid) => {
if (valid) {
this.loading = true;
try {
this.form.files = this.fileList.map(i => i.response?.data?.id).filter(i => !!i)
await save(this.form);
this.$message.success("新增成功");
this.$emit("refresh");
this.$emit("update:isShow", false);
this.loading = false;
this.$refs["elForm"].resetFields();
} catch (err) {
this.loading = false;
}
}
});
},
},
};
</script>
<style scoped lang="scss"></style>

@ -0,0 +1,354 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button
icon="el-icon-plus"
type="primary"
size="small"
@click="isShowAdd = true"
>新增</el-button
>
<el-button
icon="el-icon-search"
type="primary"
plain
size="small"
@click="getList"
>搜索</el-button
>
</template>
</vxe-toolbar>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{
trigger: 'manual',
mode: 'row',
showStatus: true,
isHover: true,
autoClear: false,
}"
:align="allAlign"
:data="tableData"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column
field="title"
width="170"
title="会议标题"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
field="start_time"
width="180"
title="会议开始时间"
:edit-render="{}"
>
<template #edit="{ row }">
<el-date-picker style="width: 100%;" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" v-model="row.start_time"></el-date-picker>
</template>
</vxe-column>
<vxe-column
field="end_time"
width="180"
title="会议结束时间"
:edit-render="{}"
>
<template #edit="{ row }">
<el-date-picker style="width: 100%;" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" v-model="row.end_time"></el-date-picker>
</template>
</vxe-column>
<vxe-column
field="address"
width="170"
title="会议地址"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
field="room_id"
width="180"
title="会议室"
:edit-render="{}"
>
<template #edit="{ row }">
<el-select
style="width: 100%;"
v-model="row.room_id"
>
<el-option
v-for="item in rooms"
:key="item.id"
:value="item.id"
:label="item.name"
></el-option>
</el-select>
</template>
</vxe-column>
<vxe-column
field="content"
min-width="200"
title="主要议程"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
field="user_ids"
width="220"
title="参会用户"
:edit-render="{}"
>
<template #edit="{ row }">
<el-select
filterable
multiple
style="width: 100%;"
v-model="row.user_ids"
>
<el-option
v-for="item in users"
:key="item.id"
:value="item.id"
:label="item.name"
></el-option>
</el-select>
</template>
</vxe-column>
<vxe-column field="operate" title="操作" min-width="220">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"
>保存</el-button
>
<el-button
size="small"
type="primary"
plain
@click="cancelRowEvent(row)"
>取消</el-button
>
</template>
<template v-else>
<el-button size="small" type="primary" @click=""
>审核</el-button
>
<el-button size="small" type="warning" @click="editRowEvent(row)"
>编辑</el-button
>
<el-button
size="small"
type="danger"
@click="destroyRowEvent(row)"
>删除</el-button
>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px"
:current-page="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
/>
</card-container>
<add-meeting
ref="AddMeeting"
:rooms="rooms"
:users="users"
:is-show.sync="isShowAdd"
@refresh="getList"
/>
</div>
</template>
<script>
import { deepCopy } from "@/utils";
import { destroy, index, save } from "@/api/meeting";
import { index as meetingRoomIndex } from "@/api/meetingRoom";
import { index as userIndex } from "@/api/user";
import AddMeeting from "./components/AddMeeting.vue";
export default {
components: {
AddMeeting,
},
data() {
return {
isShowAdd: false,
rooms: [],
users: [],
loading: false,
select: {
page: 1,
page_size: 20,
},
total: 0,
allAlign: null,
tableData: [],
validRules: {},
form: {
id: "",
title: "",
start_time: "",
end_time: "",
address: "",
room_id: "",
content: "",
meeting_status: "",
files: [],
user_ids: [],
},
};
},
computed: {
isActiveStatus() {
return function (row) {
if (this.$refs["table"]) {
return this.$refs["table"].isEditByRow(row);
}
};
},
},
created() {
this.getList();
this.getRooms();
this.getUsers();
},
methods: {
editRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].setEditRow(row);
}
},
cancelRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].clearEdit().then(() => {
//
this.$refs["table"].revertData(row);
});
}
},
async getRooms() {
try {
this.rooms = (await meetingRoomIndex({
page: 1,
page_size: 9999,
})).data
} catch (err) {
console.error(err);
}
},
async getUsers() {
try {
this.users = (await userIndex({
page: 1,
rows: 9999,
})).data
} catch (err) {
console.error(err);
}
},
async getList() {
this.loading = true;
try {
const res = await index(this.select);
this.tableData = res.data;
this.total = res.total;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async saveRowEvent(row) {
try {
const errMap = await this.$refs["table"].validate();
if (errMap) {
throw new Error(errMap);
}
await this.$confirm("确认保存?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
await this.$refs["table"].clearEdit();
const form = deepCopy(this.form);
for (const key in form) {
form[key] = row[key];
}
if (!form.password) {
delete form.password;
}
this.loading = true;
await save(form);
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent(row) {
try {
await this.$confirm("确认删除?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
this.loading = true;
if (row.id) {
await destroy({
id: row.id,
});
await this.getList();
} else {
console.log(row);
this.tableData.splice(
this.tableData.findIndex((i) => i._X_ROW_KEY === row._X_ROW_KEY),
1
);
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
};
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -0,0 +1,186 @@
<template>
<div>
<vxe-modal
:value="isShow"
show-footer
:z-index="zIndex"
title="会议使用审核"
show-confirm-button
:width="640"
:height="580"
esc-closable
:fullscreen="$store.getters.device === 'mobile'"
@input="(e) => $emit('update:isShow', e)"
>
<el-form
ref="elForm"
:model="form"
:rules="rules"
label-position="top"
label-width="100"
>
<el-form-item label="公告标题" prop="title">
<el-input v-model="form.title" />
</el-form-item>
<el-form-item label="公告内容" prop="content">
<el-input
v-model="form.content"
type="textarea"
:autosize="{ minRows: 2 }"
/>
</el-form-item>
<el-form-item label="回复格式" prop="reply_fields">
<el-input
placeholder="参会人员
联系电话"
v-model="form.reply_fields"
type="textarea"
:autosize="{ minRows: 2 }"
/>
<span style="font-size: 12px; color: #6c757d; line-height: 1">
回复方式为文字回复时有效每行一个栏位举例如需要回复参会人员和联系电话只要在以上框中输入参会人员联系电话即可
</span>
</el-form-item>
<el-form-item label="附件上传">
<el-upload
:action="action"
:headers="{
Authorization: `Bearer ${getToken()}`,
}"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
multiple
:file-list="fileList"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">上传文件不超过10Mb</div>
</el-upload>
</el-form-item>
<el-form-item label="回复方式" prop="deal_type">
<el-select style="width: 100%" v-model="form.deal_type">
<el-option value="read" label="阅读即可,无需回复"></el-option>
<el-option value="text" label="文字"></el-option>
</el-select>
</el-form-item>
<el-form-item label="回复时是否开放附件上传" prop="reply_need_files">
<el-switch
v-model="form.reply_need_files"
:active-value="1"
:inactive-value="0"
active-text="开放"
inactive-text="不开放"
></el-switch>
</el-form-item>
<el-form-item label="回复时限" prop="deadline">
<el-date-picker
style="width: 100%"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="form.deadline"
></el-date-picker>
</el-form-item>
<el-form-item label="用户选择" prop="user_ids">
<el-transfer
filterable
:titles="['未选用户', '已选用户']"
filter-placeholder="请输入关键字"
:value="form.user_ids.map((i) => i.user_id)"
:props="{
key: 'id',
label: 'name',
}"
:data="users"
@input="(e) => (form.user_ids = e.map((i) => ({ user_id: i })))"
>
</el-transfer>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"
>确认</el-button
>
</template>
</vxe-modal>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import { save } from "@/api/notice";
import { PopupManager } from "element-ui/lib/utils/popup";
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
users: {
type: Array,
default: () => [],
}
},
data() {
return {
zIndex: PopupManager.nextZIndex(),
loading: false,
form: {
title: '',
content: '',
files: '',
deal_type: '',
reply_fields: '',
reply_need_files: '',
deadline: '',
user_ids: []
},
action: process.env.VUE_APP_UPLOAD_API,
fileList: [],
rules: {},
};
},
computed: {},
watch: {
isShow(newVal) {
if(newVal) {
this.zIndex = PopupManager.nextZIndex()
}
}
},
methods: {
beforeUpload(file) {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
this.$message.error('上传文件大小不能超过 10MB!')
}
return isLt10M
},
uploadSuccess(response, file, fileList) {
this.fileList = fileList
},
getToken,
submit() {
this.$refs["elForm"].validate(async (valid) => {
if (valid) {
this.loading = true;
try {
this.form.files = this.fileList.map(i => i.response?.data?.id).filter(i => !!i)
await save(this.form);
this.$message.success("新增成功");
this.$emit("refresh");
this.$emit("update:isShow", false);
this.loading = false;
this.$refs["elForm"].resetFields();
} catch (err) {
this.loading = false;
}
}
});
},
},
};
</script>
<style scoped lang="scss"></style>

@ -0,0 +1,212 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-toolbar>
<vxe-table
ref="table"
stripe
style="margin-top: 10px;"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:align="allAlign"
:data="tableData"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column field="name" width="170" title="名字" :edit-render="{ name: 'input', attrs: { type: 'text' } }" />
<vxe-column field="key" width="150" title="标识" :edit-render="{ name: 'input', attrs: { type: 'text' } }" />
<vxe-column field="value" width="150" title="值" :edit-render="{ name: 'input', attrs: { type: 'text' } }" />
<vxe-column field="mysort" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }" />
<vxe-column field="operate" title="操作" min-width="220">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px;"
:current-page="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="e => {
select.page_size = e;
select.page = 1;
getList();
}"
@current-change="e => {
select.page = e;
getList();
}"
/>
</card-container>
<add-notice ref="AddNotice" :users="users" :is-show.sync="isShowAdd" @refresh="getList" />
</div>
</template>
<script>
import { deepCopy } from '@/utils'
import { index, save, destroy } from '@/api/notice'
import AddNotice from './components/AddNotice.vue'
import { index as userIndex } from "@/api/user";
export default {
components: {
AddNotice
},
data() {
return {
users: [],
isShowAdd: false,
loading: false,
select: {
page: 1,
page_size: 20
},
total: 0,
allAlign: null,
tableData: [],
validRules: {},
form: {
id: '',
title: '',
content: '',
files: '',
deal_type: '',
reply_fields: '',
reply_need_files: '',
deadline: '',
user_ids: []
}
}
},
computed: {
isActiveStatus() {
return function(row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
created() {
this.getUsers()
this.getList()
},
methods: {
editRowEvent(row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent(row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async getUsers() {
try {
this.users = (await userIndex({
page: 1,
rows: 9999,
})).data
} catch (err) {
console.error(err);
}
},
async getList() {
this.loading = true
try {
const res = await index(this.select)
this.tableData = res.data
this.total = res.total
this.loading = false
} catch (err) {
console.error(err)
this.loading = false
}
},
async saveRowEvent(row) {
try {
const errMap = await this.$refs['table'].validate()
if (errMap) {
throw new Error(errMap)
}
await this.$confirm('确认保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
await this.$refs['table'].clearEdit()
const form = deepCopy(this.form)
for (const key in form) {
form[key] = row[key]
}
if (!form.password) {
delete form.password
}
this.loading = true
await save(form)
await this.getList()
this.loading = false
} catch (err) {
this.loading = false
}
},
async destroyRowEvent(row) {
try {
await this.$confirm('确认删除?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
this.loading = true
if (row.id) {
await destroy({
id: row.id
})
await this.getList()
} else {
console.log(row)
this.tableData.splice(this.tableData.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY), 1)
}
this.loading = false
} catch (err) {
this.loading = false
}
}
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>
Loading…
Cancel
Save