Browse Source

顧客データ抽出画面作成

quyx@nextosd.com 4 months ago
parent
commit
867ffce6b5

+ 153 - 0
src/components/PublicRange.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="public-range-container">
+    <!-- FC选择 -->
+    <el-form-item
+        label="FC"
+        prop="brandCode"
+        class="fc-form-item"
+    >
+      <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 20px;"></div>
+      <el-checkbox-group v-model="modelValue.brandCode">
+        <el-checkbox
+            v-for="dict in yamadaFcBrand"
+            :key="dict.value"
+            :label="dict.value"
+            style="margin-right: 60px;"
+        >
+          {{ dict.label }}
+        </el-checkbox>
+      </el-checkbox-group>
+    </el-form-item>
+
+    <!-- 業種选择 -->
+    <el-form-item
+        label="業種"
+        prop="businessTypeCode"
+        class="business-type-form-item"
+    >
+      <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 14px;"></div>
+      <el-checkbox-group v-model="modelValue.businessTypeCode">
+        <el-checkbox
+            v-for="dict in yamadaBusinessType"
+            :key="dict.value"
+            :label="dict.value"
+            style="margin-right: 32px;"
+        >
+          {{ dict.label }}
+        </el-checkbox>
+      </el-checkbox-group>
+    </el-form-item>
+
+    <!-- エリア选择 -->
+    <div class="area-container">
+      <div class="area-header-row">
+        <h1 class="area-title">エリア</h1>
+        <RegionTree
+            class="region-tree-container"
+            :treeData="regionTree"
+            @check-change="handleCheckChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { defineProps, defineEmits, watch } from 'vue';
+import RegionTree from  './RegionTree.vue';
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true,
+    default: () => ({
+      brandCode: [],
+      businessTypeCode: [],
+      regions: []
+    })
+  },
+  regionTree: {
+    type: Array,
+    required: true,
+    default: () => []
+  },
+  yamadaFcBrand: {
+    type: Array,
+    required: true,
+    default: () => []
+  },
+  yamadaBusinessType: {
+    type: Array,
+    required: true,
+    default: () => []
+  }
+});
+
+const emits = defineEmits([
+  'update:modelValue',
+  'check-change'
+]);
+
+const handleCheckChange = (regionId, isChecked) => {
+  emits('check-change', regionId, isChecked);
+};
+
+watch(
+    () => props.modelValue,
+    (newValue) => {
+      emits('update:modelValue', newValue);
+    },
+    { deep: true }
+);
+</script>
+
+<style scoped>
+.public-range-container {
+  margin-top: 10px;
+}
+
+.fc-form-item {
+  margin-left: 60px;
+  display: flex;
+  align-items: center;
+  margin-top: 10px;
+}
+
+.business-type-form-item {
+  margin-left: 55px;
+  display: flex;
+  align-items: center;
+  margin-top: -15px;
+}
+
+.area-container {
+  margin-top: 10px;
+}
+
+.area-header-row {
+  display: flex;
+  align-items: flex-start;
+}
+
+.area-title {
+  font-size: 14px;
+  margin-left: 57px !important;
+  margin-right: 20px;
+  margin-top: 0;
+}
+
+.region-tree-container {
+  margin-top: -12px;
+  margin-left: -2px;
+}
+
+@media (max-width: 768px) {
+  .business-type-form-item {
+    margin-left: 13%;
+  }
+
+  .area-title {
+    margin-left: 13% !important;
+  }
+}
+</style>

+ 208 - 0
src/views/fcbi/customer/export.vue

