diff options
Diffstat (limited to 'UI source code/dns_mapping_ui-master/src/views')
61 files changed, 8511 insertions, 0 deletions
diff --git a/UI source code/dns_mapping_ui-master/src/views/components/Echarts.vue b/UI source code/dns_mapping_ui-master/src/views/components/Echarts.vue new file mode 100644 index 0000000..86533c3 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/Echarts.vue @@ -0,0 +1,19 @@ +<template> + <div class="dashboard-container"> + + </div> +</template> + +<script> + + + + +export default { + +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/components/Editor.vue b/UI source code/dns_mapping_ui-master/src/views/components/Editor.vue new file mode 100644 index 0000000..ced461c --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/Editor.vue @@ -0,0 +1,74 @@ +<template> + <div class="app-container"> + <p class="warn-content"> + 富文本基于 + <el-link type="primary" href="https://www.kancloud.cn/wangfupeng/wangeditor3/332599" target="_blank">wangEditor</el-link> + </p> + <el-row :gutter="10"> + <el-col :xs="24" :sm="24" :md="15" :lg="15" :xl="15"> + <div ref="editor" class="text" /> + </el-col> + <el-col :xs="24" :sm="24" :md="9" :lg="9" :xl="9"> + <div v-html="editorContent" /> + </el-col> + </el-row> + </div> +</template> + +<script> +import { mapGetters } from 'vuex' +import { upload } from '@/utils/upload' +import E from 'wangeditor' +export default { + name: 'Editor', + data() { + return { + editorContent: + ` + <ul> + <li>更多帮助请查看官方文档:<a style="color: #42b983" target="_blank" href="https://www.wangeditor.com/doc/">wangEditor</a></li> + </ul> + ` + } + }, + computed: { + ...mapGetters([ + 'imagesUploadApi', + 'baseApi' + ]) + }, + mounted() { + const _this = this + var editor = new E(this.$refs.editor) + // 自定义菜单配置 + editor.config.zIndex = 5 + // 文件上传 + editor.config.customUploadImg = function(files, insert) { + // files 是 input 中选中的文件列表 + // insert 是获取图片 url 后,插入到编辑器的方法 + files.forEach(image => { + upload(_this.imagesUploadApi, image).then(res => { + const data = res.data + const url = _this.baseApi + '/file/' + data.type + '/' + data.realName + insert(url) + }) + }) + } + editor.config.onchange = (html) => { + this.editorContent = html + } + editor.create() + // 初始化数据 + editor.txt.html(this.editorContent) + } +} +</script> + +<style scoped> + .text { + text-align:left; + } + ::v-deep .w-e-text-container { + height: 420px !important; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/components/MarkDown.vue b/UI source code/dns_mapping_ui-master/src/views/components/MarkDown.vue new file mode 100644 index 0000000..bb5a10e --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/MarkDown.vue @@ -0,0 +1,49 @@ +<template> + <div class="app-container"> + <p class="warn-content"> + Markdown 基于 + <el-link type="primary" href="https://github.com/hinesboy/mavonEditor" target="_blank">MavonEditor</el-link> + </p> + <mavon-editor ref="md" :style="'height:' + height" @imgAdd="imgAdd" /> + </div> +</template> + +<script> +import { upload } from '@/utils/upload' +import { mapGetters } from 'vuex' +export default { + name: 'Markdown', + data() { + return { + height: document.documentElement.clientHeight - 200 + 'px' + } + }, + computed: { + ...mapGetters([ + 'imagesUploadApi', + 'baseApi' + ]) + }, + mounted() { + const that = this + window.onresize = function temp() { + that.height = document.documentElement.clientHeight - 200 + 'px' + } + }, + methods: { + imgAdd(pos, $file) { + upload(this.imagesUploadApi, $file).then(res => { + const data = res.data + const url = this.baseApi + '/file/' + data.type + '/' + data.realName + this.$refs.md.$img2Url(pos, url) + }) + } + } +} +</script> + +<style scoped> + .v-note-wrapper.shadow { + z-index: 5; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/components/YamlEdit.vue b/UI source code/dns_mapping_ui-master/src/views/components/YamlEdit.vue new file mode 100644 index 0000000..9455fde --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/YamlEdit.vue @@ -0,0 +1,207 @@ +<template> + <div class="app-container"> + <p class="warn-content"> + Yaml编辑器 基于 + <a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>, + 主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a> + </p> + <Yaml :value="value" :height="height" /> + </div> +</template> + +<script> +import Yaml from '@/components/YamlEdit/index' +export default { + name: 'YamlEdit', + components: { Yaml }, + data() { + return { + height: document.documentElement.clientHeight - 210 + 'px', + value: '# 展示数据,如需更换主题,请在src/components/YamlEdit 目录中搜索原主题名称进行替换\n' + + '\n' + + '# ===================================================================\n' + + '# Spring Boot configuration.\n' + + '#\n' + + '# This configuration will be overridden by the Spring profile you use,\n' + + '# for example application-dev.yml if you use the "dev" profile.\n' + + '#\n' + + '# More information on profiles: https://www.jhipster.tech/profiles/\n' + + '# More information on configuration properties: https://www.jhipster.tech/common-application-properties/\n' + + '# ===================================================================\n' + + '\n' + + '# ===================================================================\n' + + '# Standard Spring Boot properties.\n' + + '# Full reference is available at:\n' + + '# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html\n' + + '# ===================================================================\n' + + '\n' + + 'eureka:\n' + + ' client:\n' + + ' enabled: true\n' + + ' healthcheck:\n' + + ' enabled: true\n' + + ' fetch-registry: true\n' + + ' register-with-eureka: true\n' + + ' instance-info-replication-interval-seconds: 10\n' + + ' registry-fetch-interval-seconds: 10\n' + + ' instance:\n' + + ' appname: product\n' + + ' instanceId: product:${spring.application.instance-id:${random.value}}\n' + + ' #instanceId: 127.0.0.1:9080\n' + + ' lease-renewal-interval-in-seconds: 5\n' + + ' lease-expiration-duration-in-seconds: 10\n' + + ' status-page-url-path: ${management.endpoints.web.base-path}/info\n' + + ' health-check-url-path: ${management.endpoints.web.base-path}/health\n' + + ' metadata-map:\n' + + ' zone: primary # This is needed for the load balancer\n' + + ' profile: ${spring.profiles.active}\n' + + ' version: ${info.project.version:}\n' + + ' git-version: ${git.commit.id.describe:}\n' + + ' git-commit: ${git.commit.id.abbrev:}\n' + + ' git-branch: ${git.branch:}\n' + + 'ribbon:\n' + + ' ReadTimeout: 120000\n' + + ' ConnectTimeout: 300000\n' + + ' eureka:\n' + + ' enabled: true\n' + + 'zuul:\n' + + ' host:\n' + + ' connect-timeout-millis: 5000\n' + + ' max-per-route-connections: 10000\n' + + ' max-total-connections: 5000\n' + + ' socket-timeout-millis: 60000\n' + + ' semaphore:\n' + + ' max-semaphores: 500\n' + + '\n' + + 'feign:\n' + + ' hystrix:\n' + + ' enabled: true\n' + + ' client:\n' + + ' config:\n' + + ' default:\n' + + ' connectTimeout: 500000\n' + + ' readTimeout: 500000\n' + + '\n' + + '# See https://github.com/Netflix/Hystrix/wiki/Configuration\n' + + 'hystrix:\n' + + ' command:\n' + + ' default:\n' + + ' circuitBreaker:\n' + + ' sleepWindowInMilliseconds: 100000\n' + + ' forceClosed: true\n' + + ' execution:\n' + + ' isolation:\n' + + '# strategy: SEMAPHORE\n' + + '# See https://github.com/spring-cloud/spring-cloud-netflix/issues/1330\n' + + ' thread:\n' + + ' timeoutInMilliseconds: 60000\n' + + ' shareSecurityContext: true\n' + + '\n' + + 'management:\n' + + ' endpoints:\n' + + ' web:\n' + + ' base-path: /management\n' + + ' exposure:\n' + + ' include: ["configprops", "env", "health", "info", "threaddump"]\n' + + ' endpoint:\n' + + ' health:\n' + + ' show-details: when_authorized\n' + + ' info:\n' + + ' git:\n' + + ' mode: full\n' + + ' health:\n' + + ' mail:\n' + + ' enabled: false # When using the MailService, configure an SMTP server and set this to true\n' + + ' metrics:\n' + + ' enabled: false # http://micrometer.io/ is disabled by default, as we use http://metrics.dropwizard.io/ instead\n' + + '\n' + + 'spring:\n' + + ' application:\n' + + ' name: product\n' + + ' jpa:\n' + + ' open-in-view: false\n' + + ' hibernate:\n' + + ' ddl-auto: update\n' + + ' naming:\n' + + ' physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy\n' + + ' implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy\n' + + ' messages:\n' + + ' basename: i18n/messages\n' + + ' mvc:\n' + + ' favicon:\n' + + ' enabled: false\n' + + ' thymeleaf:\n' + + ' mode: HTML\n' + + 'security:\n' + + ' oauth2:\n' + + ' resource:\n' + + ' filter-order: 3\n' + + '\n' + + 'server:\n' + + ' servlet:\n' + + ' session:\n' + + ' cookie:\n' + + ' http-only: true\n' + + '\n' + + '# Properties to be exposed on the /info management endpoint\n' + + 'info:\n' + + ' # Comma separated list of profiles that will trigger the ribbon to show\n' + + ' display-ribbon-on-profiles: "dev"\n' + + '\n' + + '# ===================================================================\n' + + '# JHipster specific properties\n' + + '#\n' + + '# Full reference is available at: https://www.jhipster.tech/common-application-properties/\n' + + '# ===================================================================\n' + + '\n' + + 'jhipster:\n' + + ' async:\n' + + ' core-pool-size: 2\n' + + ' max-pool-size: 50\n' + + ' queue-capacity: 10000\n' + + ' # By default CORS is disabled. Uncomment to enable.\n' + + ' #cors:\n' + + ' #allowed-origins: "*"\n' + + ' #allowed-methods: "*"\n' + + ' #allowed-headers: "*"\n' + + ' #exposed-headers: "Authorization,Link,X-Total-Count"\n' + + ' #allow-credentials: true\n' + + ' #max-age: 1800\n' + + ' mail:\n' + + ' from: product@localhost\n' + + ' swagger:\n' + + ' default-include-pattern: /api/.*\n' + + ' title: product API\n' + + ' description: product API documentation\n' + + ' version: 0.0.1\n' + + ' terms-of-service-url:\n' + + ' contact-name:\n' + + ' contact-url:\n' + + ' contact-email:\n' + + ' license:\n' + + ' license-url:\n' + + '\n' + + '# ===================================================================\n' + + '# Application specific properties\n' + + '# Add your own application properties here, see the ApplicationProperties class\n' + + '# to have type-safe configuration, like in the JHipsterProperties above\n' + + '#\n' + + '# More documentation is available at:\n' + + '# https://www.jhipster.tech/common-application-properties/\n' + + '# ===================================================================\n' + + '\n' + + '# application:\n' + } + }, + mounted() { + const that = this + window.onresize = function temp() { + that.height = document.documentElement.clientHeight - 210 + 'px' + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/components/excel/upload-excel.vue b/UI source code/dns_mapping_ui-master/src/views/components/excel/upload-excel.vue new file mode 100644 index 0000000..75f7634 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/excel/upload-excel.vue @@ -0,0 +1,41 @@ +<template> + <div class="app-container"> + <upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" /> + <el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;"> + <el-table-column v-for="item of tableHeader" :key="item" :prop="item" :label="item" /> + </el-table> + </div> +</template> + +<script> +import UploadExcelComponent from '@/components/UploadExcel/index.vue' + +export default { + name: 'UploadExcel', + components: { UploadExcelComponent }, + data() { + return { + tableData: [], + tableHeader: [] + } + }, + methods: { + beforeUpload(file) { + const isLt1M = file.size / 1024 / 1024 < 1 + if (isLt1M) { + return true + } + + this.$message({ + message: '请不要上传大于1m的文件.', + type: 'warning' + }) + return false + }, + handleSuccess({ results, header }) { + this.tableData = results + this.tableHeader = header + } + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/components/icons/element-icons.js b/UI source code/dns_mapping_ui-master/src/views/components/icons/element-icons.js new file mode 100644 index 0000000..df72201 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/icons/element-icons.js @@ -0,0 +1,74 @@ +const elementIcons = [ + 'info', + 'error', + 'success', + 'warning', + 'question', + 'back', + 'arrow-left', + 'arrow-down', + 'arrow-right', + 'arrow-up', + 'caret-left', + 'caret-bottom', + 'caret-top', + 'caret-right', + 'd-arrow-left', + 'd-arrow-right', + 'minus', + 'plus', + 'remove', + 'circle-plus', + 'remove-outline', + 'circle-plus-outline', + 'close', + 'check', + 'circle-close', + 'circle-check', + 'circle-close-outline', + 'circle-check-outline', + 'zoom-out', + 'zoom-in', + 'd-caret', + 'sort', + 'sort-down', + 'sort-up', + 'tickets', + 'document', + 'goods', + 'sold-out', + 'news', + 'message', + 'date', + 'printer', + 'time', + 'bell', + 'mobile-phone', + 'service', + 'view', + 'menu', + 'more', + 'more-outline', + 'star-on', + 'star-off', + 'location', + 'location-outline', + 'phone', + 'phone-outline', + 'picture', + 'picture-outline', + 'delete', + 'search', + 'edit', + 'edit-outline', + 'rank', + 'refresh', + 'share', + 'setting', + 'upload', + 'upload2', + 'download', + 'loading' +] + +export default elementIcons diff --git a/UI source code/dns_mapping_ui-master/src/views/components/icons/index.vue b/UI source code/dns_mapping_ui-master/src/views/components/icons/index.vue new file mode 100644 index 0000000..d060173 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/icons/index.vue @@ -0,0 +1,97 @@ +<template> + <div class="icons-container"> + <aside> + <a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use + </a> + </aside> + <el-tabs type="border-card"> + <el-tab-pane label="Icons"> + <div class="grid"> + <div v-for="item of svgIcons" :key="item" @click="handleClipboard(generateIconCode(item),$event)"> + <el-tooltip placement="top"> + <div slot="content"> + {{ generateIconCode(item) }} + </div> + <div class="icon-item"> + <svg-icon :icon-class="item" class-name="disabled" /> + <span>{{ item }}</span> + </div> + </el-tooltip> + </div> + </div> + </el-tab-pane> + <el-tab-pane label="Element-UI Icons"> + <div class="grid"> + <div v-for="item of elementIcons" :key="item" @click="handleClipboard(generateElementIconCode(item),$event)"> + <el-tooltip placement="top"> + <div slot="content"> + {{ generateElementIconCode(item) }} + </div> + <div class="icon-item"> + <i :class="'el-icon-' + item" /> + <span>{{ item }}</span> + </div> + </el-tooltip> + </div> + </div> + </el-tab-pane> + </el-tabs> + </div> +</template> + +<script> +import clipboard from '@/utils/clipboard' +import svgIcons from './svg-icons' +import elementIcons from './element-icons' +export default { + name: 'Icons', + data() { + return { + svgIcons, + elementIcons + } + }, + methods: { + generateIconCode(symbol) { + return `<svg-icon icon-class="${symbol}" />` + }, + generateElementIconCode(symbol) { + return `<i class="el-icon-${symbol}" />` + }, + handleClipboard(text, event) { + clipboard(text, event) + } + } +} +</script> + +<style lang="scss" scoped> +.icons-container { + margin: 10px 20px 0; + overflow: hidden; + + .grid { + position: relative; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + } + .icon-item { + margin: 20px; + height: 85px; + text-align: center; + width: 100px; + float: left; + font-size: 30px; + color: #24292e; + cursor: pointer; + } + span { + display: block; + font-size: 16px; + margin-top: 10px; + } + .disabled { + pointer-events: none; + } +} +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/components/icons/svg-icons.js b/UI source code/dns_mapping_ui-master/src/views/components/icons/svg-icons.js new file mode 100644 index 0000000..724cd8e --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/components/icons/svg-icons.js @@ -0,0 +1,10 @@ +const req = require.context('../../../assets/icons/svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys() + +const re = /\.\/(.*)\.svg/ + +const svgIcons = requireAll(req).map(i => { + return i.match(re)[1] +}) + +export default svgIcons diff --git a/UI source code/dns_mapping_ui-master/src/views/dashboard/LineChart.vue b/UI source code/dns_mapping_ui-master/src/views/dashboard/LineChart.vue new file mode 100644 index 0000000..e654168 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/dashboard/LineChart.vue @@ -0,0 +1,135 @@ +<template> + <div :class="className" :style="{height:height,width:width}" /> +</template> + +<script> +import echarts from 'echarts' +require('echarts/theme/macarons') // echarts theme +import resize from './mixins/resize' + +export default { + mixins: [resize], + props: { + className: { + type: String, + default: 'chart' + }, + width: { + type: String, + default: '100%' + }, + height: { + type: String, + default: '350px' + }, + autoResize: { + type: Boolean, + default: true + }, + chartData: { + type: Object, + required: true + } + }, + data() { + return { + chart: null + } + }, + watch: { + chartData: { + deep: true, + handler(val) { + this.setOptions(val) + } + } + }, + mounted() { + this.$nextTick(() => { + this.initChart() + }) + }, + beforeDestroy() { + if (!this.chart) { + return + } + this.chart.dispose() + this.chart = null + }, + methods: { + initChart() { + this.chart = echarts.init(this.$el, 'macarons') + this.setOptions(this.chartData) + }, + setOptions({ expectedData, actualData } = {}) { + this.chart.setOption({ + xAxis: { + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + boundaryGap: false, + axisTick: { + show: false + } + }, + grid: { + left: 10, + right: 10, + bottom: 20, + top: 30, + containLabel: true + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + padding: [5, 10] + }, + yAxis: { + axisTick: { + show: false + } + }, + legend: { + data: ['expected', 'actual'] + }, + series: [{ + name: 'expected', itemStyle: { + normal: { + color: '#FF005A', + lineStyle: { + color: '#FF005A', + width: 2 + } + } + }, + smooth: true, + type: 'line', + data: expectedData, + animationDuration: 2800, + animationEasing: 'cubicInOut' + }, + { + name: 'actual', + smooth: true, + type: 'line', + itemStyle: { + normal: { + color: '#3888fa', + lineStyle: { + color: '#3888fa', + width: 2 + }, + areaStyle: { + color: '#f3f8ff' + } + } + }, + data: actualData, + animationDuration: 2800, + animationEasing: 'quadraticOut' + }] + }) + } + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/dashboard/PanelGroup.vue b/UI source code/dns_mapping_ui-master/src/views/dashboard/PanelGroup.vue new file mode 100644 index 0000000..fe1815f --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/dashboard/PanelGroup.vue @@ -0,0 +1,181 @@ +<template> + <el-row :gutter="40" class="panel-group"> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('newVisitis')"> + <div class="card-panel-icon-wrapper icon-people"> + <svg-icon icon-class="peoples" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + New Visits + </div> + <count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" /> + </div> + </div> + </el-col> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('messages')"> + <div class="card-panel-icon-wrapper icon-message"> + <svg-icon icon-class="message" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + Messages + </div> + <count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" /> + </div> + </div> + </el-col> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('purchases')"> + <div class="card-panel-icon-wrapper icon-money"> + <svg-icon icon-class="money" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + Purchases + </div> + <count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" /> + </div> + </div> + </el-col> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('shoppings')"> + <div class="card-panel-icon-wrapper icon-shopping"> + <svg-icon icon-class="shopping" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + Shoppings + </div> + <count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" /> + </div> + </div> + </el-col> + </el-row> +</template> + +<script> +import CountTo from 'vue-count-to' + +export default { + components: { + CountTo + }, + methods: { + handleSetLineChartData(type) { + this.$emit('handleSetLineChartData', type) + } + } +} +</script> + +<style lang="scss" scoped> +.panel-group { + margin-top: 18px; + + .card-panel-col { + margin-bottom: 32px; + } + + .card-panel { + height: 108px; + cursor: pointer; + font-size: 12px; + position: relative; + overflow: hidden; + color: #666; + background: #fff; + box-shadow: 4px 4px 40px rgba(0, 0, 0, .05); + border-color: rgba(0, 0, 0, .05); + + &:hover { + .card-panel-icon-wrapper { + color: #fff; + } + + .icon-people { + background: #40c9c6; + } + + .icon-message { + background: #36a3f7; + } + + .icon-money { + background: #f4516c; + } + + .icon-shopping { + background: #34bfa3 + } + } + + .icon-people { + color: #40c9c6; + } + + .icon-message { + color: #36a3f7; + } + + .icon-money { + color: #f4516c; + } + + .icon-shopping { + color: #34bfa3 + } + + .card-panel-icon-wrapper { + float: left; + margin: 14px 0 0 14px; + padding: 16px; + transition: all 0.38s ease-out; + border-radius: 6px; + } + + .card-panel-icon { + float: left; + font-size: 48px; + } + + .card-panel-description { + float: right; + font-weight: bold; + margin: 26px; + margin-left: 0px; + + .card-panel-text { + line-height: 18px; + color: rgba(0, 0, 0, 0.45); + font-size: 16px; + margin-bottom: 12px; + } + + .card-panel-num { + font-size: 20px; + } + } + } +} + +// @media (max-width:550px) { +// .card-panel-description { +// display: none; +// } + +// .card-panel-icon-wrapper { +// float: none !important; +// width: 100%; +// height: 100%; +// margin: 0 !important; + +// .svg-icon { +// display: block; +// margin: 14px auto !important; +// float: none !important; +// } +// } +// } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/dashboard/mixins/resize.js b/UI source code/dns_mapping_ui-master/src/views/dashboard/mixins/resize.js new file mode 100644 index 0000000..234953b --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/dashboard/mixins/resize.js @@ -0,0 +1,55 @@ +import { debounce } from '@/utils' + +export default { + data() { + return { + $_sidebarElm: null, + $_resizeHandler: null + } + }, + mounted() { + this.$_resizeHandler = debounce(() => { + if (this.chart) { + this.chart.resize() + } + }, 100) + this.$_initResizeEvent() + this.$_initSidebarResizeEvent() + }, + beforeDestroy() { + this.$_destroyResizeEvent() + this.$_destroySidebarResizeEvent() + }, + // to fixed bug when cached by keep-alive + // https://github.com/PanJiaChen/vue-element-admin/issues/2116 + activated() { + this.$_initResizeEvent() + this.$_initSidebarResizeEvent() + }, + deactivated() { + this.$_destroyResizeEvent() + this.$_destroySidebarResizeEvent() + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_initResizeEvent() { + window.addEventListener('resize', this.$_resizeHandler) + }, + $_destroyResizeEvent() { + window.removeEventListener('resize', this.$_resizeHandler) + }, + $_sidebarResizeHandler(e) { + if (e.propertyName === 'width') { + this.$_resizeHandler() + } + }, + $_initSidebarResizeEvent() { + this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] + this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler) + }, + $_destroySidebarResizeEvent() { + this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler) + } + } +} diff --git a/UI source code/dns_mapping_ui-master/src/views/features/401.vue b/UI source code/dns_mapping_ui-master/src/views/features/401.vue new file mode 100644 index 0000000..8a3b69e --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/features/401.vue @@ -0,0 +1,89 @@ +<template> + <div class="errPage-container"> + <el-button icon="arrow-left" class="pan-back-btn" @click="back"> + 返回 + </el-button> + <el-row> + <el-col :span="12"> + <h1 class="text-jumbo text-ginormous"> + Oops! + </h1> + <h2>你没有权限去该页面</h2> + <h6>如有不满请联系你领导</h6> + <ul class="list-unstyled"> + <li>或者你可以去:</li> + <li class="link-type"> + <router-link to="/dashboard"> + 回首页 + </router-link> + </li> + </ul> + </el-col> + <el-col :span="12"> + <img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream."> + </el-col> + </el-row> + </div> +</template> + +<script> +import errGif from '@/assets/401_images/401.gif' + +export default { + name: 'Page401', + data() { + return { + errGif: errGif + '?' + +new Date() + } + }, + methods: { + back() { + if (this.$route.query.noGoBack) { + this.$router.push({ path: '/dashboard' }) + } else { + this.$router.go(-1) + } + } + } +} +</script> + +<style lang="scss" scoped> + .errPage-container { + width: 800px; + max-width: 100%; + margin: 100px auto; + .pan-back-btn { + background: #008489; + color: #fff; + border: none!important; + } + .pan-gif { + margin: 0 auto; + display: block; + } + .pan-img { + display: block; + margin: 0 auto; + width: 100%; + } + .text-jumbo { + font-size: 60px; + font-weight: 700; + color: #484848; + } + .list-unstyled { + font-size: 14px; + li { + padding-bottom: 5px; + } + a { + color: #008489; + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/features/404.vue b/UI source code/dns_mapping_ui-master/src/views/features/404.vue new file mode 100644 index 0000000..237d81f --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/features/404.vue @@ -0,0 +1,225 @@ +<template> + <div class="wscn-http404-container"> + <div class="wscn-http404"> + <div class="pic-404"> + <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404"> + <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404"> + <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404"> + <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404"> + </div> + <div class="bullshit"> + <div class="bullshit__oops">OOPS!</div> + <div class="bullshit__headline">{{ message }}</div> + <div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div> + <a href="/" class="bullshit__return-home">返回首页</a> + </div> + </div> + </div> +</template> + +<script> + +export default { + name: 'Page404', + computed: { + message() { + return '网管说这个页面你不能进......' + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> +.wscn-http404-container{ + transform: translate(-50%,-50%); + position: absolute; + top: 40%; + left: 50%; +} +.wscn-http404 { + position: relative; + width: 1200px; + padding: 0 50px; + overflow: hidden; + .pic-404 { + position: relative; + float: left; + width: 600px; + overflow: hidden; + &__parent { + width: 100%; + } + &__child { + position: absolute; + &.left { + width: 80px; + top: 17px; + left: 220px; + opacity: 0; + animation-name: cloudLeft; + animation-duration: 2s; + animation-timing-function: linear; + animation-fill-mode: forwards; + animation-delay: 1s; + } + &.mid { + width: 46px; + top: 10px; + left: 420px; + opacity: 0; + animation-name: cloudMid; + animation-duration: 2s; + animation-timing-function: linear; + animation-fill-mode: forwards; + animation-delay: 1.2s; + } + &.right { + width: 62px; + top: 100px; + left: 500px; + opacity: 0; + animation-name: cloudRight; + animation-duration: 2s; + animation-timing-function: linear; + animation-fill-mode: forwards; + animation-delay: 1s; + } + @keyframes cloudLeft { + 0% { + top: 17px; + left: 220px; + opacity: 0; + } + 20% { + top: 33px; + left: 188px; + opacity: 1; + } + 80% { + top: 81px; + left: 92px; + opacity: 1; + } + 100% { + top: 97px; + left: 60px; + opacity: 0; + } + } + @keyframes cloudMid { + 0% { + top: 10px; + left: 420px; + opacity: 0; + } + 20% { + top: 40px; + left: 360px; + opacity: 1; + } + 70% { + top: 130px; + left: 180px; + opacity: 1; + } + 100% { + top: 160px; + left: 120px; + opacity: 0; + } + } + @keyframes cloudRight { + 0% { + top: 100px; + left: 500px; + opacity: 0; + } + 20% { + top: 120px; + left: 460px; + opacity: 1; + } + 80% { + top: 180px; + left: 340px; + opacity: 1; + } + 100% { + top: 200px; + left: 300px; + opacity: 0; + } + } + } + } + .bullshit { + position: relative; + float: left; + width: 300px; + padding: 30px 0; + overflow: hidden; + &__oops { + font-size: 32px; + font-weight: bold; + line-height: 40px; + color: #1482f0; + opacity: 0; + margin-bottom: 20px; + animation-name: slideUp; + animation-duration: 0.5s; + animation-fill-mode: forwards; + } + &__headline { + font-size: 20px; + line-height: 24px; + color: #222; + font-weight: bold; + opacity: 0; + margin-bottom: 10px; + animation-name: slideUp; + animation-duration: 0.5s; + animation-delay: 0.1s; + animation-fill-mode: forwards; + } + &__info { + font-size: 13px; + line-height: 21px; + color: grey; + opacity: 0; + margin-bottom: 30px; + animation-name: slideUp; + animation-duration: 0.5s; + animation-delay: 0.2s; + animation-fill-mode: forwards; + } + &__return-home { + display: block; + float: left; + width: 110px; + height: 36px; + background: #1482f0; + border-radius: 100px; + text-align: center; + color: #ffffff; + opacity: 0; + font-size: 14px; + line-height: 36px; + cursor: pointer; + animation-name: slideUp; + animation-duration: 0.5s; + animation-delay: 0.3s; + animation-fill-mode: forwards; + } + @keyframes slideUp { + 0% { + transform: translateY(60px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } + } + } +} +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/features/redirect.vue b/UI source code/dns_mapping_ui-master/src/views/features/redirect.vue new file mode 100644 index 0000000..db4c1d6 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/features/redirect.vue @@ -0,0 +1,12 @@ +<script> +export default { + created() { + const { params, query } = this.$route + const { path } = params + this.$router.replace({ path: '/' + path, query }) + }, + render: function(h) { + return h() // avoid warning message + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/generator/config.vue b/UI source code/dns_mapping_ui-master/src/views/generator/config.vue new file mode 100644 index 0000000..2690dd5 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/generator/config.vue @@ -0,0 +1,325 @@ +<template> + <div class="app-container"> + <el-row :gutter="15"> + <el-col style="margin-bottom: 10px"> + <el-card class="box-card" shadow="never"> + <div slot="header" class="clearfix"> + <span class="role-span">字段配置:{{ tableName }}</span> + <el-button + :loading="genLoading" + icon="el-icon-s-promotion" + size="mini" + style="float: right; padding: 6px 9px;" + type="success" + @click="toGen" + >保存&生成</el-button> + <el-button + :loading="columnLoading" + icon="el-icon-check" + size="mini" + style="float: right; padding: 6px 9px;margin-right: 9px" + type="primary" + @click="saveColumnConfig" + >保存</el-button> + <el-tooltip class="item" effect="dark" content="数据库中表字段变动时使用该功能" placement="top-start"> + <el-button + :loading="syncLoading" + icon="el-icon-refresh" + size="mini" + style="float: right; padding: 6px 9px;" + type="info" + @click="sync" + >同步</el-button> + </el-tooltip> + </div> + <el-form size="small" label-width="90px"> + <el-table v-loading="loading" :data="data" :max-height="tableHeight" size="small" style="width: 100%;margin-bottom: 15px"> + <el-table-column prop="columnName" label="字段名称" /> + <el-table-column prop="columnType" label="字段类型" /> + <el-table-column prop="remark" label="字段描述"> + <template slot-scope="scope"> + <el-input v-model="data[scope.$index].remark" size="mini" class="edit-input" /> + </template> + </el-table-column> + <el-table-column align="center" label="必填" width="70px"> + <template slot-scope="scope"> + <el-checkbox v-model="data[scope.$index].notNull" /> + </template> + </el-table-column> + <el-table-column align="center" label="列表" width="70px"> + <template slot-scope="scope"> + <el-checkbox v-model="data[scope.$index].listShow" /> + </template> + </el-table-column> + <el-table-column align="center" label="表单" width="70px"> + <template slot-scope="scope"> + <el-checkbox v-model="data[scope.$index].formShow" /> + </template> + </el-table-column> + <el-table-column label="表单类型"> + <template slot-scope="scope"> + <el-select v-model="data[scope.$index].formType" filterable class="edit-input" clearable size="mini" placeholder="请选择"> + <el-option + label="文本框" + value="Input" + /> + <el-option + label="文本域" + value="Textarea" + /> + <el-option + label="单选框" + value="Radio" + /> + <el-option + label="下拉框" + value="Select" + /> + <el-option + label="日期框" + value="Date" + /> + </el-select> + </template> + </el-table-column> + <el-table-column label="查询方式"> + <template slot-scope="scope"> + <el-select v-model="data[scope.$index].queryType" filterable class="edit-input" clearable size="mini" placeholder="请选择"> + <el-option + label="=" + value="=" + /> + <el-option + label="!=" + value="!=" + /> + <el-option + label=">=" + value=">=" + /> + <el-option + label="<=" + value="<=" + /> + <el-option + label="Like" + value="Like" + /> + <el-option + label="NotNull" + value="NotNull" + /> + <el-option + label="BetWeen" + value="BetWeen" + /> + </el-select> + </template> + </el-table-column> + <el-table-column label="日期注解"> + <template slot-scope="scope"> + <el-select v-model="data[scope.$index].dateAnnotation" filterable class="edit-input" clearable size="mini" placeholder="请选择"> + <el-option + label="自动创建时间" + value="CreationTimestamp" + /> + <el-option + label="自动更新时间" + value="UpdateTimestamp" + /> + </el-select> + </template> + </el-table-column> + <el-table-column label="关联字典"> + <template slot-scope="scope"> + <el-select v-model="data[scope.$index].dictName" filterable class="edit-input" clearable size="mini" placeholder="请选择"> + <el-option v-for="item in dicts" :key="item.id" :label="item.remark === '' ? item.name : item.remark" :value="item.name" /> + </el-select> + </template> + </el-table-column> + </el-table> + </el-form> + </el-card> + </el-col> + <el-col> + <el-card class="box-card" shadow="never"> + <div slot="header" class="clearfix"> + <span class="role-span">生成配置</span> + <el-button + :loading="configLoading" + icon="el-icon-check" + size="mini" + style="float: right; padding: 6px 9px" + type="primary" + @click="doSubmit" + >保存</el-button> + </div> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="78px"> + <el-form-item label="作者名称" prop="author"> + <el-input v-model="form.author" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">类上面的作者名称</span> + </el-form-item> + <el-form-item label="模块名称" prop="moduleName"> + <el-input v-model="form.moduleName" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">模块的名称,请选择项目中已存在的模块</span> + </el-form-item> + <el-form-item label="至于包下" prop="pack"> + <el-input v-model="form.pack" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">项目包的名称,生成的代码放到哪个包里面</span> + </el-form-item> + <el-form-item label="接口名称" prop="apiAlias"> + <el-input v-model="form.apiAlias" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">接口的名称,用于控制器与接口文档中</span> + </el-form-item> + <el-form-item label="前端路径" prop="path"> + <el-input v-model="form.path" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">输入views文件夹下的目录,不存在即创建</span> + </el-form-item> + <!-- <el-form-item label="接口目录">--> + <!-- <el-input v-model="form.apiPath" style="width: 40%" />--> + <!-- <span style="color: #C0C0C0;margin-left: 10px;">Api存放路径[src/api],为空则自动生成路径</span>--> + <!-- </el-form-item>--> + <el-form-item label="去表前缀" prop="prefix"> + <el-input v-model="form.prefix" placeholder="默认不去除表前缀" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">默认不去除表前缀,可自定义</span> + </el-form-item> + <el-form-item label="是否覆盖" prop="cover"> + <el-radio-group v-model="form.cover" size="mini" style="width: 40%"> + <el-radio-button label="true">是</el-radio-button> + <el-radio-button label="false">否</el-radio-button> + </el-radio-group> + <span style="color: #C0C0C0;margin-left: 10px;">谨防误操作,请慎重选择</span> + </el-form-item> + </el-form> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import crud from '@/mixins/crud' +import { update, get } from '@/api/generator/genConfig' +import { save, sync, generator } from '@/api/generator/generator' +import { getDicts } from '@/api/system/dict' +export default { + name: 'GeneratorConfig', + components: {}, + mixins: [crud], + data() { + return { + activeName: 'first', tableName: '', tableHeight: 550, columnLoading: false, configLoading: false, dicts: [], syncLoading: false, genLoading: false, + form: { id: null, tableName: '', author: '', pack: '', path: '', moduleName: '', cover: 'false', apiPath: '', prefix: '', apiAlias: null }, + rules: { + author: [ + { required: true, message: '作者不能为空', trigger: 'blur' } + ], + pack: [ + { required: true, message: '包路径不能为空', trigger: 'blur' } + ], + moduleName: [ + { required: true, message: '包路径不能为空', trigger: 'blur' } + ], + path: [ + { required: true, message: '前端路径不能为空', trigger: 'blur' } + ], + apiAlias: [ + { required: true, message: '接口名称不能为空', trigger: 'blur' } + ], + cover: [ + { required: true, message: '不能为空', trigger: 'blur' } + ] + } + } + }, + created() { + this.tableHeight = document.documentElement.clientHeight - 385 + this.tableName = this.$route.params.tableName + this.$nextTick(() => { + this.init() + get(this.tableName).then(data => { + this.form = data + this.form.cover = this.form.cover.toString() + }) + getDicts().then(data => { + this.dicts = data + }) + }) + }, + methods: { + beforeInit() { + this.url = 'api/generator/columns' + const tableName = this.tableName + this.params = { tableName } + return true + }, + saveColumnConfig() { + this.columnLoading = true + save(this.data).then(res => { + this.notify('保存成功', 'success') + this.columnLoading = false + }).catch(err => { + this.columnLoading = false + console.log(err.response.data.message) + }) + }, + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.configLoading = true + update(this.form).then(res => { + this.notify('保存成功', 'success') + this.form = res + this.form.cover = this.form.cover.toString() + this.configLoading = false + }).catch(err => { + this.configLoading = false + console.log(err.response.data.message) + }) + } + }) + }, + sync() { + this.syncLoading = true + sync([this.tableName]).then(() => { + this.init() + this.notify('同步成功', 'success') + this.syncLoading = false + }).then(() => { + this.syncLoading = false + }) + }, + toGen() { + this.genLoading = true + save(this.data).then(res => { + this.notify('保存成功', 'success') + // 生成代码 + generator(this.tableName, 0).then(data => { + this.genLoading = false + this.notify('生成成功', 'success') + }).catch(err => { + this.genLoading = false + console.log(err.response.data.message) + }) + }).catch(err => { + this.genLoading = false + console.log(err.response.data.message) + }) + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss"> + .edit-input { + .el-input__inner { + border: 1px solid #e5e6e7; + } + } +</style> + +<style scoped> + ::v-deep .input-with-select .el-input-group__prepend { + background-color: #fff; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/generator/index.vue b/UI source code/dns_mapping_ui-master/src/views/generator/index.vue new file mode 100644 index 0000000..6a07dc4 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/generator/index.vue @@ -0,0 +1,114 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <el-input v-model="query.name" clearable size="small" placeholder="请输入表名" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <rrOperation /> + </div> + <crudOperation> + <el-tooltip slot="right" class="item" effect="dark" content="数据库中表字段变动时使用该功能" placement="top-start"> + <el-button + class="filter-item" + size="mini" + type="success" + icon="el-icon-refresh" + :loading="syncLoading" + :disabled="crud.selections.length === 0" + @click="sync" + >同步</el-button> + </el-tooltip> + </crudOperation> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column :show-overflow-tooltip="true" prop="tableName" label="表名" /> + <el-table-column :show-overflow-tooltip="true" prop="engine" label="数据库引擎" /> + <el-table-column :show-overflow-tooltip="true" prop="coding" label="字符编码集" /> + <el-table-column :show-overflow-tooltip="true" prop="remark" label="备注" /> + <el-table-column prop="createTime" label="创建日期" /> + <el-table-column label="操作" width="160px" align="center" fixed="right"> + <template slot-scope="scope"> + <el-button size="mini" style="margin-right: 2px" type="text"> + <router-link :to="'/sys-tools/generator/preview/' + scope.row.tableName"> + 预览 + </router-link> + </el-button> + <el-button size="mini" style="margin-left: -1px;margin-right: 2px" type="text" @click="toDownload(scope.row.tableName)">下载</el-button> + <el-button size="mini" style="margin-left: -1px;margin-right: 2px" type="text"> + <router-link :to="'/sys-tools/generator/config/' + scope.row.tableName"> + 配置 + </router-link> + </el-button> + <el-button type="text" style="margin-left: -1px" size="mini" @click="toGen(scope.row.tableName)">生成</el-button> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> + +import { generator, sync } from '@/api/generator/generator' +import { downloadFile } from '@/utils/index' +import CRUD, { presenter, header } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' + +export default { + name: 'GeneratorIndex', + components: { pagination, crudOperation, rrOperation }, + cruds() { + return CRUD({ url: 'api/generator/tables' }) + }, + mixins: [presenter(), header()], + data() { + return { + syncLoading: false + } + }, + created() { + this.crud.optShow = { add: false, edit: false, del: false, download: false } + }, + methods: { + toGen(tableName) { + // 生成代码 + generator(tableName, 0).then(data => { + this.$notify({ + title: '生成成功', + type: 'success', + duration: 2500 + }) + }) + }, + toDownload(tableName) { + // 打包下载 + generator(tableName, 2).then(data => { + downloadFile(data, tableName, 'zip') + }) + }, + sync() { + const tables = [] + this.crud.selections.forEach(val => { + tables.push(val.tableName) + }) + this.syncLoading = true + sync(tables).then(() => { + this.crud.refresh() + this.crud.notify('同步成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + this.syncLoading = false + }).then(() => { + this.syncLoading = false + }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/generator/preview.vue b/UI source code/dns_mapping_ui-master/src/views/generator/preview.vue new file mode 100644 index 0000000..e95fc46 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/generator/preview.vue @@ -0,0 +1,30 @@ +<template> + <el-tabs v-model="activeName" type="card"> + <el-tab-pane v-for="item in data" :key="item.name" :lazy="true" :label="item.name" :name="item.name"> + <Java :value="item.content" :height="height" /> + </el-tab-pane> + </el-tabs> +</template> + +<script> +import Java from '@/components/JavaEdit/index' +import { generator } from '@/api/generator/generator' +export default { + name: 'Preview', + components: { Java }, + data() { + return { + data: null, height: '', activeName: 'Entity' + } + }, + created() { + this.height = document.documentElement.clientHeight - 180 + 'px' + const tableName = this.$route.params.tableName + generator(tableName, 1).then(data => { + this.data = data + }).catch(() => { + this.$router.go(-1) + }) + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/home.vue b/UI source code/dns_mapping_ui-master/src/views/home.vue new file mode 100644 index 0000000..7093881 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/home.vue @@ -0,0 +1,1074 @@ +<template> + <div class="dashboard-container"> + <div class="form_list"> + <div style="width: 100%; background-color: #fff"> + <div class="form_title"> + <div :class="tabshow == 1 ? 'active' : ''" @click="tab(1)"> + <i class="el-icon-date" />服务数据 + </div> + <div :class="tabshow == 2 ? 'active' : ''" @click="tab(2)"> + <i class="el-icon-picture-outline" />网络数据 + </div> + <div :class="tabshow == 3 ? 'active' : ''" @click="tab(3)"> + <i class="el-icon-notebook-2" />主机数据 + </div> + </div> + <div class="middle"> + <!-- 父盒子 --> + <div class="father_box"> + <div class="sea_box" @click="onclick()"> + <div + v-for="(item, index) in keyword" + :key="index" + ref="Tag" + :class="index % 2 != 0 ? 'spanbox_value' : 'spanbox_key'" + > + <span v-if="Array.isArray(item)" class="tagspan"> + <span v-for="(a,i) in item" :key="i">{{ a[a.length-1] }}</span> + </span> + <span v-else class="tagspan">{{ item }}</span> + <i + v-if="index % 2 != 0" + class="span_close" + @click="removeTag(index, item)" + /> + </div> + <el-input + v-if="type === 'input'" + ref="inputTag" + v-model="currentval" + placeholder="请输入检索内容" + :style="inputStyle" + class="inputTag" + type="text" + @keydown.native="handleKeydown" + @focus="searchFoucs" + /> + <el-select + v-else-if="type == 'select'" + ref="inputTag" + v-model="currentval" + placeholder="请输入检索内容" + :style="inputStyle" + class="inputTag" + type="text" + filterable + remote + default-first-option + @change="addTags" + @keyup.native="handleKeydown" + @focus="searchFoucs" + > + <el-option + v-for="item in options" + :key="item.value" + :label="item.label" + :value="item.value" + :disabled="item.disabled" + /> + </el-select> + + <el-cascader + v-else-if="type == 'more'" + ref="inputTag" + v-model="currentval" + :options="options" + :props="{ multiple: true, checkStrictly: true }" + clearable + :style="inputStyle" + class="inputTag" + type="text" + filterable + remote + default-first-option + @change="addTags" + @keyup.native="handleKeydown" + @focus="searchFoucs" + /> + + <el-select + v-else + ref="inputTag" + v-model="currentval" + placeholder="请输入检索内容" + :style="inputStyle" + class="inputTag" + type="text" + filterable + remote + default-first-option + @change="addTags" + @keyup.native="handleKeydown" + @focus="searchFoucs" + > + <el-option + v-for="item in options" + :key="item.value" + :label="item.label" + :value="item.value" + :disabled="item.disabled" + /> + </el-select> + </div> + </div> + <!-- 搜索按钮 --> + <el-row> + <el-button + type="primary" + @click="search" + >搜 索</el-button> + </el-row> + <!-- </el-form> --> + <div class="text"> + <p class="massage"> + 共<i v-if="loging" class="el-icon-loading" /> + <span v-if="!loging">{{ list.resultTotal }}</span>条,含<i v-if="loging" class="el-icon-loading" /> + <span v-if="!loging"> + {{ handleIndependIpNum(list.independentIpNum) }}</span> + + 个独立IP,<i v-if="loging" class="el-icon-loading" /><span + v-if="!loging" + class="second" + >用时{{ list.totalTime }}秒</span> + </p> + <div class="switch"> + <el-switch v-model="value1" inactive-text="排除蜜罐" /> + <el-switch v-model="value2" inactive-text="数据去重" /> + <el-switch v-model="value3" inactive-text="排除CDN" /> + <el-switch v-model="value4" inactive-text="最新数据" /> + </div> + </div> + </div> + </div> + <div class="form_box"> + <!-- 内容 --> + <div v-show="tabshow == 1"> + <Sreviceleft v-if="leftShow" v-loading.lock="loging" /> + <SreviceRight v-if="rightShow" v-loading.lock="loging" /> + <div class="clear" /> + <!-- 分页 --> + <div class="block"> + <el-pagination + v-if="pageShow" + :page-sizes="[10, 20, 30, 40]" + :page-size="size" + layout="total, sizes, prev, pager, next, jumper" + :total="list.resultTotal" + @size-change="handleSizeChange" + @current-change="curPage" + /> + </div> + <!-- <Foot /> --> + <!-- <pagination/> --> + </div> + <div v-show="tabshow == 2" /> + <div v-show="tabshow == 3">3</div> + </div> + </div> + <!-- 返回顶部 --> + <div class="top"> + <span + v-if="btnFlag" + class="backtop" + @click="toTop" + ><i + class="el-icon-top" + /></span> + </div> + </div> +</template> + +<script> +import Sreviceleft from '../components/Search/left.vue' +import SreviceRight from '../components/Search/right.vue' +import { mapActions, mapGetters } from 'vuex' +import fackClickOutSide from '@/utils/fackClickOutSide' +// import Search from "../components/SearchInput/SearchInput.vue"; + +export default { + mixins:[fackClickOutSide], + name: 'InputTags', + components: { + Sreviceleft, + SreviceRight + // Search, + }, + props: { + parentArr: { + type: Array, + default() { + return ['service', 'dns-doh'] + } + }, + limit: { + type: Number + } + }, + data() { + return { + tabshow: 1, + value1: false, + value2: false, + value3: false, + value4: false, + pageShow: true, + rightShow: true, + leftShow: true, + btnFlag: false, // 返回顶部 + // 搜索框 + keyword: [], + obj: { service: 'dns-doh' }, + obj1: [], + currentval: '', + inputLength: '', + // nav_list: true, + // // Loading: false, + // Loadshow: false, + options: [], + options1: [{ + value: 'service', + label: 'service', + disabled: false + }, + { + value: 'ip', + label: 'ip', + disabled: false + }, + { + value: 'port', + label: 'port', + disabled: false + }], + // options3: [ { + // value: "dns-doh", + // label: "dns-doh", + // disabled: false + // }, + // { + // value: "dns-do53", + // label: "dns-do53", + // disabled: false + // },], + options2: [ + { + value: 'DNS', + label: 'DNS', + disabled: false, + children: [ + { + value: 'dns-doh', + label: 'dns-doh', + disabled: false + // children: [ + // { + // value: "proxy", + // label: "proxy", + // disabled: false, + // }, + // { + // value: "server", + // label: "server", + // disabled: false, + // }, + // ], + + }, + { + value: 'dns-do53', + label: 'dns-do53', + disabled: false + // children: [ + // { + // value: "open-rdns", + // label: "open-rdns", + // disabled: false, + // }, + // { + // value: "egress-dns", + // label: "egress-dns", + // disabled: false, + // }, + // { + // value: "forwarder", + // label: "forwarder", + // disabled: false, + // }, + // { + // value: "fwd/rdns", + // label: "fwd/rdns", + // disabled: false, + // }, + // { + // value: "nonstandard", + // label: "nonstandard", + // disabled: false, + // }, + // { + // value: "root", + // label: "root", + // disabled: false, + // }, + // { + // value: "tld", + // label: "tld", + // disabled: false, + // }, + // { + // value: "ns", + // label: "ns", + // disabled: false, + // }, + // ], + } + ] + } + // { + // value: "email", + // label: "email", + // disabled: false, + // children: [ + // { + // value: "email-1", + // label: "email-1", + // disabled: false, + // }, + // { + // value: "email-2", + // label: "email-2", + // disabled: false, + // }, + // ], + // }, + ], + isFocus: false, + type: 'select', + newSer: [], + newip: [], + newPort: [], + find: { service: 'dns-doh' } + } + }, + watch: { + keyword() { + this.$emit('on-change', this.keyword) + }, + currentval(val) { + this.inputLength = this.$refs.inputTag.value.length * 12 + 50 + }, + parentArr() { + this.keyword = this.parentArr.length ? this.parentArr : [] + } + }, + created() { + window.addEventListener('scroll', this.scrollToTop) + // this.enterSearch(); + + // document.addEventListener('click', (e) => { + // var nav = document.getElementsByClassName('listhide')[0] + // if (nav.contains(e.target)) { + // this.nav_list = true + // } else { + // this.nav_list = false + // } + // }) + }, + destroyed() { + window.removeEventListener('scroll', () => { + this.scrollToTop() + }) + }, + computed: { + ...mapGetters({ + list: 'searchlist/list', + size: 'searchlist/size', + left: 'searchlist/left', + // xylist: 'searchlist/xylist', + loging: 'searchlist/Loging' + }), + inputStyle() { + const style = {} + style.width = `${this.inputLength}px` + return style + }, + finall() { + return this.keyword.join(',') + } + }, + + methods: { + // 删除 + removeTag(index, item) { + // console.log(index, item); + this.keyword.splice(index, 1) + this.keyword.splice(index - 1, 1) + this.update() + // this.gs() + for (var i = 0; i < this.obj1.length; i++) { + if (item == this.obj1[i].service || item == this.obj1[i].ip || item == this.obj1[i].port) { + // console.log(this.obj1[i].service,i) + this.obj1.splice(i, 1) + i-- + } + } + this.concat() + // this.contrast(this.newSer,item) + // this.contrast(this.newip,item) + // this.contrast(this.newPort,item) + // console.log(this.newSer,this.newip,this.newPort,"---del") + }, + // 比较 + // contrast(arr,item){ + // if(arr){ + // for(var i=0;i<arr.length;i++){ + // // console.log(arr[0],item,90); + // if(item == arr[i]){ + // return arr.splice(i,1) + // } + // } + // } + + // }, + edit() { + var str = [] + this.keyword.forEach(item => { + if (Array.isArray(item)) { + item.forEach(a => { + str.push(a[a.length - 1]) + }) + } else { + str.push(item) + } + }) + for (var i = 0; i < str.length; i++) { + if (str[i] == 'DNS') { + this.options2[this.options2.findIndex((item) => item.value === 'DNS')].disabled = true + for (var a = 0; a < this.options2.length; a++) { + if (this.options2[a].children) { + for (var j = 0; j < this.options2[a].children.length; j++) { + this.$set(this.options2[a].children[j], 'disabled', true) + if (this.options2[a].children[j].children) { + for (var b = 0; b < this.options2[a].children[j].children.length; b++) { + this.$set(this.options2[a].children[j].children[b], 'disabled', true) + } + } + } + } + } + } else if (str[i] == 'dns-doh') { + var text1 = this.options2[this.options2.findIndex((item) => item.value === 'DNS')].children + text1[text1.findIndex((item) => item.value === 'dns-doh')].disabled = true + for (var a = 0; a < text1.length; a++) { + if (text1[a].children) { + for (var b = 0; b < text1[a].children.length; b++) { + this.$set(text1[a].children[b], 'disabled', true) + } + } + } + } else if (str[i] == 'dns-do53') { + var text1 = this.options2[this.options2.findIndex((item) => item.value === 'DNS')].children + text1[text1.findIndex((item) => item.value === 'dns-do53')].disabled = true + for (var a = 0; a < text1.length; a++) { + if (text1[a].children) { + for (var b = 0; b < text1[a].children.length; b++) { + this.$set(text1[a].children[b], 'disabled', true) + } + } + } + } + } + }, + update() { + this.options2.forEach(item => { + this.$set(item, 'disabled', false) + if (item.children) { + item.children.forEach(a => { + this.$set(a, 'disabled', false) + if (a.children) { + a.children.forEach(b => { + this.$set(b, 'disabled', false) + }) + } + }) + } + }) + }, + addTags(e) { + // console.log(this.currentval,e, "code输入"); + // console.log(this.options1, this.options2, "下拉列表"); + if (this.currentval != '') { + if (this.currentval == 'service') { + this.options = this.options2 + this.type = 'more' + this.keyword.push(this.currentval) + this.currentval = '' + } else if (this.currentval == 'ip' || this.currentval == 'port') { + this.type = 'input' + this.keyword.push(this.currentval) + this.currentval = '' + } else { + this.options = this.options1 + this.type = 'select' + this.keyword.push(this.currentval) + this.currentval = '' + } + // console.log(this.keyword); + this.edit() + this.concat() + } + }, + // 拼接格式 + concat() { + var str = [] + this.keyword.forEach(item => { + if (Array.isArray(item)) { + item.forEach(a => { + str.push(a[a.length - 1]) + }) + } else { + str.push(item) + } + }) + var ser = [] + var ip = [] + var port = [] + for (var i = 1; i <= str.length / 2; i++) { + this.$set(this.obj, str[2 * i - 2], str[2 * i - 1]) + if (str[2 * i - 2] == 'service') { + ser.push(str[2 * i - 1]) + } else if (str[2 * i - 2] == 'ip') { + ip.push(str[2 * i - 1]) + } else if (str[2 * i - 2] == 'port') { + port.push(str[2 * i - 1]) + } + } + this.newSer = this.delRepeat(ser) + this.newip = this.delRepeat(ip) + this.newPort = this.delRepeat(port) + var serArr = this.newSer.join(' AND ') + var ipArr = this.newip.join(' AND ') + var portArr = this.newPort.join(' AND ') + this.$set(this.find, 'service', serArr) + this.$set(this.find, 'ip', ipArr) + this.$set(this.find, 'port', portArr) + // console.log(this.obj1,"1111"); + console.log(this.find) + }, + // 去重 + delRepeat(arr) { + const newArr = [] + for (let i = 0; i < arr.length; i++) { + let flag = true + for (let j = 0; j < newArr.length; j++) { + arr[i] === newArr[j] ? flag = false : flag + } + flag ? newArr.push(arr[i]) : newArr + } + return newArr + }, + handleKeydown(e) { + console.log(e); + var keyCode = window.event ? e.keyCode : e.which + this.$nextTick(() => { + if (keyCode === 8) { + // console.log(this.$refs.inputTag.value,'----value'); + // console.log(this.$refs.inputTag.query,'----query'); + // console.log(this.currentval,'cur'); + + if (this.$refs.inputTag.query === '' || (this.currentval == '' && this.$refs.inputTag.query == undefined)) { + setTimeout(() => { + // console.log(888); + this.keyword.pop() + this.searchFoucs() + }) + } + } + if (keyCode == 13) { + if (this.type == 'input') { + // console.log(this.$refs.inputTag,this.type,this.currentval,898); + // this.$refs.inputTag.visible = false; + setTimeout(() => { + this.addTags() + this.search() + }) + } else if (this.type == 'select' && this.keyword.length % 2 === 0) { + setTimeout(() => { + this.addTags() + // this.keyword.pop(); + this.search() + }) + } + } + }) + }, + onclick() { + this.$nextTick(() => { + this.$refs.inputTag.focus(); + this.searchFoucs() + // this.redit() + }) + }, + // 输入框键盘删除键删除tag + // deleteTags(state) { + // var keyCode = window.event ? e.keyCode : e.which; + // if (state) { + // setTimeout(() => { + // this.keyword.pop(); + // this.searchFoucs(); + // }); + // } + // }, + // changeArr(arr) { + // var item = {}; + // // arr = arr.toString().split(""); + // for (var i = 0; i < arr.length; i++) { + // var dt = arr[i]; + // if (item[dt]) { + // item[dt]++; + // } else { + // item[dt] = 1; + // } + // } + // return item; + // }, + searchFoucs() { + var str = [] + this.keyword.forEach(item => { + if (Array.isArray(item)) { + item.forEach(a => { + str.push(a[a.length - 1]) + }) + } else { + str.push(item) + } + }) + var len = str.length + var last = str[len - 1] + if (last == 'service') { + this.options = this.options2 + this.type = 'more' + } else if (last == 'ip' || last == 'port') { + this.type = 'input' + } else { + this.options = this.options1 + this.type = 'select' + } + this.update() + this.edit() + }, + ...mapActions({ + listActions: 'searchlist/listActions', + leftlistActions: 'searchlist/leftlistActions', + changePage: 'searchlist/pageActions', + changeSize: 'searchlist/sizeActions' + }), + + // 搜索点击方法 + search() { + this.addTags() + this.concat() + this.leftlistActions(this.find) + this.curPage(1) + this.pageShow = false + this.rightShow = false; + (this.leftShow = false), + this.$nextTick(() => { + this.pageShow = true + this.rightShow = true + this.leftShow = true + }) + this.$store.state.searchlist.Loging = true + // console.log(this.obj,13); + }, + // 回车搜索 + // enterSearch() { + // document.onkeydown = (e) => { + // if (e.key === "Enter" && this.keyword.length % 2 ===0) { + // console.log("执行搜索", this.keyword.length % 2); + // this.search(); + // } + // }; + // }, + // 返回顶部 + toTop() { + window.scroll({ + top: 0, + left: 0, + behavior: 'smooth' + }) + }, + scrollToTop() { + const scrollTop = + window.pageYOffset || + document.documentElement.scrollTop || + document.body.scrollTop + const h = + window.innerHeight || + document.documentElement.clientHeight || + document.body.clientHeight + if (scrollTop > h) { + this.btnFlag = true + } else { + this.btnFlag = false + } + }, + // 分页 + curPage(page) { + this.changePage(page) + this.listActions(this.find) + this.$store.state.searchlist.Loging = true + }, + // 每页条数 + handleSizeChange(val) { + this.changeSize(val) + // this.listActions(this.obj); + this.listActions(this.find) + }, + // 选项卡切换 + tab(val) { + this.tabshow = val + }, + handleIndependIpNum(independentIpNum) { + if (!isNaN(independentIpNum)) { + return independentIpNum + } else { + // independentIpNum.forEach(a=>{ + // res += a.key + ':' + a.value + ',' + // }) + let val = JSON.stringify(independentIpNum) + val = val.substring(1, val.length - 1) + return val.replaceAll('\"', '') + } + } + }, + mounted() { + this.keyword = this.parentArr + this.listActions(this.find) + this.leftlistActions(this.find) + this.keyword = this.parentArr + this.options = this.options1 + // this.gs() + this.concat() + } +} +</script> + +<style lang="scss" scoped> +.dashboard-container { + // width: 91%; + // margin: 0px auto; + height: 100vh; + .form_list { + width: 100%; + padding-bottom: 20px; + background-color: #f9fafd; + .middle { + width: 91%; + margin: 0px auto; + background-color: white; + margin-bottom: 10px; + margin-top: 5px; + .el-button--primary { + background-color: #4608ad; + border-color: #4608ad; + } + } + .form_title { + width: 91%; + margin: 0px auto; + display: flex; + align-items: center; + height: 45px; + line-height: 45px; + text-align: center; + // margin-top: 10px; + // border: 1px solid transparent; + background-color: white; + div { + width: 180px; + padding-left: 5px; + background-color: #f9fafd; + border-radius: 10px 10px 0px 0px; + color: #000; + margin-right: 4px; + margin-top: 10px; + } + .active { + background-color: #4608ad; + color: white; + } + } + .form_box { + width: 91%; + margin: 0px auto; + } + .clear { + clear: both; + } + } + .search { + width: 100%; + height: 44px; + border: 1px solid #ccc; + border-radius: 0px 10px 10px 10px; + outline-style: none; + padding: 0; + } + .el-button--primary { + position: absolute; + right: 2px; + bottom: 4px; + height: 34px; + width: 132px; + font-size: 16px; + border-radius: 5px; + z-index: 1; + } + .text { + display: flex; + justify-content: space-between; + align-items: center; + .massage { + // background: palegreen; + width: 70%; + // float: left; + span { + color: #4608ad; + } + .second { + color: rgb(228, 107, 100); + } + } + .switch { + // background: pink; + // width: 30%; + // float: right; + margin: 16px 0px; + color: white !important; + div >>> .el-switch__label.is-active { + color: #ccc !important; + } + } + } + .backtop { + background-color: transparent; + border: 1px solid #4608ad; + border-radius: 50%; + text-align: center; + color: #4608ad; + } +} +@media only screen and (min-width: 768px) and (max-width: 1370px) { + .dashboard-container .text >>> .massage { + width: 60%; + } + .backtop { + position: fixed; + width: 40px; + height: 40px; + bottom: 40px; + right: 10px; + line-height: 40px; + } +} +@media only screen and (min-width: 1371px) and (max-width: 2400px) { + .dashboard-container .text >>> .massage { + width: 63%; + } + .backtop { + position: fixed; + width: 50px; + height: 50px; + bottom: 50px; + right: 20px; + line-height: 50px; + } +} +.el-select--small >>> .el-input--small .el-input__inner { + height: 44px !important; + border: 1px solid #ccc; + border-radius: 0px 10px 10px 10px; + outline-style: none; + padding: 0; + z-index: -3; +} +.block { + // display: flex; + // justify-content: flex-end; + float: right; + width: 76%; + height: 50px; + line-height: 50px; + background-color: #fff; +} +.block >>> .el-pagination { + height: 50px; + line-height: 50px; + padding: 10px 5px; + float: right; + .active { + color: #4608ad; + } +} +/* 外层div */ +.father_box { + .sea_box { + height: 40px; + line-height: 40px; + } + /* width: 300px; */ + box-sizing: border-box; + background-color: white; + border: 1px solid #7b68ee; + border-radius: 0px 8px 8px 8px; + font-size: 12px; + text-align: left; + padding-left: 5px; + word-wrap: break-word; + overflow: hidden; +} +/* 标签 */ +.key, +.value { + display: inline-block; + font-size: 14px; + margin: 3px 4px 3px 0; + background-color: rgb(229, 229, 229); + border: 1px solid #e8eaec; + border-radius: 3px; +} +// .spanbox { +// display: inline-block; +// font-size: 14px; +// margin: 3px 4px 3px 0; +// background-color: #e5e5e5; +// border: 1px solid #e8eaec; +// border-radius: 3px; +// height: 30px; +// line-height: 30px; +// } +.spanbox_value { + display: inline-block; + font-size: 14px; + margin: 6px 4px 3px 0; + background-color: #d9e4ff; + border: 1px solid #e8eaec; + border-radius: 3px; + height: 30px; + line-height: 30px; +} +.spanbox_key { + display: inline-block; + font-size: 14px; + margin: 6px 4px 3px 0; + background-color: #b5c8f8; + border: 1px solid #e8eaec; + border-radius: 3px; + height: 30px; + line-height: 30px; +} +.tagspan { + height: 24px; + line-height: 22px; + max-width: 100%; + position: relative; + display: inline-block; + padding-left: 8px; + padding-right: 8px; + color: #495060; + font-size: 14px; + cursor: pointer; + opacity: 1; + vertical-align: middle; + overflow: hidden; + transition: 0.25s linear; + color: #4608ad; + font-weight: 600; +} +.span_close { + padding: 0 4px 0 4px; + opacity: 1; + -webkit-filter: none; + filter: none; + color: #4608ad; + font-weight: 600; +} +.span_close:after { + content: "\00D7"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* line-height: 27px; */ + transition: 0.3s, color 0s; +} +/* input */ +.inputTag { + font-size: 16px; + border: none; + box-shadow: none; + outline: none; + background-color: transparent; + padding: 0; + width: auto; + min-width: 250px; + vertical-align: top; + height: 32px; + color: #495060; + line-height: 32px; +} +.el-select--small >>> .el-input--small .el-input__inner { + border: none; +} +.el-select--small >>> .el-input--small .el-input__suffix { + display: none; +} +.el-cascader >>>.el-input--small .el-input__inner{ + border: none; +} +.el-cascader >>> .el-input--small .el-input__suffix { + display: none; +} +.el-cascader >>> .el-cascader__tags{ + margin-top: 5px; + font-size: 13px; + padding: 0px; +} +.el-cascader >>> .el-cascader__search-input{ + margin: 0px; +} +.el-input--small >>> .el-input__inner { + border: none; + margin-top: 5px; + font-size: 13px; + padding: 0px; +} +.nav_list { + position: relative; + width: 100%; + + ul { + box-shadow: 5px 5px 16px rgba(0, 0, 0, 0.1); + width: 100%; + position: absolute; + padding: 0px; + margin: 0px; + list-style: none; + background-color: white; + top: 0; + left: 0; + z-index: 1; + li { + display: inline-block; + width: 100%; + height: 30px; + line-height: 30px; + padding-left: 12px; + } + } +} +// .el-cascader__dropdown{ +// position: absolute !important; +// top: 174px !important; +// left: 324px !important; +// z-index: 1 !important; +// } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/login.vue b/UI source code/dns_mapping_ui-master/src/views/login.vue new file mode 100644 index 0000000..0622e3c --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/login.vue @@ -0,0 +1,215 @@ +<template> + <div class="login" :style="'background-image:url('+ Background +');'"> + <el-form ref="loginForm" :model="loginForm" :rules="loginRules" label-position="left" label-width="0px" class="login-form"> + <h3 class="title"> + DiamondV 后台管理系统 + </h3> + <el-form-item prop="username"> + <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号"> + <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <el-form-item prop="password"> + <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin"> + <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <!-- <el-form-item prop="code"> + <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin"> + <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> + </el-input> + <div class="login-code"> + <img :src="codeUrl" @click="getCode"> + </div> + </el-form-item> --> + <!-- <el-checkbox v-model="loginForm.rememberMe" style="margin:0 0 25px 0;"> + 记住我 + </el-checkbox> --> + <el-form-item style="width:100%;"> + <el-button :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin"> + <span v-if="!loading">登 录</span> + <span v-else>登 录 中...</span> + </el-button> + </el-form-item> + </el-form> + <!-- 底部 --> + <!-- <div v-if="$store.state.settings.showFooter" id="el-login-footer"> + <span v-html="$store.state.settings.footerTxt" /> + <span> ⋅ </span> + <a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">{{ $store.state.settings.caseNumber }}</a> + </div> --> + </div> +</template> + +<script> +import { encrypt } from '@/utils/rsaEncrypt' +import Config from '@/settings' +import { getCodeImg } from '@/api/login' +import Cookies from 'js-cookie' +import qs from 'qs' +import Background from '@/assets/images/background.jpeg' +export default { + name: 'Login', + data() { + return { + Background: Background, + codeUrl: '', + cookiePass: '', + loginForm: { + username: 'admin', + password: '123456', + rememberMe: false, + code: '', + uuid: '' + }, + loginRules: { + username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }], + password: [{ required: true, trigger: 'blur', message: '密码不能为空' }], + code: [{ required: true, trigger: 'change', message: '验证码不能为空' }] + }, + loading: false, + redirect: undefined + } + }, + watch: { + $route: { + handler: function(route) { + const data = route.query + if (data && data.redirect) { + this.redirect = data.redirect + delete data.redirect + if (JSON.stringify(data) !== '{}') { + this.redirect = this.redirect + '&' + qs.stringify(data, { indices: false }) + } + } + }, + immediate: true + } + }, + created() { + // 获取验证码 + this.getCode() + // 获取用户名密码等Cookie + this.getCookie() + // token 过期提示 + this.point() + }, + methods: { + getCode() { + getCodeImg().then(res => { + this.codeUrl = res.img + this.loginForm.uuid = res.uuid + }) + }, + getCookie() { + const username = Cookies.get('username') + let password = Cookies.get('password') + const rememberMe = Cookies.get('rememberMe') + // 保存cookie里面的加密后的密码 + this.cookiePass = password === undefined ? '' : password + password = password === undefined ? this.loginForm.password : password + this.loginForm = { + username: username === undefined ? this.loginForm.username : username, + password: password, + rememberMe: rememberMe === undefined ? false : Boolean(rememberMe), + code: '' + } + }, + handleLogin() { + this.$refs.loginForm.validate(valid => { + const user = { + username: this.loginForm.username, + password: this.loginForm.password, + rememberMe: this.loginForm.rememberMe, + code: this.loginForm.code, + uuid: this.loginForm.uuid + } + if (user.password !== this.cookiePass) { + user.password = encrypt(user.password) + } + if (valid) { + this.loading = true + if (user.rememberMe) { + Cookies.set('username', user.username, { expires: Config.passCookieExpires }) + Cookies.set('password', user.password, { expires: Config.passCookieExpires }) + Cookies.set('rememberMe', user.rememberMe, { expires: Config.passCookieExpires }) + } else { + Cookies.remove('username') + Cookies.remove('password') + Cookies.remove('rememberMe') + } + this.$store.dispatch('Login', user).then(() => { + this.loading = false + this.$router.push({ path: this.redirect || '/' }) + }).catch(() => { + this.loading = false + this.getCode() + }) + } else { + console.log('error submit!!') + return false + } + }) + }, + point() { + const point = Cookies.get('point') !== undefined + if (point) { + this.$notify({ + title: '提示', + message: '当前登录状态已过期,请重新登录!', + type: 'warning', + duration: 5000 + }) + Cookies.remove('point') + } + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss"> + .login { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + background-size: cover; + } + .title { + margin: 0 auto 30px auto; + text-align: center; + color: #707070; + } + + .login-form { + border-radius: 6px; + background: #ffffff; + width: 385px; + padding: 25px 25px 5px 25px; + .el-input { + height: 38px; + input { + height: 38px; + } + } + .input-icon{ + height: 39px;width: 14px;margin-left: 2px; + } + } + .login-tip { + font-size: 13px; + text-align: center; + color: #bfbfbf; + } + .login-code { + width: 33%; + display: inline-block; + height: 38px; + float: right; + + img{ + cursor: pointer; + vertical-align:middle + } + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/app/index.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/app/index.vue new file mode 100644 index 0000000..ddb8fb8 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/app/index.vue @@ -0,0 +1,144 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.name" clearable placeholder="输入名称搜索" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission"> + <el-button + slot="left" + v-permission="['admin','app:add']" + :disabled="!currentRow" + class="filter-item" + size="mini" + type="primary" + icon="el-icon-plus" + @click="copy" + >复制</el-button> + </crudOperation> + </div> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="800px"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="100px"> + <el-form-item label="应用名称" prop="name"> + <el-input v-model="form.name" style="width: 670px" placeholder="部署后的文件或者目录名称,用于备份" /> + </el-form-item> + <el-form-item label="应用端口" prop="port"> + <el-input-number v-model.number="form.port" placeholder="例如:8080" /> + </el-form-item> + <el-form-item label="上传目录" prop="uploadPath"> + <el-input v-model="form.uploadPath" style="width: 670px" placeholder="例如: /opt/upload" /> + </el-form-item> + <el-form-item label="部署目录" prop="deployPath"> + <el-input v-model="form.deployPath" style="width: 670px" placeholder="例如: /opt/app" /> + </el-form-item> + <el-form-item label="备份目录" prop="backupPath"> + <el-input v-model="form.backupPath" style="width: 670px" placeholder="例如: /opt/backup" /> + </el-form-item> + <el-form-item label="部署脚本" prop="deployScript"> + <el-input v-model="form.deployScript" :rows="3" type="textarea" autosize style="width: 670px" placeholder="" /> + </el-form-item> + <el-form-item label="启动脚本" prop="startScript"> + <el-input v-model="form.startScript" :rows="3" type="textarea" autosize style="width: 670px" placeholder="" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" label="应用名称" /> + <el-table-column prop="port" label="端口号" /> + <el-table-column prop="uploadPath" label="上传目录" /> + <el-table-column prop="deployPath" label="部署目录" /> + <el-table-column prop="backupPath" label="备份目录" /> + <el-table-column prop="createTime" label="创建日期" /> + <el-table-column v-if="checkPer(['admin','app:edit','app:del'])" label="操作" width="150px" align="center"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import crudApp from '@/api/mnt/app' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, name: null, port: 8080, uploadPath: '/opt/upload', deployPath: '/opt/app', backupPath: '/opt/backup', startScript: null, deployScript: null } +export default { + name: 'App', + components: { pagination, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '应用', url: 'api/app', crudMethod: { ...crudApp }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + currentRow: null, + permission: { + add: ['admin', 'app:add'], + edit: ['admin', 'app:edit'], + del: ['admin', 'app:del'] + }, + rules: { + name: [ + { required: true, message: '请输入应用名称', trigger: 'blur' } + ], + port: [ + { required: true, message: '请输入应用端口', trigger: 'blur', type: 'number' } + ], + uploadPath: [ + { required: true, message: '请输入上传目录', trigger: 'blur' } + ], + deployPath: [ + { required: true, message: '请输入部署目录', trigger: 'blur' } + ], + backupPath: [ + { required: true, message: '请输入备份目录', trigger: 'blur' } + ], + startScript: [ + { required: true, message: '请输入启动脚本', trigger: 'blur' } + ], + deployScript: [ + { required: true, message: '请输入部署脚本', trigger: 'blur' } + ] + } + } + }, + methods: { + copy() { + for (const key in this.currentRow) { + this.form[key] = this.currentRow[key] + } + this.form.id = null + this.form.createTime = null + this.crud.toAdd() + }, + handleCurrentChange(row) { + this.currentRow = JSON.parse(JSON.stringify(row)) + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/database/execute.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/database/execute.vue new file mode 100644 index 0000000..94622fc --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/database/execute.vue @@ -0,0 +1,86 @@ +<template> + <el-dialog append-to-body :close-on-click-modal="false" :visible.sync="dialog" title="执行脚本" width="400px"> + <el-form ref="form" :rules="rules" size="small"> + <el-upload + :action="databaseUploadApi" + :data="databaseInfo" + :headers="headers" + :on-success="handleSuccess" + :on-error="handleError" + class="upload-demo" + drag + > + <i class="el-icon-upload" /> + <div class="el-upload__text"> + 将文件拖到此处,或 + <em>点击上传</em> + </div> + <div slot="tip" class="el-upload__tip">上传后,系统会自动执行SQL脚本</div> + </el-upload> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="cancel">关闭</el-button> + </div> + </el-dialog> +</template> + +<script> +import { mapGetters } from 'vuex' +import { getToken } from '@/utils/auth' +export default { + props: { + databaseInfo: { + type: Object, + default() { + return {} + } + } + }, + data() { + return { + loading: false, + dialog: false, + headers: { + Authorization: getToken() + }, + rules: {} + } + }, + computed: { + ...mapGetters(['databaseUploadApi']) + }, + mounted() { + }, + methods: { + cancel() { + this.dialog = false + }, + handleSuccess(response, file, fileList) { + if (response === 'success') { + this.$notify({ + title: '执行成功', + type: 'success', + duration: 2500 + }) + } else { + this.$notify({ + title: response, + type: 'error', + duration: 0 + }) + } + }, + handleError(e, file, fileList) { + const msg = JSON.parse(e.message) + this.$notify({ + title: msg.message, + type: 'error', + duration: 0 + }) + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/database/index.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/database/index.vue new file mode 100644 index 0000000..03d44a6 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/database/index.vue @@ -0,0 +1,148 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.blurry" clearable placeholder="模糊搜索" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission"> + <el-button + slot="right" + v-permission="['admin','database:add']" + :disabled="!selectIndex" + class="filter-item" + size="mini" + type="warning" + icon="el-icon-upload" + @click="execute" + >执行脚本 + </el-button> + </crudOperation> + </div> + <!--表单组件--> + <eForm ref="execute" :database-info="currentRow" /> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="530px"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="100px"> + <el-form-item label="连接名称" prop="name"> + <el-input v-model="form.name" style="width: 370px" /> + </el-form-item> + <el-form-item label="JDBC地址" prop="jdbcUrl"> + <el-input v-model="form.jdbcUrl" style="width: 300px" /> + <el-button :loading="loading" type="success" @click="testConnectDatabase">测试</el-button> + </el-form-item> + <el-form-item label="用户" prop="userName"> + <el-input v-model="form.userName" style="width: 370px" /> + </el-form-item> + <el-form-item label="密码" prop="pwd"> + <el-input v-model="form.pwd" type="password" style="width: 370px" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row stripe style="width: 100%" @selection-change="handleCurrentChange"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" width="130px" label="数据库名称" /> + <el-table-column prop="jdbcUrl" label="连接地址" /> + <el-table-column prop="userName" width="200px" label="用户名" /> + <el-table-column prop="createTime" width="200px" label="创建日期" /> + <el-table-column v-if="checkPer(['admin','database:edit','database:del'])" label="操作" width="150px" align="center"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import crudDatabase from '@/api/mnt/database' +import { testDbConnect } from '@/api/mnt/connect' +import eForm from './execute' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, name: null, jdbcUrl: 'jdbc:mysql://', userName: null, pwd: null } +export default { + name: 'DataBase', + components: { eForm, pagination, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '数据库', url: 'api/database', crudMethod: { ...crudDatabase }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + currentRow: {}, + selectIndex: '', + databaseInfo: '', + loading: false, + permission: { + add: ['admin', 'database:add'], + edit: ['admin', 'database:edit'], + del: ['admin', 'database:del'] + }, + rules: { + name: [ + { required: true, message: '请输入数据库名称', trigger: 'blur' } + ], + jdbcUrl: [ + { required: true, message: '请输入数据库连接地址', trigger: 'blur' } + ], + userName: [ + { required: true, message: '请输入用户名', trigger: 'blur' } + ], + pwd: [ + { required: true, message: '请输入数据库密码', trigger: 'blur' } + ] + } + } + }, + methods: { + testConnectDatabase() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + testDbConnect(this.form).then((res) => { + this.loading = false + this.crud.notify(res ? '连接成功' : '连接失败', res ? 'success' : 'error') + }).catch(() => { + this.loading = false + }) + } + }) + }, + execute() { + this.$refs.execute.dialog = true + }, + handleCurrentChange(selection) { + this.crud.selections = selection + if (selection.length === 1) { + const row = selection[0] + this.selectIndex = row.id + this.currentRow = row + } else { + this.currentRow = {} + this.selectIndex = '' + } + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/deploy.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/deploy.vue new file mode 100644 index 0000000..4a411b4 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/deploy.vue @@ -0,0 +1,190 @@ +<template> + <el-dialog append-to-body :close-on-click-modal="false" :visible.sync="dialog" title="应用部署" width="400px"> + <el-form ref="form" :model="form" :rules="rules" size="small"> + <el-upload + :action="deployUploadApi" + :data="deployInfo" + :headers="headers" + :on-success="handleSuccess" + :on-error="handleError" + class="upload-demo" + drag + > + <i class="el-icon-upload" /> + <div class="el-upload__text"> + 将文件拖到此处,或 + <em>点击上传</em> + </div> + <div slot="tip" class="el-upload__tip">多个应用上传文件名称为all.zip,数据库更新脚本扩展名为.sql,上传成功后系统自动部署系统。</div> + </el-upload> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="cancel">关闭</el-button> + </div> + </el-dialog> +</template> + +<script> +import { add, edit, getApps, getServers } from '@/api/mnt/deploy' +import { mapGetters } from 'vuex' +import { getToken } from '@/utils/auth' + +export default { + props: {}, + data() { + return { + loading: false, + dialog: false, + apps: [], + servers: [], + headers: { + Authorization: getToken() + }, + deployInfo: {}, + form: { + id: '', + appId: '', + ip: '', + selectIp: [] + }, + rules: {} + } + }, + computed: { + ...mapGetters(['deployUploadApi']) + }, + created() { + this.initWebSocket() + }, + mounted() { + this.initSelect() + }, + methods: { + cancel() { + this.resetForm() + }, + doSubmit() { + this.loading = true + if (this.isAdd) { + this.doAdd() + } else { + this.doEdit() + } + }, + joinIp() { + this.form.ip = '' + this.form.selectIp.forEach(ip => { + if (this.form.ip !== '') { + this.form.ip += ',' + } + this.form.ip += ip + }) + }, + doAdd() { + this.joinIp() + add(this.form) + .then(res => { + this.resetForm() + this.$notify({ + title: '添加成功', + type: 'success', + duration: 2500 + }) + this.loading = false + this.$parent.init() + }) + .catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + }, + doEdit() { + this.joinIp() + edit(this.form) + .then(res => { + this.resetForm() + this.$notify({ + title: '修改成功', + type: 'success', + duration: 2500 + }) + this.loading = false + this.$parent.init() + }) + .catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + }, + resetForm() { + this.dialog = false + this.$refs['form'].resetFields() + this.form = { + id: '', + appId: '', + ip: '', + selectIp: [] + } + }, + initSelect() { + getApps().then(res => { + this.apps = res.content + }) + getServers().then(res => { + this.servers = res.content + }) + }, + handleSuccess(response, file, fileList) { + this.cancel() + }, + // 监听上传失败 + handleError(e, file, fileList) { + const msg = JSON.parse(e.message) + this.$notify({ + title: msg.message, + type: 'error', + duration: 2500 + }) + }, + initWebSocket() { + const wsUri = process.env.VUE_APP_WS_API + '/webSocket/deploy' + this.websock = new WebSocket(wsUri) + this.websock.onerror = this.webSocketOnError + this.websock.onmessage = this.webSocketOnMessage + }, + webSocketOnError(e) { + this.$notify({ + title: 'WebSocket连接发生错误', + type: 'error', + duration: 0 + }) + }, + webSocketOnMessage(e) { + const data = JSON.parse(e.data) + if (data.msgType === 'INFO') { + this.$notify({ + title: '', + message: data.msg, + type: 'success', + dangerouslyUseHTMLString: true, + duration: 5500 + }) + } else if (data.msgType === 'ERROR') { + this.$notify({ + title: '', + message: data.msg, + dangerouslyUseHTMLString: true, + type: 'error', + duration: 0 + }) + } + }, + webSocketSend(agentData) { + this.websock.send(agentData) + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/index.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/index.vue new file mode 100644 index 0000000..e2ab8a7 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/index.vue @@ -0,0 +1,229 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.appName" clearable placeholder="输入应用名称查询" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission"> + <template slot="right"> + <el-button + v-permission="['admin','deploy:add']" + :disabled="!selectIndex" + class="filter-item" + size="mini" + type="primary" + icon="el-icon-upload" + @click="sysRestore" + >系统还原 + </el-button> + <el-button + v-permission="['admin','deploy:add']" + :disabled="!selectIndex" + class="filter-item" + size="mini" + type="primary" + icon="el-icon-upload" + @click="serverStatus" + >状态查询 + </el-button> + <el-button + v-permission="['admin','deploy:add']" + :disabled="!selectIndex" + class="filter-item" + size="mini" + type="success" + icon="el-icon-upload" + @click="startServer" + >启动 + </el-button> + <el-button + v-permission="['admin','deploy:add']" + :disabled="!selectIndex" + class="filter-item" + size="mini" + type="danger" + icon="el-icon-upload" + @click="stopServer" + >停止 + </el-button> + <el-button + v-permission="['admin','deploy:add']" + :disabled="!selectIndex" + class="filter-item" + size="mini" + type="warning" + icon="el-icon-upload" + @click="deploy" + >一键部署 + </el-button> + </template> + </crudOperation> + </div> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px"> + <el-form-item label="应用" prop="app.id"> + <el-select v-model.number="form.app.id" placeholder="请选择" style="width: 370px"> + <el-option v-for="item in apps" :key="item.id" :label="item.name" :value="item.id" /> + </el-select> + </el-form-item> + <el-form-item label="服务器" prop="deploys"> + <el-select v-model="form.deploys" multiple placeholder="请选择" style="width: 370px"> + <el-option v-for="item in servers" :key="item.id" :label="item.name" :value="item.id" /> + </el-select> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--统还原组件--> + <fForm ref="sysRestore" :key="times" :app-name="appName" /> + <dForm ref="deploy" /> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row stripe style="width: 100%" @selection-change="handleCurrentChange"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="app.name" label="应用名称" /> + <el-table-column prop="servers" label="服务器列表" /> + <el-table-column prop="createTime" label="部署日期" /> + <el-table-column v-if="checkPer(['admin','deploy:edit','deploy:del'])" label="操作" width="150px" align="center"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import crudDeploy from '@/api/mnt/deploy' +import dForm from './deploy' +import fForm from './sysRestore' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, app: { id: null }, deploys: [] } +export default { + name: 'Deploy', + components: { dForm, fForm, pagination, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '部署', url: 'api/deploy', crudMethod: { ...crudDeploy }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + currentRow: {}, selectIndex: '', appName: '', urlHistory: '', + times: 0, appId: '', deployId: '', apps: [], servers: [], + permission: { + add: ['admin', 'deploy:add'], + edit: ['admin', 'deploy:edit'], + del: ['admin', 'deploy:del'] + }, + rules: { + 'app.id': [ + { required: true, message: '应用不能为空', trigger: 'blur', type: 'number' } + ], + deploys: [ + { required: true, message: '服务器不能为空', trigger: 'blur' } + ] + } + } + }, + methods: { + [CRUD.HOOK.beforeRefresh]() { + this.selectIndex = '' + return true + }, + // 新增编辑前做的操作 + [CRUD.HOOK.beforeToCU](crud, form) { + this.initSelect() + const deploys = [] + form.deploys.forEach(function(deploy, index) { + deploys.push(deploy.id) + }) + this.form.deploys = deploys + }, + // 提交前 + [CRUD.HOOK.beforeSubmit]() { + const deploys = [] + this.form.deploys.forEach(function(data, index) { + const deploy = { id: data } + deploys.push(deploy) + }) + this.form.deploys = deploys + return true + }, + deploy() { + this.$refs.deploy.dialog = true + this.$refs.deploy.deployInfo = this.currentRow + }, + sysRestore() { + this.$refs.sysRestore.dialog = true + }, + handleCurrentChange(selection) { + this.crud.selections = selection + if (selection.length === 1) { + const row = selection[0] + this.selectIndex = row.id + this.currentRow = row + this.appName = row.app.name + this.times = this.times + 1 + this.appId = row.appId + this.deployId = row.id + } else { + this.currentRow = {} + this.selectIndex = '' + } + }, + startServer() { + crudDeploy.startServer(JSON.stringify(this.currentRow)) + .then(res => { + }) + .catch(err => { + console.log('error:' + err.response.data.message) + }) + }, + stopServer() { + crudDeploy.stopServer(JSON.stringify(this.currentRow)) + .then(res => { + }) + .catch(err => { + console.log('error:' + err.response.data.message) + }) + }, + serverStatus() { + crudDeploy.serverStatus(JSON.stringify(this.currentRow)) + .then(res => { + }) + .catch(err => { + console.log('error:' + err.response.data.message) + }) + }, + initSelect() { + crudDeploy.getApps().then(res => { + this.apps = res.content + }) + crudDeploy.getServers().then(res => { + this.servers = res.content + }) + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/sysRestore.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/sysRestore.vue new file mode 100644 index 0000000..e44aed6 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/deploy/sysRestore.vue @@ -0,0 +1,108 @@ +<template> + <el-dialog append-to-body :close-on-click-modal="false" :visible.sync="dialog" title="系统还原" width="800px"> + <!--工具栏--> + <div class="head-container"> + <date-range-picker v-model="query.createTime" class="date-item" /> + <el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button> + </div> + <el-form size="small" label-width="80px"> + <!--表格渲染--> + <el-table v-loading="loading" :data="data" style="width: 100%" @row-click="showRow"> + <el-table-column width="30px"> + <template slot-scope="scope"> + <el-radio v-model="radio" :label="scope.$index" /> + </template> + </el-table-column> + <el-table-column prop="appName" label="应用名称" /> + <el-table-column prop="ip" label="部署IP" /> + <el-table-column prop="deployDate" label="部署时间" /> + <el-table-column prop="deployUser" label="部署人员" /> + </el-table> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="cancel">取消</el-button> + <el-button v-permission="['admin','deploy:add']" :loading="submitLoading" type="primary" @click="doSubmit">确认</el-button> + </div> + <!--分页组件--> + <el-pagination + :total="total" + :current-page="page + 1" + style="margin-top: 8px" + layout="total, prev, pager, next, sizes" + @size-change="sizeChange" + @current-change="pageChange" + /> + </el-dialog> +</template> + +<script> +import crud from '@/mixins/crud' +import { reducte } from '@/api/mnt/deployHistory' +import DateRangePicker from '@/components/DateRangePicker' +export default { + components: { DateRangePicker }, + mixins: [crud], + props: { + appName: { + type: String, + default: '' + } + }, + data() { + return { + submitLoading: false, + dialog: false, + history: [], + radio: '', + appNames: '', + selectIndex: '' + } + }, + created() { + this.$nextTick(() => { + this.init() + }) + }, + methods: { + beforeInit() { + this.url = 'api/deployHistory' + this.deployId = this.$parent.deployId + if (this.deployId === '') { + return false + } + this.sort = 'deployDate,desc' + this.params['deployId'] = this.deployId + return true + }, + showRow(row) { + this.radio = this.data.indexOf(row) + this.selectIndex = row.id + }, + cancel() { + this.dialog = false + this.submitLoading = false + }, + doSubmit() { + if (this.selectIndex === '') { + this.$message.error('请选择要还原的备份') + } else { + this.submitLoading = true + reducte(JSON.stringify(this.data[this.radio])) + .then(res => { + this.dialog = false + this.submitLoading = false + this.appNames = '' + this.$parent.crud.toQuery() + }) + .catch(err => { + this.submitLoading = false + console.log('error:' + err.response.data.message) + }) + } + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/deployHistory/index.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/deployHistory/index.vue new file mode 100644 index 0000000..c4f9728 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/deployHistory/index.vue @@ -0,0 +1,93 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.blurry" clearable placeholder="输入搜索内容" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.deployDate" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission" /> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="appName" label="应用名称" /> + <el-table-column prop="ip" label="部署IP" /> + <el-table-column prop="deployUser" label="部署人员" /> + <el-table-column prop="deployDate" label="部署时间" /> + <el-table-column v-if="checkPer(['admin','deployHistory:del'])" label="操作" width="100px" align="center"> + <template slot-scope="scope"> + <el-popover + :ref="scope.row.id" + v-permission="['admin','deployHistory:del']" + placement="top" + width="180" + > + <p>确定删除本条数据吗?</p> + <div style="text-align: right; margin: 0"> + <el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button> + <el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button> + </div> + <el-button slot="reference" type="danger" icon="el-icon-delete" size="mini" /> + </el-popover> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import { del } from '@/api/mnt/deployHistory' +import CRUD, { presenter, header } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +export default { + name: 'DeployHistory', + components: { pagination, crudOperation, rrOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '部署历史', url: 'api/deployHistory', crudMethod: { del }}) + }, + mixins: [presenter(), header()], + data() { + return { + delLoading: false, + permission: { + del: ['admin', 'deployHistory:del'] + } + } + }, + created() { + this.crud.optShow = { + add: false, + edit: false, + del: true, + download: true + } + }, + methods: { + delMethod(id) { + this.delLoading = true + del([id]).then(() => { + this.delLoading = false + this.$refs[id].doClose() + this.crud.dleChangePage(1) + this.crud.delSuccessNotify() + this.crud.toQuery() + }).catch(() => { + this.delLoading = false + this.$refs[id].doClose() + }) + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/mnt/server/index.vue b/UI source code/dns_mapping_ui-master/src/views/mnt/server/index.vue new file mode 100644 index 0000000..c26cc9c --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/mnt/server/index.vue @@ -0,0 +1,136 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.id" clearable placeholder="输入名称或IP搜索" style="width: 200px" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission" /> + </div> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="470px"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="55px"> + <el-form-item label="名称" prop="name"> + <el-input v-model="form.name" style="width: 370px" /> + </el-form-item> + <el-form-item label="IP" prop="ip"> + <el-input v-model="form.ip" style="width: 370px" /> + </el-form-item> + <el-form-item label="端口" prop="port"> + <el-input-number v-model.number="form.port" controls-position="right" style="width: 370px;" /> + </el-form-item> + <el-form-item label="账号" prop="account"> + <el-input v-model="form.account" style="width: 370px" /> + </el-form-item> + <el-form-item label="密码" prop="password"> + <el-input v-model="form.password" type="password" style="width: 200px" /> + <el-button :loading="loading" type="success" style="align: right;" @click="testConnectServer">测试连接</el-button> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" label="名称" /> + <el-table-column prop="ip" label="IP" /> + <el-table-column prop="port" label="端口" /> + <el-table-column prop="account" label="账号" /> + <el-table-column prop="createTime" label="创建日期" /> + <el-table-column v-if="checkPer(['admin','serverDeploy:edit','serverDeploy:del'])" label="操作" width="150px" align="center"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> + +import crudServer from '@/api/mnt/serverDeploy' +import { testServerConnect } from '@/api/mnt/connect' +import { validateIP } from '@/utils/validate' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, name: null, ip: null, port: 22, account: 'root', password: null } +export default { + name: 'Server', + components: { pagination, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '服务器', url: 'api/serverDeploy', crudMethod: { ...crudServer }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + accountList: [], + accountMap: {}, + loading: false, + permission: { + add: ['admin', 'serverDeploy:add'], + edit: ['admin', 'serverDeploy:edit'], + del: ['admin', 'serverDeploy:del'] + }, + rules: { + name: [ + { required: true, message: '请输入名称', trigger: 'blur' } + ], + ip: [ + { required: true, message: '请输入IP', trigger: 'blur' }, + { validator: validateIP, trigger: 'change' } + ], + port: [ + { required: true, message: '请输入端口', trigger: 'blur', type: 'number' } + ], + account: [ + { required: true, message: '请输入账号', trigger: 'blur' } + ], + password: [ + { required: true, message: '请输入密码', trigger: 'blur' } + ] + } + } + }, + methods: { + testConnectServer() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + testServerConnect(this.form).then((res) => { + this.loading = false + this.$notify({ + title: res ? '连接成功' : '连接失败', + type: res ? 'success' : 'error', + duration: 2500 + }) + }).catch(() => { + this.loading = false + }) + } + }) + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/monitor/log/errorLog.vue b/UI source code/dns_mapping_ui-master/src/views/monitor/log/errorLog.vue new file mode 100644 index 0000000..9f2004e --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/monitor/log/errorLog.vue @@ -0,0 +1,135 @@ +<template> + <div class="app-container"> + <div class="head-container"> + <Search /> + <crudOperation> + <el-button + slot="left" + class="filter-item" + type="danger" + icon="el-icon-delete" + size="mini" + :loading="crud.delAllLoading" + @click="confirmDelAll()" + > + 清空 + </el-button> + </crudOperation> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="expand"> + <template slot-scope="props"> + <el-form label-position="left" inline class="demo-table-expand"> + <el-form-item label="请求方法"> + <span>{{ props.row.method }}</span> + </el-form-item> + <el-form-item label="请求参数"> + <span>{{ props.row.params }}</span> + </el-form-item> + </el-form> + </template> + </el-table-column> + <el-table-column prop="username" label="用户名" /> + <el-table-column prop="requestIp" label="IP" /> + <el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" /> + <el-table-column prop="description" label="描述" /> + <el-table-column prop="browser" label="浏览器" /> + <el-table-column prop="createTime" label="创建日期" /> + <el-table-column label="异常详情" width="100px"> + <template slot-scope="scope"> + <el-button size="mini" type="text" @click="info(scope.row.id)">查看详情</el-button> + </template> + </el-table-column> + </el-table> + <el-dialog :visible.sync="dialog" title="异常详情" append-to-body top="30px" width="85%"> + <pre v-highlightjs="errorInfo"><code class="java" /></pre> + </el-dialog> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import { getErrDetail, delAllError } from '@/api/monitor/log' +import Search from './search' +import CRUD, { presenter } from '@crud/crud' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' + +export default { + name: 'ErrorLog', + components: { Search, crudOperation, pagination }, + cruds() { + return CRUD({ title: '异常日志', url: 'api/logs/error' }) + }, + mixins: [presenter()], + data() { + return { + errorInfo: '', dialog: false + } + }, + created() { + this.crud.optShow = { + add: false, + edit: false, + del: false, + download: true + } + }, + methods: { + // 获取异常详情 + info(id) { + this.dialog = true + getErrDetail(id).then(res => { + this.errorInfo = res.exception + }) + }, + confirmDelAll() { + this.$confirm(`确认清空所有异常日志吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.crud.delAllLoading = true + delAllError().then(res => { + this.crud.delAllLoading = false + this.crud.dleChangePage(1) + this.crud.delSuccessNotify() + this.crud.toQuery() + }).catch(err => { + this.crud.delAllLoading = false + console.log(err.response.data.message) + }) + }).catch(() => { + }) + } + } +} +</script> + +<style scoped> +.demo-table-expand { + font-size: 0; +} +.demo-table-expand label { + width: 70px; + color: #99a9bf; +} +.demo-table-expand .el-form-item { + margin-right: 0; + margin-bottom: 0; + width: 100%; +} +.demo-table-expand .el-form-item__content { + font-size: 12px; +} +/deep/ .el-dialog__body { + padding: 0 20px 10px 20px !important; +} +.java.hljs { + color: #444; + background: #ffffff !important; + height: 630px !important; +} +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/monitor/log/index.vue b/UI source code/dns_mapping_ui-master/src/views/monitor/log/index.vue new file mode 100644 index 0000000..41a00dc --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/monitor/log/index.vue @@ -0,0 +1,114 @@ +<template> + <div class="app-container"> + <div class="head-container"> + <Search /> + <crudOperation> + <el-button + slot="left" + class="filter-item" + type="danger" + icon="el-icon-delete" + size="mini" + :loading="crud.delAllLoading" + @click="confirmDelAll()" + > + 清空 + </el-button> + </crudOperation> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="expand"> + <template slot-scope="props"> + <el-form label-position="left" inline class="demo-table-expand"> + <el-form-item label="请求方法"> + <span>{{ props.row.method }}</span> + </el-form-item> + <el-form-item label="请求参数"> + <span>{{ props.row.params }}</span> + </el-form-item> + </el-form> + </template> + </el-table-column> + <el-table-column prop="username" label="用户名" /> + <el-table-column prop="requestIp" label="IP" /> + <el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" /> + <el-table-column prop="description" label="描述" /> + <el-table-column prop="browser" label="浏览器" /> + <el-table-column prop="time" label="请求耗时" align="center"> + <template slot-scope="scope"> + <el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag> + <el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag> + <el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag> + </template> + </el-table-column> + <el-table-column prop="createTime" label="创建日期" width="180px" /> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import Search from './search' +import { delAllInfo } from '@/api/monitor/log' +import CRUD, { presenter } from '@crud/crud' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' + +export default { + name: 'Log', + components: { Search, crudOperation, pagination }, + cruds() { + return CRUD({ title: '日志', url: 'api/logs' }) + }, + mixins: [presenter()], + created() { + this.crud.optShow = { + add: false, + edit: false, + del: false, + download: true + } + }, + methods: { + confirmDelAll() { + this.$confirm(`确认清空所有操作日志吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.crud.delAllLoading = true + delAllInfo().then(res => { + this.crud.delAllLoading = false + this.crud.dleChangePage(1) + this.crud.delSuccessNotify() + this.crud.toQuery() + }).catch(err => { + this.crud.delAllLoading = false + console.log(err.response.data.message) + }) + }).catch(() => { + }) + } + } +} +</script> + +<style> +.demo-table-expand { + font-size: 0; +} +.demo-table-expand label { + width: 70px; + color: #99a9bf; +} +.demo-table-expand .el-form-item { + margin-right: 0; + margin-bottom: 0; + width: 100%; +} +.demo-table-expand .el-form-item__content { + font-size: 12px; +} +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/monitor/log/search.vue b/UI source code/dns_mapping_ui-master/src/views/monitor/log/search.vue new file mode 100644 index 0000000..ffbcc06 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/monitor/log/search.vue @@ -0,0 +1,24 @@ +<template> + <div v-if="crud.props.searchToggle"> + <el-input + v-model="query.blurry" + clearable + size="small" + placeholder="请输入你要搜索的内容" + style="width: 200px;" + class="filter-item" + /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> +</template> + +<script> +import { header } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import DateRangePicker from '@/components/DateRangePicker' +export default { + components: { rrOperation, DateRangePicker }, + mixins: [header()] +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/monitor/online/index.vue b/UI source code/dns_mapping_ui-master/src/views/monitor/online/index.vue new file mode 100644 index 0000000..b35224d --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/monitor/online/index.vue @@ -0,0 +1,121 @@ +<template> + <div class="app-container"> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <el-input v-model="query.filter" clearable size="small" placeholder="全表模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <rrOperation /> + </div> + <crudOperation> + <el-button + slot="left" + class="filter-item" + type="danger" + icon="el-icon-delete" + size="mini" + :loading="delLoading" + :disabled="crud.selections.length === 0" + @click="doDelete(crud.selections)" + > + 强退 + </el-button> + </crudOperation> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="userName" label="用户名" /> + <el-table-column prop="nickName" label="用户昵称" /> + <el-table-column prop="dept" label="部门" /> + <el-table-column prop="ip" label="登录IP" /> + <el-table-column :show-overflow-tooltip="true" prop="address" label="登录地点" /> + <el-table-column prop="browser" label="浏览器" /> + <el-table-column prop="loginTime" label="登录时间" /> + <el-table-column label="操作" width="70px" fixed="right"> + <template slot-scope="scope"> + <el-popover + :ref="scope.$index" + v-permission="['admin']" + placement="top" + width="180" + > + <p>确定强制退出该用户吗?</p> + <div style="text-align: right; margin: 0"> + <el-button size="mini" type="text" @click="$refs[scope.$index].doClose()">取消</el-button> + <el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.key, scope.$index)">确定</el-button> + </div> + <el-button slot="reference" size="mini" type="text">强退</el-button> + </el-popover> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import { del } from '@/api/monitor/online' +import CRUD, { presenter, header, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' + +export default { + name: 'OnlineUser', + components: { pagination, crudOperation, rrOperation }, + cruds() { + return CRUD({ url: 'auth/online', title: '在线用户' }) + }, + mixins: [presenter(), header(), crud()], + data() { + return { + delLoading: false, + permission: {} + } + }, + created() { + this.crud.msg.del = '强退成功!' + this.crud.optShow = { + add: false, + edit: false, + del: false, + download: true + } + }, + methods: { + doDelete(datas) { + this.$confirm(`确认强退选中的${datas.length}个用户?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.delMethod(datas) + }).catch(() => {}) + }, + // 踢出用户 + delMethod(key, index) { + const ids = [] + if (key instanceof Array) { + key.forEach(val => { + ids.push(val.key) + }) + } else ids.push(key) + this.delLoading = true + del(ids).then(() => { + this.delLoading = false + if (this.$refs[index]) { + this.$refs[index].doClose() + } + this.crud.dleChangePage(1) + this.crud.delSuccessNotify() + this.crud.toQuery() + }).catch(() => { + this.delLoading = false + if (this.$refs[index]) { + this.$refs[index].doClose() + } + }) + } + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/monitor/server/index.vue b/UI source code/dns_mapping_ui-master/src/views/monitor/server/index.vue new file mode 100644 index 0000000..920861e --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/monitor/server/index.vue @@ -0,0 +1,291 @@ +<template> + <div v-loading="!show" element-loading-text="数据加载中..." :style="!show ? 'height: 500px' : 'height: 100%'" class="app-container"> + <div v-if="show"> + <el-card class="box-card"> + <div style="color: #666;font-size: 13px;"> + <svg-icon icon-class="system" style="margin-right: 5px" /> + <span> + 系统:{{ data.sys.os }} + </span> + <span> + IP:{{ data.sys.ip }} + </span> + <span> + 项目已不间断运行:{{ data.sys.day }} + </span> + <i class="el-icon-refresh" style="margin-left: 40px" @click="init" /> + </div> + </el-card> + <el-card class="box-card"> + <div slot="header" class="clearfix"> + <span style="font-weight: bold;color: #666;font-size: 15px">状态</span> + </div> + <div> + <el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px"> + <div class="title">CPU使用率</div> + <el-tooltip placement="top-end"> + <div slot="content" style="font-size: 12px;"> + <div style="padding: 3px;"> + {{ data.cpu.name }} + </div> + <div style="padding: 3px"> + {{ data.cpu.package }} + </div> + <div style="padding: 3px"> + {{ data.cpu.core }} + </div> + <div style="padding: 3px"> + {{ data.cpu.logic }} + </div> + </div> + <div class="content"> + <el-progress type="dashboard" :percentage="parseFloat(data.cpu.used)" /> + </div> + </el-tooltip> + <div class="footer">{{ data.cpu.coreNumber }} 核心</div> + </el-col> + <el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px"> + <div class="title">内存使用率</div> + <el-tooltip placement="top-end"> + <div slot="content" style="font-size: 12px;"> + <div style="padding: 3px;"> + 总量:{{ data.memory.total }} + </div> + <div style="padding: 3px"> + 已使用:{{ data.memory.used }} + </div> + <div style="padding: 3px"> + 空闲:{{ data.memory.available }} + </div> + </div> + <div class="content"> + <el-progress type="dashboard" :percentage="parseFloat(data.memory.usageRate)" /> + </div> + </el-tooltip> + <div class="footer">{{ data.memory.used }} / {{ data.memory.total }}</div> + </el-col> + <el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px"> + <div class="title">交换区使用率</div> + <el-tooltip placement="top-end"> + <div slot="content" style="font-size: 12px;"> + <div style="padding: 3px;"> + 总量:{{ data.swap.total }} + </div> + <div style="padding: 3px"> + 已使用:{{ data.swap.used }} + </div> + <div style="padding: 3px"> + 空闲:{{ data.swap.available }} + </div> + </div> + <div class="content"> + <el-progress type="dashboard" :percentage="parseFloat(data.swap.usageRate)" /> + </div> + </el-tooltip> + <div class="footer">{{ data.swap.used }} / {{ data.swap.total }}</div> + </el-col> + <el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px"> + <div class="title">磁盘使用率</div> + <div class="content"> + <el-tooltip placement="top-end"> + <div slot="content" style="font-size: 12px;"> + <div style="padding: 3px"> + 总量:{{ data.disk.total }} + </div> + <div style="padding: 3px"> + 空闲:{{ data.disk.available }} + </div> + </div> + <div class="content"> + <el-progress type="dashboard" :percentage="parseFloat(data.disk.usageRate)" /> + </div> + </el-tooltip> + </div> + <div class="footer">{{ data.disk.used }} / {{ data.disk.total }}</div> + </el-col> + </div> + </el-card> + + <div> + <el-row :gutter="6"> + <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="margin-bottom: 10px"> + <el-card class="box-card"> + <div slot="header" class="clearfix"> + <span style="font-weight: bold;color: #666;font-size: 15px">CPU使用率监控</span> + </div> + <div> + <v-chart :options="cpuInfo" /> + </div> + </el-card> + </el-col> + <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="margin-bottom: 10px"> + <el-card class="box-card"> + <div slot="header" class="clearfix"> + <span style="font-weight: bold;color: #666;font-size: 15px">内存使用率监控</span> + </div> + <div> + <v-chart :options="memoryInfo" /> + </div> + </el-card> + </el-col> + </el-row> + </div> + </div> + </div> +</template> + +<script> +import ECharts from 'vue-echarts' +import 'echarts/lib/chart/line' +import 'echarts/lib/component/polar' +import { initData } from '@/api/data' +export default { + name: 'ServerMonitor', + components: { + 'v-chart': ECharts + }, + data() { + return { + show: false, + monitor: null, + url: 'api/monitor', + data: {}, + cpuInfo: { + tooltip: { + trigger: 'axis' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: [] + }, + yAxis: { + type: 'value', + min: 0, + max: 100, + interval: 20 + }, + series: [{ + data: [], + type: 'line', + areaStyle: { + normal: { + color: 'rgb(32, 160, 255)' // 改变区域颜色 + } + }, + itemStyle: { + normal: { + color: '#6fbae1', + lineStyle: { + color: '#6fbae1' // 改变折线颜色 + } + } + } + }] + }, + memoryInfo: { + tooltip: { + trigger: 'axis' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: [] + }, + yAxis: { + type: 'value', + min: 0, + max: 100, + interval: 20 + }, + series: [{ + data: [], + type: 'line', + areaStyle: { + normal: { + color: 'rgb(32, 160, 255)' // 改变区域颜色 + } + }, + itemStyle: { + normal: { + color: '#6fbae1', + lineStyle: { + color: '#6fbae1' // 改变折线颜色 + } + } + } + }] + } + } + }, + created() { + this.init() + this.monitor = window.setInterval(() => { + setTimeout(() => { + this.init() + }, 2) + }, 3500) + }, + destroyed() { + clearInterval(this.monitor) + }, + methods: { + init() { + initData(this.url, {}).then(data => { + this.data = data + this.show = true + if (this.cpuInfo.xAxis.data.length >= 8) { + this.cpuInfo.xAxis.data.shift() + this.memoryInfo.xAxis.data.shift() + this.cpuInfo.series[0].data.shift() + this.memoryInfo.series[0].data.shift() + } + this.cpuInfo.xAxis.data.push(data.time) + this.memoryInfo.xAxis.data.push(data.time) + this.cpuInfo.series[0].data.push(parseFloat(data.cpu.used)) + this.memoryInfo.series[0].data.push(parseFloat(data.memory.usageRate)) + }) + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .box-card { + margin-bottom: 5px; + span { + margin-right: 28px; + } + .el-icon-refresh { + margin-right: 10px; + float: right; + cursor:pointer; + } + } + .cpu, .memory, .swap, .disk { + width: 20%; + float: left; + padding-bottom: 20px; + margin-right: 5%; + } + .title { + text-align: center; + font-size: 15px; + font-weight: 500; + color: #999; + margin-bottom: 16px; + } + .footer { + text-align: center; + font-size: 15px; + font-weight: 500; + color: #999; + margin-top: -5px; + margin-bottom: 10px; + } + .content { + text-align: center; + margin-top: 5px; + margin-bottom: 5px; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/monitor/sql/index.vue b/UI source code/dns_mapping_ui-master/src/views/monitor/sql/index.vue new file mode 100644 index 0000000..0ed9034 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/monitor/sql/index.vue @@ -0,0 +1,16 @@ +<template> + <elFrame :src="sqlApi" /> +</template> +<script> +import { mapGetters } from 'vuex' +import elFrame from '@/components/Iframe/index' +export default { + name: 'Sql', + components: { elFrame }, + computed: { + ...mapGetters([ + 'sqlApi' + ]) + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/nested/menu1/menu1-1/index.vue b/UI source code/dns_mapping_ui-master/src/views/nested/menu1/menu1-1/index.vue new file mode 100644 index 0000000..132fc3f --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/nested/menu1/menu1-1/index.vue @@ -0,0 +1,36 @@ +<template> + <div class="app-container"> + <el-alert :closable="false" title="三级菜单1" type="success" /> + <el-form label-width="170px" style="margin-top: 20px"> + <el-form-item label="三级菜单缓存功能测试区"> + <el-input v-model="input" placeholder="请输入内容" style="width: 360px;" /> + </el-form-item> + </el-form> + <div> + <blockquote class="my-blockquote"> 三级菜单缓存配置教程</blockquote> + <pre class="my-code"> + 1、将前后端代码更新为最新版版本,或对照提交记录修改,点击查看-> <a href="https://gitee.com/elunez/eladmin/commit/43d1a63577f9d5347924355708429a2d210e29f7" target="_blank">提交(1)</a>、<a href="https://gitee.com/elunez/eladmin/commit/46393875148fcca5eaa327d4073f72edb3752f5c" target="_blank">提交(2)</a>、<a href="https://gitee.com/elunez/eladmin-web/commit/c93c99d8921abbb2c52afc806635f5ca08d6bda8" target="_blank">提交(3)</a> + 2、将 二级菜单 的 菜单类型 设置为 目录 级别,并且原有的 组件路径 需要清空 + 3、将 三级菜单 的 菜单缓存 设置为 是,最后将 组件名称 填写正确 + 4、具体设置可参考 菜单管理 的 多级菜单 配置进行进行相应的修改 + </pre> + <blockquote class="my-blockquote">更多帮助</blockquote> + <pre class="my-code">QQ交流群:一群:891137268、二群:947578238、三群:659622532</pre> + </div> + </div> +</template> +<script> +export default { + name: 'Test', + data() { + return { + input: '' + } + } +} +</script> +<style scoped> + .my-code a{ + color:#009688; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/nested/menu1/menu1-2/index.vue b/UI source code/dns_mapping_ui-master/src/views/nested/menu1/menu1-2/index.vue new file mode 100644 index 0000000..8508f4a --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/nested/menu1/menu1-2/index.vue @@ -0,0 +1,5 @@ +<template> + <div style="padding:30px;"> + <el-alert :closable="false" title="三级菜单2" type="success" /> + </div> +</template> diff --git a/UI source code/dns_mapping_ui-master/src/views/nested/menu2/index.vue b/UI source code/dns_mapping_ui-master/src/views/nested/menu2/index.vue new file mode 100644 index 0000000..b8283a2 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/nested/menu2/index.vue @@ -0,0 +1,5 @@ +<template> + <div style="padding:30px;"> + <el-alert :closable="false" title="二级菜单" /> + </div> +</template> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/dept/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/dept/index.vue new file mode 100644 index 0000000..d02b846 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/dept/index.vue @@ -0,0 +1,254 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.name" clearable size="small" placeholder="输入部门名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery"> + <el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" /> + </el-select> + <rrOperation /> + </div> + <crudOperation :permission="permission" /> + </div> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px"> + <el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="80px"> + <el-form-item label="部门名称" prop="name"> + <el-input v-model="form.name" style="width: 370px;" /> + </el-form-item> + <el-form-item label="部门排序" prop="deptSort"> + <el-input-number + v-model.number="form.deptSort" + :min="0" + :max="999" + controls-position="right" + style="width: 370px;" + /> + </el-form-item> + <el-form-item label="顶级部门"> + <el-radio-group v-model="form.isTop" style="width: 140px"> + <el-radio label="1">是</el-radio> + <el-radio label="0">否</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="状态" prop="enabled"> + <el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio> + </el-form-item> + <el-form-item v-if="form.isTop === '0'" style="margin-bottom: 0;" label="上级部门" prop="pid"> + <treeselect + v-model="form.pid" + :load-options="loadDepts" + :options="depts" + style="width: 370px;" + placeholder="选择上级类目" + /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table + ref="table" + v-loading="crud.loading" + lazy + :load="getDeptDatas" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + :data="crud.data" + row-key="id" + @select="crud.selectChange" + @select-all="crud.selectAllChange" + @selection-change="crud.selectionChangeHandler" + > + <el-table-column :selectable="checkboxT" type="selection" width="55" /> + <el-table-column label="名称" prop="name" /> + <el-table-column label="排序" prop="deptSort" /> + <el-table-column label="状态" align="center" prop="enabled"> + <template slot-scope="scope"> + <el-switch + v-model="scope.row.enabled" + :disabled="scope.row.id === 1" + active-color="#409EFF" + inactive-color="#F56C6C" + @change="changeEnabled(scope.row, scope.row.enabled,)" + /> + </template> + </el-table-column> + <el-table-column prop="createTime" label="创建日期" /> + <el-table-column v-if="checkPer(['admin','dept:edit','dept:del'])" label="操作" width="130px" align="center" fixed="right"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + :disabled-dle="scope.row.id === 1" + msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!" + /> + </template> + </el-table-column> + </el-table> + </div> +</template> + +<script> +import crudDept from '@/api/system/dept' +import Treeselect from '@riophae/vue-treeselect' +import '@riophae/vue-treeselect/dist/vue-treeselect.css' +import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, name: null, isTop: '1', subCount: 0, pid: null, deptSort: 999, enabled: 'true' } +export default { + name: 'Dept', + components: { Treeselect, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '部门', url: 'api/dept', crudMethod: { ...crudDept }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + // 设置数据字典 + dicts: ['dept_status'], + data() { + return { + depts: [], + rules: { + name: [ + { required: true, message: '请输入名称', trigger: 'blur' } + ], + deptSort: [ + { required: true, message: '请输入序号', trigger: 'blur', type: 'number' } + ] + }, + permission: { + add: ['admin', 'dept:add'], + edit: ['admin', 'dept:edit'], + del: ['admin', 'dept:del'] + }, + enabledTypeOptions: [ + { key: 'true', display_name: '正常' }, + { key: 'false', display_name: '禁用' } + ] + } + }, + methods: { + getDeptDatas(tree, treeNode, resolve) { + const params = { pid: tree.id } + setTimeout(() => { + crudDept.getDepts(params).then(res => { + resolve(res.content) + }) + }, 100) + }, + // 新增与编辑前做的操作 + [CRUD.HOOK.afterToCU](crud, form) { + if (form.pid !== null) { + form.isTop = '0' + } else if (form.id !== null) { + form.isTop = '1' + } + form.enabled = `${form.enabled}` + if (form.id != null) { + this.getSupDepts(form.id) + } else { + this.getDepts() + } + }, + getSupDepts(id) { + crudDept.getDeptSuperior(id).then(res => { + const date = res.content + this.buildDepts(date) + this.depts = date + }) + }, + buildDepts(depts) { + depts.forEach(data => { + if (data.children) { + this.buildDepts(data.children) + } + if (data.hasChildren && !data.children) { + data.children = null + } + }) + }, + getDepts() { + crudDept.getDepts({ enabled: true }).then(res => { + this.depts = res.content.map(function(obj) { + if (obj.hasChildren) { + obj.children = null + } + return obj + }) + }) + }, + // 获取弹窗内部门数据 + loadDepts({ action, parentNode, callback }) { + if (action === LOAD_CHILDREN_OPTIONS) { + crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => { + parentNode.children = res.content.map(function(obj) { + if (obj.hasChildren) { + obj.children = null + } + return obj + }) + setTimeout(() => { + callback() + }, 100) + }) + } + }, + // 提交前的验证 + [CRUD.HOOK.afterValidateCU]() { + if (this.form.pid !== null && this.form.pid === this.form.id) { + this.$message({ + message: '上级部门不能为空', + type: 'warning' + }) + return false + } + if (this.form.isTop === '1') { + this.form.pid = null + } + return true + }, + // 改变状态 + changeEnabled(data, val) { + this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + crudDept.edit(data).then(res => { + this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + }).catch(err => { + data.enabled = !data.enabled + console.log(err.response.data.message) + }) + }).catch(() => { + data.enabled = !data.enabled + }) + }, + checkboxT(row, rowIndex) { + return row.id !== 1 + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value { + height: 30px; + line-height: 30px; + } +</style> +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/dict/dictDetail.vue b/UI source code/dns_mapping_ui-master/src/views/system/dict/dictDetail.vue new file mode 100644 index 0000000..32756da --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/dict/dictDetail.vue @@ -0,0 +1,115 @@ +<template> + <div> + <div v-if="query.dictName === ''"> + <div class="my-code">点击字典查看详情</div> + </div> + <div v-else> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.label" clearable size="small" placeholder="输入字典标签查询" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" /> + <rrOperation /> + </div> + </div> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px"> + <el-form-item label="字典标签" prop="label"> + <el-input v-model="form.label" style="width: 370px;" /> + </el-form-item> + <el-form-item label="字典值" prop="value"> + <el-input v-model="form.value" style="width: 370px;" /> + </el-form-item> + <el-form-item label="排序" prop="dictSort"> + <el-input-number v-model.number="form.dictSort" :min="0" :max="999" controls-position="right" style="width: 370px;" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column label="所属字典"> + {{ query.dictName }} + </el-table-column> + <el-table-column prop="label" label="字典标签" /> + <el-table-column prop="value" label="字典值" /> + <el-table-column prop="dictSort" label="排序" /> + <el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> + </div> +</template> + +<script> +import crudDictDetail from '@/api/system/dictDetail' +import CRUD, { presenter, header, form } from '@crud/crud' +import pagination from '@crud/Pagination' +import rrOperation from '@crud/RR.operation' +import udOperation from '@crud/UD.operation' + +const defaultForm = { id: null, label: null, value: null, dictSort: 999 } + +export default { + components: { pagination, rrOperation, udOperation }, + cruds() { + return [ + CRUD({ title: '字典详情', url: 'api/dictDetail', query: { dictName: '' }, sort: ['dictSort,asc', 'id,desc'], + crudMethod: { ...crudDictDetail }, + optShow: { + add: true, + edit: true, + del: true, + reset: false + }, + queryOnPresenterCreated: false + }) + ] + }, + mixins: [ + presenter(), + header(), + form(function() { + return Object.assign({ dict: { id: this.dictId }}, defaultForm) + })], + data() { + return { + dictId: null, + rules: { + label: [ + { required: true, message: '请输入字典标签', trigger: 'blur' } + ], + value: [ + { required: true, message: '请输入字典值', trigger: 'blur' } + ], + dictSort: [ + { required: true, message: '请输入序号', trigger: 'blur', type: 'number' } + ] + }, + permission: { + add: ['admin', 'dict:add'], + edit: ['admin', 'dict:edit'], + del: ['admin', 'dict:del'] + } + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/dict/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/dict/index.vue new file mode 100644 index 0000000..f102023 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/dict/index.vue @@ -0,0 +1,135 @@ +<template> + <div class="app-container"> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title" width="500px"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px"> + <el-form-item label="字典名称" prop="name"> + <el-input v-model="form.name" style="width: 370px;" /> + </el-form-item> + <el-form-item label="描述"> + <el-input v-model="form.description" style="width: 370px;" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!-- 字典列表 --> + <el-row :gutter="10"> + <el-col :xs="24" :sm="24" :md="10" :lg="11" :xl="11" style="margin-bottom: 10px"> + <el-card class="box-card"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.blurry" clearable size="small" placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <rrOperation /> + </div> + <crudOperation :permission="permission" /> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" highlight-current-row style="width: 100%;" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange"> + <el-table-column type="selection" width="55" /> + <el-table-column :show-overflow-tooltip="true" prop="name" label="名称" /> + <el-table-column :show-overflow-tooltip="true" prop="description" label="描述" /> + <el-table-column v-if="checkPer(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </el-card> + </el-col> + <!-- 字典详情列表 --> + <el-col :xs="24" :sm="24" :md="14" :lg="13" :xl="13"> + <el-card class="box-card"> + <div slot="header" class="clearfix"> + <span>字典详情</span> + <el-button + v-if="checkPer(['admin','dict:add']) && this.$refs.dictDetail && this.$refs.dictDetail.query.dictName" + class="filter-item" + size="mini" + style="float: right;padding: 4px 10px" + type="primary" + icon="el-icon-plus" + @click="$refs.dictDetail && $refs.dictDetail.crud.toAdd()" + >新增</el-button> + </div> + <dictDetail ref="dictDetail" :permission="permission" /> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import dictDetail from './dictDetail' +import crudDict from '@/api/system/dict' +import CRUD, { presenter, header, form } from '@crud/crud' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' +import rrOperation from '@crud/RR.operation' +import udOperation from '@crud/UD.operation' + +const defaultForm = { id: null, name: null, description: null, dictDetails: [] } + +export default { + name: 'Dict', + components: { crudOperation, pagination, rrOperation, udOperation, dictDetail }, + cruds() { + return [ + CRUD({ title: '字典', url: 'api/dict', crudMethod: { ...crudDict }}) + ] + }, + mixins: [presenter(), header(), form(defaultForm)], + data() { + return { + queryTypeOptions: [ + { key: 'name', display_name: '字典名称' }, + { key: 'description', display_name: '描述' } + ], + rules: { + name: [ + { required: true, message: '请输入名称', trigger: 'blur' } + ] + }, + permission: { + add: ['admin', 'dict:add'], + edit: ['admin', 'dict:edit'], + del: ['admin', 'dict:del'] + } + } + }, + methods: { + // 获取数据前设置好接口地址 + [CRUD.HOOK.beforeRefresh]() { + if (this.$refs.dictDetail) { + this.$refs.dictDetail.query.dictName = '' + } + return true + }, + // 选中字典后,设置字典详情数据 + handleCurrentChange(val) { + if (val) { + this.$refs.dictDetail.query.dictName = val.name + this.$refs.dictDetail.dictId = val.id + this.$refs.dictDetail.crud.toQuery() + } + }, + // 编辑前将字典明细临时清空,避免日志入库数据过长 + [CRUD.HOOK.beforeToEdit](crud, form) { + // 将角色的菜单清空,避免日志入库数据过长 + form.dictDetails = null + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/job/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/job/index.vue new file mode 100644 index 0000000..e17ea75 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/job/index.vue @@ -0,0 +1,110 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <eHeader :dict="dict" :permission="permission" /> + <crudOperation :permission="permission" /> + </div> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" label="名称" /> + <el-table-column prop="jobSort" label="排序"> + <template slot-scope="scope"> + {{ scope.row.jobSort }} + </template> + </el-table-column> + <el-table-column prop="status" label="状态" align="center"> + <template slot-scope="scope"> + <el-switch + v-model="scope.row.enabled" + active-color="#409EFF" + inactive-color="#F56C6C" + @change="changeEnabled(scope.row, scope.row.enabled)" + /> + </template> + </el-table-column> + <el-table-column prop="createTime" label="创建日期" /> + <!-- 编辑与删除 --> + <el-table-column + v-if="checkPer(['admin','job:edit','job:del'])" + label="操作" + width="130px" + align="center" + fixed="right" + > + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + <!--表单渲染--> + <eForm :job-status="dict.job_status" /> + </div> +</template> + +<script> +import crudJob from '@/api/system/job' +import eHeader from './module/header' +import eForm from './module/form' +import CRUD, { presenter } from '@crud/crud' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' +import udOperation from '@crud/UD.operation' +export default { + name: 'Job', + components: { eHeader, eForm, crudOperation, pagination, udOperation }, + cruds() { + return CRUD({ + title: '岗位', + url: 'api/job', + sort: ['jobSort,asc', 'id,desc'], + crudMethod: { ...crudJob } + }) + }, + mixins: [presenter()], + // 数据字典 + dicts: ['job_status'], + data() { + return { + permission: { + add: ['admin', 'job:add'], + edit: ['admin', 'job:edit'], + del: ['admin', 'job:del'] + } + } + }, + methods: { + // 改变状态 + changeEnabled(data, val) { + this.$confirm('此操作将 "' + this.dict.label.job_status[val] + '" ' + data.name + '岗位, 是否继续?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + // eslint-disable-next-line no-undef + crudJob.edit(data).then(() => { + // eslint-disable-next-line no-undef + this.crud.notify(this.dict.label.job_status[val] + '成功', 'success') + }).catch(err => { + data.enabled = !data.enabled + console.log(err.data.message) + }) + }).catch(() => { + data.enabled = !data.enabled + }) + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/job/module/form.vue b/UI source code/dns_mapping_ui-master/src/views/system/job/module/form.vue new file mode 100644 index 0000000..aa538fb --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/job/module/form.vue @@ -0,0 +1,110 @@ +<template> + <el-dialog + append-to-body + :close-on-click-modal="false" + :before-close="crud.cancelCU" + :visible="crud.status.cu > 0" + :title="crud.status.title" + width="500px" + > + <el-form + ref="form" + :model="form" + :rules="rules" + size="small" + label-width="80px" + > + <el-form-item + label="名称" + prop="name" + > + <el-input + v-model="form.name" + style="width: 370px;" + /> + </el-form-item> + <el-form-item + label="排序" + prop="jobSort" + > + <el-input-number + v-model.number="form.jobSort" + :min="0" + :max="999" + controls-position="right" + style="width: 370px;" + /> + </el-form-item> + <el-form-item + v-if="form.pid !== 0" + label="状态" + prop="enabled" + > + <el-radio + v-for="item in jobStatus" + :key="item.id" + v-model="form.enabled" + :label="item.value === 'true'" + > + {{ item.label }} + </el-radio> + </el-form-item> + </el-form> + <div + slot="footer" + class="dialog-footer" + > + <el-button + type="text" + @click="crud.cancelCU" + > + 取消 + </el-button> + <el-button + :loading="crud.status.cu === 2" + type="primary" + @click="crud.submitCU" + > + 确认 + </el-button> + </div> + </el-dialog> +</template> + +<script> +import { form } from '@crud/crud' + +const defaultForm = { + id: null, + name: '', + jobSort: 999, + enabled: true +} +export default { + mixins: [form(defaultForm)], + props: { + jobStatus: { + type: Array, + required: true + } + }, + data() { + return { + rules: { + name: [ + { required: true, message: '请输入名称', trigger: 'blur' } + ], + jobSort: [ + { required: true, message: '请输入序号', trigger: 'blur', type: 'number' } + ] + } + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/job/module/header.vue b/UI source code/dns_mapping_ui-master/src/views/system/job/module/header.vue new file mode 100644 index 0000000..6503317 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/job/module/header.vue @@ -0,0 +1,32 @@ +<template> + <div + v-if="crud.props.searchToggle" + > + <el-input v-model="query.name" clearable size="small" placeholder="输入岗位名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery"> + <el-option v-for="item in dict.dict.job_status" :key="item.value" :label="item.label" :value="item.value" /> + </el-select> + <rrOperation /> + </div> +</template> + +<script> +import { header } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import DateRangePicker from '@/components/DateRangePicker' +export default { + components: { rrOperation, DateRangePicker }, + mixins: [header()], + props: { + dict: { + type: Object, + required: true + }, + permission: { + type: Object, + required: true + } + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/menu/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/menu/index.vue new file mode 100644 index 0000000..e4800e1 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/menu/index.vue @@ -0,0 +1,252 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.blurry" clearable size="small" placeholder="模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission" /> + </div> + <!--表单渲染--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="580px"> + <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px"> + <el-form-item label="菜单类型" prop="type"> + <el-radio-group v-model="form.type" size="mini" style="width: 178px"> + <el-radio-button label="0">目录</el-radio-button> + <el-radio-button label="1">菜单</el-radio-button> + <el-radio-button label="2">按钮</el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon"> + <el-popover + placement="bottom-start" + width="450" + trigger="click" + @show="$refs['iconSelect'].reset()" + > + <IconSelect ref="iconSelect" @selected="selected" /> + <el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly> + <svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon" style="height: 32px;width: 16px;" /> + <i v-else slot="prefix" class="el-icon-search el-input__icon" /> + </el-input> + </el-popover> + </el-form-item> + <el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iFrame"> + <el-radio-group v-model="form.iFrame" size="mini"> + <el-radio-button label="true">是</el-radio-button> + <el-radio-button label="false">否</el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache"> + <el-radio-group v-model="form.cache" size="mini"> + <el-radio-button label="true">是</el-radio-button> + <el-radio-button label="false">否</el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden"> + <el-radio-group v-model="form.hidden" size="mini"> + <el-radio-button label="false">是</el-radio-button> + <el-radio-button label="true">否</el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-if="form.type.toString() !== '2'" label="菜单标题" prop="title"> + <el-input v-model="form.title" :style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'" placeholder="菜单标题" /> + </el-form-item> + <el-form-item v-if="form.type.toString() === '2'" label="按钮名称" prop="title"> + <el-input v-model="form.title" placeholder="按钮名称" style="width: 178px;" /> + </el-form-item> + <el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission"> + <el-input v-model="form.permission" :disabled="form.iFrame.toString() === 'true'" placeholder="权限标识" style="width: 178px;" /> + </el-form-item> + <el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path"> + <el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" /> + </el-form-item> + <el-form-item label="菜单排序" prop="menuSort"> + <el-input-number v-model.number="form.menuSort" :min="0" :max="999" controls-position="right" style="width: 178px;" /> + </el-form-item> + <el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件名称" prop="componentName"> + <el-input v-model="form.componentName" style="width: 178px;" placeholder="匹配组件内Name字段" /> + </el-form-item> + <el-form-item v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" label="组件路径" prop="component"> + <el-input v-model="form.component" style="width: 178px;" placeholder="组件路径" /> + </el-form-item> + <el-form-item label="上级类目" prop="pid"> + <treeselect + v-model="form.pid" + :options="menus" + :load-options="loadMenus" + style="width: 450px;" + placeholder="选择上级类目" + /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table + ref="table" + v-loading="crud.loading" + lazy + :load="getMenus" + :data="crud.data" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + row-key="id" + @select="crud.selectChange" + @select-all="crud.selectAllChange" + @selection-change="crud.selectionChangeHandler" + > + <el-table-column type="selection" width="55" /> + <el-table-column :show-overflow-tooltip="true" label="菜单标题" width="125px" prop="title" /> + <el-table-column prop="icon" label="图标" align="center" width="60px"> + <template slot-scope="scope"> + <svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" /> + </template> + </el-table-column> + <el-table-column prop="menuSort" align="center" label="排序"> + <template slot-scope="scope"> + {{ scope.row.menuSort }} + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" prop="permission" label="权限标识" /> + <el-table-column :show-overflow-tooltip="true" prop="component" label="组件路径" /> + <el-table-column prop="iFrame" label="外链" width="75px"> + <template slot-scope="scope"> + <span v-if="scope.row.iFrame">是</span> + <span v-else>否</span> + </template> + </el-table-column> + <el-table-column prop="cache" label="缓存" width="75px"> + <template slot-scope="scope"> + <span v-if="scope.row.cache">是</span> + <span v-else>否</span> + </template> + </el-table-column> + <el-table-column prop="hidden" label="可见" width="75px"> + <template slot-scope="scope"> + <span v-if="scope.row.hidden">否</span> + <span v-else>是</span> + </template> + </el-table-column> + <el-table-column prop="createTime" label="创建日期" width="135px" /> + <el-table-column v-if="checkPer(['admin','menu:edit','menu:del'])" label="操作" width="130px" align="center" fixed="right"> + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!" + /> + </template> + </el-table-column> + </el-table> + </div> +</template> + +<script> +import crudMenu from '@/api/system/menu' +import IconSelect from '@/components/IconSelect' +import Treeselect from '@riophae/vue-treeselect' +import '@riophae/vue-treeselect/dist/vue-treeselect.css' +import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import DateRangePicker from '@/components/DateRangePicker' + +// crud交由presenter持有 +const defaultForm = { id: null, title: null, menuSort: 999, path: null, component: null, componentName: null, iFrame: false, roles: [], pid: 0, icon: null, cache: false, hidden: false, type: 0, permission: null } +export default { + name: 'Menu', + components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '菜单', url: 'api/menus', crudMethod: { ...crudMenu }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + menus: [], + permission: { + add: ['admin', 'menu:add'], + edit: ['admin', 'menu:edit'], + del: ['admin', 'menu:del'] + }, + rules: { + title: [ + { required: true, message: '请输入标题', trigger: 'blur' } + ], + path: [ + { required: true, message: '请输入地址', trigger: 'blur' } + ] + } + } + }, + methods: { + // 新增与编辑前做的操作 + [CRUD.HOOK.afterToCU](crud, form) { + this.menus = [] + if (form.id != null) { + if (form.pid === null) { + form.pid = 0 + } + this.getSupDepts(form.id) + } else { + this.menus.push({ id: 0, label: '顶级类目', children: null }) + } + }, + getMenus(tree, treeNode, resolve) { + const params = { pid: tree.id } + setTimeout(() => { + crudMenu.getMenus(params).then(res => { + resolve(res.content) + }) + }, 100) + }, + getSupDepts(id) { + crudMenu.getMenuSuperior(id).then(res => { + const children = res.map(function(obj) { + if (!obj.leaf && !obj.children) { + obj.children = null + } + return obj + }) + this.menus = [{ id: 0, label: '顶级类目', children: children }] + }) + }, + loadMenus({ action, parentNode, callback }) { + if (action === LOAD_CHILDREN_OPTIONS) { + crudMenu.getMenusTree(parentNode.id).then(res => { + parentNode.children = res.map(function(obj) { + if (!obj.leaf) { + obj.children = null + } + return obj + }) + setTimeout(() => { + callback() + }, 100) + }) + } + }, + // 选中图标 + selected(name) { + this.form.icon = name + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } + ::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value { + height: 30px; + line-height: 30px; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/role/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/role/index.vue new file mode 100644 index 0000000..242ed1a --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/role/index.vue @@ -0,0 +1,360 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.blurry" size="small" clearable placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission" /> + </div> + <!-- 表单渲染 --> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="520px"> + <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px"> + <el-form-item label="角色名称" prop="name"> + <el-input v-model="form.name" style="width: 380px;" /> + </el-form-item> + <el-form-item label="角色级别" prop="level"> + <el-input-number v-model.number="form.level" :min="1" controls-position="right" style="width: 145px;" /> + </el-form-item> + <el-form-item label="数据范围" prop="dataScope"> + <el-select v-model="form.dataScope" style="width: 140px" placeholder="请选择数据范围" @change="changeScope"> + <el-option + v-for="item in dateScopes" + :key="item" + :label="item" + :value="item" + /> + </el-select> + </el-form-item> + <el-form-item v-if="form.dataScope === '自定义'" label="数据权限" prop="depts"> + <treeselect + v-model="deptDatas" + :load-options="loadDepts" + :options="depts" + multiple + style="width: 380px" + placeholder="请选择" + /> + </el-form-item> + <el-form-item label="描述信息" prop="description"> + <el-input v-model="form.description" style="width: 380px;" rows="5" type="textarea" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <el-row :gutter="15"> + <!--角色管理--> + <el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="17" style="margin-bottom: 10px"> + <el-card class="box-card" shadow="never"> + <div slot="header" class="clearfix"> + <span class="role-span">角色列表</span> + </div> + <el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" :data="crud.data" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange"> + <el-table-column :selectable="checkboxT" type="selection" width="55" /> + <el-table-column prop="name" label="名称" /> + <el-table-column prop="dataScope" label="数据权限" /> + <el-table-column prop="level" label="角色级别" /> + <el-table-column :show-overflow-tooltip="true" prop="description" label="描述" /> + <el-table-column :show-overflow-tooltip="true" width="135px" prop="createTime" label="创建日期" /> + <el-table-column v-if="checkPer(['admin','roles:edit','roles:del'])" label="操作" width="130px" align="center" fixed="right"> + <template slot-scope="scope"> + <udOperation + v-if="scope.row.level >= level" + :data="scope.row" + :permission="permission" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </el-card> + </el-col> + <!-- 菜单授权 --> + <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="7"> + <el-card class="box-card" shadow="never"> + <div slot="header" class="clearfix"> + <el-tooltip class="item" effect="dark" content="选择指定角色分配菜单" placement="top"> + <span class="role-span">菜单分配</span> + </el-tooltip> + <el-button + v-permission="['admin','roles:edit']" + :disabled="!showButton" + :loading="menuLoading" + icon="el-icon-check" + size="mini" + style="float: right; padding: 6px 9px" + type="primary" + @click="saveMenu" + >保存</el-button> + </div> + <el-tree + ref="menu" + lazy + :data="menus" + :default-checked-keys="menuIds" + :load="getMenuDatas" + :props="defaultProps" + check-strictly + accordion + show-checkbox + node-key="id" + @check="menuChange" + /> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import crudRoles from '@/api/system/role' +import { getDepts, getDeptSuperior } from '@/api/system/dept' +import { getMenusTree, getChild } from '@/api/system/menu' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import pagination from '@crud/Pagination' +import Treeselect from '@riophae/vue-treeselect' +import '@riophae/vue-treeselect/dist/vue-treeselect.css' +import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, name: null, depts: [], description: null, dataScope: '全部', level: 3 } +export default { + name: 'Role', + components: { Treeselect, pagination, crudOperation, rrOperation, udOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '角色', url: 'api/roles', sort: 'level,asc', crudMethod: { ...crudRoles }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + defaultProps: { children: 'children', label: 'label', isLeaf: 'leaf' }, + dateScopes: ['全部', '本级', '自定义'], level: 3, + currentId: 0, menuLoading: false, showButton: false, + menus: [], menuIds: [], depts: [], deptDatas: [], // 多选时使用 + permission: { + add: ['admin', 'roles:add'], + edit: ['admin', 'roles:edit'], + del: ['admin', 'roles:del'] + }, + rules: { + name: [ + { required: true, message: '请输入名称', trigger: 'blur' } + ], + permission: [ + { required: true, message: '请输入权限', trigger: 'blur' } + ] + } + } + }, + created() { + crudRoles.getLevel().then(data => { + this.level = data.level + }) + }, + methods: { + getMenuDatas(node, resolve) { + setTimeout(() => { + getMenusTree(node.data.id ? node.data.id : 0).then(res => { + resolve(res) + }) + }, 100) + }, + [CRUD.HOOK.afterRefresh]() { + this.$refs.menu.setCheckedKeys([]) + }, + // 新增前初始化部门信息 + [CRUD.HOOK.beforeToAdd](crud, form) { + this.deptDatas = [] + form.menus = null + }, + // 编辑前初始化自定义数据权限的部门信息 + [CRUD.HOOK.beforeToEdit](crud, form) { + this.deptDatas = [] + if (form.dataScope === '自定义') { + this.getSupDepts(form.depts) + } + const _this = this + form.depts.forEach(function(dept) { + _this.deptDatas.push(dept.id) + }) + // 将角色的菜单清空,避免日志入库数据过长 + form.menus = null + }, + // 提交前做的操作 + [CRUD.HOOK.afterValidateCU](crud) { + if (crud.form.dataScope === '自定义' && this.deptDatas.length === 0) { + this.$message({ + message: '自定义数据权限不能为空', + type: 'warning' + }) + return false + } else if (crud.form.dataScope === '自定义') { + const depts = [] + this.deptDatas.forEach(function(data) { + const dept = { id: data } + depts.push(dept) + }) + crud.form.depts = depts + } else { + crud.form.depts = [] + } + return true + }, + // 触发单选 + handleCurrentChange(val) { + if (val) { + const _this = this + // 清空菜单的选中 + this.$refs.menu.setCheckedKeys([]) + // 保存当前的角色id + this.currentId = val.id + // 初始化默认选中的key + this.menuIds = [] + val.menus.forEach(function(data) { + _this.menuIds.push(data.id) + }) + this.showButton = true + } + }, + menuChange(menu) { + // 获取该节点的所有子节点,id 包含自身 + getChild(menu.id).then(childIds => { + // 判断是否在 menuIds 中,如果存在则删除,否则添加 + if (this.menuIds.indexOf(menu.id) !== -1) { + for (let i = 0; i < childIds.length; i++) { + const index = this.menuIds.indexOf(childIds[i]) + if (index !== -1) { + this.menuIds.splice(index, 1) + } + } + } else { + for (let i = 0; i < childIds.length; i++) { + const index = this.menuIds.indexOf(childIds[i]) + if (index === -1) { + this.menuIds.push(childIds[i]) + } + } + } + this.$refs.menu.setCheckedKeys(this.menuIds) + }) + }, + // 保存菜单 + saveMenu() { + this.menuLoading = true + const role = { id: this.currentId, menus: [] } + // 得到已选中的 key 值 + this.menuIds.forEach(function(id) { + const menu = { id: id } + role.menus.push(menu) + }) + crudRoles.editMenu(role).then(() => { + this.crud.notify('保存成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + this.menuLoading = false + this.update() + }).catch(err => { + this.menuLoading = false + console.log(err.response.data.message) + }) + }, + // 改变数据 + update() { + // 无刷新更新 表格数据 + crudRoles.get(this.currentId).then(res => { + for (let i = 0; i < this.crud.data.length; i++) { + if (res.id === this.crud.data[i].id) { + this.crud.data[i] = res + break + } + } + }) + }, + // 获取部门数据 + getDepts() { + getDepts({ enabled: true }).then(res => { + this.depts = res.content.map(function(obj) { + if (obj.hasChildren) { + obj.children = null + } + return obj + }) + }) + }, + getSupDepts(depts) { + const ids = [] + depts.forEach(dept => { + ids.push(dept.id) + }) + getDeptSuperior(ids).then(res => { + const date = res.content + this.buildDepts(date) + this.depts = date + }) + }, + buildDepts(depts) { + depts.forEach(data => { + if (data.children) { + this.buildDepts(data.children) + } + if (data.hasChildren && !data.children) { + data.children = null + } + }) + }, + // 获取弹窗内部门数据 + loadDepts({ action, parentNode, callback }) { + if (action === LOAD_CHILDREN_OPTIONS) { + getDepts({ enabled: true, pid: parentNode.id }).then(res => { + parentNode.children = res.content.map(function(obj) { + if (obj.hasChildren) { + obj.children = null + } + return obj + }) + setTimeout(() => { + callback() + }, 200) + }) + } + }, + // 如果数据权限为自定义则获取部门数据 + changeScope() { + if (this.form.dataScope === '自定义') { + this.getDepts() + } + }, + checkboxT(row) { + return row.level >= this.level + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss"> + .role-span { + font-weight: bold;color: #303133; + font-size: 15px; + } +</style> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .el-input-number .el-input__inner { + text-align: left; + } + ::v-deep .vue-treeselect__multi-value{ + margin-bottom: 0; + } + ::v-deep .vue-treeselect__multi-value-item{ + border: 0; + padding: 0; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/timing/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/timing/index.vue new file mode 100644 index 0000000..bb3bf77 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/timing/index.vue @@ -0,0 +1,210 @@ +<template> + <div class="app-container"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission"> + <!-- 任务日志 --> + <el-button + slot="right" + class="filter-item" + size="mini" + type="info" + icon="el-icon-tickets" + @click="doLog" + >日志</el-button> + </crudOperation> + <Log ref="log" /> + </div> + <!--Form表单--> + <el-dialog :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" append-to-body width="730px"> + <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="100px"> + <el-form-item label="任务名称" prop="jobName"> + <el-input v-model="form.jobName" style="width: 220px;" /> + </el-form-item> + <el-form-item label="任务描述" prop="description"> + <el-input v-model="form.description" style="width: 220px;" /> + </el-form-item> + <el-form-item label="Bean名称" prop="beanName"> + <el-input v-model="form.beanName" style="width: 220px;" /> + </el-form-item> + <el-form-item label="执行方法" prop="methodName"> + <el-input v-model="form.methodName" style="width: 220px;" /> + </el-form-item> + <el-form-item label="Cron表达式" prop="cronExpression"> + <el-input v-model="form.cronExpression" style="width: 220px;" /> + </el-form-item> + <el-form-item label="子任务ID"> + <el-input v-model="form.subTask" placeholder="多个用逗号隔开,按顺序执行" style="width: 220px;" /> + </el-form-item> + <el-form-item label="任务负责人" prop="personInCharge"> + <el-input v-model="form.personInCharge" style="width: 220px;" /> + </el-form-item> + <el-form-item label="告警邮箱" prop="email"> + <el-input v-model="form.email" placeholder="多个邮箱用逗号隔开" style="width: 220px;" /> + </el-form-item> + <el-form-item label="失败后暂停"> + <el-radio-group v-model="form.pauseAfterFailure" style="width: 220px"> + <el-radio :label="true">是</el-radio> + <el-radio :label="false">否</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="任务状态"> + <el-radio-group v-model="form.isPause" style="width: 220px"> + <el-radio :label="false">启用</el-radio> + <el-radio :label="true">暂停</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="参数内容"> + <el-input v-model="form.params" style="width: 556px;" rows="4" type="textarea" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column :selectable="checkboxT" type="selection" width="55" /> + <el-table-column :show-overflow-tooltip="true" prop="id" label="任务ID" /> + <el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" /> + <el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" /> + <el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" /> + <el-table-column :show-overflow-tooltip="true" prop="params" label="参数" /> + <el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" /> + <el-table-column :show-overflow-tooltip="true" prop="isPause" width="90px" label="状态"> + <template slot-scope="scope"> + <el-tag :type="scope.row.isPause ? 'warning' : 'success'">{{ scope.row.isPause ? '已暂停' : '运行中' }}</el-tag> + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" prop="description" width="150px" label="描述" /> + <el-table-column :show-overflow-tooltip="true" prop="createTime" width="136px" label="创建日期" /> + <el-table-column v-if="checkPer(['admin','timing:edit','timing:del'])" label="操作" width="170px" align="center" fixed="right"> + <template slot-scope="scope"> + <el-button v-permission="['admin','timing:edit']" size="mini" style="margin-right: 3px;" type="text" @click="crud.toEdit(scope.row)">编辑</el-button> + <el-button v-permission="['admin','timing:edit']" style="margin-left: -2px" type="text" size="mini" @click="execute(scope.row.id)">执行</el-button> + <el-button v-permission="['admin','timing:edit']" style="margin-left: 3px" type="text" size="mini" @click="updateStatus(scope.row.id,scope.row.isPause ? '恢复' : '暂停')"> + {{ scope.row.isPause ? '恢复' : '暂停' }} + </el-button> + <el-popover + :ref="scope.row.id" + v-permission="['admin','timing:del']" + placement="top" + width="200" + > + <p>确定停止并删除该任务吗?</p> + <div style="text-align: right; margin: 0"> + <el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button> + <el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button> + </div> + <el-button slot="reference" type="text" size="mini">删除</el-button> + </el-popover> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import crudJob from '@/api/system/timing' +import Log from './log' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, jobName: null, subTask: null, beanName: null, methodName: null, params: null, cronExpression: null, pauseAfterFailure: true, isPause: false, personInCharge: null, email: null, description: null } +export default { + name: 'Timing', + components: { Log, pagination, crudOperation, rrOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '定时任务', url: 'api/jobs', crudMethod: { ...crudJob }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + delLoading: false, + permission: { + add: ['admin', 'timing:add'], + edit: ['admin', 'timing:edit'], + del: ['admin', 'timing:del'] + }, + rules: { + jobName: [ + { required: true, message: '请输入任务名称', trigger: 'blur' } + ], + description: [ + { required: true, message: '请输入任务描述', trigger: 'blur' } + ], + beanName: [ + { required: true, message: '请输入Bean名称', trigger: 'blur' } + ], + methodName: [ + { required: true, message: '请输入方法名称', trigger: 'blur' } + ], + cronExpression: [ + { required: true, message: '请输入Cron表达式', trigger: 'blur' } + ], + personInCharge: [ + { required: true, message: '请输入负责人名称', trigger: 'blur' } + ] + } + } + }, + methods: { + // 执行 + execute(id) { + crudJob.execution(id).then(res => { + this.crud.notify('执行成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + }).catch(err => { + console.log(err.response.data.message) + }) + }, + // 改变状态 + updateStatus(id, status) { + if (status === '恢复') { + this.updateParams(id) + } + crudJob.updateIsPause(id).then(res => { + this.crud.toQuery() + this.crud.notify(status + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + }).catch(err => { + console.log(err.response.data.message) + }) + }, + updateParams(id) { + console.log(id) + }, + delMethod(id) { + this.delLoading = true + crudJob.del([id]).then(() => { + this.delLoading = false + this.$refs[id].doClose() + this.crud.dleChangePage(1) + this.crud.delSuccessNotify() + this.crud.toQuery() + }).catch(() => { + this.delLoading = false + this.$refs[id].doClose() + }) + }, + // 显示日志 + doLog() { + this.$refs.log.dialog = true + this.$refs.log.doInit() + }, + checkboxT(row, rowIndex) { + return row.id !== 1 + } + } +} +</script> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/timing/log.vue b/UI source code/dns_mapping_ui-master/src/views/system/timing/log.vue new file mode 100644 index 0000000..09c32ef --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/timing/log.vue @@ -0,0 +1,104 @@ +<template> + <el-dialog :visible.sync="dialog" append-to-body title="执行日志" width="88%"> + <!-- 搜索 --> + <div class="head-container"> + <el-input v-model="query.jobName" clearable size="small" placeholder="输入任务名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <el-select v-model="query.isSuccess" placeholder="日志状态" clearable size="small" class="filter-item" style="width: 110px" @change="toQuery"> + <el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" /> + </el-select> + <el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button> + <!-- 导出 --> + <div style="display: inline-block;"> + <el-button + :loading="downloadLoading" + size="mini" + class="filter-item" + type="warning" + icon="el-icon-download" + @click="downloadMethod" + >导出</el-button> + </div> + </div> + <!--表格渲染--> + <el-table v-loading="loading" :data="data" style="width: 100%;margin-top: -10px;"> + <el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" /> + <el-table-column :show-overflow-tooltip="true" prop="beanName" label="Bean名称" /> + <el-table-column :show-overflow-tooltip="true" prop="methodName" label="执行方法" /> + <el-table-column :show-overflow-tooltip="true" prop="params" width="120px" label="参数" /> + <el-table-column :show-overflow-tooltip="true" prop="cronExpression" label="cron表达式" /> + <el-table-column prop="createTime" label="异常详情" width="110px"> + <template slot-scope="scope"> + <el-button v-show="scope.row.exceptionDetail" size="mini" type="text" @click="info(scope.row.exceptionDetail)">查看详情</el-button> + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" align="center" prop="time" width="100px" label="耗时(毫秒)" /> + <el-table-column align="center" prop="isSuccess" width="80px" label="状态"> + <template slot-scope="scope"> + <el-tag :type="scope.row.isSuccess ? 'success' : 'danger'">{{ scope.row.isSuccess ? '成功' : '失败' }}</el-tag> + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" prop="createTime" label="创建日期" /> + </el-table> + <el-dialog :visible.sync="errorDialog" append-to-body title="异常详情" width="85%"> + <pre v-highlightjs="errorInfo"><code class="java" /></pre> + </el-dialog> + <!--分页组件--> + <el-pagination + :total="total" + :current-page="page + 1" + :page-size="6" + style="margin-top:8px;" + layout="total, prev, pager, next" + @size-change="sizeChange" + @current-change="pageChange" + /> + </el-dialog> +</template> + +<script> +import crud from '@/mixins/crud' +import DateRangePicker from '@/components/DateRangePicker' +export default { + components: { DateRangePicker }, + mixins: [crud], + data() { + return { + title: '任务日志', + errorInfo: '', errorDialog: false, + enabledTypeOptions: [ + { key: 'true', display_name: '成功' }, + { key: 'false', display_name: '失败' } + ] + } + }, + methods: { + doInit() { + this.$nextTick(() => { + this.init() + }) + }, + // 获取数据前设置好接口地址 + beforeInit() { + this.url = 'api/jobs/logs' + this.size = 6 + return true + }, + // 异常详情 + info(errorInfo) { + this.errorInfo = errorInfo + this.errorDialog = true + } + } +} +</script> + +<style scoped> + .java.hljs{ + color: #444; + background: #ffffff !important; + } + ::v-deep .el-dialog__body{ + padding: 0 20px 10px 20px !important; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/user/center.vue b/UI source code/dns_mapping_ui-master/src/views/system/user/center.vue new file mode 100644 index 0000000..1cc5681 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/user/center.vue @@ -0,0 +1,221 @@ +<template> + <div class="app-container"> + <el-row :gutter="20"> + <el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5" style="margin-bottom: 10px"> + <el-card class="box-card"> + <div slot="header" class="clearfix"> + <span>个人信息</span> + </div> + <div> + <div style="text-align: center"> + <div class="el-upload"> + <img :src="user.avatarName ? baseApi + '/avatar/' + user.avatarName : Avatar" title="点击上传头像" class="avatar" @click="toggleShow"> + <myUpload + v-model="show" + :headers="headers" + :url="updateAvatarApi" + @crop-upload-success="cropUploadSuccess" + /> + </div> + </div> + <ul class="user-info"> + <li><div style="height: 100%"><svg-icon icon-class="login" /> 登录账号<div class="user-right">{{ user.username }}</div></div></li> + <li><svg-icon icon-class="user1" /> 用户昵称 <div class="user-right">{{ user.nickName }}</div></li> + <li><svg-icon icon-class="dept" /> 所属部门 <div class="user-right"> {{ user.dept.name }}</div></li> + <li><svg-icon icon-class="phone" /> 手机号码 <div class="user-right">{{ user.phone }}</div></li> + <li><svg-icon icon-class="email" /> 用户邮箱 <div class="user-right">{{ user.email }}</div></li> + <li> + <svg-icon icon-class="anq" /> 安全设置 + <div class="user-right"> + <a @click="$refs.pass.dialog = true">修改密码</a> + <a @click="$refs.email.dialog = true">修改邮箱</a> + </div> + </li> + </ul> + </div> + </el-card> + </el-col> + <el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19"> + <!-- 用户资料 --> + <el-card class="box-card"> + <el-tabs v-model="activeName" @tab-click="handleClick"> + <el-tab-pane label="用户资料" name="first"> + <el-form ref="form" :model="form" :rules="rules" style="margin-top: 10px;" size="small" label-width="65px"> + <el-form-item label="昵称" prop="nickName"> + <el-input v-model="form.nickName" style="width: 35%" /> + <span style="color: #C0C0C0;margin-left: 10px;">用户昵称不作为登录使用</span> + </el-form-item> + <el-form-item label="手机号" prop="phone"> + <el-input v-model="form.phone" style="width: 35%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">手机号码不能重复</span> + </el-form-item> + <el-form-item label="性别"> + <el-radio-group v-model="form.gender" style="width: 178px"> + <el-radio label="男">男</el-radio> + <el-radio label="女">女</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label=""> + <el-button :loading="saveLoading" size="mini" type="primary" @click="doSubmit">保存配置</el-button> + </el-form-item> + </el-form> + </el-tab-pane> + <!-- 操作日志 --> + <el-tab-pane label="操作日志" name="second"> + <el-table v-loading="loading" :data="data" style="width: 100%;"> + <el-table-column prop="description" label="行为" /> + <el-table-column prop="requestIp" label="IP" /> + <el-table-column :show-overflow-tooltip="true" prop="address" label="IP来源" /> + <el-table-column prop="browser" label="浏览器" /> + <el-table-column prop="time" label="请求耗时" align="center"> + <template slot-scope="scope"> + <el-tag v-if="scope.row.time <= 300">{{ scope.row.time }}ms</el-tag> + <el-tag v-else-if="scope.row.time <= 1000" type="warning">{{ scope.row.time }}ms</el-tag> + <el-tag v-else type="danger">{{ scope.row.time }}ms</el-tag> + </template> + </el-table-column> + <el-table-column + align="right" + > + <template slot="header"> + <div style="display:inline-block;float: right;cursor: pointer" @click="init">创建日期<i class="el-icon-refresh" style="margin-left: 40px" /></div> + </template> + <template slot-scope="scope"> + <span>{{ scope.row.createTime }}</span> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <el-pagination + :total="total" + :current-page="page + 1" + style="margin-top: 8px;" + layout="total, prev, pager, next, sizes" + @size-change="sizeChange" + @current-change="pageChange" + /> + </el-tab-pane> + </el-tabs> + </el-card> + </el-col> + </el-row> + <updateEmail ref="email" :email="user.email" /> + <updatePass ref="pass" /> + </div> +</template> + +<script> +import myUpload from 'vue-image-crop-upload' +import { mapGetters } from 'vuex' +import updatePass from './center/updatePass' +import updateEmail from './center/updateEmail' +import { getToken } from '@/utils/auth' +import store from '@/store' +import { isvalidPhone } from '@/utils/validate' +import crud from '@/mixins/crud' +import { editUser } from '@/api/system/user' +import Avatar from '@/assets/images/avatar.png' +export default { + name: 'Center', + components: { updatePass, updateEmail, myUpload }, + mixins: [crud], + data() { + // 自定义验证 + const validPhone = (rule, value, callback) => { + if (!value) { + callback(new Error('请输入电话号码')) + } else if (!isvalidPhone(value)) { + callback(new Error('请输入正确的11位手机号码')) + } else { + callback() + } + } + return { + show: false, + Avatar: Avatar, + activeName: 'first', + saveLoading: false, + headers: { + 'Authorization': getToken() + }, + form: {}, + rules: { + nickName: [ + { required: true, message: '请输入用户昵称', trigger: 'blur' }, + { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' } + ], + phone: [ + { required: true, trigger: 'blur', validator: validPhone } + ] + } + } + }, + computed: { + ...mapGetters([ + 'user', + 'updateAvatarApi', + 'baseApi' + ]) + }, + created() { + this.form = { id: this.user.id, nickName: this.user.nickName, gender: this.user.gender, phone: this.user.phone } + store.dispatch('GetInfo').then(() => {}) + }, + methods: { + toggleShow() { + this.show = !this.show + }, + handleClick(tab, event) { + if (tab.name === 'second') { + this.init() + } + }, + beforeInit() { + this.url = 'api/logs/user' + return true + }, + cropUploadSuccess(jsonData, field) { + store.dispatch('GetInfo').then(() => {}) + }, + doSubmit() { + if (this.$refs['form']) { + this.$refs['form'].validate((valid) => { + if (valid) { + this.saveLoading = true + editUser(this.form).then(() => { + this.editSuccessNotify() + store.dispatch('GetInfo').then(() => {}) + this.saveLoading = false + }).catch(() => { + this.saveLoading = false + }) + } + }) + } + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss"> + .avatar { + width: 120px; + height: 120px; + border-radius: 50%; + } + .user-info { + padding-left: 0; + list-style: none; + li{ + border-bottom: 1px solid #F0F3F4; + padding: 11px 0; + font-size: 13px; + } + .user-right { + float: right; + a{ + color: #317EF3; + } + } + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/user/center/updateEmail.vue b/UI source code/dns_mapping_ui-master/src/views/system/user/center/updateEmail.vue new file mode 100644 index 0000000..b246684 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/user/center/updateEmail.vue @@ -0,0 +1,137 @@ +<template> + <div style="display: inline-block;"> + <el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="475px" @close="cancel"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px"> + <el-form-item label="新邮箱" prop="email"> + <el-input v-model="form.email" auto-complete="on" style="width: 200px;" /> + <el-button :loading="codeLoading" :disabled="isDisabled" size="small" @click="sendCode">{{ buttonName }}</el-button> + </el-form-item> + <el-form-item label="验证码" prop="code"> + <el-input v-model="form.code" style="width: 320px;" /> + </el-form-item> + <el-form-item label="当前密码" prop="pass"> + <el-input v-model="form.pass" type="password" style="width: 320px;" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="cancel">取消</el-button> + <el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import store from '@/store' +import { validEmail } from '@/utils/validate' +import { updateEmail } from '@/api/system/user' +import { resetEmail } from '@/api/system/code' +export default { + props: { + email: { + type: String, + required: true + } + }, + data() { + const validMail = (rule, value, callback) => { + if (value === '' || value === null) { + callback(new Error('新邮箱不能为空')) + } else if (value === this.email) { + callback(new Error('新邮箱不能与旧邮箱相同')) + } else if (validEmail(value)) { + callback() + } else { + callback(new Error('邮箱格式错误')) + } + } + return { + loading: false, dialog: false, title: '修改邮箱', form: { pass: '', email: '', code: '' }, + user: { email: '', password: '' }, codeLoading: false, + buttonName: '获取验证码', isDisabled: false, time: 60, + rules: { + pass: [ + { required: true, message: '当前密码不能为空', trigger: 'blur' } + ], + email: [ + { required: true, validator: validMail, trigger: 'blur' } + ], + code: [ + { required: true, message: '验证码不能为空', trigger: 'blur' } + ] + } + } + }, + methods: { + cancel() { + this.resetForm() + }, + sendCode() { + if (this.form.email && this.form.email !== this.email) { + this.codeLoading = true + this.buttonName = '验证码发送中' + const _this = this + resetEmail(this.form.email).then(res => { + this.$message({ + showClose: true, + message: '发送成功,验证码有效期5分钟', + type: 'success' + }) + this.codeLoading = false + this.isDisabled = true + this.buttonName = this.time-- + '秒后重新发送' + this.timer = window.setInterval(function() { + _this.buttonName = _this.time + '秒后重新发送' + --_this.time + if (_this.time < 0) { + _this.buttonName = '重新发送' + _this.time = 60 + _this.isDisabled = false + window.clearInterval(_this.timer) + } + }, 1000) + }).catch(err => { + this.resetForm() + this.codeLoading = false + console.log(err.response.data.message) + }) + } + }, + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + updateEmail(this.form).then(res => { + this.loading = false + this.resetForm() + this.$notify({ + title: '邮箱修改成功', + type: 'success', + duration: 1500 + }) + store.dispatch('GetInfo').then(() => {}) + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + }, + resetForm() { + this.dialog = false + this.$refs['form'].resetFields() + window.clearInterval(this.timer) + this.time = 60 + this.buttonName = '获取验证码' + this.isDisabled = false + this.form = { pass: '', email: '', code: '' } + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/user/center/updatePass.vue b/UI source code/dns_mapping_ui-master/src/views/system/user/center/updatePass.vue new file mode 100644 index 0000000..078ed17 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/user/center/updatePass.vue @@ -0,0 +1,95 @@ +<template> + <div style="display: inline-block"> + <el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="500px" @close="cancel"> + <el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px"> + <el-form-item label="旧密码" prop="oldPass"> + <el-input v-model="form.oldPass" type="password" auto-complete="on" style="width: 370px;" /> + </el-form-item> + <el-form-item label="新密码" prop="newPass"> + <el-input v-model="form.newPass" type="password" auto-complete="on" style="width: 370px;" /> + </el-form-item> + <el-form-item label="确认密码" prop="confirmPass"> + <el-input v-model="form.confirmPass" type="password" auto-complete="on" style="width: 370px;" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="cancel">取消</el-button> + <el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import store from '@/store' +import { updatePass } from '@/api/system/user' +export default { + data() { + const confirmPass = (rule, value, callback) => { + if (value) { + if (this.form.newPass !== value) { + callback(new Error('两次输入的密码不一致')) + } else { + callback() + } + } else { + callback(new Error('请再次输入密码')) + } + } + return { + loading: false, dialog: false, title: '修改密码', form: { oldPass: '', newPass: '', confirmPass: '' }, + rules: { + oldPass: [ + { required: true, message: '请输入旧密码', trigger: 'blur' } + ], + newPass: [ + { required: true, message: '请输入新密码', trigger: 'blur' }, + { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' } + ], + confirmPass: [ + { required: true, validator: confirmPass, trigger: 'blur' } + ] + } + } + }, + methods: { + cancel() { + this.resetForm() + }, + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + updatePass(this.form).then(res => { + this.resetForm() + this.$notify({ + title: '密码修改成功,请重新登录', + type: 'success', + duration: 1500 + }) + setTimeout(() => { + store.dispatch('LogOut').then(() => { + location.reload() // 为了重新实例化vue-router对象 避免bug + }) + }, 1500) + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + }, + resetForm() { + this.dialog = false + this.$refs['form'].resetFields() + this.form = { oldPass: '', newPass: '', confirmPass: '' } + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/system/user/index.vue b/UI source code/dns_mapping_ui-master/src/views/system/user/index.vue new file mode 100644 index 0000000..9017069 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/system/user/index.vue @@ -0,0 +1,484 @@ +<template> + <div class="app-container"> + <el-row :gutter="20"> + <!--侧边部门数据--> + <el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4"> + <div class="head-container"> + <el-input + v-model="deptName" + clearable + size="small" + placeholder="输入部门名称搜索" + prefix-icon="el-icon-search" + class="filter-item" + @input="getDeptDatas" + /> + </div> + <el-tree + :data="deptDatas" + :load="getDeptDatas" + :props="defaultProps" + :expand-on-click-node="false" + lazy + @node-click="handleNodeClick" + /> + </el-col> + <!--用户数据--> + <el-col :xs="15" :sm="18" :md="19" :lg="20" :xl="20"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input + v-model="query.blurry" + clearable + size="small" + placeholder="输入名称或者邮箱搜索" + style="width: 200px;" + class="filter-item" + @keyup.enter.native="crud.toQuery" + /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <el-select + v-model="query.enabled" + clearable + size="small" + placeholder="状态" + class="filter-item" + style="width: 90px" + @change="crud.toQuery" + > + <el-option + v-for="item in enabledTypeOptions" + :key="item.key" + :label="item.display_name" + :value="item.key" + /> + </el-select> + <rrOperation /> + </div> + <crudOperation show="" :permission="permission" /> + </div> + <!--表单渲染--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="570px"> + <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px"> + <el-form-item label="用户名" prop="username"> + <el-input v-model="form.username" @keydown.native="keydown($event)" /> + </el-form-item> + <el-form-item label="电话" prop="phone"> + <el-input v-model.number="form.phone" /> + </el-form-item> + <el-form-item label="昵称" prop="nickName"> + <el-input v-model="form.nickName" @keydown.native="keydown($event)" /> + </el-form-item> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" /> + </el-form-item> + <el-form-item label="部门" prop="dept.id"> + <treeselect + v-model="form.dept.id" + :options="depts" + :load-options="loadDepts" + style="width: 178px" + placeholder="选择部门" + /> + </el-form-item> + <el-form-item label="岗位" prop="jobs"> + <el-select + v-model="jobDatas" + style="width: 178px" + multiple + placeholder="请选择" + @remove-tag="deleteTag" + @change="changeJob" + > + <el-option + v-for="item in jobs" + :key="item.name" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="性别"> + <el-radio-group v-model="form.gender" style="width: 178px"> + <el-radio label="男">男</el-radio> + <el-radio label="女">女</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="状态"> + <el-radio-group v-model="form.enabled" :disabled="form.id === user.id"> + <el-radio + v-for="item in dict.user_status" + :key="item.id" + :label="item.value" + >{{ item.label }}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item style="margin-bottom: 0;" label="角色" prop="roles"> + <el-select + v-model="roleDatas" + style="width: 437px" + multiple + placeholder="请选择" + @remove-tag="deleteTag" + @change="changeRole" + > + <el-option + v-for="item in roles" + :key="item.name" + :disabled="level !== 1 && item.level <= level" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column :selectable="checkboxT" type="selection" width="55" /> + <el-table-column :show-overflow-tooltip="true" prop="username" label="用户名" /> + <el-table-column :show-overflow-tooltip="true" prop="nickName" label="昵称" /> + <el-table-column prop="gender" label="性别" /> + <el-table-column :show-overflow-tooltip="true" prop="phone" width="100" label="电话" /> + <el-table-column :show-overflow-tooltip="true" width="135" prop="email" label="邮箱" /> + <el-table-column :show-overflow-tooltip="true" prop="dept" label="部门"> + <template slot-scope="scope"> + <div>{{ scope.row.dept.name }}</div> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="enabled"> + <template slot-scope="scope"> + <el-switch + v-model="scope.row.enabled" + :disabled="user.id === scope.row.id" + active-color="#409EFF" + inactive-color="#F56C6C" + @change="changeEnabled(scope.row, scope.row.enabled)" + /> + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" prop="createTime" width="135" label="创建日期" /> + <el-table-column + v-if="checkPer(['admin','user:edit','user:del'])" + label="操作" + width="115" + align="center" + fixed="right" + > + <template slot-scope="scope"> + <udOperation + :data="scope.row" + :permission="permission" + :disabled-dle="scope.row.id === user.id" + /> + </template> + </el-table-column> + </el-table> + <!--分页组件--> + <pagination /> + </el-col> + </el-row> + </div> +</template> + +<script> +import crudUser from '@/api/system/user' +import { isvalidPhone } from '@/utils/validate' +import { getDepts, getDeptSuperior } from '@/api/system/dept' +import { getAll, getLevel } from '@/api/system/role' +import { getAllJob } from '@/api/system/job' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import udOperation from '@crud/UD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' +import Treeselect from '@riophae/vue-treeselect' +import { mapGetters } from 'vuex' +import '@riophae/vue-treeselect/dist/vue-treeselect.css' +import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect' +let userRoles = [] +let userJobs = [] +const defaultForm = { id: null, username: null, nickName: null, gender: '男', email: null, enabled: 'false', roles: [], jobs: [], dept: { id: null }, phone: null } +export default { + name: 'User', + components: { Treeselect, crudOperation, rrOperation, udOperation, pagination, DateRangePicker }, + cruds() { + return CRUD({ title: '用户', url: 'api/users', crudMethod: { ...crudUser }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + // 数据字典 + dicts: ['user_status'], + data() { + // 自定义验证 + const validPhone = (rule, value, callback) => { + if (!value) { + callback(new Error('请输入电话号码')) + } else if (!isvalidPhone(value)) { + callback(new Error('请输入正确的11位手机号码')) + } else { + callback() + } + } + return { + height: document.documentElement.clientHeight - 180 + 'px;', + deptName: '', depts: [], deptDatas: [], jobs: [], level: 3, roles: [], + jobDatas: [], roleDatas: [], // 多选时使用 + defaultProps: { children: 'children', label: 'name', isLeaf: 'leaf' }, + permission: { + add: ['admin', 'user:add'], + edit: ['admin', 'user:edit'], + del: ['admin', 'user:del'] + }, + enabledTypeOptions: [ + { key: 'true', display_name: '激活' }, + { key: 'false', display_name: '锁定' } + ], + rules: { + username: [ + { required: true, message: '请输入用户名', trigger: 'blur' }, + { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' } + ], + nickName: [ + { required: true, message: '请输入用户昵称', trigger: 'blur' }, + { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' } + ], + email: [ + { required: true, message: '请输入邮箱地址', trigger: 'blur' }, + { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' } + ], + phone: [ + { required: true, trigger: 'blur', validator: validPhone } + ] + } + } + }, + computed: { + ...mapGetters([ + 'user' + ]) + }, + created() { + this.crud.msg.add = '新增成功,默认密码:123456' + }, + mounted: function() { + const that = this + window.onresize = function temp() { + that.height = document.documentElement.clientHeight - 180 + 'px;' + } + }, + methods: { + // 禁止输入空格 + keydown(e) { + if (e.keyCode === 32) { + e.returnValue = false + } + }, + changeRole(value) { + userRoles = [] + value.forEach(function(data, index) { + const role = { id: data } + userRoles.push(role) + }) + }, + changeJob(value) { + userJobs = [] + value.forEach(function(data, index) { + const job = { id: data } + userJobs.push(job) + }) + }, + deleteTag(value) { + userRoles.forEach(function(data, index) { + if (data.id === value) { + userRoles.splice(index, value) + } + }) + }, + // 新增与编辑前做的操作 + [CRUD.HOOK.afterToCU](crud, form) { + this.getRoles() + if (form.id == null) { + this.getDepts() + } else { + this.getSupDepts(form.dept.id) + } + this.getRoleLevel() + this.getJobs() + form.enabled = form.enabled.toString() + }, + // 新增前将多选的值设置为空 + [CRUD.HOOK.beforeToAdd]() { + this.jobDatas = [] + this.roleDatas = [] + }, + // 初始化编辑时候的角色与岗位 + [CRUD.HOOK.beforeToEdit](crud, form) { + this.getJobs(this.form.dept.id) + this.jobDatas = [] + this.roleDatas = [] + userRoles = [] + userJobs = [] + const _this = this + form.roles.forEach(function(role, index) { + _this.roleDatas.push(role.id) + const rol = { id: role.id } + userRoles.push(rol) + }) + form.jobs.forEach(function(job, index) { + _this.jobDatas.push(job.id) + const data = { id: job.id } + userJobs.push(data) + }) + }, + // 提交前做的操作 + [CRUD.HOOK.afterValidateCU](crud) { + if (!crud.form.dept.id) { + this.$message({ + message: '部门不能为空', + type: 'warning' + }) + return false + } else if (this.jobDatas.length === 0) { + this.$message({ + message: '岗位不能为空', + type: 'warning' + }) + return false + } else if (this.roleDatas.length === 0) { + this.$message({ + message: '角色不能为空', + type: 'warning' + }) + return false + } + crud.form.roles = userRoles + crud.form.jobs = userJobs + return true + }, + // 获取左侧部门数据 + getDeptDatas(node, resolve) { + const sort = 'id,desc' + const params = { sort: sort } + if (typeof node !== 'object') { + if (node) { + params['name'] = node + } + } else if (node.level !== 0) { + params['pid'] = node.data.id + } + setTimeout(() => { + getDepts(params).then(res => { + if (resolve) { + resolve(res.content) + } else { + this.deptDatas = res.content + } + }) + }, 100) + }, + getDepts() { + getDepts({ enabled: true }).then(res => { + this.depts = res.content.map(function(obj) { + if (obj.hasChildren) { + obj.children = null + } + return obj + }) + }) + }, + getSupDepts(deptId) { + getDeptSuperior(deptId).then(res => { + const date = res.content + this.buildDepts(date) + this.depts = date + }) + }, + buildDepts(depts) { + depts.forEach(data => { + if (data.children) { + this.buildDepts(data.children) + } + if (data.hasChildren && !data.children) { + data.children = null + } + }) + }, + // 获取弹窗内部门数据 + loadDepts({ action, parentNode, callback }) { + if (action === LOAD_CHILDREN_OPTIONS) { + getDepts({ enabled: true, pid: parentNode.id }).then(res => { + parentNode.children = res.content.map(function(obj) { + if (obj.hasChildren) { + obj.children = null + } + return obj + }) + setTimeout(() => { + callback() + }, 200) + }) + } + }, + // 切换部门 + handleNodeClick(data) { + if (data.pid === 0) { + this.query.deptId = null + } else { + this.query.deptId = data.id + } + this.crud.toQuery() + }, + // 改变状态 + changeEnabled(data, val) { + this.$confirm('此操作将 "' + this.dict.label.user_status[val] + '" ' + data.username + ', 是否继续?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + crudUser.edit(data).then(res => { + this.crud.notify(this.dict.label.user_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + }).catch(() => { + data.enabled = !data.enabled + }) + }).catch(() => { + data.enabled = !data.enabled + }) + }, + // 获取弹窗内角色数据 + getRoles() { + getAll().then(res => { + this.roles = res + }).catch(() => { }) + }, + // 获取弹窗内岗位数据 + getJobs() { + getAllJob().then(res => { + this.jobs = res.content + }).catch(() => { }) + }, + // 获取权限级别 + getRoleLevel() { + getLevel().then(res => { + this.level = res.level + }).catch(() => { }) + }, + checkboxT(row, rowIndex) { + return row.id !== this.user.id + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + ::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value { + height: 30px; + line-height: 30px; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/config.vue b/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/config.vue new file mode 100644 index 0000000..e7b50d4 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/config.vue @@ -0,0 +1,98 @@ +<template> + <el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px"> + <el-form-item label="appID" prop="appId"> + <el-input v-model="form.appId" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">应用APPID,收款账号既是APPID对应支付宝账号</span> + </el-form-item> + <el-form-item label="商家账号" prop="sysServiceProviderId"> + <el-input v-model="form.sysServiceProviderId" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">商家账号</span> + </el-form-item> + <el-form-item label="商户私钥" prop="privateKey"> + <el-input v-model="form.privateKey" type="password" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">商户私钥,你的PKCS8格式RSA2私钥</span> + </el-form-item> + <el-form-item label="支付宝公钥" prop="publicKey"> + <el-input v-model="form.publicKey" type="password" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">支付宝公钥</span> + </el-form-item> + <el-form-item label="回调地址" prop="returnUrl"> + <el-input v-model="form.returnUrl" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">订单完成后返回的地址</span> + </el-form-item> + <el-form-item label="异步通知" prop="notifyUrl"> + <el-input v-model="form.notifyUrl" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">支付结果异步通知地址</span> + </el-form-item> + <el-form-item label=""> + <el-button :loading="loading" size="medium" type="primary" @click="doSubmit">保存配置</el-button> + </el-form-item> + </el-form> +</template> + +<script> +import { get, update } from '@/api/tools/alipay' +export default { + name: 'Config', + data() { + return { + loading: false, + form: { appId: '', sysServiceProviderId: '', privateKey: '', publicKey: '', returnUrl: '', notifyUrl: '' }, + rules: { + appId: [ + { required: true, message: '请输入appID', trigger: 'blur' } + ], + sysServiceProviderId: [ + { required: true, message: '请输入商家账号', trigger: 'blur' } + ], + privateKey: [ + { required: true, message: '商户私钥不能为空', trigger: 'blur' } + ], + publicKey: [ + { required: true, message: '支付宝公钥不能为空', trigger: 'blur' } + ], + returnUrl: [ + { required: true, message: '回调地址不能为空', trigger: 'blur' } + ], + notifyUrl: [ + { required: true, message: '回调地址不能为空', trigger: 'blur' } + ] + } + } + }, + created() { + this.init() + }, + methods: { + init() { + get().then(res => { + this.form = res + }) + }, + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + update(this.form).then(res => { + this.$notify({ + title: '修改成功', + type: 'success', + duration: 2500 + }) + this.loading = false + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/index.vue b/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/index.vue new file mode 100644 index 0000000..5234929 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/index.vue @@ -0,0 +1,48 @@ +<template> + <el-tabs v-model="activeName" style="padding-left: 5px;"> + <el-tab-pane label="参数配置" name="first"> + <Config /> + </el-tab-pane> + <el-tab-pane label="支付测试" name="second"> + <ToPay /> + </el-tab-pane> + <el-tab-pane label="使用说明" name="third"> + <div> + <blockquote class="my-blockquote">注意</blockquote> + <pre class="my-code"> +测试所用参数都是沙箱环境,仅供测试使用,申请地址:<a style="color: #00a0e9" href="https://openhome.alipay.com/platform/appDaily.htm?tab=info" target="_blank">支付宝开发平台</a> +如需付款测试,请使用 +密码与支付密码:111111</pre> + <blockquote class="my-blockquote"> 支付设置</blockquote> + <pre class="my-code"> +// 支付提供两个接口, +// PC端与手机端,并且在前端使用代码识别 +if (/(Android)/i.test(navigator.userAgent)){ // 判断是否为Android手机 + url = "/aliPay/toPayAsWeb" +}else if(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)){ // 判断是否为苹果手机 + url = "/aliPay/toPayAsWeb" +} else { + url = "/aliPay/toPayAsPC" +}</pre> + </div> + </el-tab-pane> + </el-tabs> +</template> + +<script> +import Config from './config' +import ToPay from './toPay' +export default { + name: 'AliPay', + components: { Config, ToPay }, + data() { + return { + activeName: 'second' + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/toPay.vue b/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/toPay.vue new file mode 100644 index 0000000..595cf08 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/aliPay/toPay.vue @@ -0,0 +1,86 @@ +<template> + <div> + <el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="90px"> + <el-form-item label="商品名称" prop="subject"> + <el-input v-model="form.subject" style="width: 35%" /> + </el-form-item> + <el-form-item label="商品价格" prop="totalAmount"> + <el-input v-model="form.totalAmount" style="width: 35%" /> + <span style="color: #C0C0C0;margin-left: 10px;">测试允许区间(0,5000]</span> + </el-form-item> + <el-form-item label="商品描述" prop="body"> + <el-input v-model="form.body" style="width: 35%" rows="8" type="textarea" /> + </el-form-item> + <el-form-item label=""> + <el-button :loading="loading" size="medium" type="primary" @click="doSubmit">去支付</el-button> + </el-form-item> + </el-form> + </div> +</template> + +<script> +import { toAliPay } from '@/api/tools/alipay' +export default { + data() { + return { + url: '', + // 新窗口的引用 + newWin: null, + loading: false, form: { subject: '', totalAmount: '', body: '' }, + rules: { + subject: [ + { required: true, message: '商品名称不能为空', trigger: 'blur' } + ], + totalAmount: [ + { required: true, message: '商品价格不能为空', trigger: 'blur' } + ], + body: [ + { required: true, message: '商品描述不能为空', trigger: 'blur' } + ] + } + } + }, + watch: { + url(newVal, oldVal) { + if (newVal && this.newWin) { + this.newWin.sessionStorage.clear() + this.newWin.location.href = newVal + // 重定向后把url和newWin重置 + this.url = '' + this.newWin = null + } + } + }, + methods: { + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + // 先打开一个空的新窗口,再请求 + this.newWin = window.open() + let url = '' + if (/(Android)/i.test(navigator.userAgent)) { // 判断是否为Android手机 + url = 'aliPay/toPayAsWeb' + } else if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { // 判断是否为苹果手机 + url = 'aliPay/toPayAsWeb' + } else { + url = 'aliPay/toPayAsPC' + } + toAliPay(url, this.form).then(res => { + this.loading = false + this.url = res + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/email/config.vue b/UI source code/dns_mapping_ui-master/src/views/tools/email/config.vue new file mode 100644 index 0000000..b72b724 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/email/config.vue @@ -0,0 +1,91 @@ +<template> + <el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px"> + <el-form-item label="发件人邮箱" prop="fromUser"> + <el-input v-model="form.fromUser" style="width: 40%" /> + <span style="color: #C0C0C0;margin-left: 10px;">Sender mailbox</span> + </el-form-item> + <el-form-item label="发件用户名" prop="user"> + <el-input v-model="form.user" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">Sender usernamex</span> + </el-form-item> + <el-form-item label="邮箱密码" prop="pass"> + <el-input v-model="form.pass" type="password" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">email Password</span> + </el-form-item> + <el-form-item label="SMTP地址" prop="host"> + <el-input v-model="form.host" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">SMTP address</span> + </el-form-item> + <el-form-item label="SMTP端口" prop="port"> + <el-input v-model="form.port" style="width: 40%;" /> + <span style="color: #C0C0C0;margin-left: 10px;">SMTP port</span> + </el-form-item> + <el-form-item label=""> + <el-button :loading="loading" size="medium" type="primary" @click="doSubmit">保存配置</el-button> + </el-form-item> + </el-form> +</template> + +<script> +import { get, update } from '@/api/tools/email' +export default { + name: 'Config', + data() { + return { + loading: false, form: { id: 1, fromUser: '', user: '', pass: '', host: '', port: '', sslEnable: '' }, + rules: { + fromUser: [ + { required: true, message: '请输入发件人邮箱', trigger: 'blur' }, + { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' } + ], + user: [ + { required: true, message: '请输入发件用户名', trigger: 'blur' } + ], + pass: [ + { required: true, message: '密码不能为空', trigger: 'blur' } + ], + host: [ + { required: true, message: 'SMTP地址不能为空', trigger: 'blur' } + ], + port: [ + { required: true, message: 'SMTP端口不能为空', trigger: 'blur' } + ] + } + } + }, + created() { + this.init() + }, + methods: { + init() { + get().then(res => { + this.form = res + }) + }, + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + update(this.form).then(res => { + this.$notify({ + title: '修改成功', + type: 'success', + duration: 2500 + }) + this.loading = false + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/email/index.vue b/UI source code/dns_mapping_ui-master/src/views/tools/email/index.vue new file mode 100644 index 0000000..d0e6387 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/email/index.vue @@ -0,0 +1,41 @@ +<template> + <el-tabs v-model="activeName" style="padding-left: 8px;"> + <el-tab-pane label="邮箱配置" name="first"> + <Config /> + </el-tab-pane> + <el-tab-pane label="发送邮件" name="second"> + <Send /> + </el-tab-pane> + <el-tab-pane label="使用说明" name="third"> + <div> + <blockquote class="my-blockquote"> 邮件服务器配置</blockquote> + <pre class="my-code"> + # 邮件服务器的SMTP地址,可选,默认为smtp + # 邮件服务器的SMTP端口,可选,默认465或者25 + # 发件人(必须正确,否则发送失败) + # 用户名,默认为发件人邮箱前缀 + # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,如QQ和163等等) + # 是否开启ssl,默认开启</pre> + <blockquote class="my-blockquote">更多帮助</blockquote> + <pre class="my-code">更多帮助请查看文档:<a style="color:#009688" href="http://hutool.mydoc.io/#text_319499" target="_black">hutool工具包</a></pre> + </div> + </el-tab-pane> + </el-tabs> +</template> + +<script> +import Config from './config' +import Send from './send' +export default { + name: 'Email', + components: { Config, Send }, + data() { + return { + activeName: 'second' + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/email/send.vue b/UI source code/dns_mapping_ui-master/src/views/tools/email/send.vue new file mode 100644 index 0000000..d4bd568 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/email/send.vue @@ -0,0 +1,98 @@ +<template> + <div> + <el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px"> + <el-form-item label="邮件标题" prop="subject"> + <el-input v-model="form.subject" style="width: 646px" placeholder="请输入邮件标题,标题不能为空" /> + </el-form-item> + <el-form-item label="收件地址" prop="tos"> + <el-input v-model="form.tos" style="width: 646px" placeholder="请输入收件地址,多个地址英文逗号,隔开" /> + </el-form-item> + <div ref="editor" class="editor" /> + <el-button :loading="loading" style="margin-left:1.6%;margin-bottom: 30px" size="medium" type="primary" @click="doSubmit">发送邮件</el-button> + </el-form> + </div> +</template> + +<script> +import { send } from '@/api/tools/email' +import { upload } from '@/utils/upload' +import { mapGetters } from 'vuex' +import E from 'wangeditor' +export default { + name: 'Index', + data() { + return { + loading: false, form: { subject: '', tos: '', content: '' }, + rules: { + subject: [ + { required: true, message: '标题不能为空', trigger: 'blur' } + ], + tos: [ + { required: true, message: '收件人不能为空', trigger: 'blur' } + ] + } + } + }, + computed: { + ...mapGetters([ + 'imagesUploadApi', + 'baseApi' + ]) + }, + mounted() { + const _this = this + var editor = new E(this.$refs.editor) + // 自定义菜单配置 + editor.config.zIndex = 10 + // 文件上传 + editor.config.customUploadImg = function(files, insert) { + // files 是 input 中选中的文件列表 + // insert 是获取图片 url 后,插入到编辑器的方法 + files.forEach(image => { + upload(_this.imagesUploadApi, image).then(res => { + const data = res.data + const url = _this.baseApi + '/file/' + data.type + '/' + data.realName + insert(url) + }) + }) + } + editor.config.onchange = (html) => { + this.form.content = html + } + editor.create() + }, + methods: { + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + send(this.form).then(res => { + this.$notify({ + title: '发送成功', + type: 'success', + duration: 2500 + }) + this.loading = false + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + } + } +} +</script> + +<style scoped> + .editor{ + text-align:left; + margin: 20px; + width: 730px; + } + ::v-deep .w-e-text-container { + height: 360px !important; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/storage/index.vue b/UI source code/dns_mapping_ui-master/src/views/tools/storage/index.vue new file mode 100644 index 0000000..5bb3fc0 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/storage/index.vue @@ -0,0 +1,36 @@ +<template> + <el-tabs v-model="activeName" style="padding-left: 8px;" @tab-click="tabClick"> + <el-tab-pane label="本地存储" name="first"> + <Local ref="local" /> + </el-tab-pane> + <el-tab-pane label="七牛云存储" name="second"> + <QiNiu ref="qiNiu" /> + </el-tab-pane> + </el-tabs> +</template> + +<script> +import QiNiu from './qiniu/index' +import Local from './local/index' +export default { + name: 'Storage', + components: { QiNiu, Local }, + data() { + return { + activeName: 'first' + } + }, + methods: { + tabClick(name) { + if (this.activeName === 'first') { + this.$refs.local.crud.toQuery() + } else { + this.$refs.qiNiu.crud.toQuery() + } + } + } +} +</script> + +<style scoped> +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/storage/local/index.vue b/UI source code/dns_mapping_ui-master/src/views/tools/storage/local/index.vue new file mode 100644 index 0000000..3adf8a0 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/storage/local/index.vue @@ -0,0 +1,184 @@ +<template> + <div class="app-container" style="padding: 8px;"> + <!--工具栏--> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.blurry" clearable size="small" placeholder="输入内容模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission"> + <!-- 新增 --> + <el-button + slot="left" + v-permission="['admin','storage:add']" + class="filter-item" + size="mini" + type="primary" + icon="el-icon-upload" + @click="crud.toAdd" + >上传 + </el-button> + </crudOperation> + </div> + <!--表单组件--> + <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.add ? '文件上传' : '编辑文件'" width="500px"> + <el-form ref="form" :model="form" size="small" label-width="80px"> + <el-form-item label="文件名"> + <el-input v-model="form.name" style="width: 370px;" /> + </el-form-item> + <!-- 上传文件 --> + <el-form-item v-if="crud.status.add" label="上传"> + <el-upload + ref="upload" + :limit="1" + :before-upload="beforeUpload" + :auto-upload="false" + :headers="headers" + :on-success="handleSuccess" + :on-error="handleError" + :action="fileUploadApi + '?name=' + form.name" + > + <div class="eladmin-upload"><i class="el-icon-upload" /> 添加文件</div> + <div slot="tip" class="el-upload__tip">可上传任意格式文件,且不超过100M</div> + </el-upload> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="crud.cancelCU">取消</el-button> + <el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button> + <el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" label="文件名"> + <template slot-scope="scope"> + <el-popover + :content="'file/' + scope.row.type + '/' + scope.row.realName" + placement="top-start" + title="路径" + width="200" + trigger="hover" + > + <a + slot="reference" + :href="baseApi + '/file/' + scope.row.type + '/' + scope.row.realName" + class="el-link--primary" + style="word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color: #1890ff;font-size: 13px;" + target="_blank" + > + {{ scope.row.name }} + </a> + </el-popover> + </template> + </el-table-column> + <el-table-column prop="path" label="预览图"> + <template slot-scope="{row}"> + <el-image + :src=" baseApi + '/file/' + row.type + '/' + row.realName" + :preview-src-list="[baseApi + '/file/' + row.type + '/' + row.realName]" + fit="contain" + lazy + class="el-avatar" + > + <div slot="error"> + <i class="el-icon-document" /> + </div> + </el-image> + </template> + </el-table-column> + <el-table-column prop="suffix" label="文件类型" /> + <el-table-column prop="type" label="类别" /> + <el-table-column prop="size" label="大小" /> + <el-table-column prop="operate" label="操作人" /> + <el-table-column prop="createTime" label="创建日期" /> + </el-table> + <!--分页组件--> + <pagination /> + </div> +</template> + +<script> +import { mapGetters } from 'vuex' +import { getToken } from '@/utils/auth' +import crudFile from '@/api/tools/localStorage' +import CRUD, { presenter, header, form, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +const defaultForm = { id: null, name: '' } +export default { + components: { pagination, crudOperation, rrOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '文件', url: 'api/localStorage', crudMethod: { ...crudFile }}) + }, + mixins: [presenter(), header(), form(defaultForm), crud()], + data() { + return { + delAllLoading: false, + loading: false, + headers: { 'Authorization': getToken() }, + permission: { + edit: ['admin', 'storage:edit'], + del: ['admin', 'storage:del'] + } + } + }, + computed: { + ...mapGetters([ + 'baseApi', + 'fileUploadApi' + ]) + }, + created() { + this.crud.optShow.add = false + }, + methods: { + // 上传文件 + upload() { + this.$refs.upload.submit() + }, + beforeUpload(file) { + let isLt2M = true + isLt2M = file.size / 1024 / 1024 < 100 + if (!isLt2M) { + this.loading = false + this.$message.error('上传文件大小不能超过 100MB!') + } + this.form.name = file.name + return isLt2M + }, + handleSuccess(response, file, fileList) { + this.crud.notify('上传成功', CRUD.NOTIFICATION_TYPE.SUCCESS) + this.$refs.upload.clearFiles() + this.crud.status.add = CRUD.STATUS.NORMAL + this.crud.resetForm() + this.crud.toQuery() + }, + // 监听上传失败 + handleError(e, file, fileList) { + const msg = JSON.parse(e.message) + this.$notify({ + title: msg.message, + type: 'error', + duration: 2500 + }) + this.loading = false + } + } +} +</script> + +<style scoped> + ::v-deep .el-image__error, .el-image__placeholder{ + background: none; + } + ::v-deep .el-image-viewer__wrapper{ + top: 55px; + } +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/storage/qiniu/form.vue b/UI source code/dns_mapping_ui-master/src/views/tools/storage/qiniu/form.vue new file mode 100644 index 0000000..c77904e --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/storage/qiniu/form.vue @@ -0,0 +1,98 @@ +<template> + <el-dialog :visible.sync="dialog" :close-on-click-modal="false" title="七牛云配置" append-to-body width="580px"> + <el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="110px"> + <el-form-item label="Access Key" prop="accessKey"> + <el-input v-model="form.accessKey" style="width: 95%" placeholder="accessKey,在安全中心,秘钥管理中查看" /> + </el-form-item> + <el-form-item label="Secret Key" prop="secretKey"> + <el-input v-model="form.secretKey" type="password" style="width: 95%;" placeholder="secretKey,在安全中心,秘钥管理中查看" /> + </el-form-item> + <el-form-item label="空间名称" prop="bucket"> + <el-input v-model="form.bucket" style="width: 95%;" placeholder="存储空间名称作为唯一的 Bucket 识别符" /> + </el-form-item> + <el-form-item label="外链域名" prop="host"> + <el-input v-model="form.host" style="width: 95%;" placeholder="外链域名,可自定义,需在七牛云绑定" /> + </el-form-item> + <el-form-item label="存储区域"> + <el-select v-model="form.zone" placeholder="请选择存储区域"> + <el-option + v-for="item in zones" + :key="item" + :label="item" + :value="item" + /> + </el-select> + </el-form-item> + <el-form-item label="空间类型" prop="type"> + <el-radio v-model="form.type" label="公开">公开</el-radio> + <el-radio v-model="form.type" label="私有">私有</el-radio> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="text" @click="dialog = false">取消</el-button> + <el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button> + </div> + </el-dialog> +</template> + +<script> +import { get, update } from '@/api/tools/qiniu' +export default { + data() { + return { + zones: ['华东', '华北', '华南', '北美', '东南亚'], dialog: false, + loading: false, form: { accessKey: '', secretKey: '', bucket: '', host: '', zone: '', type: '' }, + rules: { + accessKey: [ + { required: true, message: '请输入accessKey', trigger: 'blur' } + ], + secretKey: [ + { required: true, message: '请输入secretKey', trigger: 'blur' } + ], + bucket: [ + { required: true, message: '请输入空间名称', trigger: 'blur' } + ], + host: [ + { required: true, message: '请输入外链域名', trigger: 'blur' } + ], + type: [ + { required: true, message: '空间类型不能为空', trigger: 'blur' } + ] + } + } + }, + methods: { + init() { + get().then(res => { + this.form = res + }) + }, + doSubmit() { + this.$refs['form'].validate((valid) => { + if (valid) { + this.loading = true + update(this.form).then(res => { + this.$notify({ + title: '修改成功', + type: 'success', + duration: 2500 + }) + this.$parent.crud.toQuery() + this.loading = false + this.dialog = false + }).catch(err => { + this.loading = false + console.log(err.response.data.message) + }) + } else { + return false + } + }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/storage/qiniu/index.vue b/UI source code/dns_mapping_ui-master/src/views/tools/storage/qiniu/index.vue new file mode 100644 index 0000000..bdb9c8d --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/storage/qiniu/index.vue @@ -0,0 +1,189 @@ +<template> + <div class="app-container" style="padding: 8px;"> + <!--表单组件--> + <eForm ref="form" /> + <!-- 工具栏 --> + <div class="head-container"> + <div v-if="crud.props.searchToggle"> + <!-- 搜索 --> + <el-input v-model="query.key" clearable size="small" placeholder="输入文件名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" /> + <date-range-picker v-model="query.createTime" class="date-item" /> + <rrOperation /> + </div> + <crudOperation :permission="permission"> + <template slot="left"> + <!-- 上传 --> + <el-button class="filter-item" size="mini" type="primary" icon="el-icon-upload" @click="dialog = true">上传</el-button> + <!-- 同步 --> + <el-button :icon="icon" class="filter-item" size="mini" type="warning" @click="synchronize">同步</el-button> + <!-- 配置 --> + <el-button + class="filter-item" + size="mini" + type="success" + icon="el-icon-s-tools" + @click="doConfig" + >配置</el-button> + </template> + </crudOperation> + <!-- 文件上传 --> + <el-dialog :visible.sync="dialog" :close-on-click-modal="false" append-to-body width="500px" @close="doSubmit"> + <el-upload + :before-remove="handleBeforeRemove" + :on-success="handleSuccess" + :on-error="handleError" + :file-list="fileList" + :headers="headers" + :action="qiNiuUploadApi" + class="upload-demo" + multiple + > + <el-button size="small" type="primary">点击上传</el-button> + <div slot="tip" style="display: block;" class="el-upload__tip">请勿上传违法文件,且文件不超过15M</div> + </el-upload> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="doSubmit">确认</el-button> + </div> + </el-dialog> + <!--表格渲染--> + <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" :show-overflow-tooltip="true" label="文件名"> + <template slot-scope="scope"> + <a href="JavaScript:" class="el-link el-link--primary" target="_blank" type="primary" @click="download(scope.row.id)">{{ scope.row.key }}</a> + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" prop="suffix" label="文件类型" @selection-change="crud.selectionChangeHandler" /> + <el-table-column prop="bucket" label="空间名称" /> + <el-table-column prop="size" label="文件大小" /> + <el-table-column prop="type" label="空间类型" /> + <el-table-column prop="updateTime" label="创建日期" /> + </el-table> + <!--分页组件--> + <pagination /> + </div> + </div> +</template> + +<script> +import crudQiNiu from '@/api/tools/qiniu' +import { mapGetters } from 'vuex' +import { getToken } from '@/utils/auth' +import eForm from './form' +import CRUD, { presenter, header, crud } from '@crud/crud' +import rrOperation from '@crud/RR.operation' +import crudOperation from '@crud/CRUD.operation' +import pagination from '@crud/Pagination' +import DateRangePicker from '@/components/DateRangePicker' + +export default { + components: { eForm, pagination, crudOperation, rrOperation, DateRangePicker }, + cruds() { + return CRUD({ title: '七牛云文件', url: 'api/qiNiuContent', crudMethod: { ...crudQiNiu }}) + }, + mixins: [presenter(), header(), crud()], + data() { + return { + permission: { + del: ['admin', 'storage:del'] + }, + title: '文件', dialog: false, + icon: 'el-icon-refresh', + url: '', headers: { 'Authorization': getToken() }, + dialogImageUrl: '', dialogVisible: false, fileList: [], files: [], newWin: null + } + }, + computed: { + ...mapGetters([ + 'qiNiuUploadApi' + ]) + }, + watch: { + url(newVal, oldVal) { + if (newVal && this.newWin) { + this.newWin.sessionStorage.clear() + this.newWin.location.href = newVal + // 重定向后把url和newWin重置 + this.url = '' + this.newWin = null + } + } + }, + created() { + this.crud.optShow.add = false + this.crud.optShow.edit = false + }, + methods: { + // 七牛云配置 + doConfig() { + const _this = this.$refs.form + _this.init() + _this.dialog = true + }, + handleSuccess(response, file, fileList) { + const uid = file.uid + const id = response.id + this.files.push({ uid, id }) + }, + handleBeforeRemove(file, fileList) { + for (let i = 0; i < this.files.length; i++) { + if (this.files[i].uid === file.uid) { + crudQiNiu.del([this.files[i].id]).then(res => {}) + return true + } + } + }, + handlePictureCardPreview(file) { + this.dialogImageUrl = file.url + this.dialogVisible = true + }, + // 刷新列表数据 + doSubmit() { + this.fileList = [] + this.dialogVisible = false + this.dialogImageUrl = '' + this.dialog = false + this.crud.toQuery() + }, + // 监听上传失败 + handleError(e, file, fileList) { + const msg = JSON.parse(e.message) + this.crud.notify(msg.message, CRUD.NOTIFICATION_TYPE.ERROR) + }, + // 下载文件 + download(id) { + this.downloadLoading = true + // 先打开一个空的新窗口,再请求 + this.newWin = window.open() + crudQiNiu.download(id).then(res => { + this.downloadLoading = false + this.url = res.url + }).catch(err => { + this.downloadLoading = false + console.log(err.response.data.message) + }) + }, + // 同步数据 + synchronize() { + this.icon = 'el-icon-loading' + crudQiNiu.sync().then(res => { + this.icon = 'el-icon-refresh' + this.$message({ + showClose: true, + message: '数据同步成功', + type: 'success', + duration: 1500 + }) + this.crud.toQuery() + }).catch(err => { + this.icon = 'el-icon-refresh' + console.log(err.response.data.message) + }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/UI source code/dns_mapping_ui-master/src/views/tools/swagger/index.vue b/UI source code/dns_mapping_ui-master/src/views/tools/swagger/index.vue new file mode 100644 index 0000000..5162cd9 --- /dev/null +++ b/UI source code/dns_mapping_ui-master/src/views/tools/swagger/index.vue @@ -0,0 +1,16 @@ +<template> + <elFrame :src="swaggerApi" /> +</template> +<script> +import { mapGetters } from 'vuex' +import elFrame from '@/components/Iframe/index' +export default { + name: 'Swagger', + components: { elFrame }, + computed: { + ...mapGetters([ + 'swaggerApi' + ]) + } +} +</script> |
