zentaobiz.ui.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. $(document).ready(function() {
  2. initData();
  3. formFieldChange();
  4. initPromptEditor();
  5. initMarkdown();
  6. generateResult();
  7. actionSave();
  8. actionPublish();
  9. });
  10. // 是否已保存
  11. let isSaved = true;
  12. // Markdown 组件
  13. let markdownComponent = null;
  14. // 表单数据管理
  15. const FieldManager = {
  16. state: {
  17. fields: [],
  18. fieldsMap: {},
  19. },
  20. setFields(fields) {
  21. this.state.fields = [];
  22. fields.forEach(field => {
  23. this.state.fields
  24. .push(field.id);
  25. this.state.fieldsMap[field.id] = field;
  26. });
  27. renderList();
  28. },
  29. getFields() {
  30. return this.state.fields
  31. .map(id => this.state.fieldsMap[id]);
  32. },
  33. };
  34. // 数据初始化
  35. function initData() {
  36. let fields = [];
  37. if (currentFields && currentFields.length > 0) {
  38. fields = currentFields.map((field, index) => ({
  39. ...field,
  40. options: field.options
  41. ? field.options.split(',')
  42. : [],
  43. }));
  44. }
  45. FieldManager.setFields(fields);
  46. }
  47. // 渲染列表
  48. function renderList() {
  49. const fields = FieldManager.getFields();
  50. renderForm(fields);
  51. }
  52. // 渲染表单项
  53. function renderFormField(field) {
  54. let fieldHtml = '';
  55. switch (field.type) {
  56. case 'text':
  57. fieldHtml = `
  58. <input type="text"
  59. class="form-control"
  60. name="${field.name}"
  61. placeholder="${field.placeholder || ''}"
  62. ${field.required === '1' ? 'required' : ''}>
  63. `;
  64. break;
  65. case 'textarea':
  66. fieldHtml = `
  67. <textarea
  68. class="form-control"
  69. name="${field.name}"
  70. placeholder="${field.placeholder || ''}"
  71. rows="3"
  72. ${field.required === '1' ? 'required' : ''}></textarea>
  73. `;
  74. break;
  75. case 'radio':
  76. case 'checkbox':
  77. if(field.options && field.options.length > 0)
  78. {
  79. const options = {
  80. items: field.options.map(item => ({
  81. text: item,
  82. value: item,
  83. })),
  84. name: field.name,
  85. limitValueInList: false,
  86. multiple: field.type === 'checkbox',
  87. required: field.required === '1',
  88. };
  89. const pickerOptions = JSON.stringify(options).replace(/"/g, '&quot;');
  90. const dataAttributes = [
  91. `zui-create="picker"`,
  92. `zui-create-picker="${pickerOptions}"`,
  93. ].join(' ');
  94. fieldHtml = `
  95. <div
  96. class="input-group-control"
  97. ${dataAttributes}>
  98. </div>
  99. `;
  100. }
  101. break;
  102. default:
  103. return '';
  104. }
  105. return `
  106. <div class="form-group" data-id="${field.id}">
  107. <label class="form-label ${field.required === '1' ? 'required' : ''}" for="${field.name}">
  108. ${field.name}
  109. </label>
  110. ${fieldHtml}
  111. </div>
  112. `;
  113. }
  114. // 渲染表单
  115. function renderForm(fields) {
  116. const formFields = $('#form-fields');
  117. formFields.empty();
  118. let html = '';
  119. if (fields.length > 0) {
  120. fields.forEach(field => {
  121. html += renderFormField(field);
  122. });
  123. }
  124. formFields.html(html);
  125. }
  126. // 表单数据
  127. const FormFieldData = {};
  128. // 表单字段变化
  129. function formFieldChange() {
  130. $('#form-fields')
  131. .on(
  132. 'change',
  133. 'input, textarea, select',
  134. function() {
  135. const fieldName = $(this).attr('name');
  136. const fieldId = $(this).closest('.form-group')
  137. .data('id');
  138. const name = fieldName.endsWith('[]')
  139. ? fieldName.slice(0, -2)
  140. : fieldName;
  141. const value = $(this).val();
  142. FormFieldData[fieldId] = value;
  143. generatePromptPreview();
  144. },
  145. );
  146. }
  147. // 构建输入提示列表
  148. function buildInputSuggestions(query = '') {
  149. const list = FieldManager.getFields()
  150. .map(field => ({
  151. text: field.name,
  152. value: `${field.name}>`,
  153. }));
  154. if (!query) {
  155. return list;
  156. }
  157. return list.filter(item => item.text.includes(query));
  158. }
  159. // 处理 Prompt 文本
  160. function processPromptText(text) {
  161. if (!text) {
  162. return '';
  163. }
  164. return text
  165. .replace(/&lt;/g, '<')
  166. .replace(/&gt;/g, '>');
  167. }
  168. /* 将文本转换为 HTML */
  169. function propmtTextToHtml(text)
  170. {
  171. let html = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  172. const fields = FieldManager.getFields();
  173. const fieldNames = {};
  174. fields.forEach(field => {
  175. fieldNames[field.name] = field.id;
  176. });
  177. html = html.replace(/&lt;([^&]+)&gt;/g, function(match, fieldName)
  178. {
  179. if(!fieldNames[fieldName]) return match;
  180. return `<span class="mention-label" data-type="mention" data-id="${fieldName}&gt;" data-label="${fieldName}&gt;" data-mention-suggestion-char="&lt;">&lt;${fieldName}&gt;</span>`;
  181. });
  182. const list = html.split('\n');
  183. return list.map((line) => `<p>${line}</p>`).join('');
  184. }
  185. // Prompt 初始值
  186. const initPrompt = processPromptText(currentPrompt || '');
  187. // Prompt Data
  188. let promptData = {
  189. editor: null,
  190. text: initPrompt,
  191. html: '',
  192. };
  193. // 初始化 Prompt Editor
  194. function initPromptEditor() {
  195. // 是否为初始化
  196. let isInit = true;
  197. promptData.html = propmtTextToHtml(initPrompt);
  198. promptData.editor = new zui.TEditor('#prompt-editor', {
  199. content: promptData.html,
  200. mode: 'plain',
  201. suggestions: {
  202. '<': (query) => buildInputSuggestions(query),
  203. },
  204. onChange: function(content) {
  205. promptData.text = content.replace(/\n\n/g, '\n');
  206. promptData.html = promptData.editor.getHtml();
  207. // 跳过初始化导致的改动
  208. if (isInit) {
  209. isInit = false;
  210. } else {
  211. isSaved = false;
  212. }
  213. generatePromptPreview();
  214. },
  215. });
  216. generatePromptPreview();
  217. }
  218. // 更新 Prompt 数据
  219. function updatePromptData(data) {
  220. switch (data.action) {
  221. case 'update':
  222. promptData.text = promptData.text
  223. .replace(new RegExp(`<${data.oldName}>`, 'g'), `<${data.newName}>`);
  224. break;
  225. case 'delete':
  226. promptData.text = promptData.text
  227. .replace(new RegExp(` <${data.name}> `, 'g'), '');
  228. break;
  229. default:
  230. return;
  231. }
  232. isSaved = false;
  233. promptData.html = propmtTextToHtml(promptData.text);
  234. promptData.editor.setContent(promptData.html);
  235. generatePromptPreview();
  236. }
  237. // 初始化 Markdown 组件
  238. function initMarkdown() {
  239. markdownComponent = new zui.Markdown('#result-preview', {
  240. content: '',
  241. });
  242. }
  243. /* 生成 Prompt 预览 */
  244. function generatePromptPreview()
  245. {
  246. const promptPreview = $('#prompt-preview');
  247. promptPreview.empty();
  248. let html = promptData.text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  249. const fields = FieldManager.getFields();
  250. fields.forEach(field => {
  251. const { id, name, type } = field;
  252. let value = `&lt;${name}&gt;`;
  253. if(FormFieldData[id])
  254. {
  255. if(type === 'checkbox')
  256. {
  257. const options = FormFieldData[id].filter(item => item);
  258. if(options.length > 0) value = options.join(',');
  259. }
  260. else
  261. {
  262. value = FormFieldData[id];
  263. }
  264. }
  265. const list = html.split(`&lt;${name}&gt;`).map(item => item.trim());
  266. html = list.join(`<span>${value}</span>`);
  267. });
  268. promptPreview.html(html);
  269. }
  270. // 生成结果
  271. function generateResult() {
  272. $('#generate-result')
  273. .on('click', async function() {
  274. if($(this).hasClass('disabled')) return;
  275. if (!hasZaiConfig) {
  276. zui.Messager.show({content: zaiConfigHint, type: 'danger'});
  277. return;
  278. }
  279. const promptPreview = $('#prompt-preview');
  280. markdownComponent.render({
  281. content: '',
  282. });
  283. const text = promptPreview.text();
  284. if (!text) {
  285. zui.Messager.show({
  286. content: promptPlaceholder,
  287. type: 'danger'
  288. });
  289. return;
  290. }
  291. $(this).addClass('disabled');
  292. const res = await zui.AIPanel.shared.store
  293. .postTempMessage({
  294. content: text,
  295. onResponse: (message) => {
  296. if (message.content) {
  297. markdownComponent.render({
  298. content: message.content,
  299. });
  300. }
  301. },
  302. });
  303. markdownComponent.render({
  304. content: res.message.content,
  305. });
  306. $(this).removeClass('disabled');
  307. });
  308. }
  309. // 返回列表
  310. function backToList() {
  311. zui.Modal.hide();
  312. }
  313. // 获取字段数据
  314. function getFieldsData() {
  315. const fields = FieldManager.getFields();
  316. return fields.map((field, index) => {
  317. const {
  318. name,
  319. type,
  320. required,
  321. placeholder,
  322. options,
  323. } = field;
  324. const fieldData = {
  325. name,
  326. type,
  327. required: required ? '1' : '0',
  328. appID,
  329. };
  330. // 根据字段类型添加相应属性
  331. if (['text', 'textarea'].includes(type)) {
  332. fieldData.placeholder = placeholder || '';
  333. } else if (['radio', 'checkbox'].includes(type)) {
  334. fieldData.options = options ? options.join(',') : '';
  335. }
  336. return fieldData;
  337. });
  338. }
  339. // 发送页面数据
  340. function postData(data) {
  341. $.ajax({
  342. url: $.createLink('ai', 'configuredMiniProgram', `appID=${appID}`),
  343. type: 'POST',
  344. data: JSON.stringify(data),
  345. processData: false,
  346. contentType: 'application/json; charset=UTF-8',
  347. dataType: 'json'
  348. }).done(function(response) {
  349. if (response.result === 'success') {
  350. isSaved = true;
  351. if (response.message) {
  352. zui.Messager.show({
  353. content: response.message,
  354. type: 'success'
  355. });
  356. }
  357. backToList();
  358. } else {
  359. if (response.message) {
  360. zui.Messager.show({
  361. content: response.message,
  362. type: 'danger'
  363. });
  364. }
  365. }
  366. }).fail(function(xhr, status, error) {
  367. zui.Messager.show({
  368. content: saveFailed + ':' + error,
  369. type: 'danger'
  370. });
  371. });
  372. }
  373. // 保存页面数据
  374. function saveData(toPublish) {
  375. // 准备相关数据
  376. const fields = getFieldsData();
  377. const prompt = promptData.text.trim();
  378. // 数据验证
  379. if (!prompt) {
  380. zui.Messager.show({
  381. content: promptPlaceholder,
  382. type: 'danger'
  383. });
  384. return;
  385. }
  386. const data = {
  387. fields,
  388. prompt,
  389. toPublish,
  390. };
  391. if (toPublish === '0') {
  392. postData(data);
  393. return;
  394. }
  395. zui.Modal.confirm({
  396. title: publishConfirm[0],
  397. message: publishConfirm[1],
  398. icon: 'icon-exclamation-sign',
  399. iconClass: 'icon-2x modal-icon-warning',
  400. })
  401. .then(confirmed => {
  402. if (confirmed) {
  403. postData(data);
  404. }
  405. });
  406. }
  407. // 保存
  408. function actionSave() {
  409. $('#btn-save')
  410. .on('click', function() {
  411. saveData('0');
  412. });
  413. }
  414. // 发布
  415. function actionPublish() {
  416. $('#btn-publish')
  417. .on('click', function() {
  418. saveData('1');
  419. });
  420. }