签到完成

This commit is contained in:
aixianling
2023-02-03 15:19:34 +08:00
parent 5b41cd85e0
commit 0bed825f70
7 changed files with 370 additions and 267 deletions

View File

@@ -3,31 +3,48 @@
<ai-list> <ai-list>
<ai-title slot="title" title="签到管理" isShowBottomBorder> <ai-title slot="title" title="签到管理" isShowBottomBorder>
<template #rightBtn> <template #rightBtn>
<el-button type="primary">设置</el-button> <div flex>
<ai-download url="/app/appwechatsigninfo/export" :params="{...search}" :instance="instance" fileName="签到管理导出文件"/>
<ai-dialog-btn dialogTitle="签到提醒设置" width="500px" :submit="submit" @close="form={}" @open="getSetting">
<el-button slot="btn" type="primary">设置</el-button>
<el-form size="small" label-width="120px" ref="SettingForm" :model="form">
<el-form-item label="早提醒时间">
<el-time-picker v-model="form.morningTime" placeholder="请选择" value-format="hh:mm:ss"/>
</el-form-item>
<el-form-item label="晚提醒时间">
<el-time-picker v-model="form.nightTime" placeholder="请选择" value-format="hh:mm:ss"/>
</el-form-item>
</el-form>
</ai-dialog-btn>
</div>
</template> </template>
</ai-title> </ai-title>
<template #left> <template #left>
<ai-tree-menu title="组织部门" @search="handleSearchTree"> <ai-tree-menu title="组织部门" @search="handleSearchTree">
<el-tree ref="DeptTree" :data="treeData" :props="{label:'name'}" :filter-node-method="(v,data)=>data.label.indexOf(v)>-1"/> <el-tree ref="DeptTree" :data="treeData" :props="{label:'name'}" :filter-node-method="(v,data)=>data.name.indexOf(v)>-1"
@node-click="handleSelectDept"/>
</ai-tree-menu> </ai-tree-menu>
</template> </template>
<template #content> <template #blank>
<ai-search-bar> <div flex class="gap-16">
<template #left> <ai-bar v-for="(v,key) in sta" card class="fill" :title="key"><b class="staNum" v-text="v"/></ai-bar>
<ai-download url="/app/appwechatsigninfo/export" :params="{...search,ids}" :instance="instance" fileName="签到管理导出文件"/> </div>
</template> <ai-card panel>
<template #right> <ai-search-bar>
<el-input size="small" placeholder="搜索" v-model="search.name" clearable @change="page.current=1,getTableData()"/> <template #left>
</template> <ai-search label="签到时间">
</ai-search-bar> <el-date-picker v-model="search.createDateStart" size="small" placeholder="开始时间" value-format="yyyy-MM-dd" @change="reloadTable"/>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size" <el-date-picker v-model="search.createDateEnd" size="small" placeholder="结束时间" value-format="yyyy-MM-dd" @change="reloadTable"/>
@getList="getTableData" :col-configs="colConfigs" :dict="dict"> </ai-search>
<el-table-column slot="options" label="操作" fixed="right" align="center" width="300"> <ai-select placeholder="签到状态" v-model="search.status" :selectList="dict.getDict('wxSignStatus')" @change="reloadTable"/>
<template slot-scope="{row}">
</template> </template>
</el-table-column> <template #right>
</ai-table> <el-input size="small" placeholder="搜索用户" v-model="search.wxUserName" clearable @change="page.current=1,getTableData()"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict"/>
</ai-card>
</template> </template>
</ai-list> </ai-list>
</section> </section>
@@ -46,16 +63,19 @@ export default {
}, },
data() { data() {
return { return {
search: {name: ""}, search: {name: "", createDateStart: "", createDateEnd: ""},
page: {current: 1, size: 10, total: 0}, page: {current: 1, size: 10, total: 0},
tableData: [], tableData: [],
colConfigs: [ colConfigs: [
{prop: "createDate", label: "签到时间"}, {prop: "wxUserName", label: "用户"},
{prop: "departmentName", label: "部门"}, {prop: "departmentName", label: "部门"},
{prop: "status", label: "状态", dict: "wxSignStatus"}, {prop: "createDate", label: "签到日期"},
{prop: "wxUserName", label: "用户"} {prop: "createTime", label: "签到时间"},
{prop: "status", label: "状态", dict: "wxSignStatus"}
], ],
treeData: [] treeData: [],
form: {},
sta: {}
} }
}, },
methods: { methods: {
@@ -69,9 +89,6 @@ export default {
} }
}) })
}, },
handleAdd(id) {
this.$router.push({hash: "#add", query: {id}})
},
getDepartments() { getDepartments() {
this.instance.post("/app/wxcp/wxdepartment/listAll").then(res => { this.instance.post("/app/wxcp/wxdepartment/listAll").then(res => {
if (res?.data) { if (res?.data) {
@@ -79,13 +96,49 @@ export default {
} }
}) })
}, },
getSetting() {
this.instance.post("/app/appwechatsignconfig/queryDetailById").then(res => {
if (res?.data) {
this.form = res.data
}
})
},
getStaData() {
const {departmentId} = this.search
this.instance.post("/app/appwechatsigninfo/querySignStatistic", null, {
params: {departmentId}
}).then(res => {
if (res?.data) {
this.sta = res.data
}
})
},
handleSearchTree(name) { handleSearchTree(name) {
this.$refs.DeptTree.filter(name) this.$refs.DeptTree.filter(name)
},
reloadTable() {
this.page.current = 1
this.getTableData()
},
handleSelectDept(data) {
this.search.departmentId = data.id
this.reloadTable()
this.getStaData()
},
submit() {
return this.$refs.SettingForm.validate()
.then(() => this.instance.post("/app/appwechatsignconfig/addOrUpdate", this.form))
.then(res => {
if (res?.code == 0) {
return this.$message.success("提交成功")
}
})
} }
}, },
created() { created() {
this.getTableData() this.getTableData()
this.getDepartments() this.getDepartments()
this.getStaData()
} }
} }
</script> </script>
@@ -93,5 +146,9 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.list { .list {
height: 100%; height: 100%;
.staNum {
font-size: 20px;
}
} }
</style> </style>

