number-analysis.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <template>
  2. <view class="page-number-analysis" :class="themeClass">
  3. <!-- ========== Phone Input Section ========== -->
  4. <view class="input-card">
  5. <text class="input-label">手机号码</text>
  6. <view class="input-row">
  7. <input
  8. v-model="phoneNumber"
  9. class="phone-input"
  10. type="number"
  11. maxlength="11"
  12. placeholder="请输入11位手机号码"
  13. placeholder-class="input-placeholder"
  14. @confirm="handleAnalyze"
  15. />
  16. <view class="btn-analyze" :class="{ analyzing: analyzing }" @click="handleAnalyze">
  17. <text class="btn-analyze-text">{{ analyzing ? '分析中' : '分析' }}</text>
  18. </view>
  19. </view>
  20. </view>
  21. <!-- ========== Star Combinations Result ========== -->
  22. <view v-if="starCombinations.length > 0" class="result-section">
  23. <view class="section-header">
  24. <text class="section-title">🔢 八星数组</text>
  25. </view>
  26. <view class="star-grid">
  27. <view
  28. v-for="(star, index) in starCombinations"
  29. :key="index"
  30. class="star-card"
  31. :class="'star-type-' + star.type"
  32. >
  33. <view class="star-card-header">
  34. <text class="star-pair">{{ star.pair }}</text>
  35. <text class="star-name">{{ star.name }}</text>
  36. </view>
  37. <view class="star-card-body">
  38. <text class="star-energy">{{ star.energy }}</text>
  39. <text class="star-desc">{{ star.description || '' }}</text>
  40. </view>
  41. </view>
  42. </view>
  43. </view>
  44. <!-- ========== AI Summary ========== -->
  45. <view v-if="summary" class="summary-card">
  46. <view class="summary-header">
  47. <text class="summary-title">🤖 AI 综合解读</text>
  48. <text v-if="summaryDate" class="summary-date">{{ summaryDate }}</text>
  49. </view>
  50. <view class="summary-body">
  51. <text class="summary-content">{{ summary }}</text>
  52. </view>
  53. </view>
  54. <!-- ========== Analysis History ========== -->
  55. <view v-if="history.length > 0" class="history-section">
  56. <view class="section-header">
  57. <text class="section-title">📋 历史分析</text>
  58. </view>
  59. <view
  60. v-for="item in history"
  61. :key="item.id"
  62. class="history-card"
  63. @click="loadHistory(item)"
  64. >
  65. <text class="history-phone">{{ maskPhone(item.phoneNumber) }}</text>
  66. <view class="history-meta">
  67. <text class="history-date">{{ formatDate(item.createdAt) }}</text>
  68. <text v-if="item.summary" class="history-status">已解读</text>
  69. </view>
  70. </view>
  71. </view>
  72. </view>
  73. </template>
  74. <script setup>
  75. import { ref } from 'vue'
  76. import { onShow } from '@dcloudio/uni-app'
  77. import requestInstance from '@/utils/api'
  78. import { useTheme } from '@/composables/useThemeStyle'
  79. const { themeClass } = useTheme()
  80. const phoneNumber = ref('')
  81. const analyzing = ref(false)
  82. const starCombinations = ref([])
  83. const summary = ref('')
  84. const summaryDate = ref('')
  85. const history = ref([])
  86. const analysisApi = {
  87. analyze: (data) => requestInstance.post('/number-analysis/analyze', data),
  88. list: (data) => requestInstance.post('/number-analysis/list', data),
  89. get: (id) => requestInstance.post('/number-analysis/detail', { id })
  90. }
  91. onShow(() => {
  92. fetchHistory()
  93. })
  94. async function fetchHistory() {
  95. try {
  96. history.value = await analysisApi.list({})
  97. } catch (e) {
  98. history.value = []
  99. }
  100. }
  101. async function handleAnalyze() {
  102. const num = phoneNumber.value.trim()
  103. if (num.length < 7) {
  104. uni.showToast({ title: '请输入至少7位数字', icon: 'none' })
  105. return
  106. }
  107. if (analyzing.value) return
  108. analyzing.value = true
  109. try {
  110. const result = await analysisApi.analyze({ phoneNumber: num })
  111. if (result) {
  112. starCombinations.value = result.starCombinations || []
  113. summary.value = result.summary || ''
  114. summaryDate.value = result.createdAt || ''
  115. }
  116. // Refresh history
  117. fetchHistory()
  118. } catch (e) {
  119. uni.showToast({ title: '分析失败,请重试', icon: 'none' })
  120. } finally {
  121. analyzing.value = false
  122. }
  123. }
  124. function loadHistory(item) {
  125. phoneNumber.value = item.phoneNumber || ''
  126. if (item.starCombinations) {
  127. try {
  128. starCombinations.value = typeof item.starCombinations === 'string'
  129. ? JSON.parse(item.starCombinations)
  130. : item.starCombinations
  131. } catch (e) {
  132. starCombinations.value = []
  133. }
  134. } else {
  135. starCombinations.value = []
  136. }
  137. summary.value = item.summary || ''
  138. summaryDate.value = item.createdAt || ''
  139. }
  140. function maskPhone(phone) {
  141. if (!phone) return ''
  142. if (phone.length >= 7) {
  143. return phone.slice(0, 3) + '****' + phone.slice(-4)
  144. }
  145. return phone
  146. }
  147. function formatDate(dateStr) {
  148. if (!dateStr) return ''
  149. return dateStr.slice(0, 10)
  150. }
  151. </script>
  152. <style scoped lang="scss">
  153. .page-number-analysis {
  154. min-height: 100vh;
  155. background: var(--color-bg);
  156. padding: 16px;
  157. }
  158. /* ========== Input Card ========== */
  159. .input-card {
  160. background: var(--color-bg-card);
  161. border-radius: 16px;
  162. padding: 20px;
  163. box-shadow: var(--shadow-card);
  164. margin-bottom: 16px;
  165. }
  166. .input-label {
  167. display: block;
  168. font-size: 14px;
  169. font-weight: 600;
  170. color: var(--color-text-secondary);
  171. margin-bottom: 12px;
  172. }
  173. .input-row {
  174. display: flex;
  175. gap: 12px;
  176. }
  177. .phone-input {
  178. flex: 1;
  179. height: 48px;
  180. border: 1px solid var(--color-border);
  181. border-radius: 12px;
  182. padding: 0 14px;
  183. font-size: 18px;
  184. color: var(--color-text-primary);
  185. background: var(--color-bg-card);
  186. box-sizing: border-box;
  187. }
  188. .phone-input:focus {
  189. border-color: var(--color-brand);
  190. box-shadow: 0 0 0 2px rgba(30, 58, 95, 0.1);
  191. }
  192. .input-placeholder {
  193. color: var(--color-text-secondary);
  194. font-size: 16px;
  195. }
  196. .btn-analyze {
  197. flex-shrink: 0;
  198. height: 48px;
  199. padding: 0 24px;
  200. background: var(--color-brand);
  201. border-radius: 12px;
  202. display: flex;
  203. align-items: center;
  204. justify-content: center;
  205. }
  206. .btn-analyze:active {
  207. transform: scale(0.97);
  208. opacity: 0.9;
  209. }
  210. .btn-analyze.analyzing {
  211. opacity: 0.7;
  212. pointer-events: none;
  213. }
  214. .btn-analyze-text {
  215. color: #FFFFFF;
  216. font-size: 16px;
  217. font-weight: 600;
  218. }
  219. /* ========== Sections ========== */
  220. .result-section,
  221. .history-section {
  222. margin-bottom: 16px;
  223. }
  224. .section-header {
  225. margin-bottom: 12px;
  226. }
  227. .section-title {
  228. font-size: 17px;
  229. font-weight: 600;
  230. color: var(--color-text-primary);
  231. }
  232. /* ========== Star Grid ========== */
  233. .star-grid {
  234. display: flex;
  235. flex-direction: column;
  236. gap: 10px;
  237. }
  238. .star-card {
  239. background: var(--color-bg-card);
  240. border-radius: 14px;
  241. padding: 14px 16px;
  242. box-shadow: var(--shadow-card);
  243. border-left: 4px solid var(--color-text-secondary);
  244. }
  245. .star-card.star-type-ji { border-left-color: #D94A4A; }
  246. .star-card.star-type-huo { border-left-color: #E8A238; }
  247. .star-card.star-type-yan { border-left-color: #4A90D9; }
  248. .star-card.star-type-tian { border-left-color: #2D8B67; }
  249. .star-card.star-type-sheng { border-left-color: #7B6FA0; }
  250. .star-card.star-type-ju { border-left-color: #C9A84C; }
  251. .star-card.star-type-fu { border-left-color: #4A6FA5; }
  252. .star-card.star-type-jue { border-left-color: #B85C3A; }
  253. .star-card-header {
  254. display: flex;
  255. align-items: center;
  256. gap: 10px;
  257. margin-bottom: 6px;
  258. }
  259. .star-pair {
  260. font-size: 20px;
  261. font-weight: 700;
  262. color: var(--color-text-primary);
  263. }
  264. .star-name {
  265. font-size: 14px;
  266. font-weight: 500;
  267. color: var(--color-text-secondary);
  268. }
  269. .star-card-body {
  270. padding-left: 2px;
  271. }
  272. .star-energy {
  273. font-size: 13px;
  274. font-weight: 600;
  275. color: var(--color-brand);
  276. display: block;
  277. margin-bottom: 2px;
  278. text-transform: capitalize;
  279. }
  280. .star-desc {
  281. font-size: 12px;
  282. color: var(--color-text-secondary);
  283. line-height: 1.5;
  284. display: block;
  285. }
  286. /* ========== Summary Card ========== */
  287. .summary-card {
  288. background: var(--color-bg-card);
  289. border-radius: 16px;
  290. padding: 20px 16px;
  291. box-shadow: var(--shadow-card);
  292. margin-bottom: 16px;
  293. }
  294. .summary-header {
  295. display: flex;
  296. justify-content: space-between;
  297. align-items: center;
  298. margin-bottom: 12px;
  299. }
  300. .summary-title {
  301. font-size: 16px;
  302. font-weight: 600;
  303. color: var(--color-text-primary);
  304. }
  305. .summary-date {
  306. font-size: 12px;
  307. color: var(--color-text-secondary);
  308. }
  309. .summary-body {
  310. background: var(--color-bg-secondary);
  311. border-radius: 12px;
  312. padding: 16px;
  313. }
  314. .summary-content {
  315. font-size: 14px;
  316. color: var(--color-text-secondary);
  317. line-height: 1.8;
  318. white-space: pre-wrap;
  319. }
  320. /* ========== History ========== */
  321. .history-card {
  322. background: var(--color-bg-card);
  323. border-radius: 14px;
  324. padding: 14px 16px;
  325. margin-bottom: 10px;
  326. box-shadow: var(--shadow-card);
  327. display: flex;
  328. justify-content: space-between;
  329. align-items: center;
  330. }
  331. .history-card:active {
  332. transform: scale(0.98);
  333. opacity: 0.9;
  334. }
  335. .history-phone {
  336. font-size: 16px;
  337. font-weight: 600;
  338. color: var(--color-text-primary);
  339. }
  340. .history-meta {
  341. display: flex;
  342. align-items: center;
  343. gap: 10px;
  344. }
  345. .history-date {
  346. font-size: 12px;
  347. color: var(--color-text-secondary);
  348. }
  349. .history-status {
  350. font-size: 11px;
  351. color: var(--color-success);
  352. background: var(--color-success-bg);
  353. padding: 2px 8px;
  354. border-radius: 10px;
  355. font-weight: 500;
  356. }
  357. </style>