mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 19:35:50 +00:00
feat: 消息翻译支持切换语言
This commit is contained in:
parent
cce7523f45
commit
621726ab3b
@ -1543,12 +1543,13 @@ class DialogController extends AbstractController
|
||||
/**
|
||||
* @api {get} api/dialog/msg/translation 32. 翻译消息
|
||||
*
|
||||
* @apiDescription 将文本消息翻译成当前语言,需要token身份
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__translation
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
* @apiParam {String} [language] 目标语言,默认当前语言
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -1559,7 +1560,7 @@ class DialogController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$msg_id = intval(Request::input("msg_id"));
|
||||
$language = Base::headerOrInput('language');
|
||||
$language = Base::inputOrHeader('language');
|
||||
$targetLanguage = match ($language) {
|
||||
"zh" => "简体中文",
|
||||
"zh-CHT" => "繁体中文",
|
||||
|
||||
@ -97,6 +97,16 @@ class Base
|
||||
return Base::nullShow(Request::header($key), Request::input($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果input没有则通过header读取
|
||||
* @param $key
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function inputOrHeader($key)
|
||||
{
|
||||
return Base::nullShow(Request::input($key), Request::header($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
* @return string
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
<!--任务操作-->
|
||||
<TaskOperation/>
|
||||
|
||||
<!--下拉菜单-->
|
||||
<DropdownMenu/>
|
||||
|
||||
<!--全局浮窗加载器-->
|
||||
<FloatSpinner/>
|
||||
|
||||
@ -40,10 +43,19 @@ import PreviewImageState from "./components/PreviewImage/state";
|
||||
import NetworkException from "./components/NetworkException";
|
||||
import GuidePage from "./components/GuidePage";
|
||||
import TaskOperation from "./pages/manage/components/TaskOperation";
|
||||
import DropdownMenu from "./components/DropdownMenu";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
components: {TaskOperation, NetworkException, PreviewImageState, RightBottom, FloatSpinner, GuidePage},
|
||||
components: {
|
||||
DropdownMenu,
|
||||
TaskOperation,
|
||||
NetworkException,
|
||||
PreviewImageState,
|
||||
RightBottom,
|
||||
FloatSpinner,
|
||||
GuidePage
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
||||
176
resources/assets/js/components/DropdownMenu.vue
Normal file
176
resources/assets/js/components/DropdownMenu.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<EDropdown
|
||||
ref="dropdown"
|
||||
trigger="click"
|
||||
class="task-operation-dropdown"
|
||||
placement="bottom"
|
||||
size="small"
|
||||
:style="styles"
|
||||
@command="onCommand"
|
||||
@visible-change="visibleChange">
|
||||
<div ref="icon" class="task-operation-icon"></div>
|
||||
<EDropdownMenu ref="dropdownMenu" slot="dropdown" class="task-operation-more-dropdown">
|
||||
<li class="task-operation-more-warp small">
|
||||
<ul>
|
||||
<EDropdownItem
|
||||
v-for="(item, key) in list"
|
||||
:key="key"
|
||||
:command="item.value"
|
||||
:disabled="active === item.value">
|
||||
<div class="item">{{item.label}}</div>
|
||||
</EDropdownItem>
|
||||
</ul>
|
||||
</li>
|
||||
</EDropdownMenu>
|
||||
</EDropdown>
|
||||
</template>
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
|
||||
list: [], // 数据列表: [{label: '', value: ''}]
|
||||
active: '', // 当前选中的值
|
||||
onUpdate: null, // 选中后的回调函数
|
||||
scrollHide: false, // 滚动立即隐藏
|
||||
|
||||
element: null,
|
||||
target: null,
|
||||
styles: {},
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.target) {
|
||||
this.target.removeEventListener('scroll', this.handlerEventListeners);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['menuOperation'])
|
||||
},
|
||||
|
||||
watch: {
|
||||
menuOperation(data) {
|
||||
if (data.event && data.list) {
|
||||
if (this.$refs.dropdown.visible && this.element === data.event.target) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
const eventRect = data.event.target.getBoundingClientRect();
|
||||
this.styles = {
|
||||
left: `${eventRect.left}px`,
|
||||
top: `${eventRect.top}px`,
|
||||
width: `${eventRect.width}px`,
|
||||
height: `${eventRect.height}px`,
|
||||
}
|
||||
this.list = data.list;
|
||||
this.active = data.active && this.list.find(item => item.value === data.active) ? data.active : '';
|
||||
this.onUpdate = typeof data.onUpdate === "function" ? data.onUpdate : null;
|
||||
this.scrollHide = typeof data.scrollHide === "boolean" ? data.scrollHide : false;
|
||||
//
|
||||
this.$refs.icon.focus();
|
||||
this.updatePopper();
|
||||
this.show();
|
||||
this.setupEventListeners(data.event)
|
||||
} else {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
show() {
|
||||
this.$refs.dropdown.show()
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.$refs.dropdown.hide()
|
||||
},
|
||||
|
||||
onCommand(value) {
|
||||
this.hide();
|
||||
if (typeof this.onUpdate === "function") {
|
||||
this.onUpdate(value);
|
||||
}
|
||||
},
|
||||
|
||||
visibleChange(visible) {
|
||||
this.visible = visible;
|
||||
},
|
||||
|
||||
updatePopper() {
|
||||
this.$nextTick(this.$refs.dropdownMenu.updatePopper)
|
||||
},
|
||||
|
||||
setupEventListeners(event) {
|
||||
this.element = event.target;
|
||||
let target = this.getScrollParent(this.element);
|
||||
if (target === window.document.body || target === window.document.documentElement) {
|
||||
target = window;
|
||||
}
|
||||
if (this.target) {
|
||||
if (this.target === target) {
|
||||
return;
|
||||
}
|
||||
this.target.removeEventListener('scroll', this.handlerEventListeners);
|
||||
}
|
||||
this.target = target;
|
||||
this.target.addEventListener('scroll', this.handlerEventListeners);
|
||||
},
|
||||
|
||||
handlerEventListeners(e) {
|
||||
if (!this.visible || !this.element) {
|
||||
return
|
||||
}
|
||||
if (this.scrollHide) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
const scrollRect = e.target.getBoundingClientRect();
|
||||
const eventRect = this.element.getBoundingClientRect();
|
||||
if (eventRect.top < scrollRect.top || eventRect.top > scrollRect.top + scrollRect.height) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this.styles = {
|
||||
left: `${eventRect.left}px`,
|
||||
top: `${eventRect.top}px`,
|
||||
width: `${eventRect.width}px`,
|
||||
height: `${eventRect.height}px`,
|
||||
};
|
||||
this.updatePopper();
|
||||
},
|
||||
|
||||
getScrollParent(element) {
|
||||
const parent = element.parentNode;
|
||||
if (!parent) {
|
||||
return element;
|
||||
}
|
||||
if (parent === window.document) {
|
||||
if (window.document.body.scrollTop || window.document.body.scrollLeft) {
|
||||
return window.document.body;
|
||||
} else {
|
||||
return window.document.documentElement;
|
||||
}
|
||||
}
|
||||
if (
|
||||
['scroll', 'auto'].indexOf(this.getStyleComputedProperty(parent, 'overflow')) !== -1 ||
|
||||
['scroll', 'auto'].indexOf(this.getStyleComputedProperty(parent, 'overflow-x')) !== -1 ||
|
||||
['scroll', 'auto'].indexOf(this.getStyleComputedProperty(parent, 'overflow-y')) !== -1
|
||||
) {
|
||||
return parent;
|
||||
}
|
||||
return this.getScrollParent(element.parentNode);
|
||||
},
|
||||
|
||||
getStyleComputedProperty(element, property) {
|
||||
const css = window.getComputedStyle(element, null);
|
||||
return css[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -15,10 +15,10 @@
|
||||
<template v-if="translation">
|
||||
<div class="content-divider">
|
||||
<span></span>
|
||||
<div class="divider-label">{{ translation.label }}</div>
|
||||
<div class="divider-label translation-label" @click="viewText">{{ translation.label }}</div>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="content-additional">{{translation.value}}</div>
|
||||
<div class="content-additional">{{translation.content}}</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@ -26,7 +26,6 @@
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import DialogMarkdown from "../DialogMarkdown.vue";
|
||||
import {languageName} from "../../../../language";
|
||||
|
||||
export default {
|
||||
components: {DialogMarkdown},
|
||||
@ -35,11 +34,11 @@ export default {
|
||||
msg: Object,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['audioPlaying', 'cacheTranslations']),
|
||||
...mapState(['audioPlaying', 'cacheTranslations', 'cacheTranslationLanguage']),
|
||||
|
||||
translation() {
|
||||
const translation = this.cacheTranslations.find(item => {
|
||||
return item.key === `msg-${this.msgId}` && item.lang === languageName;
|
||||
translation({cacheTranslations, msgId, cacheTranslationLanguage}) {
|
||||
const translation = cacheTranslations.find(item => {
|
||||
return item.key === `msg-${msgId}` && item.language === cacheTranslationLanguage;
|
||||
});
|
||||
return translation ? translation : null;
|
||||
},
|
||||
@ -63,6 +62,9 @@ export default {
|
||||
}
|
||||
return `${Math.max(1, seconds)}″`
|
||||
},
|
||||
viewText(e) {
|
||||
this.$emit('viewText', e);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
<template v-if="translation">
|
||||
<div class="content-divider">
|
||||
<span></span>
|
||||
<div class="divider-label">{{ translation.label }}</div>
|
||||
<div class="divider-label translation-label" @click="viewText">{{ translation.label }}</div>
|
||||
<span></span>
|
||||
</div>
|
||||
<DialogMarkdown v-if="msg.type === 'md'" :text="translation.value"/>
|
||||
<pre v-else v-html="$A.formatTextMsg(translation.value, userId)"></pre>
|
||||
<DialogMarkdown v-if="msg.type === 'md'" :text="translation.content"/>
|
||||
<pre v-else v-html="$A.formatTextMsg(translation.content, userId)"></pre>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@ -18,7 +18,6 @@
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import DialogMarkdown from "../DialogMarkdown.vue";
|
||||
import {languageName} from "../../../../language";
|
||||
|
||||
export default {
|
||||
components: {DialogMarkdown},
|
||||
@ -27,11 +26,11 @@ export default {
|
||||
msg: Object,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['cacheTranslations']),
|
||||
...mapState(['cacheTranslations', 'cacheTranslationLanguage']),
|
||||
|
||||
translation() {
|
||||
const translation = this.cacheTranslations.find(item => {
|
||||
return item.key === `msg-${this.msgId}` && item.lang === languageName;
|
||||
translation({cacheTranslations, msgId, cacheTranslationLanguage}) {
|
||||
const translation = cacheTranslations.find(item => {
|
||||
return item.key === `msg-${msgId}` && item.language === cacheTranslationLanguage;
|
||||
});
|
||||
return translation ? translation : null;
|
||||
},
|
||||
|
||||
@ -672,6 +672,7 @@ import DialogGroupWordChain from "./DialogGroupWordChain";
|
||||
import DialogGroupVote from "./DialogGroupVote";
|
||||
import DialogComplaint from "./DialogComplaint";
|
||||
import touchclick from "../../../directives/touchclick";
|
||||
import {languageList} from "../../../language";
|
||||
|
||||
export default {
|
||||
name: "DialogWrapper",
|
||||
@ -884,7 +885,8 @@ export default {
|
||||
'keyboardType',
|
||||
'keyboardHeight',
|
||||
'safeAreaBottom',
|
||||
'formOptions'
|
||||
'formOptions',
|
||||
'cacheTranslationLanguage'
|
||||
]),
|
||||
|
||||
...mapGetters(['isLoad']),
|
||||
@ -2994,27 +2996,43 @@ export default {
|
||||
return;
|
||||
}
|
||||
const {id: msg_id} = this.operateItem
|
||||
if (this.isLoad(`msg-${msg_id}`)) {
|
||||
const key = `msg-${msg_id}`
|
||||
if (this.isLoad(key)) {
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch("setLoad", `msg-${msg_id}`)
|
||||
this.$store.dispatch("setLoad", key)
|
||||
this.$store.dispatch("call", {
|
||||
url: 'dialog/msg/translation',
|
||||
data: {
|
||||
msg_id
|
||||
msg_id,
|
||||
language: this.cacheTranslationLanguage
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.$store.dispatch("saveTranslation", {
|
||||
key: `msg-${msg_id}`,
|
||||
value: data.content,
|
||||
});
|
||||
this.$store.dispatch("saveTranslation", Object.assign(data, {key}));
|
||||
}).catch(({msg}) => {
|
||||
$A.messageError(msg);
|
||||
}).finally(_ => {
|
||||
this.$store.dispatch("cancelLoad", `msg-${msg_id}`)
|
||||
this.$store.dispatch("cancelLoad", key)
|
||||
});
|
||||
},
|
||||
|
||||
openTranslationMenu(event) {
|
||||
const list = Object.keys(languageList).map(item => ({
|
||||
label: languageList[item],
|
||||
value: item
|
||||
}))
|
||||
this.$store.state.menuOperation = {
|
||||
event,
|
||||
list,
|
||||
active: this.cacheTranslationLanguage,
|
||||
scrollHide: true,
|
||||
onUpdate: async (language) => {
|
||||
await this.$store.dispatch("setTranslationLanguage", language);
|
||||
this.onTranslation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onCopy(data) {
|
||||
if (!$A.isJson(data)) {
|
||||
return
|
||||
@ -3097,10 +3115,18 @@ export default {
|
||||
this.onPositionId(data.reply_id, data.msg_id)
|
||||
},
|
||||
|
||||
onViewText({target, clientX}, el) {
|
||||
onViewText(event, el) {
|
||||
if (this.operateVisible) {
|
||||
return
|
||||
}
|
||||
const {target, clientX} = event
|
||||
|
||||
// 点击切换翻译
|
||||
if (target.classList.contains('translation-label')) {
|
||||
this.operateItem = this.findMsgByElement(el)
|
||||
this.openTranslationMenu(event)
|
||||
return
|
||||
}
|
||||
|
||||
// 打开审批详情
|
||||
let approveElement = target;
|
||||
@ -3144,56 +3170,60 @@ export default {
|
||||
if (clientX - target.getBoundingClientRect().x > 18) {
|
||||
return;
|
||||
}
|
||||
let listElement = el.parentElement;
|
||||
while (listElement) {
|
||||
if (listElement.classList.contains('dialog-scroller')) {
|
||||
break;
|
||||
}
|
||||
if (listElement.classList.contains('dialog-view')) {
|
||||
const dataId = listElement.getAttribute("data-id")
|
||||
const dataMsg = this.allMsgs.find(item => item.id == dataId) || {}
|
||||
if (dataMsg.userid != this.userId) {
|
||||
return;
|
||||
}
|
||||
const dataIndex = [].indexOf.call(el.querySelectorAll(target.tagName), target);
|
||||
if (dataClass === 'checked') {
|
||||
target.setAttribute('data-list', 'unchecked')
|
||||
} else {
|
||||
target.setAttribute('data-list', 'checked')
|
||||
}
|
||||
this.$store.dispatch("setLoad", {
|
||||
key: `msg-${dataId}`,
|
||||
delay: 600
|
||||
})
|
||||
this.$store.dispatch("call", {
|
||||
url: 'dialog/msg/checked',
|
||||
data: {
|
||||
dialog_id: this.dialogId,
|
||||
msg_id: dataId,
|
||||
index: dataIndex,
|
||||
checked: dataClass === 'checked' ? 0 : 1
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.$store.dispatch("saveDialogMsg", data);
|
||||
}).catch(({msg}) => {
|
||||
if (dataClass === 'checked') {
|
||||
target.setAttribute('data-list', 'checked')
|
||||
} else {
|
||||
target.setAttribute('data-list', 'unchecked')
|
||||
}
|
||||
$A.modalError(msg)
|
||||
}).finally(_ => {
|
||||
this.$store.dispatch("cancelLoad", `msg-${dataId}`)
|
||||
});
|
||||
break;
|
||||
}
|
||||
listElement = listElement.parentElement;
|
||||
const dataMsg = this.findMsgByElement(el)
|
||||
if (dataMsg.userid != this.userId) {
|
||||
return;
|
||||
}
|
||||
const dataIndex = [].indexOf.call(el.querySelectorAll(target.tagName), target);
|
||||
if (dataClass === 'checked') {
|
||||
target.setAttribute('data-list', 'unchecked')
|
||||
} else {
|
||||
target.setAttribute('data-list', 'checked')
|
||||
}
|
||||
this.$store.dispatch("setLoad", {
|
||||
key: `msg-${dataMsg.id}`,
|
||||
delay: 600
|
||||
})
|
||||
this.$store.dispatch("call", {
|
||||
url: 'dialog/msg/checked',
|
||||
data: {
|
||||
dialog_id: this.dialogId,
|
||||
msg_id: dataMsg.id,
|
||||
index: dataIndex,
|
||||
checked: dataClass === 'checked' ? 0 : 1
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.$store.dispatch("saveDialogMsg", data);
|
||||
}).catch(({msg}) => {
|
||||
if (dataClass === 'checked') {
|
||||
target.setAttribute('data-list', 'checked')
|
||||
} else {
|
||||
target.setAttribute('data-list', 'unchecked')
|
||||
}
|
||||
$A.modalError(msg)
|
||||
}).finally(_ => {
|
||||
this.$store.dispatch("cancelLoad", `msg-${dataMsg.id}`)
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
findMsgByElement(el) {
|
||||
let element = el.parentElement;
|
||||
while (element) {
|
||||
if (element.classList.contains('dialog-scroller')) {
|
||||
break;
|
||||
}
|
||||
if (element.classList.contains('dialog-view')) {
|
||||
const dataId = element.getAttribute("data-id")
|
||||
return this.allMsgs.find(item => item.id == dataId) || {}
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
onViewFile(data) {
|
||||
if (this.operateVisible) {
|
||||
return
|
||||
|
||||
32
resources/assets/js/store/actions.js
vendored
32
resources/assets/js/store/actions.js
vendored
@ -826,6 +826,7 @@ export default {
|
||||
const cacheLoginEmail = await $A.IDBString("cacheLoginEmail");
|
||||
const cacheFileSort = await $A.IDBJson("cacheFileSort");
|
||||
const cacheTaskBrowse = await $A.IDBArray("cacheTaskBrowse")
|
||||
const cacheTranslationLanguage = await $A.IDBString("cacheTranslationLanguage")
|
||||
const cacheTranslations = await $A.IDBArray("cacheTranslations")
|
||||
const cacheEmojis = await $A.IDBArray("cacheEmojis")
|
||||
const userInfo = await $A.IDBJson("userInfo")
|
||||
@ -836,6 +837,7 @@ export default {
|
||||
await $A.IDBSet("cacheLoginEmail", cacheLoginEmail);
|
||||
await $A.IDBSet("cacheFileSort", cacheFileSort);
|
||||
await $A.IDBSet("cacheTaskBrowse", cacheTaskBrowse);
|
||||
await $A.IDBSet("cacheTranslationLanguage", cacheTranslationLanguage);
|
||||
await $A.IDBSet("cacheTranslations", cacheTranslations);
|
||||
await $A.IDBSet("cacheEmojis", cacheEmojis);
|
||||
await $A.IDBSet("cacheVersion", state.cacheVersion)
|
||||
@ -867,6 +869,7 @@ export default {
|
||||
state.cacheTasks = await $A.IDBArray("cacheTasks")
|
||||
state.cacheProjectParameter = await $A.IDBArray("cacheProjectParameter")
|
||||
state.cacheTaskBrowse = await $A.IDBArray("cacheTaskBrowse")
|
||||
state.cacheTranslationLanguage = await $A.IDBString("cacheTranslationLanguage")
|
||||
state.cacheTranslations = await $A.IDBArray("cacheTranslations")
|
||||
state.dialogMsgs = await $A.IDBArray("dialogMsgs")
|
||||
state.fileLists = await $A.IDBArray("fileLists")
|
||||
@ -874,6 +877,9 @@ export default {
|
||||
state.callAt = await $A.IDBArray("callAt")
|
||||
state.cacheEmojis = await $A.IDBArray("cacheEmojis")
|
||||
|
||||
// TranslationLanguage
|
||||
typeof languageList[state.cacheTranslationLanguage] === "undefined" && (state.cacheTranslationLanguage = languageName)
|
||||
|
||||
// 会员信息
|
||||
if (state.userInfo.userid) {
|
||||
state.userId = state.userInfo.userid = $A.runNum(state.userInfo.userid)
|
||||
@ -3351,24 +3357,32 @@ export default {
|
||||
/**
|
||||
* 保存翻译
|
||||
* @param state
|
||||
* @param dispatch
|
||||
* @param data {key, value}
|
||||
* @param data {key, content, language}
|
||||
*/
|
||||
saveTranslation({state, dispatch}, data) {
|
||||
saveTranslation({state}, data) {
|
||||
if (!$A.isJson(data)) {
|
||||
return
|
||||
}
|
||||
const item = state.cacheTranslations.find(item => item.key == data.key && item.lang == languageName)
|
||||
if (item) {
|
||||
item.value = data.value
|
||||
const translation = state.cacheTranslations.find(item => item.key == data.key && item.language == data.language)
|
||||
if (translation) {
|
||||
translation.content = data.content
|
||||
} else {
|
||||
data.lang = languageName
|
||||
data.label = languageList[languageName] || languageName
|
||||
state.cacheTranslations.push(data)
|
||||
const label = languageList[data.language] || data.language
|
||||
state.cacheTranslations.push(Object.assign(data, {label}))
|
||||
}
|
||||
$A.IDBSave("cacheTranslations", state.cacheTranslations.slice(-200))
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置翻译语言
|
||||
* @param state
|
||||
* @param language
|
||||
*/
|
||||
setTranslationLanguage({state}, language) {
|
||||
state.cacheTranslationLanguage = language
|
||||
$A.IDBSave('cacheTranslationLanguage', language);
|
||||
},
|
||||
|
||||
/** *****************************************************************************************/
|
||||
/** ************************************* loads *********************************************/
|
||||
/** *****************************************************************************************/
|
||||
|
||||
4
resources/assets/js/store/state.js
vendored
4
resources/assets/js/store/state.js
vendored
@ -234,5 +234,9 @@ export default {
|
||||
},
|
||||
|
||||
// 翻译
|
||||
cacheTranslationLanguage: '',
|
||||
cacheTranslations: [],
|
||||
|
||||
// 下拉菜单操作
|
||||
menuOperation: {}
|
||||
};
|
||||
|
||||
@ -1413,6 +1413,10 @@
|
||||
font-size: 12px;
|
||||
padding: 0 8px;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user