View File

@@ -17,13 +17,12 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
/** /**
常用内外边距样式 常用内外边距样式
*/ */
@each $padMar, $pm in (mar:margin, pad:padding) { @each $v in (8, 10, 16, 20, 32, 48, 56, 64, 80) {
@each $v in (8, 10, 16, 20, 32, 48, 56, 64, 80) { //gap
@each $pos, $p in (l:left, r:right, t:top, b:bottom) { .gap-#{$v} {
.#{$padMar}-#{$pos+$v} { gap: #{$v}px
#{$pm}-#{$p}: #{$v}px }
} @each $padMar, $pm in (mar:margin, pad:padding) {
}
.#{$padMar}-#{$v} { .#{$padMar}-#{$v} {
#{$pm}: #{$v}px #{$pm}: #{$v}px
} }
@@ -37,6 +36,11 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
#{$pm}-left: #{$v}px; #{$pm}-left: #{$v}px;
#{$pm}-right: #{$v}px; #{$pm}-right: #{$v}px;
} }
@each $pos, $p in (l:left, r:right, t:top, b:bottom) {
.#{$padMar}-#{$pos+$v} {
#{$pm}-#{$p}: #{$v}px
}
}
} }
} }

View File

@@ -1,117 +1,113 @@
<template> <template>
<div class="aibar" :style="{ marginBottom: marginBottom }" :class="[titlePosition === 'center' ? 'aibar-center' : '']"> <div class="aibar" :style="{ marginBottom }" :class="{'aibar-cente':titlePosition === 'center',card}">
<div v-if="titlePosition === 'center'"></div> <div v-if="titlePosition === 'center'"/>
<div class="aibar-left" :class="[titlePosition === 'center' ? 'aibar-left__center' : '']"> <div class="aibar-left" :class="[titlePosition === 'center' ? 'aibar-left__center' : '']">
<template v-if="!isHasTitleSlot">{{ title }}</template> <template v-if="!$scopedSlots.title">{{ title }}</template>
<slot name="title" v-else></slot> <slot name="title" v-else></slot>
</div> </div>
<div class="aibar-right"> <div class="aibar-right">
<slot name="right"></slot> <slot v-if="$scopedSlots.right" name="right"/>
<slot/>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'AiBar', name: 'AiBar',
props: { props: {
title: { title: {
type: String type: String
},
customCliker: {
type: Boolean,
default: false
},
marginBottom: {
type: String,
default: '16px'
},
titlePosition: {
type: String,
default: 'left'
}
}, },
computed: { customCliker: {
isHasTitleSlot () { type: Boolean,
return this.$slots.title default: false
}
}, },
data () { marginBottom: {
return { type: String,
} default: '16px'
} },
}
titlePosition: {
type: String,
default: 'left'
},
card: Boolean
},
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.aibar { .aibar {
display: flex; display: flex;
position: relative;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 16px;
box-sizing: border-box;
border-bottom: 1px solid #EEEEEE;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
&.card {
background: #fff;
}
.aibar-left {
color: #222;
font-size: 16px;
font-weight: 700;
}
.aibar-left__center {
position: relative; position: relative;
width: 556px;
text-align: center;
word-break: break-all;
line-height: 24px;
}
.aibar-right {
display: flex;
align-items: center; align-items: center;
justify-content: space-between; color: #5088FF;
height: 56px; font-size: 12px;
padding: 0 16px;
box-sizing: border-box;
border-bottom: 1px solid #EEEEEE;
.aibar-left { i {
color: #222; line-height: 1;
font-size: 16px; color: #5088FF;
font-weight: 700;
} }
.aibar-left__center { span {
position: relative; font-size: 12px;
width: 556px;
text-align: center;
word-break: break-all;
line-height: 24px;
} }
.aibar-right { & > div, & > a {
display: flex; display: flex;
align-items: center; align-items: center;
color: #5088FF; margin-right: 20px;
font-size: 12px;
i { &:last-child {
line-height: 1; margin-right: 0;
color: #5088FF;
}
span {
font-size: 12px;
}
& > div, & > a {
display: flex;
align-items: center;
margin-right: 20px;
&:last-child {
margin-right: 0;
}
} }
} }
} }
}
.aibar-center { .aibar-center {
height: auto; height: auto;
padding: 10px 0; padding: 10px 0;
h2 { h2 {
margin: 0 0 10px 0; margin: 0 0 10px 0;
}
p {
color: #888;
font-size: 14px;
}
} }
p {
color: #888;
font-size: 14px;
}
}
</style> </style>

