customText.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <!-- 移动端自定义文本编辑组件 - 使用 renderjs 支持 contenteditable -->
  2. <template>
  3. <view class="custom-text-container">
  4. <view id="htmlContent" class="content-editable" :contenteditable="!readonly" v-html="form" :prop="renderProps"
  5. :change:prop="renderScript.handlePropChange">
  6. </view>
  7. </view>
  8. </template>
  9. <script>
  10. export default {
  11. props: {
  12. id: {
  13. type: String,
  14. default: "",
  15. },
  16. readonly: {
  17. default: false,
  18. type: Boolean,
  19. },
  20. },
  21. data() {
  22. return {
  23. form: null,
  24. valueObj: {},
  25. equation: {},
  26. units: {},
  27. domId: "",
  28. cachedHtml: "",
  29. equationData: null,
  30. };
  31. },
  32. computed: {
  33. // 合并所有需要传递给 renderjs 的数据
  34. renderProps() {
  35. return {
  36. form: this.form,
  37. valueObj: this.valueObj,
  38. equationData: this.equationData,
  39. equation: this.equation,
  40. readonly: this.readonly,
  41. timestamp: Date.now(), // 确保每次都有变化
  42. };
  43. },
  44. },
  45. methods: {
  46. // renderjs 回调:失去焦点
  47. onHtmlBlur({
  48. html
  49. }) {
  50. this.cachedHtml = html;
  51. },
  52. onInputINPUT(data) {
  53. console.log("input 值变化:", data);
  54. if (data.inputId) {
  55. if (data.inputType === "checkbox") {
  56. this.valueObj[data.inputId] = data.checked;
  57. } else {
  58. this.valueObj[data.inputId] = data.value;
  59. }
  60. }
  61. this.$emit("calculation");
  62. },
  63. equationValue({
  64. domId,
  65. value
  66. }) {
  67. console.log(value, "value");
  68. this.valueObj[domId] = value;
  69. this.equationData = {
  70. domId: domId,
  71. value: value,
  72. timestamp: Date.now(), // 确保每次都能触发 change
  73. };
  74. console.log(this.equationData, "this.equationData");
  75. },
  76. // 获取值
  77. getValue() {
  78. return {
  79. form: this.cachedHtml || this.form || "",
  80. valueObj: this.valueObj,
  81. equation: this.equation,
  82. units: this.units,
  83. };
  84. },
  85. // 初始化
  86. init({
  87. form,
  88. valueObj,
  89. equation,
  90. units
  91. }) {
  92. this.form = form || "";
  93. this.valueObj = valueObj || {};
  94. console.log(this.valueObj, "this.valueObj");
  95. this.equation = equation || {};
  96. this.units = units || {};
  97. this.cachedHtml = this.form;
  98. },
  99. },
  100. };
  101. </script>
  102. <script module="renderScript" lang="renderjs">
  103. export default {
  104. data() {
  105. return {
  106. };
  107. },
  108. mounted() {
  109. console.log('renderjs mounted');
  110. this.initEditor();
  111. },
  112. methods: {
  113. // 统一处理所有 prop 变化
  114. handlePropChange(newVal, oldVal) {
  115. if (!newVal) return;
  116. console.log('renderjs 收到数据:', newVal);
  117. // 处理初始化数据 (valueObj)
  118. if (newVal.valueObj && Object.keys(newVal.valueObj).length > 0) {
  119. this.initValues(newVal.valueObj);
  120. }
  121. // 处理公式计算数据 (equationData)
  122. if (newVal.equationData && newVal.equationData.domId) {
  123. this.updateEquationValue(newVal.equationData);
  124. }
  125. if (newVal.readonly) {
  126. if (this.readonly) {
  127. let inputs = document.querySelectorAll('.templateInput');
  128. inputs.forEach((item) => {
  129. item.setAttribute('readonly', 'readonly');
  130. });
  131. }
  132. }
  133. if (newVal.equation.length) {
  134. for (key in newVal.equation) {
  135. const dom = document.getElementById(key);
  136. dom.setAttribute('readonly', 'readonly');
  137. }
  138. }
  139. },
  140. // 初始化 input 值
  141. initValues(valueObj) {
  142. console.log('初始化值:', valueObj);
  143. for (let key in valueObj) {
  144. const dom = document.getElementById(key);
  145. if (dom && dom.tagName === 'INPUT') {
  146. if (dom.type === 'checkbox') {
  147. dom.checked = valueObj[key];
  148. } else {
  149. dom.value = valueObj[key];
  150. }
  151. }
  152. }
  153. },
  154. // 更新公式计算值
  155. updateEquationValue(equationData) {
  156. console.log('更新公式值:', equationData);
  157. const {
  158. domId,
  159. value
  160. } = equationData;
  161. const input = document.getElementById(domId);
  162. if (input && input.tagName === 'INPUT') {
  163. input.value = value;
  164. // // 触发 input 事件通知逻辑层
  165. // const event = new Event('input', { bubbles: true });
  166. // input.dispatchEvent(event);
  167. }
  168. },
  169. // 初始化编辑器
  170. initEditor() {
  171. const el = document.getElementById('htmlContent');
  172. if (!el) {
  173. console.log('htmlContent 元素不存在');
  174. return;
  175. }
  176. // el.style.border = '1px solid #ddd';
  177. el.style.padding = '10px';
  178. el.style.minHeight = '100px';
  179. el.style.overflow = 'auto';
  180. // 监听 input 事件
  181. el.addEventListener('input', this.handleInput.bind(this));
  182. console.log('renderjs 编辑器初始化完成');
  183. },
  184. // 处理 input 事件
  185. handleInput(e) {
  186. const html = e.target.innerHTML;
  187. // 通知逻辑层 HTML 内容变化
  188. if (e.target.tagName === 'INPUT') {
  189. const data = {
  190. inputId: e.target.id,
  191. value: e.target.value,
  192. checked: e.target.checked,
  193. inputType: e.target.type
  194. };
  195. this.$ownerInstance.callMethod('onInputINPUT', data);
  196. } else {
  197. this.$ownerInstance.callMethod('onHtmlBlur', {
  198. html
  199. });
  200. }
  201. },
  202. }
  203. };
  204. </script>
  205. <style lang="scss" scoped>
  206. .custom-text-container {
  207. width: 100%;
  208. min-height: 200rpx;
  209. position: relative;
  210. background-color: #fff;
  211. }
  212. .content-editable {
  213. width: 100%;
  214. min-height: 150rpx;
  215. padding: 10px;
  216. background-color: #fff;
  217. border-radius: 4px;
  218. font-size: 14px;
  219. line-height: 1.5;
  220. word-wrap: break-word;
  221. }
  222. /* contenteditable 内部样式 */
  223. .content-editable :deep(input) {
  224. border: 1px solid #ddd;
  225. padding: 2px 5px;
  226. border-radius: 3px;
  227. font-size: 14px;
  228. }
  229. .content-editable :deep(input:focus) {
  230. outline: none;
  231. border-color: #007aff;
  232. }
  233. </style>