StockListItem.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. "use strict";
  2. const common_vendor = require("../common/vendor.js");
  3. const _sfc_main = {
  4. __name: "StockListItem",
  5. props: {
  6. stock: {
  7. type: Object,
  8. required: true
  9. },
  10. showDelete: {
  11. type: Boolean,
  12. default: false
  13. }
  14. },
  15. emits: ["delete"],
  16. setup(__props, { emit }) {
  17. const props = __props;
  18. let componentInstance = null;
  19. const deleteWidth = 60;
  20. const moveX = common_vendor.ref(0);
  21. let currentX = 0;
  22. let autoResetTimer = null;
  23. let checkTimer = null;
  24. const AUTO_RESET_DELAY = 2500;
  25. let isSlideOpen = false;
  26. const forceReset = () => {
  27. console.log("[滑动删除] 强制还原");
  28. moveX.value = 0;
  29. currentX = 0;
  30. isSlideOpen = false;
  31. clearAutoResetTimer();
  32. };
  33. const startAutoResetTimer = () => {
  34. clearAutoResetTimer();
  35. isSlideOpen = true;
  36. console.log("[滑动删除] 启动自动还原计时器");
  37. autoResetTimer = setTimeout(() => {
  38. console.log("[滑动删除] 主计时器触发");
  39. forceReset();
  40. }, AUTO_RESET_DELAY);
  41. checkTimer = setInterval(() => {
  42. if (isSlideOpen && moveX.value < 0) {
  43. console.log("[滑动删除] 备用检查中...");
  44. } else if (isSlideOpen && moveX.value === 0) {
  45. isSlideOpen = false;
  46. clearAutoResetTimer();
  47. }
  48. }, 500);
  49. setTimeout(() => {
  50. if (isSlideOpen) {
  51. console.log("[滑动删除] 保险计时器触发");
  52. forceReset();
  53. }
  54. }, AUTO_RESET_DELAY + 1e3);
  55. };
  56. const clearAutoResetTimer = () => {
  57. if (autoResetTimer) {
  58. clearTimeout(autoResetTimer);
  59. autoResetTimer = null;
  60. }
  61. if (checkTimer) {
  62. clearInterval(checkTimer);
  63. checkTimer = null;
  64. }
  65. };
  66. const canvasId = common_vendor.ref(`chart-${props.stock.code}-${Math.random().toString(36).slice(2, 11)}`);
  67. const getMarketTag = (code) => {
  68. if (code.startsWith("6"))
  69. return "沪";
  70. if (code.startsWith("0"))
  71. return "深";
  72. if (code.startsWith("3"))
  73. return "创";
  74. return "沪";
  75. };
  76. const getMarketClass = (code) => {
  77. if (code.startsWith("6"))
  78. return "market-sh";
  79. if (code.startsWith("0"))
  80. return "market-sz";
  81. if (code.startsWith("3"))
  82. return "market-cy";
  83. return "market-sh";
  84. };
  85. const getChangeClass = (changePercent) => {
  86. if (!changePercent)
  87. return "";
  88. const str = String(changePercent).replace("%", "").replace("+", "");
  89. const value = parseFloat(str);
  90. if (value > 0)
  91. return "change-up";
  92. if (value < 0)
  93. return "change-down";
  94. return "";
  95. };
  96. const formatChangePercent = (changePercent) => {
  97. if (!changePercent)
  98. return "--";
  99. return String(changePercent);
  100. };
  101. const formatPrice = (price) => {
  102. if (!price)
  103. return "--";
  104. return parseFloat(price).toFixed(2);
  105. };
  106. const hasValidChange = (changePercent) => {
  107. if (!changePercent)
  108. return false;
  109. const str = String(changePercent).replace("%", "").replace("+", "");
  110. const value = parseFloat(str);
  111. return value !== 0 && !isNaN(value);
  112. };
  113. const drawTrendChart = (instance) => {
  114. let trendData = props.stock.trendData;
  115. if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
  116. trendData = generateMockTrendData();
  117. }
  118. const ctx = common_vendor.index.createCanvasContext(canvasId.value, instance);
  119. const width = 100;
  120. const height = 30;
  121. const padding = 2;
  122. const maxValue = Math.max(...trendData);
  123. const minValue = Math.min(...trendData);
  124. const dataRange = maxValue - minValue;
  125. const avgValue = (maxValue + minValue) / 2;
  126. const minRange = avgValue * 0.03 || 1;
  127. const range = Math.max(dataRange, minRange);
  128. const baseValue = trendData[0];
  129. const baseY = height - padding - (baseValue - minValue) / range * (height - padding * 2);
  130. const changePercent = parseFloat(String(props.stock.changePercent || "0").replace("%", "").replace("+", ""));
  131. const isUp = changePercent >= 0;
  132. const lineColor = isUp ? "#FF3B30" : "#34C759";
  133. const fillColor = isUp ? "rgba(255, 59, 48, 0.15)" : "rgba(52, 199, 89, 0.15)";
  134. ctx.beginPath();
  135. ctx.setStrokeStyle("#e0e0e0");
  136. ctx.setLineWidth(0.5);
  137. ctx.setLineDash([2, 2], 0);
  138. ctx.moveTo(padding, baseY);
  139. ctx.lineTo(width - padding, baseY);
  140. ctx.stroke();
  141. ctx.setLineDash([], 0);
  142. ctx.beginPath();
  143. ctx.moveTo(padding, baseY);
  144. trendData.forEach((value, index) => {
  145. const x = padding + index / (trendData.length - 1) * (width - padding * 2);
  146. const y = height - padding - (value - minValue) / range * (height - padding * 2);
  147. ctx.lineTo(x, y);
  148. });
  149. ctx.lineTo(width - padding, baseY);
  150. ctx.closePath();
  151. ctx.setFillStyle(fillColor);
  152. ctx.fill();
  153. ctx.beginPath();
  154. trendData.forEach((value, index) => {
  155. const x = padding + index / (trendData.length - 1) * (width - padding * 2);
  156. const y = height - padding - (value - minValue) / range * (height - padding * 2);
  157. if (index === 0) {
  158. ctx.moveTo(x, y);
  159. } else {
  160. ctx.lineTo(x, y);
  161. }
  162. });
  163. ctx.setStrokeStyle(lineColor);
  164. ctx.setLineWidth(1.5);
  165. ctx.stroke();
  166. ctx.draw();
  167. };
  168. const generateMockTrendData = () => {
  169. const changePercent = parseFloat(String(props.stock.changePercent || "0").replace("%", "").replace("+", ""));
  170. const points = 15;
  171. const data = [];
  172. let baseValue = 100;
  173. const trend = changePercent / 100;
  174. for (let i = 0; i < points; i++) {
  175. const randomChange = (Math.random() - 0.5) * 6;
  176. const trendChange = i / points * trend * 100;
  177. baseValue = baseValue + randomChange + trendChange / points;
  178. data.push(baseValue);
  179. }
  180. return data;
  181. };
  182. const handleMoveChange = (e) => {
  183. currentX = e.detail.x;
  184. };
  185. const handleMoveEnd = () => {
  186. if (!props.showDelete)
  187. return;
  188. if (currentX < -deleteWidth / 3) {
  189. moveX.value = -deleteWidth;
  190. startAutoResetTimer();
  191. } else {
  192. forceReset();
  193. }
  194. };
  195. const handleDelete = () => {
  196. forceReset();
  197. emit("delete");
  198. };
  199. common_vendor.onMounted(() => {
  200. componentInstance = common_vendor.getCurrentInstance();
  201. common_vendor.nextTick$1(() => {
  202. setTimeout(() => {
  203. drawTrendChart(componentInstance);
  204. }, 300);
  205. });
  206. });
  207. common_vendor.watch(() => props.stock.trendData, (newData) => {
  208. if (newData && componentInstance) {
  209. common_vendor.nextTick$1(() => {
  210. drawTrendChart(componentInstance);
  211. });
  212. }
  213. }, { deep: true });
  214. common_vendor.watch(() => props.stock.changePercent, () => {
  215. if (componentInstance) {
  216. common_vendor.nextTick$1(() => {
  217. drawTrendChart(componentInstance);
  218. });
  219. }
  220. });
  221. common_vendor.watch(moveX, (newVal) => {
  222. if (newVal === 0 && isSlideOpen) {
  223. isSlideOpen = false;
  224. clearAutoResetTimer();
  225. }
  226. });
  227. common_vendor.onUnmounted(() => {
  228. clearAutoResetTimer();
  229. });
  230. return (_ctx, _cache) => {
  231. return common_vendor.e({
  232. a: common_vendor.t(__props.stock.name),
  233. b: common_vendor.t(getMarketTag(__props.stock.code)),
  234. c: common_vendor.n(getMarketClass(__props.stock.code)),
  235. d: common_vendor.t(__props.stock.code),
  236. e: canvasId.value,
  237. f: canvasId.value,
  238. g: hasValidChange(__props.stock.changePercent)
  239. }, hasValidChange(__props.stock.changePercent) ? {
  240. h: common_vendor.t(formatChangePercent(__props.stock.changePercent)),
  241. i: common_vendor.n(getChangeClass(__props.stock.changePercent))
  242. } : {}, {
  243. j: common_vendor.t(formatPrice(__props.stock.currentPrice)),
  244. k: __props.showDelete
  245. }, __props.showDelete ? {
  246. l: common_vendor.o(handleDelete)
  247. } : {}, {
  248. m: moveX.value,
  249. n: common_vendor.o(handleMoveChange),
  250. o: common_vendor.o(handleMoveEnd)
  251. });
  252. };
  253. }
  254. };
  255. const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-29af7fd7"], ["__file", "D:/program/gupiao-wx/src/components/StockListItem.vue"]]);
  256. wx.createComponent(Component);