View File

@@ -1,163 +1,165 @@
<template> <template>
<section class="ai-dialog__wrapper"> <section class="ai-dialog__wrapper">
<el-dialog <el-dialog
custom-class="ai-dialog" custom-class="ai-dialog"
v-on="$listeners" v-on="$listeners"
v-if="isEmpty" v-if="isEmpty"
:close-on-click-modal="closeOnClickModal" :close-on-click-modal="closeOnClickModal"
v-bind="$attrs" v-bind="$attrs"
:destroy-on-close="destroyOnClose" :destroy-on-close="destroyOnClose"
:visible.sync="dialogVisible"> :visible.sync="dialogVisible">
<div class="ai-dialog__header" slot="title"> <div class="ai-dialog__header" slot="title">
<h2>{{ title }}</h2> <h2>{{ title }}</h2>
</div> </div>
<div class="ai-dialog__content" :style="{'max-height': isScrool ? '500px' : 'auto'}"> <div class="ai-dialog__content" :style="{'max-height': isScrool ? '500px' : 'auto'}">
<div class="ai-dialog__content--wrapper" :style="{'padding-right': isScrool ? '8px' : '0'}"> <div class="ai-dialog__content--wrapper" :style="{'padding-right': isScrool ? '8px' : '0'}">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
<template v-if="customFooter" slot="footer"> <template v-if="customFooter" slot="footer">
<slot name="footer"></slot> <slot name="footer"></slot>
</template> </template>
<div v-else class="dialog-footer" slot="footer"> <div v-else class="dialog-footer" slot="footer">
<el-button @click="onCancel">取消</el-button> <el-button @click="onCancel">取消</el-button>
<el-button @click="onConfirm" type="primary" style="width: 92px;">确认</el-button> <el-button @click="onConfirm" type="primary" style="width: 92px;">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
</section> </section>
</template> </template>
<script> <script>
export default { export default {
name: 'AiDialog', name: 'AiDialog',
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: false default: false
}, },
title: { title: {
type: String, type: String,
default: '' default: ''
}, },
customFooter: { customFooter: {
type: Boolean, type: Boolean,
default: false default: false
}, },
'close-on-click-modal': { 'close-on-click-modal': {
type: Boolean, type: Boolean,
default: false default: false
}, },
destroyOnClose: { destroyOnClose: {
type: Boolean, type: Boolean,
default: true default: true
} }
}, },
data () { data() {
return { return {
dialogVisible: false, dialogVisible: false,
isScrool: true, isScrool: true,
isEmpty: true isEmpty: true
} }
}, },
watch: { watch: {
visible: { visible: {
handler (val) { handler(val) {
this.dialogVisible = val this.dialogVisible = val
// if (val) { // if (val) {
// this.$nextTick(() => { // this.$nextTick(() => {
// setTimeout(() => { // setTimeout(() => {
// this.isScrool = document.querySelector('.ai-dialog__content') && document.querySelector('.ai-dialog__content').clientHeight >= 500 // this.isScrool = document.querySelector('.ai-dialog__content') && document.querySelector('.ai-dialog__content').clientHeight >= 500
// }, 100) // }, 100)
// }) // })
// } // }
if (this.destroyOnClose && !val) { if (this.destroyOnClose && !val) {
setTimeout(() => { setTimeout(() => {
this.isEmpty = false this.isEmpty = false
setTimeout(() => { setTimeout(() => {
this.isEmpty = true this.isEmpty = true
}, 50) }, 50)
}, 500) }, 500)
} }
} }
} }
}, },
mounted () { mounted() {
}, },
methods: { methods: {
onCancel () { onCancel() {
this.$emit('update:visible', false) this.$emit('update:visible', false)
this.$emit('onCancel') this.$emit('onCancel')
}, this.$emit('cancel')
},
onConfirm () { onConfirm() {
this.$emit('onConfirm') this.$emit('onConfirm')
} this.$emit('confirm')
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.ai-dialog { .ai-dialog {
margin: 0!important; margin: 0 !important;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
.el-dialog__body { .el-dialog__body {
padding: 20px 40px 20px; padding: 20px 40px 20px;
} }
.ai-dialog__content { .ai-dialog__content {
overflow-y: auto; overflow-y: auto;
padding-bottom: 4px; padding-bottom: 4px;
.ai-dialog__content--wrapper { .ai-dialog__content--wrapper {
height: 100%; height: 100%;
overflow-x: hidden; overflow-x: hidden;
overflow-y: overlay; overflow-y: overlay;
} }
} }
.ai-dialog__header { .ai-dialog__header {
height: 48px; height: 48px;
line-height: 48px; line-height: 48px;
padding: 0 16px; padding: 0 16px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
h2 { h2 {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
} }
} }
.el-dialog__footer { .el-dialog__footer {
padding: 16px 20px; padding: 16px 20px;
box-sizing: border-box; box-sizing: border-box;
background: #F3F6F9; background: #F3F6F9;
text-align: center; text-align: center;
& + .el-button { & + .el-button {
margin-left: 8px; margin-left: 8px;
} }
.el-button { .el-button {
width: 92px!important; width: 92px !important;
} }
} }
.el-dialog__header { .el-dialog__header {
padding: 0; padding: 0;
} }
.el-dialog__headerbtn { .el-dialog__headerbtn {
top: 24px; top: 24px;
transform: translateY(-50%); transform: translateY(-50%);
} }
} }
</style> </style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<section class="ai-card"> <section class="ai-card" :class="{panel}">
<ai-bar v-if="!hideTitle" :title="title" v-bind="$attrs"> <ai-bar v-if="!hideHeader" :title="title" v-bind="$attrs">
<template #title> <template #title>
<slot name="title"></slot> <slot name="title"></slot>
</template> </template>
@@ -9,7 +9,8 @@
</template> </template>
</ai-bar> </ai-bar>
<div class="ai-card__body"> <div class="ai-card__body">
<slot name="content"></slot> <slot v-if="$scopedSlots.content" name="content"/>
<slot v-else/>
</div> </div>
</section> </section>
</template> </template>
@@ -21,7 +22,11 @@ export default {
title: { title: {
type: String type: String
}, },
hideTitle: Boolean hideTitle: Boolean,
panel: Boolean
},
computed: {
hideHeader: v => v.hideTitle || v.panel
} }
} }
</script> </script>
@@ -37,5 +42,15 @@ export default {
.ai-card__body { .ai-card__body {
padding: 12px 40px 22px; padding: 12px 40px 22px;
} }
&.panel {
margin-bottom: 0;
.ai-card__body {
padding: 12px 16px;
}
}
} }
</style> </style>

