xy 2 years ago
parent b20ff9f88a
commit 4b00c9bda0

@ -30,7 +30,8 @@
"vue-router": "3.0.6",
"vuex": "3.1.0",
"vxe-table": "^3.8.22",
"wujie-vue2": "^1.0.22"
"wujie-vue2": "^1.0.22",
"moment": "^2.29.4"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",

@ -0,0 +1,19 @@
import request from '@/utils/request'
export function index(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/attendance/index',
params,
isLoading
})
}
export function sign(params,isLoading = true) {
return request({
method: 'get',
url: '/api/oa/attendance/sign',
params,
isLoading
})
}

@ -0,0 +1,72 @@
// 公共文件柜
import request from '@/utils/request'
// 导航栏
export function index(params) {
return request({
url: '/api/oa/document/index',
method: 'get',
params
})
}
export function show(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/document/show',
params,
isLoading
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/oa/document/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/document/destroy',
params,
isLoading
})
}
// 文件柜
export function menuIndex(params) {
return request({
url: '/api/oa/document-menu/index',
method: 'get',
params
})
}
export function menuShow(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/document-menu/show',
params,
isLoading
})
}
export function menuSave(data, isLoading = true) {
return request({
method: 'post',
url: '/api/oa/document-menu/save',
data,
isLoading
})
}
export function menuDestroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/document-menu/destroy',
params,
isLoading
})
}

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

@ -0,0 +1 @@
<svg t="1727226512125" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4440" width="200" height="200"><path d="M896 85.333333h-170.666667V42.666667a42.666667 42.666667 0 0 0-85.333333 0v42.666666H384V42.666667a42.666667 42.666667 0 0 0-85.333333 0v42.666666H128a128 128 0 0 0-128 128v682.666667a128 128 0 0 0 128 128h768a128 128 0 0 0 128-128V213.333333a128 128 0 0 0-128-128zM128 170.666667h170.666667v42.666666a42.666667 42.666667 0 0 0 85.333333 0V170.666667h256v42.666666a42.666667 42.666667 0 0 0 85.333333 0V170.666667h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666666v128H85.333333V213.333333a42.666667 42.666667 0 0 1 42.666667-42.666666z m768 768H128a42.666667 42.666667 0 0 1-42.666667-42.666667V426.666667h853.333334v469.333333a42.666667 42.666667 0 0 1-42.666667 42.666667z" fill="#333333" p-id="4441"></path><path d="M663.04 559.786667l-213.333333 213.333333-90.026667-92.586667a42.666667 42.666667 0 0 0-60.16 0 42.666667 42.666667 0 0 0 0 60.586667l120.746667 120.746667a42.666667 42.666667 0 0 0 30.293333 12.8 42.666667 42.666667 0 0 0 30.293333-12.8l241.493334-241.493334a42.666667 42.666667 0 0 0 0-60.586666 42.666667 42.666667 0 0 0-59.306667 0z" fill="currentColor" p-id="4442"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1 @@
<svg t="1727230940478" class="icon" viewBox="0 0 1225 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4382" width="200" height="200"><path d="M950.857143 1024H292.571429A182.857143 182.857143 0 0 1 109.714286 841.142857V182.857143A182.857143 182.857143 0 0 1 292.571429 0h658.285714a182.857143 182.857143 0 0 1 182.857143 182.857143v658.285714a182.857143 182.857143 0 0 1-182.857143 182.857143z m73.142857-804.571429a109.714286 109.714286 0 0 0-109.714286-109.714285H329.142857a109.714286 109.714286 0 0 0-109.714286 109.714285v585.142858a109.714286 109.714286 0 0 0 109.714286 109.714285h585.142857a109.714286 109.714286 0 0 0 109.714286-109.714285V219.428571zM841.142857 731.428571a36.571429 36.571429 0 0 1-36.571428-36.571428V475.428571a36.571429 36.571429 0 0 1 73.142857 0v219.428572a36.571429 36.571429 0 0 1-36.571429 36.571428z m-219.428571 0a36.571429 36.571429 0 0 1-36.571429-36.571428V329.142857a36.571429 36.571429 0 0 1 73.142857 0v365.714286a36.571429 36.571429 0 0 1-36.571428 36.571428z m-219.428572 0a36.571429 36.571429 0 0 1-36.571428-36.571428v-146.285714a36.571429 36.571429 0 0 1 73.142857 0v146.285714a36.571429 36.571429 0 0 1-36.571429 36.571428z" fill="currentColor" p-id="4383"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

