impr:change the word and article Settings to pop-up boxes
This commit is contained in:
@@ -96,7 +96,7 @@
|
||||
"id": "hNUgn-",
|
||||
"title": "Not for jazz",
|
||||
"titleTranslate": "不适于演奏爵士乐",
|
||||
"text": "We have an old musical instrument. \nIt is called a clavichord. \nIt was made in Germany in 1681. \nOur clavichord is kept in the living room. \nIt has belonged to our family for a long time. \nThe instrument was bought by my grandfather many years ago. \nRecently it was damaged by a visitor. \nShe tried to play jazz on it! \nShe struck the keys too hard and two of the strings were broken. \nMy father was shocked. \nNow we are not allowed to touch it. \nIt is being repaired by a friend of my fathers.",
|
||||
"text": "We have an old musical instrument. \nIt is called a clavichord. \nIt was made in Germany in 1681. \nOur clavichord is kept in the living room. \nIt has belonged to our family for a long time. \nThe instrument was bought by my grandfather many years ago. \nRecently it was damaged by a visitor. \nShe tried to play jazz on it! \nShe struck the keys too hard and two of the strings were broken. \nMy father was shocked. \nNow we are not allowed to touch it. \nIt is being repaired by a friend of my father's.",
|
||||
"textTranslate": "我家有件古乐器, \n被称作古钢琴, \n是1681年德国造的。 \n我们的这架古钢琴存放在起居室里。 \n我们家有这件乐器已经很久了, \n是我祖父在很多年以前买的。 \n可它最近被一个客人弄坏了, \n因为她用它来弹奏爵士乐。 \n她在击琴键时用力过猛,损坏了两根琴弦。 \n我父亲大为吃惊, \n不许我们再动它。 \n父亲的一个朋友正在修理这件乐器。",
|
||||
"newWords": [],
|
||||
"textAllWords": [],
|
||||
|
||||
@@ -73,61 +73,71 @@
|
||||
"id": "FroVsX",
|
||||
"title": "The sporting spirit",
|
||||
"titleTranslate": "体育的精神",
|
||||
"text": "I am always amazed when I hear people saying that sport creates goodwill between the nations, \nand that if only the common peoples of the would could meet one another at football or cricket, they would have no inclination to meet on the hattlefield. \nEven if one didn't know from concrete examples (the 1936 Olympic Games, for instance) that international sporting contests lead to orgies of hatred, one could deduce if from general principles. \n\nNearly all the sports practised nowadays are competitive. \nYou play to win, \nand the game has little meaning unless you do your utmost to win. \nOn the village green, where you pick up sides and no feeling of local patriotism is involved, it is possible to play simply for the fun and exercise:but as soon as a the question of prestige arises, as soon as you feel that you and some larger unit will be disgraced if you lose, the most savage combative instincts are aroused. \nAnyone who has played even in a school football match knows this. \nAt the international level, sport is frankly mimic warfare. \nBut the significant thing is not the behaviour of the players but the attitude of the spectators:and, behind the spectators, of the nations who work themselves into furies over these absurd contests, \nand seriously believe--at any rate for short periods--that running, jumping and kicking a ball are tests of national virtue. \n\nGEORGE ORWELL The sporting spirit",
|
||||
"textTranslate": "当我听到人们说体育在国家之间创造善意时,我总是很惊讶, \n如果威尔的普通民众能在足球或板球比赛中相遇,他们就不会有在哈特菲尔德见面的意愿。 \n即使人们从具体的例子(例如1936年奥运会)中不知道国际体育比赛会导致仇恨的狂欢,人们也可以从一般原则中推断出来。 \n\n现在几乎所有的运动都是竞争性的。 \n你打球是为了赢, \n除非你竭尽全力去赢得比赛,否则这场比赛毫无意义。 \n在村庄的绿地上,你站在一边,没有当地爱国主义的感觉,可以纯粹为了娱乐和锻炼而玩:但一旦出现声望问题,一旦你觉得如果你输了,你和一些更大的单位会蒙羞,最野蛮的战斗本能就会被激发出来。 \n任何参加过学校足球比赛的人都知道这一点。 \n在国际层面上,体育坦率地说是模仿战争。 \n但重要的不是球员的行为,而是观众的态度:在观众背后,是那些对这些荒谬的比赛大发雷霆的国家, \n并认真相信——至少在短时间内——跑步、跳跃和踢球是对民族美德的考验。 \n\n乔治·奥威尔体育精神",
|
||||
"text": "I am always amazed when I hear people saying that sport creates goodwill between the nations, and that if only the common peoples of the would could meet one another at football or cricket, they would have no inclination to meet on the hattlefield. \nEven if one didn't know from concrete examples (the 1936 Olympic Games, for instance) that international sporting contests lead to orgies of hatred, one could deduce if from general principles.\n\nNearly all the sports practised nowadays are competitive. \nYou play to win, and the game has little meaning unless you do your utmost to win. \nOn the village green, where you pick up sides and no feeling of local patriotism is involved, it is possible to play simply for the fun and exercise: but as soon as the question of prestige arises, as soon as you feel that you and some larger unit will be disgraced if you lose, the most savage combative instincts are aroused. \nAnyone who has played even in a school football match knows this. \nAt the international level, sport is frankly mimic warfare. \nBut the significant thing is not the behaviour of the players but the attitude of the spectators:and, behind the spectators, of the nations who work themselves into furies over these absurd contests, and seriously believe--at any rate for short periods--that running, jumping and kicking a ball are tests of national virtue.\n\nGEORGE ORWELL The sporting spirit",
|
||||
"textTranslate": "当我听人们说体育运动可创造国家之间的友谊,还说各国民众若在足球场或板球场上交锋,就不愿在战场上残杀的时候,我总是惊愕不已。 \n一个人即使不能从具体的事例(例如1936年的奥林匹克运动会)了解到国际运动比赛会导致疯狂的仇恨,也可以从常理中推断出结论。 \n\n现在开展的体育运动几乎都是竞争性的。 \n参加比赛就是为了取胜。如果不拚命去赢,比赛就没有什么意义了。 \n在乡间的草坪上,当你随意组成两个队,并且不涉及任何地方情绪时,那才可能是单纯的为了娱乐和锻炼而进行比赛。可是一旦涉及到荣誉问题,一旦你想到你和某一团体会因为你输而丢脸时,那么最野蛮的争斗天性便会激发起来。 \n即使是仅仅参加过学校足球赛的人也有种体会。 \n在国际比赛中,体育简直是一场模拟战争。 \n但是,要紧的还不是运动员的行为,而是观众的态度,以及观众身后各个国家的态度。面对着这些荒唐的比赛,参赛的各个国家会如痴如狂,甚至煞有介事地相信 — 至少在短期内如此 — 跑跑、跳跳、踢踢球是对一个民族品德素质的检验。",
|
||||
"newWords": [],
|
||||
"textAllWords": [],
|
||||
"audioSrc": "",
|
||||
"lrcPosition": [],
|
||||
"questions": []
|
||||
"audioSrc": "/sound/article/nce4/06-The Sporting Spirit.mp3",
|
||||
"audioFileId": "",
|
||||
"lrcPosition": [[14.09,30.32],[30.32,45.53],[45.53,49.4],[49.4,55.66],[55.66,79.34],[79.34,83.62],[83.62,88.62],[88.62,112.23],[112.23,116.31]],
|
||||
"questions": [],
|
||||
"nameList": [],
|
||||
"textAllWords": []
|
||||
},
|
||||
{
|
||||
"id": "tCid2P",
|
||||
"title": "Bats",
|
||||
"titleTranslate": "蝙蝠",
|
||||
"text": "Not all sounds made by animals serve as language, \nand we have only to turn to that extraordinary discovery of echo-location in bats to see a case in which the voice plays a strictly utilitarian role. \n\nTo get a full appreciation of what this means we must turn first to some recent human inventions. \nEveryone knows that if he shouts in the vicinity of a wall or a mountainside, an echo will come back. \nThe further off this solid obstruction, the longer time will elapse for the return of the echo. \nA sound made by tapping on the hull of a ship will be reflected from the sea bottom, \nand by measuring the time interval between the taps and the receipt of the echoes, the depth of the sea at that point can be calculated. \nSo was born the echo-sounding apparatus, now in general use in ships. \nEvery solid object will reflect a sound, varying according to the size and nature of the object. \nA shoal of fish will do this. \nSo it is a comparatively simple step from locating the sea bottom to locating a shoal of fish. \nWith experience, \nand with improved apparatus, it is now possible not only to locate a shoal but to tell if it is herring, cod, or other well-known fish, by the pattern of its echo. \n\nIt has been found that certain bats emit squeaks and by receiving the echoes, they can locate and steer clear of obstacles--or locate flying insects on which they feed. \nThis echo-location in bats is often compared with radar, the principle of which is similar. \n\n--MAURICE BURTON Curiosities of animal--",
|
||||
"textTranslate": "并非所有动物发出的声音都是语言, \n我们只要看看蝙蝠回声定位的非凡发现,就能看到声音在其中起着严格实用作用的案例。 \n\n为了充分理解这意味着什么,我们必须首先转向一些最近的人类发明。 \n每个人都知道,如果他在墙壁或山腰附近大喊大叫,回声就会回来。 \n离固体障碍物越远,回声返回的时间就越长。 \n敲击船体发出的声音会从海底反射回来, \n通过测量抽头和接收回声之间的时间间隔,可以计算出该点的海水深度。 \n回声测深仪就这样诞生了,现在在船上普遍使用。 \n每个固体物体都会反射声音,根据物体的大小和性质而变化。 \n一群鱼就能做到这一点。 \n因此,从定位海底到定位鱼群是一个相对简单的步骤。 \n根据经验, \n通过改进的设备,现在不仅可以定位鱼群,还可以通过回声模式分辨出它是鲱鱼、鳕鱼还是其他众所周知的鱼类。 \n\n人们发现,某些蝙蝠会发出吱吱声,通过接收回声,它们可以定位并避开障碍物,或者找到它们赖以为生的飞行昆虫。 \n蝙蝠的这种回声定位经常与雷达进行比较,雷达的原理是相似的。 \n\n--莫里斯·伯顿动物好奇心--",
|
||||
"text": "Not all sounds made by animals serve as language, \nand we have only to turn to that extraordinary discovery of echo-location in bats to see a case in which the voice plays a strictly utilitarian role.\n\nTo get a full appreciation of what this means we must turn first to some recent human inventions. \nEveryone knows that if he shouts in the vicinity of a wall or a mountainside, an echo will come back. \nThe further off this solid obstruction, the longer time will elapse for the return of the echo. \nA sound made by tapping on the hull of a ship will be reflected from the sea bottom, \nand by measuring the time interval between the taps and the receipt of the echoes, the depth of the sea at that point can be calculated. \nSo was born the echo-sounding apparatus, now in general use in ships. \nEvery solid object will reflect a sound, varying according to the size and nature of the object. \nA shoal of fish will do this. \nSo it is a comparatively simple step from locating the sea bottom to locating a shoal of fish. \nWith experience, and with improved apparatus, it is now possible not only to locate a shoal but to tell if it is herring, cod, or other well-known fish, by the pattern of its echo.\n\nIt has been found that certain bats emit squeaks and by receiving the echoes, they can locate and steer clear of obstacles--or locate flying insects on which they feed. \nThis echo-location in bats is often compared with radar, the principle of which is similar.\n\nMAURICE BURTON Curiosities of animal life",
|
||||
"textTranslate": "动物发出的声音不都是用作语言交际。 \n我们只要看一看蝙蝠回声定位这一极不寻常的发现,就可以探究一下声音在什么情况下有绝对的实用价值。 \n\n要透彻理解这句话的意义,我们应先回顾一下人类最近的几项发明。 \n大家都知道,在墙壁或山腰附近发出的喊声,就会听到回声。 \n固体障碍物越远。回声返回所用时间就越长。 \n敲打船体所发了的声音会从海底传回来, \n测出回声间隔的时间,便可算出该处海洋的深度。 \n这样就产生了目前各种船舶上普遍应用的回声探测仪。 \n任何固体者反射声音,反射的声音因物体的大小和性质的不同而不同。 \n鱼群也反射声音。 \n从测定海深到测定鱼群,这一进展比较容易。 \n根据经验和改进了的仪器,不仅能够确定鱼群的位置,而且可以根据鱼群回声的特点分辨出是鲱鱼、鳕鱼,这是人们所熟悉的其他鱼。 \n\n人们发现,某些蝙蝠能发出尖叫声,并能通过回声来确定并躲开障碍物,或找到它们赖以为生的昆虫。 \n蝙蝠这种回声定位常常可与雷达相比较,其原理是相似的。 \n\n莫里斯·伯顿 对动物生活的好奇心",
|
||||
"newWords": [],
|
||||
"textAllWords": [],
|
||||
"audioSrc": "",
|
||||
"lrcPosition": [],
|
||||
"questions": []
|
||||
"audioSrc": "/sound/article/nce4/07-Bats.mp3",
|
||||
"audioFileId": "",
|
||||
"lrcPosition": [[17.89,22.31],[22.31,35.21],[35.21,44.33],[44.33,52.71],[52.71,59.88],[61.08,67.71],[67.71,78.93],[79.36,86.48],[86.48,94.94],[94.94,97.58],[97.58,105.89],[105.89,121.55],[121.55,135.34],[135.81,144.16],[145.36,150.6]],
|
||||
"questions": [],
|
||||
"nameList": [],
|
||||
"textAllWords": []
|
||||
},
|
||||
{
|
||||
"id": "miIgSU",
|
||||
"title": "Trading standards",
|
||||
"titleTranslate": "贸易标准",
|
||||
"text": "Chickens slaughtered in the United States, claim officials in Brussels, are not fit to grace European tables. \nNo, say the American:our fowl are fine, we simply clean them in a different way. \nThese days, it is differences in national regulations, far more than tariffs, that put sand in the wheels of trade between rich countries. \nIt is not just farmers who are complaining. \nAn electric razor that meets the European Union's safety standards must be approved by American testers before it can be sold in the United States, \nand an American-made dialysis machine needs the EU's okay before is hits the market in Europe. \n\nAs it happens, a razor that is safe in Europe is unlikely to electrocute Americans. \nSo, ask businesses on both sides of the Atlantic, why have two lots of tests where one would do? \nPoliticians agree, in principle, \nso America and the EU have been trying to reach a deal which would eliminate the need to double-test many products. \nThey hope to finish in time for a trade summit between America and the EU on May 28TH. \nAlthough negotiators are optimistic, the details are complex enough that they may be hard-pressed to get a deal at all. \n\nWhy? \nOne difficulty is to construct the agreements. \nThe Americans would happily reach one accord on standards for medical devices and them hammer out different pacts covering, say, electronic goods and drug manufacturing. \nThe EU--following fine continental traditions--wants agreement on general principles, which could be applied to many types of products and perhaps extended to other countries. \n\n--From:The Economist, May 24th, 1997--",
|
||||
"textTranslate": "布鲁塞尔的官员声称,在美国屠宰的鸡不适合摆在欧洲餐桌上。 \n不,美国人说:我们的家禽很好,我们只是用不同的方式清洗它们。 \n如今,正是国家法规的差异,远远超过了关税,阻碍了富裕国家之间的贸易。 \n抱怨的不仅仅是农民。 \n符合欧盟安全标准的电动剃须刀必须经过美国测试人员的批准,才能在美国销售, \n美国制造的透析机在进入欧洲市场之前需要得到欧盟的批准。 \n\n碰巧的是,在欧洲安全的剃须刀不太可能电死美国人。 \n那么,大西洋两岸的企业都在问,为什么要进行两批测试,而一批测试就可以了? \n政客们原则上同意, \n因此,美国和欧盟一直在努力达成一项协议,以消除对许多产品进行双重测试的需要。 \n他们希望在5月28日美国和欧盟举行贸易峰会之前完成谈判。 \n尽管谈判代表持乐观态度,但细节足够复杂,他们可能很难达成协议。 \n\n为什么? \n一个困难是制定协议。 \n美国人很乐意就医疗器械标准达成一项协议,并制定涵盖电子产品和药品制造等不同领域的协议。 \n欧盟遵循欧洲大陆的优良传统,希望就一般原则达成一致,这些原则可以适用于许多类型的产品,也可能扩展到其他国家。 \n\n--来源:《经济学人》,1997年5月24日--",
|
||||
"text": "Chickens slaughtered in the United States, claim officials in Brussels, are not fit to grace European tables. \nNo, say the American:our fowl are fine, we simply clean them in a different way. \nThese days, it is differences in national regulations, far more than tariffs, that put sand in the wheels of trade between rich countries. \nIt is not just farmers who are complaining. \nAn electric razor that meets the European Union's safety standards must be approved by American testers before it can be sold in the United States, \nand an American-made dialysis machine needs the EU's okay before is hits the market in Europe.\n\nAs it happens, a razor that is safe in Europe is unlikely to electrocute Americans. \nSo, ask businesses on both sides of the Atlantic, why have two lots of tests where one would do? \nPoliticians agree, in principle, so America and the EU have been trying to reach a deal which would eliminate the need to double-test many products. \nThey hope to finish in time for a trade summit between America and the EU on May 28TH. \nAlthough negotiators are optimistic, the details are complex enough that they may be hard-pressed to get a deal at all.\n\nWhy? One difficulty is to construct the agreements. \nThe Americans would happily reach one accord on standards for medical devices and them hammer out different pacts covering, say, electronic goods and drug manufacturing. \nThe EU--following fine continental traditions--wants agreement on general principles, which could be applied to many types of products and perhaps extended to other countries.\n\n--From:The Economist, May 24th, 1997--",
|
||||
"textTranslate": "布鲁塞尔的官员说,在美国屠宰的鸡不适于用来装点欧洲的餐桌。 \n不,美国人说,我们的家禽很好,只是我们使用了另一种清洗方式。 \n当前,是各国管理条例上的差异,而不是关税阻碍了发达国家之间的贸易。 \n并不仅仅是农民在抱怨。 \n一把符合欧洲联盟安全标准的电动剃须刀必须得到美国检测人员的认可,方可在美国市场上销售; \n而美国制造的透析仪也要得到欧盟的首肯才能进入欧洲市场。 \n\n碰巧在欧洲使用安全的剃须刀不大可能使美国人触电身亡, \n因此,大西洋两岸的企业都在问,当一套测试可以解决问题时,为什么需要两套呢? \n政治家在原则上同意了, 因此,美国和欧洲一直在寻求达成协议,以便为许多产品取消双重检查。 \n他们希望尽早达成协议,为5月28日举行的美国和欧洲贸易的最高通级会议作准备。 \n然谈判代表持乐观态度,但协议细节如此复杂,他们所面临的困难很可能使他们无法取得一致。 \n\n为什么呢?困难之一是起草这些协议。 \n美国人很愿意就医疗器械的标准达成一个协议,然后推敲出不同的合同,用以涵盖 -- 比如说 -- 电子产品和药品的生产。 \n欧洲人遵循优良的大陆传统,则希望就普遍的原则取得一致,而这些原则适用于许多不同产品,同时可能延伸到其它国家。 \n\n--来源:《经济学人》,1997年5月24日--",
|
||||
"newWords": [],
|
||||
"textAllWords": [],
|
||||
"audioSrc": "",
|
||||
"lrcPosition": [],
|
||||
"questions": []
|
||||
"audioSrc": "/sound/article/nce4/08-Trading Standards.mp3",
|
||||
"audioFileId": "",
|
||||
"lrcPosition": [[13.09,22],[22,28.94],[28.94,39.33],[39.33,42.33],[42.33,52.75],[52.75,60.32],[61.16,67.6],[67.6,75.57],[75.57,85.21],[85.21,92.01],[92.01,100.07],[100.07,104.69],[104.69,116.17],[116.17,128.92],[128.92,134.83]],
|
||||
"questions": [],
|
||||
"nameList": [],
|
||||
"textAllWords": []
|
||||
},
|
||||
{
|
||||
"id": "f-oMLe",
|
||||
"title": "Royal espionage",
|
||||
"titleTranslate": "王室谍报活动",
|
||||
"text": "Alfred the Great acted his own spy, visiting Danish camps disguised as a minstrel. \nIn those days wandering minstrels were welcome everywhere. \nThey were not fighting men, and their harp was their passport. \nAlfred had learned many of their ballads in his youth, \nand could vary his programme with acrobatic tricks and simple conjuring. \n\nWhile Alfred's little army slowly began to gather at Athelney, the king himself set out to penetrate the camp of Guthrum, the commander of the Danish invaders. \nThere had settled down for the winter at Chippenham:thither Alfred went. \nHe noticed at once that discipline was slack:the Danes had the self-confidence of conquerors, \nand their security precautions were casual. \nThey lived well, on the proceeds of raids on neighbouring regions. \nThere they collected women as well as food and drink, \nand a life of ease had made them soft. \n\nAlfred stayed in the camp a week before he returned to Athelney. \nThe force there assembled was trivial compared with the Danish horde. \nBut Alfred had deduced that the Danes were no longer fit for prolonged battle:and that their commissariat had no organization, \nbut depended on irregular raids. \n\nSo, faced with the Danish advance, Alfred did not risk open battle but harried the enemy. \nHe was constantly on the move, drawing the Danes after him. \nHis patrols halted the raiding parties:hunger assailed the Danish army. \nNow Alfred began a long series of skirmishes--and within a month the Danes had surrendered. \nThe episode could reasonably serve as a unique epic of royal espionage! \n\n--BERNARD NEWMAN Spies in Britain--",
|
||||
"textTranslate": "阿尔弗雷德大帝扮演了自己的间谍,伪装成吟游诗人参观丹麦营地。 \n在那些日子里,到处都欢迎流浪的吟游诗人。 \n他们不是战士,竖琴是他们的通行证。 \n阿尔弗雷德年轻时学过他们的许多民谣, \n他可以用杂技和简单的魔术来改变他的节目。 \n\n当阿尔弗雷德的小军队慢慢开始在阿塞尔尼集结时,国王自己开始渗透丹麦侵略者指挥官古瑟勒姆的营地。 \n希彭纳姆已经安顿下来过冬了,阿尔弗雷德也去了那里。 \n他立刻注意到纪律松弛:丹麦人有征服者的自信, \n他们的安全防范措施也很随意。 \n他们靠袭击邻近地区的收益生活得很好。 \n在那里,他们收集了妇女以及食物和饮料, \n安逸的生活使他们变得柔软。 \n\n阿尔弗雷德在营地呆了一周,然后回到阿塞尔尼。 \n与丹麦部落相比,那里集结的部队微不足道。 \n但阿尔弗雷德推断,丹麦人不再适合长期战斗:他们的粮食没有组织, \n但这取决于不定期的突袭。 \n\n因此,面对丹麦的进攻,阿尔弗雷德没有冒着公开战斗的风险,而是骚扰敌人。 \n他一直在移动,把丹麦人拉到他身后。 \n他的巡逻队阻止了突袭队:饥饿袭击了丹麦军队。 \n现在,阿尔弗雷德开始了一系列的小规模冲突,一个月内,丹麦人投降了。 \n这一集可以合理地成为一部独特的皇家间谍史诗! \n\n--伯纳德·纽曼在英国的间谍--",
|
||||
"text": "Alfred the Great acted his own spy, visiting Danish camps disguised as a minstrel. \nIn those days wandering minstrels were welcome everywhere. \nThey were not fighting men, and their harp was their passport. \nAlfred had learned many of their ballads in his youth, and could vary his programme with acrobatic tricks and simple conjuring.\n\nWhile Alfred's little army slowly began to gather at Athelney, the king himself set out to penetrate the camp of Guthrum, the commander of the Danish invaders. \nThere had settled down for the winter at Chippenham:thither Alfred went. \nHe noticed at once that discipline was slack:the Danes had the self-confidence of conquerors, and their security precautions were casual. \nThey lived well, on the proceeds of raids on neighbouring regions. \nThere they collected women as well as food and drink, and a life of ease had made them soft.\n\nAlfred stayed in the camp a week before he returned to Athelney. \nThe force there assembled was trivial compared with the Danish horde. \nBut Alfred had deduced that the Danes were no longer fit for prolonged battle:and that their commissariat had no organization, but depended on irregular raids.\n\nSo, faced with the Danish advance, Alfred did not risk open battle but harried the enemy. \nHe was constantly on the move, drawing the Danes after him. \nHis patrols halted the raiding parties:hunger assailed the Danish army. \nNow Alfred began a long series of skirmishes--and within a month the Danes had surrendered. \nThe episode could reasonably serve as a unique epic of royal espionage!\n\n--BERNARD NEWMAN Spies in Britain--",
|
||||
"textTranslate": "阿尔弗雷德大帝曾亲自充当间谍。他扮作吟游歌手到丹麦军队的营地里侦察。 \n当时,浪迹天涯的吟游歌手到处受欢迎, \n他们不是作战人员,竖琴就是他们的通行证。 \n阿尔弗德年轻时学过许多民歌, 并能穿插演一些杂技和小魔术使自己的节目多样化。 \n\n阿尔弗雷德人数不多的军队开始在阿塞尔纳慢慢集结时,他亲自潜入丹麦入侵司令官古瑟罗姆的营地。 \n丹麦军已在切本哈姆扎下营准备过冬,阿尔弗雷便来到此地。 \n他马上发现丹麦军纪律松弛,他们以征服者自居,安全措施马马虎虎。 \n他们靠掠夺附近的地区的财物过着舒适的生活。 \n他们不仅搜刮吃的喝的,而且抢掠妇女,安逸的生活已使丹麦军队变得软弱无力。 \n\n阿尔弗雷德在敌营呆了一个星期后,回到了阿塞尔纳。 \n他集结在那里的军队和丹麦大军相比是微不足道的, \n然而,阿尔弗雷德断定,丹麦人已不再适应持久的战争,他们的军需供应处于无组织状态,只是靠临时抢夺来维持。 \n\n因此,面对丹麦人的进攻,阿尔弗雷德没有贸然同敌人作战,而是采用骚扰敌人的战术。 \n他的部队不停地移动,牵着敌人的鼻子,让他们跟着跑。 \n他派出巡逻队阻止敌人抢劫,因而饥饿威胁着丹麦军队。 \n这时,阿尔弗雷德发起一连串小规模的进攻,结果不出一个月,丹麦人就投降了。 \n这一幕历史可以说是王室谍报活动中最精彩的篇章。 \n\n--伯纳德·纽曼 在英国的间谍--",
|
||||
"newWords": [],
|
||||
"textAllWords": [],
|
||||
"audioSrc": "",
|
||||
"lrcPosition": [],
|
||||
"questions": []
|
||||
"audioSrc": "/sound/article/nce4/09-Royal Espionage.mp3",
|
||||
"audioFileId": "",
|
||||
"lrcPosition": [[20.72,29.54],[29.54,34.45],[34.45,39.2],[39.2,49.17],[49.17,62.26],[62.26,69.32],[69.32,81.06],[81.06,86.77],[86.77,94.89],[94.89,100.13],[100.13,105.12],[105.12,118.52],[118.52,126.57],[126.57,131.58],[131.58,137.97],[137.97,145.74],[145.74,151.59],[153.09,157.35]],
|
||||
"questions": [],
|
||||
"nameList": [],
|
||||
"textAllWords": []
|
||||
},
|
||||
{
|
||||
"id": "ASotyh",
|
||||
"title": "Silicon valley",
|
||||
"titleTranslate": "硅谷",
|
||||
"text": "Technology trends may push Silicon Valley back to the future. \nCarver Mead, a pioneer in integrated circuits and a professor of computer science at the California Institute of Technology, notes there are now work-stations that enable engineers to design, test and produce chips right on their desks, much the way an editor creates a newsletter on a Macintosh. \nAs the time and cost of making a chip drop to a few days and a few hundred dollars, engineers may soon be free to let their imaginations soar without being penalized by expensive failures. \nMead predicts that inventors will be able to perfect powerful customized chips over a weekend at the office--spawning a new generation of garage start-ups and giving the U.S. a jump on its foreign rivals in getting new products to market fast. \n'We're got more garages with smart people,' Mead observes. \n'We really thrive on anarchy.' And on Asians. \nAlready, orientals and Asian Americans constitute the majority of the engineering staffs at many Valley firms. \nAnd Chinese, Korean, Filipino and Indian engineers are graduating in droves from California's colleges. \nAs the heads of next-generation start-ups, these Asian innovators can draw on customs and languages to forge righter links with crucial Pacific Rim markets. \nFor instance, Alex Au, a Stanford Ph. \nD. from Hong Kong, has set up a Taiwan factory to challenge Japan's near lock on the memory-chip market. \nIndia-born N.Damodar Reddy's tiny California company reopened an AT & T chip plant in Kansas City last spring with financing from the state of Missouri. \nBefore it becomes a retirement village, Silicon Valley may prove a classroom for building a global business.",
|
||||
"textTranslate": "技术趋势可能会将硅谷推向未来。 \nCarver Mead是集成电路的先驱,也是加州理工学院的计算机科学教授,他指出,现在有了工作站,工程师们可以在办公桌上设计、测试和生产芯片,就像编辑在Macintosh上创建时事通讯一样。 \n随着制造芯片的时间和成本降至几天和几百美元,工程师们可能很快就可以自由地让他们的想象力飙升,而不会受到昂贵的失败的惩罚。 \n米德预测,发明家们将能够在周末的办公室里完善功能强大的定制芯片,催生新一代的车库初创企业,并使美国在快速将新产品推向市场方面领先于外国竞争对手。 \n米德观察到:“我们有更多聪明人的车库。”。 \n“我们真的在无政府状态下茁壮成长。“还有亚洲人。 \n在许多硅谷公司,东方人和亚裔美国人已经占据了工程人员的大多数。 \n中国、韩国、菲律宾和印度的工程师正成群结队地从加州的大学毕业。 \n作为下一代初创企业的领导者,这些亚洲创新者可以利用习俗和语言与关键的环太平洋市场建立更正确的联系。 \n例如,斯坦福大学博士Alex Au。 \n来自香港的D.在台湾建立了一家工厂,以挑战日本对记忆芯片市场的近乎锁定。 \n印度出生的N.Damodar Reddy在加利福尼亚州的一家小公司去年春天在密苏里州的资助下重新开放了位于堪萨斯城的AT&T芯片厂。 \n在成为退休村之前,硅谷可能会成为建立全球业务的教室。",
|
||||
"text": "Technology trends may push Silicon Valley back to the future. \nCarver Mead, a pioneer in integrated circuits and a professor of computer science at the California Institute of Technology, notes there are now work-stations that enable engineers to design, test and produce chips right on their desks, much the way an editor creates a newsletter on a Macintosh. \nAs the time and cost of making a chip drop to a few days and a few hundred dollars, engineers may soon be free to let their imaginations soar without being penalized by expensive failures. \nMead predicts that inventors will be able to perfect powerful customized chips over a weekend at the office--spawning a new generation of garage start-ups and giving the U.S. a jump on its foreign rivals in getting new products to market fast. \n'We're got more garages with smart people,' Mead observes. \n'We really thrive on anarchy.' And on Asians. \nAlready, orientals and Asian Americans constitute the majority of the engineering staffs at many Valley firms. \nAnd Chinese, Korean, Filipino and Indian engineers are graduating in droves from California's colleges. \nAs the heads of next-generation start-ups, these Asian innovators can draw on customs and languages to forge righter links with crucial Pacific Rim markets. \nFor instance, Alex Au, a Stanford Ph.D. from Hong Kong, has set up a Taiwan factory to challenge Japan's near lock on the memory-chip market. \nIndia-born N.Damodar Reddy's tiny California company reopened an AT & T chip plant in Kansas City last spring with financing from the state of Missouri. \nBefore it becomes a retirement village, Silicon Valley may prove a classroom for building a global business.\n\nUS NEWS AND WORLD REPORT, October 2, 1989",
|
||||
"textTranslate": "技术的发展趋势有可能把硅谷重新推向未来。 \n卡弗·米德 -- 集成电路的一位先驱,加州理工学院的计算机教授 -- 注意到,现在有些计算机工作站使工程技术人员可以在他们的办公桌上设计、试验和生产芯片,就像一位编辑在苹果机上编出一份时事通讯一样。 \n由于制造一块芯片的时间已缩短至几天,费用也只有几百美元,因此,工程技术人员可能很块就可充分发挥他们的想像力,而不会因失败而造成经济上的损失。 \n米德预言发明者可以在办公室用一个周末的时间生产了完美的、功能很强的、按客户需求设计的芯片 -- 造就新一代从汽车间起家的技术人员,在把产品推向市场方面使美国把它的外国对手们打个措手不及。 \n“我们有更多的汽车间,那里有许多聪明人,”米德说。 \n“我们确实是靠这种无政府状态发展起来的。” 靠的是亚洲人。 \n硅谷许多公司中工程技术人员的大多数是东方人和亚裔美国人。 \n中国、韩国、菲律宾和印度的工程师一批批地从加州的大学毕业。 \n作为新掘起一代的带头人,亚裔发明家可以凭借他们在习惯和语言上的优势,与关键的太平洋沿岸市场建立起更加牢固的联系。 \n比如说,亚历克斯·奥,一位来自香港的斯坦福大学博士,已经在台湾建厂,对日本在内存条市场上近似垄断的局面提出了挑战。 \n印度出生的N·达莫达·雷迪经营的小小的加州公司在堪萨斯城重新启用了美国电话电报公司的一家芯片工厂,并从密苏里州获取了财政上的支持。 \n在硅谷变成一个退休村之前,它很可能成为建立全球商业的一个教学场地。 \n\n《美国新闻与世界报道》1989年10月2日",
|
||||
"newWords": [],
|
||||
"textAllWords": [],
|
||||
"audioSrc": "",
|
||||
"lrcPosition": [],
|
||||
"questions": []
|
||||
"audioSrc": "/sound/article/nce4/10-Silicon Valley.mp3",
|
||||
"audioFileId": "",
|
||||
"lrcPosition": [[14.59,19.04],[19.04,41.88],[41.88,55.03],[55.03,72.61],[72.61,77.47],[77.47,82.67],[82.67,91.09],[91.09,100.09],[100.09,112.17],[112.17,124.52],[124.52,137.26],[137.26,144.36],[144.96,150.38]],
|
||||
"questions": [],
|
||||
"nameList": [],
|
||||
"textAllWords": []
|
||||
},
|
||||
{
|
||||
"id": "T4p-Jp",
|
||||
|
||||
@@ -53,8 +53,13 @@ export function levelBenefits(params) {
|
||||
}
|
||||
|
||||
export function orderCreate(params) {
|
||||
return http<{ orderNo: string,result:string, }>('/member/orderCreate', params, null, 'post')
|
||||
return http<{ orderNo: string, result: string, }>('/member/orderCreate', params, null, 'post')
|
||||
}
|
||||
|
||||
export function alipayQuery(params) {
|
||||
return http('/member/alipayQuery', null, params, 'get')
|
||||
}
|
||||
|
||||
export function testPay() {
|
||||
return http('/member/testPay', null, null, 'get')
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import {defineAsyncComponent, onMounted, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import { jump2Feedback } from "@/utils";
|
||||
import {jump2Feedback} from "@/utils";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
@@ -17,41 +18,33 @@ watch(() => settingStore.load, (n) => {
|
||||
}
|
||||
}, {immediate: true})
|
||||
|
||||
useDisableEventListener(() => show)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model="show"
|
||||
title="提示"
|
||||
footer
|
||||
:closeOnClickBg="false"
|
||||
cancel-button-text="不再提醒"
|
||||
confirm-button-text="关闭"
|
||||
@cancel="settingStore.conflictNotice = false"
|
||||
<Dialog
|
||||
v-model="show"
|
||||
title="重要提示"
|
||||
footer
|
||||
:closeOnClickBg="false"
|
||||
cancel-button-text="不再提醒"
|
||||
confirm-button-text="关闭"
|
||||
@cancel="settingStore.conflictNotice = false"
|
||||
>
|
||||
<div class="card w-120 center flex-col color-main py-0 mb-0">
|
||||
<div>
|
||||
<div class="text">
|
||||
<div>
|
||||
1、 如果您安装了 <span class="font-bold text-red">“调速” “Vim”</span> 等插件/脚本,将导致本网站无法正常使用。
|
||||
</div>
|
||||
<div>
|
||||
因为它们会强行接管键盘按下事件,<span class="font-bold text-red">导致使用本网站时按 'A'、 'S' 等等按钮无反应</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-4">
|
||||
<div>①:在对应插件/脚本的设置里面排除本网站</div>
|
||||
<div>②:临时禁用对应插件/脚本</div>
|
||||
<div>③:请打开浏览器无痕模式尝试</div>
|
||||
</div>
|
||||
<div class="text mt-2">
|
||||
2、如果您未安装以上插件/脚本,还是无法使用
|
||||
</div>
|
||||
<div class="pl-4">
|
||||
<div>①:请打开浏览器无痕模式尝试</div>
|
||||
<div>②:无痕模式下无法正常使用,请给<span class="color-link mx-1 cp" @click="jump2Feedback">点此</span>给作者反馈
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-150 center flex-col color-main py-0 mb-0">
|
||||
<div class="text">
|
||||
如果您安装了 <span class="font-bold text-red">“调速” “Vim”</span> 等插件/脚本,它们会拦截键盘按下事件,<span
|
||||
class="font-bold text-red">导致在本网站练习时按 'A'、 'S' 、'D' 等键无反应</span>,您可以根据以下步骤解决冲突:
|
||||
</div>
|
||||
<ul class="m-0">
|
||||
<li>用浏览器无痕模式打开本网站,确认能否正常输入?</li>
|
||||
<li>无痕模式下无法输入,请给<span class="color-link mx-1 cp" @click="jump2Feedback">点此</span>反馈</li>
|
||||
<li>无痕模式下可以输入,则是插件/脚本导致的冲突</li>
|
||||
<li>临时禁用对应插件/脚本,或在对应插件/脚本的设置里面排除本网站</li>
|
||||
<li>可安装 <a
|
||||
href="https://chromewebstore.google.com/detail/one-click-extensions-mana/pbgjpgbpljobkekbhnnmlikbbfhbhmem" target="_blank">此插件</a> 来快速激活、禁用其他插件</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@@ -123,10 +123,9 @@ function startStudy() {
|
||||
}
|
||||
window.umami?.track('startStudyArticle', {
|
||||
name: base.sbook.name,
|
||||
index: base.sbook.lastLearnIndex,
|
||||
custom: base.sbook.custom,
|
||||
complete: base.sbook.complete,
|
||||
title: base.sbook.articles[base.sbook.lastLearnIndex].title
|
||||
s:`name:${base.sbook.name},index:${base.sbook.lastLearnIndex},title:${base.sbook.articles[base.sbook.lastLearnIndex].title}`,
|
||||
})
|
||||
nav('/practice-articles/' + store.sbook.id)
|
||||
} else {
|
||||
|
||||
@@ -63,9 +63,9 @@ async function addMyStudyList() {
|
||||
|
||||
window.umami?.track('startStudyArticle', {
|
||||
name: sbook.name,
|
||||
index: sbook.lastLearnIndex,
|
||||
custom: sbook.custom,
|
||||
complete: sbook.complete,
|
||||
s:`name:${sbook.name},index:${sbook.lastLearnIndex},title:${sbook.articles[sbook.lastLearnIndex].title}`,
|
||||
})
|
||||
nav('/practice-articles/' + sbook.id)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, onMounted, onUnmounted, provide, watch } from "vue";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import {computed, onMounted, onUnmounted, provide, watch} from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {
|
||||
Article,
|
||||
ArticleItem,
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
Statistics,
|
||||
Word
|
||||
} from "@/types/types.ts";
|
||||
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
|
||||
import {useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { _getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total } from "@/utils";
|
||||
import { usePracticeStore } from "@/stores/practice.ts";
|
||||
import { useArticleOptions } from "@/hooks/dict.ts";
|
||||
import { genArticleSectionData, usePlaySentenceAudio } from "@/hooks/article.ts";
|
||||
import { getDefaultArticle, getDefaultDict, getDefaultWord } from "@/types/func.ts";
|
||||
import {_getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total} from "@/utils";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
import {getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types/func.ts";
|
||||
import TypingArticle from "@/pages/article/components/TypingArticle.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Panel from "@/components/Panel.vue";
|
||||
@@ -30,13 +30,13 @@ import ArticleList from "@/components/list/ArticleList.vue";
|
||||
import EditSingleArticleModal from "@/pages/article/components/EditSingleArticleModal.vue";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import ConflictNotice from "@/components/ConflictNotice.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import PracticeLayout from "@/components/PracticeLayout.vue";
|
||||
import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
|
||||
import VolumeSetting from "@/pages/article/components/VolumeSetting.vue";
|
||||
import { AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
|
||||
import { addStat, setDictProp } from "@/apis";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import {AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig} from "@/config/env.ts";
|
||||
import {addStat, setDictProp} from "@/apis";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
@@ -591,14 +591,15 @@ provide('currentPractice', currentPractice)
|
||||
></ArticleAudio>
|
||||
<div class="flex flex-col items-center justify-center gap-1">
|
||||
<div class="flex gap-2 center">
|
||||
<VolumeSetting/>
|
||||
<SettingDialog type="article"/>
|
||||
|
||||
<BaseIcon
|
||||
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
@click="skip">
|
||||
<IconFluentArrowBounce20Regular class="transform-rotate-180"/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
:title="`重听(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
:title="`播放当前句子(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
@click="play">
|
||||
<IconFluentReplay20Regular/>
|
||||
</BaseIcon>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import MiniDialog from "@/components/dialog/MiniDialog.vue";
|
||||
import { useWindowClick } from "@/hooks/event.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Switch from "@/components/base/Switch.vue";
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
let show = $ref(false)
|
||||
useWindowClick(() => show = false)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative"
|
||||
@click.stop="null"
|
||||
>
|
||||
<BaseIcon
|
||||
title="播放设置"
|
||||
@click="show = !show">
|
||||
<IconFluentSpeakerSettings20Regular/>
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
width="12rem"
|
||||
v-model="show">
|
||||
<div class="mini-row-title">
|
||||
播放设置
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<label class="">自动播放句子</label>
|
||||
<Switch v-model="settingStore.articleSound"/>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<label class="">自动播放下一篇</label>
|
||||
<Switch v-model="settingStore.articleAutoPlayNext"/>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -39,7 +39,7 @@ const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
}>()
|
||||
|
||||
const tabIndex = $ref(0)
|
||||
const tabIndex = $ref(3)
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const store = useBaseStore()
|
||||
@@ -296,18 +296,6 @@ function transferOk() {
|
||||
<div class="flex flex-1 overflow-hidden gap-4">
|
||||
<div class="left">
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
|
||||
<IconFluentSettings20Regular width="20"/>
|
||||
<span>通用练习设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
|
||||
<IconFluentTextUnderlineDouble20Regular width="20"/>
|
||||
<span>单词练习设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
|
||||
<IconFluentBookLetter20Regular width="20"/>
|
||||
<span>文章练习设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">
|
||||
<IconFluentKeyboardLayoutFloat20Regular width="20"/>
|
||||
<span>快捷键设置</span>
|
||||
@@ -333,224 +321,6 @@ function transferOk() {
|
||||
</div>
|
||||
<div class="col-line"></div>
|
||||
<div class="flex-1 overflow-y-auto overflow-x-hidden pr-4 content">
|
||||
<!-- 通用练习设置-->
|
||||
<!-- 通用练习设置-->
|
||||
<!-- 通用练习设置-->
|
||||
<div v-if="tabIndex === 0">
|
||||
<SettingItem title="忽略大小写"
|
||||
desc="开启后,输入时不区分大小写,如输入“hello”和“Hello”都会被认为是正确的"
|
||||
>
|
||||
<Switch v-model="settingStore.ignoreCase"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="允许默写模式下显示提示"
|
||||
:desc="`开启后,可以通过将鼠标移动到单词上或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
|
||||
>
|
||||
<Switch v-model="settingStore.allowWordTip"/>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="简单词过滤"
|
||||
desc="开启后,练习的单词中不会包含简单词;文章统计的总词数中不会包含简单词"
|
||||
>
|
||||
<Switch v-model="settingStore.ignoreSimpleWord"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="简单词列表"
|
||||
class="items-start!"
|
||||
v-if="settingStore.ignoreSimpleWord"
|
||||
>
|
||||
<Textarea
|
||||
placeholder="多个单词用英文逗号隔号"
|
||||
v-model="simpleWords" :autosize="{minRows: 6, maxRows: 10}"/>
|
||||
</SettingItem>
|
||||
|
||||
<!-- 音效-->
|
||||
<!-- 音效-->
|
||||
<!-- 音效-->
|
||||
<div class="line"></div>
|
||||
<SettingItem main-title="音效"/>
|
||||
<SettingItem title="单词/句子发音口音">
|
||||
<Select v-model="settingStore.soundType"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
>
|
||||
<Option label="美音" value="us"/>
|
||||
<Option label="英音" value="uk"/>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="按键音">
|
||||
<Switch v-model="settingStore.keyboardSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="按键音效">
|
||||
<Select v-model="settingStore.keyboardSoundFile"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
>
|
||||
<Option
|
||||
v-for="item in SoundFileOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div class="flex justify-between items-center w-full">
|
||||
<span>{{ item.label }}</span>
|
||||
<VolumeIcon
|
||||
:time="100"
|
||||
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
|
||||
</div>
|
||||
</Option>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.keyboardSoundVolume" showText showValue unit="%"/>
|
||||
</SettingItem>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 单词练习设置-->
|
||||
<!-- 单词练习设置-->
|
||||
<!-- 单词练习设置-->
|
||||
<div v-if="tabIndex === 1">
|
||||
<SettingItem title="练习模式">
|
||||
<RadioGroup v-model="settingStore.wordPracticeMode" class="flex-col gap-0!">
|
||||
<Radio :value="WordPracticeMode.System" label="智能模式:自动规划学习、复习、听写、默写"/>
|
||||
<Radio :value="WordPracticeMode.Free" label="自由模式:系统不强制复习与默写"/>
|
||||
</RadioGroup>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="显示上一个/下一个单词"
|
||||
desc="开启后,练习中会在上方显示上一个/下一个单词"
|
||||
>
|
||||
<Switch v-model="settingStore.showNearWord"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="不默认显示练习设置弹框"
|
||||
desc="在词典详情页面,点击学习按钮后,是否显示练习设置弹框"
|
||||
>
|
||||
<Switch v-model="settingStore.disableShowPracticeSettingDialog"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="输入错误时,清空已输入内容"
|
||||
>
|
||||
<Switch v-model="settingStore.inputWrongClear"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="单词循环设置" class="gap-0!">
|
||||
<RadioGroup v-model="settingStore.repeatCount">
|
||||
<Radio :value="1" size="default">1</Radio>
|
||||
<Radio :value="2" size="default">2</Radio>
|
||||
<Radio :value="3" size="default">3</Radio>
|
||||
<Radio :value="5" size="default">5</Radio>
|
||||
<Radio :value="100" size="default">自定义</Radio>
|
||||
</RadioGroup>
|
||||
<div class="ml-2 center gap-space" v-if="settingStore.repeatCount === 100">
|
||||
<span>循环次数</span>
|
||||
<InputNumber v-model="settingStore.repeatCustomCount"
|
||||
:min="6"
|
||||
:max="15"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</SettingItem>
|
||||
|
||||
|
||||
<!-- 发音-->
|
||||
<!-- 发音-->
|
||||
<!-- 发音-->
|
||||
<div class="line"></div>
|
||||
<SettingItem mainTitle="音效"/>
|
||||
<SettingItem title="单词自动发音">
|
||||
<Switch v-model="settingStore.wordSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.wordSoundVolume" showText showValue unit="%"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="倍速">
|
||||
<Slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3" showText showValue/>
|
||||
</SettingItem>
|
||||
<div class="line"></div>
|
||||
<SettingItem title="效果音(输入错误、完成时的音效)">
|
||||
<Switch v-model="settingStore.effectSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.effectSoundVolume" showText showValue unit="%"/>
|
||||
</SettingItem>
|
||||
|
||||
<!-- 自动切换-->
|
||||
<!-- 自动切换-->
|
||||
<!-- 自动切换-->
|
||||
<div class="line"></div>
|
||||
<SettingItem mainTitle="自动切换"/>
|
||||
<SettingItem title="自动切换下一个单词"
|
||||
desc="仅在 **跟写** 时生效,听写、辨认、默写均不会自动切换,需要手动按 **空格键** 切换"
|
||||
>
|
||||
<Switch v-model="settingStore.autoNextWord"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="自动切换下一个单词时间"
|
||||
desc="正确输入单词后,自动跳转下一个单词的时间"
|
||||
>
|
||||
<InputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:disabled="!settingStore.autoNextWord"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:step="100"
|
||||
type="number"
|
||||
/>
|
||||
<span class="ml-4">毫秒</span>
|
||||
</SettingItem>
|
||||
|
||||
|
||||
<!-- 字体设置-->
|
||||
<!-- 字体设置-->
|
||||
<!-- 字体设置-->
|
||||
<div class="line"></div>
|
||||
<SettingItem mainTitle="字体设置"/>
|
||||
<SettingItem title="外语字体">
|
||||
<Slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordForeignFontSize" showText showValue unit="px"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="中文字体">
|
||||
<Slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordTranslateFontSize" showText showValue unit="px"/>
|
||||
</SettingItem>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 文章练习设置-->
|
||||
<!-- 文章练习设置-->
|
||||
<!-- 文章练习设置-->
|
||||
<div v-if="tabIndex === 2">
|
||||
<!-- 发音-->
|
||||
<!-- 发音-->
|
||||
<!-- 发音-->
|
||||
<SettingItem mainTitle="音效"/>
|
||||
<SettingItem title="自动播放句子">
|
||||
<Switch v-model="settingStore.articleSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="自动播放下一篇">
|
||||
<Switch v-model="settingStore.articleAutoPlayNext"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.articleSoundVolume" showText showValue unit="%"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="倍速">
|
||||
<Slider v-model="settingStore.articleSoundSpeed" :step="0.1" :min="0.5" :max="3" showText showValue/>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="输入时忽略符号/数字/人名">
|
||||
<Switch v-model="settingStore.ignoreSymbol"/>
|
||||
</SettingItem>
|
||||
</div>
|
||||
|
||||
<div class="body" v-if="tabIndex === 3">
|
||||
<div class="row">
|
||||
@@ -615,6 +385,14 @@ function transferOk() {
|
||||
|
||||
<!-- 日志-->
|
||||
<div v-if="tabIndex === 5">
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/3</div>
|
||||
<div>内容:单词、文章设置修改为弹框,更方便</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
@@ -829,9 +607,6 @@ function transferOk() {
|
||||
<p>
|
||||
GitHub地址:<a :href="GITHUB" target="_blank">{{ GITHUB }}</a>
|
||||
</p>
|
||||
<p>
|
||||
反馈:<a :href="`${GITHUB}/issues`" target="_blank">{{ GITHUB }}/issues</a>
|
||||
</p>
|
||||
<p>
|
||||
作者邮箱:<a :href="`mailto:${EMAIL}`">{{ EMAIL }}</a>
|
||||
</p>
|
||||
@@ -879,17 +654,18 @@ function transferOk() {
|
||||
@apply cursor-pointer flex items-center relative;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: .5rem;
|
||||
width: 10rem;
|
||||
gap: .6rem;
|
||||
transition: all .5s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
import {FormInstance} from "@/components/base/form/types.ts";
|
||||
import {codeRules, emailRules, passwordRules, phoneRules} from "@/utils/validation.ts";
|
||||
import {_dateFormat, cloneDeep} from "@/utils";
|
||||
import {_dateFormat, cloneDeep, jump2Feedback} from "@/utils";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import Code from "@/pages/user/Code.vue";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
@@ -37,10 +37,6 @@ const contactSupport = () => {
|
||||
console.log('Contact support')
|
||||
}
|
||||
|
||||
const goIssues = () => {
|
||||
window.open(GITHUB + '/issues', '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
userStore.fetchUserInfo()
|
||||
})
|
||||
@@ -541,7 +537,7 @@ function onFileChange(e) {
|
||||
|
||||
<!-- 去github issue-->
|
||||
<div class="item cp"
|
||||
@click="goIssues">
|
||||
@click="jump2Feedback()">
|
||||
<div class="flex-1">
|
||||
给 {{ APP_NAME }} 提交意见
|
||||
</div>
|
||||
@@ -554,7 +550,7 @@ function onFileChange(e) {
|
||||
<BaseButton
|
||||
@click="handleLogout"
|
||||
size="large"
|
||||
class="w-[80%]"
|
||||
class="w-[40%]"
|
||||
>
|
||||
登出
|
||||
</BaseButton>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {User} from "@/apis/user.ts";
|
||||
import {computed, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import Header from "@/components/Header.vue";
|
||||
import {
|
||||
alipayQuery,
|
||||
CouponInfo,
|
||||
couponInfo,
|
||||
LevelBenefits,
|
||||
@@ -202,6 +203,8 @@ let orderNo = $ref('')
|
||||
let timer: number = $ref()
|
||||
let showCouponInput = $ref(false)
|
||||
let coupon = $ref<CouponInfo>({code: ''} as CouponInfo)
|
||||
let checkLoading = $ref(false)
|
||||
let showCheckBtn = $ref(false)
|
||||
|
||||
watch(() => startLoop, (n) => {
|
||||
if (n) {
|
||||
@@ -221,8 +224,12 @@ watch(() => startLoop, (n) => {
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
showCheckBtn = true
|
||||
}, 3000)
|
||||
} else {
|
||||
clearInterval(timer)
|
||||
showCheckBtn = false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -269,6 +276,16 @@ async function handlePayment() {
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function checkOrderStatus() {
|
||||
if (checkLoading) return
|
||||
checkLoading = true
|
||||
let res = await alipayQuery({orderNo})
|
||||
if (!res.success) {
|
||||
Toast.info(res.msg || '未付款')
|
||||
}
|
||||
checkLoading = false
|
||||
}
|
||||
|
||||
let couponLoading = $ref(false)
|
||||
|
||||
async function getCouponInfo() {
|
||||
@@ -302,7 +319,7 @@ async function getCouponInfo() {
|
||||
<div class="card-white">
|
||||
<Header title="会员介绍"></Header>
|
||||
<div class="grid grid-cols-3 grid-rows-3 gap-3">
|
||||
<div class="text-lg items-center" v-for="f in data.benefits" :key="f.name">
|
||||
<div class="text-lg flex items-center" v-for="f in data.benefits" :key="f.name">
|
||||
<IconFluentCheckmarkCircle20Regular class="mr-2 text-green-600"/>
|
||||
<span>
|
||||
<span>{{ f.name }}</span>
|
||||
@@ -371,7 +388,7 @@ async function getCouponInfo() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pay" class="mb-50">
|
||||
<div id="pay" class="mb-50" v-if="selectedPlanId">
|
||||
<!-- Page Header -->
|
||||
<div class="text-center mb-6">
|
||||
<h1 class="text-xl font-semibold mb-2">安全支付</h1>
|
||||
@@ -509,9 +526,15 @@ async function getCouponInfo() {
|
||||
</div>
|
||||
|
||||
<iframe id="payFrame" class="w-[205px] h-[205px] center border-none"></iframe>
|
||||
<div class="text-center mt-4">
|
||||
<div class="text-center my-4">
|
||||
请使用支付宝扫码支付
|
||||
</div>
|
||||
<BaseButton size="large"
|
||||
v-if="showCheckBtn"
|
||||
:loading="checkLoading"
|
||||
@click="checkOrderStatus">
|
||||
我已付款
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -295,7 +295,7 @@ enum ImportStep {
|
||||
}
|
||||
|
||||
const {exportData} = useExport()
|
||||
let importStep = $ref<ImportStep>(ImportStep.SUCCESS)
|
||||
let importStep = $ref<ImportStep>(ImportStep.CONFIRMATION)
|
||||
let isImporting = $ref(false)
|
||||
let reason = $ref('')
|
||||
let timer = $ref(-1)
|
||||
|
||||
@@ -1,509 +1 @@
|
||||
<script setup lang="ts">
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { useRouter } from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {
|
||||
_getAccomplishDate,
|
||||
_getDictDataByUrl,
|
||||
_nextTick,
|
||||
isMobile,
|
||||
loadJsLib,
|
||||
resourceWrap,
|
||||
shuffle,
|
||||
useNav
|
||||
} from "@/utils";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import { DictResource, WordPracticeMode } from "@/types/types.ts";
|
||||
import { watch } from "vue";
|
||||
import { getCurrentStudyWord } from "@/hooks/dict.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import Book from "@/components/Book.vue";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Progress from '@/components/base/Progress.vue';
|
||||
import Toast from '@/components/base/toast/Toast.ts';
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { getDefaultDict } from "@/types/func.ts";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
|
||||
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { useFetch } from "@vueuse/core";
|
||||
import { AppEnv, DICT_LIST, Host, LIB_JS_URL, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
|
||||
import { myDictList } from "@/apis";
|
||||
import PracticeWordListDialog from "@/pages/word/components/PracticeWordListDialog.vue";
|
||||
import ShufflePracticeSettingDialog from "@/pages/word/components/ShufflePracticeSettingDialog.vue";
|
||||
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const router = useRouter()
|
||||
const {nav} = useNav()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let loading = $ref(true)
|
||||
let isSaveData = $ref(false)
|
||||
|
||||
let currentStudy = $ref({
|
||||
new: [],
|
||||
review: [],
|
||||
write: [],
|
||||
shuffle: [],
|
||||
})
|
||||
|
||||
watch(() => store.load, n => {
|
||||
if (n) {
|
||||
init()
|
||||
_nextTick(async () => {
|
||||
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
|
||||
const tour = new Shepherd.Tour(TourConfig);
|
||||
tour.on('cancel', () => {
|
||||
localStorage.setItem('tour-guide', '1');
|
||||
});
|
||||
tour.addStep({
|
||||
id: 'step1',
|
||||
text: '点击这里选择一本词典开始学习',
|
||||
attachTo: {
|
||||
element: '#step1',
|
||||
on: 'bottom'
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: `下一步(1/${TourConfig.total})`,
|
||||
action() {
|
||||
tour.next()
|
||||
router.push('/dict-list')
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
const r = localStorage.getItem('tour-guide');
|
||||
if (settingStore.first && !r && !isMobile()) tour.start();
|
||||
}, 500)
|
||||
}
|
||||
}, {immediate: true})
|
||||
|
||||
async function init() {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await myDictList({type: "word"})
|
||||
if (res.success) {
|
||||
store.setState(Object.assign(store.$state, res.data))
|
||||
}
|
||||
}
|
||||
if (store.word.studyIndex >= 3) {
|
||||
if (!store.sdict.custom && !store.sdict.words.length) {
|
||||
store.word.bookList[store.word.studyIndex] = await _getDictDataByUrl(store.sdict)
|
||||
}
|
||||
}
|
||||
if (!currentStudy.new.length && store.sdict.words.length) {
|
||||
let d = localStorage.getItem(PracticeSaveWordKey.key)
|
||||
if (d) {
|
||||
try {
|
||||
let obj = JSON.parse(d)
|
||||
currentStudy = obj.val.taskWords
|
||||
isSaveData = true
|
||||
} catch (e) {
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
} else {
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
function startPractice() {
|
||||
if (store.sdict.id) {
|
||||
if (!store.sdict.words.length) {
|
||||
return Toast.warning('没有单词可学习!')
|
||||
}
|
||||
window.umami?.track('startStudyWord', {
|
||||
name: store.sdict.name,
|
||||
index: store.sdict.lastLearnIndex,
|
||||
perDayStudyNumber: store.sdict.perDayStudyNumber,
|
||||
custom: store.sdict.custom,
|
||||
complete: store.sdict.complete,
|
||||
wordPracticeMode: settingStore.wordPracticeMode
|
||||
})
|
||||
//把是否是第一次设置为false
|
||||
settingStore.first = false
|
||||
nav('practice-words/' + store.sdict.id, {}, {taskWords: currentStudy})
|
||||
} else {
|
||||
window.umami?.track('no-dict')
|
||||
Toast.warning('请先选择一本词典')
|
||||
}
|
||||
}
|
||||
|
||||
let showPracticeSettingDialog = $ref(false)
|
||||
let showShufflePracticeSettingDialog = $ref(false)
|
||||
let showChangeLastPracticeIndexDialog = $ref(false)
|
||||
let showPracticeWordListDialog = $ref(false)
|
||||
|
||||
async function goDictDetail(val: DictResource) {
|
||||
if (!val.id) return nav('dict-list')
|
||||
runtimeStore.editDict = getDefaultDict(val)
|
||||
nav('dict-detail', {})
|
||||
}
|
||||
|
||||
let isManageDict = $ref(false)
|
||||
let selectIds = $ref([])
|
||||
|
||||
function handleBatchDel() {
|
||||
selectIds.forEach(id => {
|
||||
let r = store.word.bookList.findIndex(v => v.id === id)
|
||||
if (r !== -1) {
|
||||
if (store.word.studyIndex === r) {
|
||||
store.word.studyIndex = -1
|
||||
}
|
||||
if (store.word.studyIndex > r) {
|
||||
store.word.studyIndex--
|
||||
}
|
||||
store.word.bookList.splice(r, 1)
|
||||
}
|
||||
})
|
||||
selectIds = []
|
||||
Toast.success("删除成功!")
|
||||
}
|
||||
|
||||
function toggleSelect(item) {
|
||||
let rIndex = selectIds.findIndex(v => v === item.id)
|
||||
if (rIndex > -1) {
|
||||
selectIds.splice(rIndex, 1)
|
||||
} else {
|
||||
selectIds.push(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
const progressTextLeft = $computed(() => {
|
||||
if (store.sdict.complete) return '已学完,进入总复习阶段'
|
||||
return '已学习' + store.currentStudyProgress + '%'
|
||||
})
|
||||
const progressTextRight = $computed(() => {
|
||||
// if (store.sdict.complete) return store.sdict?.length
|
||||
return store.sdict?.lastLearnIndex
|
||||
})
|
||||
|
||||
function check(cb: Function) {
|
||||
if (!store.sdict.id) {
|
||||
Toast.warning('请先选择一本词典')
|
||||
} else {
|
||||
runtimeStore.editDict = getDefaultDict(store.sdict)
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
async function savePracticeSetting() {
|
||||
Toast.success('修改成功')
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
await store.changeDict(runtimeStore.editDict)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
async function onShufflePracticeSettingOk(total) {
|
||||
window.umami?.track('startShuffleStudyWord', {
|
||||
name: store.sdict.name,
|
||||
index: store.sdict.lastLearnIndex,
|
||||
perDayStudyNumber: store.sdict.perDayStudyNumber,
|
||||
total,
|
||||
custom: store.sdict.custom,
|
||||
complete: store.sdict.complete,
|
||||
})
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
|
||||
currentStudy.shuffle = shuffle(store.sdict.words.slice(0, store.sdict.lastLearnIndex).filter(v => !ignoreList.includes(v.word))).slice(0, total)
|
||||
nav('practice-words/' + store.sdict.id, {}, {
|
||||
taskWords: currentStudy,
|
||||
total //用于再来一组时,随机出正确的长度,因为练习中可能会点击已掌握,导致重学一遍之后长度变少,如果再来一组,此时长度就不正确
|
||||
})
|
||||
}
|
||||
|
||||
async function saveLastPracticeIndex(e) {
|
||||
Toast.success('修改成功')
|
||||
runtimeStore.editDict.lastLearnIndex = e
|
||||
showChangeLastPracticeIndexDialog = false
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
await store.changeDict(runtimeStore.editDict)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
const {
|
||||
data: recommendDictList,
|
||||
isFetching
|
||||
} = useFetch(resourceWrap(DICT_LIST.WORD.RECOMMENDED)).json()
|
||||
|
||||
let isNewHost = $ref(window.location.host === Host)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="mb-4" v-if="!isNewHost">
|
||||
新域名已启用,后续请访问 <a href="https://typewords.cc/words?from_old_site=1">https://typewords.cc</a>。当前
|
||||
2study.top 域名将在不久后停止使用
|
||||
</div>
|
||||
|
||||
<!-- <SettingDialog/>-->
|
||||
|
||||
<div class="card flex flex-col md:flex-row gap-8">
|
||||
<div class="flex-1 w-full flex flex-col justify-between">
|
||||
<div class="flex gap-3">
|
||||
<div class="p-1 center rounded-full bg-white">
|
||||
<IconFluentBookNumber20Filled class="text-xl color-link"/>
|
||||
</div>
|
||||
<div
|
||||
@click="goDictDetail(store.sdict)"
|
||||
class="text-2xl font-bold cursor-pointer">
|
||||
{{ store.sdict.name || '当前无正在学习的词典' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="store.sdict.id">
|
||||
<div class="mt-4 flex flex-col gap-2">
|
||||
<div class="">当前进度:{{ progressTextLeft }}</div>
|
||||
<Progress size="large" :percentage="store.currentStudyProgress" :show-text="false"></Progress>
|
||||
<div class="text-sm flex justify-between">
|
||||
<span>已完成 {{ progressTextRight }} 词 / 共 {{ store.sdict.words.length }} 词</span>
|
||||
<span v-if="store.sdict.id">
|
||||
预计完成日期:{{ _getAccomplishDate(store.sdict.words.length, store.sdict.perDayStudyNumber) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-4 gap-4">
|
||||
<BaseButton type="info"
|
||||
size="small"
|
||||
@click="router.push('/dict-list')">
|
||||
<div class="center gap-1">
|
||||
<IconFluentArrowSwap20Regular/>
|
||||
<span>选择词典</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
<PopConfirm
|
||||
:disabled="!isSaveData"
|
||||
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
|
||||
@confirm="check(()=>showChangeLastPracticeIndexDialog = true)">
|
||||
<BaseButton type="info"
|
||||
size="small"
|
||||
v-if="store.sdict.id"
|
||||
>
|
||||
<div class="center gap-1">
|
||||
<IconFluentSlideTextTitleEdit20Regular/>
|
||||
<span>更改进度</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</PopConfirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex items-center gap-4 mt-2 flex-1" v-else>
|
||||
<div class="title">请选择一本词典开始学习</div>
|
||||
<BaseButton id="step1" type="primary" size="large" @click="router.push('/dict-list')">
|
||||
<div class="center gap-1">
|
||||
<IconFluentAdd16Regular/>
|
||||
<span>选择词典</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 w-full mt-4 md:mt-0" :class="!store.sdict.id && 'opacity-30 cursor-not-allowed'">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="p-2 center rounded-full bg-white ">
|
||||
<IconFluentStar20Filled class="text-lg color-amber"/>
|
||||
</div>
|
||||
<div class="text-xl font-bold">
|
||||
{{ isSaveData ? '上次任务' : '今日任务' }}
|
||||
</div>
|
||||
<span class="color-link cursor-pointer"
|
||||
v-if="store.sdict.id"
|
||||
@click="showPracticeWordListDialog = true">词表</span>
|
||||
|
||||
</div>
|
||||
<div class="flex gap-1 items-center"
|
||||
v-if="store.sdict.id"
|
||||
>
|
||||
每日目标
|
||||
<div style="color:#ac6ed1;"
|
||||
class="bg-third px-2 h-10 flex center text-2xl rounded">
|
||||
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
|
||||
</div>
|
||||
个单词
|
||||
<PopConfirm
|
||||
:disabled="!isSaveData"
|
||||
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
|
||||
@confirm="check(()=>showPracticeSettingDialog = true)">
|
||||
<BaseButton
|
||||
type="info" size="small">更改
|
||||
</BaseButton>
|
||||
</PopConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-4 justify-between">
|
||||
<div class="stat">
|
||||
<div class="num">{{ currentStudy.new.length }}</div>
|
||||
<div class="txt">新词数</div>
|
||||
</div>
|
||||
<template v-if="settingStore.wordPracticeMode === WordPracticeMode.System">
|
||||
<div class="stat">
|
||||
<div class="num">{{ currentStudy.review.length }}</div>
|
||||
<div class="txt">复习上次</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="num">{{ currentStudy.write.length }}</div>
|
||||
<div class="txt">复习之前</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-end mt-4">
|
||||
<BaseButton size="large"
|
||||
class="flex-1"
|
||||
:disabled="!store.sdict.id"
|
||||
:loading="loading"
|
||||
@click="startPractice">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
|
||||
<div
|
||||
v-if="false"
|
||||
class="w-full flex box-border cp color-white">
|
||||
<div
|
||||
@click="startPractice"
|
||||
class="flex-1 rounded-l-lg center gap-2 py-1 bg-[var(--btn-primary)] hover:opacity-50">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl"/>
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="w-10 rounded-r-lg h-full center bg-[var(--btn-primary)] hover:bg-gray border-solid border-2 border-l-gray border-transparent box-border">
|
||||
<IconFluentChevronDown20Regular/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-2 pt-2 absolute z-2 right-0 border rounded opacity-0 scale-95
|
||||
group-hover:opacity-100 group-hover:scale-100
|
||||
transition-all duration-150 pointer-events-none group-hover:pointer-events-auto"
|
||||
>
|
||||
<div>
|
||||
<BaseButton
|
||||
size="large" type="orange"
|
||||
:loading="loading"
|
||||
@click="check(()=>showShufflePracticeSettingDialog = true)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">随机复习</span>
|
||||
<IconFluentArrowShuffle20Filled class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div>
|
||||
<BaseButton
|
||||
size="large" type="orange"
|
||||
:loading="loading"
|
||||
@click="check(()=>showShufflePracticeSettingDialog = true)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">重新学习</span>
|
||||
<IconFluentArrowShuffle20Filled class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-if="store.sdict.id && store.sdict.lastLearnIndex"
|
||||
size="large" type="orange"
|
||||
:loading="loading"
|
||||
@click="check(()=>showShufflePracticeSettingDialog = true)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">随机复习</span>
|
||||
<IconFluentArrowShuffle20Filled class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col">
|
||||
<div class="flex justify-between">
|
||||
<div class="title">我的词典</div>
|
||||
<div class="flex gap-4 items-center">
|
||||
<PopConfirm title="确认删除所有选中词典?" @confirm="handleBatchDel" v-if="selectIds.length">
|
||||
<BaseIcon class="del" title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
|
||||
<div class="color-link cursor-pointer" v-if="store.word.bookList.length > 3"
|
||||
@click="isManageDict = !isManageDict; selectIds = []">{{ isManageDict ? '取消' : '管理词典' }}
|
||||
</div>
|
||||
<div class="color-link cursor-pointer" @click="nav('dict-detail', { isAdd: true })">创建个人词典</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4 flex-wrap mt-4">
|
||||
<Book :is-add="false" quantifier="个词" :item="item" :checked="selectIds.includes(item.id)"
|
||||
@check="() => toggleSelect(item)" :show-checkbox="isManageDict && j >= 3"
|
||||
v-for="(item, j) in store.word.bookList" @click="goDictDetail(item)"/>
|
||||
<Book :is-add="true" @click="router.push('/dict-list')"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col overflow-hidden" v-loading="isFetching">
|
||||
<div class="flex justify-between">
|
||||
<div class="title">推荐</div>
|
||||
<div class="flex gap-4 items-center">
|
||||
<div class="color-link cursor-pointer" @click="router.push('/dict-list')">更多</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 flex-wrap mt-4 min-h-50">
|
||||
<Book :is-add="false"
|
||||
quantifier="个词"
|
||||
:item="item as any"
|
||||
v-for="(item, j) in recommendDictList" @click="goDictDetail(item as any)"/>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
|
||||
<PracticeSettingDialog
|
||||
:show-left-option="false"
|
||||
v-model="showPracticeSettingDialog"
|
||||
@ok="savePracticeSetting"/>
|
||||
|
||||
<ChangeLastPracticeIndexDialog
|
||||
v-model="showChangeLastPracticeIndexDialog"
|
||||
@ok="saveLastPracticeIndex"
|
||||
/>
|
||||
|
||||
<PracticeWordListDialog
|
||||
:data="currentStudy"
|
||||
v-model="showPracticeWordListDialog"
|
||||
/>
|
||||
|
||||
<ShufflePracticeSettingDialog
|
||||
v-model="showShufflePracticeSettingDialog"
|
||||
@ok="onShufflePracticeSettingOk"/>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.stat {
|
||||
@apply w-31% box-border flex flex-col items-center justify-center rounded-xl p-2 bg-[var(--bg-history)];
|
||||
border: 1px solid gainsboro;
|
||||
|
||||
.num {
|
||||
@apply color-[#409eff] text-4xl font-bold;
|
||||
}
|
||||
|
||||
.txt {
|
||||
@apply color-gray-500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
WordsPage.vue
|
||||
@@ -7,6 +7,7 @@ import { PracticeData, WordPracticeType, ShortcutKey, TaskWords } from "@/types/
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import Progress from '@/components/base/Progress.vue'
|
||||
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
|
||||
|
||||
const statStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -123,6 +124,8 @@ const progress = $computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-center items-center" id="toolbar-icons">
|
||||
<SettingDialog type="word"/>
|
||||
|
||||
<BaseIcon
|
||||
v-if="statStore.step < 9"
|
||||
@click="emit('skipStep')"
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {getAudioFileUrl, usePlayAudio} from "@/hooks/sound.ts";
|
||||
import {ShortcutKey, WordPracticeMode} from "@/types/types.ts";
|
||||
import {ShortcutKey} from "@/types/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {SoundFileOptions} from "@/config/env.ts";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import {Option, Select} from "@/components/base/select";
|
||||
import Switch from "@/components/base/Switch.vue";
|
||||
import Slider from "@/components/base/Slider.vue";
|
||||
@@ -14,12 +13,19 @@ import Radio from "@/components/base/radio/Radio.vue";
|
||||
import InputNumber from "@/components/base/InputNumber.vue";
|
||||
import Textarea from "@/components/base/Textarea.vue";
|
||||
import SettingItem from "@/pages/setting/SettingItem.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {defineAsyncComponent} from "vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
|
||||
const tabIndex = $ref(1)
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
const props = defineProps<{
|
||||
type: 'article' | 'word'
|
||||
}>()
|
||||
|
||||
const tabIndex = $ref(props.type === 'word' ? 1 : 2)
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const store = useBaseStore()
|
||||
let show = $ref(false)
|
||||
|
||||
const simpleWords = $computed({
|
||||
get: () => store.simpleWords.join(','),
|
||||
@@ -35,23 +41,22 @@ const simpleWords = $computed({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="setting text-lg w-200 h-200 bg-white text-md flex flex-col">
|
||||
<div class="page-title text-align-center">设置</div>
|
||||
<Dialog v-model="show" title="设置">
|
||||
<div class="setting text-lg w-200 h-[50vh] text-md flex flex-col">
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<div class="left">
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1" v-if="type === 'word'">
|
||||
<IconFluentTextUnderlineDouble20Regular width="20"/>
|
||||
<span>单词练习设置</span>
|
||||
<span>单词</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
|
||||
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2" v-if="type === 'article'">
|
||||
<IconFluentBookLetter20Regular width="20"/>
|
||||
<span>文章练习设置</span>
|
||||
<span>文章</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
|
||||
<IconFluentSettings20Regular width="20"/>
|
||||
<span>通用练习设置</span>
|
||||
<span>通用</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,7 +98,9 @@ const simpleWords = $computed({
|
||||
<!-- 音效-->
|
||||
<div class="line"></div>
|
||||
<SettingItem main-title="音效"/>
|
||||
<SettingItem title="单词/句子发音口音">
|
||||
<SettingItem title="单词/句子发音口音"
|
||||
desc="仅单词生效,文章固定美音"
|
||||
>
|
||||
<Select v-model="settingStore.soundType"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
@@ -138,12 +145,12 @@ const simpleWords = $computed({
|
||||
<!-- 单词练习设置-->
|
||||
<!-- 单词练习设置-->
|
||||
<div v-if="tabIndex === 1">
|
||||
<SettingItem title="练习模式">
|
||||
<RadioGroup v-model="settingStore.wordPracticeMode" class="flex-col gap-0!">
|
||||
<Radio :value="WordPracticeMode.System" label="智能模式:自动规划学习、复习、听写、默写"/>
|
||||
<Radio :value="WordPracticeMode.Free" label="自由模式:系统不强制复习与默写"/>
|
||||
</RadioGroup>
|
||||
</SettingItem>
|
||||
<!-- <SettingItem title="练习模式">-->
|
||||
<!-- <RadioGroup v-model="settingStore.wordPracticeMode" class="flex-col gap-0!">-->
|
||||
<!-- <Radio :value="WordPracticeMode.System" label="智能模式:自动规划学习、复习、听写、默写"/>-->
|
||||
<!-- <Radio :value="WordPracticeMode.Free" label="自由模式:系统不强制复习与默写"/>-->
|
||||
<!-- </RadioGroup>-->
|
||||
<!-- </SettingItem>-->
|
||||
|
||||
<SettingItem title="显示上一个/下一个单词"
|
||||
desc="开启后,练习中会在上方显示上一个/下一个单词"
|
||||
@@ -277,7 +284,10 @@ const simpleWords = $computed({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</Dialog>
|
||||
<BaseIcon title="设置" @click="show = true;tabIndex = props.type === 'word' ? 1 : 2">
|
||||
<IconFluentSettings20Regular/>
|
||||
</BaseIcon>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -289,10 +299,10 @@ const simpleWords = $computed({
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-right: 2px solid gainsboro;
|
||||
border-right: 1px solid gainsboro;
|
||||
|
||||
.tabs {
|
||||
padding: .6rem 1.6rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem;
|
||||
@@ -302,17 +312,18 @@ const simpleWords = $computed({
|
||||
@apply cursor-pointer flex items-center relative;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: .5rem;
|
||||
width: 10rem;
|
||||
gap: .6rem;
|
||||
transition: all .5s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user