View File

@@ -4,8 +4,8 @@
<slot v-if="$scopedSlots.btn" name="btn"/> <slot v-if="$scopedSlots.btn" name="btn"/>
<el-button v-else type="text">{{ text }}</el-button> <el-button v-else type="text">{{ text }}</el-button>
</div> </div>
<ai-dialog :visible.sync="dialog" :title="dialogTitle" :width="width" :customFooter="customFooter" v-on="$listeners" <ai-dialog :visible.sync="dialog" :title="dialogTitle" :width="width" :customFooter="needFooter" v-on="$listeners"
@onConfirm="dialog=false,$emit('onConfirm')" v-bind="$attrs"> @onConfirm="handleConfirm" v-bind="$attrs">
<slot/> <slot/>
<template #footer> <template #footer>
<el-button @click="dialog=false">关闭</el-button> <el-button @click="dialog=false">关闭</el-button>
@@ -21,12 +21,26 @@ export default {
text: {default: "点击弹窗"}, text: {default: "点击弹窗"},
dialogTitle: {default: "展示信息"}, dialogTitle: {default: "展示信息"},
customFooter: {default: true}, customFooter: {default: true},
width: {default: "1200px"} width: {default: "1200px"},
submit: {default: null}
},
computed: {
needFooter: v => v.customFooter && !v.submit
}, },
data() { data() {
return { return {
dialog: false dialog: false
} }
},
methods: {
handleConfirm() {
if (!this.submit) {
this.dialog = false
this.$emit('onConfirm')
} else {
this.submit?.()?.then(() => this.dialog = false)
}
}
} }
} }
</script> </script>