@ -0,0 +1 @@
<svg t="1727232430934" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6277" width="200" height="200"><path d="M277 150.9c20.3 0 36.8 16.5 36.8 36.9s-16.5 36.9-36.8 36.9l-3.8-0.1h-22.5c-21.5 0-39.2 15.7-42.5 36.1l-0.6 7v67h616.6v-66.9c0-21.4-15.6-39.1-36-42.5l-7-0.6h-21.1l-3.7 0.1c-20.3-0.1-36.7-16.7-36.6-37.1 0.1-17.6 12.7-32.7 30-36l6.6-0.6h25.2c31.1 0 60.2 12.1 82.3 34.2 19.3 19.4 30.9 44.1 33.5 70.8l0.6 11.5-0.1 329.4 0.3 202.7c-0.2 60.4-46.4 110.7-106.4 116l-9.9 0.4H250.6c-60.4 0-110.8-46.3-116-106.6l-0.4-10v-532c0-61 46.9-111 106.4-116.1l10.1-0.4h23.7l2.6-0.1z m106.4-34.7c17.9 0 32.5 16.9 32.5 37.7v95.4c0 20.8-14.6 37.7-32.5 37.7s-32.5-16.9-32.5-37.9V154c0-20.7 14.6-37.8 32.5-37.8z m265.8 0c17.8 0 32.4 16.9 32.4 37.7v95.4c0 20.8-14.6 37.7-32.5 37.7-17.8 0-32.3-16.9-32.5-37.9V154c0-20.9 14.6-37.8 32.6-37.8z m-133.6 0c17.8 0 32.3 16.7 32.3 37.5v95.7c0 20.6-14.4 37.6-32.3 37.6-17.8 0-32.3-16.9-32.3-37.7v-95.6c-0.1-20.6 14.3-37.5 32.3-37.5z m68.6 371.2H444.4c-17.9 0-32.5 14.6-32.5 32.5 0 18 14.5 32.5 32.5 32.5h78.9c-14.4 23.2-30.7 55.4-37.2 90.4-2.1 11.2-3.8 22.4-5.1 33.7l-1.9 20.1-1 16.8-0.5 20.6c-0.1 18 14.3 32.6 32.2 32.8 15.8 0.1 29.5-11.2 32.2-26.9l0.6-6.8 1.1-25.8 1.2-16.7 1.8-18.4 1.1-9.3 2.8-18c4.8-19.6 16.8-42.4 28.8-62.1l13.9-21.7 4.7-6.5 4.6-5.7c2.8-3.5 5.5-6.7 8-10.4 2.1-2.9 3.6-6 4.7-9.4l1.4-6.4v-3.1c-0.1-17.6-14.6-32.1-32.5-32.2z" p-id="6278"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1 @@
<svg t="1727603200581" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4311" width="200" height="200"><path d="M512.8 587.9c-5 0-10-1.3-14.5-3.9L309.6 475c-8.9-5.2-14.5-14.8-14.5-25.1V232.1c0-10.3 5.5-19.9 14.5-25.1L498.3 98c8.9-5.2 20-5.2 29 0L716 207c8.9 5.2 14.5 14.8 14.5 25.1V450c0 10.3-5.5 19.9-14.5 25.1l-188.7 109c-4.5 2.5-9.5 3.8-14.5 3.8zM335.4 443.5l177.4 102.4 177.4-102.4V238.7L512.8 136.2 335.4 238.7v204.8z" fill="currentColor" p-id="4312"></path><path d="M710.3 930c-5 0-10-1.3-14.5-3.9l-188.7-109c-8.9-5.2-14.5-14.8-14.5-25.1V574.2c0-10.3 5.5-19.9 14.5-25.1l188.7-108.9c8.9-5.2 20-5.2 29 0l188.7 109c8.9 5.2 14.5 14.8 14.5 25.1v217.9c0 10.3-5.5 19.9-14.5 25.1l-188.7 109c-4.5 2.4-9.5 3.7-14.5 3.7zM532.9 785.6L710.3 888l177.4-102.4V580.7L710.3 478.3 532.9 580.7v204.9z" fill="currentColor" p-id="4313"></path><path d="M315.3 930.7c-5 0-10-1.3-14.5-3.9l-188.7-109c-8.9-5.2-14.5-14.8-14.5-25.1V574.9c0-10.3 5.5-19.9 14.5-25.1l188.7-108.9c8.9-5.2 20-5.2 29 0l188.7 109c8.9 5.2 14.5 14.8 14.5 25.1v217.9c0 10.3-5.5 19.9-14.5 25.1L329.8 927c-4.5 2.4-9.5 3.7-14.5 3.7zM137.9 786.2l177.4 102.4 177.4-102.4V581.4L315.3 479 137.9 581.4v204.8z" fill="currentColor" p-id="4314"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -1,19 +1,19 @@
import Vue from "vue";
import Vue from 'vue'
import "normalize.css/normalize.css"; // A modern alternative to CSS resets
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
import locale from "element-ui/lib/locale/lang/en"; // lang i18n
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
//import locale from 'element-ui/lib/locale/lang/en' // lang i18n
import "@/styles/index.scss"; // global css
import '@/styles/index.scss' // global css
import App from "./App";
import store from "./store";
import router from "./router";
import App from './App'
import store from './store'
import router from './router'
import "@/icons"; // icon
import "@/permission"; // permission control
import '@/icons' // icon
import '@/permission' // permission control
/**
* If you don't want to use mock-server
@ -23,34 +23,37 @@ import "@/permission"; // permission control
* Currently MockJs will be used in the production environment,
* please remove it before going online ! ! !
*/
if (process.env.NODE_ENV === "production") {
const { mockXHR } = require("../mock");
mockXHR();
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
//moment
import moment from 'moment'
Vue.prototype.$moment = moment
// set ElementUI lang to EN
//Vue.use(ElementUI, { locale });
// Vue.use(ElementUI, { locale });
// 如果想要中文版 element-ui按如下方式声明
Vue.use(ElementUI)
Vue.use(ElementUI)
//vxetable
import { VxeIcon, VxeTable, VxeColumn, VxeColgroup, VxeTableEditModule, VxeTableValidatorModule, VxeModal, VxeToolbar } from "vxe-table";
import "vxe-table/styles/index.scss"
Vue.use(VxeTableEditModule);
Vue.use(VxeTableValidatorModule);
Vue.use(VxeIcon);
Vue.use(VxeTable);
Vue.use(VxeColumn);
Vue.use(VxeColgroup);
Vue.use(VxeModal);
Vue.use(VxeToolbar);
//treeselect
// vxetable
import { VxeIcon, VxeTable, VxeColumn, VxeColgroup, VxeTableEditModule, VxeTableValidatorModule, VxeModal, VxeToolbar } from 'vxe-table'
import 'vxe-table/styles/index.scss'
Vue.use(VxeTableEditModule)
Vue.use(VxeTableValidatorModule)
Vue.use(VxeIcon)
Vue.use(VxeTable)
Vue.use(VxeColumn)
Vue.use(VxeColgroup)
Vue.use(VxeModal)
Vue.use(VxeToolbar)
// treeselect
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import Treeselect from "@riophae/vue-treeselect"
Vue.component("Treeselect",Treeselect);
import Treeselect from '@riophae/vue-treeselect'
Vue.component('Treeselect', Treeselect)
import CardContainer from "@/layout/CardContainer.vue"
Vue.component("CardContainer",CardContainer)
//wujie
import CardContainer from '@/layout/CardContainer.vue'
Vue.component('CardContainer', CardContainer)
// wujie
// import Wujie from "wujie-vue2";
// Vue.use(Wujie);
// const { setupApp, preloadApp } = Wujie;
@ -62,11 +65,11 @@ Vue.component("CardContainer",CardContainer)
// url: "http://localhost:9530/admin/#/",
// exec: true,
// });
Vue.config.productionTip = false;
Vue.config.productionTip = false
new Vue({
el: "#app",
el: '#app',
router,
store,
render: (h) => h(App)
});
})

