feature: 新增 MockInput 组件,用于部分列表配置页面,目的是为了SearchBar能够搜索到输入框中的内容。

develop
王良 2025-02-21 10:59:56 +08:00
parent aaf6ca8e9f
commit bd185c3aa5
11 changed files with 191 additions and 66 deletions

View File

@ -1,15 +1,11 @@
<script> <script>
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
import { SearchBar } from 'search-bar-vue2'
import createMenus from '@/view/router/menu' import createMenus from '@/view/router/menu'
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN' import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import { colorTheme } from './composables/theme' import { colorTheme } from './composables/theme'
export default { export default {
name: 'App', name: 'App',
components: {
SearchBar,
},
data () { data () {
return { return {
locale: zhCN, locale: zhCN,

View File

@ -23,16 +23,16 @@ export default {
<style lang="scss"> <style lang="scss">
.ds-container { .ds-container {
height: 100%; height: 100%;
background: #fff; background-color: #fff;
display: flex; display: flex;
position: relative; position: relative;
.body-wrapper { .body-wrapper {
position: absolute; position: absolute;
top: 0px; top: 0;
right: 0px; right: 0;
bottom: 0px; bottom: 0;
left: 0px; left: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
@ -41,7 +41,7 @@ export default {
.container-header { .container-header {
padding: 15px; padding: 15px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
background: #fff; background-color: #fff;
height: 60px; height: 60px;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -0,0 +1,65 @@
<!--
组件模拟输入框当前为简易版本只添加了value属性
作用全文检索SearchBar组件无法检索 `<a-input/>` 的内容所以使用 `<span contenteditable="true"></span>` 代替
-->
<script>
export default {
name: 'MockInput',
props: {
value: {
type: String,
default: '',
required: false,
},
},
methods: {
onKeydown (event) {
//
if (event.key === 'Enter' || event.keyCode === 13) {
event.preventDefault()
}
},
onChange () {
if (this.$refs.input.textContent !== this.value) {
this.$emit('input', this.$refs.input.textContent)
}
},
},
}
</script>
<template>
<span ref="input" class="fake-input" contenteditable="true" :title="value" @focus="onChange" @blur="onChange" @keydown="onKeydown" v-html="value" />
</template>
<style lang="scss">
.fake-input {
/* 鼠标样式 */
cursor: text;
/* 内容不换行 */
overflow-y: auto;
/*white-space: nowrap;
vertical-align: middle;*/
/* 复制 ant-input 样式 */
box-sizing: border-box;
margin: 0;
padding: 4px 11px;
font-variant: tabular-nums;
list-style: none;
font-feature-settings: 'tnum';
position: relative;
display: inline-block;
width: 100%;
height: 32px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5;
background-color: #fff;
background-image: none;
border: 1px solid #d9d9d9;
border-radius: 4px;
transition: all 0.3s;
}
</style>

View File

@ -142,5 +142,17 @@ export default {
const dir = await this.$api.info.getLogDir() const dir = await this.$api.info.getLogDir()
this.$api.ipc.openPath(dir) this.$api.ipc.openPath(dir)
}, },
handleHostname (hostname) {
if (this.isNotHostname(hostname)) {
return ''
}
// 移除所有空白符
return hostname.replaceAll(/\s+/g, '')
},
isNotHostname (hostname) {
// 暂时只判断数字
return !hostname || /^[\d\s]+$/.test(hostname)
},
}, },
} }

View File

@ -1,8 +1,10 @@
<script> <script>
import Plugin from '../../mixins/plugin' import Plugin from '../../mixins/plugin'
import MockInput from '@/view/components/mock-input.vue'
export default { export default {
name: 'Git', name: 'Git',
components: { MockInput },
mixins: [Plugin], mixins: [Plugin],
data () { data () {
return { return {
@ -45,7 +47,7 @@ export default {
} }
}, },
addNoProxyUrl () { addNoProxyUrl () {
this.noProxyUrls.unshift({ key: '', value: true }) this.noProxyUrls.unshift({ key: '' })
}, },
delNoProxyUrl (item, index) { delNoProxyUrl (item, index) {
this.noProxyUrls.splice(index, 1) this.noProxyUrls.splice(index, 1)
@ -54,7 +56,10 @@ export default {
const noProxyUrls = {} const noProxyUrls = {}
for (const item of this.noProxyUrls) { for (const item of this.noProxyUrls) {
if (item.key) { if (item.key) {
noProxyUrls[item.key] = item.value const hostname = this.handleHostname(item.key)
if (hostname) {
noProxyUrls[hostname] = true
}
} }
} }
this.config.plugin.git.setting.noProxyUrls = noProxyUrls this.config.plugin.git.setting.noProxyUrls = noProxyUrls
@ -103,7 +108,7 @@ export default {
</a-row> </a-row>
<a-row v-for="(item, index) of noProxyUrls" :key="index" :gutter="10"> <a-row v-for="(item, index) of noProxyUrls" :key="index" :gutter="10">
<a-col :span="22"> <a-col :span="22">
<a-input v-model="item.key" :disabled="item.value === false" /> <MockInput v-model="item.key" />
</a-col> </a-col>
<a-col :span="2"> <a-col :span="2">
<a-button type="danger" icon="minus" @click="delNoProxyUrl(item, index)" /> <a-button type="danger" icon="minus" @click="delNoProxyUrl(item, index)" />

View File

@ -1,8 +1,10 @@
<script> <script>
import Plugin from '../../mixins/plugin' import Plugin from '../../mixins/plugin'
import MockInput from '@/view/components/mock-input.vue'
export default { export default {
name: 'Overwall', name: 'Overwall',
components: { MockInput },
mixins: [Plugin], mixins: [Plugin],
data () { data () {
return { return {
@ -40,8 +42,8 @@ export default {
this.initServer() this.initServer()
}, },
async applyBefore () { async applyBefore () {
this.saveTarget() this.submitTarget()
this.saveServer() this.submitServer()
}, },
initTarget () { initTarget () {
this.targets = [] this.targets = []
@ -60,11 +62,14 @@ export default {
deleteTarget (item, index) { deleteTarget (item, index) {
this.targets.splice(index, 1) this.targets.splice(index, 1)
}, },
saveTarget () { submitTarget () {
const map = {} const map = {}
for (const item of this.targets) { for (const item of this.targets) {
if (item.key) { if (item.key) {
map[item.key] = item.value === 'true' const hostname = this.handleHostname(item.key)
if (hostname) {
map[hostname] = (item.value === 'true')
}
} }
} }
this.config.plugin.overwall.targets = map this.config.plugin.overwall.targets = map
@ -90,11 +95,14 @@ export default {
addServer () { addServer () {
this.servers.unshift({ key: '', value: { type: 'path' } }) this.servers.unshift({ key: '', value: { type: 'path' } })
}, },
saveServer () { submitServer () {
const map = {} const map = {}
for (const item of this.servers) { for (const item of this.servers) {
if (item.key) { if (item.key) {
map[item.key] = item.value const hostname = this.handleHostname(item.key)
if (hostname) {
map[hostname] = item.value
}
} }
} }
this.config.plugin.overwall.server = map this.config.plugin.overwall.server = map
@ -161,7 +169,7 @@ export default {
</a-row> </a-row>
<a-row v-for="(item, index) of targets" :key="index" :gutter="10"> <a-row v-for="(item, index) of targets" :key="index" :gutter="10">
<a-col :span="18"> <a-col :span="18">
<a-input v-model="item.key" /> <MockInput v-model="item.key" />
</a-col> </a-col>
<a-col :span="4"> <a-col :span="4">
<a-select v-model="item.value" style="width:100%"> <a-select v-model="item.value" style="width:100%">

View File

@ -1,8 +1,10 @@
<script> <script>
import Plugin from '../mixins/plugin' import Plugin from '../mixins/plugin'
import MockInput from '@/view/components/mock-input.vue'
export default { export default {
name: 'Proxy', name: 'Proxy',
components: { MockInput },
mixins: [Plugin], mixins: [Plugin],
data () { data () {
return { return {
@ -68,7 +70,10 @@ export default {
const excludeIpList = {} const excludeIpList = {}
for (const item of this.excludeIpList) { for (const item of this.excludeIpList) {
if (item.key) { if (item.key) {
excludeIpList[item.key] = item.value === 'true' const hostname = this.handleHostname(item.key)
if (hostname) {
excludeIpList[hostname] = (item.value === 'true')
}
} }
} }
this.config.proxy.excludeIpList = excludeIpList this.config.proxy.excludeIpList = excludeIpList
@ -163,7 +168,7 @@ export default {
</a-row> </a-row>
<a-row v-for="(item, index) of excludeIpList" :key="index" :gutter="10"> <a-row v-for="(item, index) of excludeIpList" :key="index" :gutter="10">
<a-col :span="17"> <a-col :span="17">
<a-input v-model="item.key" /> <MockInput v-model="item.key" />
</a-col> </a-col>
<a-col :span="5"> <a-col :span="5">
<a-select v-model="item.value" style="width:100%"> <a-select v-model="item.value" style="width:100%">

View File

@ -2,11 +2,13 @@
import _ from 'lodash' import _ from 'lodash'
import VueJsonEditor from 'vue-json-editor-fix-cn' import VueJsonEditor from 'vue-json-editor-fix-cn'
import Plugin from '../mixins/plugin' import Plugin from '../mixins/plugin'
import MockInput from '@/view/components/mock-input.vue'
export default { export default {
name: 'Server', name: 'Server',
components: { components: {
VueJsonEditor, VueJsonEditor,
MockInput,
}, },
mixins: [Plugin], mixins: [Plugin],
data () { data () {
@ -68,8 +70,9 @@ export default {
} }
}, },
async applyBefore () { async applyBefore () {
this.submitDnsMapping() this.submitDnsMappings()
this.submitWhiteList() this.submitWhiteList()
this.delEmptySpeedHostname()
}, },
async applyAfter () { async applyAfter () {
if (this.status.server.enabled) { if (this.status.server.enabled) {
@ -87,11 +90,14 @@ export default {
}) })
} }
}, },
submitDnsMapping () { submitDnsMappings () {
const dnsMapping = {} const dnsMapping = {}
for (const item of this.dnsMappings) { for (const item of this.dnsMappings) {
if (item.key) { if (item.key) {
dnsMapping[item.key] = item.value const hostname = this.handleHostname(item.key)
if (hostname) {
dnsMapping[hostname] = item.value
}
} }
} }
this.config.server.dns.mapping = dnsMapping this.config.server.dns.mapping = dnsMapping
@ -127,7 +133,10 @@ export default {
const whiteList = {} const whiteList = {}
for (const item of this.whiteList) { for (const item of this.whiteList) {
if (item.key) { if (item.key) {
whiteList[item.key] = item.value === 'true' const hostname = this.handleHostname(item.key)
if (hostname) {
whiteList[hostname] = (item.value === 'true')
}
} }
} }
this.config.server.whiteList = whiteList this.config.server.whiteList = whiteList
@ -141,6 +150,14 @@ export default {
delSpeedHostname (item, index) { delSpeedHostname (item, index) {
this.getSpeedTestConfig().hostnameList.splice(index, 1) this.getSpeedTestConfig().hostnameList.splice(index, 1)
}, },
delEmptySpeedHostname () {
for (let i = this.getSpeedTestConfig().hostnameList.length - 1; i >= 0; i--) {
const hostname = this.handleHostname(this.getSpeedTestConfig().hostnameList[i])
if (hostname) {
this.getSpeedTestConfig().hostnameList.splice(i, 1)
}
}
},
reSpeedTest () { reSpeedTest () {
this.$api.server.reSpeedTest() this.$api.server.reSpeedTest()
}, },
@ -305,7 +322,7 @@ export default {
</a-row> </a-row>
<a-row v-for="(item, index) of whiteList" :key="index" :gutter="10" style="margin-top: 5px"> <a-row v-for="(item, index) of whiteList" :key="index" :gutter="10" style="margin-top: 5px">
<a-col :span="16"> <a-col :span="16">
<a-input v-model="item.key" /> <MockInput v-model="item.key" />
</a-col> </a-col>
<a-col :span="5"> <a-col :span="5">
<a-select v-model="item.value" style="width:100%"> <a-select v-model="item.value" style="width:100%">
@ -360,7 +377,7 @@ export default {
</a-row> </a-row>
<a-row v-for="(item, index) of dnsMappings" :key="index" :gutter="10" style="margin-top: 5px"> <a-row v-for="(item, index) of dnsMappings" :key="index" :gutter="10" style="margin-top: 5px">
<a-col :span="15"> <a-col :span="15">
<a-input v-model="item.key" :disabled="item.value === false" /> <MockInput v-model="item.key" />
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-select v-model="item.value" :disabled="item.value === false" style="width: 100%"> <a-select v-model="item.value" :disabled="item.value === false" style="width: 100%">
@ -407,12 +424,9 @@ export default {
<a-button style="margin-left:10px" type="primary" icon="plus" @click="addSpeedHostname()" /> <a-button style="margin-left:10px" type="primary" icon="plus" @click="addSpeedHostname()" />
</a-col> </a-col>
</a-row> </a-row>
<a-row <a-row v-for="(item, index) of getSpeedTestConfig().hostnameList" :key="index" :gutter="10" style="margin-top: 5px">
v-for="(item, index) of getSpeedTestConfig().hostnameList" :key="index" :gutter="10"
style="margin-top: 5px"
>
<a-col :span="21"> <a-col :span="21">
<a-input v-model="getSpeedTestConfig().hostnameList[index]" /> <MockInput v-model="getSpeedTestConfig().hostnameList[index]" />
</a-col> </a-col>
<a-col :span="2"> <a-col :span="2">
<a-button style="margin-left:10px" type="danger" icon="minus" @click="delSpeedHostname(item, index)" /> <a-button style="margin-left:10px" type="danger" icon="minus" @click="delSpeedHostname(item, index)" />
@ -493,5 +507,8 @@ export default {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
.ant-input-group-addon:first-child {
width: 50px;
}
} }
</style> </style>

View File

@ -48,18 +48,24 @@ export default {
case 'ArrowDown': case 'ArrowDown':
case 'ArrowLeft': case 'ArrowLeft':
case 'ArrowRight': case 'ArrowRight':
return '无' return ''
//
case 'NumpadDivide': // return 'Num/'
case 'NumpadMultiply': // return 'Num*'
case 'NumpadDecimal': // return 'Num.'
case 'NumpadSubtract': // return 'Num-'
case 'NumpadAdd': // return 'Num+'
return '无'
} }
switch (event.code) { switch (event.code) {
// F1 ~ F12
case 'F1': return 'F1'
case 'F2': return 'F2'
case 'F3': return 'F3'
case 'F4': return 'F4'
case 'F5': return 'F5'
case 'F6': return 'F6'
case 'F7': return 'F7'
case 'F8': return 'F8'
case 'F9': return 'F9'
case 'F10': return 'F10'
case 'F11': return 'F11'
case 'F12': return 'F12'
// 0 ~ 9 // 0 ~ 9
case 'Digit0': return '0' case 'Digit0': return '0'
case 'Digit1': return '1' case 'Digit1': return '1'
@ -104,6 +110,14 @@ export default {
case 'Numpad8': return 'Num8' case 'Numpad8': return 'Num8'
case 'Numpad9': return 'Num9' case 'Numpad9': return 'Num9'
case 'Numpad0': return 'Num0' case 'Numpad0': return 'Num0'
//
case 'NumpadDivide': // return 'Num/'
case 'NumpadMultiply': // return 'Num*'
case 'NumpadDecimal': // return 'Num.'
case 'NumpadSubtract': // return 'Num-'
case 'NumpadAdd': // return 'Num+'
return ''
} }
// //
@ -112,7 +126,7 @@ export default {
} }
console.error(`未能识别的按键key=${event.key}, code=${event.code}, keyCode=${event.keyCode}`) console.error(`未能识别的按键key=${event.key}, code=${event.code}, keyCode=${event.keyCode}`)
return '' return ''
}, },
async disableBeforeInputEvent () { async disableBeforeInputEvent () {
clearTimeout(window.enableBeforeInputEventTimeout) clearTimeout(window.enableBeforeInputEventTimeout)

View File

@ -17,7 +17,7 @@ $dark-input: #777; //输入框:背景色
.ds-container, .ds-container,
.ds-container .container-header, .ds-container .container-header,
.ant-layout-footer { .ant-layout-footer {
background: $dark-bg; background-color: $dark-bg;
color: $dark-text; color: $dark-text;
} }
div, div,
@ -36,7 +36,7 @@ $dark-input: #777; //输入框:背景色
/* 高亮块:背景色和字体颜色 */ /* 高亮块:背景色和字体颜色 */
/* 警告类型 */ /* 警告类型 */
.ant-alert-warning { .ant-alert-warning {
background: $dark-bg-highlight; background-color: $dark-bg-highlight;
border-color: $dark-bg-highlight; border-color: $dark-bg-highlight;
color: $dark-text; color: $dark-text;
/* 关闭图标颜色 */ /* 关闭图标颜色 */
@ -46,7 +46,7 @@ $dark-input: #777; //输入框:背景色
} }
/* 消息类型 */ /* 消息类型 */
.ant-alert-info { .ant-alert-info {
background: $dark-bg-highlight; background-color: $dark-bg-highlight;
border-color: $dark-bg-highlight; border-color: $dark-bg-highlight;
color: $dark-text; color: $dark-text;
} }
@ -66,7 +66,7 @@ $dark-input: #777; //输入框:背景色
background-color: #666; background-color: #666;
} }
.ant-divider { .ant-divider {
background: $dark-bd; background-color: $dark-bd;
} }
.help-list .title1 { .help-list .title1 {
@ -76,7 +76,7 @@ $dark-input: #777; //输入框:背景色
/* 左侧 */ /* 左侧 */
/** 背景色 **/ /** 背景色 **/
.ant-layout-sider { .ant-layout-sider {
background: $dark-bg; background-color: $dark-bg;
} }
/** Logo **/ /** Logo **/
.logo { .logo {
@ -84,14 +84,14 @@ $dark-input: #777; //输入框:背景色
} }
/** 菜单 **/ /** 菜单 **/
.ant-menu { .ant-menu {
background: $dark-bg; background-color: $dark-bg;
color: $dark-text; color: $dark-text;
} }
/* 菜单选中时,或鼠标移到菜单上时的样式 */ /* 菜单选中时,或鼠标移到菜单上时的样式 */
.ant-menu-item:hover, .ant-menu-item:hover,
.ant-menu-submenu .ant-menu-submenu-title:hover, .ant-menu-submenu .ant-menu-submenu-title:hover,
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background: $dark-bg-highlight; background-color: $dark-bg-highlight;
color: #1890ff; color: #1890ff;
span { span {
color: #1890ff; color: #1890ff;
@ -99,12 +99,12 @@ $dark-input: #777; //输入框:背景色
} }
/* 输入框、下拉框 */ /* 输入框、下拉框 */
.ant-input, .ant-input, .fake-input,
.ant-input-number-input, .ant-input-number-input,
.ant-input-number, .ant-input-number,
.ant-select-selection, .ant-select-selection,
.ant-input-group-addon { .ant-input-group-addon {
background: $dark-input; background-color: $dark-input;
border-color: #aaa; border-color: #aaa;
color: $dark-text; color: $dark-text;
&:hover, &:hover,
@ -112,10 +112,13 @@ $dark-input: #777; //输入框:背景色
border-color: #fff; border-color: #fff;
} }
} }
.fake-input {
background-color: red;
}
/* 卡片消息IP测速 */ /* 卡片消息IP测速 */
.ant-card { .ant-card {
background: $dark-input; background-color: $dark-input;
border-color: $dark-input; border-color: $dark-input;
.ant-card-head { .ant-card-head {
border-bottom-color: #929292; border-bottom-color: #929292;
@ -124,26 +127,26 @@ $dark-input: #777; //输入框:背景色
/* 标签:未启用 */ /* 标签:未启用 */
.ant-tag-red { .ant-tag-red {
background: #4f4749; background-color: #4f4749;
border-color: #4f4749; border-color: #4f4749;
color: #bf8285; color: #bf8285;
} }
/* 标签:已启用 */ /* 标签:已启用 */
.ant-tag-green { .ant-tag-green {
background: #505f5f; background-color: #505f5f;
border-color: #505f5f; border-color: #505f5f;
color: #90cb9f; color: #90cb9f;
} }
/* 标签:警告 */ /* 标签:警告 */
.ant-tag-orange { .ant-tag-orange {
background: #5a5750; background-color: #5a5750;
border-color: #5a5750; border-color: #5a5750;
color: #cfa572; color: #cfa572;
} }
/* 按钮 */ /* 按钮 */
.ant-btn:not(.ant-btn-danger, .ant-btn-primary) { .ant-btn:not(.ant-btn-danger, .ant-btn-primary) {
background: $dark-btn; background-color: $dark-btn;
border-color: $dark-btn; border-color: $dark-btn;
color: $dark-text; color: $dark-text;
&:hover { &:hover {
@ -153,7 +156,7 @@ $dark-input: #777; //输入框:背景色
/* 单选框:开关式 */ /* 单选框:开关式 */
.ant-switch:not(.ant-switch-checked) { .ant-switch:not(.ant-switch-checked) {
background: $dark-btn; background-color: $dark-btn;
border-color: $dark-btn; border-color: $dark-btn;
&:hover { &:hover {
opacity: 0.8; opacity: 0.8;
@ -161,7 +164,7 @@ $dark-input: #777; //输入框:背景色
} }
/* 单选框:按钮式 */ /* 单选框:按钮式 */
.ant-radio-button-wrapper { .ant-radio-button-wrapper {
background: $dark-btn; background-color: $dark-btn;
border-color: $dark-btn; border-color: $dark-btn;
color: $dark-text; color: $dark-text;
&:hover { &:hover {
@ -173,24 +176,24 @@ $dark-input: #777; //输入框:背景色
.jsoneditor-vue { .jsoneditor-vue {
/*整个编辑框:背景色和边框*/ /*整个编辑框:背景色和边框*/
div.jsoneditor { div.jsoneditor {
background: $dark-bg-highlight; background-color: $dark-bg-highlight;
border: none; border: none;
} }
/* 头部菜单栏:边框 */ /* 头部菜单栏:边框 */
div.jsoneditor-menu { div.jsoneditor-menu {
background: $dark-bg-highlight; background-color: $dark-bg-highlight;
border-color: $dark-bg-highlight; border-color: $dark-bg-highlight;
} }
/* 内容区域左边:行号 */ /* 内容区域左边:行号 */
.ace_gutter { .ace_gutter {
background: #444; background-color: #444;
.ace_gutter-cell { .ace_gutter-cell {
color: #aaa; color: #aaa;
} }
} }
/* 内容区域右边JSON内容 */ /* 内容区域右边JSON内容 */
.ace_scroller { .ace_scroller {
background: #555; background-color: #555;
} }
/* key的颜色 */ /* key的颜色 */
.ace_variable, .ace_variable,
@ -215,12 +218,12 @@ $dark-input: #777; //输入框:背景色
/* 当前行高亮样式 */ /* 当前行高亮样式 */
.ace_gutter-active-line, .ace_gutter-active-line,
.ace_marker-layer .ace_active-line { .ace_marker-layer .ace_active-line {
background: #838774; background-color: #838774;
} }
/* 选中行高亮样式 */ /* 选中行高亮样式 */
.ace-jsoneditor { .ace-jsoneditor {
.ace_marker-layer .ace_selection { .ace_marker-layer .ace_selection {
background: #8b2929; /* 同时应用于当前选中的搜索结果项的背景色,建议与搜索结果边框颜色保持一致 */ background-color: #8b2929; /* 同时应用于当前选中的搜索结果项的背景色,建议与搜索结果边框颜色保持一致 */
} }
/* 光标颜色 */ /* 光标颜色 */