I am using vue "vue": "^1.0.24"
to develop a google chrome extension right now. This is my widget template.html
file:
<st-div id="__selection-translator__">
<st-div class="__st-box__" v-el:st-box :style="boxStyle">
<st-header v-if="!inline" v-el:st-drag>
<st-span class="st-icon-pin" title="固定" @click="pinned=!pinned" :class="{'__pinned__':pinned}"></st-span>
<st-span class="st-icon-down-open" :class="{'__open__':showForm}" :title="showForm?'收起':'展开'" @click="showForm=!showForm"></st-span>
<st-span class="st-icon-cog" title="设置" @click="openOptions"></st-span>
</st-header>
<st-div class="__query-form__" v-show="showForm">
<st-div>
<textarea placeholder="输入要翻译的句子或单词" v-model="query.text" v-el:textarea @keyup.enter="ctrlEnter" @keyup="safeTranslate | debounce 600"></textarea>
</st-div>
<st-div>
<select v-model="query.from" @change="safeTranslate">
<option value="">自动判断</option>
<option v-for="locale in locales" track-by="localeId" :value="locale.localeId" v-text="locale['zh-CN']"></option>
</select>
<st-div class="__exchange__">
<st-span class="st-icon-exchange" @click="exchangeLocale"></st-span>
</st-div>
<select v-model="query.to" @change="safeTranslate">
<option value="">自动选择</option>
<option v-for="locale in locales" track-by="localeId" :value="locale.localeId" v-text="locale['zh-CN']"></option>
</select>
</st-div>
<st-div>
<select v-model="query.api" @change="safeTranslate">
<option value="YouDao">有道翻译</option>
<option value="BaiDu">百度翻译</option>
<!-- <option value="Bing">必应翻译</option> -->
<option value="Google">谷歌翻译</option>
<option value="GoogleCN">谷歌翻译(国内)</option>
<option value="Reddwarf">红矮星翻译</option>
</select>
<st-div class="__action-list__">
<st-div class="__button__ __btn-translate__" @click="safeTranslate">翻译
<st-span class="st-icon-down-dir"></st-span>
</st-div>
<st-div class="__expand__">
<st-div class="__button__" @click="play(query.text,query.from)">朗读</st-div>
<st-div class="__button__" @click="copy(query.text,$event)">复制</st-div>
</st-div>
</st-div>
</st-div>
</st-div>
<st-div class="__translate-result__" v-show="loading">正在查询,请稍候……</st-div>
<st-div class="__translate-result__" v-show="showResult && !loading">
<st-div v-show="result.error">
<st-span v-text="result.error"></st-span>
<st-span class="__retry__" @click="safeTranslate">重试</st-span>
</st-div>
<st-div v-else>
<st-div class="__phonetic__">
<st-span v-show="result.phonetic" v-text="result.phonetic"></st-span>
<st-span class="__copy-and-read__">
<st-span @click="play(query.text,query.from)">朗读</st-span>
<st-span v-show="result.phonetic" @click="copy(result.phonetic,$event)">复制</st-span>
<st-span v-show="result.phonetic" @click="addGlossary(query.text,query.from)">添加到单词本</st-span>
</st-span>
</st-div>
<st-div v-show="result.dict && result.dict.length">
<st-ul>
<st-li v-for="d in result.dict" v-text="d"></st-li>
</st-ul>
<st-div class="__copy-and-read__">
<st-span class="__copy-and-read__" @click="copy(result.dict,$event)">复制</st-span>
</st-div>
</st-div>
<st-div v-show="result.result&&result.result.length">
<st-div v-for="text in result.result" v-text="text"></st-div>
<st-div class="__copy-and-read__">
<st-span class="__copy-and-read__" @click="play(result.result,result.to)">朗读</st-span>
<st-span class="__copy-and-read__" @click="copy(result.result,$event)">复制</st-span>
</st-div>
</st-div>
</st-div>
</st-div>
<st-footer>
<st-span v-show="!loading && apiName">via
<a :href="result.link" target="_blank" v-text="apiName"></a></st-span>
</st-footer>
</st-div>
<st-div class="__st-btn__" v-el:st-btn :style="btnStyle">译</st-div>
</st-div>
when I run this extension, the console show warning like this:
vue.common.js:1137 [Vue warn]: Unknown custom element: <st-span> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
and this is index.js
file:
/**
* @files 基础 ST 组件,内容脚本和弹出页都会用到
*/
import '../fontello/css/selection-translator.css';
import './style.scss';
import Vue from 'vue';
import widgetMixin from './vue-st';
import chromeCall from 'chrome-call';
import locales from '../locales';
import template from './template.html';
// const request = require('superagent');
// 去掉 locales 里的 *-* 类语种,除了 zh-CN、zh-TW 和 zh-HK(百度翻译里的粤语)
const translateLocales = [];
locales.forEach( locale => {
const {localeId} = locale;
if ( !localeId.includes( '-' ) || ( localeId === 'zh-CN' || localeId == 'zh-TW' || localeId == 'zh-HK' ) ) {
translateLocales.push( locale );
}
} );
const resolvedEmptyPromise = Promise.resolve() ,
noop = ()=> {};
/**
* 翻译窗口的基础 Vue 构造函数。
* 注意:这个构造函数需要一个额外的 options:client
*/
export default Vue.extend( {
template ,
data : ()=>({
access_token: '', // 扇贝单词授权 token
locales : translateLocales ,
showForm : false ,
query : {
text : '' ,
from : '' ,
to : '' ,
api : ''
} ,
result : {
error : '' ,
phonetic : '' ,
dict : [] ,
result : [] ,
link : '',
}
}) ,
created() {
this.$options.client.on( 'disconnect' , ()=> {
this.result = {
error : 'index连接到翻译引擎时发生了错误,请刷新网页或重启浏览器后再试。'
}
} );
} ,
computed : {
apiName() {
return {
YouDao: '有道翻译',
Google: '谷歌翻译',
GoogleCN: '谷歌翻译(国内)',
BaiDu: '百度翻译',
Reddwarf: '红矮星翻译'
}[this.query.api] || ''
}
},
methods : {
/**
* 翻译快捷键:Ctrl + Enter
* @param event
*/
ctrlEnter( event ) {
if ( event.ctrlKey ) {
this.safeTranslate();
}
} ,
/**
* 仅当有文本时才翻译
*/
safeTranslate() {
if ( this.query.text.trim() ) {
this.translate();
}
} ,
/**
* 从后台网页获取查询结果
* @returns {Promise}
*/
getResult() {
if ( this.$options.client.disconnected ) {
return resolvedEmptyPromise;
}
return this.$options.client
.send( 'get translate result' , this.query , true )
.then( resultObj => {
debugger;
if ("200" !==resultObj.response.statusCode||"200" !== resultObj.response.resultCode) {
let errMsg = {
NETWORK_ERROR: '网络错误,请检查你的网络设置。',
API_SERVER_ERROR: '接口返回了错误的数据,请稍候重试。',
UNSUPPORTED_LANG: '不支持的语种,请使用谷歌翻译重试。',
NETWORK_TIMEOUT: '查询超时:5 秒内没有查询到翻译结果,已中断查询。'
}[resultObj.code]
if (resultObj.error) {
errMsg += resultObj.error
}
this.result = {error: errMsg}
} else {
const {phonetic} = resultObj;
this.result = resultObj;
this.result.error = '';
this.result.phonetic = resultObj.response.result.translation;
}
} , noop );
// 只有在一种特殊情况下才会走进 catch 分支:
// 消息发送出去后但还没得到响应时就被后台断开了连接.
// 不过出现这种情况的可能性极低.
} ,
/**
* 交换源语种与目标语种
*/
exchangeLocale() {
const {to,from} = this.query;
this.query.to = from;
this.query.from = to;
} ,
/**
* 打开设置页
*/
openOptions() {
this.$options.client.send( 'open options' );
} ,
/**
* 复制文本
* @param {String|String[]} textOrTextArray
* @param {MouseEvent} event
*/
copy( textOrTextArray , event ) {
if ( Array.isArray( textOrTextArray ) ) {
textOrTextArray = textOrTextArray.join( 'n' );
}
this.$options.client.send( 'copy' , textOrTextArray );
const {target} = event ,
original = target.textContent;
target.textContent = '已复制';
setTimeout( ()=> target.textContent = original , 2000 );
} ,
/**
* 添加单词
* @param {String|String[]} textOrTextArray
* @param {MouseEvent} event
*/
addWord(text, event) {
chromeCall('storage.local.get', ['access_token'])
.then((res) => {
if (res.access_token) {
this.access_token = res.access_token;
this.queryWord(text, event);
} else {
alert('未绑定扇贝账号,请授权绑定')
this.gotoAccessToken();
}
});
},
/**
* 添加单词
* @param {String|String[]} textOrTextArray
* @param {MouseEvent} event
*/
addGlossary(text, event) {
chromeCall('storage.local.get', ['reddwarf_access_token'])
.then((res) => {
if (res.access_token) {
this.access_token = res.access_token;
this.queryWord(text, event);
} else {
alert('未绑定红矮星账号,请授权绑定')
this.gotoReddwarfAccessToken();
}
});
},
gotoAccessToken() {
chrome.runtime.sendMessage({ action: 'shanbay_authorize' })
},
gotoReddwarfAccessToken() {
chrome.runtime.sendMessage({ action: 'reddwarf_authorize' })
},
queryWord(text, event) {
let params = { word: text, access_token: this.access_token }
request.get('https://api.shanbay.com/bdc/search/')
.query(params)
.end((err, res) => {
switch (res.status) {
case 200:
let info = res.body
if (info.status_code == 0) {
this.realAddWord(info.data.id, event);
} else {
alert(`查词错误, ${info.msg}`)
}
break;
case 401:
alert('token 失效,请重新授权')
this.gotoAccessToken()
break;
case 429:
alert('今日请求次数过多')
break;
default:
alert(`未知错误, ${err}`)
break;
}
})
},
realAddWord(id, event) {
let params = { id: id, access_token: this.access_token }
request.post('https://api.shanbay.com/bdc/learning/')
.type('form')
.send(params)
.end((err, res) => {
switch (res.status) {
case 200:
let info = res.body
if (info.status_code == 0) {
const { target } = event;
let original = target.textContent;
target.textContent = '已添加';
setTimeout(() => target.textContent = original, 2000);
} else {
alert(`添加单词发生错误, ${info.msg}`)
}
break;
default:
alert(`添加单词发生错误, ${err}`)
break;
}
})
},
/**
* 播放语音
* @param {String|String[]} textOrTextArray
* @param {String} [lang] - 文本的语种
*/
play( textOrTextArray , lang ) {
if ( Array.isArray( textOrTextArray ) ) {
textOrTextArray = textOrTextArray.join( 'n' );
}
this.$options.client.send( 'play' , {
text : textOrTextArray ,
api : this.query.api ,
from : lang
} );
}
} ,
mixins : [ widgetMixin ]
} );
I read the docs that tell me should register the custom lable like this:
components : {
'st-span' : <component>
},
but I did not know how to implement the custom <component>
, how to define the <component>
of st-span
/st-div
/st-footer
?