View File

@@ -6,9 +6,14 @@
<div class="ai-list__tabs" v-if="$slots.tabs"> <div class="ai-list__tabs" v-if="$slots.tabs">
<slot name="tabs"></slot> <slot name="tabs"></slot>
</div> </div>
<div class="ai-list__blank" v-else-if="$slots.blank"> <el-row type="flex" class="ai-list__blank" v-else-if="$scopedSlots.blank" :class="{hasLeft:$scopedSlots.left}">
<slot name="blank"/> <div class="ai-list__content--left" v-if="$scopedSlots.left">
</div> <slot name="left"/>
</div>
<div class="fill">
<slot name="blank"/>
</div>
</el-row>
<div class="ai-list__content" v-else :class="contentClass"> <div class="ai-list__content" v-else :class="contentClass">
<div class="ai-list__content--wrapper"> <div class="ai-list__content--wrapper">
<slot name="custom" v-if="!!$slots.custom"/> <slot name="custom" v-if="!!$slots.custom"/>
@@ -68,7 +73,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
//全局tab css //全局tab css
:deep( .ai-list__tabs ){ :deep( .ai-list__tabs ) {
margin-top: 0 !important; margin-top: 0 !important;
.el-tabs__item { .el-tabs__item {
@@ -148,7 +153,7 @@ export default {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
:deep( .el-tabs__item ){ :deep( .el-tabs__item ) {
min-width: 80px; min-width: 80px;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
@@ -158,15 +163,15 @@ export default {
border-bottom: 1px solid transparent !important; border-bottom: 1px solid transparent !important;
} }
:deep(.el-tabs__header ){ :deep(.el-tabs__header ) {
margin: 0; margin: 0;
} }
:deep(.el-tabs ){ :deep(.el-tabs ) {
height: 100%; height: 100%;
} }
:deep(.el-tabs__content ){ :deep(.el-tabs__content ) {
height: calc(100% - 32px); height: calc(100% - 32px);
background: #f3f6f9; background: #f3f6f9;
@@ -222,7 +227,17 @@ export default {
flex: 1; flex: 1;
padding: 16px; padding: 16px;
box-sizing: border-box; box-sizing: border-box;
overflow: auto; height: calc(100% - 50px);
&.hasLeft {
padding: 16px 0;
}
& > .fill {
height: 100%;
overflow: auto;
}
} }
} }