ai-error-assistant-mobile 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -120,4 +120,20 @@ export async function sendMessageEventSource(params, signal, onmessage) {
120
120
  body: JSON.stringify(params),
121
121
  onmessage,
122
122
  });
123
- }
123
+ }
124
+
125
+ /**
126
+ * 提交意见反馈
127
+ * @param {FormData} formData
128
+ * @param {string} token
129
+ * @returns {Promise}
130
+ */
131
+ export const submitFeedback = (formData, token) => {
132
+ return request({
133
+ url: prefix + '/assistant/tools/feedback',
134
+ method: 'post',
135
+ headers: { 'Content-Type': 'multipart/form-data' },
136
+ data: formData,
137
+ });
138
+ }
139
+
@@ -587,6 +587,16 @@ export default {
587
587
  }
588
588
  }
589
589
 
590
+ :deep {
591
+ .robot-message {
592
+ ol,ul{
593
+ font-size: 14px;
594
+ margin: 0;
595
+ padding: 0;
596
+ }
597
+ }
598
+ }
599
+
590
600
  @media screen and (min-width: 768px) {
591
601
  .chat-contain {
592
602
  padding: 1rem;
@@ -0,0 +1,319 @@
1
+ <template>
2
+ <div class="feedback-drawer-wrap">
3
+ <div class="feedback-mask" @click="$emit('close')"></div>
4
+ <div class="feedback-drawer" :class="{ open: true }">
5
+ <div class="feedback-header">
6
+ <div class="header-icon-wrap">
7
+ <svg class="header-icon" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#7a8bff"/><text x="16" y="22" text-anchor="middle" font-size="20" fill="#fff">!</text></svg>
8
+ </div>
9
+ <button class="close-btn" @click="$emit('close')">×</button>
10
+ <h2>您的反馈对我们很重要</h2>
11
+ <p class="subtitle">请详细描述您遇到的问题,我们会尽快处理</p>
12
+ </div>
13
+ <el-form :model="form" :rules="rules" ref="feedbackForm" label-width="0">
14
+ <div class="feedback-form">
15
+ <div class="form-group">
16
+ <el-form-item prop="description" style="margin-bottom: 0px;">
17
+ <label for="description">问题描述</label>
18
+ <textarea
19
+ id="description"
20
+ v-model="form.description"
21
+ placeholder="请详细描述您遇到的问题,以便我们更好地帮助您"
22
+ rows="4"
23
+ />
24
+ </el-form-item>
25
+ </div>
26
+ <div class="form-group">
27
+ <el-form-item prop="phone">
28
+ <label for="phone">联系方式</label>
29
+ <input
30
+ id="phone"
31
+ v-model="form.phone"
32
+ placeholder="方便我们联系您解决问题"
33
+ />
34
+ </el-form-item>
35
+ </div>
36
+ <label class="upload-label">上传图片</label>
37
+ <div class="upload-section">
38
+
39
+ <div class="upload-row">
40
+ <el-upload
41
+ class="el-upload-feedback"
42
+ action="#"
43
+ :auto-upload="false"
44
+ :limit="1"
45
+ :on-change="onElUploadChange"
46
+ :before-upload="beforeElUpload"
47
+ :file-list="elFileList"
48
+ :show-file-list="true"
49
+ list-type="picture-card"
50
+ accept="image/*"
51
+ >
52
+ <i class="el-icon-plus"></i>
53
+ </el-upload>
54
+ </div>
55
+ <div class="upload-tip">最多上传1张图片,大小不超过10MB</div>
56
+ </div>
57
+ <div class="submit-section">
58
+ <el-button type="primary" @click="submitForm" class="submit-button" block>提交反馈</el-button>
59
+ </div>
60
+ </div>
61
+ </el-form>
62
+ </div>
63
+ </div>
64
+ </template>
65
+
66
+ <script>
67
+ import { Upload, Form, FormItem, Button } from 'element-ui';
68
+ import { submitFeedback } from '../../demo/api/index';
69
+ export default {
70
+ name: 'Feedback',
71
+ components: { Upload, ElForm: Form, ElFormItem: FormItem, ElButton: Button },
72
+ props: {
73
+ businessSource: {
74
+ type: String,
75
+ default: ''
76
+ },
77
+ },
78
+ data() {
79
+ return {
80
+ form: {
81
+ description: '',
82
+ phone: ''
83
+ },
84
+ elFileList: [],
85
+ token: '',
86
+ rules: {
87
+ description: [
88
+ { required: true, message: '请输入问题描述', trigger: 'blur' },
89
+ ],
90
+ phone: []
91
+ }
92
+ };
93
+ },
94
+ methods: {
95
+ beforeElUpload(file) {
96
+ const MAX_SIZE = 1024 * 1024 * 10;
97
+ if (file.size > MAX_SIZE) {
98
+ this.$message.warning('图片大小不能超过10MB');
99
+ return false;
100
+ }
101
+ return true;
102
+ },
103
+ onElUploadChange(file, fileList) {
104
+ this.elFileList = fileList.slice(-1); // 只保留最后一张
105
+ },
106
+ submitForm() {
107
+ this.$refs.feedbackForm.validate(valid => {
108
+ if (!valid) return;
109
+ this.onSubmit();
110
+ });
111
+ },
112
+ async onSubmit() {
113
+ const formData = new FormData();
114
+ formData.append('description', this.form.description);
115
+ if (this.form.phone) formData.append('phone', this.form.phone);
116
+ if (this.elFileList.length > 0 && this.elFileList[0].raw) {
117
+ formData.append('file', this.elFileList[0].raw);
118
+ }
119
+ try {
120
+ const response = await submitFeedback(formData, this.businessSource);
121
+ if (response === '反馈成功') {
122
+ this.$message.success('提交成功');
123
+ this.form.description = '';
124
+ this.form.phone = '';
125
+ this.elFileList = [];
126
+ } else {
127
+ this.$message.warning(response.data.message || '提交失败');
128
+ }
129
+ } catch (error) {
130
+ this.$message.closeAll && this.$message.closeAll();
131
+ this.$message.error('提交失败,请稍后重试');
132
+ }
133
+ }
134
+ },
135
+ };
136
+ </script>
137
+
138
+ <style scoped>
139
+ .feedback-drawer-wrap {
140
+ position: fixed;
141
+ z-index: 2000;
142
+ top: 0;
143
+ left: 0;
144
+ width: 100vw;
145
+ height: 100vh;
146
+ }
147
+ .feedback-mask {
148
+ position: absolute;
149
+ top: 0;
150
+ left: 0;
151
+ width: 100vw;
152
+ height: 100vh;
153
+ background: rgba(0,0,0,0.35);
154
+ z-index: 1;
155
+ }
156
+ .feedback-drawer {
157
+ position: fixed;
158
+ top: 0;
159
+ right: 0;
160
+ width: 100vw;
161
+ height: 100vh;
162
+ background: #eef2ff;
163
+ z-index: 2;
164
+ box-shadow: -2px 0 16px rgba(0,0,0,0.08);
165
+ border-radius: 0;
166
+ display: flex;
167
+ flex-direction: column;
168
+ transform: translateX(100vw);
169
+ transition: transform 0.35s cubic-bezier(0.4,0,0.2,1);
170
+ }
171
+ .feedback-drawer.open {
172
+ transform: translateX(0);
173
+ }
174
+ .close-btn {
175
+ position: absolute;
176
+ top: -15px;
177
+ right: 16px;
178
+ background: none;
179
+ border: none;
180
+ font-size: 28px;
181
+ color: #888;
182
+ cursor: pointer;
183
+ z-index: 10;
184
+ line-height: 1;
185
+ }
186
+ .feedback-header {
187
+ text-align: center;
188
+ margin: 24px 0 32px;
189
+ position: relative;
190
+ }
191
+ .header-icon-wrap {
192
+ display: flex;
193
+ justify-content: center;
194
+ margin-bottom: 12px;
195
+ }
196
+ .header-icon {
197
+ color: #7a8bff;
198
+ }
199
+ .feedback-header h2 {
200
+ font-size: 20px;
201
+ font-weight: 600;
202
+ color: #323233;
203
+ margin: 0 0 8px;
204
+ }
205
+ .feedback-header .subtitle {
206
+ font-size: 14px;
207
+ color: #8c8c8c;
208
+ margin: 0;
209
+ }
210
+ .feedback-form {
211
+ background: #fff;
212
+ border-radius: 12px;
213
+ box-shadow: 0 2px 12px rgba(100, 101, 102, 0.04);
214
+ padding: 20px;
215
+ margin: 0 16px 16px 16px;
216
+ }
217
+ .form-group {
218
+ margin-bottom: 18px;
219
+ }
220
+ .form-group label {
221
+ font-weight: 500;
222
+ color: #4a4a4a;
223
+ display: block;
224
+ }
225
+ .form-group textarea,
226
+ .form-group input {
227
+ width: 100%;
228
+ border: 1px solid #e5e5e5;
229
+ border-radius: 8px;
230
+ padding: 8px 12px;
231
+ font-size: 15px;
232
+ color: #323233;
233
+ background: #f5f7fa;
234
+ resize: none;
235
+ box-sizing: border-box;
236
+ }
237
+ .upload-section {
238
+ background-color: #ffffff;
239
+ border-radius: 12px;
240
+ box-shadow: 0 2px 12px rgba(100, 101, 102, 0.04);
241
+ }
242
+ .upload-header {
243
+ display: flex;
244
+ align-items: center;
245
+ margin-bottom: 16px;
246
+ font-size: 15px;
247
+ font-weight: 500;
248
+ color: #4a4a4a;
249
+ }
250
+ .upload-tip {
251
+ margin-top: 12px;
252
+ font-size: 12px;
253
+ color: #8c8c8c;
254
+ text-align: center;
255
+ }
256
+ .submit-section {
257
+ margin: 32px 16px 0 16px;
258
+ }
259
+ .submit-button {
260
+ width: 100%;
261
+ height: 44px;
262
+ font-size: 16px;
263
+ font-weight: 500;
264
+ background: linear-gradient(135deg, #7a8bff 0%, #a3b3ff 100%);
265
+ border: none;
266
+ border-radius: 22px;
267
+ color: #fff;
268
+ box-shadow: 0 4px 12px rgba(122, 139, 255, 0.3);
269
+ transition: all 0.3s;
270
+ }
271
+ .submit-button:active {
272
+ transform: translateY(1px);
273
+ box-shadow: 0 2px 8px rgba(122, 139, 255, 0.3);
274
+ }
275
+ .upload-label {
276
+ font-weight: 500;
277
+ color: #4a4a4a;
278
+ display: block;
279
+ line-height: 40px;
280
+ font-size: 14px;
281
+ }
282
+ .upload-row {
283
+ display: flex;
284
+ align-items: flex-start;
285
+ }
286
+ .el-upload-feedback {
287
+ margin-bottom: 8px;
288
+ display: flex;
289
+ align-items: center;
290
+ }
291
+ .el-upload-feedback .el-upload {
292
+ margin-right: 8px;
293
+ }
294
+ .el-upload-feedback .el-upload-list {
295
+ margin: 0;
296
+ display: flex;
297
+ flex-wrap: wrap;
298
+ }
299
+ .el-upload-feedback >>> .el-upload-list__item {
300
+ width: 100px !important;
301
+ height: 100px !important;
302
+ margin: 0 8px 0 0 !important;
303
+ }
304
+ .el-upload-feedback >>> .el-upload--picture-card {
305
+ width: 100px !important;
306
+ height: 100px !important;
307
+ line-height: 100px !important;
308
+ }
309
+ .el-upload-feedback >>> .el-upload-list__item-thumbnail {
310
+ width: 64px !important;
311
+ height: 64px !important;
312
+ object-fit: cover;
313
+ border-radius: 8px;
314
+ }
315
+ .el-upload-feedback >>> .el-icon-plus {
316
+ font-size: 24px !important;
317
+ line-height: 64px !important;
318
+ }
319
+ </style>
@@ -1,10 +1,19 @@
1
1
  <template>
2
2
  <div class="ai-contain" v-if="visible">
3
+ <div
4
+ class="nav-top-space"
5
+ :style="`height: ${navTopHeight}px`"
6
+ ></div>
3
7
  <div class="ai-contain-header">
4
8
  <div class="header-left">
5
9
  <i class="el-icon-back" @click="closeMessage"></i>
6
10
  <span class="title-font">错题解析</span>
7
11
  </div>
12
+ <div class="header-right">
13
+ <div class="feedback-icon" @click="feedback">
14
+ <img alt="" src="../static/feedback.png" style="width: 20px; height: 20px"/>
15
+ </div>
16
+ </div>
8
17
  </div>
9
18
  <div v-if="!courseFlag" class="message-wrap">
10
19
  <div class="message-line">{{ message }}</div>
@@ -39,19 +48,32 @@
39
48
  <!-- 此功能公测期间对正式版用户开放-->
40
49
  <!-- </p>-->
41
50
  </template>
51
+ <el-drawer
52
+ :visible.sync="showFeedback"
53
+ direction="rtl"
54
+ size="100%"
55
+ :with-header="false"
56
+ custom-class="feedback-el-drawer"
57
+ :modal="false"
58
+ :close-on-click-modal="false"
59
+ >
60
+ <feedback @close="showFeedback = false" :businessSource="businessSource"/>
61
+ </el-drawer>
42
62
  </div>
43
63
  </template>
44
64
  <script>
45
65
  import Chat from './chat.vue';
66
+ import Feedback from './feedback.vue';
46
67
  import Cookies from "js-cookie";
47
68
  import {getUserInfo} from '../utils/config';
48
- import {checkCourseIdIsExist, queryCallWord} from '../api/index';
69
+ import {checkCourseIdIsExist, queryCallWord, ssoAuth} from '../api/index';
49
70
  import cache from "../plugins/cache";
50
71
 
51
72
  export default {
52
73
  name: 'AiErrorAssistantMobile',
53
74
  components: {
54
- Chat
75
+ Chat,
76
+ Feedback
55
77
  },
56
78
  props: {
57
79
  visible: true,
@@ -59,6 +81,10 @@ export default {
59
81
  businessSource: {
60
82
  type: String,
61
83
  default: ''
84
+ },
85
+ navTopHeight: {
86
+ type: Number,
87
+ default: 0
62
88
  }
63
89
  },
64
90
  computed: {
@@ -93,19 +119,24 @@ export default {
93
119
  showMoreQA: false,
94
120
  showFile: false,
95
121
  avatarUrl: '',
122
+ showFeedback: false,
96
123
  }
97
124
  },
98
- mounted() {
125
+ async mounted() {
99
126
  if (!Cookies.get("token")) {
100
127
  return this.$message.warning('未获取到登录信息,请重新登录');
101
128
  }
102
- getUserInfo(Cookies.get("token"), this.businessSource, async () => {
103
- const wordRes = await queryCallWord(this.resId);
104
- if (!wordRes) return;
105
- const {showMoreQA, isShowFile} = wordRes;
106
- this.showMoreQA = showMoreQA;
107
- this.showFile = !!isShowFile;
108
- this.avatarUrl = cache.session.getJSON('USER_AVATAR_URL');
129
+ await ssoAuth(Cookies.get("token"), this.businessSource).then(async (ssoRes) => {
130
+ if (ssoRes) {
131
+ const wordRes = await queryCallWord(this.resId);
132
+ if (!wordRes) return;
133
+ const {showMoreQA, isShowFile} = wordRes;
134
+ this.showMoreQA = showMoreQA;
135
+ this.showFile = !!isShowFile;
136
+ if (ssoRes?.avatarUrl) {
137
+ this.avatarUrl = ssoRes.avatarUrl;
138
+ }
139
+ }
109
140
  });
110
141
  },
111
142
  methods: {
@@ -155,6 +186,9 @@ export default {
155
186
  }
156
187
  return true;
157
188
  },
189
+ feedback() {
190
+ this.showFeedback = true;
191
+ },
158
192
  }
159
193
  }
160
194
  </script>
@@ -208,6 +242,29 @@ export default {
208
242
  }
209
243
  }
210
244
  }
245
+
246
+ .header-right {
247
+ display: flex;
248
+ align-items: center;
249
+ margin-right: 12px;
250
+ .feedback-icon {
251
+ width: 32px;
252
+ height: 32px;
253
+ border-radius: 50%;
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: center;
257
+ background: none;
258
+ cursor: pointer;
259
+ transition: background 0.2s;
260
+ &:active {
261
+ background: #ececec;
262
+ }
263
+ svg {
264
+ display: block;
265
+ }
266
+ }
267
+ }
211
268
  }
212
269
 
213
270
  .logo-title {
@@ -405,4 +462,14 @@ export default {
405
462
  }
406
463
  }
407
464
  }
465
+
466
+ .feedback-el-drawer {
467
+ z-index: 2000;
468
+ .el-drawer__body {
469
+ padding: 0;
470
+ height: 100vh;
471
+ background: #eef2ff;
472
+ overflow: auto;
473
+ }
474
+ }
408
475
  </style>
@@ -86,14 +86,14 @@ export const cacheMessageList = [
86
86
  // });
87
87
  // cache.session.setJSON('SRKJ_TOKEN_CACHE', userRes);
88
88
  // }
89
- export const getUserInfo = async (token, businessSource, fn) => {
90
- await ssoAuth(token, businessSource).then(async (ssoRes) => {
91
- if (ssoRes?.avatarUrl){
92
- cache.session.setJSON('USER_AVATAR_URL', ssoRes.avatarUrl);
93
- }else{
94
- cache.session.setJSON('USER_AVATAR_URL', '');
95
- }
96
- });
97
- cache.session.setJSON('SRKJ_TOKEN_CACHE', token);
98
- if (fn) fn();
99
- }
89
+ // export const getUserInfo = async (token, businessSource, fn) => {
90
+ // await ssoAuth(token, businessSource).then(async (ssoRes) => {
91
+ // if (ssoRes?.avatarUrl){
92
+ // cache.session.setJSON('USER_AVATAR_URL', ssoRes.avatarUrl);
93
+ // }else{
94
+ // cache.session.setJSON('USER_AVATAR_URL', '');
95
+ // }
96
+ // });
97
+ // cache.session.setJSON('SRKJ_TOKEN_CACHE', token);
98
+ // if (fn) fn();
99
+ // }