article_generator.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import common from '../../utils/util';
  2. import main from '../../utils/main';
  3. import constant1 from '../../utils/constant';
  4. Page({
  5. data: {
  6. wordList: [],
  7. selectedWords: [],
  8. difficulty: 0,
  9. difficulties: constant1.arrHardLevel,
  10. generatedArticle: '',
  11. articleTranslation: '',
  12. generating: false,
  13. generatingImage: false,
  14. Content:"",
  15. },
  16. onLoad: function(options) {
  17. let that=this;
  18. that.setData({
  19. Containnerheight: main.getWindowHeight(),
  20. IsShowTranslate:false,
  21. });
  22. if (options.words) {
  23. try {
  24. const wordList = options.words.split(",");
  25. this.setData({ wordList });
  26. } catch (e) {
  27. wx.showToast({
  28. title: '数据加载失败',
  29. icon: 'none'
  30. });
  31. }
  32. }
  33. },
  34. onDifficultyChange: function(e) {
  35. this.setData({
  36. difficulty: Number(e.detail.value)
  37. });
  38. },
  39. generateArticle: async function() {
  40. let that=this;
  41. this.setData({ generating: true });
  42. try {
  43. let url = common.Encrypt("GenerateArticle");
  44. url="https://www.kylx365.com/apiData/"+url;
  45. let words=that.data.wordList;
  46. const cloudRes = await new Promise((resolve, reject) => {
  47. wx.request({
  48. url: url,
  49. method: "POST",
  50. data: {
  51. Words:JSON.stringify(words),
  52. Level:that.data.difficulty
  53. },
  54. success: resolve,
  55. fail: reject,
  56. })
  57. });
  58. console.log(cloudRes);
  59. let result=cloudRes.data.result.choices[0].message.content;
  60. let content=JSON.parse(result);
  61. for(let i=0;i<content.ArticleEnglish.length;i++){
  62. for(let j=0;j<content.FormsOfWords.length;j++){
  63. let word = content.FormsOfWords[j];
  64. let regex = new RegExp(`\\b${word}\\b[.,!?;:]?`, 'gi');
  65. content.ArticleEnglish[i] = content.ArticleEnglish[i].replace(regex, match => {
  66. let punctuation = match.match(/[.,!?;:]$/);
  67. let punc = punctuation ? punctuation[0] : '';
  68. let wordPart = match.replace(/[.,!?;:]$/, '');
  69. return `<span class='highlight'>${wordPart}</span>${punc}`;
  70. });
  71. }
  72. }
  73. content.ArticleEnglishStr=content.ArticleEnglish.join("");
  74. for(let i=0;i<content.Question.length;i++){
  75. for(let j=0;j<content.Question[i].OptionsEnglish.length;j++){
  76. let str=content.Question[i].OptionsChinese[j];
  77. content.Question[i].OptionsChinese[j]=str.substr(2);
  78. }
  79. }
  80. this.setData({
  81. Content:content,
  82. generating: false
  83. });
  84. } catch (error) {
  85. console.error('生成文章失败:', error);
  86. wx.showToast({
  87. title: '生成文章失败',
  88. icon: 'none'
  89. });
  90. this.setData({ generating: false });
  91. }
  92. },
  93. showTranslate:function(e){
  94. this.setData({
  95. IsShowTranslate:!this.data.IsShowTranslate,
  96. })
  97. },
  98. generateImage: async function() {
  99. const that = this;
  100. this.setData({ generatingImage: true });
  101. try {
  102. const query = wx.createSelectorQuery();
  103. query.select('#articleCanvas')
  104. .fields({ node: true, size: true })
  105. .exec((res) => {
  106. const canvas = res[0].node;
  107. const ctx = canvas.getContext('2d');
  108. // 基本设置
  109. canvas.width = 595;
  110. canvas.height = 842;
  111. const margin = 40;
  112. const lineHeight = 24;
  113. const articleWidth = canvas.width - 2 * margin;
  114. // 计算内容高度的函数
  115. const calculateHeight = () => {
  116. let yPos = 40;
  117. // 标题高度
  118. yPos += 40;
  119. // 文章高度
  120. ctx.font = '16px Arial';
  121. let articleText = that.data.Content.ArticleEnglish.join(" ");
  122. articleText = articleText.replace(/<span[^>]*>(.*?)<\/span>/g, '###BOLD###$1###BOLD###');
  123. const words = articleText.split(/\s+/);
  124. let lineWidth = 0;
  125. words.forEach(word => {
  126. if (!word) return;
  127. let wordToDraw = word.includes('###BOLD###') ?
  128. word.replace(/###BOLD###/g, '') : word;
  129. if (/[.,!?;:]$/.test(wordToDraw)) {
  130. wordToDraw = wordToDraw.slice(0, -1);
  131. }
  132. const spaceWidth = ctx.measureText(' ').width;
  133. const wordWidth = ctx.measureText(wordToDraw).width;
  134. const puncWidth = /[.,!?;:]$/.test(word) ?
  135. ctx.measureText(word.slice(-1)).width : 0;
  136. if (lineWidth + wordWidth + puncWidth + (lineWidth > 0 ? spaceWidth : 0) > articleWidth) {
  137. yPos += lineHeight;
  138. lineWidth = 0;
  139. }
  140. lineWidth += wordWidth + puncWidth + (lineWidth > 0 ? spaceWidth : 0);
  141. });
  142. yPos += lineHeight * 2;
  143. // 分隔线和问题标题高度
  144. yPos += lineHeight * 2.5;
  145. // 问题高度
  146. const questions = that.data.Content.Question;
  147. questions.forEach(question => {
  148. //debugger;
  149. // 问题文本高度
  150. const questionText = question.QuestionEnglish || '';
  151. let textWidth = 0;
  152. const words = questionText.split(' ');
  153. words.forEach(word => {
  154. const wordWidth = ctx.measureText(word + ' ').width;
  155. if (textWidth + wordWidth > articleWidth) {
  156. yPos += lineHeight;
  157. textWidth = wordWidth;
  158. } else {
  159. textWidth += wordWidth;
  160. }
  161. });
  162. yPos += lineHeight;
  163. // 选项高度
  164. if (question.OptionsEnglish && Array.isArray(question.OptionsEnglish)) {
  165. question.OptionsEnglish.forEach(option => {
  166. if (!option) return;
  167. textWidth = 20; // 选项缩进
  168. const words = option.split(' ');
  169. words.forEach(word => {
  170. const wordWidth = ctx.measureText(word + ' ').width;
  171. if (textWidth + wordWidth > articleWidth) {
  172. yPos += lineHeight;
  173. textWidth = 20 + wordWidth;
  174. } else {
  175. textWidth += wordWidth;
  176. }
  177. });
  178. yPos += lineHeight;
  179. });
  180. }
  181. yPos += lineHeight / 2;
  182. });
  183. return yPos + margin;
  184. };
  185. // 计算所需高度
  186. const requiredHeight = calculateHeight();
  187. canvas.height = Math.max(842, requiredHeight);
  188. // 绘制背景
  189. ctx.fillStyle = '#ffffff';
  190. ctx.fillRect(0, 0, canvas.width, canvas.height);
  191. ctx.fillStyle = '#000000';
  192. ctx.textBaseline = 'top';
  193. // 绘制标题
  194. let yPos = 40;
  195. ctx.font = 'bold 20px Arial';
  196. const title = that.data.Content.Title || "English Reading Practice";
  197. const titleWidth = ctx.measureText(title).width;
  198. ctx.fillText(title, (canvas.width - titleWidth) / 2, yPos);
  199. yPos += 40;
  200. // 绘制文章
  201. ctx.font = '16px Arial';
  202. let articleText = that.data.Content.ArticleEnglish.join(" ");
  203. articleText = articleText.replace(/<span[^>]*>(.*?)<\/span>/g, '###BOLD###$1###BOLD###');
  204. const words = articleText.split(/\s+/);
  205. let xPos = margin;
  206. let lineWidth = 0;
  207. words.forEach(word => {
  208. if (!word) return;
  209. // 处理加粗和标点
  210. let isBold = word.includes('###BOLD###');
  211. let wordToDraw = isBold ? word.replace(/###BOLD###/g, '') : word;
  212. let punctuation = '';
  213. if (/[.,!?;:]$/.test(wordToDraw)) {
  214. punctuation = wordToDraw.slice(-1);
  215. wordToDraw = wordToDraw.slice(0, -1);
  216. }
  217. // 设置字体
  218. ctx.font = isBold ? 'bold 16px Arial' : '16px Arial';
  219. // 计算宽度
  220. const spaceWidth = ctx.measureText(' ').width;
  221. const wordWidth = ctx.measureText(wordToDraw).width;
  222. const puncWidth = punctuation ? ctx.measureText(punctuation).width : 0;
  223. const totalWidth = wordWidth + puncWidth;
  224. // 检查换行
  225. if (lineWidth + totalWidth + (lineWidth > 0 ? spaceWidth : 0) > articleWidth) {
  226. xPos = margin;
  227. yPos += lineHeight;
  228. lineWidth = 0;
  229. }
  230. // 添加空格
  231. if (lineWidth > 0) {
  232. xPos += spaceWidth;
  233. lineWidth += spaceWidth;
  234. }
  235. // 绘制单词
  236. ctx.fillText(wordToDraw, xPos, yPos);
  237. // 绘制标点
  238. if (punctuation) {
  239. ctx.font = '16px Arial';
  240. ctx.fillText(punctuation, xPos + wordWidth, yPos);
  241. }
  242. xPos += totalWidth;
  243. lineWidth += totalWidth;
  244. });
  245. // 添加分隔线
  246. yPos += lineHeight * 2;
  247. ctx.beginPath();
  248. ctx.moveTo(margin, yPos);
  249. ctx.lineTo(canvas.width - margin, yPos);
  250. ctx.stroke();
  251. yPos += lineHeight;
  252. // 绘制问题标题
  253. ctx.font = 'bold 16px Arial';
  254. ctx.fillText("Reading Comprehension Questions:", margin, yPos);
  255. yPos += lineHeight * 1.5;
  256. // 绘制问题
  257. const questions = that.data.Content.Question;
  258. questions.forEach((question, index) => {
  259. // 绘制问题
  260. ctx.font = 'bold 16px Arial';
  261. const questionText = `${index + 1}. ${question.QuestionEnglish || ''}`;
  262. const words = questionText.split(' ');
  263. let line = '';
  264. let testLine = '';
  265. words.forEach(word => {
  266. testLine = line + (line ? ' ' : '') + word;
  267. if (ctx.measureText(testLine).width > articleWidth) {
  268. ctx.fillText(line, margin, yPos);
  269. yPos += lineHeight;
  270. line = word;
  271. } else {
  272. line = testLine;
  273. }
  274. });
  275. if (line) {
  276. ctx.fillText(line, margin, yPos);
  277. yPos += lineHeight;
  278. }
  279. // 绘制选项
  280. ctx.font = '16px Arial';
  281. if (question.OptionsEnglish && Array.isArray(question.OptionsEnglish)) {
  282. question.OptionsEnglish.forEach((option, optIndex) => {
  283. if (!option) return;
  284. const optionText = `${option}`;
  285. const optionWords = optionText.split(' ');
  286. line = '';
  287. testLine = '';
  288. optionWords.forEach(word => {
  289. testLine = line + (line ? ' ' : '') + word;
  290. if (ctx.measureText(testLine).width > articleWidth - 20) {
  291. ctx.fillText(line, margin + 20, yPos);
  292. yPos += lineHeight;
  293. line = word;
  294. } else {
  295. line = testLine;
  296. }
  297. });
  298. if (line) {
  299. ctx.fillText(line, margin + 20, yPos);
  300. yPos += lineHeight;
  301. }
  302. });
  303. }
  304. yPos += lineHeight / 2;
  305. });
  306. // 保存图片
  307. wx.canvasToTempFilePath({
  308. canvas: canvas,
  309. width: canvas.width,
  310. height: canvas.height,
  311. destWidth: canvas.width * 2,
  312. destHeight: canvas.height * 2,
  313. fileType: 'jpg',
  314. quality: 0.9,
  315. success(res) {
  316. wx.saveImageToPhotosAlbum({
  317. filePath: res.tempFilePath,
  318. success() {
  319. wx.showToast({
  320. title: '图片已保存到相册',
  321. icon: 'success',
  322. duration: 2000
  323. });
  324. that.setData({ generatingImage: false });
  325. },
  326. fail(err) {
  327. console.error('保存图片失败:', err);
  328. if (err.errMsg.indexOf('auth deny') >= 0 || err.errMsg.indexOf('authorize') >= 0) {
  329. wx.showModal({
  330. title: '提示',
  331. content: '需要您授权保存图片到相册',
  332. showCancel: false,
  333. success() {
  334. wx.openSetting({
  335. success(res) {
  336. console.log('设置结果', res);
  337. }
  338. });
  339. }
  340. });
  341. } else {
  342. wx.showToast({
  343. title: '保存图片失败',
  344. icon: 'none'
  345. });
  346. }
  347. that.setData({ generatingImage: false });
  348. }
  349. });
  350. },
  351. fail(err) {
  352. console.error('生成图片失败:', err);
  353. wx.showToast({
  354. title: '生成图片失败',
  355. icon: 'none'
  356. });
  357. that.setData({ generatingImage: false });
  358. }
  359. });
  360. });
  361. } catch (error) {
  362. console.error('生成图片过程出错:', error);
  363. wx.showToast({
  364. title: '生成图片失败',
  365. icon: 'none'
  366. });
  367. this.setData({ generatingImage: false });
  368. }
  369. }
  370. });