mirror of
https://github.com/veops/oneterm.git
synced 2025-09-26 19:31:14 +08:00
feat(ui): remove unused code and unused files
This commit is contained in:
@@ -5,14 +5,6 @@ if (IS_PROD) {
|
||||
plugins.push('transform-remove-console')
|
||||
}
|
||||
|
||||
// lazy load ant-design-vue
|
||||
// if your use import on Demand, Use this code
|
||||
// plugins.push(['import', {
|
||||
// 'libraryName': 'ant-design-vue',
|
||||
// 'libraryDirectory': 'es',
|
||||
// 'style': true // `style: true` 会加载 less 文件
|
||||
// }])
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset',
|
||||
|
@@ -2,7 +2,7 @@ const ThemeColorReplacer = require('webpack-theme-color-replacer')
|
||||
const generate = require('@ant-design/colors/lib/generate').default
|
||||
|
||||
const getAntdSerials = (color) => {
|
||||
// 淡化(即less的tint)
|
||||
// Lighten (similar to less's tint)
|
||||
const lightens = new Array(9).fill().map((t, i) => {
|
||||
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
|
||||
})
|
||||
@@ -13,8 +13,8 @@ const getAntdSerials = (color) => {
|
||||
|
||||
const themePluginOption = {
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
matchColors: getAntdSerials('#2f54eb'), // primary color series
|
||||
// change style selectors to solve style override issues
|
||||
changeSelector (selector) {
|
||||
switch (selector) {
|
||||
case '.ant-calendar-today .ant-calendar-date':
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
Binary file not shown.
@@ -66,133 +66,7 @@ export default {
|
||||
})
|
||||
)
|
||||
|
||||
// 注册富文本自定义元素
|
||||
// const resume = {
|
||||
// type: 'attachment',
|
||||
// attachmentLabel: '',
|
||||
// attachmentValue: '',
|
||||
// children: [{ text: '' }], // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
|
||||
// }
|
||||
|
||||
function withAttachment(editor) {
|
||||
// JS 语法
|
||||
const { isInline, isVoid } = editor
|
||||
const newEditor = editor
|
||||
|
||||
newEditor.isInline = (elem) => {
|
||||
const type = DomEditor.getNodeType(elem)
|
||||
if (type === 'attachment') return true // 针对 type: attachment ,设置为 inline
|
||||
return isInline(elem)
|
||||
}
|
||||
|
||||
newEditor.isVoid = (elem) => {
|
||||
const type = DomEditor.getNodeType(elem)
|
||||
if (type === 'attachment') return true // 针对 type: attachment ,设置为 void
|
||||
return isVoid(elem)
|
||||
}
|
||||
|
||||
return newEditor // 返回 newEditor ,重要!!!
|
||||
}
|
||||
Boot.registerPlugin(withAttachment)
|
||||
/**
|
||||
* 渲染“附件”元素到编辑器
|
||||
* @param elem 附件元素,即上文的 myResume
|
||||
* @param children 元素子节点,void 元素可忽略
|
||||
* @param editor 编辑器实例
|
||||
* @returns vnode 节点(通过 snabbdom.js 的 h 函数生成)
|
||||
*/
|
||||
function renderAttachment(elem, children, editor) {
|
||||
// JS 语法
|
||||
|
||||
// 获取“附件”的数据,参考上文 myResume 数据结构
|
||||
const { attachmentLabel = '', attachmentValue = '' } = elem
|
||||
|
||||
// 附件元素 vnode
|
||||
const attachVnode = h(
|
||||
// HTML tag
|
||||
'span',
|
||||
// HTML 属性、样式、事件
|
||||
{
|
||||
props: { contentEditable: false }, // HTML 属性,驼峰式写法
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
margin: '0 3px',
|
||||
padding: '0 3px',
|
||||
backgroundColor: '#e6f7ff',
|
||||
border: '1px solid #91d5ff',
|
||||
borderRadius: '2px',
|
||||
color: '#1890ff',
|
||||
}, // style ,驼峰式写法
|
||||
on: {
|
||||
click() {
|
||||
console.log('clicked', attachmentValue)
|
||||
} /* 其他... */,
|
||||
},
|
||||
},
|
||||
// 子节点
|
||||
[attachmentLabel]
|
||||
)
|
||||
|
||||
return attachVnode
|
||||
}
|
||||
const renderElemConf = {
|
||||
type: 'attachment', // 新元素 type ,重要!!!
|
||||
renderElem: renderAttachment,
|
||||
}
|
||||
Boot.registerRenderElem(renderElemConf)
|
||||
|
||||
/**
|
||||
* 生成“附件”元素的 HTML
|
||||
* @param elem 附件元素,即上文的 myResume
|
||||
* @param childrenHtml 子节点的 HTML 代码,void 元素可忽略
|
||||
* @returns “附件”元素的 HTML 字符串
|
||||
*/
|
||||
function attachmentToHtml(elem, childrenHtml) {
|
||||
// JS 语法
|
||||
|
||||
// 获取附件元素的数据
|
||||
const { attachmentValue = '', attachmentLabel = '' } = elem
|
||||
|
||||
// 生成 HTML 代码
|
||||
const html = `<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline data-attachmentValue="${attachmentValue}" data-attachmentLabel="${attachmentLabel}">${attachmentLabel}</span>`
|
||||
|
||||
return html
|
||||
}
|
||||
const elemToHtmlConf = {
|
||||
type: 'attachment', // 新元素的 type ,重要!!!
|
||||
elemToHtml: attachmentToHtml,
|
||||
}
|
||||
Boot.registerElemToHtml(elemToHtmlConf)
|
||||
|
||||
/**
|
||||
* 解析 HTML 字符串,生成“附件”元素
|
||||
* @param domElem HTML 对应的 DOM Element
|
||||
* @param children 子节点
|
||||
* @param editor editor 实例
|
||||
* @returns “附件”元素,如上文的 myResume
|
||||
*/
|
||||
function parseAttachmentHtml(domElem, children, editor) {
|
||||
// JS 语法
|
||||
|
||||
// 从 DOM element 中获取“附件”的信息
|
||||
const attachmentValue = domElem.getAttribute('data-attachmentValue') || ''
|
||||
const attachmentLabel = domElem.getAttribute('data-attachmentLabel') || ''
|
||||
|
||||
// 生成“附件”元素(按照此前约定的数据结构)
|
||||
const myResume = {
|
||||
type: 'attachment',
|
||||
attachmentValue,
|
||||
attachmentLabel,
|
||||
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!!
|
||||
}
|
||||
|
||||
return myResume
|
||||
}
|
||||
const parseHtmlConf = {
|
||||
selector: 'span[data-w-e-type="attachment"]', // CSS 选择器,匹配特定的 HTML 标签
|
||||
parseElemHtml: parseAttachmentHtml,
|
||||
}
|
||||
Boot.registerParseElemHtml(parseHtmlConf)
|
||||
this.handleEditor()
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
@@ -227,6 +101,119 @@ export default {
|
||||
}
|
||||
this.SET_LOCALE(saveLocale)
|
||||
this.$i18n.locale = saveLocale
|
||||
},
|
||||
|
||||
handleEditor() {
|
||||
// register custom rich text element: attachment
|
||||
function withAttachment(editor) {
|
||||
const { isInline, isVoid } = editor
|
||||
const newEditor = editor
|
||||
|
||||
newEditor.isInline = (elem) => {
|
||||
const type = DomEditor.getNodeType(elem)
|
||||
if (type === 'attachment') return true // For type: attachment, set to inline
|
||||
return isInline(elem)
|
||||
}
|
||||
|
||||
newEditor.isVoid = (elem) => {
|
||||
const type = DomEditor.getNodeType(elem)
|
||||
if (type === 'attachment') return true // For type: attachment ,set to void
|
||||
return isVoid(elem)
|
||||
}
|
||||
|
||||
return newEditor // Must return, important!!!
|
||||
}
|
||||
Boot.registerPlugin(withAttachment)
|
||||
/**
|
||||
* Render "attachment" element in editor
|
||||
* @param elem Attachment element
|
||||
* @param children Child nodes (ignored for void elements)
|
||||
* @param editor Editor instance
|
||||
* @returns vnode (generated by snabbdom's h function)
|
||||
*/
|
||||
function renderAttachment(elem, children, editor) {
|
||||
const { attachmentLabel = '', attachmentValue = '' } = elem
|
||||
|
||||
const attachVnode = h(
|
||||
// HTML tag
|
||||
'span',
|
||||
// HTML attr, style, event
|
||||
{
|
||||
props: { contentEditable: false },
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
margin: '0 3px',
|
||||
padding: '0 3px',
|
||||
backgroundColor: '#e6f7ff',
|
||||
border: '1px solid #91d5ff',
|
||||
borderRadius: '2px',
|
||||
color: '#1890ff',
|
||||
},
|
||||
on: {
|
||||
click() {
|
||||
console.log('clicked', attachmentValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
// child node
|
||||
[attachmentLabel]
|
||||
)
|
||||
|
||||
return attachVnode
|
||||
}
|
||||
const renderElemConf = {
|
||||
type: 'attachment',
|
||||
renderElem: renderAttachment,
|
||||
}
|
||||
Boot.registerRenderElem(renderElemConf)
|
||||
|
||||
/**
|
||||
* Generate HTML for "attachment" element
|
||||
* @param elem Attachment element
|
||||
* @param childrenHtml Child HTML (ignored for void elements)
|
||||
* @returns HTML string
|
||||
*/
|
||||
function attachmentToHtml(elem, childrenHtml) {
|
||||
// Getting data for attached elements
|
||||
const { attachmentValue = '', attachmentLabel = '' } = elem
|
||||
|
||||
// generate HTML
|
||||
const html = `<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline data-attachmentValue="${attachmentValue}" data-attachmentLabel="${attachmentLabel}">${attachmentLabel}</span>`
|
||||
|
||||
return html
|
||||
}
|
||||
const elemToHtmlConf = {
|
||||
type: 'attachment',
|
||||
elemToHtml: attachmentToHtml,
|
||||
}
|
||||
Boot.registerElemToHtml(elemToHtmlConf)
|
||||
|
||||
/**
|
||||
* Parse HTML to generate "attachment" element
|
||||
* @param domElem DOM element
|
||||
* @param children Children
|
||||
* @param editor Editor instance
|
||||
* @returns Attachment element
|
||||
*/
|
||||
function parseAttachmentHtml(domElem, children, editor) {
|
||||
// Getting “attachment” information from DOM element
|
||||
const attachmentValue = domElem.getAttribute('data-attachmentValue') || ''
|
||||
const attachmentLabel = domElem.getAttribute('data-attachmentLabel') || ''
|
||||
|
||||
const myResume = {
|
||||
type: 'attachment',
|
||||
attachmentValue,
|
||||
attachmentLabel,
|
||||
children: [{ text: '' }], // The void node must have children with an empty string in it, important!!!!
|
||||
}
|
||||
|
||||
return myResume
|
||||
}
|
||||
const parseHtmlConf = {
|
||||
selector: 'span[data-w-e-type="attachment"]', // CSS selector to match specific HTML tags
|
||||
parseElemHtml: parseAttachmentHtml,
|
||||
}
|
||||
Boot.registerParseElemHtml(parseHtmlConf)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,7 +17,7 @@ export const expList = () => {
|
||||
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
|
||||
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
|
||||
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') },
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
@@ -301,7 +301,7 @@ export default {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') },
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
@@ -87,9 +87,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* @param isInitOne When the initialization exp is null, does the ruleList default to giving one
|
||||
*/
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
@@ -204,7 +206,7 @@ export default {
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.ruleList[0].type = 'and' // after add/delete, just in case the first one is not 'and'
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 元素折叠过度效果
|
||||
* Collapse transition effect for elements
|
||||
*/
|
||||
export default {
|
||||
name: 'CollapseTransition',
|
||||
@@ -33,20 +33,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
collapseBeforeEnter(el) {
|
||||
// console.log('11, collapseBeforeEnter');
|
||||
this.oldPaddingBottom = el.style.paddingBottom
|
||||
this.oldPaddingTop = el.style.paddingTop
|
||||
// 过渡效果开始前设置元素的maxHeight为0,让元素maxHeight有一个初始值
|
||||
// set the element's maxHeight to 0 before the transition effect starts so that the element's maxHeight has an initial value
|
||||
el.style.paddingTop = '0'
|
||||
el.style.paddingBottom = '0'
|
||||
el.style.maxHeight = '0'
|
||||
},
|
||||
collapseEnter(el, done) {
|
||||
// console.log('22, collapseEnter');
|
||||
//
|
||||
this.oldOverflow = el.style.overflow
|
||||
const elHeight = el.scrollHeight
|
||||
// 过渡效果进入后将元素的maxHeight设置为元素本身的高度,将元素maxHeight设置为auto不会有过渡效果
|
||||
// After entering, set maxHeight to the element's height; setting maxHeight to auto will not have a transition effect
|
||||
if (elHeight > 0) {
|
||||
el.style.maxHeight = elHeight + 'px'
|
||||
} else {
|
||||
@@ -59,24 +56,20 @@ export default {
|
||||
// done();
|
||||
const onTransitionDone = function() {
|
||||
done()
|
||||
// console.log('enter onTransitionDone');
|
||||
el.removeEventListener('transitionend', onTransitionDone, false)
|
||||
el.removeEventListener('transitioncancel', onTransitionDone, false)
|
||||
}
|
||||
// 绑定元素的transition完成事件,在transition完成后立即完成vue的过度动效
|
||||
// Bind transition end event to finish Vue's transition immediately after the CSS transition
|
||||
el.addEventListener('transitionend', onTransitionDone, false)
|
||||
el.addEventListener('transitioncancel', onTransitionDone, false)
|
||||
},
|
||||
collapseAfterEnter(el) {
|
||||
// console.log('33, collapseAfterEnter');
|
||||
// 过渡效果完成后恢复元素的maxHeight
|
||||
// Restore maxHeight after transition is complete
|
||||
el.style.maxHeight = ''
|
||||
el.style.overflow = this.oldOverflow
|
||||
},
|
||||
|
||||
collapseBeforeLeave(el) {
|
||||
// console.log('44, collapseBeforeLeave', el.scrollHeight);
|
||||
|
||||
this.oldPaddingBottom = el.style.paddingBottom
|
||||
this.oldPaddingTop = el.style.paddingTop
|
||||
this.oldOverflow = el.style.overflow
|
||||
@@ -85,8 +78,6 @@ export default {
|
||||
el.style.overflow = 'hidden'
|
||||
},
|
||||
collapseLeave(el, done) {
|
||||
// console.log('55, collapseLeave', el.scrollHeight);
|
||||
|
||||
if (el.scrollHeight !== 0) {
|
||||
el.style.maxHeight = '0'
|
||||
el.style.paddingBottom = '0'
|
||||
@@ -95,16 +86,14 @@ export default {
|
||||
// done();
|
||||
const onTransitionDone = function() {
|
||||
done()
|
||||
// console.log('leave onTransitionDone');
|
||||
el.removeEventListener('transitionend', onTransitionDone, false)
|
||||
el.removeEventListener('transitioncancel', onTransitionDone, false)
|
||||
}
|
||||
// 绑定元素的transition完成事件,在transition完成后立即完成vue的过度动效
|
||||
// Bind transition end event to finish Vue's transition immediately after the CSS transition
|
||||
el.addEventListener('transitionend', onTransitionDone, false)
|
||||
el.addEventListener('transitioncancel', onTransitionDone, false)
|
||||
},
|
||||
collapseAfterLeave(el) {
|
||||
// console.log('66, collapseAfterLeave');
|
||||
el.style.maxHeight = ''
|
||||
el.style.overflow = this.oldOverflow
|
||||
el.style.paddingBottom = this.oldPaddingBottom
|
||||
|
@@ -1,9 +1,6 @@
|
||||
/* eslint-disable */
|
||||
/*
|
||||
!!!!!!!
|
||||
以下为凶残的cron表达式验证,胆小肾虚及心脏病者慎入!!!
|
||||
不听劝告者后果自负T T
|
||||
!!!!!!!
|
||||
cron表达式验证
|
||||
cron表达式为秒,分,时,日,月,周,年
|
||||
判断正误方法:错误的话返回错误信息,正确的话返回true
|
||||
*/
|
||||
|
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 穿梭框双击实现
|
||||
// Double-click to move from left to right
|
||||
leftToRight(leftList, dataSource, targetKeys, sourceImportantKey, targetImportantKey) {
|
||||
for (let i = 0; i < leftList.length; i++) {
|
||||
leftList[i].ondblclick = e => {
|
||||
@@ -28,6 +28,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
// Double-click to move from right to left
|
||||
rightToLeft(rightList, dataSource, targetKeys, sourceImportantKey, targetImportantKey) {
|
||||
for (let i = 0; i < rightList.length; i++) {
|
||||
rightList[i].ondblclick = e => {
|
||||
@@ -42,7 +43,10 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
// 必须传入importantKey,用来做键名比对,传错或不传会造成错误
|
||||
/**
|
||||
* You must pass importantKey for key comparison.
|
||||
* Passing the wrong key or not passing it will cause errors.
|
||||
*/
|
||||
dbClick(sourceSelectedKeys, targetSelectedKeys, sourceImportantKey, targetImportantKey) {
|
||||
window.setTimeout(() => {
|
||||
const element = document.getElementsByClassName('ant-transfer-list-content')
|
||||
|
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div class="footer">
|
||||
<div class="links">
|
||||
<a
|
||||
href="http://dfc.sh/"
|
||||
target="_blank"
|
||||
>DFC首页</a>
|
||||
<a
|
||||
href="https://github.com/sendya/ant-design-pro-vue"
|
||||
target="_blank"
|
||||
>
|
||||
<a-icon type="github" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="copyright">
|
||||
Copyright
|
||||
<a-icon type="copyright" /> 2019 <span>IT@dfc.sh</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GlobalFooter',
|
||||
data () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.footer {
|
||||
padding: 0 16px;
|
||||
margin: 48px 0 24px;
|
||||
text-align: center;
|
||||
|
||||
.links {
|
||||
margin-bottom: 8px;
|
||||
|
||||
a {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.copyright {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,2 +0,0 @@
|
||||
import GlobalFooter from './GlobalFooter'
|
||||
export default GlobalFooter
|
@@ -17,7 +17,9 @@
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||
/**
|
||||
* This component is used in the same way as vxe-table, but when calling its methods, you need to call `getVxetableRef()` to get the vxe-table component first.
|
||||
*/
|
||||
export default {
|
||||
name: 'OpsTable',
|
||||
data() {
|
||||
@@ -34,7 +36,7 @@ export default {
|
||||
return this.$listeners
|
||||
}
|
||||
return Object.assign(this.$listeners, {
|
||||
// 在这里覆盖原有的change事件
|
||||
// overriding vxe-table change events
|
||||
// 'checkbox-change': this.selectChangeEvent,
|
||||
'checkbox-range-change': this.checkboxRangeChange,
|
||||
'checkbox-range-start': this.checkboxRangeStart,
|
||||
|
@@ -1,10 +0,0 @@
|
||||
import { Spin } from 'ant-design-vue'
|
||||
|
||||
export default {
|
||||
name: 'PageLoading',
|
||||
render () {
|
||||
return (<div style={{ paddingTop: 100, textAlign: 'center' }}>
|
||||
<Spin size="large" />
|
||||
</div>)
|
||||
}
|
||||
}
|
@@ -1,354 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-drawer" ref="settingDrawer">
|
||||
<a-drawer
|
||||
width="300"
|
||||
placement="right"
|
||||
@close="onClose"
|
||||
:closable="false"
|
||||
:visible="visible"
|
||||
>
|
||||
<div class="setting-drawer-index-content">
|
||||
|
||||
<div :style="{ marginBottom: '24px' }">
|
||||
<h3 class="setting-drawer-index-title">整体风格设置</h3>
|
||||
|
||||
<div class="setting-drawer-index-blockChecbox">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
暗色菜单风格
|
||||
</template>
|
||||
<div class="setting-drawer-index-item" @click="handleMenuTheme('dark')">
|
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark">
|
||||
<div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'">
|
||||
<a-icon type="check"/>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
亮色菜单风格
|
||||
</template>
|
||||
<div class="setting-drawer-index-item" @click="handleMenuTheme('light')">
|
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light">
|
||||
<div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'">
|
||||
<a-icon type="check"/>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :style="{ marginBottom: '24px' }">
|
||||
<h3 class="setting-drawer-index-title">主题色</h3>
|
||||
|
||||
<div style="height: 20px">
|
||||
<a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
|
||||
<template slot="title">
|
||||
{{ item.key }}
|
||||
</template>
|
||||
<a-tag :color="item.color" @click="changeColor(item.color)">
|
||||
<a-icon type="check" v-if="item.color === primaryColor"></a-icon>
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '24px' }">
|
||||
<h3 class="setting-drawer-index-title">导航模式</h3>
|
||||
|
||||
<div class="setting-drawer-index-blockChecbox">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
侧边栏导航
|
||||
</template>
|
||||
<div class="setting-drawer-index-item" @click="handleLayout('sidemenu')">
|
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu">
|
||||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'">
|
||||
<a-icon type="check"/>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
顶部栏导航
|
||||
</template>
|
||||
<div class="setting-drawer-index-item" @click="handleLayout('topmenu')">
|
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu">
|
||||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'">
|
||||
<a-icon type="check"/>
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div :style="{ marginTop: '24px' }">
|
||||
<a-list :split="false">
|
||||
<a-list-item>
|
||||
<a-tooltip slot="actions">
|
||||
<template slot="title">
|
||||
该设定仅 [顶部栏导航] 时有效
|
||||
</template>
|
||||
<a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange">
|
||||
<a-select-option value="Fixed">固定</a-select-option>
|
||||
<a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
|
||||
</a-select>
|
||||
</a-tooltip>
|
||||
<a-list-item-meta>
|
||||
<div slot="title">内容区域宽度</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
|
||||
<a-list-item-meta>
|
||||
<div slot="title">固定 Header</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
|
||||
<a-list-item-meta>
|
||||
<a-tooltip slot="title" placement="left">
|
||||
<template slot="title">固定 Header 时可配置</template>
|
||||
<div :style="{ opacity: !fixedHeader ? '0.5' : '1' }">下滑时隐藏 Header</div>
|
||||
</a-tooltip>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item >
|
||||
<a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" />
|
||||
<a-list-item-meta>
|
||||
<div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '24px' }">
|
||||
<h3 class="setting-drawer-index-title">其他设置</h3>
|
||||
<div>
|
||||
<a-list :split="false">
|
||||
<a-list-item>
|
||||
<a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
|
||||
<a-list-item-meta>
|
||||
<div slot="title">色弱模式</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch slot="actions" size="small" :defaultChecked="multiTab" @change="onMultiTab" />
|
||||
<a-list-item-meta>
|
||||
<div slot="title">多页签模式</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div :style="{ marginBottom: '24px' }">
|
||||
<a-button
|
||||
@click="doCopy"
|
||||
icon="copy"
|
||||
block
|
||||
>拷贝设置</a-button>
|
||||
<a-alert type="warning" :style="{ marginTop: '24px' }">
|
||||
<span slot="message">
|
||||
配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件
|
||||
<a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/setting.js" target="_blank">src/config/setting.js</a>
|
||||
</span>
|
||||
</a-alert>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-drawer-index-handle" @click="toggle">
|
||||
<a-icon type="setting" v-if="!visible"/>
|
||||
<a-icon type="close" v-else/>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DetailList } from '@/components'
|
||||
import SettingItem from './SettingItem'
|
||||
import config from '@/config/setting'
|
||||
import { updateTheme, updateColorWeak, colorList } from './settingConfig'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DetailList,
|
||||
SettingItem
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
data () {
|
||||
return {
|
||||
visible: true,
|
||||
colorList
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
mounted () {
|
||||
const vm = this
|
||||
setTimeout(() => {
|
||||
vm.visible = false
|
||||
}, 16)
|
||||
updateTheme(this.primaryColor)
|
||||
if (this.colorWeak !== config.colorWeak) {
|
||||
updateColorWeak(this.colorWeak)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDrawer () {
|
||||
this.visible = true
|
||||
},
|
||||
onClose () {
|
||||
this.visible = false
|
||||
},
|
||||
toggle () {
|
||||
this.visible = !this.visible
|
||||
},
|
||||
onColorWeak (checked) {
|
||||
this.$store.dispatch('ToggleWeak', checked)
|
||||
updateColorWeak(checked)
|
||||
},
|
||||
onMultiTab (checked) {
|
||||
this.$store.dispatch('ToggleMultiTab', checked)
|
||||
},
|
||||
handleMenuTheme (theme) {
|
||||
this.$store.dispatch('ToggleTheme', theme)
|
||||
},
|
||||
doCopy () {
|
||||
// get current settings from mixin or this.$store.state.app, pay attention to the property name
|
||||
const text = `export default {
|
||||
primaryColor: '${this.primaryColor}', // primary color of ant design
|
||||
navTheme: '${this.navTheme}', // theme for nav menu
|
||||
layout: '${this.layoutMode}', // nav menu position: sidemenu or topmenu
|
||||
contentWidth: '${this.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu
|
||||
fixedHeader: ${this.fixedHeader}, // sticky header
|
||||
fixSiderbar: ${this.fixSiderbar}, // sticky siderbar
|
||||
autoHideHeader: ${this.autoHideHeader}, // auto hide header
|
||||
colorWeak: ${this.colorWeak},
|
||||
multiTab: ${this.multiTab},
|
||||
production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true',
|
||||
// vue-ls options
|
||||
storageOptions: {
|
||||
namespace: 'pro__',
|
||||
name: 'ls',
|
||||
storage: 'local',
|
||||
}
|
||||
}`
|
||||
this.$copyText(text).then(message => {
|
||||
console.log('copy', message)
|
||||
this.$message.success('复制完毕')
|
||||
}).catch(err => {
|
||||
console.log('copy.err', err)
|
||||
this.$message.error('复制失败')
|
||||
})
|
||||
},
|
||||
handleLayout (mode) {
|
||||
this.$store.dispatch('ToggleLayoutMode', mode)
|
||||
// 因为顶部菜单不能固定左侧菜单栏,所以强制关闭
|
||||
this.handleFixSiderbar(false)
|
||||
},
|
||||
handleContentWidthChange (type) {
|
||||
this.$store.dispatch('ToggleContentWidth', type)
|
||||
},
|
||||
changeColor (color) {
|
||||
if (this.primaryColor !== color) {
|
||||
this.$store.dispatch('ToggleColor', color)
|
||||
updateTheme(color)
|
||||
}
|
||||
},
|
||||
handleFixedHeader (fixed) {
|
||||
this.$store.dispatch('ToggleFixedHeader', fixed)
|
||||
},
|
||||
handleFixedHeaderHidden (autoHidden) {
|
||||
this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
|
||||
},
|
||||
handleFixSiderbar (fixed) {
|
||||
if (this.layoutMode === 'topmenu') {
|
||||
this.$store.dispatch('ToggleFixSiderbar', false)
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('ToggleFixSiderbar', fixed)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.setting-drawer-index-content {
|
||||
|
||||
.setting-drawer-index-blockChecbox {
|
||||
display: flex;
|
||||
|
||||
.setting-drawer-index-item {
|
||||
margin-right: 16px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.setting-drawer-index-selectIcon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding-top: 15px;
|
||||
padding-left: 24px;
|
||||
height: 100%;
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
.setting-drawer-theme-color-colorBlock {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-drawer-index-handle {
|
||||
position: absolute;
|
||||
top: 240px;
|
||||
background: #1890ff;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
right: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
z-index: 1001;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
i {
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-drawer-index-item">
|
||||
<h3 class="setting-drawer-index-title">{{ title }}</h3>
|
||||
<slot></slot>
|
||||
<a-divider v-if="divider"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettingItem',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
divider: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.setting-drawer-index-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.setting-drawer-index-title {
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
line-height: 22px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@@ -1,2 +0,0 @@
|
||||
import SettingDrawer from './SettingDrawer'
|
||||
export default SettingDrawer
|
@@ -1,105 +0,0 @@
|
||||
import { message } from 'ant-design-vue/es'
|
||||
// import setting from '../setting';
|
||||
import themeColor from './themeColor.js'
|
||||
|
||||
// let lessNodesAppended
|
||||
|
||||
const colorList = [
|
||||
{
|
||||
key: '薄暮', color: '#F5222D'
|
||||
},
|
||||
{
|
||||
key: '火山', color: '#FA541C'
|
||||
},
|
||||
{
|
||||
key: '日暮', color: '#FAAD14'
|
||||
},
|
||||
{
|
||||
key: '明青', color: '#13C2C2'
|
||||
},
|
||||
{
|
||||
key: '极光绿', color: '#52C41A'
|
||||
},
|
||||
{
|
||||
key: '拂晓蓝(默认)', color: '#1890FF'
|
||||
},
|
||||
{
|
||||
key: '极客蓝', color: '#2F54EB'
|
||||
},
|
||||
{
|
||||
key: '酱紫', color: '#722ED1'
|
||||
}
|
||||
]
|
||||
|
||||
const updateTheme = newPrimaryColor => {
|
||||
const hideMessage = message.loading('正在切换主题!', 0)
|
||||
themeColor.changeColor(newPrimaryColor).finally(t => {
|
||||
hideMessage()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
const updateTheme = primaryColor => {
|
||||
// Don't compile less in production!
|
||||
/* if (process.env.NODE_ENV === 'production') {
|
||||
return;
|
||||
} * /
|
||||
// Determine if the component is remounted
|
||||
if (!primaryColor) {
|
||||
return
|
||||
}
|
||||
const hideMessage = message.loading('正在编译主题!', 0)
|
||||
function buildIt () {
|
||||
if (!window.less) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.less
|
||||
.modifyVars({
|
||||
'@primary-color': primaryColor
|
||||
})
|
||||
.then(() => {
|
||||
hideMessage()
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('Failed to update theme')
|
||||
hideMessage()
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
if (!lessNodesAppended) {
|
||||
// insert less.js and color.less
|
||||
const lessStyleNode = document.createElement('link')
|
||||
const lessConfigNode = document.createElement('script')
|
||||
const lessScriptNode = document.createElement('script')
|
||||
lessStyleNode.setAttribute('rel', 'stylesheet/less')
|
||||
lessStyleNode.setAttribute('href', '/color.less')
|
||||
lessConfigNode.innerHTML = `
|
||||
window.less = {
|
||||
async: true,
|
||||
env: 'production',
|
||||
javascriptEnabled: true
|
||||
};
|
||||
`
|
||||
lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js'
|
||||
lessScriptNode.async = true
|
||||
lessScriptNode.onload = () => {
|
||||
buildIt()
|
||||
lessScriptNode.onload = null
|
||||
}
|
||||
document.body.appendChild(lessStyleNode)
|
||||
document.body.appendChild(lessConfigNode)
|
||||
document.body.appendChild(lessScriptNode)
|
||||
lessNodesAppended = true
|
||||
} else {
|
||||
buildIt()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const updateColorWeak = colorWeak => {
|
||||
// document.body.className = colorWeak ? 'colorWeak' : '';
|
||||
colorWeak ? document.body.classList.add('colorWeak') : document.body.classList.remove('colorWeak')
|
||||
}
|
||||
|
||||
export { updateTheme, colorList, updateColorWeak }
|
@@ -1,23 +0,0 @@
|
||||
import client from 'webpack-theme-color-replacer/client'
|
||||
import generate from '@ant-design/colors/lib/generate'
|
||||
|
||||
export default {
|
||||
getAntdSerials (color) {
|
||||
// 淡化(即less的tint)
|
||||
const lightens = new Array(9).fill().map((t, i) => {
|
||||
return client.varyColor.lighten(color, i / 10)
|
||||
})
|
||||
// colorPalette变换得到颜色值
|
||||
const colorPalettes = generate(color)
|
||||
return lightens.concat(colorPalettes)
|
||||
},
|
||||
changeColor (newColor) {
|
||||
var options = {
|
||||
newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
|
||||
changeUrl (cssUrl) {
|
||||
return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
|
||||
}
|
||||
}
|
||||
return client.changer.changeColor(options, Promise)
|
||||
}
|
||||
}
|
@@ -130,7 +130,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
// press the slide button
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
document.addEventListener('mouseup', this.handleMouseUp)
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 按下滑动器后移动鼠标
|
||||
// move the mouse after pressing the slide button
|
||||
handleMouseMove(e) {
|
||||
this.isExpanded = false
|
||||
this.$emit('expand', this.isExpanded)
|
||||
@@ -168,7 +168,7 @@ export default {
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
// release the slide button
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 清理空值,对象
|
||||
* clear null objects in children
|
||||
* @param children
|
||||
* @returns {*[]}
|
||||
*/
|
||||
@@ -12,7 +12,8 @@ export function filterEmpty (children = []) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串长度,英文字符 长度1,中文字符长度2
|
||||
* get string length
|
||||
* English character: length 1, Chinese character: length 2
|
||||
* @param {*} str
|
||||
*/
|
||||
export const getStrFullLength = (str = '') =>
|
||||
@@ -25,7 +26,7 @@ export const getStrFullLength = (str = '') =>
|
||||
}, 0)
|
||||
|
||||
/**
|
||||
* 截取字符串,根据 maxLength 截取后返回
|
||||
* cut the string based on maxLength
|
||||
* @param {*} str
|
||||
* @param {*} maxLength
|
||||
*/
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const appConfig = {
|
||||
buildModules: ['oneterm', 'acl'], // 需要编译的模块
|
||||
redirectTo: '/oneterm', // 首页的重定向路径
|
||||
buildAclToModules: true, // 是否在各个应用下 内联权限管理
|
||||
showDocs: false,
|
||||
useEncryption: false,
|
||||
buildModules: ['oneterm', 'acl'], // Modules to be compiled
|
||||
redirectTo: '/oneterm', // Redirect path for the home page
|
||||
buildAclToModules: true, // Inline permission management in each app
|
||||
showDocs: false,
|
||||
useEncryption: false,
|
||||
}
|
||||
|
||||
export default appConfig
|
||||
|
@@ -1,16 +1,15 @@
|
||||
/**
|
||||
* 项目默认配置项
|
||||
* primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage
|
||||
* navTheme - sidebar theme ['dark', 'light'] 两种主题
|
||||
* colorWeak - 色盲模式
|
||||
* layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局
|
||||
* fixedHeader - 固定 Header : boolean
|
||||
* fixSiderbar - 固定左侧菜单栏 : boolean
|
||||
* autoHideHeader - 向下滚动时,隐藏 Header : boolean
|
||||
* contentWidth - 内容区布局: 流式 | 固定
|
||||
*
|
||||
* storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage)
|
||||
* Project default configuration
|
||||
* primaryColor - Default theme color. If color change does not take effect, please clear localStorage.
|
||||
* navTheme - Sidebar theme ['dark', 'light']
|
||||
* colorWeak - Color blindness mode
|
||||
* layout - Overall layout ['sidemenu', 'topmenu']
|
||||
* fixedHeader - Sticky Header : boolean
|
||||
* fixSiderbar - Sticky sidebar : boolean
|
||||
* autoHideHeader - Hide Header when scrolling down : boolean
|
||||
* contentWidth - Content area layout: Fluid | Fixed
|
||||
*
|
||||
* storageOptions: {} - Vue-ls plugin options (localStorage/sessionStorage)
|
||||
*/
|
||||
|
||||
export default {
|
||||
|
@@ -2,15 +2,15 @@ import Vue from 'vue'
|
||||
import store from '@/store'
|
||||
|
||||
/**
|
||||
* Action 权限指令
|
||||
* 指令用法:
|
||||
* - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下:
|
||||
* <i-button v-action:add >添加用户</a-button>
|
||||
* <a-button v-action:delete>删除用户</a-button>
|
||||
* <a v-action:edit @click="edit(record)">修改</a>
|
||||
* Action permission directive
|
||||
* Usage:
|
||||
* - Use v-action:[method] on components that require action-level permission control, e.g.:
|
||||
* <i-button v-action:add>Add User</i-button>
|
||||
* <a-button v-action:delete>Delete User</a-button>
|
||||
* <a v-action:edit @click="edit(record)">Edit</a>
|
||||
*
|
||||
* - 当前用户没有权限时,组件上使用了该指令则会被隐藏
|
||||
* - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可
|
||||
* - If the current user does not have permission, the component using this directive will be hidden.
|
||||
* - If the backend permission model is different from the pro model, just modify the permission filtering logic here.
|
||||
*
|
||||
* @see https://github.com/sendya/ant-design-pro-vue/pull/53
|
||||
*/
|
||||
|
@@ -1,10 +1,8 @@
|
||||
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* 该文件是为了按需加载,剔除掉了一些不需要的框架组件。
|
||||
* 减少了编译支持库包大小
|
||||
* This file is for on-demand loading, removing unnecessary framework components.
|
||||
* This reduces the size of the compiled support library.
|
||||
*
|
||||
* 当需要更多组件依赖时,在该文件加入即可
|
||||
* Add more component dependencies here as needed.
|
||||
*/
|
||||
import Vue from 'vue'
|
||||
import {
|
||||
@@ -98,4 +96,4 @@ Vue.prototype.$notification = notification
|
||||
Vue.prototype.$info = Modal.info
|
||||
Vue.prototype.$success = Modal.success
|
||||
Vue.prototype.$error = Modal.error
|
||||
Vue.prototype.$warning = Modal.warning
|
||||
Vue.prototype.$warning = Modal.warning
|
||||
|
@@ -2,26 +2,17 @@ import Vue from 'vue'
|
||||
import VueStorage from 'vue-ls'
|
||||
import config from '@/config/setting'
|
||||
|
||||
// base library
|
||||
// Base libraries
|
||||
import Antd from 'ant-design-vue'
|
||||
import Viser from 'viser-vue'
|
||||
import VueCropper from 'vue-cropper'
|
||||
/* eslint-disable */
|
||||
// import 'ant-design-vue/dist/antd.less'
|
||||
import '../style/index.less'
|
||||
// import 'element-ui/lib/theme-chalk/select.css';
|
||||
// import 'element-ui/lib/theme-chalk/time-picker.css';
|
||||
// import 'element-ui/lib/theme-chalk/icon.css';
|
||||
// import 'element-ui/lib/theme-chalk/date-picker.css';
|
||||
// import 'element-ui/lib/theme-chalk/button.css';
|
||||
// import 'element-ui/lib/theme-chalk/autocomplete.css';
|
||||
// import 'element-ui/lib/theme-chalk/time-select.css';
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
|
||||
// ext library
|
||||
|
||||
// External libraries
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import PermissionHelper from '@/utils/helper/permission'
|
||||
// import '@/components/use'
|
||||
import './directives/action'
|
||||
|
||||
import { VueAxios } from '../utils/request'
|
||||
@@ -44,6 +35,7 @@ import i18n from '@/lang'
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$bus = EventBus
|
||||
|
||||
// Setup VXETable with i18n
|
||||
VXETable.setup({
|
||||
i18n: (key, args) => i18n.t(key, args)
|
||||
})
|
||||
@@ -57,7 +49,7 @@ Vue.prototype.$bus = EventBus
|
||||
|
||||
Vue.use(VueAxios)
|
||||
Vue.use(infiniteScroll)
|
||||
Vue.use(ElementUI);
|
||||
Vue.use(ElementUI)
|
||||
|
||||
VueClipboard.config.autoSetContainer = true
|
||||
|
||||
@@ -78,6 +70,7 @@ Vue.use(VueStorage, config.storageOptions)
|
||||
Vue.use(VueClipboard)
|
||||
Vue.use(PermissionHelper)
|
||||
Vue.use(VueCropper)
|
||||
|
||||
Vue.component('CustomDrawer', CustomDrawer)
|
||||
Vue.component('CustomTransfer', CustomTransfer)
|
||||
Vue.component('CustomRadio', CustomRadio)
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
import Vue from 'vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
@@ -11,17 +10,24 @@ import i18n from '@/lang'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
// 不用认证的页面
|
||||
const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/login', '/api/sso/logout', '/user/forgetPassword']
|
||||
// pages that do not require authentication
|
||||
const whitePath = [
|
||||
'/user/login',
|
||||
'/user/logout',
|
||||
'/user/register',
|
||||
'/api/sso/login',
|
||||
'/api/sso/logout',
|
||||
'/user/forgetPassword'
|
||||
]
|
||||
const startsMathWhitePath = ['/oneterm/share']
|
||||
|
||||
// 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录
|
||||
// 登录页面处理处理 是否使用单点登录
|
||||
// Only handle user info authentication here, not login logic.
|
||||
// Frontend permission handling; axios handles 401 -> login.
|
||||
// Login page handles whether to use SSO.
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start() // start progress bar
|
||||
to.meta && (!!to.meta.title && setDocumentTitle(`${i18n.t(to.meta.title)} - ${domTitle}`))
|
||||
|
||||
const authed = store.state.authed
|
||||
const auth_type = localStorage.getItem('ops_auth_type')
|
||||
if (
|
||||
whitePath.includes(to.path) ||
|
||||
@@ -32,17 +38,17 @@ router.beforeEach(async (to, from, next) => {
|
||||
store.dispatch('GetAuthDataEnable')
|
||||
store.dispatch('GetInfo').then(res => {
|
||||
const roles = res.result && res.result.role
|
||||
store.dispatch("loadAllUsers")
|
||||
store.dispatch("loadAllEmployees")
|
||||
store.dispatch("loadAllDepartments")
|
||||
store.dispatch('loadAllUsers')
|
||||
store.dispatch('loadAllEmployees')
|
||||
store.dispatch('loadAllDepartments')
|
||||
store.dispatch('GenerateRoutes', { roles }).then(() => {
|
||||
router.addRoutes(store.getters.appRoutes)
|
||||
const redirect = decodeURIComponent(from.query.redirect || to.path)
|
||||
if (to.path === redirect) {
|
||||
// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
|
||||
// Ensure addRoutes is complete, set replace: true so navigation will not leave a history record
|
||||
next({ ...to, replace: true })
|
||||
} else {
|
||||
// 跳转到目的路由
|
||||
// Redirect to the target route
|
||||
next({ path: redirect })
|
||||
}
|
||||
})
|
||||
|
@@ -7,12 +7,12 @@ import enUS from 'vxe-table/lib/locale/lang/en-US'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
const i18n = new VueI18n({
|
||||
locale: 'zh', // 初始化中文
|
||||
messages: {
|
||||
'zh': { ...zh, ...zhCN },
|
||||
'en': { ...en, ...enUS },
|
||||
},
|
||||
silentTranslationWarn: true
|
||||
locale: 'zh',
|
||||
messages: {
|
||||
'zh': { ...zh, ...zhCN },
|
||||
'en': { ...en, ...enUS },
|
||||
},
|
||||
silentTranslationWarn: true
|
||||
})
|
||||
|
||||
export default i18n
|
||||
|
@@ -63,8 +63,6 @@ import RouteView from './RouteView'
|
||||
import MultiTab from '@/components/MultiTab'
|
||||
import SideMenu from '@/components/Menu/SideMenu'
|
||||
import GlobalHeader from '@/components/GlobalHeader'
|
||||
import GlobalFooter from '@/components/GlobalFooter'
|
||||
import SettingDrawer from '@/components/SettingDrawer'
|
||||
|
||||
export default {
|
||||
name: 'BasicLayout',
|
||||
@@ -74,8 +72,6 @@ export default {
|
||||
MultiTab,
|
||||
SideMenu,
|
||||
GlobalHeader,
|
||||
GlobalFooter,
|
||||
SettingDrawer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -86,7 +82,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
// 动态主路由
|
||||
// dynamic main route
|
||||
mainMenu: (state) => state.routes.appRoutes,
|
||||
}),
|
||||
contentPaddingLeft() {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
import '@babel/polyfill'
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
@@ -14,10 +13,9 @@ import i18n from './lang'
|
||||
|
||||
import iconFont from '../public/iconfont/iconfont'
|
||||
|
||||
// 存在直接crash的风险 还未到
|
||||
const customIcon = Icon.createFromIconfontCN(iconFont)
|
||||
Vue.component('ops-icon', customIcon)
|
||||
var vue;
|
||||
var vue
|
||||
|
||||
async function start() {
|
||||
const _vue = new Vue({
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
@@ -13,8 +12,7 @@ export function currentUser() {
|
||||
export function getOnDutyUser() {
|
||||
return axios({
|
||||
url: urlPrefix + '/users/employee',
|
||||
method: 'GET',
|
||||
// data: { 'originUrl': 'http://hr.dfc.sh/api/all_users?work_status=在职' }
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -211,7 +211,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
|
@@ -44,9 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import { addResource, searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { addResourceGroup } from '@/modules/acl/api/resource'
|
||||
import { addResource, searchResourceType, addResourceGroup } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
|
@@ -151,7 +151,7 @@ export default {
|
||||
const tunnel = new Guacamole.WebSocketTunnel(socketLink)
|
||||
const client = new Guacamole.Client(tunnel)
|
||||
|
||||
// 处理从虚拟机收到的剪贴板内容
|
||||
// clipboard contents received by remote desktop
|
||||
client.onclipboard = this.handleClipboardReceived
|
||||
|
||||
if (this?.controlConfig?.[`${queryProtocol?.split?.(':')?.[0]}_config`]?.copy) {
|
||||
@@ -260,8 +260,6 @@ export default {
|
||||
this.$message.destroy(this.messageKey)
|
||||
this.$message.success({ content: this.$t('oneterm.guacamole.connected'), duration: 3, key: this.messageKey })
|
||||
this.$emit('open')
|
||||
// 向后台发送请求,更新会话的状态
|
||||
// sessionApi.connect(sessionId)
|
||||
break
|
||||
case STATE_DISCONNECTING:
|
||||
break
|
||||
|
@@ -278,8 +278,8 @@ export default {
|
||||
const dragTab = document.getElementById('workstation-drag-tab')?.querySelector?.('.ant-tabs-nav')?.firstChild
|
||||
if (dragTab) {
|
||||
this.sortableInstance = Sortable.create(dragTab, {
|
||||
handle: '.ant-tabs-tab', // 标签选择器
|
||||
draggable: '.ant-tabs-tab:not(:first-child)', // 可拖动的标签选择器
|
||||
handle: '.ant-tabs-tab', // css selector
|
||||
draggable: '.ant-tabs-tab:not(:first-child)', // draggable css selector
|
||||
onEnd: this.handleSortEnd
|
||||
})
|
||||
}
|
||||
|
@@ -113,7 +113,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixinPermissions } from '@/utils/mixin'
|
||||
import { WORKSTATION_TAB_TYPE } from '@/modules/oneterm/views/workStation/constants.js'
|
||||
import { OPERATION_MENU_TYPE } from './constants.js'
|
||||
|
||||
@@ -121,7 +120,6 @@ import ChooseAssetsModal from '../batchExecution/chooseAssetsModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'OperationMenu',
|
||||
mixins: [mixinPermissions],
|
||||
components: {
|
||||
ChooseAssetsModal
|
||||
},
|
||||
@@ -160,23 +158,16 @@ export default {
|
||||
const controlDisplayList = [
|
||||
OPERATION_MENU_TYPE.FULL_SCREEN,
|
||||
OPERATION_MENU_TYPE.RECENT_SESSION,
|
||||
OPERATION_MENU_TYPE.BATCH_EXECUTION
|
||||
OPERATION_MENU_TYPE.BATCH_EXECUTION,
|
||||
OPERATION_MENU_TYPE.DISPLAY_SETTING,
|
||||
OPERATION_MENU_TYPE.THEME_SETTING
|
||||
]
|
||||
|
||||
const showTerminalSetting = this.hasDetailPermission('oneterm', 'System_Config', ['terminal_show'])
|
||||
if (showTerminalSetting) {
|
||||
controlDisplayList.push(
|
||||
OPERATION_MENU_TYPE.DISPLAY_SETTING,
|
||||
OPERATION_MENU_TYPE.THEME_SETTING
|
||||
)
|
||||
|
||||
if (this.isGuacamole) {
|
||||
controlDisplayList.push(OPERATION_MENU_TYPE.RESOLUTION)
|
||||
}
|
||||
if (this.isGuacamole) {
|
||||
controlDisplayList.push(OPERATION_MENU_TYPE.RESOLUTION)
|
||||
}
|
||||
|
||||
const showQuickCommand = this.hasDetailPermission('oneterm', 'System_Config', ['quick_command'])
|
||||
if (this.isTerminal && showQuickCommand) {
|
||||
if (this.isTerminal) {
|
||||
controlDisplayList.push(OPERATION_MENU_TYPE.QUICK_COMMAND)
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
import { UserLayout, BasicLayout, RouteView } from '@/layouts'
|
||||
import appConfig from '@/config/app'
|
||||
import { getAppAclRouter } from './utils'
|
||||
@@ -7,7 +6,7 @@ import store from '../store'
|
||||
export const generatorDynamicRouter = async () => {
|
||||
const packages = []
|
||||
const { apps = undefined } = store.getters.userInfo
|
||||
for (let appName of appConfig.buildModules) {
|
||||
for (const appName of appConfig.buildModules) {
|
||||
if (!apps || !apps.length || apps.includes(appName)) {
|
||||
const module = await import(`@/modules/${appName}/index.js`)
|
||||
const r = await module.default.route()
|
||||
@@ -28,19 +27,6 @@ export const generatorDynamicRouter = async () => {
|
||||
let routes = packages
|
||||
routes = routes.concat([
|
||||
{ path: '*', redirect: '/404', hidden: true },
|
||||
{
|
||||
hidden: true,
|
||||
path: '/noticecenter',
|
||||
name: 'notice_center',
|
||||
component: BasicLayout,
|
||||
children: [{
|
||||
hidden: true,
|
||||
path: '/noticecenter',
|
||||
name: 'notice_center',
|
||||
meta: { title: '消息中心' },
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/noticeCenter/index')
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
component: BasicLayout,
|
||||
@@ -91,11 +77,11 @@ export const generatorDynamicRouter = async () => {
|
||||
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/auth/index')
|
||||
},
|
||||
]
|
||||
},])
|
||||
}, ])
|
||||
return routes
|
||||
}
|
||||
|
||||
// 基础模块路由,根据当前 app config 配置添加
|
||||
// basic route (module based), added according to app config configuration
|
||||
const constantModuleRouteMap = []
|
||||
if (appConfig.buildModules.includes('oneterm')) {
|
||||
constantModuleRouteMap.push({
|
||||
@@ -103,12 +89,12 @@ if (appConfig.buildModules.includes('oneterm')) {
|
||||
name: 'oneterm_share',
|
||||
hidden: true,
|
||||
component: () => import('@/modules/oneterm/views/share'),
|
||||
meta: { title: '分享', keepAlive: false }
|
||||
meta: { title: 'oneterm.menu.share', keepAlive: false }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础路由
|
||||
* basic route
|
||||
*/
|
||||
export const constantRouterMap = [
|
||||
{
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import { constantRouterMap } from '@/router/config'
|
||||
|
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import app from './global/app'
|
||||
@@ -53,11 +51,11 @@ const store = new Vuex.Store({
|
||||
})
|
||||
|
||||
appConfig.buildModules.forEach(appName => {
|
||||
import(`@/modules/${appName}/index.js`).then(m => {
|
||||
if (m.default.store) {
|
||||
store.registerModule(m.default.store.name || m.deault.name, m.default.store)
|
||||
}
|
||||
})
|
||||
import(`@/modules/${appName}/index.js`).then(m => {
|
||||
if (m.default.store) {
|
||||
store.registerModule(m.default.store.name || m.deault.name, m.default.store)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default store
|
||||
|
@@ -1,10 +1,8 @@
|
||||
/* eslint-disable */
|
||||
export function intersection(thisSet, otherSet) {
|
||||
//初始化一个新集合,用于表示交集。
|
||||
// 初始化一个新集合,用于表示交集。
|
||||
var interSectionSet = new Set()
|
||||
var values = Array.from(thisSet)
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
|
||||
if (otherSet.has(values[i])) {
|
||||
interSectionSet.add(values[i])
|
||||
}
|
||||
@@ -20,8 +18,8 @@ export function union(thisSet, otherSet) {
|
||||
unionSet.add(values[i])
|
||||
}
|
||||
values = Array.from(otherSet)
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
unionSet.add(values[i])
|
||||
for (var j = 0; j < values.length; j++) {
|
||||
unionSet.add(values[j])
|
||||
}
|
||||
|
||||
return unionSet
|
||||
@@ -31,7 +29,6 @@ export function difference(thisSet, otherSet) {
|
||||
var differenceSet = new Set()
|
||||
var values = Array.from(thisSet)
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
|
||||
if (!otherSet.has(values[i])) {
|
||||
differenceSet.add(values[i])
|
||||
}
|
||||
|
@@ -11,7 +11,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
@@ -11,7 +11,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
@@ -11,7 +11,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
@@ -1,360 +0,0 @@
|
||||
<template>
|
||||
<TwoColumnLayout appName="notice-center">
|
||||
<template #one>
|
||||
<div
|
||||
:class="{ 'notice-center-left': true, 'notice-center-left-select': current.value === item.value }"
|
||||
v-for="item in leftList"
|
||||
:key="item.label"
|
||||
@click="
|
||||
() => {
|
||||
current = item
|
||||
selectedRowKeys = []
|
||||
$refs.opsTable.getVxetableRef().clearCheckboxRow()
|
||||
$refs.opsTable.getVxetableRef().clearCheckboxReserve()
|
||||
updateTableData()
|
||||
}
|
||||
"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
<span v-if="item.value === false">{{ totalUnreadNum > 99 ? '99+' : totalUnreadNum }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #two>
|
||||
<div class="notice-center-header">
|
||||
<div>
|
||||
<a-badge
|
||||
v-for="app in apps"
|
||||
:key="app.value"
|
||||
:count="getAppCount(app)"
|
||||
:offset="[-4, 5]"
|
||||
:numberStyle="{
|
||||
minWidth: '14px',
|
||||
height: '14px',
|
||||
lineHeight: '14px',
|
||||
borderRadius: '7px',
|
||||
padding: ' 0 4px',
|
||||
}"
|
||||
:class="{ 'notice-center-header-app': true, 'notice-center-header-app-selected': currentApp === app.value }"
|
||||
>
|
||||
<span @click="changeApp(app)">{{ app.label }}</span>
|
||||
</a-badge>
|
||||
</div>
|
||||
<div class="notice-center-categories">
|
||||
<span
|
||||
:class="{ 'notice-center-categories-selected': currentCategory === cate }"
|
||||
v-for="cate in categories"
|
||||
:key="cate"
|
||||
@click="changeCate(cate)"
|
||||
>{{ cate }}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model="filterName"
|
||||
class="ops-input-radius"
|
||||
:style="{ width: '300px', marginRight: '20px' }"
|
||||
placeholder="请输入你需要搜索的内容"
|
||||
@search="updateTableData()"
|
||||
/>
|
||||
<div class="ops-list-batch-action">
|
||||
<template v-if="!!selectedRowKeys.length">
|
||||
<span @click="batchChangeIsRead('read')">标为已读</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchChangeIsRead('unread')">标为未读</span>
|
||||
<span>选取: {{ selectedRowKeys.length }} 项</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OpsTable
|
||||
size="small"
|
||||
ref="opsTable"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
:row-class-name="rowClassName"
|
||||
:checkbox-config="{ reserve: true }"
|
||||
:row-config="{ keyField: 'id' }"
|
||||
:height="tableHeight"
|
||||
>
|
||||
<vxe-column type="checkbox" width="60px"></vxe-column>
|
||||
<vxe-column field="content" title="标题内容">
|
||||
<template #default="{row}">
|
||||
<span v-html="row.content"></span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="created_at" title="提交时间" width="150px">
|
||||
<template #default="{row}">
|
||||
{{ moment(row.created_at).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="category" title="类型" width="150px">
|
||||
<template #default="{row}">
|
||||
{{ `${row.app_name}-${row.category}` }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
</OpsTable>
|
||||
<div class="notice-center-pagination">
|
||||
<a-pagination
|
||||
size="small"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.totalResult"
|
||||
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</TwoColumnLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { mapState } from 'vuex'
|
||||
import TwoColumnLayout from '@/components/TwoColumnLayout'
|
||||
import { getMessage, getNoticeApps, getNoticeCategoriesByApp, batchUpdateMessage } from '../../api/message'
|
||||
import Bus from '../../bus'
|
||||
|
||||
export default {
|
||||
name: 'Notice',
|
||||
components: {
|
||||
TwoColumnLayout,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
leftList: [
|
||||
{
|
||||
value: '',
|
||||
label: '全部消息',
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: '未读消息',
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
label: '已读消息',
|
||||
},
|
||||
],
|
||||
current: {
|
||||
value: '',
|
||||
label: '全部消息',
|
||||
},
|
||||
tablePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
totalResult: 0,
|
||||
},
|
||||
filterName: '',
|
||||
tableData: [],
|
||||
apps: [],
|
||||
interval: null,
|
||||
currentApp: '',
|
||||
categories: [],
|
||||
currentCategory: undefined,
|
||||
selectedRowKeys: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
totalUnreadNum: (state) => state.notice.totalUnreadNum,
|
||||
appUnreadNum: (state) => state.notice.appUnreadNum,
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
tableHeight() {
|
||||
if (this.categories.length) {
|
||||
return this.windowHeight - 236
|
||||
}
|
||||
return this.windowHeight - 205
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.intervalFunc()
|
||||
this.interval = setInterval(() => {
|
||||
this.intervalFunc()
|
||||
}, 30000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.interval)
|
||||
this.interval = null
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
intervalFunc() {
|
||||
this.getNoticeApps()
|
||||
this.getNoticeCategoriesByApp()
|
||||
this.updateTableData(this.tablePage.currentPage, this.tablePage.pageSize)
|
||||
},
|
||||
getNoticeApps() {
|
||||
getNoticeApps().then((res) => {
|
||||
const _apps = res.app_names.map((appName) => ({
|
||||
value: appName,
|
||||
label: appName,
|
||||
}))
|
||||
if (_apps.length) {
|
||||
_apps.unshift({
|
||||
value: '',
|
||||
label: '全部',
|
||||
})
|
||||
}
|
||||
this.apps = _apps
|
||||
})
|
||||
},
|
||||
getNoticeCategoriesByApp() {
|
||||
if (this.currentApp) {
|
||||
getNoticeCategoriesByApp(this.currentApp).then((res) => {
|
||||
this.categories = res.categories
|
||||
})
|
||||
} else {
|
||||
this.categories = []
|
||||
}
|
||||
},
|
||||
updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) {
|
||||
getMessage({
|
||||
is_read: this.current.value,
|
||||
app_name: this.currentApp,
|
||||
category: this.currentCategory,
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
order: this.tableSortData || '-create_at',
|
||||
search: this.filterName,
|
||||
}).then((res) => {
|
||||
this.tableData = res?.data_list || []
|
||||
this.tablePage = {
|
||||
...this.tablePage,
|
||||
currentPage: res.page,
|
||||
pageSize: res.page_size,
|
||||
totalResult: res.total,
|
||||
}
|
||||
})
|
||||
},
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.updateTableData(currentPage, pageSize)
|
||||
},
|
||||
onSelectChange({ records }) {
|
||||
this.selectedRowKeys = records
|
||||
},
|
||||
getAppCount(app) {
|
||||
if (app.value) {
|
||||
const _find = this.appUnreadNum.find((item) => item.app_name === app.value)
|
||||
return _find?.count ?? 0
|
||||
}
|
||||
return this.totalUnreadNum
|
||||
},
|
||||
changeApp(app) {
|
||||
this.currentApp = app.value
|
||||
this.currentCategory = undefined
|
||||
this.filterName = ''
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.opsTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.opsTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.getNoticeCategoriesByApp()
|
||||
this.updateTableData()
|
||||
},
|
||||
changeCate(cate) {
|
||||
this.filterName = ''
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.opsTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.opsTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.currentCategory = cate
|
||||
this.updateTableData()
|
||||
},
|
||||
batchChangeIsRead(action) {
|
||||
batchUpdateMessage({ action, message_id_list: this.selectedRowKeys.map((item) => item.id) }).then((res) => {
|
||||
this.updateTableData()
|
||||
Bus.$emit('getUnreadMessageCount')
|
||||
})
|
||||
},
|
||||
rowClassName({ row, rowIndex, $rowIndex }) {
|
||||
if (row.is_read) {
|
||||
return 'notice-center-is_read'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.notice-center-left {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
padding: 0 12px 0 24px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-left: 3px solid #fff;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> span:nth-child(2) {
|
||||
background-color: #e1efff;
|
||||
border-radius: 2px;
|
||||
color: #9094a6;
|
||||
padding: 0 4px;
|
||||
font-size: 12px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
.notice-center-left:hover,
|
||||
.notice-center-left-select {
|
||||
background-color: #f0f5ff;
|
||||
border-color: @primary-color;
|
||||
> span:nth-child(2) {
|
||||
background-color: #fff;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.notice-center-header {
|
||||
> div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.notice-center-header-app {
|
||||
border-radius: 16px;
|
||||
background-color: #f0f5ff;
|
||||
color: #9094a6;
|
||||
padding: 5px 14px;
|
||||
cursor: pointer;
|
||||
margin-right: 12px;
|
||||
}
|
||||
> .notice-center-header-app:hover,
|
||||
.notice-center-header-app-selected {
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
.notice-center-categories {
|
||||
> span {
|
||||
color: #a5a9bc;
|
||||
padding: 4px 18px;
|
||||
background-color: #f0f5ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
> span:hover,
|
||||
.notice-center-categories-selected {
|
||||
color: #fff;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.notice-center-pagination {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.notice-center-is_read {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
@@ -191,7 +191,8 @@ export default {
|
||||
},
|
||||
loginSuccess(res) {
|
||||
this.$router.push({ path: this.$route.query?.redirect ?? '/' })
|
||||
// 延迟 1 秒显示欢迎信息
|
||||
|
||||
// delayed welcome message
|
||||
setTimeout(() => {
|
||||
this.$notification.success({
|
||||
message: this.$t('cs.login.welcomeMessage'),
|
||||
|
@@ -9,17 +9,16 @@ function resolve(dir) {
|
||||
|
||||
// vue.config.js
|
||||
module.exports = {
|
||||
// runtimeCompiler: true,
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
// Ignore all locale files of moment.js
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
// 生成仅包含颜色的替换样式(主题色等)
|
||||
// TODO 需要增加根据环境不开启主题需求
|
||||
// generate theme color replacement styles
|
||||
new ThemeColorReplacer({
|
||||
fileName: 'css/theme-colors-[contenthash:8].css',
|
||||
matchColors: getAntdSerials('#2f54eb'), // 主色系列
|
||||
// 改变样式选择器,解决样式覆盖问题
|
||||
matchColors: getAntdSerials('#2f54eb'), // primary color series
|
||||
|
||||
// change style selectors to solve style override issues
|
||||
changeSelector(selector) {
|
||||
switch (selector) {
|
||||
case '.ant-calendar-today .ant-calendar-date':
|
||||
@@ -64,29 +63,14 @@ module.exports = {
|
||||
.options({
|
||||
name: 'assets/[name].[hash:8].[ext]'
|
||||
})
|
||||
/* svgRule.oneOf('inline')
|
||||
.resourceQuery(/inline/)
|
||||
.use('vue-svg-loader')
|
||||
.loader('vue-svg-loader')
|
||||
.end()
|
||||
.end()
|
||||
.oneOf('external')
|
||||
.use('file-loader')
|
||||
.loader('file-loader')
|
||||
.options({
|
||||
name: 'assets/[name].[hash:8].[ext]'
|
||||
})
|
||||
*/
|
||||
},
|
||||
|
||||
css: {
|
||||
loaderOptions: {
|
||||
less: {
|
||||
modifyVars: {
|
||||
/* less 变量覆盖,用于自定义 ant design 主题 */
|
||||
// override less variables for custom ant design theme
|
||||
'primary-color': '#2f54eb',
|
||||
// 'link-color': '#F5222D',
|
||||
// 'border-radius-base': '4px',
|
||||
},
|
||||
javascriptEnabled: true
|
||||
}
|
||||
@@ -111,7 +95,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
function getAntdSerials(color) {
|
||||
// 淡化(即less的tint)
|
||||
// Lighten (similar to less's tint)
|
||||
const lightens = new Array(9).fill().map((t, i) => {
|
||||
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
|
||||
})
|
||||
|
Reference in New Issue
Block a user