detail.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. <!-- アンケート照会 -->
  2. <template>
  3. <div class="app-container">
  4. <p>アンケートフォーム作成</p>
  5. <div class="form-container">
  6. <el-form ref="queryRef"
  7. :model="queryParams"
  8. label-width="140px"
  9. label-position="top">
  10. <el-form-item label="タイトル" style="margin-left: 30px;margin-top: -35px;">
  11. <el-input v-model="queryParams.title" class="table-input" :maxlength="100" ></el-input>
  12. </el-form-item>
  13. <el-form-item label="説明文" style="margin-left: 30px;margin-top: -15px;">
  14. <el-input
  15. v-model="queryParams.description"
  16. class="table-input"
  17. :maxlength="255"
  18. type="textarea"
  19. :rows="2"
  20. ></el-input>
  21. </el-form-item>
  22. <div style=" padding-left: 29px;margin-top: -15px;">公開範囲</div>
  23. <el-form-item label="FC" prop="brandCode" style="margin-left: 60px; display: flex; align-items: center; margin-top: 10px;">
  24. <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 20px;"></div>
  25. <el-checkbox-group v-model="queryParams.brandCode">
  26. <el-checkbox v-for="dict in yamada_fc_brand" :key="dict.value" :label="dict.value" style="margin-right: 60px;">{{ dict.label }}</el-checkbox>
  27. </el-checkbox-group>
  28. </el-form-item>
  29. <el-form-item label="業種" prop="businessTypeCode" class="el-form-item__businessTypeCode">
  30. <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 14px;"></div>
  31. <el-checkbox-group v-model="queryParams.businessTypeCode">
  32. <el-checkbox v-for="dict in yamada_business_type" :key="dict.value" :label="dict.value" style="margin-right: 32px;">{{ dict.label }}</el-checkbox>
  33. </el-checkbox-group>
  34. </el-form-item>
  35. <div class="container">
  36. <div class="area-header-row">
  37. <h1 class="area-title">エリア</h1>
  38. <RegionTree class="region-tree-container" :treeData="regionTree" @check-change="handleCheckChange" />
  39. </div>
  40. </div>
  41. <!-- 公開期間 -->
  42. <div class="form-section">公開期間</div>
  43. <!-- 垂直排列的单选框组 -->
  44. <el-form-item class="radio-group-vertical">
  45. <div class="radio-container"> <!-- 新增容器用于控制布局 -->
  46. <el-radio
  47. label="immediate"
  48. v-model="queryParams.releaseType"
  49. class="vertical-radio"
  50. >即時公開</el-radio>
  51. <el-radio
  52. label="scheduled"
  53. v-model="queryParams.releaseType"
  54. class="scheduled-radio"
  55. >期間指定</el-radio>
  56. <div class="scheduled-row">
  57. <!-- 期間指定时显示的日期选择(同行) -->
  58. <div class="date-group" >
  59. <!-- 日期选择器代码保持不变 -->
  60. <el-select v-model="queryParams.startYear" placeholder="" class="date-select" :disabled="queryParams.releaseType === 'immediate'" @change="formatDate">
  61. <el-option v-for="year in years" :key="year" :label="year" :value="year"></el-option>
  62. </el-select>
  63. <span class="date-label">年</span>
  64. <el-select
  65. v-model="queryParams.startMonth"
  66. placeholder=""
  67. class="date-select-small"
  68. @change="formatDate"
  69. :disabled="queryParams.releaseType === 'immediate'"
  70. >
  71. <el-option
  72. v-for="month in months"
  73. :key="month"
  74. :label="month"
  75. :value="month"
  76. ></el-option>
  77. </el-select>
  78. <span class="date-label">月</span>
  79. <el-select
  80. v-model="queryParams.startDay"
  81. placeholder=""
  82. class="date-select-small"
  83. @change="formatDate"
  84. :disabled="queryParams.releaseType === 'immediate'"
  85. >
  86. <el-option
  87. v-for="day in days"
  88. :key="day"
  89. :label="day"
  90. :value="day"
  91. ></el-option>
  92. </el-select>
  93. <span class="date-label">日</span>
  94. <span class="date-label">~</span>
  95. <el-select
  96. v-model="queryParams.endYear"
  97. placeholder=""
  98. class="date-select"
  99. @change="formatDate"
  100. :disabled="queryParams.releaseType === 'immediate'"
  101. >
  102. <el-option
  103. v-for="year in years"
  104. :key="year"
  105. :label="year"
  106. :value="year"
  107. ></el-option>
  108. </el-select>
  109. <span class="date-label">年</span>
  110. <el-select
  111. v-model="queryParams.endMonth"
  112. placeholder=""
  113. class="date-select-small"
  114. @change="formatDate"
  115. :disabled="queryParams.releaseType === 'immediate'"
  116. >
  117. <el-option
  118. v-for="month in months"
  119. :key="month"
  120. :label="month"
  121. :value="month"
  122. ></el-option>
  123. </el-select>
  124. <span class="date-label">月</span>
  125. <el-select
  126. v-model="queryParams.endDay"
  127. placeholder=""
  128. class="date-select-small"
  129. @change="formatDate"
  130. :disabled="queryParams.releaseType === 'immediate'"
  131. >
  132. <el-option
  133. v-for="day in days"
  134. :key="day"
  135. :label="day"
  136. :value="day"
  137. ></el-option>
  138. </el-select>
  139. <span class="date-label">日</span>
  140. </div>
  141. </div>
  142. </div>
  143. </el-form-item>
  144. <div style="margin-left: 25px; margin-top: 15px;">自由作成フォーム</div>
  145. <div v-for="(question, index) in questions" :key="question.sortOrder" class="question-block">
  146. <el-form-item style="margin-left: 50px; margin-top: 3px;">
  147. <el-button class="append-button" @click="addQuestionAbove(index)">この上に設問を追加</el-button>
  148. </el-form-item>
  149. <el-form-item style="margin-left: 55px;">
  150. <template #label>
  151. <div style="display: flex; align-items: center;margin-top: -20px">
  152. <span style="color: #606266; font-weight: bold">設問文</span>
  153. <span style="color: #666; margin-left: 8px;">: {{ question.sortOrder }}</span>
  154. </div>
  155. </template>
  156. <el-input v-model="question.questionText" class="small-input" type="textarea" :rows="2" :maxlength="255"></el-input>
  157. </el-form-item>
  158. <el-form-item label="回答型" style="margin-left: 55px;margin-top: -15px">
  159. <el-radio-group v-model="question.questionType" class="custom-radio-group" @change="handleQuestionTypeChange(question, $event)">
  160. <el-radio label="INPUT_BOX_50">入力ボックス(50文字)</el-radio>
  161. <el-radio label="INPUT_BOX_100">入力ボックス(100文字)</el-radio>
  162. <el-radio label="CHECK_BOX">チェックボックス</el-radio>
  163. <el-radio label="RADIO_BUTTON">ラジオボタン</el-radio>
  164. <el-radio label="NUMBER_SELECT">数字選択(プルダウン)</el-radio>
  165. </el-radio-group>
  166. </el-form-item>
  167. <el-form-item>
  168. <el-input
  169. v-if="question.questionType === 'INPUT_BOX_50'"
  170. v-model="question.input50"
  171. :maxlength="50"
  172. class="textarea-input"
  173. type="textarea"
  174. :rows="2"
  175. ></el-input>
  176. </el-form-item>
  177. <el-form-item>
  178. <el-input
  179. v-if="question.questionType === 'INPUT_BOX_100'"
  180. v-model="question.input100"
  181. :maxlength="100"
  182. class="textarea-input100"
  183. type="textarea"
  184. :rows="2"
  185. ></el-input>
  186. </el-form-item>
  187. <el-form-item v-if="question.questionType === 'CHECK_BOX'" style="margin-left: 125px;margin-top: -60px;">
  188. <!-- 循环渲染每一行:复选框 + 输入框 + 削除按钮 -->
  189. <div
  190. v-for="(item, itemIndex) in question.checkboxItems"
  191. :key="itemIndex"
  192. class="checkbox-row"
  193. >
  194. <div style="display: flex; align-items: center; width: 100%;">
  195. <span style="min-width: 30px; color: #666;">{{ item.sortOrder || itemIndex + 1 }}</span>
  196. <el-checkbox
  197. v-model="item.checked"
  198. class="checkbox-item"
  199. ></el-checkbox>
  200. <el-input
  201. v-model="item.optionText"
  202. class="input-item"
  203. ></el-input>
  204. <el-button
  205. type="text"
  206. class="delete-btn"
  207. @click="deleteCheckboxItem(index, itemIndex)"
  208. >削除</el-button>
  209. </div>
  210. </div>
  211. <el-button
  212. class="append-btn"
  213. @click="addCheckboxItem(index)"
  214. >追加</el-button>
  215. </el-form-item>
  216. <el-form-item v-if="question.questionType === 'RADIO_BUTTON'" style="margin-left: 125px;margin-top: -60px;">
  217. <!-- 循环渲染每一行:单选按钮 + 输入框 + 削除按钮 -->
  218. <div
  219. v-for="(item, itemIndex) in question.radioItems"
  220. :key="itemIndex"
  221. class="checkbox-row"
  222. >
  223. <div style="display: flex; align-items: center; width: 100%;">
  224. <span style="min-width: 30px; color: #666;">{{ item.sortOrder || itemIndex + 1 }}</span>
  225. <el-radio
  226. :label="item.value"
  227. v-model="question.radioGroupValue"
  228. class="hidden-label-radio"
  229. ></el-radio>
  230. <el-input
  231. v-model="item.optionText"
  232. class="radio-item"
  233. ></el-input>
  234. <el-button
  235. type="text"
  236. class="radio-btn"
  237. @click="deleteRadioItem(index, itemIndex)"
  238. >削除</el-button>
  239. </div>
  240. </div>
  241. <el-button
  242. class="append-btn"
  243. @click="addRadioItem(index)"
  244. >追加</el-button>
  245. </el-form-item>
  246. <el-form-item v-if="question.questionType === 'NUMBER_SELECT'" style="margin-left: 120px;margin-top: -50px">
  247. <div style="display: flex; flex-direction: column; gap: 10px; width: 100%;">
  248. <!-- 選択肢部分 -->
  249. <div style="display: flex; align-items: center; width: 100%;">
  250. <span>選択肢</span>
  251. <div style="width: 35px;"></div>
  252. <el-input type="number" min="0" max="999" v-model="question.numberSelectStart" class="mx-2"
  253. style="width: 60px;margin-right: 10px;"></el-input>
  254. <span>~</span>
  255. <el-input type="number" min="0" max="999" v-model="question.numberSelectEnd" class="mx-2" style="width: 60px;margin-left: 10px;"></el-input>
  256. </div>
  257. <!-- 単位部分 -->
  258. <div style="display: flex; align-items: center; width: 100%; flex-wrap: nowrap; white-space: nowrap;">
  259. <span>単位</span>
  260. <el-radio-group v-model="question.unitHas" class="mx-radio-group">
  261. <el-radio :label="false" class="radio-group">なし</el-radio>
  262. <el-radio :label="true">あり</el-radio>
  263. </el-radio-group>
  264. <el-input
  265. v-model="question.questionUnit"
  266. :maxlength="20"
  267. style="width: 85px; margin-left: 10px;"
  268. :disabled="!question.unitHas"
  269. ></el-input>
  270. </div>
  271. </div>
  272. </el-form-item>
  273. <el-form-item style="margin-left: 50px; margin-top: -10px;">
  274. <el-button class="append-button" @click="addQuestionBelow(index)">この下に設問を追加</el-button>
  275. </el-form-item>
  276. </div>
  277. <el-form-item style="text-align: center;">
  278. <el-button @click="Confirm" class="confirm-button">確認</el-button>
  279. </el-form-item>
  280. </el-form>
  281. </div>
  282. </div>
  283. </template>
  284. <script name="SurveyDetail" setup>
  285. import { getRegionTree, addSurvey} from "@/api/fcbi/survey.js"
  286. import { reactive, toRefs, ref, watch, onMounted } from 'vue';
  287. import RegionTree from '../../../components/RegionTree.vue';
  288. import { ElMessage } from "element-plus";
  289. import {useRoute, useRouter} from 'vue-router';
  290. import { useSurveyStore } from '@/store/yamadaStore.js'
  291. const { proxy } = getCurrentInstance();
  292. const surveyStore = useSurveyStore()
  293. // アップロード対象を取得
  294. const { yamada_fc_brand } = proxy.useDict('yamada_fc_brand');
  295. surveyStore.setBrandCode(yamada_fc_brand)
  296. const { yamada_business_type } = proxy.useDict('yamada_business_type');
  297. surveyStore.setBusinessTypeCode(yamada_business_type)
  298. const error = ref('');
  299. const regionTree = ref([]);
  300. const selectedRegions = ref([]);
  301. const router = useRouter();
  302. const route= useRoute()
  303. const surveyId = route.params.surveyId
  304. /**
  305. * コンポーネントの状態を管理するリアクティブオブジェクト
  306. */
  307. const data = reactive({
  308. form: {},
  309. // 検索条件を格納するオブジェクト
  310. queryParams: {
  311. title: null,
  312. description: null,
  313. brandCode: [],
  314. businessTypeCode: [],
  315. releaseType: 'immediate',
  316. startYear: '',
  317. startMonth: '',
  318. startDay: '',
  319. endYear: '',
  320. endMonth: '',
  321. endDay: '',
  322. publicStartTime: '',
  323. publicEndTime: '',
  324. publicMode: '',
  325. insertFlag: 1,
  326. regions: []
  327. },
  328. questions: [
  329. {
  330. questionText: '',
  331. questionType: 'INPUT_BOX_50',
  332. oldQuestionType: 'INPUT_BOX_50',
  333. input50: '',
  334. input100: '',
  335. checkboxItems: [
  336. { checked: false, optionText: '', sortOrder: 1 },
  337. { checked: false, optionText: '', sortOrder: 2 },
  338. { checked: false, optionText: '', sortOrder: 3 },
  339. ],
  340. radioItems: [
  341. { value: 'option1', optionText: '', sortOrder: 1 },
  342. { value: 'option2', optionText: '', sortOrder: 2 },
  343. { value: 'option3', optionText: '', sortOrder: 3 },
  344. ],
  345. radioGroupValue: null,
  346. numberSelectStart: '',
  347. numberSelectEnd: '',
  348. unitHas: false,
  349. sortOrder: 1,
  350. questionUnit: ''
  351. }
  352. ]
  353. });
  354. // 問題を動的に追加する方法
  355. const addQuestionAbove = (index) => {
  356. const newSortOrder = data.questions[index].sortOrder;
  357. // 指定した場所に新しい問題を追加する
  358. data.questions.splice(index, 0, {
  359. questionText: '',
  360. questionType: 'INPUT_BOX_50',
  361. oldQuestionType: 'INPUT_BOX_50',
  362. input50: '',
  363. input100: '',
  364. checkboxItems: [
  365. { checked: false, optionText: '', sortOrder: 1 },
  366. { checked: false, optionText: '', sortOrder: 2 },
  367. { checked: false, optionText: '', sortOrder: 3 },
  368. ],
  369. radioItems: [
  370. { value: 'option1', optionText: '', sortOrder: 1 },
  371. { value: 'option2', optionText: '', sortOrder: 2 },
  372. { value: 'option3', optionText: '', sortOrder: 3 },
  373. ],
  374. radioGroupValue: null,
  375. numberSelectStart: '',
  376. numberSelectEnd: '',
  377. unitHas: false,
  378. sortOrder: newSortOrder,
  379. questionUnit: ''
  380. });
  381. adjustSortOrders(index);
  382. };
  383. const addQuestionBelow = (index) => {
  384. const newSortOrder = data.questions[index].sortOrder + 1;
  385. // 指定した場所の下に新しい問題を追加する
  386. data.questions.splice(index + 1, 0, {
  387. questionText: '',
  388. questionType: 'INPUT_BOX_50',
  389. oldQuestionType: 'INPUT_BOX_50',
  390. input50: '',
  391. input100: '',
  392. checkboxItems: [
  393. { checked: false, optionText: '', sortOrder: 1 },
  394. { checked: false, optionText: '', sortOrder: 2 },
  395. { checked: false, optionText: '', sortOrder: 3 },
  396. ],
  397. radioItems: [
  398. { value: 'option1', optionText: '', sortOrder: 1 },
  399. { value: 'option2', optionText: '', sortOrder: 2 },
  400. { value: 'option3', optionText: '', sortOrder: 3 },
  401. ],
  402. radioGroupValue: null,
  403. numberSelectStart: '',
  404. numberSelectEnd: '',
  405. unitHas: false,
  406. sortOrder: newSortOrder,
  407. questionUnit: ''
  408. });
  409. adjustSortOrders(index + 1);
  410. };
  411. // 問題のソートの調整
  412. const adjustSortOrders = (startIndex) => {
  413. for (let i = startIndex; i < data.questions.length; i++) {
  414. data.questions[i].sortOrder = i + 1;
  415. }
  416. };
  417. // 回答类型切换时重置前一类型的状态
  418. const handleQuestionTypeChange = (question, newType) => {
  419. // 保存旧类型用于判断需要重置哪些数据
  420. const oldType = question.questionType;
  421. // 如果类型未变化,不执行重置
  422. if (oldType === newType) return;
  423. // 根据旧类型重置对应的数据(回到初始状态)
  424. switch (oldType) {
  425. case 'INPUT_BOX_50':
  426. question.input50 = ''; // 重置50文字输入框
  427. break;
  428. case 'INPUT_BOX_100':
  429. question.input100 = ''; // 重置100文字输入框
  430. break;
  431. case 'CHECK_BOX':
  432. // 重置复选框选项(保留3个空选项)
  433. question.checkboxItems = [
  434. { checked: false, optionText: '', sortOrder: 1 },
  435. { checked: false, optionText: '', sortOrder: 2 },
  436. { checked: false, optionText: '', sortOrder: 3 },
  437. ];
  438. break;
  439. case 'RADIO_BUTTON':
  440. // 重置单选按钮选项(保留3个空选项)
  441. question.radioItems = [
  442. { value: 'option1', optionText: '', sortOrder: 1 },
  443. { value: 'option2', optionText: '', sortOrder: 2 },
  444. { value: 'option3', optionText: '', sortOrder: 3 },
  445. ];
  446. question.radioGroupValue = null; // 重置选中状态
  447. break;
  448. case 'NUMBER_SELECT':
  449. // 重置数字选择器相关数据
  450. question.numberSelectStart = '';
  451. question.numberSelectEnd = '';
  452. question.unitHas = false; // 重置单位选择
  453. question.questionUnit = ''; // 重置单位输入框
  454. break;
  455. }
  456. // 更新为新类型(确保数据同步)
  457. question.questionType = newType;
  458. };
  459. //ボタンの追加方法
  460. const addCheckboxItem = (questionIndex) => {
  461. const question = data.questions[questionIndex];
  462. const newSortOrder = question.checkboxItems.length + 1;
  463. question.checkboxItems.push({
  464. checked: false,
  465. optionText: '',
  466. sortOrder: newSortOrder
  467. });
  468. };
  469. const addRadioItem = (questionIndex) => {
  470. const question = data.questions[questionIndex];
  471. const newId = question.radioItems.length + 1;
  472. const newSortOrder = question.radioItems.length + 1;
  473. question.radioItems.push({
  474. value: `option${newId}`,
  475. optionText: '',
  476. sortOrder: newSortOrder
  477. });
  478. };
  479. //消去ボタンの方法
  480. const deleteRadioItem = (questionIndex, itemIndex) => {
  481. const question = data.questions[questionIndex];
  482. if (question.radioItems.length <= 3) {
  483. ElMessage.warning('少なくとも3つの選択肢を残してください');
  484. return;
  485. }
  486. question.radioItems.splice(itemIndex, 1);
  487. question.radioItems.forEach((item, index) => {
  488. item.sortOrder = index + 1;
  489. });
  490. };
  491. const deleteCheckboxItem = (questionIndex, itemIndex) => {
  492. const question = data.questions[questionIndex];
  493. if (question.checkboxItems.length <= 3) {
  494. ElMessage.warning('少なくとも3つの選択肢を残してください');
  495. return;
  496. }
  497. question.checkboxItems.splice(itemIndex, 1);
  498. question.checkboxItems.forEach((item, index) => {
  499. item.sortOrder = index + 1;
  500. });
  501. };
  502. // リアクティブオブジェクトからプロパティを参照可能なオブジェクトに変換
  503. const { queryParams, questions } = toRefs(data);
  504. const currentYear = new Date().getFullYear();
  505. const years = Array.from({ length: 11 }, (_, i) => currentYear + i);
  506. const months = Array.from({ length: 12 }, (_, i) => i + 1);
  507. const days = Array.from({ length: 31 }, (_, i) => i + 1);
  508. const formatDate = () => {
  509. if (queryParams.value.releaseType === 'scheduled') {
  510. const publicStartTime = `${queryParams.value.startYear}-${String(queryParams.value.startMonth).padStart(2, '0')}-${String(queryParams.value.startDay).padStart(2, '0')}`;
  511. const publicEndTime = `${queryParams.value.endYear}-${String(queryParams.value.endMonth).padStart(2, '0')}-${String(queryParams.value.endDay).padStart(2, '0')}`;
  512. queryParams.publicStartTime = publicStartTime;
  513. queryParams.publicEndTime = publicEndTime;
  514. queryParams.publicMode = 'PERIOD';
  515. } else {
  516. queryParams.publicStartTime = '';
  517. queryParams.publicEndTime = '';
  518. queryParams.value.startYear = '';
  519. queryParams.value.startMonth = '';
  520. queryParams.value.startDay = '';
  521. queryParams.value.endYear = '';
  522. queryParams.value.endMonth = '';
  523. queryParams.value.endDay = '';
  524. queryParams.publicMode = 'INSTANT';
  525. }
  526. };
  527. watch(() => queryParams.value.releaseType, () => {
  528. formatDate();
  529. });
  530. watch(regionTree, () => {
  531. if (regionTree.value.length > 0) {
  532. updateSelectedRegions(regionTree.value);
  533. }
  534. }, { deep: true });
  535. watch(
  536. () => data.questions.map(q => q.unitHas),
  537. (newValues, oldValues) => {
  538. data.questions.forEach((question, index) => {
  539. if (newValues[index] === false && oldValues[index] === true) {
  540. question.questionUnit = '';
  541. }
  542. });
  543. },
  544. { deep: true }
  545. );
  546. watch(
  547. () => data.questions.map(q => q.questionType),
  548. (newTypes, oldTypes) => {
  549. data.questions.forEach((question, index) => {
  550. const newType = newTypes[index];
  551. const oldType = oldTypes[index];
  552. // 类型未变化则跳过
  553. if (newType === oldType) return;
  554. // 执行重置逻辑
  555. resetOldTypeData(question, oldType);
  556. // 更新旧类型记录
  557. question.oldQuestionType = oldType;
  558. });
  559. },
  560. { deep: true }
  561. );
  562. // 重置旧类型对应的所有数据
  563. const resetOldTypeData = (question, oldType) => {
  564. switch (oldType) {
  565. case 'INPUT_BOX_50':
  566. question.input50 = ''; // 清空输入
  567. break;
  568. case 'INPUT_BOX_100':
  569. question.input100 = ''; // 清空输入
  570. break;
  571. case 'CHECK_BOX':
  572. // 强制重置为3个空选项
  573. question.checkboxItems = [
  574. { checked: false, optionText: '', sortOrder: 1 },
  575. { checked: false, optionText: '', sortOrder: 2 },
  576. { checked: false, optionText: '', sortOrder: 3 },
  577. ];
  578. break;
  579. case 'RADIO_BUTTON':
  580. // 强制重置为3个空选项,清空选中值
  581. question.radioItems = [
  582. { value: 'option1', optionText: '', sortOrder: 1 },
  583. { value: 'option2', optionText: '', sortOrder: 2 },
  584. { value: 'option3', optionText: '', sortOrder: 3 },
  585. ];
  586. question.radioGroupValue = null;
  587. break;
  588. case 'NUMBER_SELECT':
  589. // 清空所有数字选择器相关数据
  590. question.numberSelectStart = '';
  591. question.numberSelectEnd = '';
  592. question.unitHas = false;
  593. question.questionUnit = '';
  594. break;
  595. }
  596. };
  597. onMounted(async () => {
  598. const response = await getRegionTree();
  599. if (response?.success) {
  600. const regions = response.data.regions || []
  601. regionTree.value = regions
  602. surveyStore.setRegionTree(regions)
  603. if (regionTree.value.length > 0) {
  604. updateSelectedRegions(regionTree.value);
  605. }
  606. } else {
  607. error.value = response?.message || '地域データの取得に失敗しました';
  608. console.error('地域データの取得に失敗しました:', response);
  609. }
  610. });
  611. // 選択した状態変更の処理
  612. const handleCheckChange = (regionId, isChecked) => {
  613. const region = findRegionById(regionTree.value, regionId);
  614. if (region) {
  615. if (isChecked) {
  616. if (!selectedRegions.value.some(r => r.region_code === region.region_code)) {
  617. selectedRegions.value.push({
  618. region_code: region.region_code
  619. });
  620. }
  621. } else {
  622. selectedRegions.value = selectedRegions.value.filter(r => r.region_code !== region.region_code);
  623. }
  624. queryParams.value.regions = selectedRegions.value;
  625. }
  626. };
  627. const findRegionById = (nodes, id) => {
  628. for (const node of nodes) {
  629. if (node.id === id) {
  630. return node;
  631. }
  632. if (node.children && node.children.length > 0) {
  633. const found = findRegionById(node.children, id);
  634. if (found) {
  635. return found;
  636. }
  637. }
  638. }
  639. return null;
  640. };
  641. // 選択したすべての領域を再帰的に更新し、サブノードのみを収集
  642. const updateSelectedRegions = (nodes) => {
  643. selectedRegions.value = [];
  644. // 最上位ノードは親ノードとしてマークされ、再帰的になる
  645. collectSelectedRegions(nodes, true);
  646. queryParams.value.regions = selectedRegions.value;
  647. };
  648. // 選択したすべてのサブエリアオブジェクトを再帰的に収集する(親ノードをスキップ)
  649. const collectSelectedRegions = (nodes, isParent = false) => {
  650. nodes.forEach(node => {
  651. // 親ノード(上位ノード)の場合は、収集をスキップして再帰的な子ノードを続行します
  652. if (isParent) {
  653. if (node.children && node.children.length > 0) {
  654. collectSelectedRegions(node.children, false);
  655. }
  656. return;
  657. }
  658. if (node.checked) {
  659. selectedRegions.value.push({
  660. region_code: node.region_code
  661. });
  662. }
  663. if (node.children && node.children.length > 0) {
  664. collectSelectedRegions(node.children, false);
  665. }
  666. });
  667. };
  668. // 確認按钮点击处理函数
  669. const Confirm = async () => {
  670. if (!queryParams.value.title) {
  671. ElMessage.error('タイトルを入力してください');
  672. return;
  673. }
  674. if (queryParams.value.releaseType === 'scheduled') {
  675. if (!queryParams.value.startYear || !queryParams.value.startMonth || !queryParams.value.startDay ||
  676. !queryParams.value.endYear || !queryParams.value.endMonth || !queryParams.value.endDay) {
  677. ElMessage.error('期間指定の場合は全ての日付を入力してください');
  678. return;
  679. }
  680. }
  681. if (regionTree.value.length > 0) {
  682. updateSelectedRegions(regionTree.value);
  683. }
  684. formatDate();
  685. if (queryParams.value.releaseType === 'scheduled') {
  686. // 确保日期格式正确(YYYY-MM-DD)
  687. const startDate = new Date(queryParams.publicStartTime);
  688. const endDate = new Date(queryParams.publicEndTime);
  689. // 检查日期是否有效且开始时间大于结束时间
  690. if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime()) && startDate > endDate) {
  691. ElMessage.error('公開開始日時は公開終了日時より後に設定できません');
  692. return; // 中断提交
  693. }
  694. }
  695. let hasError = false;
  696. const questionsData = data.questions.map(question => {
  697. if (!question.questionText || question.questionText.trim() === '') {
  698. ElMessage.warning(`設問文${question.sortOrder}の設問文を入力してください`);
  699. hasError = true;
  700. return null;
  701. }
  702. const questionData = {
  703. questionText: question.questionText,
  704. questionType: question.questionType,
  705. input50: question.input50,
  706. input100: question.input100,
  707. numberSelectStart: question.numberSelectStart,
  708. numberSelectEnd: question.numberSelectEnd,
  709. questionUnit: question.questionUnit,
  710. unitHas: question.unitHas,
  711. sortOrder: question.sortOrder
  712. };
  713. // CHECKBOXタイプの問題を処理するオプション
  714. if (question.questionType === 'CHECK_BOX') {
  715. questionData.checkboxItems = question.checkboxItems.map((item, itemIndex) => {
  716. // 新增:校验optionText是否为空
  717. if (!item.optionText || item.optionText.trim() === '') {
  718. ElMessage.warning(`設問文${question.sortOrder}の選択肢${itemIndex + 1}を入力してください`);
  719. hasError = true;
  720. }
  721. return {
  722. optionText: item.optionText,
  723. sortOrder: item.sortOrder
  724. };
  725. });
  726. }
  727. // RADIOタイプの問題を処理するオプション
  728. if (question.questionType === 'RADIO_BUTTON') {
  729. questionData.radioItems = question.radioItems.map((item, itemIndex) => {
  730. // 新增:校验optionText是否为空
  731. if (!item.optionText || item.optionText.trim() === '') {
  732. ElMessage.warning(`設問文${question.sortOrder}の選択肢${itemIndex + 1}を入力してください`);
  733. hasError = true;
  734. }
  735. return {
  736. optionText: item.optionText,
  737. sortOrder: item.sortOrder
  738. };
  739. });
  740. }
  741. // SELECTタイプの問題:動的生成オプション
  742. if (question.questionType === 'NUMBER_SELECT') {
  743. // 先判断start或end是否为空(包括空字符串、纯空格)
  744. const isStartEmpty = !question.numberSelectStart || question.numberSelectStart.trim() === '';
  745. const isEndEmpty = !question.numberSelectEnd || question.numberSelectEnd.trim() === '';
  746. if (isStartEmpty) {
  747. ElMessage.warning(`設問文${question.sortOrder}の選択肢1を入力してください`);
  748. hasError = true;
  749. }
  750. if (isEndEmpty) {
  751. ElMessage.warning(`設問文${question.sortOrder}の選択肢2を入力してください`);
  752. hasError = true;
  753. }
  754. if (isStartEmpty || isEndEmpty) {
  755. return null;
  756. }
  757. const start = parseInt(question.numberSelectStart);
  758. const end = parseInt(question.numberSelectEnd);
  759. if (!isNaN(start) && !isNaN(end) && start <= end) {
  760. // startからendへのオプションの動的生成
  761. questionData.selectOptions = Array.from({ length: end - start + 1 }, (_, i) => ({
  762. optionText: (start + i).toString(),
  763. sortOrder: i + 1
  764. }));
  765. } else {
  766. ElMessage.warning(`SELECTタイプ問題の範囲が無効です:${start}~${end}`);
  767. hasError = true; // 标记有错误
  768. return null; // 临时返回null
  769. }
  770. // 新增:単位「あり」时的输入框空值校验
  771. if (question.unitHas && (!question.questionUnit || question.questionUnit.trim() === '')) {
  772. ElMessage.warning(`設問文${question.sortOrder}の単位を入力してください`);
  773. hasError = true;
  774. return null;
  775. }
  776. }
  777. return questionData;
  778. });
  779. if (hasError) {
  780. return;
  781. }
  782. const validQuestionsData = questionsData.filter(q => q !== null);
  783. const params = {
  784. title: queryParams.value.title,
  785. description: queryParams.value.description,
  786. publicStartTime: queryParams.publicStartTime,
  787. publicEndTime: queryParams.publicEndTime,
  788. publicMode: queryParams.publicMode,
  789. brandCodes: queryParams.value.brandCode,
  790. businessTypeCodes: queryParams.value.businessTypeCode,
  791. numberSelectStart: queryParams.value.numberSelectStart,
  792. numberSelectEnd: queryParams.value.numberSelectEnd,
  793. questions: validQuestionsData,
  794. regions: queryParams.value.regions
  795. };
  796. addSurvey(params).then((res) => {
  797. if (res.success) {
  798. surveyStore.updateFormData({
  799. queryParams: data.queryParams,
  800. questions: data.questions
  801. });
  802. router.push({ name: 'SurveyDecision' });
  803. }
  804. });
  805. };
  806. </script>
  807. <style>
  808. /* 小さい入力框样式(設問文输入框) */
  809. .small-input {
  810. width: 600px;
  811. margin-left: 20px;
  812. margin-top: -10px;
  813. }
  814. /* 表格输入框样式(タイトル和説明文输入框) */
  815. .table-input{
  816. width: 600px;
  817. margin-left: 20px;
  818. margin-top: -5px;
  819. }
  820. /* 确认按钮样式 */
  821. .confirm-button{
  822. background-color: #FF0066;
  823. color: white;
  824. width:300px;
  825. margin-left: 160px;
  826. }
  827. /* 追加按钮样式(この上に設問を追加/この下に設問を追加按钮) */
  828. .append-button{
  829. background-color: #FF0066;
  830. color: white;
  831. }
  832. /* 表单容器整体样式 */
  833. .form-container {
  834. margin-top: 40px;
  835. }
  836. /* 段落样式调整 */
  837. p {
  838. margin-bottom: 10px;
  839. }
  840. /* 表单区块标题样式(公開期間、エリア等标题) */
  841. .form-section {
  842. font-size: 14px;
  843. font-weight: bold;
  844. margin: -10px 0 5px 27px;
  845. }
  846. /* 垂直排列的单选框组容器样式 */
  847. .radio-group-vertical {
  848. margin-left: 65px;
  849. }
  850. /* 移动端垂直排列的单选框组容器样式适配 */
  851. @media (max-width:48em) {
  852. .radio-group-vertical {
  853. margin-left: 7%;
  854. }
  855. }
  856. /* 单选框容器布局控制 */
  857. .radio-container {
  858. display: flex;
  859. flex-direction: column;
  860. gap: 12px;
  861. }
  862. /* 即時公開单选框样式 */
  863. .vertical-radio {
  864. display: flex;
  865. align-items: center;
  866. }
  867. /* 期間指定单选框样式 */
  868. .scheduled-radio{
  869. display: flex;
  870. align-items: center;
  871. margin-top: -15px;
  872. }
  873. /* 日期选择区域布局 */
  874. .scheduled-row {
  875. display: flex;
  876. align-items: center;
  877. margin-left: 88px;
  878. margin-top: -45px;
  879. }
  880. /* 移动端日期选择区域布局适配 */
  881. @media (max-width:48em) {
  882. .scheduled-row {
  883. margin-left: 13%;
  884. width: 80%;
  885. }
  886. }
  887. /* 日期选择器组合样式 */
  888. .date-group {
  889. display: flex;
  890. align-items: center;
  891. gap: 8px;
  892. margin-left: 8px;
  893. }
  894. /* 年份选择器宽度 */
  895. .date-select {
  896. min-width: 80px;
  897. }
  898. /* 月/日选择器宽度 */
  899. .date-select-small {
  900. min-width: 60px;
  901. }
  902. /* 日期单位文本样式(年/月/日) */
  903. .date-label {
  904. font-size: 14px;
  905. color: #666;
  906. }
  907. /* 地区选择区域布局 */
  908. .area-header-row {
  909. display: flex;
  910. align-items: flex-start;
  911. }
  912. /* 地区标题与选择器间距 */
  913. .area-header-row h1 {
  914. margin: -8px 20px 0 0;
  915. }
  916. /* 地区标题样式 */
  917. .area-title {
  918. font-size: 14px;
  919. margin-left: 57px !important;
  920. }
  921. /* 移动端地区标题适配 */
  922. @media (max-width:48em) {
  923. .area-title {
  924. margin-left:13% !important;
  925. }
  926. }
  927. /* 回答类型单选框组布局 */
  928. .custom-radio-group {
  929. display: flex;
  930. flex-wrap: wrap;
  931. align-items: center;
  932. margin-left: 35px !important;
  933. }
  934. /* 前4个回答类型单选框间距 */
  935. .custom-radio-group > .el-radio:nth-child(-n+4) {
  936. margin-right: 20px;
  937. }
  938. /* 第5个回答类型单选框(数字選択)强制换行 */
  939. .custom-radio-group > .el-radio:nth-child(5) {
  940. width: 100%;
  941. }
  942. /* 50文字输入框样式 */
  943. .textarea-input{
  944. width: 600px;
  945. margin-left: 105px !important;
  946. margin-top: -20px;
  947. }
  948. /* 100文字输入框样式 */
  949. .textarea-input100{
  950. width: 600px;
  951. margin-left: 105px !important;
  952. margin-top: -40px;
  953. }
  954. /* 复选框选项行布局 */
  955. .checkbox-row {
  956. display: flex;
  957. flex-direction: row;
  958. align-items: center;
  959. width: 100%;
  960. }
  961. /* 复选框与输入框间距 */
  962. .checkbox-item {
  963. margin-right: 8px;
  964. }
  965. /* 单选框样式(隐藏原生标签) */
  966. .hidden-label-radio {
  967. margin-right: 8px;
  968. }
  969. .hidden-label-radio .el-radio__label {
  970. display: none !important;
  971. }
  972. /* 复选框选项输入框宽度 */
  973. .input-item {
  974. margin: 0 8px;
  975. width: 15%;
  976. }
  977. /* 移动端复选框输入框适配 */
  978. @media (max-width:48em) {
  979. .input-item {
  980. width: 60%;
  981. }
  982. }
  983. /* 单选框选项输入框宽度 */
  984. .radio-item{
  985. width: 15%;
  986. }
  987. /* 移动端单选框输入框适配 */
  988. @media (max-width:48em) {
  989. .radio-item {
  990. width: 60%;
  991. }
  992. }
  993. /* 复选框删除按钮样式 */
  994. .delete-btn {
  995. color: #409eff;
  996. text-decoration: underline;
  997. }
  998. /* 单选框删除按钮样式 */
  999. .radio-btn{
  1000. margin-left: 8px;
  1001. color: #409eff;
  1002. text-decoration: underline;
  1003. }
  1004. /* 选项追加按钮样式 */
  1005. .append-btn {
  1006. display: inline-block;
  1007. background-color: black;
  1008. color: white;
  1009. margin-left:18%;
  1010. }
  1011. /* 移动端追加按钮适配 */
  1012. @media (max-width:48em) {
  1013. .append-btn {
  1014. margin-left:80%;
  1015. }
  1016. }
  1017. /* 地区选择树样式 */
  1018. .region-tree-container {
  1019. margin-top: -12px;
  1020. margin-left: -2px;
  1021. }
  1022. /* 業種复选框组布局 */
  1023. .el-form-item__businessTypeCode{
  1024. margin-left: 55px;
  1025. display: flex;
  1026. align-items: center;
  1027. margin-top: -15px;
  1028. }
  1029. /* 移动端業種复选框组适配 */
  1030. @media (max-width:48em) {
  1031. .el-form-item__businessTypeCode {
  1032. margin-left:13%;
  1033. }
  1034. }
  1035. /* 数字选择器单位单选框组样式 */
  1036. .mx-radio-group{
  1037. margin-left: 60px;
  1038. min-width: 120px;
  1039. }
  1040. /* 移动端数字选择器单位适配 */
  1041. @media (max-width:48em) {
  1042. .mx-radio-group {
  1043. margin-left: 45px;
  1044. min-width: 110px;
  1045. }
  1046. }
  1047. /* 单位单选框间距 */
  1048. .radio-group{
  1049. margin-right: 50px;
  1050. }
  1051. /* 移动端单位单选框间距适配 */
  1052. @media (max-width:48em) {
  1053. .radio-group {
  1054. margin-right: 10px;
  1055. }
  1056. }
  1057. </style>