screening.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. <template>
  2. <view class="initial screenBody" :class="{ 'border_bottom': showScreen }">
  3. <view class="screenBox" :class="{ 'padding_0': !isShowTime }">
  4. <view v-if="isShowTime" class="dataBox displayFlexRow" :class="type">
  5. <view
  6. class="screen_time_item"
  7. :class="{ 'backgroundCustom': item.check && type }"
  8. v-for="(item, index) in screenTime"
  9. :key="index"
  10. @click="choiceTime(index)"
  11. >
  12. <text :class="{ 'font_weight': item.check }">{{ item.label }}</text>
  13. <view :class="{ 'backgroundCustom': item.check }"></view>
  14. </view>
  15. </view>
  16. <view v-if="isShowScreen" class="screenBtn" :class="{ 'width_100p': !isShowTime }" @click="toggleScreen">
  17. <view>
  18. <text :class="{ 'colorCustom': showScreen }">{{ localScreen.btnName ? localScreen.btnName : '筛选' }}</text>
  19. </view>
  20. <image class="screenIcon" v-if="!showScreen" :src="iconUrl.icon_bottom"></image>
  21. <image class="screenIcon" v-else :src="iconUrl.icon_bottom_y"></image>
  22. </view>
  23. </view>
  24. <!-- 筛选条件 -->
  25. <view class="mainScreenBox border-top" v-if="showScreen">
  26. <view class="cententBox">
  27. <view v-for="(column, index) in localScreen.columns" :key="index">
  28. <text class="screenTitle">{{ column.title }}</text>
  29. <view class="screenList">
  30. <text
  31. :class="{ 'active_screen': option.check }"
  32. v-for="(option, opIndex) in column.options"
  33. :key="opIndex"
  34. @click="selectItem(index, opIndex)"
  35. >
  36. {{ option.name }}
  37. </text>
  38. </view>
  39. </view>
  40. <view>
  41. <text class="screenTitle">查询时段</text>
  42. <view class="time_box displayFlexRow">
  43. <picker class="displayFlexRow" mode="date" :value="localScreen.startDate" :start="pickerStart" :end="localScreen.endDate" @change="onChangeStartDate">
  44. <view class="picker textTag">
  45. {{ localScreen.startDate }}
  46. </view>
  47. </picker>
  48. <text>至</text>
  49. <picker class="displayFlexRow" mode="date" :value="localScreen.endDate" :start="localScreen.startDate" :end="pickerEnd" @change="onChangeEndDate">
  50. <view class="picker textTag">
  51. {{ localScreen.endDate }}
  52. </view>
  53. </picker>
  54. </view>
  55. </view>
  56. </view>
  57. <view class="screenFooterBtn">
  58. <view class="border" @click.stop="onResetScreen">重置</view>
  59. <view class="backgroundCustom" @click.stop="onQueryRecords">确定</view>
  60. </view>
  61. </view>
  62. <!-- 遮罩 -->
  63. <view class="mask" v-if="showScreen" @click="hideScreen"></view>
  64. </view>
  65. </template>
  66. <script setup lang="ts">
  67. import { ref, watch, onMounted } from 'vue';
  68. import { common } from '@/utils';
  69. import icon from '@/utils/icon';
  70. const props = defineProps({
  71. screen: {
  72. type: Object,
  73. default: () => ({})
  74. },
  75. isShowTime: {
  76. type: Boolean,
  77. default: true
  78. },
  79. isShowScreen: {
  80. type: Boolean,
  81. default: true
  82. },
  83. pageType: {
  84. type: String,
  85. default: ""
  86. },
  87. pickerStart: {
  88. type: String,
  89. default: () => common.aFewDaysago(3650)
  90. },
  91. pickerEnd: {
  92. type: String,
  93. default: () => common.afterFewDays(3650)
  94. },
  95. type: {
  96. type: String,
  97. default: ""
  98. },
  99. defaultValue: {
  100. type: String,
  101. default: ""
  102. }
  103. });
  104. const emit = defineEmits(['changeStartDate', 'changeEndDate', 'setScreenData', 'queryRecords', 'resetScreen']);
  105. const iconUrl = ref(icon)
  106. const showScreen = ref(false);
  107. const screenTime = ref([
  108. { label: "近一周", value: "7", check: true },
  109. { label: "近一个月", value: "30", check: false },
  110. { label: "近三个月", value: "90", check: false },
  111. ]);
  112. const localScreen = ref<any>({});
  113. const backupsScreen = ref<any>(null);
  114. // Initialize
  115. onMounted(() => {
  116. if (common.isNotEmpty(props.screen.screenTime)) {
  117. screenTime.value = JSON.parse(JSON.stringify(props.screen.screenTime));
  118. }
  119. if (common.isNotEmpty(props.screen)) {
  120. localScreen.value = JSON.parse(JSON.stringify(props.screen));
  121. backupsScreen.value = JSON.parse(JSON.stringify(props.screen));
  122. }
  123. });
  124. watch(() => props.screen, (newVal) => {
  125. if (newVal) {
  126. localScreen.value = JSON.parse(JSON.stringify(newVal));
  127. }
  128. }, { deep: true });
  129. watch(() => props.defaultValue, (v) => {
  130. if (v) {
  131. screenTime.value.forEach(item => {
  132. item.check = item.value == v;
  133. });
  134. }
  135. });
  136. // Methods
  137. const choiceTime = (index: number) => {
  138. screenTime.value.forEach((item) => {
  139. item.check = false;
  140. });
  141. screenTime.value[index].check = true;
  142. let obj: any = {};
  143. let value = parseInt(screenTime.value[index].value);
  144. if (props.pageType == "yyjl") {
  145. obj = {
  146. startDate: common.dateFormat(new Date(Date.now())).formatYear,
  147. endDate: common.dateFormat(new Date(Date.now() + (value * 24 * 60 * 60 * 1000))).formatYear
  148. };
  149. } else if (props.pageType == "tabs") {
  150. obj = screenTime.value[index];
  151. // Special handling for tabs might be needed if it expects different structure
  152. } else {
  153. obj = {
  154. startDate: common.dateFormat(new Date(Date.now() - (value * 24 * 60 * 60 * 1000))).formatYear,
  155. endDate: common.dateFormat(new Date(Date.now() + (1 * 24 * 60 * 60 * 1000))).formatYear
  156. };
  157. }
  158. // Update local screen dates
  159. if (obj.startDate) localScreen.value.startDate = obj.startDate;
  160. if (obj.endDate) localScreen.value.endDate = obj.endDate;
  161. emit('changeStartDate', obj);
  162. };
  163. const onChangeStartDate = (e: any) => {
  164. let val = e.detail.value;
  165. localScreen.value.startDate = val;
  166. let obj = { startDate: val };
  167. if (showScreen.value) {
  168. emit('setScreenData', localScreen.value);
  169. } else {
  170. emit('changeStartDate', obj);
  171. }
  172. };
  173. const onChangeEndDate = (e: any) => {
  174. let val = e.detail.value;
  175. localScreen.value.endDate = val;
  176. let obj = { endDate: val };
  177. if (showScreen.value) {
  178. emit('setScreenData', localScreen.value);
  179. } else {
  180. emit('changeEndDate', obj);
  181. }
  182. };
  183. const toggleScreen = () => {
  184. showScreen.value = !showScreen.value;
  185. };
  186. const hideScreen = () => {
  187. showScreen.value = false;
  188. };
  189. const selectItem = (cIndex: number, index: number) => {
  190. let column = localScreen.value.columns[cIndex];
  191. let option = column.options[index];
  192. let key = column.key;
  193. // Logic for single/multiple selection
  194. let optionCheck = !option.check;
  195. // Modify localScreen directly
  196. column.options[index].check = optionCheck;
  197. if (column.single) {
  198. if (option.name == '全部') {
  199. if (optionCheck) {
  200. // Uncheck others
  201. column.options.forEach((opt: any, i: number) => {
  202. if (i != index) opt.check = false;
  203. });
  204. // Set value
  205. if (typeof(option.value) == "string" && option.value.indexOf(',') > -1) {
  206. localScreen.value[key] = option.value.split(',');
  207. } else {
  208. localScreen.value[key] = [option.value];
  209. }
  210. }
  211. } else {
  212. if (optionCheck) {
  213. // Uncheck others
  214. column.options.forEach((opt: any, i: number) => {
  215. if (i != index) opt.check = false;
  216. });
  217. localScreen.value[key] = [option.value];
  218. }
  219. }
  220. } else {
  221. // Multiple
  222. if (option.name == '全部') {
  223. if (optionCheck) {
  224. column.options.forEach((opt: any, i: number) => {
  225. if (i != index) opt.check = false;
  226. });
  227. if (option.value.indexOf(',') > -1) {
  228. localScreen.value[key] = option.value.split(',');
  229. } else {
  230. localScreen.value[key] = [option.value];
  231. }
  232. }
  233. } else {
  234. let keyVal = localScreen.value[key] || [];
  235. if (optionCheck) {
  236. // Uncheck '全部' if it was checked
  237. column.options.forEach((opt: any) => {
  238. if (opt.name == '全部' && opt.check) {
  239. opt.check = false;
  240. keyVal = [];
  241. }
  242. });
  243. keyVal.push(option.value);
  244. localScreen.value[key] = keyVal;
  245. } else {
  246. // Must select at least one? Original logic:
  247. if (keyVal.length > 1) {
  248. let idx = keyVal.indexOf(option.value);
  249. if (idx > -1) keyVal.splice(idx, 1);
  250. localScreen.value[key] = keyVal;
  251. } else {
  252. // Prevent unchecking the last one?
  253. // Original: if(keyVal.length>1){...} else { nothing } -> implies cannot uncheck last one
  254. // Revert check
  255. column.options[index].check = true;
  256. }
  257. }
  258. }
  259. }
  260. emit('setScreenData', localScreen.value);
  261. };
  262. const onQueryRecords = () => {
  263. screenTime.value.forEach((item) => {
  264. item.check = false;
  265. });
  266. emit('queryRecords', localScreen.value);
  267. showScreen.value = false;
  268. };
  269. const onResetScreen = () => {
  270. if (backupsScreen.value) {
  271. localScreen.value = JSON.parse(JSON.stringify(backupsScreen.value));
  272. emit('resetScreen', localScreen.value);
  273. }
  274. showScreen.value = false;
  275. };
  276. </script>
  277. <style lang="scss" scoped>
  278. @import "@/App.scss";
  279. .font_weight {
  280. font-weight: bold;
  281. }
  282. .displayFlexRow {
  283. display: flex;
  284. flex-direction: row;
  285. justify-content: center;
  286. align-items: center;
  287. }
  288. .displayFlexCol {
  289. display: flex;
  290. flex-direction: column;
  291. justify-content: center;
  292. align-items: center;
  293. }
  294. .padding_0 {
  295. padding: 0 !important;
  296. }
  297. .width_100p {
  298. width: 100% !important;
  299. }
  300. .screenBody {
  301. position: relative;
  302. }
  303. /* 普通版 */
  304. .screenBox {
  305. width: 100%;
  306. height: 106upx;
  307. background: white;
  308. padding: 0upx 30upx;
  309. box-sizing: border-box;
  310. display: flex;
  311. flex-direction: row;
  312. justify-content: space-between;
  313. align-items: center;
  314. /* position: relative; */
  315. z-index: 15;
  316. border-radius: 0 0 30upx 30upx;
  317. }
  318. .dataBox {
  319. width: 100%;
  320. height: 100%;
  321. display: flex;
  322. flex-direction: row;
  323. justify-content: space-between;
  324. align-items: center;
  325. }
  326. .screen_time_item {
  327. height: 100%;
  328. text-align: center;
  329. position: relative;
  330. }
  331. .screen_time_item text {
  332. line-height: 106upx;
  333. font-size: 32upx;
  334. color: #333;
  335. }
  336. .screen_time_item view {
  337. width: 56upx;
  338. height: 6upx;
  339. position: absolute;
  340. left: 50%;
  341. bottom: 0;
  342. margin-left: -28upx;
  343. }
  344. .screenBtn {
  345. width: 30%;
  346. min-width: 30%;
  347. display: flex;
  348. flex-direction: row;
  349. justify-content: flex-end;
  350. align-items: center;
  351. }
  352. .screenBtn text {
  353. font-size: 30upx;
  354. color: #666;
  355. padding-right: 16upx;
  356. }
  357. .screenIcon {
  358. width: 24upx;
  359. height: 14upx;
  360. }
  361. .mainScreenBox {
  362. width: 100%;
  363. max-height: 800upx;
  364. background: white;
  365. border-radius: 24upx 24upx 0 0;
  366. position: fixed;
  367. /* top: 100upx; */
  368. left: 0;
  369. bottom: 0;
  370. z-index: 15;
  371. }
  372. .cententBox {
  373. max-height: 640upx;
  374. padding: 40upx 30upx;
  375. box-sizing: border-box;
  376. overflow: auto;
  377. }
  378. .screenTitle {
  379. display: inline-block;
  380. font-size: 32upx;
  381. font-weight: bold;
  382. color: #333;
  383. margin-top: 64upx;
  384. margin-bottom: 36upx;
  385. }
  386. .screenTitle:nth-child(1) {
  387. margin-top: 0upx;
  388. }
  389. .screenList {
  390. display: flex;
  391. flex-direction: row;
  392. justify-content: flex-start;
  393. align-items: center;
  394. flex-wrap: wrap;
  395. }
  396. .screenList text {
  397. display: inline-block;
  398. width: 30%;
  399. line-height: 80upx;
  400. /* width: 210upx; */
  401. margin-right: 5%;
  402. margin-bottom: 30upx;
  403. font-size: 28upx;
  404. text-align: center;
  405. color: #333;
  406. padding: 0 20upx;
  407. background: #f7f7f7;
  408. border-radius: 80upx;
  409. }
  410. .active_screen {
  411. color: #007aff !important; /* var(--dominantColor) replacement */
  412. border: 1px solid #007aff;
  413. }
  414. .screenList text:nth-child(3n) {
  415. margin-right: 0upx;
  416. }
  417. .time_box {
  418. justify-content: flex-start;
  419. }
  420. .time_box text {
  421. margin: 0 30upx;
  422. }
  423. .picker {
  424. font-size: 32upx;
  425. padding: 27upx 44upx;
  426. background: #f1f1f6;
  427. border-radius: 100upx;
  428. }
  429. .screenFooterBtn {
  430. padding: 30upx;
  431. box-sizing: border-box;
  432. display: flex;
  433. flex-direction: row;
  434. justify-content: space-between;
  435. align-items: center;
  436. }
  437. .screenFooterBtn view {
  438. width: 30%;
  439. line-height: 98upx;
  440. font-size: 36upx;
  441. text-align: center;
  442. border-radius: 98upx;
  443. }
  444. .screenFooterBtn view:nth-child(2) {
  445. width: 65%;
  446. }
  447. .mask {
  448. width: 100%;
  449. height: 100%;
  450. background: black;
  451. opacity: 0.6;
  452. position: fixed;
  453. /* top: 480upx; */
  454. bottom: 0;
  455. z-index: 6;
  456. }
  457. .textColor {
  458. color: #306eff !important;
  459. }
  460. .border-bottom {
  461. position: relative;
  462. z-index: 10;
  463. }
  464. .border-bottom::after {
  465. content: " ";
  466. position: absolute;
  467. left: 0;
  468. bottom: 0;
  469. width: 100%;
  470. height: 1px;
  471. border-top: 1px solid #e6e6e6;
  472. -webkit-transform-origin: 0 0;
  473. transform-origin: 0 0;
  474. -webkit-transform: scaleY(0.5);
  475. transform: scaleY(0.5);
  476. }
  477. /* Card Type Styles */
  478. .card .screen_time_item {
  479. height: 80upx;
  480. padding: 0 20upx;
  481. background-color: #e5e9ec;
  482. border-radius: 20upx;
  483. }
  484. .card .screen_time_item text {
  485. line-height: 80upx;
  486. font-size: 28upx;
  487. }
  488. .card .screen_time_item.backgroundCustom text {
  489. color: #fff;
  490. }
  491. .card .screen_time_item:not(:first-child) {
  492. margin-left: 20upx;
  493. }
  494. .card .screen_time_item view {
  495. display: none;
  496. }
  497. </style>