sumSettings.vue 32 KB

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