@@ -0,0 +1,208 @@
+<!-- 顧客データ抽出 -->
+<template>
+  <div class="app-container">
+    <div class="form-container">
+      <div class="filter-item" style="padding-left: 58px; margin-top: -15px;">抽出条件</div>
+      <PublicRange
+          v-model="queryParams"
+          :region-tree="regionTree"
+          :yamada-fc-brand="yamada_fc_brand"
+          :yamada-business-type="yamada_business_type"
+          @check-change="handleCheckChange"
+      />
+      <div class="shop-name-section" style="padding-left: 58px;">
+        <label for="shopName" class="shop-name-label">店铺名</label>
+        <input type="text" id="shopName" class="shop-name-input">
+      </div>
+      <div class="btn-section" style="padding-left: 78px; margin-top: 25px;">
+        <button class="operate-btn" @click="handleExportCSV">CSV出力</button>
+        <button class="operate-btn" style="margin-left: 20px;" @click="handleExportDM">DM宛名出力</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script name="SurveyExport" setup>
+import { getRegionTree } from "@/api/fcbi/survey.js";
+import {reactive, ref, onMounted, getCurrentInstance, watch, toRefs} from 'vue';
+import PublicRange from '../../../components/PublicRange.vue';
+import { useSurveyStore } from '@/store/surveyStore';
+import {download} from "@/utils/request.js";
+
+const { proxy } = getCurrentInstance();
+const surveyStore = useSurveyStore();
+
+/**
+ * データ辞書から品牌と業務タイプのデータを取得します
+ */
+const { yamada_fc_brand } = proxy.useDict('yamada_fc_brand');
+const { yamada_business_type } = proxy.useDict('yamada_business_type');
+
+/**
+ * 地域ツリーデータと選択された地域コードの配列を保持します
+ */
+const regionTree = ref([]);
+const selectedRegions = ref([]);
+
+/**
+ * フォームの入力データを保持するオブジェクトです
+ */
+const data = reactive({
+  // 検索条件を格納するオブジェクト
+  queryParams: {
+    brandCode: [],
+    businessTypeCode: [],
+    regions: []
+  }
+});
+const { queryParams} = toRefs(data);
+
+/**
+ * 地域ツリーのデータが更新されたときに、選択された地域を更新します
+ */
+watch(regionTree, () => {
+  if (regionTree.value.length > 0) {
+    updateSelectedRegions(regionTree.value);
+  }
+}, { deep: true });
+
+/**
+ * コンポーネントのマウント後に地域ツリーデータを初期化します
+ */
+onMounted(async () => {
+  const response = await getRegionTree();
+  if (response?.success) {
+    const regions = response.data.regions || []
+    regionTree.value = regions
+    surveyStore.setRegionTree(regions)
+    if (regionTree.value.length > 0) {
+      updateSelectedRegions(regionTree.value);
+    }
+  } else {
+    error.value = response?.message || '地域データの取得に失敗しました';
+    console.error('地域データの取得に失敗しました:', response);
+  }
+});
+
+/**
+ * 地域のチェック状態が変更されたときのハンドラー関数です
+ */
+const handleCheckChange = (regionId, isChecked) => {
+  const region = findRegionById(regionTree.value, regionId);
+  if (region) {
+    if (isChecked) {
+      if (!selectedRegions.value.some(r => r.region_code === region.region_code)) {
+        selectedRegions.value.push({
+          region_code: region.region_code
+        });
+      }
+    } else {
+      selectedRegions.value = selectedRegions.value.filter(r => r.region_code !== region.region_code);
+    }
+    queryParams.value.regions = selectedRegions.value;
+  }
+};
+
+/**
+ * 指定されたIDを持つ地域ノードをツリーから再帰的に検索します
+ */
+const findRegionById = (nodes, id) => {
+  for (const node of nodes) {
+    if (node.id === id) {
+      return node;
+    }
+    if (node.children && node.children.length > 0) {
+      const found = findRegionById(node.children, id);
+      if (found) {
+        return found;
+      }
+    }
+  }
+  return null;
+};
+
+/**
+ * 選択されたすべての地域を再帰的に更新し、サブノードのみを収集します
+ */
+const updateSelectedRegions = (nodes) => {
+  selectedRegions.value = [];
+  // 最上位ノードは親ノードとしてマークされ、再帰的に子ノードを処理します
+  collectSelectedRegions(nodes, true);
+  queryParams.value.regions = selectedRegions.value;
+};
+
+/**
+ * 選択されたすべてのサブエリアオブジェクトを再帰的に収集します(親ノードをスキップ)
+ */
+const collectSelectedRegions = (nodes, isParent = false) => {
+  nodes.forEach(node => {
+    // 親ノード(上位ノード)の場合は、収集をスキップして再帰的に子ノードを処理します
+    if (isParent) {
+      if (node.children && node.children.length > 0) {
+        collectSelectedRegions(node.children, false);
+      }
+      return;
+    }
+    if (node.checked) {
+      selectedRegions.value.push({
+        region_code: node.region_code
+      });
+    }
+    if (node.children && node.children.length > 0) {
+      collectSelectedRegions(node.children, false);
+    }
+  });
+};
+
+/**
+ * CSV形式でデータをエクスポートする関数です
+ */
+function handleExportCSV() {
+  download('system/survey/exportCustomerCsv', {}, `ymdf_customer${new Date().getTime()}.csv`);
+}
+
+/**
+ * DM宛名用のデータをエクスポートする関数です
+ */
+function handleExportDM() {
+  download('system/survey/exportCustomerDm', {}, `ymdf_customer${new Date().getTime()}.csv`);
+}
+</script>
+
+<style scoped>
+.form-container {
+  margin-top: 40px;
+}
+.shop-name-label {
+  display: inline-block;
+  margin-right: 10px;
+  color: #606266;
+}
+.shop-name-input {
+  width: 300px;
+  height: 32px;
+  padding: 0 8px;
+  font-size: 14px;
+  margin-left: 20px;
+}
+@media (max-width:48em) {
+  .shop-name-input {
+    width: 260px;
+  }
+}
+.operate-btn {
+  background-color: #0080c7;
+  color: white;
+  border: none;
+  padding: 6px 12px;
+  cursor: pointer;
+  width: 240px;
+  margin-left: 90px;
+}
+@media (max-width:48em) {
+  .operate-btn {
+    width: 100px;
+    margin-left: 20px;
+  }
+}
+</style>

