图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
8.5 KiB

2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
1 month ago
2 months ago
2 months ago
  1. <template>
  2. <view class="feedback-container">
  3. <view class="form-box">
  4. <view class="item">
  5. <text class="label">姓名</text>
  6. <input
  7. class="input"
  8. placeholder="请输入姓名"
  9. v-model="formData.name"
  10. :disabled="hasNickname"
  11. />
  12. </view>
  13. <view class="item">
  14. <text class="label">读者证</text>
  15. <input
  16. class="input"
  17. placeholder="请输入读者证号"
  18. v-model="formData.readerCard"
  19. disabled
  20. />
  21. </view>
  22. <view class="item">
  23. <text class="label">主题</text>
  24. <input
  25. class="input"
  26. placeholder="请输入主题(最少5个字)"
  27. v-model="formData.subject"
  28. maxlength="50"
  29. />
  30. </view>
  31. <view class="item">
  32. <text class="label">联系方式/手机号码</text>
  33. <input
  34. class="input"
  35. placeholder="请输入手机号码"
  36. v-model="formData.phone"
  37. type="number"
  38. maxlength="11"
  39. />
  40. </view>
  41. <view class="item textarea-item">
  42. <text class="label">您的建议或意见</text>
  43. <textarea
  44. class="textarea"
  45. placeholder="HI,请留下您的留言吧!(最少10个字)"
  46. v-model="formData.content"
  47. :maxlength="500"
  48. />
  49. </view>
  50. <button class="commit" type="primary" @click="onBtnClick">提交</button>
  51. </view>
  52. </view>
  53. </template>
  54. <script>
  55. import { FetchReaderMessage,FetchAccessToken,FetchMsgSecCheck } from '@/api/user';
  56. import config from '@/utils/config';
  57. import { getCurrentReaderCard,getOpenId } from '@/utils/storage';
  58. const USER_KEY = 'user-info';
  59. export default {
  60. data() {
  61. return {
  62. hasNickname: false,
  63. formData: {
  64. name: '',
  65. readerCard: '',
  66. subject: '',
  67. phone: '',
  68. content: ''
  69. }
  70. };
  71. },
  72. onLoad() {
  73. this.loadReaderCardInfo();
  74. this.loadUserInfo();
  75. },
  76. methods: {
  77. async loadReaderCardInfo() {
  78. try {
  79. const readerCard = await getCurrentReaderCard();
  80. if (readerCard) {
  81. this.formData.readerCard = readerCard.bindValue || '';
  82. }
  83. } catch (err) {
  84. console.error('获取读者证信息失败:', err);
  85. }
  86. },
  87. loadUserInfo() {
  88. const userInfo = uni.getStorageSync(USER_KEY) || {};
  89. if (userInfo.nickname) {
  90. this.formData.name = userInfo.nickname;
  91. this.hasNickname = true;
  92. }
  93. },
  94. validatePhone(phone) {
  95. const phoneReg = /^1[3-9]\d{9}$/;
  96. return phoneReg.test(phone);
  97. },
  98. async onBtnClick() {
  99. const { name, readerCard, subject, phone, content } = this.formData;
  100. if (!name || name.trim().length === 0) {
  101. uni.showToast({
  102. title: '请输入姓名',
  103. icon: 'none'
  104. });
  105. return;
  106. }
  107. if (!readerCard || readerCard.trim().length === 0) {
  108. uni.showToast({
  109. title: '请先绑定读者证',
  110. icon: 'none'
  111. });
  112. return;
  113. }
  114. if (!subject || subject.trim().length < 5) {
  115. uni.showToast({
  116. title: '主题至少需要5个字',
  117. icon: 'none'
  118. });
  119. return;
  120. }
  121. if (!phone) {
  122. uni.showToast({
  123. title: '请输入手机号码',
  124. icon: 'none'
  125. });
  126. return;
  127. }
  128. if (!this.validatePhone(phone)) {
  129. uni.showToast({
  130. title: '手机号码格式不正确',
  131. icon: 'none'
  132. });
  133. return;
  134. }
  135. if (!content || content.trim().length < 10) {
  136. uni.showToast({
  137. title: '建议内容至少需要10个字',
  138. icon: 'none'
  139. });
  140. return;
  141. }
  142. uni.showLoading({
  143. title: '提交中'
  144. });
  145. try {
  146. const openId = await getOpenId();
  147. if (!openId) {
  148. uni.showToast({ title: '获取用户信息失败', icon: 'none' });
  149. return;
  150. }
  151. // 1. 获取 access_token
  152. const tokenRes = await FetchAccessToken({ libcode: config.LIB_CODE });
  153. if (!tokenRes || tokenRes.code !== 200 || !tokenRes.data) {
  154. uni.showToast({ title: '获取访问令牌失败', icon: 'none' });
  155. return;
  156. }
  157. const accessToken = tokenRes.data.access_token;
  158. // 2. 敏感信息检测
  159. const secCheckRes = await FetchMsgSecCheck({
  160. libcode: config.LIB_CODE,
  161. content: content,
  162. version: 2,
  163. scene: 2,
  164. openid: openId,
  165. title: subject,
  166. nickname: name,
  167. accessToken: accessToken
  168. });
  169. console.log('敏感信息检测结果:', secCheckRes);
  170. // {"code":200,"message":"操作成功","data":{"errcode":0,"result":{"suggest":"risky","label":20003},"trace_id":"6a13c316-710640ab-04e22c75","errmsg":"ok","detail":[{"errcode":0,"prob":90,"suggest":"risky","label":20003,"strategy":"content_model"},{"errcode":0,"strategy":"keyword"}]},"timestamp":1779680023278}
  171. // 1. 检查接口调用是否成功
  172. if (!secCheckRes || secCheckRes.code !== 200) {
  173. uni.showToast({ title: secCheckRes?.message || '敏感信息检测失败', icon: 'none' });
  174. return;
  175. }
  176. // 2. 检查微信官方 errcode(仅当 errcode 为 0 时,结果有效)
  177. const { errcode, errmsg, result } = secCheckRes.data || {};
  178. if (errcode !== 0) {
  179. uni.showToast({ title: errmsg || `检测失败,错误码: ${errcode}`, icon: 'none' });
  180. return;
  181. }
  182. // 3. 检查是否返回了检测结果
  183. if (!result) {
  184. uni.showToast({ title: '未获取到检测结果', icon: 'none' });
  185. return;
  186. }
  187. console.log('敏感信息检测结果:', result);
  188. // result:{
  189. // label: 20003, // 100 正常;10001 广告;20001 时政;20002 色情;20003 辱骂;20006 违法犯罪;20008 欺诈;20012 低俗;20013 版权;21000 其他
  190. // suggest: "risky" // risky、pass、review
  191. // }
  192. // 4. 根据检测结果判断是否允许提交
  193. const labelMap = {
  194. 100: '正常',
  195. 10001: '广告',
  196. 20001: '时政',
  197. 20002: '色情',
  198. 20003: '辱骂',
  199. 20006: '违法犯罪',
  200. 20008: '欺诈',
  201. 20012: '低俗',
  202. 20013: '版权',
  203. 21000: '其他'
  204. };
  205. if (result.suggest !== 'pass') {
  206. const labelText = labelMap[result.label] || '敏感内容';
  207. uni.showToast({ title: `内容包含${labelText},请修改后重试`, icon: 'none' });
  208. return;
  209. }
  210. // 3. 检测通过,提交反馈
  211. const res = await FetchReaderMessage({
  212. libcode: config.LIB_CODE,
  213. openid: openId,
  214. phone,
  215. readCardNo: readerCard,
  216. readName: name,
  217. suggestion: content,
  218. title: subject
  219. });
  220. if (res.code !== 200) {
  221. uni.showToast({ title: res.message || '留言提交失败', icon: 'none' });
  222. return;
  223. }
  224. // 设置刷新标记
  225. uni.setStorageSync('needRefreshFeedback', true);
  226. uni.showToast({ title: '留言提交成功', icon: 'success' });
  227. setTimeout(() => {
  228. uni.navigateBack();
  229. }, 1500);
  230. } catch (err) {
  231. console.error('留言提交失败', err);
  232. uni.showToast({ title: err.message || '留言提交失败', icon: 'none' });
  233. } finally {
  234. uni.hideLoading();
  235. }
  236. }
  237. }
  238. };
  239. </script>
  240. <style lang="scss" scoped>
  241. .feedback-container {
  242. padding: 15px;
  243. background-color: #f5f5f5;
  244. min-height: 100vh;
  245. }
  246. .form-box {
  247. background-color: #fff;
  248. border-radius: 10px;
  249. padding: 20px;
  250. }
  251. .item {
  252. margin-bottom: 20px;
  253. }
  254. .label {
  255. display: block;
  256. font-size: 15px;
  257. font-weight: bold;
  258. color: #333;
  259. margin-bottom: 10px;
  260. }
  261. .input {
  262. width: 100%;
  263. height: 44px;
  264. background-color: #f5f5f5;
  265. border-radius: 6px;
  266. padding: 0 15px;
  267. font-size: 14px;
  268. color: #333;
  269. box-sizing: border-box;
  270. }
  271. .input[disabled] {
  272. background-color: #e8e8e8;
  273. color: #999;
  274. }
  275. .textarea-item {
  276. margin-bottom: 30px;
  277. }
  278. .textarea {
  279. width: 100%;
  280. min-height: 120px;
  281. background-color: #f5f5f5;
  282. border-radius: 6px;
  283. padding: 12px 15px;
  284. font-size: 14px;
  285. color: #333;
  286. box-sizing: border-box;
  287. line-height: 1.6;
  288. }
  289. .commit {
  290. width: 100%;
  291. height: 44px;
  292. background-color: #01a4fe;
  293. color: #fff;
  294. border-radius: 22px;
  295. font-size: 16px;
  296. border: none;
  297. margin-top: 20px;
  298. }
  299. .commit::after {
  300. border: none;
  301. }
  302. </style>