sumSettings.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. <!-- 売上集計条件指定 -->
  2. <template>
  3. <div class="app-container">
  4. <div class="form-container">
  5. <div class="section-container">
  6. <div class="form-section">
  7. 対象
  8. </div>
  9. <el-form-item class="content-group">
  10. <div class="radio-group-vertical">
  11. <!-- 月指定行 -->
  12. <div class="monthly-section">
  13. <div class="date-groups-wrapper">
  14. <div class="date-group">
  15. <el-radio class="common-radio" :label="TARGET_PERIOD_TYPE.MONTHLY" v-model="queryParams.targetPeriodType">
  16. 月指定
  17. </el-radio>
  18. <el-select class="date-select" v-model="queryParams.startYear" placeholder=""
  19. :disabled="queryParams.targetPeriodType !== TARGET_PERIOD_TYPE.MONTHLY">
  20. <el-option v-for="year in years" :key="year" :label="year" :value="year"></el-option>
  21. </el-select>
  22. <span class="date-label">年</span>
  23. </div>
  24. <div class="date-group">
  25. <el-select class="date-select-small" v-model="queryParams.startMonth" placeholder=""
  26. :disabled="queryParams.targetPeriodType !== TARGET_PERIOD_TYPE.MONTHLY">
  27. <el-option v-for="month in months" :key="month" :label="month" :value="month"></el-option>
  28. </el-select>
  29. <span class="date-label">月</span>
  30. </div>
  31. </div>
  32. </div>
  33. <!-- 年度指定行 -->
  34. <div class="radio-item annual-group">
  35. <el-radio class="common-radio" :label="TARGET_PERIOD_TYPE.ANNUAL" v-model="queryParams.targetPeriodType">
  36. 年度指定
  37. </el-radio>
  38. <div class="annual-row">
  39. <div class="date-group">
  40. <el-select class="date-select" v-model="queryParams.annual" placeholder=""
  41. :disabled="queryParams.targetPeriodType !== TARGET_PERIOD_TYPE.ANNUAL">
  42. <el-option v-for="year in years" :key="year" :label="year" :value="year"></el-option>
  43. </el-select>
  44. <span class="date-label">年度</span>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. </el-form-item>
  50. </div>
  51. <!-- 集計種別区域:使用相同的section-container结构 -->
  52. <div class="section-container">
  53. <!-- 与“対象”区域保持一致:使用form-section作为标题容器 -->
  54. <div class="form-section">集計種別</div>
  55. <el-form-item class="content-group"> <!-- 复用content-group,确保内容区域基准一致 -->
  56. <el-radio-group v-model="queryParams.aggregationType" class="radio-group-horizontal">
  57. <!-- 为每个radio添加common-radio类,与“対象”区域的单选框样式统一 -->
  58. <el-radio class="common-radio" :label="AGGREGATION_TYPE.FC">FC別集計</el-radio>
  59. <el-radio class="common-radio" :label="AGGREGATION_TYPE.AREA">エリア別集計</el-radio>
  60. <el-radio class="common-radio" :label="AGGREGATION_TYPE.STORE">店舗別集計</el-radio>
  61. </el-radio-group>
  62. </el-form-item>
  63. </div>
  64. <div class="divider"></div>
  65. <PublicRange
  66. v-model="queryParams"
  67. :region-tree="regionTree"
  68. :yamada-fc-brand="yamadaFcBrand"
  69. :yamada-business-type="yamadaBusinessType"
  70. @check-change="handleCheckChange"
  71. :show-fc="true"
  72. :show-business-type="true"
  73. />
  74. <div class="btn-section">
  75. <el-button class="btn-aggregation" @click="handleAggregate" v-hasPermi="['fcbi:sales:sum']">集計</el-button>
  76. <el-button class="btn-reset" @click="resetForm" icon="Refresh">リセット</el-button>
  77. </div>
  78. </div>
  79. </div>
  80. </template>
  81. <script name="purchaseSaleSettings" setup>
  82. import { getRegionTree } from "@/api/fcbi/survey.js";
  83. import { reactive, ref, onMounted, getCurrentInstance, watch, toRefs } from 'vue';
  84. import PublicRange from '../../../components/PublicRange.vue';
  85. import { useSurveyStore, useSalesStore } from '@/store/yamadaStore';
  86. import { useRouter } from 'vue-router';
  87. import { AGGREGATION_TYPE, TARGET_PERIOD_TYPE } from '@/constants';
  88. const currentYear = new Date().getFullYear();
  89. const years = Array.from({ length: 11 }, (_, i) => currentYear + i);
  90. const months = Array.from({ length: 12 }, (_, i) => i + 1);
  91. const error = ref('');
  92. const { proxy } = getCurrentInstance();
  93. const surveyStore = useSurveyStore();
  94. const salesStore = useSalesStore();
  95. const router = useRouter();
  96. // テンプレートで定数を使用可能にするため、エクスポートします
  97. defineExpose({ AGGREGATION_TYPE, TARGET_PERIOD_TYPE });
  98. /**
  99. * データ辞書からブランドと業務タイプのデータを取得します
  100. */
  101. const { yamada_fc_brand: yamadaFcBrand } = proxy.useDict('yamada_fc_brand');
  102. const { yamada_business_type: yamadaBusinessType } = proxy.useDict('yamada_business_type');
  103. /**
  104. * 地域ツリーデータと選択された地域コードの配列を保持します
  105. */
  106. const regionTree = ref([]);
  107. const selectedRegions = ref([]);
  108. /**
  109. * フォームの入力データを保持するオブジェクトです
  110. */
  111. const data = reactive({
  112. // 検索条件を格納するオブジェクト
  113. queryParams: {
  114. targetPeriodType: TARGET_PERIOD_TYPE.MONTHLY,
  115. aggregationType: AGGREGATION_TYPE.FC,
  116. startYear: null,
  117. startMonth: null,
  118. annual: null,
  119. brandCode: [],
  120. businessTypeCode: [],
  121. regions: []
  122. }
  123. });
  124. const { queryParams } = toRefs(data);
  125. /**
  126. * 地域ツリーのデータが更新されたときに、選択された地域を更新します
  127. */
  128. watch(() => regionTree.value, (newVal) => {
  129. if (newVal?.length) {
  130. updateSelectedRegions(newVal);
  131. }
  132. }, { deep: true });
  133. watch(
  134. () => queryParams.value.targetPeriodType,
  135. (newVal) => {
  136. if (newVal === TARGET_PERIOD_TYPE.MONTHLY) {
  137. queryParams.value.annual = null;
  138. } else {
  139. queryParams.value.startYear = null;
  140. queryParams.value.startMonth = null;
  141. }
  142. },
  143. { immediate: true }
  144. );
  145. /**
  146. * コンポーネントのマウント後に地域ツリーデータを初期化します
  147. */
  148. onMounted(async () => {
  149. try {
  150. const response = await getRegionTree();
  151. if (response?.success) {
  152. const regions = response.data.regions || [];
  153. regionTree.value = regions;
  154. surveyStore.setRegionTree(regions)
  155. if (regions.length) {
  156. updateSelectedRegions(regions);
  157. }
  158. } else {
  159. error.value = response?.message || '地域データの取得に失敗しました';
  160. proxy.$message.error(error.value);
  161. console.error('地域データの取得に失敗しました:', response);
  162. }
  163. } catch (err) {
  164. const errorMsg = '地域データの取得中にエラーが発生しました';
  165. proxy.$message.error(errorMsg);
  166. console.error('地域データの取得中にエラーが発生しました:', err);
  167. }
  168. });
  169. /**
  170. * 地域のチェック状態が変更されたときのハンドラー関数です
  171. */
  172. const handleCheckChange = (regionId, isChecked) => {
  173. const region = findRegionById(regionTree.value, regionId);
  174. if (!region) return;
  175. if (isChecked) {
  176. if (!selectedRegions.value.some(r => r.regionCode === region.regionCode)) {
  177. selectedRegions.value.push({ regionCode: region.regionCode });
  178. }
  179. } else {
  180. selectedRegions.value = selectedRegions.value.filter(r => r.regionCode !== region.regionCode);
  181. }
  182. queryParams.value.regions = [...selectedRegions.value];
  183. };
  184. /**
  185. * 指定されたIDを持つ地域ノードをツリーから再帰的に検索します
  186. */
  187. const findRegionById = (nodes, id) => {
  188. if (!nodes?.length) return null;
  189. for (const node of nodes) {
  190. if (node.id === id) {
  191. return node;
  192. }
  193. if (node.children && node.children.length > 0) {
  194. const found = findRegionById(node.children, id);
  195. if (found) {
  196. return found;
  197. }
  198. }
  199. }
  200. return null;
  201. };
  202. /**
  203. * 選択されたすべての地域を再帰的に更新し、サブノードのみを収集します
  204. */
  205. const updateSelectedRegions = (nodes) => {
  206. if (!nodes?.length) return;
  207. selectedRegions.value = [];
  208. // 最上位ノードは親ノードとしてマークされ、再帰的に子ノードを処理します
  209. collectSelectedRegions(nodes, true);
  210. queryParams.value.regions = [...selectedRegions.value];
  211. };
  212. /**
  213. * 選択されたすべてのサブエリアオブジェクトを再帰的に収集します(親ノードをスキップ)
  214. */
  215. const collectSelectedRegions = (nodes, isParent = false) => {
  216. nodes.forEach(node => {
  217. // 親ノード(上位ノード)の場合は、収集をスキップして再帰的に子ノードを処理します
  218. if (isParent) {
  219. if (node.children && node.children.length > 0) {
  220. collectSelectedRegions(node.children, false);
  221. }
  222. return;
  223. }
  224. if (node.checked) {
  225. selectedRegions.value.push({
  226. regionCode: node.regionCode
  227. });
  228. }
  229. if (node.children && node.children.length > 0) {
  230. collectSelectedRegions(node.children, false);
  231. }
  232. });
  233. };
  234. /**
  235. * 検索条件を検証する関数です
  236. */
  237. const validateForm = () => {
  238. const errors = [];
  239. if (queryParams.value.targetPeriodType === TARGET_PERIOD_TYPE.MONTHLY) {
  240. if (!queryParams.value.startYear) {
  241. errors.push('「対象 月指定」の年を選択してください');
  242. }
  243. if (!queryParams.value.startMonth) {
  244. errors.push('「対象 月指定」の月を選択してください');
  245. }
  246. } else if (queryParams.value.targetPeriodType === TARGET_PERIOD_TYPE.ANNUAL) {
  247. if (!queryParams.value.annual) {
  248. errors.push('「対象 年度指定」の年度を選択してください');
  249. }
  250. }
  251. return errors;
  252. };
  253. /**
  254. * 集計ボタンをクリックしたときの処理関数です
  255. */
  256. const handleAggregate = () => {
  257. const errors = validateForm();
  258. if (errors.length) {
  259. errors.forEach(error => proxy.$message.warning(error));
  260. return;
  261. }
  262. let salesFlag = 0;
  263. const isMonthly = queryParams.value.targetPeriodType === TARGET_PERIOD_TYPE.MONTHLY;
  264. const { aggregationType } = queryParams.value;
  265. switch (aggregationType) {
  266. case AGGREGATION_TYPE.FC:
  267. const hasArea = queryParams.value.regions?.length > 0;
  268. salesFlag = isMonthly ? (hasArea ? 1 : 2) : (hasArea ? 3 : 4);
  269. break;
  270. case AGGREGATION_TYPE.AREA:
  271. salesFlag = isMonthly ? 7 : 8;
  272. break;
  273. case AGGREGATION_TYPE.STORE:
  274. salesFlag = isMonthly ? 5 : 6;
  275. break;
  276. default:
  277. proxy.$message.warning('集計種別を選択してください');
  278. return;
  279. }
  280. const transferData = {
  281. targetPeriodType: queryParams.value.targetPeriodType,
  282. startYear: isMonthly ? queryParams.value.startYear : null,
  283. startMonth: isMonthly ? queryParams.value.startMonth : null,
  284. annual: !isMonthly ? queryParams.value.annual : null,
  285. brandCodes: queryParams.value.brandCode,
  286. regionCodes: queryParams.value.regions.map(r => r.regionCode),
  287. aggregationType: queryParams.value.aggregationType,
  288. salesFlag: salesFlag
  289. };
  290. salesStore.setSalesData(transferData);
  291. router.push({ name: 'salesSumResult' });
  292. };
  293. /**
  294. * リセットボタンをクリックしたときの処理関数です
  295. */
  296. const resetForm = () => {
  297. queryParams.value.targetPeriodType = TARGET_PERIOD_TYPE.MONTHLY;
  298. queryParams.value.aggregationType = AGGREGATION_TYPE.FC;
  299. queryParams.value.startYear = null;
  300. queryParams.value.startMonth = null;
  301. queryParams.value.annual = null;
  302. queryParams.value.brandCode = [];
  303. queryParams.value.regions = [];
  304. selectedRegions.value = [];
  305. if (regionTree.value.length) {
  306. resetRegionTreeCheck(regionTree.value);
  307. }
  308. };
  309. /**
  310. * 地域ツリーのチェック状態をリセットします
  311. */
  312. const resetRegionTreeCheck = (nodes) => {
  313. nodes.forEach(node => {
  314. node.checked = false;
  315. if (node.children?.length) {
  316. resetRegionTreeCheck(node.children);
  317. }
  318. });
  319. };
  320. </script>
  321. <style scoped>
  322. /* 表单容器样式 - 整体布局控制 */
  323. .form-container {
  324. margin-top: 0; /* 顶部外边距 */
  325. padding: 0 !important; /* 清除原有左右内边距,避免干扰基准线 */
  326. }
  327. /* 1. 统一标题与内容的布局容器 */
  328. .section-container {
  329. display: flex;
  330. align-items: flex-start; /* 顶部对齐,确保基准一致 */
  331. margin-bottom: 12px;
  332. gap: 10px; /* 标题与内容的固定间距 */
  333. }
  334. /* 2. 统一标题样式(确保“対”和“集”字对齐) */
  335. /* 统一标题文字的垂直对齐基准(核心修正) */
  336. .form-section {
  337. /* 保留原有所有属性 */
  338. width: 80px !important; /* 固定宽度,确保文字左侧起点一致 */
  339. font-size: 14px !important; /* 统一字体大小 */
  340. line-height: 1.4 !important; /* 统一行高,确保文字基线一致 */
  341. padding-left: 0 !important; /* 清除额外左内边距 */
  342. margin: 0 !important; /* 清除默认外边距 */
  343. margin-top: 5px !important; /* 保持与单选框的垂直居中对齐 */
  344. text-align: left !important; /* 强制左对齐 */
  345. letter-spacing: normal !important; /* 清除可能的字间距差异 */
  346. display: inline-block !important; /* 确保宽度和高度计算准确 */
  347. /* 修正对齐的核心属性 - 与分割线严格一致 */
  348. margin-left: 27px !important; /* 精确匹配分割线左侧偏移 */
  349. box-sizing: border-box !important; /* 确保宽度计算不包含padding */
  350. position: relative !important; /* 建立稳定的定位基准 */
  351. left: 0 !important; /* 清除可能的定位偏移 */
  352. top: 0 !important;
  353. }
  354. /* 确保标题文字的左侧起点完全一致 */
  355. .form-section::first-letter {
  356. margin-left: 0 !important; /* 首个字符(対、集)左侧无额外间距 */
  357. }
  358. /* 公共区域标题样式 - 深层选择器修改子组件样式 */
  359. :deep(.public-range-title) {
  360. text-align: left !important; /* 强制左对齐 */
  361. padding-left: 20px; /* 左侧内边距 */
  362. }
  363. /* 核心:将FC、業種、エリア三组控件整体向左平移 */
  364. :deep(.public-range-container) {
  365. /* 计算平移量:原有左内边距(60px) - 目标左偏移(27px) = 需向左移动33px */
  366. padding-left: 27px !important; /* 直接设置目标左内边距为27px */
  367. margin-left: 0 !important;
  368. }
  369. .content-group {
  370. flex: 1; /* 占满剩余宽度 */
  371. margin: 0 !important; /* 清除element-ui默认margin */
  372. padding-left: 20px !important; /* 统一内容区左侧缩进,修复对齐问题 */
  373. }
  374. /* 4. 垂直排列的单选组(月指定/年度指定) */
  375. .radio-group-vertical {
  376. display: flex;
  377. flex-direction: column;
  378. gap: 15px; /* 月指定与年度指定的垂直间距 */
  379. padding: 0 !important;
  380. margin: 0 !important;
  381. }
  382. /* 5. 水平排列的单选组(集計種別) */
  383. .radio-group-horizontal {
  384. display: flex;
  385. gap: 20px; /* 集計種別选项之间的水平间距 */
  386. align-items: center;
  387. flex-wrap: wrap; /* 确保小屏幕下不溢出 */
  388. margin: 0 !important; /* 清除可能的默认margin */
  389. padding: 0 !important;
  390. }
  391. /* 6. 所有单选框统一样式(核心:确保左侧对齐) */
  392. .common-radio {
  393. width: 100px !important; /* 固定宽度,确保文字左侧起点对齐 */
  394. flex-shrink: 0 !important; /* 禁止压缩,保持宽度稳定 */
  395. margin: 0 !important; /* 清除默认margin */
  396. padding: 0 0 0 20px !important; /* 为单选框圆圈预留空间 */
  397. text-align: left !important; /* 文字左对齐,确保起始点一致 */
  398. position: relative !important; /* 建立定位基准 */
  399. }
  400. /* 单选框圆圈(input)定位 - 核心对齐控制 */
  401. :deep(.common-radio .el-radio__input) {
  402. position: absolute !important; /* 脱离文档流,基于父元素定位 */
  403. left: 0 !important; /* 固定在左侧,所有单选框left=0对齐 */
  404. top: 50% !important; /* 垂直居中 */
  405. transform: translateY(-50%) !important; /* 精确垂直居中 */
  406. margin: 0 !important; /* 清除默认margin */
  407. }
  408. /* 单选框文字与圆圈的间距控制 */
  409. :deep(.common-radio .el-radio__label) {
  410. padding-left: 5px !important; /* 文字与圆圈的固定间距 */
  411. margin: 0 !important;
  412. }
  413. /* 只调整月指定区域的年份下拉框,适配年度指定的样式 */
  414. .monthly-section .date-select {
  415. /* 复制年度指定下拉框的宽度 */
  416. width: 100px !important;
  417. min-width: 100px !important;
  418. max-width: 100px !important;
  419. }
  420. /* 调整月指定单选框的宽度,与年度指定的单选框保持一致的左侧空间 */
  421. .monthly-section .common-radio {
  422. /* 与年度指定的单选框宽度保持一致 */
  423. width: 80px !important;
  424. }
  425. /* 确保月指定的日期组与年度指定的布局对齐 */
  426. .monthly-section .date-group {
  427. /* 消除可能的额外边距,与年度指定保持一致 */
  428. margin: 0 !important;
  429. padding: 0 !important;
  430. }
  431. .monthly-header {
  432. display: flex;
  433. align-items: center;
  434. gap: 8px;
  435. width: 100%;
  436. }
  437. .date-groups-wrapper {
  438. display: flex;
  439. align-items: center;
  440. gap: 8px; /* 年和月下拉列表之间的间隔 */
  441. flex-wrap: wrap; /* 允许在小屏幕上折行 */
  442. }
  443. /* 7. 单选按钮与下拉框的组合容器 - 确保不折行 */
  444. .radio-item {
  445. display: flex;
  446. align-items: center;
  447. gap: 8px;
  448. width: 100%;
  449. min-width: 0;
  450. flex-wrap: nowrap; /* 强制不换行 */
  451. }
  452. /* 下拉框容器 - 自适应剩余宽度,与单选框同行 */
  453. .annual-row {
  454. flex: 1; /* 占据剩余宽度 */
  455. min-width: 0; /* 允许收缩 */
  456. margin: 0; /* 清除默认边距 */
  457. padding: 2px 0; /* 减少内边距,节省空间 */
  458. }
  459. /* 日期项容器 - 每个整体作为独立项 */
  460. .date-group {
  461. display: flex; /* 内部元素(下拉框+标签)不折行 */
  462. align-items: center;
  463. gap: 6px; /* 下拉框与标签的间距 */
  464. white-space: nowrap; /* 防止内部元素折行 */
  465. }
  466. /* 日期选择器样式 - 年下拉框(强制适配宽度) */
  467. .date-select {
  468. width: 100px !important;
  469. min-width: 80px !important;
  470. max-width: 120px !important;
  471. padding: 0 8px !important; /* 减少内边距 */
  472. }
  473. /* 日期选择器样式 - 月下拉框 */
  474. .date-select-small {
  475. width: 80px !important;
  476. min-width: 60px !important;
  477. max-width: 90px !important;
  478. padding: 0 8px !important; /* 减少内边距 */
  479. }
  480. /* 日期标签样式 - "年"和"月"文字 */
  481. .date-label {
  482. font-size: 14px; /* 字体大小 */
  483. color: #666; /* 文字颜色 */
  484. white-space: nowrap; /* 禁止文字折行 */
  485. }
  486. /* 分隔线样式 - 区域分隔 */
  487. .divider {
  488. height: 1px; /* 高度 */
  489. background-color: #FF0066; /* 蓝色背景 */
  490. margin: 10px 27px; /* 上下外边距 */
  491. width: calc(100% - 27px) !important; /* 宽度自适应,保持右侧对齐 */
  492. box-sizing: border-box !important;
  493. }
  494. /* 按钮区域样式 - 集計按钮容器 */
  495. .btn-section {
  496. padding-left: calc(27px + 80px + 10px); /* 动态计算与标题对齐,27px偏移+80px标题宽度+10px间距 */
  497. margin-top: 25px; /* 顶部外边距 */
  498. display: flex; /* 添加Flex布局 */
  499. gap: 10px; /* 按钮间距 */
  500. align-items: center; /* 垂直居中对齐 */
  501. justify-content: start; /* 子元素左对齐 */
  502. flex-wrap: wrap; /* 允许在极小屏幕下按钮换行 */
  503. }
  504. /* 操作按钮样式 - 集計 */
  505. .btn-aggregation {
  506. background-color: #FF0066; /* 蓝色背景 */
  507. color: white; /* 白色文字 */
  508. padding: 6px 10px; /* 内边距 */
  509. box-sizing: border-box; /* 确保padding不影响宽度计算 */
  510. margin-left: 0; /* 左侧外边距 */
  511. white-space: nowrap; /* 禁止文字折行 */
  512. width: 120px; /* 固定基础宽度 */
  513. height: 32px; /* 统一高度 */
  514. }
  515. /* 操作按钮样式 - リセット */
  516. .btn-reset{
  517. background-color: #FF0066; /* 蓝色背景 */
  518. color: white; /* 白色文字 */
  519. padding: 6px 10px; /* 内边距 */
  520. box-sizing: border-box; /* 确保padding不影响宽度计算 */
  521. margin-left: 10px; /* 左侧外边距 */
  522. white-space: nowrap; /* 禁止文字折行 */
  523. width: 120px; /* 固定基础宽度 */
  524. height: 32px; /* 统一高度 */
  525. }
  526. /* 年度指定整体容器样式 - 核心不折行控制 */
  527. .annual-group {
  528. display: flex;
  529. align-items: center;
  530. gap: 0;
  531. width: 100%;
  532. flex-wrap: nowrap; /* 关键修改:强制不折行 */
  533. min-width: 300px; /* 确保有足够宽度容纳内容 */
  534. }
  535. /* 年度指定单选框样式 - 统一宽度 */
  536. .annual-group .common-radio {
  537. width: 100px !important; /* 与其他单选框保持一致 */
  538. min-width: 80px;
  539. flex-shrink: 0;
  540. }
  541. /* 年度下拉框容器样式 - 优化宽度分配 */
  542. .annual-group .annual-row {
  543. flex: 1; /* 占据剩余空间 */
  544. min-width: 0;
  545. padding: 0 !important;
  546. margin: 0 !important;
  547. }
  548. /* 日期组合容器样式 */
  549. .annual-group .date-group {
  550. display: flex;
  551. align-items: center;
  552. gap: 100px;
  553. }
  554. /* 响应式样式 - 大屏幕 (>1023px) */
  555. @media (min-width: 1024px) {
  556. .section-container {
  557. gap: 15px; /* 增大间距提升可读性 */
  558. }
  559. .radio-group-horizontal {
  560. gap: 30px; /* 增大选项间距 */
  561. }
  562. }
  563. /* 响应式样式 - 平板端 (768px-1023px) */
  564. @media (min-width: 768px) and (max-width: 1023px) {
  565. .btn-section {
  566. padding-left: calc(15px + 70px + 10px); /* 与标题对齐,动态计算 */
  567. }
  568. /* 保持与移动端相同的宽度 */
  569. .btn-aggregation, .btn-reset {
  570. width: 120px; /* 统一宽度 */
  571. }
  572. /* 平板设备适配 */
  573. .radio-item, .monthly-header {
  574. gap: 5px; /* 进一步缩小间距 */
  575. }
  576. .date-group {
  577. gap: 4px;
  578. }
  579. .form-section {
  580. margin-left: 15px !important; /* 左对齐标题,节省空间 */
  581. width: 70px !important; /* 缩短标题宽度,为内容区让空间 */
  582. font-size: 13px !important; /* 标题文字微调 */
  583. }
  584. }
  585. /* 响应式样式 - 移动端 (<768px) */
  586. @media (max-width: 767px) {
  587. .btn-section {
  588. padding-left: 20px; /* 调整左侧内边距 */
  589. justify-content: flex-start; /* 左对齐避免溢出 */
  590. }
  591. /* 保持按钮宽度一致 */
  592. .btn-aggregation, .btn-reset {
  593. width: 120px;
  594. flex-shrink: 0;
  595. }
  596. .annual-row {
  597. margin-left: 20px;
  598. width: calc(100% - 40px);
  599. }
  600. .form-section {
  601. width: 80px !important;
  602. white-space: nowrap;
  603. margin-left: 0;
  604. padding-left: 20px;
  605. }
  606. .date-group {
  607. align-items: center;
  608. gap: 4px; /* 缩小间距 */
  609. }
  610. /* 年下拉框区域固定宽度,防止被挤压 */
  611. .date-group > .date-select:first-child {
  612. width: 90px !important;
  613. min-width: 90px !important;
  614. max-width: 90px !important;
  615. flex-shrink: 0;
  616. }
  617. /* 年标签与年下拉框绑定,不单独折行 */
  618. .date-group > .date-label:nth-child(2) {
  619. margin-right: 8px;
  620. flex-shrink: 0;
  621. font-size: 13px; /* 日期标签字体微调 */
  622. }
  623. .radio-group-horizontal {
  624. gap: 10px; /* 缩小水平间距 */
  625. }
  626. /* 在小屏幕上,让日期组垂直排列 */
  627. .date-groups-wrapper {
  628. flex-direction: column;
  629. align-items: flex-start;
  630. width: 100%;
  631. padding-left: 0 !important;
  632. }
  633. /* 垂直单选组样式调整 */
  634. .radio-group-vertical {
  635. padding-left: 0 !important;
  636. }
  637. /* 年度指定整体容器样式 - 增强不折行控制 */
  638. .annual-group {
  639. display: flex;
  640. align-items: center;
  641. gap: 0px !important; /* 仅减少年度指定单选框与下拉列表的间隔从8px减少到4px */
  642. width: 100%;
  643. flex-wrap: nowrap; /* 关键修改:强制不折行 */
  644. min-width: 300px; /* 确保有足够宽度容纳内容 */
  645. overflow-x: auto; /* 当内容过宽时显示横向滚动条 */
  646. padding-bottom: 5px; /* 为滚动条预留空间 */
  647. scrollbar-width: thin; /* 优化滚动条样式 */
  648. }
  649. /* 防止滚动时影响其他元素 */
  650. .annual-group::-webkit-scrollbar {
  651. height: 4px;
  652. }
  653. /* 调整单选框宽度,为下拉框留出更多空间 */
  654. .annual-group .common-radio {
  655. width: 90px !important;
  656. min-width: 90px !important;
  657. padding-right: 0 !important; /* 移除右侧内边距 */
  658. }
  659. }
  660. </style>