前端添加自定义模板配置

This commit is contained in:
John Smith
2025-04-06 16:23:52 +08:00
parent 7b91cbae3f
commit 22d80e3af2
6 changed files with 208 additions and 0 deletions

View File

@@ -9,3 +9,7 @@ export async function uploadEmoticon(file) {
body.set('file', file)
return (await axios.post('/api/emoticon', body)).data
}
export async function getTemplates() {
return (await axios.get('/api/templates')).data
}

View File

@@ -65,6 +65,13 @@ export default {
addEmoticon: 'Add emote',
emoticonFileTooLarge: 'File size is too large. Max size is 1MB',
template: 'Custom HTML Templates',
templateDefaultTitle: 'Default',
templateDefaultDescription: 'A YouTube-style template that can be styled with custom CSS. Choose this if you are unfamiliar with custom HTML templates, otherwise your custom CSS will not take effect',
templateCustomUrlTitle: 'Enter template URL',
templateCustomUrlDescription: 'For templates not listed, you can manually enter the URL provided by the author here',
author: 'Author: ',
urlTooLong: 'The room URL is too long, and will be truncated by Livehime (but not by OBS)',
roomUrlUpdated: 'The room URL is updated. Remember to copy it again',
roomUrl: 'Room URL',

View File

@@ -65,6 +65,13 @@ export default {
addEmoticon: 'スタンプを追加',
emoticonFileTooLarge: 'ファイルサイズが大きすぎます。最大サイズは1MBです',
template: 'カスタムHTMLテンプレート',
templateDefaultTitle: 'デフォルト',
templateDefaultDescription: 'YouTube風のテンプレートで、カスタムCSSでスタイルを変更できます。カスタムHTMLテンプレートの機能に慣れていない場合はこれを選択してください。そうでない場合、カスタムCSSが適用されない',
templateCustomUrlTitle: 'テンプレートURLを入力',
templateCustomUrlDescription: 'リストにないテンプレートは、作者が提供するURLをここで手動で入力できます',
author: '作者:',
urlTooLong: 'ルームのURLが長すぎて、直播姬によって切り詰められますただし、OBSでは切り詰められません',
roomUrlUpdated: 'ルームのURLが更新されました。再度コピーすることをお忘れなく',
roomUrl: 'ルームのURL',

View File

@@ -65,6 +65,13 @@ export default {
addEmoticon: '添加表情',
emoticonFileTooLarge: '文件尺寸太大最大1MB',
template: '自定义HTML模板',
templateDefaultTitle: '默认',
templateDefaultDescription: '仿YouTube风格的模板可以用自定义CSS修改样式。如果你不了解自定义HTML模板的功能就选择这个否则自定义CSS不会生效',
templateCustomUrlTitle: '输入模板URL',
templateCustomUrlDescription: '没有列出的模板也可以在这里手动输入作者提供的URL',
author: '作者:',
urlTooLong: '房间URL太长了会被直播姬截断OBS不会',
roomUrlUpdated: '房间URL已更新记得重新复制',
roomUrl: '房间URL',

View File

@@ -0,0 +1,177 @@
<template>
<div>
<el-radio-group :value="selectedIndex" @input="onSelect">
<el-radio :label="INDEX_DEFAULT" border>
<div class="card-body">
<div class="title-line">
<h3 class="name">{{ $t('home.templateDefaultTitle') }}</h3>
</div>
<div class="description">{{ $t('home.templateDefaultDescription') }}</div>
</div>
</el-radio>
<el-radio :label="INDEX_CUSTOM" border>
<div class="card-body">
<div class="title-line">
<h3 class="name">{{ $t('home.templateCustomUrlTitle') }}</h3>
</div>
<div class="description">{{ $t('home.templateCustomUrlDescription') }}</div>
<div>
<el-input v-model="customUrl" placeholder="https://example.com/path/to/you/template/index.html"></el-input>
</div>
</div>
</el-radio>
<el-radio v-for="(template, index) in templates" :key="template.id" :label="index" border>
<div class="card-body">
<div class="title-line">
<h3 class="name">{{ template.name }}</h3>
<span class="version">{{ template.version }}</span>
<span class="author push-inline-end">{{ $t('home.author') }}{{ template.author }}</span>
</div>
<div v-if="template.description !== ''" class="description">{{ template.description }}</div>
<div>URL: {{ template.url }}</div>
</div>
</el-radio>
</el-radio-group>
</div>
</template>
<script>
import * as mainApi from '@/api/main'
const INDEX_DEFAULT = 'default'
const INDEX_CUSTOM = 'custom'
export default {
name: 'TemplateSelect',
props: {
value: String
},
data() {
return {
INDEX_DEFAULT,
INDEX_CUSTOM,
templates: [],
customUrl: '',
}
},
computed: {
selectedIndex() {
if (this.value == '') {
return INDEX_DEFAULT
}
let index = this.templates.findIndex(template => template.url === this.value)
if (index !== -1) {
return index
}
return INDEX_CUSTOM
}
},
watch: {
value: {
immediate: true,
handler(val) {
if (this.selectedIndex === INDEX_CUSTOM) {
this.customUrl = val
}
}
},
customUrl(val) {
if (this.selectedIndex === INDEX_CUSTOM) {
this.$emit('input', val)
}
}
},
mounted() {
this.updateTemplates()
},
methods: {
async updateTemplates() {
try {
this.templates = (await mainApi.getTemplates()).templates
} catch (e) {
this.$message.error(`Failed to fetch templates: ${e}`)
throw e
} finally {
if (this.selectedIndex !== INDEX_CUSTOM) {
this.customUrl = ''
}
}
},
onSelect(selectedIndex) {
let url
if (selectedIndex === INDEX_DEFAULT) {
url = ''
} else if (selectedIndex === INDEX_CUSTOM) {
url = this.customUrl
} else {
url = this.templates[selectedIndex].url
}
this.$emit('input', url)
}
}
}
</script>
<style scoped>
.el-radio-group {
display: block;
}
.el-radio {
display: block;
height: unset;
margin: 0 0 16px 0 !important;
padding: 20px;
}
.el-radio >>> .el-radio__input {
display: none;
}
.el-radio >>> .el-radio__label {
padding-left: unset;
}
.push-inline-end {
margin-inline-start: auto;
}
.card-body {
max-height: 200px;
display: flex;
flex-flow: column nowrap;
}
.title-line {
margin-block-end: 1em;
flex: none;
display: flex;
flex-flow: row nowrap;
align-items: baseline;
text-wrap: nowrap;
}
.name {
margin-block: 0 0;
margin-inline-end: 8px;
display: inline;
}
.version {
color: #909399;
}
.author {
color: #606266;
}
.description {
margin-block-end: 1em;
flex: auto;
overflow-y: auto;
text-wrap: wrap;
}
</style>

View File

@@ -217,6 +217,10 @@
<el-button type="primary" icon="el-icon-plus" @click="addEmoticon">{{$t('home.addEmoticon')}}</el-button>
</p>
</el-tab-pane>
<el-tab-pane :label="$t('home.template')" lazy>
<template-select v-model="form.templateUrl"></template-select>
</el-tab-pane>
</el-tabs>
</el-form>
</p>
@@ -249,12 +253,14 @@
import _ from 'lodash'
import download from 'downloadjs'
import TemplateSelect from './TemplateSelect'
import { mergeConfig } from '@/utils'
import * as mainApi from '@/api/main'
import * as chatConfig from '@/api/chatConfig'
export default {
name: 'Home',
components: { TemplateSelect },
data() {
return {
serverConfig: {