+ 8 - 19
src/views/fcbi/survey/form.vue

@@ -20,24 +20,13 @@
           ></el-input>
         </el-form-item>
         <div style=" padding-left: 29px;margin-top: -15px;">公開範囲</div>
-        <el-form-item label="FC" prop="brandCode" style="margin-left: 60px; display: flex; align-items: center; margin-top: 10px;">
-          <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 20px;"></div>
-          <el-checkbox-group v-model="queryParams.brandCode">
-            <el-checkbox v-for="dict in yamada_fc_brand" :key="dict.value" :label="dict.value" style="margin-right: 60px;">{{ dict.label }}</el-checkbox>
-          </el-checkbox-group>
-        </el-form-item>
-        <el-form-item label="業種" prop="businessTypeCode" class="el-form-item__businessTypeCode">
-          <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 14px;"></div>
-          <el-checkbox-group v-model="queryParams.businessTypeCode">
-            <el-checkbox v-for="dict in yamada_business_type" :key="dict.value" :label="dict.value" style="margin-right: 32px;">{{ dict.label }}</el-checkbox>
-          </el-checkbox-group>
-        </el-form-item>
-        <div class="container">
-          <div class="area-header-row">
-            <h1 class="area-title">エリア</h1>
-            <RegionTree class="region-tree-container"  :treeData="regionTree" @check-change="handleCheckChange" />
-          </div>
-        </div>
+        <PublicRange
+            v-model="queryParams"
+            :region-tree="regionTree"
+            :yamada-fc-brand="yamada_fc_brand"
+            :yamada-business-type="yamada_business_type"
+            @check-change="handleCheckChange"
+        />
         <!-- 公開期間 -->
         <div class="form-section">公開期間</div>
         <!-- 垂直排列的单选框组 -->
@@ -289,7 +278,7 @@
 
 import { getRegionTree, addSurvey } from "@/api/fcbi/survey.js"
 import { reactive, toRefs, ref, watch, onMounted } from 'vue';
-import RegionTree from '../../../components/RegionTree.vue';
+import PublicRange from '../../../components/PublicRange.vue';
 import { ElMessage } from "element-plus";
 import { useRouter } from 'vue-router';
 import { useSurveyStore } from '@/store/surveyStore'

+ 3 - 1
src/views/fcbi/survey/index.vue

@@ -4,6 +4,8 @@
     <el-form ref="queryRef" :inline="true" :model="queryParams">
       <el-form-item label="アンケートコード" prop="surveyCode">
         <el-input v-model="queryParams.surveyCode"
+                  :parser="parseAlphabetNumeric"
+                  :formatter=parseAlphabetNumeric
                   clearable maxlength="13" @keyup.enter="handleQuery"/>
       </el-form-item>
       <el-form-item label="アンケートタイトル" prop="title">
@@ -162,7 +164,7 @@
 
 import { listSurvey} from "@/api/fcbi/survey.js"
 import { useRouter } from 'vue-router';
-import {formatMsg} from "@/utils/yamada.js"
+import {formatMsg, parseAlphabetNumeric} from "@/utils/yamada.js"
 import {ElMessage} from "element-plus";
 
 // 認証項目のテーブルデータを格納するリアクティブ

+ 3 - 1
src/views/fcbi/survey/results.vue

@@ -4,6 +4,8 @@
     <el-form ref="queryRef" :inline="true" :model="queryParams">
       <el-form-item label="アンケートコード" prop="surveyCode">
         <el-input v-model="queryParams.surveyCode"
+                  :parser="parseAlphabetNumeric"
+                  :formatter=parseAlphabetNumeric
                   clearable maxlength="13" @keyup.enter="handleQuery"/>
       </el-form-item>
       <el-form-item label="FC会員名" prop="fcMemberName">
@@ -167,7 +169,7 @@ import { listSurveyDetails} from "@/api/fcbi/survey.js"
 import { useRoute } from 'vue-router'
 import { useRouter } from 'vue-router';
 import {download} from "@/utils/request.js";
-import {formatMsg} from "@/utils/yamada.js"
+import {formatMsg, parseAlphabetNumeric} from "@/utils/yamada.js"
 import {ElMessage} from "element-plus";
 // 認証項目のテーブルデータを格納するリアクティブ変数
 const certificationItemList = ref([]);