index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import { defineComponent, onMounted, reactive, ref, nextTick, onUnmounted } from 'vue';
  2. import styles from './index.module.less';
  3. import { List, Popup, DatePicker, Popover, Field, Picker, showToast } from 'vant';
  4. import request from '@/helpers/request';
  5. import { useRoute, useRouter } from 'vue-router';
  6. import OEmpty from '@/components/m-empty';
  7. import MWxTip from '@/components/m-wx-tip';
  8. import OSearch from '@/components/m-search';
  9. import positionIcon from './images/position_icon.png';
  10. import scIcon1 from './images/sc_icon1.png';
  11. import scIcon2 from './images/sc_icon2.png';
  12. import scIcon3 from './images/sc_icon3.png';
  13. import schoolIcon from './images/school_icon.png';
  14. import searchIcon from './images/search_icon.png';
  15. import searchBtn from './images/search_btn.png';
  16. import totalBoxBg from './images/total_box_icon.png';
  17. import wxShareIcon from './images/wx_share_icon.png';
  18. import { drawCircle } from './drawGraph'
  19. import useWeChatShare from '@/hooks/useWeChatShare';
  20. import { browser } from '@/helpers/utils';
  21. import OFullRefresh from '@/components/m-full-refresh';
  22. export default defineComponent({
  23. name: 'questionnaire-statistics-new',
  24. setup() {
  25. const route = useRoute();
  26. const router = useRouter();
  27. const tabName = ref('all');
  28. const forms = reactive({
  29. schoolName: '',
  30. id: route.query.id,
  31. // id: '1687275949971763202',
  32. yearStatus: false,
  33. schoolId: null,
  34. classList: [] as any,
  35. page: 1,
  36. rows: 20,
  37. isClick: false,
  38. tenantId: route.query.id,
  39. areaList: [] as any,
  40. areaColumns: [] as any,
  41. areaStatus: false,
  42. areaPopupShow: false,
  43. areaOptionIndex: [] as any,
  44. currentArea: null as any, // 当前的区域
  45. currentAreaInfo: null as any,
  46. schoolList: [] as any,
  47. totalInfo: {} as any,
  48. sortType: 'DESC' as any, // 排序方式,ASC(升序)/DESC(降序)
  49. sortField: 'totalNum', // totalNum: 总人数,supportNum:支持人数,supportRate:支持率
  50. areaIdx: 0 as any,
  51. });
  52. const refreshing = ref(false);
  53. const loading = ref(true);
  54. const finished = ref(false);
  55. const showContact = ref(false);
  56. const list = ref([]);
  57. const queryArea = async () => {
  58. try {
  59. const { data } = await request.get(
  60. `/edu-app/open/tenantInfo/getArea?tenantId=${forms.tenantId}`, {
  61. hideLoading: false,
  62. }
  63. );
  64. forms.areaList = data || []
  65. data.forEach((item: any, index: number) => {
  66. const {provinceName='',cityName='',regionName=''} = item
  67. forms.areaColumns.push({
  68. text: provinceName + ' ' + cityName + ' ' + (regionName ? regionName : ''),
  69. value: index
  70. })
  71. })
  72. // 没有缓存
  73. if (!sessionStorage.getItem('areaIdx')) {
  74. const defaultIndex = data.findIndex((item: any) => item.defaultFlag)
  75. forms.areaIdx = defaultIndex !== -1 ? Number(defaultIndex) : 0
  76. }
  77. forms.currentArea = forms.areaColumns.length ? forms.areaColumns[forms.areaIdx].text : ''
  78. forms.currentAreaInfo = forms.areaList.length ? forms.areaList[forms.areaIdx] : null;
  79. } catch (error) {
  80. }
  81. await queryInfo();
  82. await getList();
  83. }
  84. const queryInfo = async () => {
  85. try {
  86. const { provinceCode='',cityCode='',regionCode='' } = forms.currentAreaInfo
  87. const res = await request.post(
  88. '/edu-app/open/schoolMeetingQuestion/areaSummarySum',
  89. {
  90. data: {
  91. tenantId: forms.tenantId,
  92. provinceCode,
  93. cityCode,
  94. districtCode: regionCode,
  95. }
  96. }
  97. );
  98. forms.totalInfo = res.data|| {}
  99. nextTick(() => {
  100. let percentage = 0;
  101. state.intervalOne = setInterval(() => {
  102. if (percentage <= forms.totalInfo.supportRate) {
  103. // console.log(percentage,forms.totalInfo.supportRate)
  104. drawCircle('circle1', 1, percentage)
  105. }
  106. if (percentage <= forms.totalInfo.participationRate) {
  107. drawCircle('circle2', 2, percentage)
  108. }
  109. if (percentage === Math.floor(Number(forms.totalInfo.supportRate))) {
  110. drawCircle('circle1', 1, forms.totalInfo.supportRate)
  111. }
  112. if (percentage === Math.floor(Number(forms.totalInfo.participationRate))) {
  113. drawCircle('circle2', 2, forms.totalInfo.participationRate)
  114. }
  115. percentage += 1; // 每次增加1%
  116. if (percentage > Math.max(forms.totalInfo.supportRate, forms.totalInfo.participationRate)) {
  117. clearDataAnimation();
  118. }
  119. }, 25); // 每25ms更新一次
  120. });
  121. } catch (error) {
  122. }
  123. }
  124. const getList = async () => {
  125. try {
  126. const { provinceCode='',cityCode='',regionCode='' } = forms.currentAreaInfo
  127. const res = await request.post(
  128. '/edu-app/open/schoolMeetingQuestion/areaSummary',
  129. {
  130. data: {
  131. tenantId: forms.tenantId,
  132. schoolName: forms.schoolName,
  133. provinceCode,
  134. cityCode,
  135. districtCode: regionCode,
  136. sortType: forms.sortType,
  137. sortField: forms.sortField,
  138. }
  139. }
  140. );
  141. forms.schoolList = res?.data || []
  142. } catch {
  143. //
  144. } finally {
  145. //
  146. }
  147. };
  148. const state = reactive({
  149. saveLoading: false,
  150. image: null as any,
  151. shareLoading: false,
  152. intervalOne: null as any,
  153. });
  154. // 微信分享学校
  155. const wxShareItem = (event: MouseEvent, item: any) => {
  156. showToast('分享学校链接');
  157. const shareTitle = '测试';
  158. const weChatShare = useWeChatShare(
  159. shareTitle,
  160. '科技赋能音乐(器乐)学习,在每一个孩子心中奏响美妙的乐章。',
  161. window.location.origin + '/classroom-app/shareImg/questionnaire-statistics-new.png'
  162. );
  163. if (browser().weixin) {
  164. weChatShare.getAppSignature()
  165. }
  166. event.stopPropagation(); // 阻止事件冒泡
  167. }
  168. const skipDetail = (id: any) => {
  169. // sessionStorage.setItem('areaIdx', forms.areaIdx)
  170. sessionStorage.setItem('areaTenantName', forms.totalInfo.tenantName || '')
  171. sessionStorage.setItem('qsFilterParams', JSON.stringify({
  172. schoolName: forms.schoolName,
  173. sortType: forms.sortType,
  174. sortField: forms.sortField,
  175. }))
  176. router.push({
  177. path: '/statistics-detail-new',
  178. query: {
  179. id,
  180. }
  181. });
  182. }
  183. const filterList = (val: string) => {
  184. if (forms.sortField !== val) {
  185. forms.sortType = 'DESC'
  186. } else {
  187. forms.sortType = forms.sortType === 'DESC' ? 'ASC' : 'DESC'
  188. }
  189. forms.sortField = val
  190. getList()
  191. }
  192. const formatNumberWithComma = (num: number | string) => {
  193. // 将数字转换为字符串,去掉小数点后面的部分
  194. let [integer, decimal] = num.toString().split('.');
  195. // 使用正则表达式添加千分位分隔符
  196. integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  197. // 如果有小数部分,则保留小数部分
  198. return decimal ? `${integer}.${decimal}` : integer;
  199. }
  200. const initWxShare = () => {
  201. const shareTitle = (forms.totalInfo.tenantName||'') + '音乐(器乐)数字化转型问卷统计';
  202. const weChatShare = useWeChatShare(
  203. shareTitle,
  204. '科技赋能音乐(器乐)学习,在每一个孩子心中奏响美妙的乐章。',
  205. window.location.origin + '/classroom-app/shareImg/questionnaire-statistics-new.png'
  206. );
  207. if (browser().weixin) {
  208. weChatShare.getAppSignature()
  209. }
  210. }
  211. const onRefresh = async () => {
  212. // console.log('刷新111')
  213. clearDataAnimation();
  214. forms.areaColumns = []
  215. finished.value = false;
  216. // 重新加载数据
  217. // 将 loading 设置为 true,表示处于加载状态
  218. loading.value = true;
  219. await queryArea();
  220. refreshing.value = false
  221. };
  222. const clearDataAnimation = () => {
  223. clearInterval(state.intervalOne); // 停止定时器
  224. state.intervalOne = null;
  225. }
  226. onMounted(async () => {
  227. console.log('刷新页面')
  228. forms.areaIdx = sessionStorage.getItem('areaIdx') || 0;
  229. // @ts-ignore
  230. const qsFilterParams: any = sessionStorage.getItem('qsFilterParams') ? JSON.parse(sessionStorage.getItem('qsFilterParams')) : {};
  231. forms.schoolName = qsFilterParams.schoolName || ''
  232. forms.sortField = qsFilterParams.sortField || 'totalNum'
  233. forms.sortType = qsFilterParams.sortType || 'DESC'
  234. await queryArea();
  235. nextTick(() => {
  236. initWxShare()
  237. });
  238. });
  239. onUnmounted(() => {
  240. clearDataAnimation();
  241. });
  242. return () => (
  243. <OFullRefresh
  244. v-model:modelValue={refreshing.value}
  245. freshDisabled={forms.areaStatus}
  246. onRefresh={onRefresh}
  247. class={styles.refreshC}>
  248. <div class={[styles.statisBody]}>
  249. {
  250. forms.areaColumns.length > 1 &&
  251. <div class={[styles.spColumn, forms.areaStatus && styles.openVal]} onClick={() => {
  252. forms.areaOptionIndex = [Number(forms.areaIdx)]
  253. forms.areaStatus = true
  254. }}>
  255. <img src={positionIcon} />
  256. <p>{forms.currentArea}</p>
  257. <i></i>
  258. </div>
  259. }
  260. {/** 参与学校统计 */}
  261. <div class={styles.sTotal}>
  262. <div class={[styles.stOne, styles.stOneLine]}>
  263. <div class={styles.soTitle}><span class={styles.sOrange}>{formatNumberWithComma(forms.totalInfo.schoolNum||0)}</span><i>所</i></div>
  264. <p class={styles.soDesc}><img src={schoolIcon} />参与学校</p>
  265. </div>
  266. <div class={styles.stOne}>
  267. <div class={styles.soTitle}><span class={styles.sRed}>{formatNumberWithComma(forms.totalInfo.totalNum||0)}</span><i>人</i></div>
  268. <p class={styles.soDesc}><img src={scIcon1} />参与调查</p>
  269. </div>
  270. </div>
  271. {/** 圆环统计 */}
  272. <div class={styles.sRing}>
  273. <div class={[styles.srItem,styles.srItemOne]}>
  274. <div class={styles.siLeft}>
  275. <canvas id="circle1" width="85" height="85"></canvas>
  276. <p>支持率</p>
  277. </div>
  278. <div class={styles.siRight}>
  279. <div><span class={styles.sBlue}>{formatNumberWithComma(forms.totalInfo.supportNum||0)}</span><i>人</i></div>
  280. <p>支持开展</p>
  281. </div>
  282. </div>
  283. <div class={styles.srItem}>
  284. <div class={styles.siLeft}>
  285. <canvas id="circle2" width="85" height="85"></canvas>
  286. <p>参加率</p>
  287. </div>
  288. <div class={styles.siRight}>
  289. <div><span class={styles.sGreen}>{formatNumberWithComma(forms.totalInfo.participationNum||0)}</span><i>人</i></div>
  290. <p>报名参加</p>
  291. </div>
  292. </div>
  293. </div>
  294. {/** 搜索栏 */}
  295. <div class={styles.searechInfo}>
  296. <img src={searchIcon} class={styles.searchIcon} />
  297. <Field
  298. clearable={true}
  299. inputAlign="left"
  300. placeholder="请输入学校名称"
  301. autocomplete="off"
  302. center
  303. maxlength={30}
  304. v-model={forms.schoolName}
  305. onUpdate:modelValue={(val: any) => {
  306. // 输入框内容变化时触发
  307. console.log('搜索内容变化',val)
  308. forms.schoolName = val
  309. sessionStorage.setItem('qsFilterParams', JSON.stringify({
  310. schoolName: forms.schoolName,
  311. sortType: forms.sortType,
  312. sortField: forms.sortField,
  313. }))
  314. getList()
  315. }}>
  316. </Field>
  317. <img src={searchBtn} class={styles.searchBtn} onClick={getList} />
  318. </div>
  319. {/** 排序栏 */}
  320. <ul class={styles.sortColumn}>
  321. <li class={forms.sortField === 'totalNum' && styles.sortActive} onClick={() => filterList('totalNum')}>
  322. <span>参与调查人数</span>
  323. <i class={[(forms.sortField === 'totalNum' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'totalNum' && forms.sortType === 'ASC') && styles.actUp]}></i>
  324. </li>
  325. <li class={forms.sortField === 'supportNum' && styles.sortActive} onClick={() => filterList('supportNum')}>
  326. <span>支持人数</span>
  327. <i class={[(forms.sortField === 'supportNum' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'supportNum' && forms.sortType === 'ASC') && styles.actUp]}></i>
  328. </li>
  329. <li class={forms.sortField === 'supportRate' && styles.sortActive} onClick={() => filterList('supportRate')}>
  330. <span>支持率</span>
  331. <i class={[(forms.sortField === 'supportRate' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'supportRate' && forms.sortType === 'ASC') && styles.actUp]}></i>
  332. </li>
  333. <li class={forms.sortField === 'participationNum' && styles.sortActive} onClick={() => filterList('participationNum')}>
  334. <span>参加人数</span>
  335. <i class={[(forms.sortField === 'participationNum' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'participationNum' && forms.sortType === 'ASC') && styles.actUp]}></i>
  336. </li>
  337. <li class={forms.sortField === 'participationRate' && styles.sortActive} onClick={() => filterList('participationRate')}>
  338. <span>参加率</span>
  339. <i class={[(forms.sortField === 'participationRate' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'participationRate' && forms.sortType === 'ASC') && styles.actUp]}></i>
  340. </li>
  341. </ul>
  342. {/** 学校列表 */}
  343. {
  344. forms.schoolList.length ?
  345. <div class={styles.scList}>
  346. {forms.schoolList.map((item: any) => (
  347. <div class={styles.sItem} onClick={() => skipDetail(item.schoolAreaId)}>
  348. <img class={styles.shareIcon} src={wxShareIcon} onClick={(event: MouseEvent) => wxShareItem(event, item)} />
  349. <div class={styles.itemTile}>
  350. <img src={schoolIcon} />
  351. <p>{item.schoolName}</p>
  352. {/* <i></i> */}
  353. </div>
  354. <div class={[styles.itemDesc]}><span class={styles.sRed}>{formatNumberWithComma(item.totalNum || 0)}</span> 人参与调查</div>
  355. <ul class={styles.itemContent}>
  356. <li>
  357. <div class={styles.icTop}>
  358. <span class={styles.sBlue}>{formatNumberWithComma(item.supportNum || 0)}</span><i>人</i>
  359. </div>
  360. <p>支持开展</p>
  361. </li>
  362. <li>
  363. <div class={styles.icTop}>
  364. <span class={styles.sBlue}>{Number(item.supportRate || 0).toFixed(2)}%</span>
  365. </div>
  366. <p>支持率</p>
  367. </li>
  368. <li>
  369. <div class={styles.icTop}>
  370. <span class={styles.sGreen}>{formatNumberWithComma(item.participationNum || 0)}</span><i>人</i>
  371. </div>
  372. <p>报名参加</p>
  373. </li>
  374. <li>
  375. <div class={styles.icTop}>
  376. <span class={styles.sGreen}>{Number(item.participationRate || 0).toFixed(2)}%</span>
  377. </div>
  378. <p>参加率</p>
  379. </li>
  380. </ul>
  381. </div>
  382. ))}
  383. </div> :
  384. <OEmpty description="暂无内容" class={styles.emptyC} />
  385. }
  386. {/* 区域 */}
  387. <Popup
  388. v-model:show={forms.areaStatus}
  389. position="bottom"
  390. round
  391. safeAreaInsetBottom
  392. lazyRender={false}
  393. class={'popupBottomSearch'}
  394. onOpen={() => {
  395. forms.areaPopupShow = true;
  396. }}
  397. onClosed={() => {
  398. forms.areaPopupShow = false;
  399. }}>
  400. {forms.areaPopupShow && (
  401. <Picker
  402. showToolbar
  403. v-model={forms.areaOptionIndex}
  404. columns={forms.areaColumns}
  405. onCancel={() => (forms.areaStatus = false)}
  406. onConfirm={(val: any) => {
  407. // forms.gradeAndClassIndex = [val.selectedOptions[0].value, val.selectedOptions[1].value]
  408. // forms.currentArea = val.selectedOptions[0].text;
  409. // forms.currentClass = val.selectedOptions[1].text;
  410. forms.currentArea = val.selectedOptions[0].text
  411. forms.areaOptionIndex = [val.selectedOptions[0].value]
  412. forms.areaIdx = val.selectedOptions[0].value
  413. forms.areaStatus = false;
  414. forms.schoolName = '';
  415. forms.currentAreaInfo = forms.areaList[val.selectedOptions[0].value]
  416. sessionStorage.setItem('areaIdx', forms.areaIdx)
  417. clearDataAnimation()
  418. queryInfo()
  419. getList()
  420. // console.log('选择1111',val,forms.areaOptionIndex)
  421. }}
  422. />
  423. )}
  424. </Popup>
  425. {/* <MWxTip /> */}
  426. </div>
  427. </OFullRefresh>
  428. );
  429. }
  430. });