@ -0,0 +1,48 @@
<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 }"
:data="tableData"
>
<vxe-column type="seq" width="64" align="center" />
</vxe-table>
</card-container>
</div>
</template>
<script>
export default {
data() {
return {
isShowAdd: false,
loading: false,
validRules: {
},
tableData: []
}
},
computed: {},
methods: {
getList() {}
}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,87 @@
<template>
<div>
<vxe-modal
:value="isShow"
show-footer
title="特殊日期"
show-confirm-button
:width="600"
:height="400"
esc-closable
@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="date" required>
<el-date-picker v-model="form.date" value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type">
<el-option value="workday" label="工作日" />
<el-option value="holiday" label="节假日" />
</el-select>
</el-form-item>
<el-form-item label="说明" prop="reason">
<el-input v-model="form.reason" type="textarea" :autosize="{ minRows: 2 }" />
</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/specialDays'
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
date: '',
type: '',
reason: ''
},
rules: {
date: [
{ required: true, message: '请选择日期' }
],
type: [
{ required: true, message: '请选择类型' }
]
}
}
},
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,203 @@
<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="date" width="170" title="日期" :edit-render="{}">
<template #edit="{ row }">
<el-date-picker style="width: 100%;" v-model="row.date" value-format="yyyy-MM-dd" />
</template>
</vxe-column>
<vxe-column field="type" title="类型" width="120" :edit-render="{}">
<template #edit="{ row }">
<el-select v-model="row.type">
<el-option value="workday" label="工作日" />
<el-option value="holiday" label="节假日" />
</el-select>
</template>
</vxe-column>
<vxe-column field="reason" min-width="180" title="说明" align="center" :edit-render="{ name: 'input', attrs: { type: 'text' } }" />
<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-specialDays ref="addSpecialDays" :is-show.sync="isShowAdd" @refresh="getList" />
</div>
</template>
<script>
import { deepCopy } from '@/utils'
import { index, save, destroy } from '@/api/specialDays'
import addSpecialDays from './components/addSpecialDays.vue'
export default {
components: {
addSpecialDays
},
data() {
return {
isShowAdd: false,
loading: false,
select: {
page: 1,
page_size: 20
},
total: 0,
allAlign: null,
tableData: [],
validRules: {
name: [
{ required: true, message: '请输入角色' }
]
},
form: {
id: '',
date: '',
type: '',
reason: ''
}
}
},
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 {
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,85 @@
<template>
<div>
<el-calendar v-model="day">
<template #dateCell="{ date, data }">
<div>
<div>{{ data.day.split('-')[2] }}</div>
<div v-if="dayAttendances(data.day) && dayAttendances(data.day).attendance && device === 'desktop'" class="clock-in">
<el-tag v-if="dayAttendances(data.day).attendance.sign_in_at" effect="dark" size="mini">{{ $moment(dayAttendances(data.day).attendance.sign_in_at).format('HH:mm:ss') }}</el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.sign_out_at" effect="dark" type="success" size="mini">{{ $moment(dayAttendances(data.day).attendance.sign_out_at).format('HH:mm:ss') }}</el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.sign_in_at1" effect="dark" size="mini">{{ $moment(dayAttendances(data.day).attendance.sign_in_at1).format('HH:mm:ss') }}</el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.sign_out_at1" effect="dark" type="success" size="mini">{{ $moment(dayAttendances(data.day).attendance.sign_out_at1).format('HH:mm:ss') }}</el-tag>
</div>
</div>
</template>
</el-calendar>
</div>
</template>
<script>
import { index } from '@/api/attendance'
export default {
data() {
return {
day: new Date(),
clockLogs: {
attendances: [],
is_workday: true,
on_dutys: [],
today_attendance: {}
}
}
},
computed: {
dayAttendances() {
return function(day) {
return this.clockLogs.attendances.find(i => i.date === day)
}
},
device() {
return this.$store.getters.device
}
},
watch: {
'day': {
handler: function(newVal, oldVal) {
if (new Date(newVal).getFullYear() !== new Date(oldVal).getFullYear() || new Date(newVal).getMonth() !== new Date(oldVal).getMonth()) {
this.getData()
}
},
immediate: true
}
},
created() {
},
methods: {
async getData() {
try {
this.clockLogs = await index({
month: this.$moment(this.day).format('YYYY-MM')
})
} catch (err) {
console.error(err)
}
}
}
}
</script>
<style scoped lang="scss">
::v-deep .el-calendar-day {
height: auto!important;
min-height: 60px;
}
::v-deep .el-tag--mini + .el-tag--mini {
margin-top: 4px;
}
.clock-in {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
}
</style>

@ -0,0 +1,95 @@
<template>
<div>
<CardContainer>
<div>
<button class="sign-btn" @click="clockIn">
<span>打卡</span>
</button>
<div class="sign-statsu">
<div>当前位置</div>
<div>当前距离</div>
<div>最大打卡范围</div>
</div>
</div>
<MonthStatics></MonthStatics>
</CardContainer>
</div>
</template>
<script>
import { sign } from '@/api/attendance'
import { throttle } from '@/utils'
import MonthStatics from './components/MonthStatics'
export default {
components: {
MonthStatics
},
data() {
return {}
},
computed: {},
methods: {
clockIn: throttle(async function() {
try {
const res = await sign()
console.log(res)
} catch (err) {
console.error(err)
}
}, 1000, true)
}
}
</script>
<style scoped lang="scss">
.sign-btn {
width: 6.5rem;
height: 6.5rem;
border-radius: 100%;
border: none;
display: block;
background: #6687d2;
position: relative;
transition: all .2s;
margin: auto;
& > span {
font-size: 1rem;
font-weight: 600;
text-align: center;
color: #fff;
position: relative;
}
&:active {
background: #5176cb;
}
&::before {
content: "";
background: #7ab9ff;
opacity: 0.45;
border-radius: 100%;
animation: scale 5s infinite linear;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
.sign-statsu {
margin-top: 20px;
line-height: 2;
text-align: center;
}
@keyframes scale {
0%,100% {
transform: scale(1, 1);
}
50% {
transform: scale(1.1, 1.1);
}
}
</style>

@ -0,0 +1,45 @@
<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 }"
:data="tableData"
>
<vxe-column type="seq" width="64" align="center" />
</vxe-table>
</card-container>
</div>
</template>
<script>
export default {
data() {
return {
loading: false,
validRules: {
},
tableData: []
}
},
computed: {},
methods: {}
}
</script>
<style scoped lang="scss">
</style>

@ -6,36 +6,42 @@
direction="rtl"
size="72%"
append-to-body
@close="$emit('update:isShow',false)">
<div style="padding: 10px;"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-tabs type="border-card" v-model="activeName">
@close="$emit('update:isShow',false)"
>
<div
v-loading="loading"
style="padding: 10px;"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<el-tabs v-model="activeName" type="border-card">
<el-tab-pane label="权限菜单" name="menu">
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-arrow-down" type="primary" size="small" @click="$refs['menuTable'].setAllTreeExpand(true)"></el-button>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAddMenu = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getMenu"></el-button>
</template>
</vxe-toolbar>
<vxe-table
style="margin-top: 10px;"
ref="menuTable"
style="margin-top: 10px;"
max-height="600"
keep-source
:row-config="{ useKey: 'id', isHover: true }"
:column-config="{ resizable: true }"
:edit-rules="validMenuRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, autoClear: false }"
:tree-config="{ rowField: 'id', parentField: 'pid', expandAll: true }"
:data="menus">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" tree-node width="160" title="菜单" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
:data="menus"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column field="name" tree-node width="160" title="菜单" :edit-render="{ name: 'input', attrs: { type: 'text'} }" />
<vxe-column field="path" title="路由地址" min-width="140" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<template v-if="/#/g.test(row.path)">
<SvgIcon icon-class="folder" class-name="icon-folder"></SvgIcon>
<SvgIcon icon-class="folder" class-name="icon-folder" />
</template>
<template v-else>
<span>{{ row.path }}</span>
@ -45,7 +51,7 @@
<vxe-column field="url" title="页面模块路径" min-width="140" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<template v-if="/#/g.test(row.url)">
<SvgIcon icon-class="folder" class-name="icon-folder"></SvgIcon>
<SvgIcon icon-class="folder" class-name="icon-folder" />
</template>
<template v-else>
<span>{{ row.url }}</span>
@ -55,20 +61,20 @@
<vxe-column field="api_profix" min-width="140" title="api前缀" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<template v-if="/#/g.test(row.api_profix)">
<SvgIcon icon-class="folder" class-name="icon-folder"></SvgIcon>
<SvgIcon icon-class="folder" class-name="icon-folder" />
</template>
<template v-else>
<span>{{ row.api_profix }}</span>
</template>
</template>
</vxe-column>
<vxe-column field="visible" width="100" align="center" title="是否显示" :edit-render="{ name: 'select', options: [{ value: 1, label: '' },{ value: 0, label: '' }] }"></vxe-column>
<vxe-column field="visible" width="100" align="center" title="是否显示" :edit-render="{ name: 'select', options: [{ value: 1, label: '' },{ value: 0, label: '' }] }" />
<vxe-column field="icon" width="160" align="center" title="图标" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<Icon :icon="row.icon"></Icon>
<Icon :icon="row.icon" />
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }" />
<vxe-column field="operate" title="操作" min-width="240">
<template #default="{ row }">
<template v-if="isActiveStatus(row,'menuTable')">
@ -92,17 +98,18 @@
</template>
</vxe-toolbar>
<vxe-table
ref="roleTable"
stripe
style="margin-top: 10px;"
ref="roleTable"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRoleRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:data="roles">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="160" title="角色" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
:data="roles"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column field="name" width="160" title="角色" :edit-render="{ name: 'input', attrs: { type: 'text'} }" />
<vxe-column field="permissions" title="权限菜单" show-overflow="tooltip">
<template #default="{ row }">
<div>
@ -110,7 +117,7 @@
</div>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="sortnumber" 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,'roleTable')">
@ -130,18 +137,19 @@
</el-tab-pane>
<el-tab-pane label="用户授权" name="permission">
<vxe-table
ref="table"
stripe
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:data="users">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="140" title="姓名" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="username" width="160" title="用户名" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
:data="users"
>
<vxe-column type="seq" width="70" align="center" />
<vxe-column field="name" width="140" title="姓名" :edit-render="{ name: 'input', attrs: { type: 'text'} }" />
<vxe-column field="username" width="160" title="用户名" :edit-render="{ name: 'input', attrs: { type: 'text'} }" />
<vxe-column field="module_roles" title="角色" min-width="200" show-overflow="tooltip">
<template #default="{ row }">
<div>
@ -149,7 +157,7 @@
</div>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }" />
<vxe-column field="operate" title="操作" min-width="240">
<template #default="{ row }">
<el-button size="small" type="success" @click="bindRole(row)"></el-button>
@ -162,10 +170,10 @@
</div>
</el-drawer>
<ModuleAuthAddMenu ref="ModuleAuthAddMenu" :is-show.sync="isShowAddMenu" :list="menus" :module-id="moduleId" @refresh="getMenu"></ModuleAuthAddMenu>
<ModuleAuthAddRole ref="ModuleAuthAddRole" :is-show.sync="isShowAddRole" :module-id="moduleId" @refresh="getRole"></ModuleAuthAddRole>
<ModuleAuthBindPermissions ref="ModuleAuthBindPermissions" :is-show.sync="isShowBindPermissions" :module-id="moduleId" @refresh="getRole"></ModuleAuthBindPermissions>
<ModuleAuthBindRole ref="ModuleAuthBindRole" :is-show.sync="isShowBindRoles" :module-id="moduleId" @refresh="getUsers"></ModuleAuthBindRole>
<ModuleAuthAddMenu ref="ModuleAuthAddMenu" :is-show.sync="isShowAddMenu" :list="menus" :module-id="moduleId" @refresh="getMenu" />
<ModuleAuthAddRole ref="ModuleAuthAddRole" :is-show.sync="isShowAddRole" :module-id="moduleId" @refresh="getRole" />
<ModuleAuthBindPermissions ref="ModuleAuthBindPermissions" :is-show.sync="isShowBindPermissions" :module-id="moduleId" @refresh="getRole" />
<ModuleAuthBindRole ref="ModuleAuthBindRole" :is-show.sync="isShowBindRoles" :module-id="moduleId" @refresh="getUsers" />
</div>
</template>
@ -173,7 +181,7 @@
import SvgIcon from '@/components/SvgIcon/index.vue'
import ModuleAuthAddMenu from './ModuleAuthAddMenu.vue'
import ModuleAuthAddRole from './ModuleAuthAddRole.vue'
import Icon from "@/layout/components/Navbar/Icon.vue"
import Icon from '@/layout/components/Navbar/Icon.vue'
import ModuleAuthBindPermissions from './ModuleAuthBindPermissions.vue'
import ModuleAuthBindRole from './ModuleAuthBindRole.vue'
import { menu, menuSave, menuDestroy, role, roleDestroy, roleSave, grant } from '@/api/module'
@ -197,32 +205,32 @@ export default {
data() {
return {
loading: false,
activeName: "menu",
moduleId: "",
activeName: 'menu',
moduleId: '',
visible: false,
isShowAddMenu: false,
menus: [],
validMenuRules: {
name: [
{ required: true, message: "请输入名称" }
{ required: true, message: '请输入名称' }
],
path: [
{ required: true, message: "请输入路由路径" },
{ required: true, message: '请输入路由路径' }
],
api_profix: [
{ required: true, message: "请输入api前缀" }
{ required: true, message: '请输入api前缀' }
]
},
menuForm: {
id: "",
module_id: "",
pid: "",
name: "",
url: "",
path: "",
api_profix: "",
icon: "",
id: '',
module_id: '',
pid: '',
name: '',
url: '',
path: '',
api_profix: '',
icon: '',
visible: 1,
sortnumber: 0
},
@ -232,38 +240,74 @@ export default {
roleTotal: 0,
validRoleRules: {
name: [
{ required: true, message: "请输入角色" }
{ required: true, message: '请输入角色' }
]
},
roleForm: {
id: "",
module_id: "",
name: "",
id: '',
module_id: '',
name: '',
sortnumber: 0
},
isShowBindPermissions: false,
users: [],
userTotal: 0,
isShowBindRoles: false,
isShowBindRoles: false
}
},
computed: {
isActiveStatus() {
return function(row, table) {
if (this.$refs[table]) {
return this.$refs[table].isEditByRow(row)
}
}
}
},
watch: {
isShow(newVal) {
this.visible = newVal
this.getMenu()
},
visible(newVal) {
this.$emit('update:isShow', newVal)
},
activeName: {
handler: function(newVal) {
switch (newVal) {
case 'menu':
this.getMenu()
break
case 'role':
this.getRole()
break
case 'permission':
this.getUsers()
break
}
}
}
},
created() {
this.getRole()
},
methods: {
bindPermission(row) {
this.$refs['ModuleAuthBindPermissions'].setForm(['permission_id'],[row.permissions?.map(i => i.id)||[]])
this.$refs['ModuleAuthBindPermissions'].setForm(['permission_id'], [row.permissions?.map(i => i.id) || []])
this.isShowBindPermissions = true
},
bindRole(row) {
this.$refs['ModuleAuthBindRole'].setForm(['id','role_id'],[row.id,row.roles?.map(i => i.id)||[]])
this.isShowBindRoles = true;
this.$refs['ModuleAuthBindRole'].setForm(['id', 'role_id'], [row.id, row.roles?.map(i => i.id) || []])
this.isShowBindRoles = true
},
editRowEvent (row,table) {
editRowEvent(row, table) {
if (this.$refs[table]) {
this.$refs[table].setEditRow(row)
}
},
cancelRowEvent (row,table) {
cancelRowEvent(row, table) {
if (this.$refs[table]) {
this.$refs[table].clearEdit().then(() => {
//
@ -277,7 +321,7 @@ export default {
},
async getMenu() {
this.loading = true;
this.loading = true
try {
const res = await menu({
with_root: 1,
@ -285,161 +329,127 @@ export default {
module_id: this.moduleId
})
console.log(res)
this.menus = res;
this.loading = false;
this.menus = res
this.loading = false
this.$nextTick(() => {
this.$refs['menuTable'].setAllTreeExpand(true)
})
} catch (err) {
console.error(err)
this.loading = false;
this.loading = false
}
},
async saveMenuRowEvent (row) {
async saveMenuRowEvent(row) {
try {
await this.$confirm("确认保存?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
await this.$confirm('确认保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
await this.$refs['menuTable'].clearEdit()
let form = deepCopy(this.menuForm)
for (let key in form) {
const form = deepCopy(this.menuForm)
for (const key in form) {
form[key] = row[key]
}
this.loading = true;
this.loading = true
await menuSave(form)
await this.getMenu();
this.loading = false;
await this.getMenu()
this.loading = false
} catch (err) {
this.loading = false;
this.loading = false
}
},
async destroyMenuRowEvent (row) {
async destroyMenuRowEvent(row) {
try {
await this.$confirm("确认删除?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
await this.$confirm('确认删除?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
this.loading = true;
this.loading = true
if (row.id) {
await menuDestroy({
id: row.id
})
await this.getMenu();
await this.getMenu()
} else {
this.menus.splice(this.menus.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY),1)
this.menus.splice(this.menus.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY), 1)
}
this.loading = false;
this.loading = false
} catch (err) {
this.loading = false;
this.loading = false
}
},
async getRole() {
this.loading = true;
this.loading = true
try {
const res = await role({
module_id: this.moduleId
})
console.log(res)
this.roles = res.rows;
this.roleTotal = res.total;
this.loading = false;
this.roles = res.rows
this.roleTotal = res.total
this.loading = false
} catch (err) {
console.error(err)
this.loading = false;
this.loading = false
}
},
async saveRoleRowEvent (row) {
async saveRoleRowEvent(row) {
try {
await this.$confirm("确认保存?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
await this.$confirm('确认保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
await this.$refs['menuTable'].clearEdit()
let form = deepCopy(this.menuForm)
for (let key in form) {
const form = deepCopy(this.menuForm)
for (const key in form) {
form[key] = row[key]
}
this.loading = true;
this.loading = true
await roleSave(form)
await this.getRole();
this.loading = false;
await this.getRole()
this.loading = false
} catch (err) {
this.loading = false;
this.loading = false
}
},
async destroyRoleRowEvent (row) {
async destroyRoleRowEvent(row) {
try {
await this.$confirm("确认删除?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
await this.$confirm('确认删除?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
this.loading = true;
this.loading = true
if (row.id) {
await roleDestroy({
id: row.id
})
await this.getRole();
await this.getRole()
} else {
this.roles.splice(this.roles.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY),1)
this.roles.splice(this.roles.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY), 1)
}
this.loading = false;
this.loading = false
} catch (err) {
this.loading = false;
this.loading = false
}
},
async getUsers() {
this.loading = true;
this.loading = true
try {
const res = await grant({
module_id: this.moduleId
})
console.log(res)
this.users = res.rows;
this.userTotal = res.total;
this.loading = false;
this.users = res.rows
this.userTotal = res.total
this.loading = false
} catch (err) {
console.error(err)
this.loading = false;
}
}
},
computed: {
isActiveStatus () {
return function (row,table) {
if (this.$refs[table]) {
return this.$refs[table].isEditByRow(row)
}
}
},
},
watch: {
isShow(newVal) {
this.visible = newVal;
this.getMenu()
},
visible(newVal) {
this.$emit('update:isShow', newVal);
},
activeName: {
handler:function(newVal) {
switch (newVal){
case 'menu':
this.getMenu()
break;
case 'role':
this.getRole()
break;
case 'permission':
this.getUsers()
break;
}
this.loading = false
}
}
},
created() {
this.getRole()
}
}
</script>

@ -73,14 +73,14 @@ export default {
tableData: [],
validRules: {
name: [
{ required: true, message: "请输入模块名称" }
{ required: true, message: '请输入模块名称' }
]
},
form: {
id: "",
name: "",
manager_id: "",
leader_id: "",
id: '',
name: '',
manager_id: '',
leader_id: '',
sortnumber: 0
}
}

@ -1,9 +1,7 @@
<template>
<div id="iframe-app"
v-loading="isLoading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgb(255, 255, 255)">
<div
id="iframe-app"
>
<!-- <Wujie id="wujie-app"-->
<!-- width="100%"-->
<!-- sync-->
@ -14,17 +12,27 @@
<!-- :url="module_uri"-->
<!-- :props="props">-->
<!-- </Wujie>-->
<iframe ref="program"
:src="module_uri"
frameborder="0"></iframe>
<div
v-show="isLoading"
v-loading="isLoading"
class="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgb(255, 255, 255)"
/>
<iframe
ref="program"
:src="module_uri"
frameborder="0"
/>
</div>
</template>
<script>
import { getToken } from "@/utils/auth"
import { getToken } from '@/utils/auth'
import Wujie from 'wujie-vue2'
import { mapGetters } from 'vuex'
let loadedModels = [];
const loadedModels = []
export default {
components: {
Wujie
@ -32,33 +40,35 @@ export default {
data() {
return {
props: {
auth_token: "",
module_name: "",
auth_token: '',
module_name: ''
},
isLoading: true,
isLoading: true
}
},
methods: {
postMessage() {
this.$refs['program'].contentWindow.postMessage(this.props)
if(this.props.auth_token && this.props.module_name) {
this.$refs['program'].contentWindow.postMessage(this.props)
}
}
},
computed: {
...mapGetters(['module_uri','module_name'])
...mapGetters(['module_uri', 'module_name'])
},
watch: {
module_uri: {
handler:function(newVal) {
this.isLoading = true;
this.props.module_name = this.module_name;
this.props.auth_token = getToken();
handler: function(newVal) {
this.isLoading = true
this.props.module_name = this.module_name
this.props.auth_token = getToken()
this.$nextTick(() => {
this.$refs['program'].onload = this.postMessage
console.log(loadedModels)
if(loadedModels.indexOf(this.props.module_name) === -1) {
if (loadedModels.indexOf(this.props.module_name) === -1) {
setTimeout(() => {
this.isLoading = false
},1500)
}, 1500)
loadedModels.push(this.props.module_name)
} else {
this.isLoading = false
@ -74,7 +84,7 @@ export default {
mounted() {
},
beforeDestroy() {
this.$refs['program'].onload = null;
this.$refs['program'].onload = null
}
}
</script>
@ -98,6 +108,13 @@ export default {
width: 100%;
height: 100%;
.loading {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
iframe {
width: 100%;
height: 100%;

Loading…
Cancel
Save