chengjie 6 months ago
commit
ac82a10035

BIN
.DS_Store


+ 69 - 0
app.js

@@ -0,0 +1,69 @@
1
+// app.js
2
+App({
3
+  globalData: {
4
+    Version: "1.0.0",
5
+    IsProduction: true,
6
+    ShareTitle: "",
7
+    SharePath: "pages/index/index",
8
+    ShareImage: '../images/program_share-a01.png',
9
+    ProgramID: 107,
10
+    ProgramName: "",
11
+    AppID: "wx80059777521b897c",
12
+    serverUrl: "https://www.kylx365.com/apiData/",
13
+    serverUrlServer: "https://www.kylx365.com/apiData/",
14
+    serverUrlLocalhost: "http://localhost:3020/apiData/",
15
+    audioUrlBaidu: "https://tsn.baidu.com/text2audio?lan=zh&ctp=1&cuid=abcdxxx&tok=[token]&tex=[word]&vol=9&per=0&spd=3&pit=5",
16
+    audioUrlYoudao: "https://dict.youdao.com/dictvoice?rate=10&le=auto&audio=[word]",
17
+    uploadImageUrl: "https://miaguo-1253256735.file.myqcloud.com/",
18
+    officialAccounts:"https://mp.weixin.qq.com/s/gO9S4PrPl1Uu1iksyRuBgw",
19
+    BaiduToken: "",//百度开发平台token
20
+    IsIOS: true,
21
+    IsAndroid: false,
22
+    IsIPad: false,
23
+    IsIPhoneX: false,
24
+    IsOppo: false,
25
+    systemInfo: null,
26
+    userInfo: null,
27
+    SourceID: 0,//来源ID
28
+    shareTicket: null,
29
+    userSource: null,//用户来源方式
30
+    Key: "kylx365_chengjie",
31
+    IV: "kylx365hongliren",
32
+    TempStr:"",
33
+    ocrReady: false
34
+  },
35
+  onLaunch: function (options) {
36
+    var that=this;
37
+    that.getSystemInfo();
38
+    // wx.setEnableDebug({
39
+    //   enableDebug: true
40
+    // });
41
+  },
42
+  getSystemInfo: function () {
43
+    this.globalData.systemInfo = wx.getSystemInfoSync();
44
+    
45
+    if (this.globalData.systemInfo.system.indexOf("Android") >= 0) {
46
+      this.globalData.IsIOS =false;
47
+      this.globalData.IsAndroid=true;
48
+    }
49
+    else if (this.globalData.systemInfo.system.indexOf("iOS") >= 0) {
50
+      this.globalData.IsIOS = true;
51
+      this.globalData.IsAndroid=false;
52
+
53
+      if (this.globalData.systemInfo.model.indexOf("X") >= 0
54
+        || this.globalData.systemInfo.model.indexOf("11") >= 0
55
+        || this.globalData.systemInfo.model.indexOf("12") >= 0
56
+        || this.globalData.systemInfo.model.indexOf("13") >= 0 
57
+        || this.globalData.systemInfo.model.indexOf("14") >= 0)
58
+        this.globalData.IsIPhoneX=true;
59
+    }
60
+
61
+    if (this.globalData.systemInfo.model.indexOf("iPad")>=0){
62
+      this.globalData.IsIPad=true;
63
+    }
64
+
65
+    if (this.globalData.systemInfo.brand.indexOf("OPPO")>=0){
66
+      this.globalData.IsOppo=true;
67
+    }
68
+  }
69
+})

+ 27 - 0
app.json

@@ -0,0 +1,27 @@
1
+{
2
+  "pages": [
3
+    "pages/index/index",
4
+    "pages/logs/logs",
5
+    "pages/ocr/ocr",
6
+    "pages/article_generator/article_generator"
7
+  ],
8
+  "minPlatformVersion": "2.21.0",
9
+  "window": {
10
+    "navigationBarBackgroundColor": "#ffffff",
11
+    "navigationBarTitleText": "秒过",
12
+    "navigationBarTextStyle": "black",
13
+    "backgroundColor": "#ffffff",
14
+    "enablePullDownRefresh": false
15
+  },
16
+  "sitemapLocation": "sitemap.json",
17
+  "lazyCodeLoading": "requiredComponents",
18
+  "requiredPrivateInfos": [
19
+    "getLocation",
20
+    "chooseLocation"
21
+  ],
22
+  "permission": {
23
+    "scope.camera": {
24
+      "desc": "需要使用您的相机进行文字识别"
25
+    }
26
+  }
27
+}

+ 28 - 0
app.wxss

@@ -0,0 +1,28 @@
1
+::-webkit-scrollbar {
2
+  width: 0;
3
+  height: 0;
4
+  color: transparent;
5
+  display: none;
6
+}
7
+
8
+.FlexColumn {
9
+  display: flex;
10
+  flex-direction: column;
11
+  align-items: center;
12
+  justify-content: center;
13
+}
14
+
15
+.FlexRow {
16
+  display: flex;
17
+  flex-direction: row;
18
+  align-items: center;
19
+  justify-content: center;
20
+}
21
+
22
+.container {
23
+  width:100%;
24
+  color: #1e1e1e;
25
+  justify-content: flex-start;
26
+  font-weight: 700;
27
+  background-color: #F2F2F2;
28
+} 

+ 105 - 0
components/navigation-bar/navigation-bar.js

