sumSettings.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  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. .content-group {
  364. flex: 1; /* 占满剩余宽度 */
  365. margin: 0 !important; /* 清除element-ui默认margin */
  366. padding-left: 0 !important; /* 统一内容区左侧缩进,修复对齐问题 */
  367. }
  368. /* 4. 垂直排列的单选组(月指定/年度指定) */
  369. .radio-group-vertical {
  370. display: flex;
  371. flex-direction: column;
  372. gap: 15px; /* 月指定与年度指定的垂直间距 */
  373. padding: 0 !important;
  374. margin: 0 !important;
  375. }
  376. /* 5. 水平排列的单选组(集計種別) */
  377. .radio-group-horizontal {
  378. display: flex;
  379. gap: 20px; /* 集計種別选项之间的水平间距 */
  380. align-items: center;
  381. flex-wrap: wrap; /* 确保小屏幕下不溢出 */
  382. margin: 0 !important; /* 清除可能的默认margin */
  383. padding: 0 !important;
  384. }
  385. /* 6. 所有单选框统一样式(核心:确保左侧对齐) */
  386. .common-radio {
  387. width: 100px !important; /* 固定宽度,确保文字左侧起点对齐 */
  388. flex-shrink: 0 !important; /* 禁止压缩,保持宽度稳定 */
  389. margin: 0 !important; /* 清除默认margin */
  390. padding: 0 0 0 20px !important; /* 为单选框圆圈预留空间 */
  391. text-align: left !important; /* 文字左对齐,确保起始点一致 */
  392. position: relative !important; /* 建立定位基准 */
  393. }
  394. /* 单选框圆圈(input)定位 - 核心对齐控制 */
  395. :deep(.common-radio .el-radio__input) {
  396. position: absolute !important; /* 脱离文档流,基于父元素定位 */
  397. left: 0 !important; /* 固定在左侧,所有单选框left=0对齐 */
  398. top: 50% !important; /* 垂直居中 */
  399. transform: translateY(-50%) !important; /* 精确垂直居中 */
  400. margin: 0 !important; /* 清除默认margin */
  401. }
  402. /* 单选框文字与圆圈的间距控制 */
  403. :deep(.common-radio .el-radio__label) {
  404. padding-left: 5px !important; /* 文字与圆圈的固定间距 */
  405. margin: 0 !important;
  406. }
  407. /* 只调整月指定区域的年份下拉框,适配年度指定的样式 */
  408. .monthly-section .date-select {
  409. /* 复制年度指定下拉框的宽度 */
  410. width: 100px !important;
  411. min-width: 100px !important;
  412. max-width: 100px !important;
  413. }
  414. /* 调整月指定单选框的宽度,与年度指定的单选框保持一致的左侧空间 */
  415. .monthly-section .common-radio {
  416. /* 与年度指定的单选框宽度保持一致 */
  417. width: 80px !important;
  418. }
  419. /* 确保月指定的日期组与年度指定的布局对齐 */
  420. .monthly-section .date-group {
  421. /* 消除可能的额外边距,与年度指定保持一致 */
  422. margin: 0 !important;
  423. padding: 0 !important;
  424. }
  425. .date-groups-wrapper {
  426. display: flex;
  427. align-items: center;
  428. gap: 8px; /* 年和月下拉列表之间的间隔 */
  429. flex-wrap: wrap; /* 允许在小屏幕上折行 */
  430. }
  431. /* 7. 单选按钮与下拉框的组合容器 - 确保不折行 */
  432. .radio-item {
  433. display: flex;
  434. align-items: center;
  435. gap: 8px;
  436. width: 100%;
  437. min-width: 0;
  438. flex-wrap: nowrap; /* 强制不换行 */
  439. }
  440. /* 日期项容器 - 每个整体作为独立项 */
  441. .date-group {
  442. display: flex; /* 内部元素(下拉框+标签)不折行 */
  443. align-items: center;
  444. gap: 0; /* 下拉框与标签的间距 */
  445. white-space: nowrap; /* 防止内部元素折行 */
  446. }
  447. /* 日期选择器样式 - 年下拉框(强制适配宽度) */
  448. .date-select {
  449. width: 100px !important;
  450. min-width: 80px !important;
  451. max-width: 120px !important;
  452. padding: 0 8px !important; /* 减少内边距 */
  453. }
  454. /* 日期选择器样式 - 月下拉框 */
  455. .date-select-small {
  456. width: 80px !important;
  457. min-width: 60px !important;
  458. max-width: 90px !important;
  459. padding: 0 8px !important; /* 减少内边距 */
  460. }
  461. /* 日期标签样式 - "年"和"月"文字 */
  462. .date-label {
  463. font-size: 14px; /* 字体大小 */
  464. color: #666; /* 文字颜色 */
  465. white-space: nowrap; /* 禁止文字折行 */
  466. }
  467. /* 分隔线样式 - 区域分隔 */
  468. .divider {
  469. height: 1px; /* 高度 */
  470. background-color: #FF0066; /* 蓝色背景 */
  471. margin: 10px 27px; /* 上下外边距 */
  472. width: calc(100% - 27px) !important; /* 宽度自适应,保持右侧对齐 */
  473. box-sizing: border-box !important;
  474. }
  475. /* 按钮区域样式 - 集計按钮容器 */
  476. .btn-section {
  477. padding-left: calc(27px + 80px + 10px); /* 动态计算与标题对齐,27px偏移+80px标题宽度+10px间距 */
  478. margin-top: 25px; /* 顶部外边距 */
  479. display: flex; /* 添加Flex布局 */
  480. gap: 10px; /* 按钮间距 */
  481. align-items: center; /* 垂直居中对齐 */
  482. justify-content: start; /* 子元素左对齐 */
  483. flex-wrap: wrap; /* 允许在极小屏幕下按钮换行 */
  484. }
  485. /* 操作按钮样式 - 集計 */
  486. .btn-aggregation {
  487. background-color: #FF0066; /* 蓝色背景 */
  488. color: white; /* 白色文字 */
  489. padding: 6px 10px; /* 内边距 */
  490. box-sizing: border-box; /* 确保padding不影响宽度计算 */
  491. margin-left: 0; /* 左侧外边距 */
  492. white-space: nowrap; /* 禁止文字折行 */
  493. width: 120px; /* 固定基础宽度 */
  494. height: 32px; /* 统一高度 */
  495. }
  496. /* 操作按钮样式 - リセット */
  497. .btn-reset{
  498. background-color: #FF0066; /* 蓝色背景 */
  499. color: white; /* 白色文字 */
  500. padding: 6px 10px; /* 内边距 */
  501. box-sizing: border-box; /* 确保padding不影响宽度计算 */
  502. margin-left: 10px; /* 左侧外边距 */
  503. white-space: nowrap; /* 禁止文字折行 */
  504. width: 120px; /* 固定基础宽度 */
  505. height: 32px; /* 统一高度 */
  506. }
  507. /* 年度指定整体容器样式 - 核心不折行控制 */
  508. .annual-group {
  509. display: flex;
  510. align-items: center;
  511. gap: 0 !important; /* 🔴 强制消除单选框与下拉列表间距 */
  512. width: 100%;
  513. flex-wrap: nowrap; /* 关键修改:强制不折行 */
  514. min-width: 300px; /* 确保有足够宽度容纳内容 */
  515. }
  516. /* 年度指定单选框样式 - 统一宽度 */
  517. .annual-group .common-radio {
  518. width: 80px !important; /* 与其他单选框保持一致 */
  519. min-width: 80px;
  520. flex-shrink: 0;
  521. }
  522. /* 年度下拉框容器样式 - 优化宽度分配 */
  523. .annual-group .annual-row {
  524. flex: 1; /* 占据剩余空间 */
  525. min-width: 0;
  526. padding: 0 !important;
  527. margin: 0 !important;
  528. }
  529. /* 日期组合容器样式 */
  530. .annual-group .date-group {
  531. display: flex;
  532. align-items: center;
  533. gap: 0;
  534. }
  535. /* 下拉框容器 - 自适应剩余宽度,与单选框同行 */
  536. .annual-row {
  537. flex: 1; /* 占据剩余宽度 */
  538. min-width: 0; /* 允许收缩 */
  539. margin: 0; /* 清除默认边距 */
  540. padding: 2px 0; /* 减少内边距,节省空间 */
  541. }
  542. /* 响应式样式 - 大屏幕 (>1023px) */
  543. @media (min-width: 1024px) {
  544. .section-container {
  545. gap: 15px; /* 增大间距提升可读性 */
  546. }
  547. .radio-group-horizontal {
  548. gap: 30px; /* 增大选项间距 */
  549. }
  550. }
  551. /* 响应式样式 - 平板端 (768px-1023px) */
  552. @media (min-width: 768px) and (max-width: 1023px) {
  553. .btn-section {
  554. padding-left: calc(15px + 70px + 10px); /* 与标题对齐,动态计算 */
  555. }
  556. /* 保持与移动端相同的宽度 */
  557. .btn-aggregation, .btn-reset {
  558. width: 120px; /* 统一宽度 */
  559. }
  560. .date-group {
  561. gap: 4px;
  562. }
  563. .form-section {
  564. margin-left: 15px !important; /* 左对齐标题,节省空间 */
  565. width: 70px !important; /* 缩短标题宽度,为内容区让空间 */
  566. font-size: 14px !important; /* 标题文字微调 */
  567. }
  568. /* 仅调整年度指定行的布局,与月指定对齐 */
  569. .annual-group {
  570. gap: 5px !important; /* 与月指定的radio-item保持相同间距 */
  571. }
  572. /* 年度下拉框容器与月指定的date-group保持一致的左偏移 */
  573. .annual-group .date-group {
  574. margin-left: 0 !important; /* 清除额外左偏移 */
  575. }
  576. /* 确保年度下拉框与年下拉框宽度/位置一致 */
  577. .annual-group .date-select {
  578. width: 100px !important; /* 与月指定的年下拉框宽度完全一致 */
  579. margin-left: 0 !important; /* 清除默认左外边距 */
  580. }
  581. }
  582. /* 响应式样式 - 移动端 (<768px) */
  583. @media (max-width: 767px) {
  584. .btn-section {
  585. padding-left: 20px; /* 调整左侧内边距 */
  586. justify-content: flex-start; /* 左对齐避免溢出 */
  587. }
  588. /* 保持按钮宽度一致 */
  589. .btn-aggregation, .btn-reset {
  590. width: 120px;
  591. flex-shrink: 0;
  592. }
  593. /* 仅修改年度指定行的对齐样式 */
  594. .annual-row {
  595. margin-left: 0 !important; /* 移除20px左偏移,与月指定的date-groups-wrapper对齐 */
  596. width: auto !important; /* 取消强制宽度计算,跟随父容器 */
  597. }
  598. .form-section {
  599. width: 80px !important; /* 保持宽度为80px */
  600. white-space: nowrap !important; /* 保留并加强优先级,防止禁止换行 */
  601. margin-left: 0 !important; /* 清除左侧外边距干扰 */
  602. padding-left: 20px !important; /* 固定左侧内边距 */
  603. min-width: 80px !important; /* 新增:强制最小宽度为80px */
  604. max-width: 80px !important; /* 新增:强制最大宽度为80px */
  605. flex-shrink: 0 !important; /* 新增:禁止宽度被压缩 */
  606. box-sizing: content-box !important; /* 新增:确保宽度计算不包含内边距 */
  607. }
  608. .date-group {
  609. align-items: center;
  610. gap: 0; /* 缩小间距 */
  611. }
  612. /* 年度下拉框区域固定宽度,防止被挤压 */
  613. .date-group > .date-select:first-child {
  614. width: 100px !important;
  615. min-width: 100px !important;
  616. max-width: 100px !important;
  617. flex-shrink: 0;
  618. }
  619. /* 年标签与年下拉框绑定,不单独折行 */
  620. .date-group > .date-label:nth-child(2) {
  621. margin-right: 8px;
  622. flex-shrink: 0;
  623. font-size: 13px; /* 日期标签字体微调 */
  624. }
  625. .radio-group-horizontal {
  626. gap: 10px; /* 缩小水平间距 */
  627. }
  628. /* 在小屏幕上,让日期组垂直排列 */
  629. .date-groups-wrapper {
  630. flex-direction: column;
  631. align-items: flex-start;
  632. width: 100%;
  633. padding-left: 0 !important;
  634. }
  635. /* 垂直单选组样式调整 */
  636. .radio-group-vertical {
  637. padding-left: 0 !important;
  638. }
  639. /* 年度指定整体容器样式 - 增强不折行控制 */
  640. .annual-group {
  641. display: flex;
  642. align-items: center;
  643. gap: 0 !important; /* 仅减少年度指定单选框与下拉列表的间隔从8px减少到4px */
  644. width: 100%;
  645. flex-wrap: nowrap; /* 关键修改:强制不折行 */
  646. min-width: 300px; /* 确保有足够宽度容纳内容 */
  647. overflow-x: auto; /* 当内容过宽时显示横向滚动条 */
  648. padding-bottom: 5px; /* 为滚动条预留空间 */
  649. scrollbar-width: thin; /* 优化滚动条样式 */
  650. }
  651. /* 年度下拉框与年下拉框保持完全一致的位置 */
  652. .annual-group .date-group {
  653. margin-left: 0 !important; /* 与月指定的date-group左对齐 */
  654. gap: 0 !important; /* 与月指定的date-group内部间距一致 */
  655. }
  656. /* 年度下拉框宽度/位置与年下拉框完全匹配 */
  657. .annual-group .date-select {
  658. width: 100px !important; /* 与月指定的年下拉框宽度相同 */
  659. margin-left: 0 !important; /* 清除默认左外边距 */
  660. }
  661. /* 防止滚动时影响其他元素 */
  662. .annual-group::-webkit-scrollbar {
  663. height: 4px;
  664. }
  665. /* 调整单选框宽度,为下拉框留出更多空间 */
  666. .annual-group .common-radio {
  667. width: 80px !important;
  668. min-width: 80px !important;
  669. padding-right: 0 !important; /* 移除右侧内边距 */
  670. }
  671. /* 关键修正:让月指定的年下拉框容器与年度下拉框对齐 */
  672. .monthly-section .date-groups-wrapper {
  673. margin-left: 0 !important; /* 清除可能的默认左偏移 */
  674. padding-left: 0 !important;
  675. }
  676. /* 强制月指定的年下拉框与年度下拉框左对齐基准一致 */
  677. .monthly-section .date-group:first-child {
  678. margin-left: 0 !important; /* 与年度下拉框的date-group保持相同左偏移 */
  679. position: relative;
  680. left: 0 !important; /* 消除任何隐性定位偏移 */
  681. }
  682. /* 月下拉列表行与年下拉列表对齐 */
  683. .monthly-section .date-groups-wrapper .date-group:nth-child(2) {
  684. /* 与年下拉列表的单选框宽度+间距保持一致,确保左对齐 */
  685. margin-left: 80px !important; /* 匹配"月指定"单选框的宽度(80px),使月下拉列表左对齐基准与年下拉列表一致 */
  686. margin-top: 8px; /* 可选:添加微小间距,避免与年下拉列表过于拥挤 */
  687. }
  688. }
  689. </style>