feat(ui): remove unused code and unused files

This commit is contained in:
LH_R
2025-06-23 16:23:55 +08:00
parent e746c99a70
commit cf054fc439
50 changed files with 238 additions and 1290 deletions

View File

@@ -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',

View File

@@ -2,7 +2,7 @@ const ThemeColorReplacer = require('webpack-theme-color-replacer')
const generate = require('@ant-design/colors/lib/generate').default
const getAntdSerials = (color) => {
// 淡化(即lesstint
// 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':

View File

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -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)
}
},
}

View File

@@ -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') },
]
}

View File

@@ -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') },
]
}

View File

@@ -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 = ''

View File

@@ -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

View File

@@ -1,9 +1,6 @@
/* eslint-disable */
/*
!!!!!!!
以下为凶残的cron表达式验证胆小肾虚及心脏病者慎入!!!
不听劝告者后果自负T T
!!!!!!!
cron表达式验证
cron表达式为秒
判断正误方法错误的话返回错误信息正确的话返回true
*/

View File

@@ -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')

View File

@@ -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>

View File

@@ -1,2 +0,0 @@
import GlobalFooter from './GlobalFooter'
export default GlobalFooter

View File

@@ -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,

View File

@@ -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>)
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,2 +0,0 @@
import SettingDrawer from './SettingDrawer'
export default SettingDrawer

View File

@@ -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 }

View File

@@ -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)
}
}

View File

@@ -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)
},

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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)

View File

@@ -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 })
}
})

View File

@@ -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

View File

@@ -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() {

View File

@@ -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({

View File

@@ -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'
})
}

View File

@@ -211,7 +211,6 @@ export default {
},
},
methods: {
// 获取数据
async getTable(queryParams) {
try {
this.loading = true

View File

@@ -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',

View File

@@ -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

View File

@@ -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
})
}

View File

@@ -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)
}

View File

@@ -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 = [
{

View File

@@ -1,4 +1,3 @@
/* eslint-disable */
import Vue from 'vue'
import Router from 'vue-router'
import { constantRouterMap } from '@/router/config'

View File

@@ -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

View File

@@ -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])
}

View File

@@ -11,7 +11,3 @@ export default {
}
}
</script>
<style scoped>
</style>

View File

@@ -11,7 +11,3 @@ export default {
}
}
</script>
<style scoped>
</style>

View File

@@ -11,7 +11,3 @@ export default {
}
}
</script>
<style scoped>
</style>

View File

@@ -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>

View File

@@ -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'),

View File

@@ -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) {
// 淡化(即lesstint
// Lighten (similar to less's tint)
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
})