@@ -0,0 +1,105 @@
1
+Component({
2
+  options: {
3
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
4
+  },
5
+  /**
6
+   * 组件的属性列表
7
+   */
8
+  properties: {
9
+    extClass: {
10
+      type: String,
11
+      value: ''
12
+    },
13
+    title: {
14
+      type: String,
15
+      value: ''
16
+    },
17
+    background: {
18
+      type: String,
19
+      value: ''
20
+    },
21
+    color: {
22
+      type: String,
23
+      value: ''
24
+    },
25
+    back: {
26
+      type: Boolean,
27
+      value: true
28
+    },
29
+    loading: {
30
+      type: Boolean,
31
+      value: false
32
+    },
33
+    homeButton: {
34
+      type: Boolean,
35
+      value: false,
36
+    },
37
+    animated: {
38
+      // 显示隐藏的时候opacity动画效果
39
+      type: Boolean,
40
+      value: true
41
+    },
42
+    show: {
43
+      // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在
44
+      type: Boolean,
45
+      value: true,
46
+      observer: '_showChange'
47
+    },
48
+    // back为true的时候,返回的页面深度
49
+    delta: {
50
+      type: Number,
51
+      value: 1
52
+    },
53
+  },
54
+  /**
55
+   * 组件的初始数据
56
+   */
57
+  data: {
58
+    displayStyle: ''
59
+  },
60
+  lifetimes: {
61
+    attached() {
62
+      const rect = wx.getMenuButtonBoundingClientRect()
63
+      wx.getSystemInfo({
64
+        success: (res) => {
65
+          const isAndroid = res.platform === 'android'
66
+          const isDevtools = res.platform === 'devtools'
67
+          this.setData({
68
+            ios: !isAndroid,
69
+            innerPaddingRight: `padding-right: ${res.windowWidth - rect.left}px`,
70
+            leftWidth: `width: ${res.windowWidth - rect.left }px`,
71
+            safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${res.safeArea.top}px); padding-top: ${res.safeArea.top}px` : ``
72
+          })
73
+        }
74
+      })
75
+    },
76
+  },
77
+  /**
78
+   * 组件的方法列表
79
+   */
80
+  methods: {
81
+    _showChange(show) {
82
+      const animated = this.data.animated
83
+      let displayStyle = ''
84
+      if (animated) {
85
+        displayStyle = `opacity: ${
86
+          show ? '1' : '0'
87
+        };transition:opacity 0.5s;`
88
+      } else {
89
+        displayStyle = `display: ${show ? '' : 'none'}`
90
+      }
91
+      this.setData({
92
+        displayStyle
93
+      })
94
+    },
95
+    back() {
96
+      const data = this.data
97
+      if (data.delta) {
98
+        wx.navigateBack({
99
+          delta: data.delta
100
+        })
101
+      }
102
+      this.triggerEvent('back', { delta: data.delta }, {})
103
+    }
104
+  },
105
+})

+ 5 - 0
components/navigation-bar/navigation-bar.json

@@ -0,0 +1,5 @@
1
+{
2
+  "component": true,
3
+  "styleIsolation": "apply-shared",
4
+  "usingComponents": {}
5
+}

+ 64 - 0
components/navigation-bar/navigation-bar.wxml

@@ -0,0 +1,64 @@
1
+<view class="weui-navigation-bar {{extClass}}">
2
+  <view class="weui-navigation-bar__inner {{ios ? 'ios' : 'android'}}" style="color: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}};">
3
+
4
+    <!-- 左侧按钮 -->
5
+    <view class='weui-navigation-bar__left' style="{{leftWidth}};">
6
+      <block wx:if="{{back || homeButton}}">
7
+        <!-- 返回上一页 -->
8
+        <block wx:if="{{back}}">
9
+          <view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_goback">
10
+            <view
11
+              bindtap="back"
12
+              class="weui-navigation-bar__btn_goback_wrapper"
13
+              hover-class="weui-active"
14
+              hover-stay-time="100"
15
+              aria-role="button"
16
+              aria-label="返回"
17
+            >
18
+              <view class="weui-navigation-bar__button weui-navigation-bar__btn_goback"></view>
19
+            </view>
20
+          </view>
21
+        </block>
22
+        <!-- 返回首页 -->
23
+        <block wx:if="{{homeButton}}">
24
+          <view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_home">
25
+            <view
26
+              bindtap="home"
27
+              class="weui-navigation-bar__btn_home_wrapper"
28
+              hover-class="weui-active"
29
+              aria-role="button"
30
+              aria-label="首页"
31
+            >
32
+              <view class="weui-navigation-bar__button weui-navigation-bar__btn_home"></view>
33
+            </view>
34
+          </view>
35
+        </block>
36
+      </block>
37
+      <block wx:else>
38
+        <slot name="left"></slot>
39
+      </block>
40
+    </view>
41
+
42
+    <!-- 标题 -->
43
+    <view class='weui-navigation-bar__center'>
44
+      <view wx:if="{{loading}}" class="weui-navigation-bar__loading" aria-role="alert">
45
+        <view
46
+          class="weui-loading"
47
+          aria-role="img"
48
+          aria-label="加载中"
49
+        ></view>
50
+      </view>
51
+      <block wx:if="{{title}}">
52
+        <text>{{title}}</text>
53
+      </block>
54
+      <block wx:else>
55
+        <slot name="center"></slot>
56
+      </block>
57
+    </view>
58
+    
59
+    <!-- 右侧留空 -->
60
+    <view class='weui-navigation-bar__right'>
61
+      <slot name="right"></slot>
62
+    </view>
63
+  </view>
64
+</view>

File diff suppressed because it is too large
+ 96 - 0
components/navigation-bar/navigation-bar.wxss


BIN
images/words.jpg


+ 418 - 0
pages/article_generator/article_generator.js

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

+ 5 - 0
pages/article_generator/article_generator.json

@@ -0,0 +1,5 @@
1
+
2
+{
3
+  "navigationBarTitleText": "AI文章生成",
4
+  "usingComponents": {}
5
+}

+ 79 - 0
pages/article_generator/article_generator.wxml

@@ -0,0 +1,79 @@
1
+<view class="container FlexColumn" style='min-height:{{Containnerheight}}rpx;'>
2
+
3
+  <view style="height: 30rpx;"></view>
4
+  <!-- 单词选择区域 -->
5
+  <view class="section">
6
+    <view class="section-title">需要生成的单词</view>
7
+    <view class="word-list">
8
+      <view 
9
+        wx:for="{{wordList}}" 
10
+        wx:key="*this" 
11
+        class="word-item"
12
+        data-word="{{item}}">
13
+        {{item}}
14
+      </view>
15
+    </view>
16
+  </view>
17
+
18
+  <!-- 难度选择区域 -->
19
+  <view class="section">
20
+    <view class="section-title">选择难度</view>
21
+    <radio-group class="difficulty-group" bindchange="onDifficultyChange">
22
+      <label class="difficulty-item" wx:for="{{difficulties}}" wx:key="value">
23
+        <radio value="{{item.Level}}" checked="{{difficulty === item.Level}}"/>
24
+        <text>{{item.Name}}</text>
25
+      </label>
26
+    </radio-group>
27
+  </view>
28
+
29
+  <!-- 生成按钮 -->
30
+  <view class="section">
31
+    <button 
32
+      class="generate-btn" 
33
+      type="primary" 
34
+      bindtap="generateArticle"
35
+      loading="{{generating}}">
36
+      生成文章
37
+    </button>
38
+  </view>
39
+
40
+  <!-- 生成的文章区域 -->
41
+  <block wx:if="{{Content}}">
42
+    <view class="section" bind:tap="showTranslate">
43
+      <view class="section-title">生成的文章和阅读理解习题</view>
44
+      <view class="section-title1">点击文章可查看答案和逐句翻译</view>
45
+      <rich-text class="article-content" hidden="{{IsShowTranslate}}" nodes="{{Content.ArticleEnglishStr}}"></rich-text>
46
+      <view class="article-content FlexColumn" hidden="{{!IsShowTranslate}}">
47
+        <view wx:for="{{Content.ArticleEnglish}}" wx:key="index" class="translation-item">
48
+          <rich-text class="article-content1" nodes="{{item}}"></rich-text>
49
+          <view class="article-content2">{{Content.ArticleChinese[index]}}</view>
50
+        </view>
51
+      </view>
52
+      <view class="Question FlexColumn" wx:for="{{Content.Question}}" wx:key="index">
53
+        <text class="Question1">({{index+1}}) {{item.QuestionEnglish}}\r\n<text class="article-content2" wx:if="{{IsShowTranslate}}">{{item.QuestionChinese}}</text></text>
54
+        <text class="Options" wx:for="{{item.OptionsEnglish}}" wx:for-item="item2" wx:for-index="index2" wx:key="*this">
55
+          <text wx:if="{{!IsShowTranslate || !((item.Answer=='A' && index2==0) || (item.Answer=='B' && index2==1) || (item.Answer=='C' && index2==2) || (item.Answer=='D' && index2==3))}}">{{item2}}</text><text class="AnswerSelected" wx:if="{{((item.Answer=='A' && index2==0) || (item.Answer=='B' && index2==1) || (item.Answer=='C' && index2==2) || (item.Answer=='D' && index2==3)) && IsShowTranslate}}">{{item2}}</text>
56
+          <text class="article-content2" wx:if="{{IsShowTranslate}}">{{item.OptionsChinese[index2]}}</text>
57
+        </text>
58
+      </view>
59
+    </view>
60
+    <view class="section" wx:if="{{IsShowTranslate}}">
61
+      <view class="Answer FlexRow">
62
+        答案:<view class="Answer1" wx:for="{{Content.Question}}" wx:key="index">({{index+1}}) {{item.Answer}}</view>
63
+      </view>
64
+    </view>
65
+    <!-- 生成图片按钮 -->
66
+    <view class="section" wx:if="{{Content}}">
67
+      <button 
68
+        class="generate-image-btn" 
69
+        bindtap="generateImage"
70
+        loading="{{generatingImage}}">
71
+        生成练习图片
72
+      </button>
73
+    </view>
74
+  </block>
75
+
76
+  <!-- 用于生成图片的canvas,默认隐藏 -->
77
+  <canvas type="2d" id="articleCanvas" class="article-canvas" style="width: 595px; height: 842px;"></canvas>
78
+  <view style="height: 100rpx;"></view>
79
+</view>

+ 154 - 0
pages/article_generator/article_generator.wxss

@@ -0,0 +1,154 @@
1
+
2
+.container {
3
+}
4
+
5
+.section {
6
+  margin-bottom: 30rpx;
7
+  background: #fff;
8
+  border-radius: 10rpx;
9
+  padding: 20rpx;
10
+  width: 650rpx;
11
+  box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
12
+}
13
+
14
+.section-title {
15
+  font-size: 32rpx;
16
+  font-weight: bold;
17
+  color: #333;
18
+  margin-bottom: 10rpx;
19
+}
20
+
21
+.section-title1 {
22
+  font-size: 24rpx;
23
+  font-weight: 400;
24
+  color: #999;
25
+  margin-bottom: 20rpx;
26
+}
27
+/* 单词列表样式 */
28
+.word-list {
29
+  display: flex;
30
+  flex-wrap: wrap;
31
+  gap: 20rpx;
32
+  margin-bottom: 20rpx;
33
+  width: 650rpx;
34
+}
35
+
36
+.word-item {
37
+  padding: 10rpx 20rpx;
38
+  background: #f5f5f5;
39
+  border-radius: 10rpx;
40
+  font-size: 28rpx;
41
+  color: #666;
42
+  border: 2rpx solid transparent;
43
+  transition: all 0.3s;
44
+}
45
+
46
+.word-item.selected {
47
+  background: #e6f3ff;
48
+  color: #0066cc;
49
+  border-color: #0066cc;
50
+}
51
+
52
+.selected-count {
53
+  font-size: 28rpx;
54
+  color: #666;
55
+  text-align: right;
56
+}
57
+
58
+/* 难度选择样式 */
59
+.difficulty-group {
60
+  display: flex;
61
+  flex-direction: column;
62
+  gap: 20rpx;
63
+  width: 650rpx;
64
+}
65
+
66
+.difficulty-item {
67
+  display: flex;
68
+  align-items: center;
69
+  gap: 10rpx;
70
+  font-size: 28rpx;
71
+  color: #333;
72
+}
73
+
74
+/* 生成按钮样式 */
75
+.generate-btn {
76
+  width: 100% !important;
77
+  margin-top: 20rpx;
78
+}
79
+
80
+/* 文章区域样式 */
81
+.article-content {
82
+  font-size: 36rpx;
83
+  line-height: 1.6;
84
+  margin-bottom: 40rpx;
85
+  border-radius: 8rpx;
86
+  width: 650rpx;
87
+}
88
+
89
+.translation-item {
90
+  margin-bottom: 20rpx;
91
+  padding: 10rpx 0;
92
+  border-bottom: 1rpx solid #f0f0f0;
93
+  width: 100%;
94
+}
95
+
96
+.article-content1 {
97
+  margin-bottom: 8rpx;
98
+  color: #333;
99
+  width: 100%;
100
+  display: block;
101
+}
102
+
103
+.article-content2 {
104
+  color: #666;
105
+  font-size: 28rpx;
106
+  padding-left: 20rpx;
107
+  border-left: 4rpx solid #1890ff;
108
+  margin-top: 8rpx;
109
+  width: calc(100% - 24rpx);
110
+  display: block;
111
+}
112
+
113
+.AnswerSelected{
114
+  background-color: #27AE60;
115
+  color:#fff;
116
+}
117
+.Question{
118
+  width:90%;
119
+  margin: 30rpx 0;
120
+  align-items: flex-start;
121
+}
122
+.Question1{
123
+  font-size:36rpx;
124
+  font-weight: 700;
125
+}
126
+.Options{
127
+  font-size:32rpx;
128
+  margin: 15rpx 0;
129
+}
130
+.Answer{
131
+  width:650rpx;
132
+  justify-content: flex-start;
133
+}
134
+.Answer1{
135
+  width:80rpx;
136
+  margin: 0 10rpx;
137
+}
138
+.highlight{
139
+  color: #1890ff; 
140
+  font-weight: bold;
141
+}
142
+
143
+.article-canvas {
144
+  position: fixed;
145
+  left: -9999px;
146
+  visibility: hidden;
147
+}
148
+
149
+.generate-image-btn {
150
+  width: 80% !important;
151
+  margin: 30rpx auto !important;
152
+  background-color: #1890ff;
153
+  color: #fff;
154
+}

+ 52 - 0
pages/index/index.js

@@ -0,0 +1,52 @@
1
+import common from '../../utils/util';
2
+import main from '../../utils/main';
3
+
4
+const app = getApp();
5
+
6
+Page({
7
+  data: {
8
+    //Words:"apple\rblue\rred\rgreen\rbanana\rfish\rtable\rboy\rgirl"
9
+    Words:"go\r\ndo\r\nbe\r\ngood\r\nking\r\nriver\r\nfire\r\ndragon"
10
+  },
11
+  onLoad: function (options) {
12
+    var that = this;
13
+    
14
+    that.setData({
15
+      Containnerheight: main.getWindowHeight(),
16
+    });
17
+
18
+    if (options.words) {
19
+      let wordList = options.words.split(",");
20
+      wordList=wordList.join("\r\n");
21
+      this.setData({ 
22
+        Words:wordList,
23
+      });
24
+    }
25
+  },
26
+  bindinputField: function (e) {
27
+    var that = this;
28
+    var value=e.detail.value;
29
+    that.setData({
30
+      Words: value,
31
+    });
32
+  },
33
+  goto: function (e) {
34
+    let that=this;
35
+    var url=e.currentTarget.dataset.url;
36
+    
37
+    if (e.currentTarget.dataset.url.indexOf("article_generator")>0){
38
+      let words=that.data.Words.split("\n");
39
+      url+="?words="+words.join(",");
40
+    }
41
+    wx.navigateTo({
42
+      url: url,
43
+    });
44
+  },
45
+  onShareAppMessage: function () {
46
+    return {
47
+      title: app.globalData.ShareTitle,
48
+      path: app.globalData.SharePath + '?UserID=' + app.globalData.userInfo.UserID,
49
+      imageUrl: app.globalData.ShareImage,
50
+    }
51
+  },
52
+})

+ 3 - 0
pages/index/index.json

@@ -0,0 +1,3 @@
1
+{
2
+  "usingComponents": {}
3
+}

+ 11 - 0
pages/index/index.wxml

@@ -0,0 +1,11 @@
1
+<view class="container FlexColumn" style='min-height:{{Containnerheight}}rpx;'>
2
+  <view style="margin-top: 30rpx; align-self: flex-start;margin:30rpx 0 0 30rpx">输入要记忆的单词</view>
3
+  <textarea class="txtWords" value="{{Words}}" bindinput="bindinputField" ></textarea>
4
+  <button class="feature-btn" bindtap="goto" data-url="../ocr/ocr">
5
+    <view class="feature-name">拍照识别获得单词</view>
6
+  </button>
7
+  
8
+  <button class="footer feature-btn btn-retake" bindtap="goto" data-url="../article_generator/article_generator">
9
+      <view class="feature-name">生成文章</view>
10
+  </button>
11
+</view>

+ 33 - 0
pages/index/index.wxss

@@ -0,0 +1,33 @@
1
+/**index.wxss**/
2
+page {
3
+  height: 100vh;
4
+  display: flex;
5
+  flex-direction: column;
6
+}
7
+.txtWords{
8
+  width:690rpx;
9
+  height:600rpx;
10
+  background-color: #fff;
11
+  margin: 30rpx 0;
12
+  font-size: 48rpx;
13
+}
14
+
15
+.feature-name{
16
+  font-size: 36rpx;
17
+}
18
+
19
+.feature-btn{
20
+  margin: 30rpx;
21
+}
22
+
23
+.btn-retake {
24
+  background-color: #07c160;
25
+  color:#fff;
26
+  width: 650rpx;
27
+  border-radius: 20rpx;
28
+}
29
+
30
+.footer{
31
+  position: fixed;
32
+  bottom:60rpx;
33
+}

+ 20 - 0
pages/logs/logs.js

@@ -0,0 +1,20 @@
1
+// logs.js
2
+const util = require('../../utils/util.js')
3
+
4
+Component({
5
+  data: {
6
+    logs: []
7
+  },
8
+  lifetimes: {
9
+    attached() {
10
+      this.setData({
11
+        logs: (wx.getStorageSync('logs') || []).map(log => {
12
+          return {
13
+            date: util.formatTime(new Date(log)),
14
+            timeStamp: log
15
+          }
16
+        })
17
+      })
18
+    }
19
+  },
20
+})

+ 5 - 0
pages/logs/logs.json

@@ -0,0 +1,5 @@
1
+{
2
+  "usingComponents": {
3
+    "navigation-bar": "/components/navigation-bar/navigation-bar"
4
+  }
5
+}

+ 7 - 0
pages/logs/logs.wxml

@@ -0,0 +1,7 @@
1
+<!--logs.wxml-->
2
+<navigation-bar title="查看启动日志" back="{{true}}" color="black" background="#FFF"></navigation-bar>
3
+<scroll-view class="scrollarea" scroll-y type="list">
4
+  <block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
5
+    <view class="log-item">{{index + 1}}. {{log.date}}</view>
6
+  </block>
7
+</scroll-view>

+ 16 - 0
pages/logs/logs.wxss

@@ -0,0 +1,16 @@
1
+page {
2
+  height: 100vh;
3
+  display: flex;
4
+  flex-direction: column;
5
+}
6
+.scrollarea {
7
+  flex: 1;
8
+  overflow-y: hidden;
9
+}
10
+.log-item {
11
+  margin-top: 20rpx;
12
+  text-align: center;
13
+}
14
+.log-item:last-child {
15
+  padding-bottom: env(safe-area-inset-bottom);
16
+}

+ 274 - 0
pages/ocr/ocr.js

@@ -0,0 +1,274 @@
1
+import common from '../../utils/util';
2
+import main from '../../utils/main';
3
+
4
+Page({
5
+  data: {
6
+    cameraActive: true,
7
+    imageUrl: '',
8
+    recognizedTexts: [],
9
+    showCanvas: false,
10
+    Count:0,
11
+  },
12
+  onLoad: function(options) {
13
+    let that=this;
14
+    that.setData({
15
+      englishWords: null,  // 存储提取的英语单词
16
+    });
17
+  },
18
+  // 提取英语单词的函数 - 增强版
19
+  extractEnglishWords(texts) {
20
+    console.group('英语单词提取');
21
+    const words = new Set();
22
+    
23
+    texts.forEach(item => {
24
+      const text = item.text;
25
+      console.log('处理文本:', text);
26
+      
27
+      // 改进的正则表达式,能更好处理中英文混合文本
28
+      const wordRegex = /(?:^|\s|[\u4e00-\u9fa5])([A-Za-z]{2,}(?:['’-][A-Za-z]+)*)(?=$|\s|[\u4e00-\u9fa5])/g;
29
+      
30
+      let match;
31
+      while ((match = wordRegex.exec(text)) !== null) {
32
+        const word = match[1];
33
+        console.log('匹配到单词:', word);
34
+        
35
+        // 验证单词有效性
36
+        if (/^[A-Za-z'’-]+$/.test(word)) {
37
+          const lowerWord = word.toLowerCase();
38
+          words.add(lowerWord);
39
+          console.log('添加单词:', lowerWord);
40
+        }
41
+      }
42
+    });
43
+    
44
+    //const result = Array.from(words).sort();
45
+    const result=Array.from(words);
46
+    console.log('提取结果:', result);
47
+    console.groupEnd();
48
+    return result;
49
+  },
50
+
51
+  // 拍照识别
52
+  takePhoto() {
53
+    const ctx = wx.createCameraContext()
54
+    ctx.takePhoto({
55
+      quality: 'high',
56
+      success: (res) => {
57
+        this.setData({
58
+          imageUrl: res.tempImagePath,
59
+          cameraActive: false,
60
+          showCanvas: true
61
+        })
62
+        this.performOCR(res.tempImagePath)
63
+      },
64
+      fail: (err) => {
65
+        console.error('拍照失败:', err)
66
+        wx.showToast({
67
+          title: '拍照失败,请重试',
68
+          icon: 'none'
69
+        })
70
+      }
71
+    })
72
+  },
73
+
74
+  // 从相册选择
75
+  chooseImage() {
76
+    wx.chooseMedia({
77
+      count: 1,
78
+      mediaType: ['image'],
79
+      sourceType: ['album'],
80
+      success: (res) => {
81
+        const tempFilePath = res.tempFiles[0].tempFilePath
82
+        this.setData({
83
+          imageUrl: tempFilePath,
84
+          cameraActive: false,
85
+          showCanvas: true
86
+        })
87
+        this.performOCR(tempFilePath)
88
+      },
89
+      fail: (err) => {
90
+        console.error('选择图片失败:', err)
91
+        wx.showToast({
92
+          title: '选择图片失败',
93
+          icon: 'none'
94
+        })
95
+      }
96
+    })
97
+  },
98
+
99
+  // 加强版的OCR识别方法
100
+  async performOCR(imagePath) {
101
+    if (!imagePath) {
102
+      console.error('图片路径无效')
103
+    }
104
+
105
+    wx.showLoading({ title: '识别中...', mask: true })
106
+
107
+    try {
108
+      // 1. 压缩图片
109
+      const compressed = await new Promise((resolve, reject) => {
110
+        wx.compressImage({
111
+          src: imagePath,
112
+          quality: 70,
113
+          success: resolve,
114
+          fail: () => resolve({ tempFilePath: imagePath })
115
+        })
116
+      })
117
+      //console.log("1");
118
+      // 2. 转换为base64
119
+      const fileRes = await new Promise((resolve, reject) => {
120
+        wx.getFileSystemManager().readFile({
121
+          filePath: compressed.tempFilePath,
122
+          encoding: 'base64',
123
+          success: resolve,
124
+          fail: reject
125
+        })
126
+      })
127
+      //console.log("2");
128
+      // 3. 调用云函数(添加超时处理)
129
+      let postData={ ImageBase64: `data:image/jpeg;base64,${fileRes.data}` };
130
+      
131
+      let url = common.Encrypt("OCRImageData");
132
+      url="https://www.kylx365.com/apiData/"+url;
133
+      //url="http://localhost:3020/api/OCRImageData";
134
+      //console.log("url:"+url);
135
+      const cloudRes = await new Promise((resolve, reject) => {
136
+        wx.request({
137
+          url: url,
138
+          method: "POST",
139
+          data: postData,
140
+          success: resolve,
141
+          fail: reject,
142
+        })
143
+      });
144
+      //console.log("3");
145
+
146
+      // 4. 验证返回结果
147
+      if (!cloudRes || !cloudRes.data.result) {
148
+        throw new Error('无效的响应格式')
149
+      }
150
+      //console.log("4");
151
+      if (!cloudRes.data.result) {
152
+        throw new Error(cloudRes.data.result.message || '识别服务返回空数据')
153
+      }
154
+
155
+      // 5. 处理识别结果
156
+      const texts = cloudRes.data.result.TextDetections.map(item => ({
157
+        text: item.DetectedText || '未识别到文字',
158
+        pos: this.convertPosition(item.ItemPolygon || { Points: [] })
159
+      })).filter(item => item.DetectedText !== '未识别到文字')
160
+
161
+      //console.log("5");
162
+
163
+      if (texts.length === 0) {
164
+        throw new Error('未识别到有效文字')
165
+      }
166
+
167
+      // 6.提取英文单词
168
+      const engTexts=this.extractEnglishWords(texts);
169
+
170
+      let arr=[];
171
+      for(var i=0;i<engTexts.length;i++){
172
+        let obj={};
173
+        obj.Word=engTexts[i];
174
+        obj.Selected=0;
175
+        arr.push(obj);
176
+      }
177
+
178
+      this.setData({ 
179
+        recognizedTexts: texts,
180
+        englishWords:arr,
181
+      })
182
+
183
+    } catch (err) {
184
+      console.error('OCR处理失败:', err)
185
+      wx.showToast({
186
+        title: err.message || '识别失败',
187
+        icon: 'none',
188
+        duration: 3000
189
+      })
190
+    } finally {
191
+      wx.hideLoading()
192
+    }
193
+  },
194
+
195
+  // 加强坐标转换
196
+  convertPosition(polygon) {
197
+    try {
198
+      const points = polygon.Points || []
199
+      if (points.length === 0) {
200
+        return { x: 50, y: 50, width: 200, height: 30 }
201
+      }
202
+
203
+      const xs = points.map(p => p.X || 0)
204
+      const ys = points.map(p => p.Y || 0)
205
+      return {
206
+        x: Math.min(...xs),
207
+        y: Math.min(...ys),
208
+        width: Math.max(...xs) - Math.min(...xs),
209
+        height: Math.max(...ys) - Math.min(...ys)
210
+      }
211
+    } catch (e) {
212
+      return { x: 50, y: 50, width: 200, height: 30 }
213
+    }
214
+  },
215
+
216
+  
217
+  // 重新拍照
218
+  retake() {
219
+    this.setData({
220
+      cameraActive: true,
221
+      imageUrl: '',
222
+      recognizedTexts: [],
223
+      showCanvas: false
224
+    })
225
+  },
226
+
227
+  // 选择单词
228
+  selectWord(e) {
229
+    let that=this;
230
+
231
+    let list=that.data.englishWords;
232
+    const text = e.currentTarget.dataset.text
233
+    if (!text) return;
234
+
235
+    for(let i=0;i<list.length;i++){
236
+      if (text.Word==list[i].Word){
237
+        list[i].Selected=list[i].Selected==1?0:1;
238
+      }
239
+    }
240
+    that.setData({
241
+      englishWords:that.data.englishWords,
242
+    });
243
+
244
+    let count=0;
245
+    for(let i=0;i<list.length;i++){
246
+      if (list[i].Selected==1){
247
+        count++;
248
+      }
249
+    }
250
+    if (count>10){
251
+      wx.showToast({
252
+        title: '最多10个单词',
253
+      })
254
+    }
255
+    that.setData({
256
+      Count:count,
257
+    });
258
+  },
259
+
260
+  // 跳转到文章生成页面
261
+  goToArticleGenerator() {
262
+    let that=this;
263
+    let words=[];
264
+    let list=that.data.englishWords;
265
+    for(let i=0;i<list.length;i++){
266
+      if (list[i].Selected==1){
267
+        words.push(list[i].Word);
268
+      }
269
+    }
270
+    wx.redirectTo({
271
+      url: '/pages/index/index?words=' + words.join(","),
272
+    });
273
+  }
274
+})

+ 7 - 0
pages/ocr/ocr.json

@@ -0,0 +1,7 @@
1
+
2
+{
3
+  "navigationBarTitleText": "文字识别",
4
+  "navigationBarBackgroundColor": "#07c160",
5
+  "navigationBarTextStyle": "white",
6
+  "usingComponents": {}
7
+}

+ 44 - 0
pages/ocr/ocr.wxml

@@ -0,0 +1,44 @@
1
+<view class="container FlexColumn" style='min-height:{{Containnerheight}}rpx;'>
2
+  <!-- 相机视图 -->
3
+  <view class="camera-container" wx:if="{{cameraActive}}">
4
+    <camera device-position="back" flash="auto" class="camera"></camera>
5
+    <view class="camera-controls">
6
+      <view class="btn-capture FlexRow" bindtap="chooseImage">从相册选择</view>
7
+      <view class="btn-capture btn-retake FlexRow" bindtap="takePhoto">拍照</view>
8
+    </view>
9
+  </view>
10
+
11
+  <!-- 识别结果视图 -->
12
+  <view class="result-container" wx:else>
13
+    
14
+    <!-- 识别文字列表 -->
15
+    <view class="text-list" >
16
+      <view class="Title FlexColumn"  wx:if="{{englishWords.length >0 }}">
17
+        <view>请选择图片中识别出来的英语单词</view>
18
+        <view class="Title1">已经选中 {{Count}} 个单词</view>
19
+      </view>
20
+      
21
+     
22
+      <!-- 英语单词标签页 -->
23
+      <view class="english-tab-content">
24
+        <view scroll-y class="text-scroll FlexRow">
25
+          <view wx:if="{{englishWords.length === 0}}" class="no-result">
26
+            未识别到英语单词
27
+          </view>
28
+          <view wx:for="{{englishWords}}" wx:key="index" class="text-item FlexRow" bindtap="selectWord" data-text="{{item}}">
29
+            <view class="text-content textSelected{{item.Selected}}">{{item.Word}}</view>
30
+          </view>
31
+        </view>
32
+        <view class="generate-article-btn" wx:if="{{englishWords.length > 0}}">
33
+          <button type="primary" bindtap="goToArticleGenerator" disabled="{{Count<5 || Count>10}}">确定</button>
34
+        </view>
35
+      </view>
36
+    </view>
37
+
38
+    <!-- 底部控制栏 -->
39
+    <view class="bottom-controls">
40
+      <button class="btn-capture btn-retake" bindtap="retake">重新拍照</button>
41
+    </view>
42
+   
43
+  </view>
44
+</view>

+ 162 - 0
pages/ocr/ocr.wxss

@@ -0,0 +1,162 @@
1
+
2
+.container {
3
+  width: 100%;
4
+  height: 100vh;
5
+  display: flex;
6
+  flex-direction: column;
7
+  background-color: #f8f8f8;
8
+}
9
+
10
+/* 相机样式 */
11
+.camera-container {
12
+  width: 100%;
13
+  height: 100%;
14
+  position: relative;
15
+}
16
+
17
+.camera {
18
+  width: 100%;
19
+  height: 100%;
20
+}
21
+
22
+.camera-controls {
23
+  position: absolute;
24
+  bottom: 40rpx;
25
+  left: 0;
26
+  right: 0;
27
+  display: flex;
28
+  justify-content: center;
29
+  align-items: center;
30
+}
31
+
32
+.btn-capture {
33
+  width: 280rpx;
34
+  height: 100rpx;
35
+  line-height: 100rpx;
36
+  background-color: #ffffff;
37
+  margin: 0 20rpx;
38
+  border-radius: 30rpx;
39
+}
40
+
41
+
42
+/* 结果视图样式 */
43
+.result-container {
44
+  width: 100%;
45
+  height: 100%;
46
+  display: flex;
47
+  flex-direction: column;
48
+}
49
+
50
+.image-container {
51
+  width: 100%;
52
+  height: 50%;
53
+  display: flex;
54
+  justify-content: center;
55
+  align-items: center;
56
+  background-color: #000;
57
+}
58
+
59
+.preview-image, .highlight-canvas {
60
+  width: 100%;
61
+  height: 100%;
62
+  object-fit: contain;
63
+}
64
+
65
+.text-list {
66
+  flex: 1;
67
+  width: 100%;
68
+  background-color: #fff;
69
+  border-top-left-radius: 20rpx;
70
+  border-top-right-radius: 20rpx;
71
+  margin-top: -20rpx;
72
+  padding: 20rpx;
73
+  box-sizing: border-box;
74
+  display: flex;
75
+  flex-direction: column;
76
+}
77
+
78
+.text-list-header {
79
+  font-size: 32rpx;
80
+  font-weight: bold;
81
+  padding: 20rpx 0;
82
+  border-bottom: 1rpx solid #eee;
83
+}
84
+
85
+.text-scroll {
86
+  flex-wrap: wrap;
87
+  width: 100%;
88
+}
89
+
90
+.no-result {
91
+  text-align: center;
92
+  color: #999;
93
+  padding: 40rpx 0;
94
+}
95
+
96
+.text-item {
97
+  border: 1rpx solid #f0f0f0;
98
+  margin: 10rpx;
99
+  padding: 5rpx 10rpx;
100
+  background-color: #eee;
101
+}
102
+
103
+.text-content {
104
+  font-size: 30rpx;
105
+  color: #333;
106
+  padding: 3rpx 5rpx;
107
+  line-height: 1.5;
108
+  border-radius: 10rpx;
109
+}
110
+
111
+.text-tip {
112
+  font-size: 24rpx;
113
+  color: #999;
114
+  margin-top: 10rpx;
115
+}
116
+
117
+.bottom-controls {
118
+  padding: 20rpx;
119
+  display: flex;
120
+  justify-content: center;
121
+}
122
+
123
+.btn-retake {
124
+  background-color: #07c160;
125
+  color:#fff;
126
+}
127
+
128
+.Title{
129
+  width: 650rpx;
130
+  height:100rpx;
131
+  font-size:36rpx;
132
+  justify-content: flex-start;
133
+  align-items: flex-start;
134
+  margin: 30rpx 0;
135
+}
136
+
137
+.Title1{
138
+  font-size:28rpx;
139
+  color:#1d1d1d;
140
+}
141
+
142
+/* 英语单词标签页内容样式 */
143
+.english-tab-content {
144
+  display: flex;
145
+  flex-direction: column;
146
+  height: 100%;
147
+}
148
+
149
+.generate-article-btn {
150
+  padding: 20rpx;
151
+  margin-top: 20rpx;
152
+}
153
+
154
+.generate-article-btn button {
155
+  width: 100%;
156
+  border-radius: 40rpx;
157
+}
158
+
159
+.textSelected1{
160
+  background-color: #07c160;
161
+  color:#fff;
162
+}

+ 42 - 0
project.config.json

@@ -0,0 +1,42 @@
1
+{
2
+  "appid": "wx80059777521b897c",
3
+  "compileType": "miniprogram",
4
+  "libVersion": "trial",
5
+  "packOptions": {
6
+    "ignore": [],
7
+    "include": []
8
+  },
9
+  "setting": {
10
+    "coverView": true,
11
+    "es6": true,
12
+    "postcss": true,
13
+    "minified": true,
14
+    "enhance": true,
15
+    "showShadowRootInWxmlPanel": true,
16
+    "packNpmRelationList": [],
17
+    "babelSetting": {
18
+      "ignore": [],
19
+      "disablePlugins": [],
20
+      "outputPath": ""
21
+    },
22
+    "compileWorklet": false,
23
+    "uglifyFileName": false,
24
+    "uploadWithSourceMap": true,
25
+    "packNpmManually": false,
26
+    "minifyWXSS": true,
27
+    "minifyWXML": true,
28
+    "localPlugins": false,
29
+    "condition": false,
30
+    "swc": false,
31
+    "disableSWC": true,
32
+    "disableUseStrict": false,
33
+    "useCompilerPlugins": false
34
+  },
35
+  "condition": {},
36
+  "editorSetting": {
37
+    "tabIndent": "auto",
38
+    "tabSize": 2
39
+  },
40
+  "simulatorPluginLibVersion": {},
41
+  "cloudfunctionTemplateRoot": "cloudfunctionTemplate/"
42
+}

+ 23 - 0
project.private.config.json

@@ -0,0 +1,23 @@
1
+{
2
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3
+  "projectname": "english_learned_master",
4
+  "setting": {
5
+    "compileHotReLoad": true,
6
+    "skylineRenderEnable": false,
7
+    "urlCheck": false,
8
+    "coverView": true,
9
+    "lazyloadPlaceholderEnable": false,
10
+    "preloadBackgroundData": false,
11
+    "autoAudits": false,
12
+    "useApiHook": true,
13
+    "useApiHostProcess": true,
14
+    "showShadowRootInWxmlPanel": true,
15
+    "useStaticServer": false,
16
+    "useLanDebug": false,
17
+    "showES6CompileOption": false,
18
+    "bigPackageSizeSupport": false,
19
+    "checkInvalidKey": true,
20
+    "ignoreDevUnusedFiles": true
21
+  },
22
+  "libVersion": "3.8.6"
23
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
1
+{
2
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3
+  "rules": [{
4
+  "action": "allow",
5
+  "page": "*"
6
+  }]
7
+}

BIN
utils/.DS_Store


+ 79 - 0
utils/ai_service.js

@@ -0,0 +1,79 @@
1
+
2
+/**
3
+ * AI服务工具函数
4
+ * 用于调用腾讯云AI接口生成文章
5
+ */
6
+
7
+// 引入通用工具函数
8
+const util = require('./util.js');
9
+
10
+/**
11
+ * 调用腾讯云AI接口生成文章
12
+ * @param {Array} words - 选中的单词列表
13
+ * @param {String} difficulty - 难度级别 (primary/middle/high)
14
+ * @return {Promise} - 返回Promise对象,包含生成的文章和译文
15
+ */
16
+function generateArticle(words, difficulty) {
17
+  return new Promise((resolve, reject) => {
18
+    // 获取访问凭证
19
+    const accessToken = wx.getStorageSync('accessToken');
20
+    if (!accessToken) {
21
+      reject(new Error('未获取到访问凭证'));
22
+      return;
23
+    }
24
+
25
+    // 构建请求参数
26
+    const params = {
27
+      words: words,
28
+      difficulty: difficulty,
29
+      // 可以根据需要添加其他参数
30
+    };
31
+
32
+    // 调用腾讯云AI接口
33
+    wx.request({
34
+      url: 'https://api.cloud.tencent.com/article/generate', // 替换为实际的API地址
35
+      method: 'POST',
36
+      header: {
37
+        'Content-Type': 'application/json',
38
+        'Authorization': `Bearer ${accessToken}`
39
+      },
40
+      data: params,
41
+      success: (res) => {
42
+        if (res.statusCode === 200 && res.data) {
43
+          // 处理API返回的数据
44
+          resolve({
45
+            article: processArticleWithUnderline(res.data.article, words),
46
+            translation: res.data.translation || ''
47
+          });
48
+        } else {
49
+          reject(new Error(`API调用失败: ${res.statusCode}`));
50
+        }
51
+      },
52
+      fail: (err) => {
53
+        reject(new Error(`请求失败: ${err.errMsg}`));
54
+      }
55
+    });
56
+  });
57
+}
58
+
59
+/**
60
+ * 处理文章,为选中的单词添加下划线
61
+ * @param {String} article - 原始文章
62
+ * @param {Array} words - 需要添加下划线的单词列表
63
+ * @return {String} - 处理后的文章
64
+ */
65
+function processArticleWithUnderline(article, words) {
66
+  if (!article || !words || words.length === 0) {
67
+    return article;
68
+  }
69
+
70
+  // 创建一个正则表达式,匹配所有选中的单词(考虑单词边界)
71
+  const wordPattern = new RegExp(`\\b(${words.join('|')})\\b`, 'gi');
72
+  
73
+  // 替换匹配到的单词,添加下划线标记
74
+  return article.replace(wordPattern, '<u>$1</u>');
75
+}
76
+
77
+module.exports = {
78
+  generateArticle
79
+};

+ 15 - 0
utils/constant.js

@@ -0,0 +1,15 @@
1
+module.exports = {
2
+  arrHardLevel: [{
3
+    Level:0,
4
+    Name:"小学生难度"
5
+  },{
6
+    Level:1,
7
+    Name:"初中生难度"
8
+   },{
9
+    Level:2,
10
+    Name:"高中生难度"
11
+   },{
12
+    Level:3,
13
+    Name:"大学生难度"
14
+   }],
15
+}

+ 5 - 0
utils/cryptojs.js

@@ -0,0 +1,5 @@
1
+import BlockModes from '../utils/lib/BlockModes';
2
+import AES from '../utils/lib/AES';
3
+import { Crypto } from './lib/Crypto';
4
+
5
+export { Crypto };

BIN
utils/lib/.DS_Store


+ 402 - 0
utils/lib/AES.js

@@ -0,0 +1,402 @@
1
+(function(){
2
+
3
+var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto;
4
+
5
+// Shortcuts
6
+var util = C.util,
7
+    charenc = C.charenc,
8
+    UTF8 = charenc.UTF8;
9
+
10
+// Precomputed SBOX
11
+var SBOX = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
12
+             0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
13
+             0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
14
+             0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
15
+             0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
16
+             0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
17
+             0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
18
+             0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
19
+             0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
20
+             0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
21
+             0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
22
+             0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
23
+             0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
24
+             0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
25
+             0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
26
+             0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
27
+             0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
28
+             0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
29
+             0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
30
+             0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
31
+             0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
32
+             0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
33
+             0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
34
+             0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
35
+             0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
36
+             0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
37
+             0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
38
+             0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
39
+             0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
40
+             0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
41
+             0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
42
+             0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ];
43
+
44
+// Compute inverse SBOX lookup table
45
+for (var INVSBOX = [], i = 0; i < 256; i++) INVSBOX[SBOX[i]] = i;
46
+
47
+// Compute mulitplication in GF(2^8) lookup tables
48
+var MULT2 = [],
49
+    MULT3 = [],
50
+    MULT9 = [],
51
+    MULTB = [],
52
+    MULTD = [],
53
+    MULTE = [];
54
+
55
+function xtime(a, b) {
56
+	for (var result = 0, i = 0; i < 8; i++) {
57
+		if (b & 1) result ^= a;
58
+		var hiBitSet = a & 0x80;
59
+		a = (a << 1) & 0xFF;
60
+		if (hiBitSet) a ^= 0x1b;
61
+		b >>>= 1;
62
+	}
63
+	return result;
64
+}
65
+
66
+for (var i = 0; i < 256; i++) {
67
+	MULT2[i] = xtime(i,2);
68
+	MULT3[i] = xtime(i,3);
69
+	MULT9[i] = xtime(i,9);
70
+	MULTB[i] = xtime(i,0xB);
71
+	MULTD[i] = xtime(i,0xD);
72
+	MULTE[i] = xtime(i,0xE);
73
+}
74
+
75
+// Precomputed RCon lookup
76
+var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
77
+
78
+// Inner state
79
+var state = [[], [], [], []],
80
+    keylength,
81
+    nrounds,
82
+    keyschedule;
83
+
84
+var AES = C.AES = {
85
+
86
+	/**
87
+	 * Public API
88
+	 */
89
+
90
+	encrypt: function (message, password, options) {
91
+
92
+		options = options || {};
93
+
94
+		// Determine mode
95
+		var mode = options.mode || new C.mode.OFB;
96
+
97
+		// Allow mode to override options
98
+		if (mode.fixOptions) mode.fixOptions(options);
99
+
100
+		var
101
+
102
+			// Convert to bytes if message is a string
103
+			m = (
104
+				message.constructor == String ?
105
+				UTF8.stringToBytes(message) :
106
+				message
107
+			),
108
+
109
+			// Generate random IV
110
+			iv = options.iv || util.randomBytes(AES._blocksize * 4),
111
+
112
+			// Generate key
113
+			k = (
114
+				password.constructor == String ?
115
+				// Derive key from passphrase
116
+				C.PBKDF2(password, iv, 32, { asBytes: true }) :
117
+				// else, assume byte array representing cryptographic key
118
+				password
119
+			);
120
+
121
+		// Encrypt
122
+		AES._init(k);
123
+		mode.encrypt(AES, m, iv);
124
+
125
+		// Return ciphertext
126
+		m = options.iv ? m : iv.concat(m);
127
+		return (options && options.asBytes) ? m : util.bytesToBase64(m);
128
+
129
+	},
130
+
131
+	decrypt: function (ciphertext, password, options) {
132
+
133
+		options = options || {};
134
+
135
+		// Determine mode
136
+		var mode = options.mode || new C.mode.OFB;
137
+
138
+		// Allow mode to override options
139
+		if (mode.fixOptions) mode.fixOptions(options);
140
+
141
+		var
142
+
143
+			// Convert to bytes if ciphertext is a string
144
+			c = (
145
+				ciphertext.constructor == String ?
146
+				util.base64ToBytes(ciphertext):
147
+			    ciphertext
148
+			),
149
+
150
+			// Separate IV and message
151
+			iv = options.iv || c.splice(0, AES._blocksize * 4),
152
+
153
+			// Generate key
154
+			k = (
155
+				password.constructor == String ?
156
+				// Derive key from passphrase
157
+				C.PBKDF2(password, iv, 32, { asBytes: true }) :
158
+				// else, assume byte array representing cryptographic key
159
+				password
160
+			);
161
+
162
+		// Decrypt
163
+		AES._init(k);
164
+		mode.decrypt(AES, c, iv);
165
+
166
+		// Return plaintext
167
+		return (options && options.asBytes) ? c : UTF8.bytesToString(c);
168
+
169
+	},
170
+
171
+
172
+	/**
173
+	 * Package private methods and properties
174
+	 */
175
+
176
+	_blocksize: 4,
177
+
178
+	_encryptblock: function (m, offset) {
179
+
180
+		// Set input
181
+		for (var row = 0; row < AES._blocksize; row++) {
182
+			for (var col = 0; col < 4; col++)
183
+				state[row][col] = m[offset + col * 4 + row];
184
+		}
185
+
186
+		// Add round key
187
+		for (var row = 0; row < 4; row++) {
188
+			for (var col = 0; col < 4; col++)
189
+				state[row][col] ^= keyschedule[col][row];
190
+		}
191
+
192
+		for (var round = 1; round < nrounds; round++) {
193
+
194
+			// Sub bytes
195
+			for (var row = 0; row < 4; row++) {
196
+				for (var col = 0; col < 4; col++)
197
+					state[row][col] = SBOX[state[row][col]];
198
+			}
199
+
200
+			// Shift rows
201
+			state[1].push(state[1].shift());
202
+			state[2].push(state[2].shift());
203
+			state[2].push(state[2].shift());
204
+			state[3].unshift(state[3].pop());
205
+
206
+			// Mix columns
207
+			for (var col = 0; col < 4; col++) {
208
+
209
+				var s0 = state[0][col],
210
+				    s1 = state[1][col],
211
+				    s2 = state[2][col],
212
+				    s3 = state[3][col];
213
+
214
+				state[0][col] = MULT2[s0] ^ MULT3[s1] ^ s2 ^ s3;
215
+				state[1][col] = s0 ^ MULT2[s1] ^ MULT3[s2] ^ s3;
216
+				state[2][col] = s0 ^ s1 ^ MULT2[s2] ^ MULT3[s3];
217
+				state[3][col] = MULT3[s0] ^ s1 ^ s2 ^ MULT2[s3];
218
+
219
+			}
220
+
221
+			// Add round key
222
+			for (var row = 0; row < 4; row++) {
223
+				for (var col = 0; col < 4; col++)
224
+					state[row][col] ^= keyschedule[round * 4 + col][row];
225
+			}
226
+
227
+		}
228
+
229
+		// Sub bytes
230
+		for (var row = 0; row < 4; row++) {
231
+			for (var col = 0; col < 4; col++)
232
+				state[row][col] = SBOX[state[row][col]];
233
+		}
234
+
235
+		// Shift rows
236
+		state[1].push(state[1].shift());
237
+		state[2].push(state[2].shift());
238
+		state[2].push(state[2].shift());
239
+		state[3].unshift(state[3].pop());
240
+
241
+		// Add round key
242
+		for (var row = 0; row < 4; row++) {
243
+			for (var col = 0; col < 4; col++)
244
+				state[row][col] ^= keyschedule[nrounds * 4 + col][row];
245
+		}
246
+
247
+		// Set output
248
+		for (var row = 0; row < AES._blocksize; row++) {
249
+			for (var col = 0; col < 4; col++)
250
+				m[offset + col * 4 + row] = state[row][col];
251
+		}
252
+
253
+	},
254
+
255
+	_decryptblock: function (c, offset) {
256
+
257
+		// Set input
258
+		for (var row = 0; row < AES._blocksize; row++) {
259
+			for (var col = 0; col < 4; col++)
260
+				state[row][col] = c[offset + col * 4 + row];
261
+		}
262
+
263
+		// Add round key
264
+		for (var row = 0; row < 4; row++) {
265
+			for (var col = 0; col < 4; col++)
266
+				state[row][col] ^= keyschedule[nrounds * 4 + col][row];
267
+		}
268
+
269
+		for (var round = 1; round < nrounds; round++) {
270
+
271
+			// Inv shift rows
272
+			state[1].unshift(state[1].pop());
273
+			state[2].push(state[2].shift());
274
+			state[2].push(state[2].shift());
275
+			state[3].push(state[3].shift());
276
+
277
+			// Inv sub bytes
278
+			for (var row = 0; row < 4; row++) {
279
+				for (var col = 0; col < 4; col++)
280
+					state[row][col] = INVSBOX[state[row][col]];
281
+			}
282
+
283
+			// Add round key
284
+			for (var row = 0; row < 4; row++) {
285
+				for (var col = 0; col < 4; col++)
286
+					state[row][col] ^= keyschedule[(nrounds - round) * 4 + col][row];
287
+			}
288
+
289
+			// Inv mix columns
290
+			for (var col = 0; col < 4; col++) {
291
+
292
+				var s0 = state[0][col],
293
+				    s1 = state[1][col],
294
+				    s2 = state[2][col],
295
+				    s3 = state[3][col];
296
+
297
+				state[0][col] = MULTE[s0] ^ MULTB[s1] ^ MULTD[s2] ^ MULT9[s3];
298
+				state[1][col] = MULT9[s0] ^ MULTE[s1] ^ MULTB[s2] ^ MULTD[s3];
299
+				state[2][col] = MULTD[s0] ^ MULT9[s1] ^ MULTE[s2] ^ MULTB[s3];
300
+				state[3][col] = MULTB[s0] ^ MULTD[s1] ^ MULT9[s2] ^ MULTE[s3];
301
+
302
+			}
303
+
304
+		}
305
+
306
+		// Inv shift rows
307
+		state[1].unshift(state[1].pop());
308
+		state[2].push(state[2].shift());
309
+		state[2].push(state[2].shift());
310
+		state[3].push(state[3].shift());
311
+
312
+		// Inv sub bytes
313
+		for (var row = 0; row < 4; row++) {
314
+			for (var col = 0; col < 4; col++)
315
+				state[row][col] = INVSBOX[state[row][col]];
316
+		}
317
+
318
+		// Add round key
319
+		for (var row = 0; row < 4; row++) {
320
+			for (var col = 0; col < 4; col++)
321
+				state[row][col] ^= keyschedule[col][row];
322
+		}
323
+
324
+		// Set output
325
+		for (var row = 0; row < AES._blocksize; row++) {
326
+			for (var col = 0; col < 4; col++)
327
+				c[offset + col * 4 + row] = state[row][col];
328
+		}
329
+
330
+	},
331
+
332
+
333
+	/**
334
+	 * Private methods
335
+	 */
336
+
337
+	_init: function (k) {
338
+		keylength = k.length / 4;
339
+		nrounds = keylength + 6;
340
+		AES._keyexpansion(k);
341
+	},
342
+
343
+	// Generate a key schedule
344
+	_keyexpansion: function (k) {
345
+
346
+		keyschedule = [];
347
+
348
+		for (var row = 0; row < keylength; row++) {
349
+			keyschedule[row] = [
350
+				k[row * 4],
351
+				k[row * 4 + 1],
352
+				k[row * 4 + 2],
353
+				k[row * 4 + 3]
354
+			];
355
+		}
356
+
357
+		for (var row = keylength; row < AES._blocksize * (nrounds + 1); row++) {
358
+
359
+			var temp = [
360
+				keyschedule[row - 1][0],
361
+				keyschedule[row - 1][1],
362
+				keyschedule[row - 1][2],
363
+				keyschedule[row - 1][3]
364
+			];
365
+
366
+			if (row % keylength == 0) {
367
+
368
+				// Rot word
369
+				temp.push(temp.shift());
370
+
371
+				// Sub word
372
+				temp[0] = SBOX[temp[0]];
373
+				temp[1] = SBOX[temp[1]];
374
+				temp[2] = SBOX[temp[2]];
375
+				temp[3] = SBOX[temp[3]];
376
+
377
+				temp[0] ^= RCON[row / keylength];
378
+
379
+			} else if (keylength > 6 && row % keylength == 4) {
380
+
381
+				// Sub word
382
+				temp[0] = SBOX[temp[0]];
383
+				temp[1] = SBOX[temp[1]];
384
+				temp[2] = SBOX[temp[2]];
385
+				temp[3] = SBOX[temp[3]];
386
+
387
+			}
388
+
389
+			keyschedule[row] = [
390
+				keyschedule[row - keylength][0] ^ temp[0],
391
+				keyschedule[row - keylength][1] ^ temp[1],
392
+				keyschedule[row - keylength][2] ^ temp[2],
393
+				keyschedule[row - keylength][3] ^ temp[3]
394
+			];
395
+
396
+		}
397
+
398
+	}
399
+
400
+};
401
+
402
+})();

+ 378 - 0
utils/lib/BlockModes.js

@@ -0,0 +1,378 @@
1
+/*!
2
+ * Crypto-JS contribution from Simon Greatrix
3
+ */
4
+
5
+(function(){
6
+
7
+var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto;
8
+
9
+// Create pad namespace
10
+var C_pad = C.pad = {};
11
+
12
+// Calculate the number of padding bytes required.
13
+function _requiredPadding(cipher, message) {
14
+    var blockSizeInBytes = cipher._blocksize * 4;
15
+    var reqd = blockSizeInBytes - message.length % blockSizeInBytes;
16
+    return reqd;
17
+};
18
+
19
+// Remove padding when the final byte gives the number of padding bytes.
20
+var _unpadLength = function (message) {
21
+        var pad = message.pop();
22
+        for (var i = 1; i < pad; i++) {
23
+            message.pop();
24
+        }
25
+    };
26
+
27
+// No-operation padding, used for stream ciphers
28
+C_pad.NoPadding = {
29
+        pad : function (cipher,message) {},
30
+        unpad : function (message) {}
31
+    };
32
+
33
+// Zero Padding.
34
+//
35
+// If the message is not an exact number of blocks, the final block is
36
+// completed with 0x00 bytes. There is no unpadding.
37
+C_pad.ZeroPadding = {
38
+    pad : function (cipher, message) {
39
+        var blockSizeInBytes = cipher._blocksize * 4;
40
+        var reqd = message.length % blockSizeInBytes;
41
+        if( reqd!=0 ) {
42
+            for(reqd = blockSizeInBytes - reqd; reqd>0; reqd--) {
43
+                message.push(0x00);
44
+            }
45
+        }
46
+    },
47
+
48
+    unpad : function (message) {}
49
+};
50
+
51
+// ISO/IEC 7816-4 padding.
52
+//
53
+// Pads the plain text with an 0x80 byte followed by as many 0x00
54
+// bytes are required to complete the block.
55
+C_pad.iso7816 = {
56
+    pad : function (cipher, message) {
57
+        var reqd = _requiredPadding(cipher, message);
58
+        message.push(0x80);
59
+        for (; reqd > 1; reqd--) {
60
+            message.push(0x00);
61
+        }
62
+    },
63
+
64
+    unpad : function (message) {
65
+        while (message.pop() != 0x80) {}
66
+    }
67
+};
68
+
69
+// ANSI X.923 padding
70
+//
71
+// The final block is padded with zeros except for the last byte of the
72
+// last block which contains the number of padding bytes.
73
+C_pad.ansix923 = {
74
+    pad : function (cipher, message) {
75
+        var reqd = _requiredPadding(cipher, message);
76
+        for (var i = 1; i < reqd; i++) {
77
+            message.push(0x00);
78
+        }
79
+        message.push(reqd);
80
+    },
81
+
82
+    unpad : _unpadLength
83
+};
84
+
85
+// ISO 10126
86
+//
87
+// The final block is padded with random bytes except for the last
88
+// byte of the last block which contains the number of padding bytes.
89
+C_pad.iso10126 = {
90
+    pad : function (cipher, message) {
91
+        var reqd = _requiredPadding(cipher, message);
92
+        for (var i = 1; i < reqd; i++) {
93
+            message.push(Math.floor(Math.random() * 256));
94
+        }
95
+        message.push(reqd);
96
+    },
97
+
98
+    unpad : _unpadLength
99
+};
100
+
101
+// PKCS7 padding
102
+//
103
+// PKCS7 is described in RFC 5652. Padding is in whole bytes. The
104
+// value of each added byte is the number of bytes that are added,
105
+// i.e. N bytes, each of value N are added.
106
+C_pad.pkcs7 = {
107
+    pad : function (cipher, message) {
108
+        var reqd = _requiredPadding(cipher, message);
109
+        for (var i = 0; i < reqd; i++) {
110
+            message.push(reqd);
111
+        }
112
+    },
113
+
114
+    unpad : _unpadLength
115
+};
116
+
117
+// Create mode namespace
118
+var C_mode = C.mode = {};
119
+
120
+/**
121
+ * Mode base "class".
122
+ */
123
+var Mode = C_mode.Mode = function (padding) {
124
+    if (padding) {
125
+        this._padding = padding;
126
+    }
127
+};
128
+
129
+Mode.prototype = {
130
+    encrypt: function (cipher, m, iv) {
131
+        this._padding.pad(cipher, m);
132
+        this._doEncrypt(cipher, m, iv);
133
+    },
134
+
135
+    decrypt: function (cipher, m, iv) {
136
+        this._doDecrypt(cipher, m, iv);
137
+        this._padding.unpad(m);
138
+    },
139
+
140
+    // Default padding
141
+    _padding: C_pad.iso7816
142
+};
143
+
144
+
145
+/**
146
+ * Electronic Code Book mode.
147
+ * 
148
+ * ECB applies the cipher directly against each block of the input.
149
+ * 
150
+ * ECB does not require an initialization vector.
151
+ */
152
+var ECB = C_mode.ECB = function () {
153
+    // Call parent constructor
154
+    Mode.apply(this, arguments);
155
+};
156
+
157
+// Inherit from Mode
158
+var ECB_prototype = ECB.prototype = new Mode;
159
+
160
+// Concrete steps for Mode template
161
+ECB_prototype._doEncrypt = function (cipher, m, iv) {
162
+    var blockSizeInBytes = cipher._blocksize * 4;
163
+    // Encrypt each block
164
+    for (var offset = 0; offset < m.length; offset += blockSizeInBytes) {
165
+        cipher._encryptblock(m, offset);
166
+    }
167
+};
168
+ECB_prototype._doDecrypt = function (cipher, c, iv) {
169
+    var blockSizeInBytes = cipher._blocksize * 4;
170
+    // Decrypt each block
171
+    for (var offset = 0; offset < c.length; offset += blockSizeInBytes) {
172
+        cipher._decryptblock(c, offset);
173
+    }
174
+};
175
+
176
+// ECB never uses an IV
177
+ECB_prototype.fixOptions = function (options) {
178
+    options.iv = [];
179
+};
180
+
181
+
182
+/**
183
+ * Cipher block chaining
184
+ * 
185
+ * The first block is XORed with the IV. Subsequent blocks are XOR with the
186
+ * previous cipher output.
187
+ */
188
+var CBC = C_mode.CBC = function () {
189
+    // Call parent constructor
190
+    Mode.apply(this, arguments);
191
+};
192
+
193
+// Inherit from Mode
194
+var CBC_prototype = CBC.prototype = new Mode;
195
+
196
+// Concrete steps for Mode template
197
+CBC_prototype._doEncrypt = function (cipher, m, iv) {
198
+    var blockSizeInBytes = cipher._blocksize * 4;
199
+
200
+    // Encrypt each block
201
+    for (var offset = 0; offset < m.length; offset += blockSizeInBytes) {
202
+        if (offset == 0) {
203
+            // XOR first block using IV
204
+            for (var i = 0; i < blockSizeInBytes; i++)
205
+            m[i] ^= iv[i];
206
+        } else {
207
+            // XOR this block using previous crypted block
208
+            for (var i = 0; i < blockSizeInBytes; i++)
209
+            m[offset + i] ^= m[offset + i - blockSizeInBytes];
210
+        }
211
+        // Encrypt block
212
+        cipher._encryptblock(m, offset);
213
+    }
214
+};
215
+CBC_prototype._doDecrypt = function (cipher, c, iv) {
216
+    var blockSizeInBytes = cipher._blocksize * 4;
217
+
218
+    // At the start, the previously crypted block is the IV
219
+    var prevCryptedBlock = iv;
220
+
221
+    // Decrypt each block
222
+    for (var offset = 0; offset < c.length; offset += blockSizeInBytes) {
223
+        // Save this crypted block
224
+        var thisCryptedBlock = c.slice(offset, offset + blockSizeInBytes);
225
+        // Decrypt block
226
+        cipher._decryptblock(c, offset);
227
+        // XOR decrypted block using previous crypted block
228
+        for (var i = 0; i < blockSizeInBytes; i++) {
229
+            c[offset + i] ^= prevCryptedBlock[i];
230
+        }
231
+        prevCryptedBlock = thisCryptedBlock;
232
+    }
233
+};
234
+
235
+
236
+/**
237
+ * Cipher feed back
238
+ * 
239
+ * The cipher output is XORed with the plain text to produce the cipher output,
240
+ * which is then fed back into the cipher to produce a bit pattern to XOR the
241
+ * next block with.
242
+ * 
243
+ * This is a stream cipher mode and does not require padding.
244
+ */
245
+var CFB = C_mode.CFB = function () {
246
+    // Call parent constructor
247
+    Mode.apply(this, arguments);
248
+};
249
+
250
+// Inherit from Mode
251
+var CFB_prototype = CFB.prototype = new Mode;
252
+
253
+// Override padding
254
+CFB_prototype._padding = C_pad.NoPadding;
255
+
256
+// Concrete steps for Mode template
257
+CFB_prototype._doEncrypt = function (cipher, m, iv) {
258
+    var blockSizeInBytes = cipher._blocksize * 4,
259
+        keystream = iv.slice(0);
260
+
261
+    // Encrypt each byte
262
+    for (var i = 0; i < m.length; i++) {
263
+
264
+        var j = i % blockSizeInBytes;
265
+        if (j == 0) cipher._encryptblock(keystream, 0);
266
+
267
+        m[i] ^= keystream[j];
268
+        keystream[j] = m[i];
269
+    }
270
+};
271
+CFB_prototype._doDecrypt = function (cipher, c, iv) {
272
+    var blockSizeInBytes = cipher._blocksize * 4,
273
+        keystream = iv.slice(0);
274
+
275
+    // Encrypt each byte
276
+    for (var i = 0; i < c.length; i++) {
277
+
278
+        var j = i % blockSizeInBytes;
279
+        if (j == 0) cipher._encryptblock(keystream, 0);
280
+
281
+        var b = c[i];
282
+        c[i] ^= keystream[j];
283
+        keystream[j] = b;
284
+    }
285
+};
286
+
287
+
288
+/**
289
+ * Output feed back
290
+ * 
291
+ * The cipher repeatedly encrypts its own output. The output is XORed with the
292
+ * plain text to produce the cipher text.
293
+ * 
294
+ * This is a stream cipher mode and does not require padding.
295
+ */
296
+var OFB = C_mode.OFB = function () {
297
+    // Call parent constructor
298
+    Mode.apply(this, arguments);
299
+};
300
+
301
+// Inherit from Mode
302
+var OFB_prototype = OFB.prototype = new Mode;
303
+
304
+// Override padding
305
+OFB_prototype._padding = C_pad.NoPadding;
306
+
307
+// Concrete steps for Mode template
308
+OFB_prototype._doEncrypt = function (cipher, m, iv) {
309
+
310
+    var blockSizeInBytes = cipher._blocksize * 4,
311
+        keystream = iv.slice(0);
312
+
313
+    // Encrypt each byte
314
+    for (var i = 0; i < m.length; i++) {
315
+
316
+        // Generate keystream
317
+        if (i % blockSizeInBytes == 0)
318
+            cipher._encryptblock(keystream, 0);
319
+
320
+        // Encrypt byte
321
+        m[i] ^= keystream[i % blockSizeInBytes];
322
+
323
+    }
324
+};
325
+OFB_prototype._doDecrypt = OFB_prototype._doEncrypt;
326
+
327
+/**
328
+ * Counter
329
+ * @author Gergely Risko
330
+ *
331
+ * After every block the last 4 bytes of the IV is increased by one
332
+ * with carry and that IV is used for the next block.
333
+ *
334
+ * This is a stream cipher mode and does not require padding.
335
+ */
336
+var CTR = C_mode.CTR = function () {
337
+    // Call parent constructor
338
+    Mode.apply(this, arguments);
339
+};
340
+
341
+// Inherit from Mode
342
+var CTR_prototype = CTR.prototype = new Mode;
343
+
344
+// Override padding
345
+CTR_prototype._padding = C_pad.NoPadding;
346
+
347
+CTR_prototype._doEncrypt = function (cipher, m, iv) {
348
+    var blockSizeInBytes = cipher._blocksize * 4;
349
+    var counter = iv.slice(0);
350
+
351
+    for (var i = 0; i < m.length;) {
352
+        // do not lose iv
353
+        var keystream = counter.slice(0);
354
+
355
+        // Generate keystream for next block
356
+        cipher._encryptblock(keystream, 0);
357
+
358
+        // XOR keystream with block
359
+        for (var j = 0; i < m.length && j < blockSizeInBytes; j++, i++) {
360
+            m[i] ^= keystream[j];
361
+        }
362
+
363
+        // Increase counter
364
+        if(++(counter[blockSizeInBytes-1]) == 256) {
365
+            counter[blockSizeInBytes-1] = 0;
366
+            if(++(counter[blockSizeInBytes-2]) == 256) {
367
+                counter[blockSizeInBytes-2] = 0;
368
+                if(++(counter[blockSizeInBytes-3]) == 256) {
369
+                    counter[blockSizeInBytes-3] = 0;
370
+                    ++(counter[blockSizeInBytes-4]);
371
+                }
372
+            }
373
+        }
374
+    }
375
+};
376
+CTR_prototype._doDecrypt = CTR_prototype._doEncrypt;
377
+
378
+})();

+ 155 - 0
utils/lib/Crypto.js

@@ -0,0 +1,155 @@
1
+if (typeof Crypto == "undefined" || ! Crypto.util)
2
+{
3
+(function(){
4
+
5
+var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6
+
7
+// Global Crypto object
8
+// with browser window or with node module
9
+var Crypto = (typeof window === 'undefined') ? exports.Crypto = {} : window.Crypto = {}; 
10
+
11
+// Crypto utilities
12
+var util = Crypto.util = {
13
+
14
+	// Bit-wise rotate left
15
+	rotl: function (n, b) {
16
+		return (n << b) | (n >>> (32 - b));
17
+	},
18
+
19
+	// Bit-wise rotate right
20
+	rotr: function (n, b) {
21
+		return (n << (32 - b)) | (n >>> b);
22
+	},
23
+
24
+	// Swap big-endian to little-endian and vice versa
25
+	endian: function (n) {
26
+
27
+		// If number given, swap endian
28
+		if (n.constructor == Number) {
29
+			return util.rotl(n,  8) & 0x00FF00FF |
30
+			       util.rotl(n, 24) & 0xFF00FF00;
31
+		}
32
+
33
+		// Else, assume array and swap all items
34
+		for (var i = 0; i < n.length; i++)
35
+			n[i] = util.endian(n[i]);
36
+		return n;
37
+
38
+	},
39
+
40
+	// Generate an array of any length of random bytes
41
+	randomBytes: function (n) {
42
+		for (var bytes = []; n > 0; n--)
43
+			bytes.push(Math.floor(Math.random() * 256));
44
+		return bytes;
45
+	},
46
+
47
+	// Convert a byte array to big-endian 32-bit words
48
+	bytesToWords: function (bytes) {
49
+		for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
50
+			words[b >>> 5] |= (bytes[i] & 0xFF) << (24 - b % 32);
51
+		return words;
52
+	},
53
+
54
+	// Convert big-endian 32-bit words to a byte array
55
+	wordsToBytes: function (words) {
56
+		for (var bytes = [], b = 0; b < words.length * 32; b += 8)
57
+			bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
58
+		return bytes;
59
+	},
60
+
61
+	// Convert a byte array to a hex string
62
+	bytesToHex: function (bytes) {
63
+		for (var hex = [], i = 0; i < bytes.length; i++) {
64
+			hex.push((bytes[i] >>> 4).toString(16));
65
+			hex.push((bytes[i] & 0xF).toString(16));
66
+		}
67
+		return hex.join("");
68
+	},
69
+
70
+	// Convert a hex string to a byte array
71
+	hexToBytes: function (hex) {
72
+		for (var bytes = [], c = 0; c < hex.length; c += 2)
73
+			bytes.push(parseInt(hex.substr(c, 2), 16));
74
+		return bytes;
75
+	},
76
+
77
+	// Convert a byte array to a base-64 string
78
+	bytesToBase64: function (bytes) {
79
+
80
+		// Use browser-native function if it exists
81
+		if (typeof btoa == "function") return btoa(Binary.bytesToString(bytes));
82
+
83
+		for(var base64 = [], i = 0; i < bytes.length; i += 3) {
84
+			var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
85
+			for (var j = 0; j < 4; j++) {
86
+				if (i * 8 + j * 6 <= bytes.length * 8)
87
+					base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F));
88
+				else base64.push("=");
89
+			}
90
+		}
91
+
92
+		return base64.join("");
93
+
94
+	},
95
+
96
+	// Convert a base-64 string to a byte array
97
+	base64ToBytes: function (base64) {
98
+
99
+		// Use browser-native function if it exists
100
+		if (typeof atob == "function") return Binary.stringToBytes(atob(base64));
101
+
102
+		// Remove non-base-64 characters
103
+		base64 = base64.replace(/[^A-Z0-9+\/]/ig, "");
104
+
105
+		for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) {
106
+			if (imod4 == 0) continue;
107
+			bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2)) |
108
+			           (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)));
109
+		}
110
+
111
+		return bytes;
112
+
113
+	}
114
+
115
+};
116
+
117
+// Crypto character encodings
118
+var charenc = Crypto.charenc = {};
119
+
120
+// UTF-8 encoding
121
+var UTF8 = charenc.UTF8 = {
122
+
123
+	// Convert a string to a byte array
124
+	stringToBytes: function (str) {
125
+		return Binary.stringToBytes(unescape(encodeURIComponent(str)));
126
+	},
127
+
128
+	// Convert a byte array to a string
129
+	bytesToString: function (bytes) {
130
+		return decodeURIComponent(escape(Binary.bytesToString(bytes)));
131
+	}
132
+
133
+};
134
+
135
+// Binary encoding
136
+var Binary = charenc.Binary = {
137
+
138
+	// Convert a string to a byte array
139
+	stringToBytes: function (str) {
140
+		for (var bytes = [], i = 0; i < str.length; i++)
141
+			bytes.push(str.charCodeAt(i) & 0xFF);
142
+		return bytes;
143
+	},
144
+
145
+	// Convert a byte array to a string
146
+	bytesToString: function (bytes) {
147
+		for (var str = [], i = 0; i < bytes.length; i++)
148
+			str.push(String.fromCharCode(bytes[i]));
149
+		return str.join("");
150
+	}
151
+
152
+};
153
+
154
+})();
155
+}

+ 114 - 0
utils/main.js

@@ -0,0 +1,114 @@
1
+import common from '../utils/util';
2
+import constant from '../utils/constant';
3
+
4
+var app = getApp();
5
+var dataSendTimeout = 0;
6
+
7
+function getData(url, callback) {
8
+  if (!app.globalData.IsProduction)
9
+    console.log("加密前的结果为===", url);
10
+  var url = common.Encrypt(url);
11
+  //console.log("加密后的结果为===",url);
12
+  wx.request({
13
+    url: app.globalData.serverUrl + url,
14
+    success: function (res) {
15
+      if (res.statusCode)
16
+        common.checkError(res.statusCode);
17
+      var data = res.data.result;
18
+      callback(data);
19
+    },
20
+    fail: function () {
21
+      wx.showToast({
22
+        title: '系统忙请稍候',
23
+        // image: "../images/universalpic_warning_white_126x120.png",
24
+        duration: 3000
25
+      });
26
+      var err={};
27
+      err.errCode=100;
28
+      err.errMsg="网络错误";
29
+      callback(null,err);
30
+    },
31
+  });
32
+}
33
+
34
+function postData(url, postData, callback) {
35
+  var url = common.Encrypt(url);
36
+  //console.log("加密后的结果为===",url);
37
+  wx.request({
38
+    url: app.globalData.serverUrl + url,
39
+    method: "POST",
40
+    data: postData,
41
+    success: function (res) {
42
+      if (res.statusCode)
43
+        common.checkError(res.statusCode);
44
+      var data = res.data.result;
45
+      callback(data);
46
+    },
47
+    fail: function () {
48
+      wx.showToast({
49
+        title: '系统忙请稍候',
50
+        duration: 3000
51
+      });
52
+    },
53
+  });
54
+}
55
+
56
+function getLocalHost(callback) {
57
+  if (!app.globalData.IsProduction) {
58
+    var url = common.Encrypt("Ping");
59
+    wx.request({
60
+      url: app.globalData.serverUrlLocalhost + url,
61
+      success: function (res) {
62
+        app.globalData.serverUrl = app.globalData.serverUrlLocalhost;
63
+        callback();
64
+      },
65
+      fail: function () {
66
+        app.globalData.serverUrl = app.globalData.serverUrlServer;
67
+        callback();
68
+      },
69
+    });
70
+  } else {
71
+    app.globalData.serverUrl = app.globalData.serverUrlServer;
72
+    callback();
73
+  }
74
+}
75
+
76
+
77
+function getWindowHeight() {
78
+
79
+  var height = app.globalData.systemInfo.windowHeight;
80
+  //console.log("app.globalData.systemInfo.windowHeight:" + app.globalData.systemInfo.windowHeight * 2);
81
+  if (app.globalData.systemInfo.model) {
82
+    if (height == 504 && (
83
+        app.globalData.systemInfo.model.indexOf("iPhone 6<") >= 0 ||
84
+        app.globalData.systemInfo.model.indexOf("iPhone 7<") >= 0 ||
85
+        app.globalData.systemInfo.model.indexOf("iPhone 6s<") >= 0 ||
86
+        app.globalData.systemInfo.model.indexOf("iPhone 5") >= 0 ||
87
+        app.globalData.systemInfo.model.indexOf("iPhone SE") >= 0
88
+      )) {
89
+      height = 596;
90
+    } else if (app.globalData.systemInfo.model.indexOf("iPad") >= 0) {
91
+      height = 470;
92
+    }
93
+  }
94
+
95
+  height = height * 2;
96
+  if (app.globalData.systemInfo.system && app.globalData.systemInfo.system.indexOf("Android") >= 0) {
97
+    height = height + 168;
98
+  } else {
99
+    height = height + 50;
100
+  }
101
+  //console.log("height:" + height);
102
+
103
+  //var height = app.globalData.systemInfo.screenHeight * 2;
104
+  return height;
105
+}
106
+
107
+
108
+
109
+module.exports = {
110
+  getData: getData,
111
+  postData: postData,
112
+  getLocalHost: getLocalHost,
113
+  getWindowHeight: getWindowHeight,
114
+}

+ 529 - 0
utils/util.js

@@ -0,0 +1,529 @@
1
+var Crypto = require('cryptojs.js').Crypto;
2
+var app = getApp();
3
+
4
+function formatTime(date, format, isShort) {
5
+  if (!date) return "";
6
+  
7
+  let dateObj;
8
+  if (date instanceof Date) {
9
+    dateObj = date;
10
+  } else {
11
+    // 统一处理日期字符串格式
12
+    let dateStr = date.toString();
13
+    
14
+    // 处理包含GMT的格式
15
+    if (dateStr.includes('GMT')) {
16
+      // 提取日期部分,转换为 yyyy/MM/dd HH:mm:ss 格式
17
+      let parts = dateStr.match(/(\w+)\s+(\w+)\s+(\d+)\s+(\d+)\s+(\d+):(\d+):(\d+)/);
18
+      if (parts) {
19
+        let months = {'Jan':1,'Feb':2,'Mar':3,'Apr':4,'May':5,'Jun':6,
20
+                     'Jul':7,'Aug':8,'Sep':9,'Oct':10,'Nov':11,'Dec':12};
21
+        let month = months[parts[2]];
22
+        dateStr = `${parts[4]}/${formatNumber(month)}/${formatNumber(parts[3])} ${parts[5]}:${parts[6]}:${parts[7]}`;
23
+      }
24
+    }
25
+    
26
+    // 统一将-转换为/
27
+    dateStr = dateStr.replace(/-/g, '/');
28
+    
29
+    // 确保日期格式为 yyyy/MM/dd HH:mm:ss
30
+    if (!/^\d{4}\/\d{1,2}\/\d{1,2}/.test(dateStr)) {
31
+      return "";
32
+    }
33
+    
34
+    dateObj = new Date(dateStr);
35
+  }
36
+
37
+  // 检查日期是否有效
38
+  if (isNaN(dateObj.getTime())) {
39
+    return "";
40
+  }
41
+
42
+  var year = dateObj.getFullYear();
43
+  var month = dateObj.getMonth() + 1;
44
+  var day = dateObj.getDate();
45
+  var hour = dateObj.getHours();
46
+  var minute = dateObj.getMinutes();
47
+  var second = dateObj.getSeconds();
48
+
49
+  if (!format) {
50
+    format = "/";
51
+  }
52
+
53
+  if (isShort) {
54
+    return [year, month, day].map(formatNumber).join(format);
55
+  } else {
56
+    return [year, month, day].map(formatNumber).join(format) + ' ' + 
57
+           [hour, minute, second].map(formatNumber).join(':');
58
+  }
59
+}
60
+
61
+function formatDateCHS(date,isLong) {
62
+  if (date) {
63
+    date = date.toString();
64
+    var year = date.substr(0, 4);
65
+    var month = date.substr(5, 2);
66
+    var day = date.substr(8, 2);
67
+
68
+    if (year <= "1900")
69
+      return "";
70
+    if (isLong)
71
+      return year + "年" + month + "月" + day + "日" +date.substr(10);
72
+    else
73
+      return year + "年" + month + "月" + day + "日";
74
+  }
75
+  else
76
+    return "";
77
+}
78
+
79
+function formatDateENG(date, format) {
80
+  if (date) {
81
+    date = date.toString();
82
+    if (!format)
83
+      format = "/";
84
+    date = date.replace("年", format);
85
+    date = date.replace("月", format);
86
+    date = date.replace("日", "");
87
+    return date;
88
+  }
89
+  else
90
+    return "";
91
+}
92
+
93
+
94
+function formatNumber(n) {
95
+  n = n.toString()
96
+  return n[1] ? n : '0' + n
97
+}
98
+
99
+//给字符串左侧补零
100
+function addZero(str, length) {
101
+  while (str.length < length) {
102
+    str = "0" + str;
103
+  }
104
+  return str;
105
+}
106
+
107
+function getMinuteSecond(second, chs) {
108
+  if (!second)
109
+    second = 0;
110
+  var secondUnit = "″";
111
+  var minuteUnit = "′";
112
+  var hourUnit = ":";
113
+  if (chs) {
114
+    secondUnit = "秒";
115
+    minuteUnit = "分";
116
+    hourUnit = "时";
117
+    //second = Math.round(second);
118
+  }
119
+  if (second < 60)
120
+    return second + secondUnit;
121
+  else {
122
+    var minute = Math.floor(second / 60);
123
+    second = Math.round((second - minute * 60) * 1000) / 1000;
124
+
125
+    if (minute >= 60) {
126
+      var hour = Math.floor(minute / 60);
127
+      minute = minute - hour * 60;
128
+
129
+      if (minute == 0 && second == 0)
130
+        return hour + hourUnit;
131
+      else if (second == 0)
132
+        return hour + hourUnit + minute + minuteUnit;
133
+      else
134
+        return hour + hourUnit + minute + minuteUnit + second + secondUnit;
135
+    }
136
+    else {
137
+      if (second == 0)
138
+        return minute + minuteUnit;
139
+      else
140
+        return minute + minuteUnit + second + secondUnit;
141
+    }
142
+  }
143
+}
144
+
145
+function Random(start, end) {
146
+  var result = parseInt(Math.random() * (end - start + 1) + start);
147
+  return result;
148
+}
149
+//打乱数组
150
+function RandomArray(arr) {
151
+  var arrResult = [];
152
+  var maxCount = 0;
153
+  do {
154
+    var rnd = Random(0, arr.length - 1);
155
+    if (arr[rnd]) {
156
+      arrResult.push(arr[rnd]);
157
+      arr[rnd] = null;
158
+    }
159
+    maxCount++;
160
+  }
161
+  while (arrResult.length < arr.length && maxCount < 1000);
162
+  for (var i = 0; i < arr.length; i++)
163
+    arr[i] = arrResult[i];
164
+  return arrResult;
165
+}
166
+
167
+function Unique(arr) {
168
+  var res = [];
169
+  var json = {};
170
+  for (var i = 0; i < arr.length; i++) {
171
+    if (!json[arr[i]]) {
172
+      res.push(arr[i]);
173
+      json[arr[i]] = 1;
174
+    }
175
+  }
176
+  return res;
177
+}
178
+
179
+function getEnumerationName(id, list) {
180
+  for (var i = 0; i < list.length; i++) {
181
+    if (id == list[i].EnumID) {
182
+      return list[i].Name;
183
+    }
184
+  }
185
+  return "";
186
+}
187
+
188
+function getEnumerationNameByDescription(description, list) {
189
+  for (var i = 0; i < list.length; i++) {
190
+    if (description == list[i].Description) {
191
+      return list[i].Name;
192
+    }
193
+  }
194
+  return "";
195
+}
196
+
197
+function getStringMaxLength(str, len) {
198
+  if (str.length > len)
199
+    return str.substr(0, len) + "...";
200
+  else
201
+    return str;
202
+}
203
+
204
+function sort(array, sort_order, obj, objType, obj2, objType2) {
205
+  for (var i = 0; i < array.length - 1; i++) {
206
+    for (var j = i + 1; j < array.length; j++) {
207
+      var check;
208
+      if (objType == "Number") {
209
+        if (sort_order == "ASC")
210
+          check = array[i][obj] > array[j][obj];
211
+        else
212
+          check = array[i][obj] < array[j][obj];
213
+      }
214
+      else {
215
+        //console.log("array["+i+"]:"+array[i][obj]);
216
+        //console.log("array["+j+"]:"+array[j][obj]);
217
+        if (array[i][obj] && array[j][obj]) {
218
+          try {
219
+            if (sort_order == "ASC")
220
+              check = array[i][obj].toString().localeCompare(array[j][obj].toString()) >= 0;
221
+            else if (sort_order == "DESC")
222
+              check = array[i][obj].toString().localeCompare(array[j][obj].toString()) < 0;
223
+          }
224
+          catch (ex) {
225
+            console.log("ex:" + ex);
226
+            if (sort_order == "ASC")
227
+              check = array[i][obj].toString() >= array[j][obj].toString();
228
+            else if (sort_order == "DESC")
229
+              check = array[i][obj].toString() < array[j][obj].toString();
230
+
231
+          }
232
+        }
233
+        else {
234
+          check = false;
235
+        }
236
+      }
237
+      if (check) {
238
+        var temp = swap(array[i], array[j]);
239
+        array[i] = temp.a;
240
+        array[j] = temp.b;
241
+      }
242
+    }
243
+  }
244
+  return array;
245
+
246
+  function swap(a, b) {
247
+    var tempA = JSON.stringify(a);
248
+    var tempB = JSON.stringify(b);
249
+    return {
250
+      a: JSON.parse(tempB),
251
+      b: JSON.parse(tempA),
252
+    }
253
+  }
254
+}
255
+////测试sort
256
+// var a = [
257
+//   {
258
+//     time: "2017-2-1",
259
+//     a: 3,
260
+//   }, {
261
+//     time: "2017-4-1",
262
+//     a: 5,
263
+//   }, {
264
+//     time: "2017-1-1",
265
+//     a: 6
266
+//   },{
267
+//     time: "2017-2-1",
268
+//     a: 3
269
+//   },
270
+// ];
271
+// console.log(a);
272
+// // var a = common.sort(a,"ASC", "time","String");
273
+// // console.log(a);
274
+// var a = common.sort(a,"DESC", "time","String");
275
+// console.log(a);
276
+// //var a = common.sort(a,"DESC", "a","Number");
277
+// // console.log(a);
278
+
279
+function checkError(err) {
280
+  switch (err) {
281
+    case 404:
282
+    case 500:
283
+    case 501:
284
+    case 502:
285
+    case 503:
286
+    case 504:
287
+    case 505:
288
+      wx.redirectTo({
289
+        url: './error',
290
+      });
291
+      break;
292
+  }
293
+  return null;
294
+}
295
+
296
+function Encrypt(word) {
297
+  var mode = new Crypto.mode.CBC(Crypto.pad.pkcs7);
298
+  var eb = Crypto.charenc.UTF8.stringToBytes(word);
299
+  var kb = Crypto.charenc.UTF8.stringToBytes(app.globalData.Key);//KEY
300
+  var vb = Crypto.charenc.UTF8.stringToBytes(app.globalData.IV);//IV
301
+  var ub = Crypto.AES.encrypt(eb, kb, { iv: vb, mode: mode, asBpytes: true });
302
+  return ub;
303
+}
304
+
305
+function Decrypt(word) {
306
+  var mode = new Crypto.mode.CBC(Crypto.pad.pkcs7);
307
+  var eb = Crypto.util.base64ToBytes(word);
308
+  var kb = Crypto.charenc.UTF8.stringToBytes(app.globalData.Key);//KEY
309
+  var vb = Crypto.charenc.UTF8.stringToBytes(app.globalData.IV);//IV
310
+  var ub = Crypto.AES.decrypt(eb, kb, { asBpytes: true, mode: mode, iv: vb });
311
+  return ub;
312
+}
313
+
314
+function isExistStr(str1, str2) {
315
+  var result = false;
316
+  if (str1) {
317
+    if (str1.toString().indexOf(str2) >= 0) {
318
+      result = true;
319
+    }
320
+  }
321
+  return result;
322
+}
323
+
324
+//获取存储数据,若不存在,则获得缺省值。
325
+function getStorageValue(obj, name, defaultStatus, callback) {
326
+  wx.getStorage({
327
+    key: name,
328
+    success: function (res) {
329
+      obj.data[name] = res.data;
330
+      obj.setData(obj.data);
331
+      if (callback)
332
+        callback();
333
+    },
334
+    fail: function (res) {
335
+      obj.data[name] = defaultStatus;
336
+      obj.setData(obj.data);
337
+      if (callback)
338
+        callback();
339
+    },
340
+  });
341
+}
342
+
343
+function addDate(interval, number, date) {
344
+  const newDate = new Date(date);
345
+  
346
+  switch (interval.toLowerCase()) {
347
+    case "y":
348
+      newDate.setFullYear(newDate.getFullYear() + number);
349
+      break;
350
+    case "m":
351
+      newDate.setMonth(newDate.getMonth() + number);
352
+      break;
353
+    case "d":
354
+      newDate.setDate(newDate.getDate() + number);
355
+      break;
356
+    case "w":
357
+      newDate.setDate(newDate.getDate() + 7 * number);
358
+      break;
359
+    case "h":
360
+      newDate.setHours(newDate.getHours() + number);
361
+      break;
362
+    case "n":
363
+      newDate.setMinutes(newDate.getMinutes() + number);
364
+      break;
365
+    case "s":
366
+      newDate.setSeconds(newDate.getSeconds() + number);
367
+      break;
368
+    case "l":
369
+      newDate.setMilliseconds(newDate.getMilliseconds() + number);
370
+      break;
371
+    default:
372
+      throw new Error(`不支持的间隔类型: ${interval}`);
373
+  }
374
+  
375
+  return newDate;
376
+}
377
+
378
+
379
+function diffDate(interval, date1, date2) {
380
+  var long = date2.getTime() - date1.getTime(); //相差毫秒
381
+  switch (interval.toLowerCase()) {
382
+    case "y":
383
+      return parseInt(date2.getFullYear() - date1.getFullYear());
384
+    case "m":
385
+      return parseInt((date2.getFullYear() - date1.getFullYear()) * 12 + (date2.getMonth() - date1.getMonth()));
386
+    case "d":
387
+      return parseInt(long / 1000 / 60 / 60 / 24);
388
+    case "w":
389
+      return parseInt(long / 1000 / 60 / 60 / 24 / 7);
390
+    case "h":
391
+      return parseInt(long / 1000 / 60 / 60);
392
+    case "n":
393
+      return parseInt(long / 1000 / 60);
394
+    case "s":
395
+      return parseInt(long / 1000);
396
+    case "l":
397
+      return parseInt(long);
398
+  }
399
+}
400
+
401
+/**
402
+ * 替换字符串中的所有匹配项
403
+ * @param {string} str - 需要替换的字符串
404
+ * @param {string} replaceStrFrom - 需要被替换的子字符串
405
+ * @param {string} replaceStrTo - 替换后的子字符串
406
+ * @returns {string} 替换后的字符串
407
+ */
408
+function ReplaceAllString(str, replaceStrFrom, replaceStrTo) {
409
+  if (typeof str === 'string' && typeof replaceStrFrom === 'string' && typeof replaceStrTo === 'string' && str.length > 0) {
410
+    const reg = new RegExp(replaceStrFrom, 'g');
411
+    return str.replace(reg, replaceStrTo);
412
+  }
413
+  return str;
414
+}
415
+
416
+
417
+
418
+function Trim(str) { //删除左右两端的空格
419
+  return str.replace(/(^\s*)|(\s*$)/g, "");
420
+}
421
+
422
+function IsNumber(value) {//判断是否是数字,true:是,false:不是
423
+  return !isNaN(value);
424
+}
425
+
426
+function formatMoney(money) {
427
+  var result;
428
+  if (money == 0)
429
+      result = "0.00";
430
+  else {
431
+      result = Math.round(money).toString();
432
+      //console.log("1:"+result);
433
+      if (result.indexOf(".") >= 0) {
434
+          if (result.substr(result.indexOf(".")).length < 3)
435
+              result += "0";
436
+      }
437
+      else {
438
+          result += ".00";
439
+      }
440
+      //console.log("2:"+result);
441
+
442
+  }
443
+  return result;
444
+}
445
+
446
+function initMonthCalendar(dates, line = 6,isNull=true) {
447
+  //console.log(dates); 
448
+  //dates="2022-07-19";
449
+  var date = new Date(dates);                          // 初始时间格式
450
+   
451
+   var y = date.getFullYear();
452
+   var m = date.getMonth();
453
+   var days = new Date(y, m+1, 0).getDate();          // 获取这个月共有多少天
454
+   var firstDayWeek = new Date(y, m, 1).getDay();       // 月份第一天星期几
455
+ 
456
+   firstDayWeek--
457
+   if (firstDayWeek==-1)
458
+    firstDayWeek=6;
459
+   //console.log("firstDayWeek:"+firstDayWeek); 
460
+   var arr = [];     // 存储日历格式的数组
461
+   var n = [];       // 日历格式中的一行
462
+   var d = 1;        // 日历格式中的天数
463
+ 
464
+   // 先根据这个月第一天排星期几
465
+   // 把上个月剩下几天留在这个月的'奸细'放在最前头
466
+   for(let i = 0; i < firstDayWeek; i++) {
467
+     if(isNull)
468
+        n.unshift("");
469
+     else
470
+        n.unshift(new Date(y, m, 0 - i).getDate());
471
+   }
472
+ 
473
+   // 开启循环
474
+   // 一星期占一行,一行一个外循环
475
+   // 这里我默认想要6行
476
+   for (let j = 0; j < line; j++) {
477
+     // 一天占一个格子,最多一星期7个格子
478
+     // 这里我想要7个格子
479
+     for (let i = 0; i < 7; i++) {
480
+       if(d > days) {
481
+         // 这个月都放完了,该放什么?
482
+         // new Date(2020, 8, 31)  --> 9月没有31 === 10月1
483
+          if (isNull){
484
+            //n.push("");
485
+          }
486
+          else
487
+            n.push(new Date(y, m, d++).getDate());
488
+          
489
+       } else {
490
+         // 放置这个月的天数
491
+        n.push(d++);
492
+       } 
493
+       if (n.length == 7) break;    // 放了7个格子该结束了
494
+     }
495
+ 
496
+     //arr.push(n);
497
+     //n = [];           // 这一行放完了,清空ba
498
+   }
499
+ 
500
+ 
501
+   return n;
502
+}
503
+
504
+module.exports = {
505
+  formatTime: formatTime,
506
+  formatDateCHS: formatDateCHS,
507
+  getMinuteSecond: getMinuteSecond,
508
+  random: Random,
509
+  randomArray: RandomArray,
510
+  unique: Unique,
511
+  getEnumerationName: getEnumerationName,
512
+  getStringMaxLength: getStringMaxLength,
513
+  sort: sort,
514
+  addZero: addZero,
515
+  checkError: checkError,
516
+  Encrypt: Encrypt,
517
+  Decrypt: Decrypt,
518
+  getEnumerationNameByDescription: getEnumerationNameByDescription,
519
+  isExistStr: isExistStr,
520
+  getStorageValue: getStorageValue,
521
+  ReplaceAllString: ReplaceAllString,
522
+  addDate: addDate,
523
+  diffDate: diffDate,
524
+  Trim: Trim,
525
+  IsNumber:IsNumber,
526
+  formatDateENG: formatDateENG,
527
+  formatMoney:formatMoney,
528
+  initMonthCalendar:initMonthCalendar
529
+}