This commit is contained in:
zyronon
2023-09-21 01:43:24 +08:00
parent a262f1ec49
commit 5c1b5cf70e
7 changed files with 245 additions and 1306 deletions

View File

@@ -6,6 +6,7 @@ defineProps<{
keyboard?: string,
active?: boolean
disabled?: boolean
loading?: boolean
size?: string
}>()
@@ -19,14 +20,20 @@ function click() {
<template>
<Tooltip :disabled="!keyboard" :title="`快捷键: ${keyboard}`">
<div class="my-button"
@click="!disabled && $emit('click')"
@click="(!disabled && !loading) && $emit('click')"
:class="[
active && 'active',
size,
disabled && 'disabled',
(disabled||loading) && 'disabled',
!disabled && 'hvr-grow'
]">
<span><slot></slot></span>
<span :style="{opacity:loading?0:1}"><slot></slot></span>
<Icon v-if="loading"
class="loading"
icon="eos-icons:loading"
width="18"
color="#ffffff"
/>
<div class="key-notice" v-if="keyboard">
<Icon icon="bi:keyboard" width="14" color="#ffffff"/>
<span class="key">{{ keyboard }}</span>
@@ -52,6 +59,11 @@ function click() {
//background: var(--color-second-bg);
height: 36rem;
line-height: 1;
position: relative;
.loading {
position: absolute;
}
&.disabled {
opacity: .6;

View File

@@ -10,39 +10,12 @@ import {
updateLocalSentenceTranslate
} from "@/hooks/translate.ts";
import * as copy from "copy-to-clipboard";
import {getSplitTranslateText, splitArticle} from "@/hooks/article.ts";
import {getSplitTranslateText, splitEnArticle} from "@/hooks/article.ts";
import EditAbleText from "@/components/EditAbleText.vue";
import {Icon} from "@iconify/vue";
import {cloneDeep} from "lodash-es";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useDisableEventListener, useEsc} from "@/hooks/event.ts";
let article1 = `How does the older investor differ in his approach to investment from the younger investor?
There is no shortage of tipsters around offering 'get-rich-quick' opportunities. But if you are a serious private investor, leave the Las Vegas mentality to those with money to fritter. The serious investor needs a proper 'portfolio' -- a well-planned selection of investments, with a definite structure and a clear aim. But exactly how does a newcomer to the stock market go about achieving that?
Well, if you go to five reputable stock brokers and ask them what you should do with your money, you're likely to get five different answers, -- even if you give all the relevant information about your age age, family, finances and what you want from your investments. Moral? There is no one 'right' way to structure a portfolio. However, there are undoubtedly some wrong ways, and you can be sure that none of our five advisers would have suggested sinking all (or perhaps any) of your money into Periwigs*.
So what should you do? We'll assume that you have sorted out the basics -- like mortgages, pensions, insurance and access to sufficient cash reserves. You should then establish your own individual aims. These are partly a matter of personal circumstances, partly a matter of psychology.
For instance, if you are older you have less time to recover from any major losses, and you may well wish to boost your pension income. So preserving your capital and generating extra income are your main priorities. In this case, you'd probably construct a portfolio with some shares (but not high risk ones), along with gilts, cash deposits, and perhaps convertibles or the income shares of split capital investment trusts.
If you are younger, and in a solid financial position, you may decide to take an aggressive approach -- but only if you're blessed with a sanguine disposition and won't suffer sleepless nights over share prices. If portfolio, alongside your more pedestrian in vestments. Once you have decided on your investment aims, you can then decide where to put your money. The golden rule here is spread your risk -- if you put all of your money into Periwigs International, you're setting yourself up as a hostage to fortune.
*'Periwigs' is the name of a fictitious company.
INVESTOR'S CHRONICLE, March 23 1990`
article1 = `How does the older investor differ in his approach to investment from the younger investor?`
article1 = `Last week I went to the theatre. I had a very good seat. The play was very interesting. I did not enjoy it. A young man and a young woman were sitting behind me. They were talking loudly. I got very angry. I could not hear the actors. I turned round. I looked at the man and the woman angrily. They did not pay any attention. In the end, I could not bear it. I turned round again. I can't hear a word! I said angrily.
It's none of your business, the young man said rudely. This is a private conversation!`
// article1 = `It's none of your business, the young man said rudely. This is a private conversation!`
let article2 = `What is one of the features of modern camping where nationality is concerned?
Economy is one powerful motive for camping, since after the initial outlay upon equipment, or through hiring it, the total expense can be far less than the cost of hotels. But, contrary to a popular assumption, it is far from being the only one, or even the greatest. The man who manoeuvres carelessly into his twenty pounds' worth of space at one of Europe's myriad permanent sites may find himself bumping a Bentley. More likely, Ford Escort will be hub to hub with Renault or Mercedes, but rarely with bicycles made for two.
That the equipment of modern camping becomes yearly more sophisticated is an entertaining paradox for the cynic, a brighter promise for the hopeful traveler who has sworn to get away from it all. It also provides-and some student sociologist might care to base his thesis upon the phenomenon -- an escape of another kind. The modern traveller is often a man who dislikes the Splendide and the Bellavista, not because he cannot afford, or shuns their material comforts. but because he is afford of them. Affluent he may be, but he is by no means sure what to tip the doorman or the chambermaid. Master in his own house, he has little idea of when to say boo to a maitre d'hotel.
From all such fears camping releases him. Granted, a snobbery of camping itself, based upon equipment and techniques, already exists; but it is of a kind that, if he meets it, he can readily understand and deal with. There is no superior 'they' in the shape of managements and hotel hierarchies to darken his holiday days.
To such motives, yet another must be added. The contemporary phenomenon of car worship is to be explained not least by the sense of independence and freedom that ownership entails. To this pleasure camping gives an exquisite refinement.
From one's own front door to home or foreign hills or sands and back again, everything is to hand. Not only are the means of arriving at the holiday paradise entirely within one's own command and keeping, but the means of escape from holiday hel (if the beach proves too crowded, the local weather too inclement) are there, outside -- or, as likely, part of -- the tent.
Idealists have objected to the package tour, that the traveller abroad thereby denies himself the opportunity of getting to know the people of the country visited. Insularity and self-containment, it is argued, go hand in hand. The opinion does not survive experience of a popular Continental camping place. Holiday hotels tend to cater for one nationality of visitors especially, sometimes exclusively. Camping sites, by contrast, are highly cosmopolitan. Granted, a preponderance of Germans is a characteristic that seems common to most Mediterranean sites; but as yet there is no overwhelmingly specialized patronage. Notices forbidding the open-air drying of clothes, or the use of water points for car washing, or those inviting 'our camping friends' to a dance or a boat trip are printed not only in French or Italian or Spanish, but also in English, German and Dutch. At meal times the odour of sauerkraut vies with that of garlic. The Frenchman's breakfast coffee competes with the Englishman's bacon and eggs.
Whether the remarkable growth of organized camping means the eventual death of the more independent kind is hard to say. Municipalities naturally want to secure the campers' site fees and other custom. Police are wary of itinerants who cannot be traced to a recognized camp boundary or to four walls. But most probably it will all depend upon campers themselves: how many heath fires they cause; how much litter they leave; in short, whether or not they wholly alienate landowners and those who live in the countryside. Only good scouting is likely to preserve the freedoms so dear to the heart of the eternal Boy Scout.
NIGEL BUXTON The Great Escape from The Weekend Telegraph`
// article2 = `Economy is one powerful motive for camping? since after the initial outlay upon equipment, or through hiring it, the total expense can be far less than the cost of hotels. But, contrary to a popular assumption, it is far from being the only one, or even the greatest. The man who manoeuvres carelessly into his twenty pounds' worth of space at one of Europe's myriad permanent sites may find himself bumping a Bentley. More likely, Ford Escort will be hub to hub with Renault or Mercedes, but rarely with bicycles made for two.`
interface IProps {
article?: Article
}
@@ -58,7 +31,7 @@ const TranslateEngineOptions = [
{value: 'baidu', label: '百度'},
{value: 'youdao', label: '有道'},
]
const emit = defineEmits(['close'])
const emit = defineEmits(['close', 'save'])
useDisableEventListener()
useEsc(() => {
@@ -70,7 +43,9 @@ onMounted(() => {
})
function updateSections() {
article.sections = splitArticle(article.article)
let {text, sections} = splitEnArticle(article.article)
article.article = text
article.sections = sections
}
async function startNetworkTranslate() {
@@ -155,6 +130,46 @@ function onFocus() {
function save() {
console.log('article', article)
copy(JSON.stringify(article))
if (!article.title.trim()) {
return ElMessage.error('请填写标题!')
}
if (!article.article.trim()) {
return ElMessage.error('请填写正文!')
}
const saveTemp = ()=>{
article.translateSplit = true
emit('close')
emit('save', cloneDeep(article))
}
if (article.translateType === TranslateType.network) {
if (!article.networkTranslate.trim()) {
return ElMessageBox.confirm('您选择了“网络翻译”,但译文内容却为空白,是否修改为“不需要翻译”并保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
article.translateType = TranslateType.none
saveTemp()
}).catch(() => void 0)
}
}
if (article.translateType === TranslateType.custom) {
if (!article.customTranslate.trim()) {
return ElMessageBox.confirm('您选择了“本地翻译”,但译文内容却为空白,是否修改为“不需要翻译”并保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
article.translateType = TranslateType.none
saveTemp()
}).catch(() => void 0)
}
}
}
watch(() => article.customTranslate, (str: string) => {
@@ -222,6 +237,7 @@ watch(() => article.translateType, () => {
<el-radio-group v-model="article.translateType">
<el-radio-button :label="TranslateType.custom">本地翻译</el-radio-button>
<el-radio-button :label="TranslateType.network">网络翻译</el-radio-button>
<el-radio-button :label="TranslateType.none">不需要翻译</el-radio-button>
</el-radio-group>
</div>
<textarea
@@ -254,7 +270,9 @@ watch(() => article.translateType, () => {
</el-select>
<BaseButton
size="small"
@click="startNetworkTranslate">开始翻译
@click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100"
>开始翻译
</BaseButton>
</div>
@@ -271,7 +289,7 @@ watch(() => article.translateType, () => {
>
</textarea>
<textarea
v-else
v-if="article.translateType === TranslateType.network"
v-model="article.networkTranslate"
@blur="onBlur"
@focus="onFocus"

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,14 @@
<script setup lang="ts">
import {computed, nextTick, watchEffect} from "vue"
import {$ref} from "vue/macros";
import {Article, ArticleWord, ShortKeyMap, Word} from "@/types";
import {Article, ArticleWord, DefaultArticle, ShortKeyMap, Word} from "@/types";
import {useBaseStore} from "@/stores/base";
import {usePracticeStore} from "@/stores/practice.ts";
import TypeWord from "@/components/Practice/TypeWord.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
import {useEventListener, useOnKeyboardEventListener} from "@/hooks/event.ts";
import {cloneDeep} from "lodash-es";
let article1 = `How does the older investor differ in his approach to investment from the younger investor?
There is no shortage of tipsters around offering 'get-rich-quick' opportunities. But if you are a serious private investor, leave the Las Vegas mentality to those with money to fritter. The serious investor needs a proper 'portfolio' -- a well-planned selection of investments, with a definite structure and a clear aim. But exactly how does a newcomer to the stock market go about achieving that?
@@ -60,18 +61,7 @@ interface IProps {
}
const props = withDefaults(defineProps<IProps>(), {
article: {
"title": "",
"titleTranslate": "",
article: '',
"customTranslate": "",
networkTranslate: '',
"isTranslated": false,
"newWords": [],
"articleAllWords": [],
"sections": [],
"translateType": 0
},
article: () => cloneDeep(DefaultArticle),
sectionIndex: 0,
sentenceIndex: 0,
wordIndex: 0,
@@ -149,6 +139,7 @@ const currentIndex = computed(() => {
function onKeyDown(e: KeyboardEvent) {
if (tabIndex !== 0) return
if (!props.article.sections.length) return
// console.log('keyDown', e.key, e.code, e.keyCode)
wrong = ''
let currentSection = props.article.sections[sectionIndex]

View File

@@ -27,8 +27,10 @@ export const EnKeyboardMap: KeyboardMap = {
QuoteRight: '"',
}
export function splitArticle(article: string, lang: string = 'en', keyboardMap: KeyboardMap = EnKeyboardMap): Sentence[][] {
export function splitEnArticle(text: string, lang: string = 'en', keyboardMap: KeyboardMap = EnKeyboardMap): {
sections: Sentence[][],
text: string
} {
let sections: Sentence[][] = []
let section: Sentence[] = []
let sentence: Sentence = {
@@ -40,17 +42,18 @@ export function splitArticle(article: string, lang: string = 'en', keyboardMap:
sections.push(section)
let word = cloneDeep({...DefaultArticleWord, name: '', nextSpace: true});
//加\n用于添加最后一段
article += '\n'
if (lang === 'en') {
article = article.replaceAll(``, '"')
article = article.replaceAll(``, '"')
article = article.replaceAll(``, '"')
article = article.replaceAll(``, '"')
}
text += '\n'
text = text.replaceAll(``, '"')
text = text.replaceAll(``, '"')
text = text.replaceAll(``, '"')
text = text.replaceAll(``, '"')
//替换所有单引号为双引号
text = text.replaceAll(`'`, '"')
//将缩写词的双引号替换回单引号
text = text.replaceAll(`"t`, `'t`)
text = text.replaceAll(`"s`, `'s`)
// console.log('articles', articles)
article.split('').map((v, i, arr) => {
text.split('').map((v, i, arr) => {
switch (v) {
case ' ':
if (word.name) {
@@ -165,7 +168,10 @@ export function splitArticle(article: string, lang: string = 'en', keyboardMap:
}, '')
})
})
return sections
return {
text,
sections
}
}
export function splitCNArticle(article: string, lang: string = 'cn', keyboardMap: KeyboardMap = CnKeyboardMap): Sentence[][] {

View File

@@ -1,7 +1,7 @@
import {Article, Sentence, TranslateEngine, TranslateType} from "@/types.ts";
import Baidu from "@opentranslate/baidu";
import {axiosInstance} from "@/utils/http.ts";
import {getSplitTranslateText, splitArticle} from "@/hooks/article.ts";
import {getSplitTranslateText, splitEnArticle} from "@/hooks/article.ts";
import {Translator} from "@opentranslate/translator/src/translator.ts";
export function updateLocalSentenceTranslate(article: Article, translate: string) {
@@ -191,7 +191,7 @@ export async function getNetworkTranslate(
}
export function test(article: Article) {
if (article?.isTranslated) {
if (article?.translateSplit) {
if (!article.sections?.length) {
}
@@ -203,7 +203,7 @@ export function test(article: Article) {
updateLocalSentenceTranslate(article, article.networkTranslate)
}
} else {
article.sections = splitArticle(article.article)
// article.sections = splitEnArticle(article.article)
if (article.customTranslate) {
article.customTranslate = getSplitTranslateText(article.customTranslate)
updateLocalSentenceTranslate(article, article.customTranslate)

View File

@@ -1,15 +1,15 @@
export type Word = {
"name": string,
"usphone": string,
"ukphone": string,
"trans": string[]
"name": string,
"usphone": string,
"ukphone": string,
"trans": string[]
}
export const DefaultWord: Word = {
name: '',
usphone: '',
ukphone: '',
trans: []
name: '',
usphone: '',
ukphone: '',
trans: []
}
export const SaveKey = 'type-word-config'
@@ -23,201 +23,202 @@ export type LanguageCategoryType = 'en' | 'ja' | 'de' | 'code'
export type DictionaryResource = {
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
//override default pronunciation when not undefined
defaultPronIndex?: number
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
//override default pronunciation when not undefined
defaultPronIndex?: number
}
export type Dictionary = {
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
// calculated in the store
chapterCount: number
//override default pronunciation when not undefined
defaultPronIndex?: number
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
// calculated in the store
chapterCount: number
//override default pronunciation when not undefined
defaultPronIndex?: number
}
export type PronunciationConfig = {
name: string
pron: PronunciationType
name: string
pron: PronunciationType
}
export type LanguagePronunciationMapConfig = {
defaultPronIndex: number
pronunciation: PronunciationConfig[]
defaultPronIndex: number
pronunciation: PronunciationConfig[]
}
export type LanguagePronunciationMap = {
[key in LanguageType]: LanguagePronunciationMapConfig
[key in LanguageType]: LanguagePronunciationMapConfig
}
export type SoundResource = {
key: string
name: string
filename: string
key: string
name: string
filename: string
}
export interface DictJson {
description: string,
category: string,
tags: string[],
url: string,
length: number,
language: string,
languageCategory: string,
description: string,
category: string,
tags: string[],
url: string,
length: number,
language: string,
languageCategory: string,
}
export enum DictType {
newDict = 'newDict',
skipDict = 'skipDict',
wrongDict = 'wrongDict',
publicDict = 'publicDict',
customDict = 'customDict',
publicArticle = 'publicArticle',
customArticle = 'customArticle'
newDict = 'newDict',
skipDict = 'skipDict',
wrongDict = 'wrongDict',
publicDict = 'publicDict',
customDict = 'customDict',
publicArticle = 'publicArticle',
customArticle = 'customArticle'
}
export const DefaultArticleWord: ArticleWord = {
name: '',
usphone: '',
ukphone: '',
trans: [],
nextSpace: true,
isSymbol: false,
symbolPosition: ''
name: '',
usphone: '',
ukphone: '',
trans: [],
nextSpace: true,
isSymbol: false,
symbolPosition: ''
}
export interface ArticleWord extends Word {
nextSpace: boolean,
isSymbol: boolean,
symbolPosition: 'start' | 'end' | '',
nextSpace: boolean,
isSymbol: boolean,
symbolPosition: 'start' | 'end' | '',
}
export interface Sentence {
text: string,
translate: string,
words: ArticleWord[]
text: string,
translate: string,
words: ArticleWord[]
}
export enum TranslateType {
custom = 0,
network = 1
custom = 0,
network = 1,
none = 2
}
export interface Article {
title: string,
titleTranslate: string,
article: string,
customTranslate: string,
networkTranslate: string,
isTranslated: boolean,
newWords: Word[],
articleAllWords: string[],
sections: Sentence[][],
translateType: TranslateType
title: string,
titleTranslate: string,
article: string,
customTranslate: string,
networkTranslate: string,
translateSplit: boolean,//翻译是否分割
newWords: Word[],
articleAllWords: string[],
sections: Sentence[][],
translateType: TranslateType
}
export const DefaultArticle: Article = {
title: '',
titleTranslate: '',
article: '',
customTranslate: '',
networkTranslate: '',
isTranslated: false,
newWords: [],
articleAllWords: [],
sections: [],
translateType: TranslateType.network
title: '',
titleTranslate: '',
article: '',
customTranslate: '',
networkTranslate: '',
translateSplit: false,
newWords: [],
articleAllWords: [],
sections: [],
translateType: TranslateType.network
}
export interface Dict {
name: string,
sort: Sort,
type: DictType,
originWords: Word[],//原始单词
words: Word[],
chapterWordNumber: number,//章节单词数量
chapterWords: Word[][],
articles: Article[],
chapterIndex: number,
chapterWordIndex: number,
statistics: Statistics[],
url: string,
name: string,
sort: Sort,
type: DictType,
originWords: Word[],//原始单词
words: Word[],
chapterWordNumber: number,//章节单词数量
chapterWords: Word[][],
articles: Article[],
chapterIndex: number,
chapterWordIndex: number,
statistics: Statistics[],
url: string,
}
export interface Statistics {
startDate: number,//开始日期
endDate: number//结束日期
spend: number,//花费时间
total: number//单词数量
wrongWordNumber: number//错误数
correctRate: number//正确率
startDate: number,//开始日期
endDate: number//结束日期
spend: number,//花费时间
total: number//单词数量
wrongWordNumber: number//错误数
correctRate: number//正确率
}
export interface DisplayStatistics extends Statistics {
wrongWords: Word[]
wrongWords: Word[]
}
export const DefaultDisplayStatistics: DisplayStatistics = {
startDate: Date.now(),
endDate: -1,
spend: -1,
total: -1,
correctRate: -1,
wrongWordNumber: -1,
wrongWords: [],
startDate: Date.now(),
endDate: -1,
spend: -1,
total: -1,
correctRate: -1,
wrongWordNumber: -1,
wrongWords: [],
}
export enum Sort {
normal = 0,
random = 1,
reverse = 2
normal = 0,
random = 1,
reverse = 2
}
export interface State {
newWordDict: Dict,
skipWordDict: Dict,
wrongWordDict: Dict,
dict: Dict,
oldDicts: Dict[],
current: {
dictType: DictType,
words: Word[],
index: number,
wrongWords: Word[],
originWrongWords: Word[],
repeatNumber: number,
statistics: Statistics
},
simpleWords: string[],
sideIsOpen: boolean,
isDictation: boolean,
theme: string,
load: boolean
newWordDict: Dict,
skipWordDict: Dict,
wrongWordDict: Dict,
dict: Dict,
oldDicts: Dict[],
current: {
dictType: DictType,
words: Word[],
index: number,
wrongWords: Word[],
originWrongWords: Word[],
repeatNumber: number,
statistics: Statistics
},
simpleWords: string[],
sideIsOpen: boolean,
isDictation: boolean,
theme: string,
load: boolean
}
export const ShortKeyMap = {
Show: 'Escape',
Ignore: 'Tab',
Remove: '`',
Collect: 'Enter',
Show: 'Escape',
Ignore: 'Tab',
Remove: '`',
Collect: 'Enter',
}
export enum TranslateEngine {
Baidu = 0,
Baidu = 0,
}