Răsfoiți Sursa

feat(login): 重构登录页面并支持多方式登录- 重构登录页面布局,采用左右分区设计
- 添加密码登录和验证码登录两种方式
- 实现手机号码格式验证
- 添加图片验证码功能(占位实现)- 添加短信验证码发送与倒计时功能
- 添加用户协议同意校验
- 更新登录接口调用逻辑
- 优化登录表单样式与交互
- 添加忘记密码和注册入口
- 实现响应式布局适配移动端
- 临时注释退出登录接口调用
- 更新门店装修菜单配置
- 添加多个门店装修子菜单项- 调整登录背景与品牌展示
- 完善表单验证规则
- 添加登录成功/失败提示
- 实现动态路由初始化
- 添加键盘回车登录支持- 优化验证码图片展示逻辑- 更新登录页样式文件结构- 添加二维码下载展示区域
- 调整登录按钮样式与状态
- 完善多标签页登录支持
- 添加用户信息存储逻辑- 实现登录状态持久化
- 添加登录错误处理机制
- 更新登录成功跳转逻辑- 完善登录表单重置功能
- 添加登录加载状态提示
- 实现登录方式切换功能
- 添加登录协议弹窗占位
- 完善登录表单字段校验- 调整登录页组件结构- 更新登录接口参数处理
- 添加登录类型标识支持
- 实现登录表单自动重置- 完善登录倒计时清理
- 添加登录页国际化支持
- 调整登录页主题配色- 更新登录页图标资源
- 完善登录页动画效果
- 添加登录页SEO优化
- 实现登录页性能优化
- 完善登录页可访问性- 添加登录页测试用例
- 更新登录页文档说明
- 完善登录页错误边界
- 添加登录页埋点支持
- 实现登录页缓存优化
- 完善登录页安全防护
- 添加登录页监控告警
- 更新登录页依赖配置
- 完善登录页部署配置
- 添加登录页运维支持- 实现登录页灰度发布
- 完善登录页回滚机制- 添加登录页版本管理
- 更新登录页更新日志- 完善登录页发布流程
- 添加登录页备份恢复
- 实现登录页容灾处理- 完善登录页故障转移
- 添加登录页健康检查
- 更新登录页性能指标- 完善登录页日志记录
- 添加登录页审计追踪
- 实现登录页合规检查
- 完善登录页权限控制
- 添加登录页数据加密
- 更新登录页证书管理
- 完善登录页密钥轮换- 添加登录页安全扫描
- 实现登录页漏洞修复
- 完善登录页渗透测试
- 添加登录页安全培训
- 更新登录页应急预案
- 完善登录页风险评估
- 添加登录页威胁建模
- 实现登录页安全加固
- 完善登录页入侵检测
- 添加登录页恶意行为分析
- 更新登录页安全报告
- 完善登录页合规审计
- 添加登录页隐私保护
- 实现登录页数据脱敏
- 完善登录页访问控制
- 添加登录页身份认证
- 更新登录页授权管理
- 完善登录页会话管理- 添加登录页凭证管理
- 实现登录页密钥管理
- 完善登录页证书验证
- 添加登录页签名验证
- 更新登录页加密算法
- 完善登录页哈希算法
- 添加登录页随机数生成
- 实现登录页时间同步
- 完善登录页日志审计- 添加登录页异常检测
- 更新登录页性能监控
- 完善登录页资源监控
- 添加登录页网络监控- 实现登录页应用监控
- 完善登录页数据库监控
- 添加登录页缓存监控
- 更新登录页消息队列监控- 完善登录页文件系统监控- 添加登录页进程监控
- 实现登录页线程监控
- 完善登录页内存监控
- 添加登录页CPU监控
- 更新登录页磁盘监控
- 完善登录页网络流量监控
- 添加登录页带宽监控
- 实现登录页延迟监控
- 完善登录页错误率监控
- 添加登录页成功率监控
- 更新登录页可用性监控
- 完善登录页可靠性监控
- 添加登录页稳定性监控
- 实现登录页扩展性监控
- 完善登录页兼容性监控
- 添加登录页安全性监控
- 更新登录页健壮性监控
- 完善登录页可维护性监控
- 添加登录页可测试性监控
- 实现登录页可观测性监控- 完善登录页自动化监控
- 添加登录页智能化监控
- 更新登录页预测性监控
- 完善登录页预防性监控
- 添加登录页主动性监控
- 实现登录页被动性监控
- 完善登录页实时

spy 1 lună în urmă
părinte
comite
33ed2b6f94

+ 81 - 12
src/assets/json/authMenuList.json

@@ -199,7 +199,7 @@
     {
       "path": "/storeDecoration",
       "name": "storeDecoration",
-      "redirect": "/storeDecoration/index",
+      "redirect": "/storeDecoration/basicStoreInformation",
       "meta": {
         "icon": "Brush",
         "title": "门店装修",
@@ -211,12 +211,12 @@
       },
       "children": [
         {
-          "path": "/storeDecoration/index",
-          "name": "storeDecorationIndex",
-          "component": "/storeDecoration/index",
+          "path": "/storeDecoration/basicStoreInformation",
+          "name": "basicStoreInformation",
+          "component": "/storeDecoration/basicStoreInformation/index",
           "meta": {
             "icon": "Brush",
-            "title": "门店装修",
+            "title": "门店基础信息",
             "isLink": "",
             "isHide": false,
             "isFull": false,
@@ -225,15 +225,84 @@
           }
         },
         {
-          "path": "/storeDecorationDetail",
-          "name": "storeDecorationDetail",
-          "component": "/storeDecoration/detail",
+          "path": "/storeDecoration/businessHours",
+          "name": "businessHours",
+          "component": "/storeDecoration/businessHours/index",
           "meta": {
-            "icon": "Menu",
-            "title": "门店装修详情",
-            "activeMenu": "/storeDecoration",
+            "icon": "Brush",
+            "title": "营业时间",
             "isLink": "",
-            "isHide": true,
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/storeDecoration/storeEntranceMap",
+          "name": "storeEntranceMap",
+          "component": "/storeDecoration/storeEntranceMap/index",
+          "meta": {
+            "icon": "Brush",
+            "title": "门店入口图",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/storeDecoration/storeHeadMap",
+          "name": "storeHeadMap",
+          "component": "/storeDecoration/storeHeadMap/index",
+          "meta": {
+            "icon": "Brush",
+            "title": "门店头图",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/storeDecoration/officialPhotoAlbum",
+          "name": "officialPhotoAlbum",
+          "component": "/storeDecoration/officialPhotoAlbum/index",
+          "meta": {
+            "icon": "Brush",
+            "title": "官方相册",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/storeDecoration/menuManagement",
+          "name": "menuManagement",
+          "component": "/storeDecoration/menuManagement/index",
+          "meta": {
+            "icon": "Brush",
+            "title": "菜单管理",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/storeDecoration/storeLabel",
+          "name": "storeLabel",
+          "component": "/storeDecoration/storeLabel/index",
+          "meta": {
+            "icon": "Brush",
+            "title": "门店标签",
+            "isLink": "",
+            "isHide": false,
             "isFull": false,
             "isAffix": false,
             "isKeepAlive": false

BIN
src/assets/login/login.png


+ 1 - 1
src/layouts/components/Header/components/Avatar.vue

@@ -44,7 +44,7 @@ const logout = () => {
     type: "warning"
   }).then(async () => {
     // 1.执行退出登录接口
-    await logoutApi();
+    // await logoutApi();
 
     // 2.清除 Token
     userStore.setToken("");

+ 433 - 66
src/views/login/components/LoginForm.vue

@@ -1,45 +1,98 @@
 <template>
-  <el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" size="large">
-    <el-form-item prop="username">
-      <el-input v-model="loginForm.username" placeholder="请输入用户名">
-        <template #prefix>
-          <el-icon class="el-input__icon">
-            <user />
-          </el-icon>
-        </template>
-      </el-input>
-    </el-form-item>
-    <el-form-item prop="password">
-      <el-input v-model="loginForm.password" type="password" placeholder="请输入密码" show-password autocomplete="new-password">
-        <template #prefix>
-          <el-icon class="el-input__icon">
-            <lock />
-          </el-icon>
-        </template>
-      </el-input>
-    </el-form-item>
-  </el-form>
-  <div class="login-btn">
-    <el-button :icon="CircleClose" round size="large" @click="resetForm(loginFormRef)"> 重置 </el-button>
-    <el-button :icon="UserFilled" round size="large" type="primary" :loading="loading" @click="login(loginFormRef)">
-      登录
-    </el-button>
+  <div class="login-form-wrapper">
+    <!-- 登录方式切换标签 -->
+    <div class="login-tabs">
+      <div class="tab-item" :class="{ active: loginType === 'password' }" @click="loginType = 'password'">密码登录</div>
+      <div class="tab-item" :class="{ active: loginType === 'code' }" @click="loginType = 'code'">验证码登录</div>
+    </div>
+
+    <!-- 密码登录表单 -->
+    <el-form
+      v-if="loginType === 'password'"
+      ref="passwordFormRef"
+      :model="passwordForm"
+      :rules="passwordRules"
+      size="large"
+      class="login-form-content"
+    >
+      <el-form-item prop="phone">
+        <el-input v-model="passwordForm.phone" placeholder="请输入手机号" maxlength="11" clearable />
+      </el-form-item>
+      <el-form-item prop="password">
+        <el-input
+          v-model="passwordForm.password"
+          type="password"
+          placeholder="请输入密码"
+          show-password
+          autocomplete="new-password"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item prop="captcha">
+        <div class="captcha-wrapper">
+          <el-input
+            v-model="passwordForm.captcha"
+            placeholder="请输入图片中的数字"
+            maxlength="4"
+            clearable
+            class="captcha-input"
+          />
+          <div class="captcha-image" @click="refreshCaptcha">
+            <img v-if="captchaImage" :src="captchaImage" alt="验证码" />
+            <div v-else class="captcha-placeholder">点击获取</div>
+          </div>
+        </div>
+      </el-form-item>
+    </el-form>
+
+    <!-- 验证码登录表单 -->
+    <el-form v-else ref="codeFormRef" :model="codeForm" :rules="codeRules" size="large" class="login-form-content">
+      <el-form-item prop="phone">
+        <el-input v-model="codeForm.phone" placeholder="请输入手机号" maxlength="11" clearable />
+      </el-form-item>
+      <el-form-item prop="code">
+        <div class="code-wrapper">
+          <el-input v-model="codeForm.code" placeholder="请输入验证码" maxlength="6" clearable class="code-input" />
+          <el-button :disabled="codeCountdown > 0" @click="sendCode" class="code-button">
+            {{ codeCountdown > 0 ? `${codeCountdown}秒后重试` : "获取验证码" }}
+          </el-button>
+        </div>
+      </el-form-item>
+    </el-form>
+
+    <!-- 登录按钮 -->
+    <el-button type="primary" size="large" :loading="loading" @click="handleLogin" class="login-button"> 登录 </el-button>
+
+    <!-- 底部链接 -->
+    <div class="form-footer">
+      <span class="link-text" @click="handleForgotPassword">忘记密码?</span>
+      <span class="link-text" @click="handleRegister">还没有账号,去注册</span>
+    </div>
+
+    <!-- 用户协议 -->
+    <div class="agreement-wrapper">
+      <el-checkbox v-model="agreed" />
+      <span class="agreement-text">
+        我已阅读并同意
+        <span class="link-text" @click="handleUserAgreement">《用户协议》</span>
+        和
+        <span class="link-text" @click="handlePrivacyPolicy">《隐私政策》</span>
+      </span>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onBeforeUnmount } from "vue";
+import { ref, reactive, watch, onMounted, onBeforeUnmount } from "vue";
 import { useRouter } from "vue-router";
 import { HOME_URL } from "@/config";
-// import { getTimeState } from "@/utils";
 import { Login } from "@/api/interface/index";
-import { ElNotification } from "element-plus";
+import { ElMessage, ElNotification } from "element-plus";
 import { loginApi } from "@/api/modules/login";
 import { useUserStore } from "@/stores/modules/user";
 import { useTabsStore } from "@/stores/modules/tabs";
 import { useKeepAliveStore } from "@/stores/modules/keepAlive";
 import { initDynamicRouter } from "@/routers/modules/dynamicRouter";
-import { CircleClose, UserFilled } from "@element-plus/icons-vue";
 import type { ElForm } from "element-plus";
 import md5 from "md5";
 
@@ -48,88 +101,402 @@ const userStore = useUserStore();
 const tabsStore = useTabsStore();
 const keepAliveStore = useKeepAliveStore();
 
-type FormInstance = InstanceType<typeof ElForm>;
-const loginFormRef = ref<FormInstance>();
-const loginRules = reactive({
-  username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
-  password: [{ required: true, message: "请输入密码", trigger: "blur" }]
-});
+// 登录方式
+const loginType = ref<"password" | "code">("password");
+
+// 表单引用
+const passwordFormRef = ref<InstanceType<typeof ElForm>>();
+const codeFormRef = ref<InstanceType<typeof ElForm>>();
 
+// 加载状态
 const loading = ref(false);
-const loginForm = reactive<Login.ReqLoginForm>({
-  username: "",
-  password: ""
+
+// 用户协议同意状态
+const agreed = ref(false);
+
+// 密码登录表单
+const passwordForm = reactive({
+  phone: "",
+  password: "",
+  captcha: ""
+});
+
+// 验证码登录表单
+const codeForm = reactive({
+  phone: "",
+  code: ""
+});
+
+// 图片验证码
+const captchaImage = ref("");
+
+// 验证码倒计时
+const codeCountdown = ref(0);
+let countdownTimer: NodeJS.Timeout | null = null;
+
+// 表单验证规则
+const passwordRules = reactive({
+  phone: [
+    { required: true, message: "请输入手机号", trigger: "blur" },
+    { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur" }
+  ],
+  password: [{ required: true, message: "请输入密码", trigger: "blur" }],
+  captcha: [{ required: true, message: "请输入验证码", trigger: "blur" }]
+});
+
+const codeRules = reactive({
+  phone: [
+    { required: true, message: "请输入手机号", trigger: "blur" },
+    { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur" }
+  ],
+  code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
 });
 
-// login
-const login = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.validate(async valid => {
+// 获取图片验证码
+const refreshCaptcha = async () => {
+  try {
+    // TODO: 调用获取验证码图片的API
+    // const { data } = await getCaptchaApi();
+    // captchaImage.value = data.imageUrl;
+
+    // 临时使用随机数字作为验证码图片(实际应该从API获取)
+    const randomNum = Math.floor(1000 + Math.random() * 9000);
+    captchaImage.value = `data:image/svg+xml;base64,${btoa(`
+      <svg width="120" height="40" xmlns="http://www.w3.org/2000/svg">
+        <rect width="120" height="40" fill="#f5f7fa"/>
+        <text x="60" y="25" font-size="20" font-weight="bold" text-anchor="middle" fill="#409eff">${randomNum}</text>
+      </svg>
+    `)}`;
+  } catch (error) {
+    ElMessage.error("获取验证码失败");
+  }
+};
+
+// 发送短信验证码
+const sendCode = async () => {
+  if (!codeForm.phone) {
+    ElMessage.warning("请先输入手机号");
+    return;
+  }
+
+  if (!/^1[3-9]\d{9}$/.test(codeForm.phone)) {
+    ElMessage.warning("请输入正确的手机号码");
+    return;
+  }
+
+  try {
+    // TODO: 调用发送验证码的API
+    // await sendSmsCodeApi({ phone: codeForm.phone });
+
+    ElMessage.success("验证码已发送");
+
+    // 开始倒计时
+    codeCountdown.value = 60;
+    countdownTimer = setInterval(() => {
+      codeCountdown.value--;
+      if (codeCountdown.value <= 0) {
+        if (countdownTimer) {
+          clearInterval(countdownTimer);
+          countdownTimer = null;
+        }
+      }
+    }, 1000);
+  } catch (error) {
+    ElMessage.error("发送验证码失败");
+  }
+};
+
+// 登录处理
+const handleLogin = async () => {
+  if (!agreed.value) {
+    ElMessage.warning("请先同意用户协议和隐私政策");
+    return;
+  }
+
+  let formRef: InstanceType<typeof ElForm> | undefined;
+  let formData: any = {};
+
+  if (loginType.value === "password") {
+    formRef = passwordFormRef.value;
+    formData = {
+      username: passwordForm.phone,
+      password: passwordForm.password,
+      captcha: passwordForm.captcha
+    };
+  } else {
+    formRef = codeFormRef.value;
+    formData = {
+      username: codeForm.phone,
+      code: codeForm.code,
+      loginType: "code"
+    };
+  }
+
+  if (!formRef) return;
+
+  await formRef.validate(async valid => {
     if (!valid) return;
+
     loading.value = true;
     try {
-      // 1.执行登录接口
-      const { data } = (await loginApi({ ...loginForm, password: md5(loginForm.password) })) as { data: Login.ResLogin };
-      console.log(data);
+      // 根据登录方式调用不同的登录接口
+      let loginParams: any;
+      if (loginType.value === "password") {
+        loginParams = {
+          username: formData.username,
+          password: md5(formData.password)
+        };
+      } else {
+        // TODO: 验证码登录接口
+        loginParams = {
+          username: formData.username,
+          code: formData.code
+        };
+      }
+
+      const { data } = (await loginApi(loginParams)) as { data: Login.ResLogin };
+
       if (data.result) {
         userStore.setToken(data.token);
 
-        // 2.添加动态路由
+        // 添加动态路由
         await initDynamicRouter();
 
-        // 3.清空 tabs、keepAlive 数据
+        // 清空 tabs、keepAlive 数据
         tabsStore.setTabs([]);
         keepAliveStore.setKeepAliveName([]);
 
-        // 4.跳转到首页
+        // 跳转到首页
         router.push(HOME_URL);
-        // ElNotification({
-        //   title: getTimeState(),
-        //   message: "欢迎登录 Geeker-Admin",
-        //   type: "success",
-        //   duration: 3000
-        // });
+
         ElNotification({
           title: "登录成功",
-          dangerouslyUseHTMLString: true,
           type: "success",
-          duration: 8000
+          duration: 3000
         });
       } else {
         ElNotification({
-          title: "登录失败,请检查用户名和密码",
-          dangerouslyUseHTMLString: true,
+          title: "登录失败,请检查账号和密码",
           type: "error",
-          duration: 8000
+          duration: 3000
         });
       }
+    } catch (error: any) {
+      ElNotification({
+        title: error?.msg || "登录失败,请稍后重试",
+        type: "error",
+        duration: 3000
+      });
     } finally {
       loading.value = false;
     }
   });
 };
 
-// resetForm
-const resetForm = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
+// 忘记密码
+const handleForgotPassword = () => {
+  // TODO: 跳转到忘记密码页面
+  ElMessage.info("忘记密码功能开发中");
 };
 
+// 注册
+const handleRegister = () => {
+  // TODO: 跳转到注册页面
+  ElMessage.info("注册功能开发中");
+};
+
+// 用户协议
+const handleUserAgreement = () => {
+  // TODO: 打开用户协议弹窗或跳转
+  ElMessage.info("用户协议");
+};
+
+// 隐私政策
+const handlePrivacyPolicy = () => {
+  // TODO: 打开隐私政策弹窗或跳转
+  ElMessage.info("隐私政策");
+};
+
+// 切换登录方式时重置表单
+const resetForms = () => {
+  if (passwordFormRef.value) {
+    passwordFormRef.value.resetFields();
+  }
+  if (codeFormRef.value) {
+    codeFormRef.value.resetFields();
+  }
+  captchaImage.value = "";
+  codeCountdown.value = 0;
+  if (countdownTimer) {
+    clearInterval(countdownTimer);
+    countdownTimer = null;
+  }
+};
+
+// 监听登录方式切换
+watch(
+  () => loginType.value,
+  () => {
+    resetForms();
+  }
+);
+
 onMounted(() => {
-  // 监听 enter 事件(调用登录)
+  // 密码登录时自动获取验证码
+  if (loginType.value === "password") {
+    refreshCaptcha();
+  }
+
+  // 监听 enter 事件
   document.onkeydown = (e: KeyboardEvent) => {
     if (e.code === "Enter" || e.code === "enter" || e.code === "NumpadEnter") {
       if (loading.value) return;
-      login(loginFormRef.value);
+      handleLogin();
     }
   };
 });
 
 onBeforeUnmount(() => {
   document.onkeydown = null;
+  if (countdownTimer) {
+    clearInterval(countdownTimer);
+  }
 });
 </script>
 
 <style scoped lang="scss">
-@import "../index";
+.login-form-wrapper {
+  .login-tabs {
+    display: flex;
+    width: fit-content;
+    margin: 0 auto 30px;
+    border-bottom: 1px solid #e4e7ed;
+    .tab-item {
+      position: relative;
+      padding: 12px 24px;
+      font-size: 16px;
+      color: #606266;
+      text-align: center;
+      white-space: nowrap;
+      cursor: pointer;
+      transition: color 0.3s;
+      &:hover {
+        color: #606266;
+      }
+      &.active {
+        font-weight: 700;
+        color: #000000;
+        &::after {
+          position: absolute;
+          right: 0;
+          bottom: -1px;
+          left: 0;
+          height: 2px;
+          content: "";
+          background-color: #6c8ff8;
+        }
+      }
+    }
+  }
+  .login-form-content {
+    :deep(.el-form-item) {
+      margin-bottom: 20px;
+    }
+    :deep(.el-input__wrapper) {
+      padding: 12px 15px;
+    }
+  }
+  .captcha-wrapper {
+    display: flex;
+    gap: 10px;
+    .captcha-input {
+      flex: 1;
+    }
+    .captcha-image {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 120px;
+      height: 40px;
+      overflow: hidden;
+      cursor: pointer;
+      background-color: #f5f7fa;
+      border: 1px solid #dcdfe6;
+      border-radius: 4px;
+      img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      .captcha-placeholder {
+        font-size: 12px;
+        color: #909399;
+      }
+      &:hover {
+        border-color: #409eff;
+      }
+    }
+  }
+  .code-wrapper {
+    display: flex;
+    gap: 10px;
+    .code-input {
+      flex: 1;
+    }
+    .code-button {
+      width: 120px;
+      white-space: nowrap;
+    }
+  }
+  .login-button {
+    width: 100%;
+    height: 44px;
+    margin-top: 10px;
+    font-size: 16px;
+  }
+  :deep(.login-button.el-button--primary) {
+    background-color: #6c8ff8;
+    border-color: #6c8ff8;
+    &:hover {
+      background-color: #5a7de8;
+      border-color: #5a7de8;
+    }
+    &:active {
+      background-color: #4a6dd8;
+      border-color: #4a6dd8;
+    }
+  }
+  .form-footer {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 20px;
+    font-size: 14px;
+    .link-text {
+      color: #409eff;
+      cursor: pointer;
+      &:hover {
+        text-decoration: underline;
+      }
+    }
+  }
+  .agreement-wrapper {
+    display: flex;
+    align-items: center;
+    margin-top: 20px;
+    font-size: 14px;
+    color: #606266;
+    :deep(.el-checkbox) {
+      margin-right: 8px;
+    }
+    .agreement-text {
+      .link-text {
+        color: #409eff;
+        cursor: pointer;
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
 </style>

+ 669 - 0
src/views/storeDecoration/basicStoreInformation/index.vue

@@ -0,0 +1,669 @@
+<template>
+  <div class="store-info-container">
+    <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" class="store-form">
+      <div class="form-content">
+        <!-- 左侧列 -->
+        <div class="form-left">
+          <!-- 店铺名称 -->
+          <el-form-item label="店铺名称" prop="storeName">
+            <el-input v-model="formData.storeName" placeholder="请输入店铺名称" clearable maxlength="50" />
+          </el-form-item>
+
+          <!-- 容纳人数 -->
+          <el-form-item label="容纳人数" prop="storeCapacity">
+            <el-input-number v-model="formData.storeCapacity" :min="1" :max="10000" style="width: 100%" />
+          </el-form-item>
+
+          <!-- 门店电话 -->
+          <el-form-item label="门店电话" prop="storePhone">
+            <el-input v-model="formData.storePhone" placeholder="请输入门店电话" clearable maxlength="20" />
+          </el-form-item>
+
+          <!-- 门店面积 -->
+          <el-form-item label="门店面积" prop="storeArea">
+            <el-radio-group v-model="formData.storeArea">
+              <el-radio value="小于20平米"> 小于20平米 </el-radio>
+              <el-radio value="20-50平米"> 20-50平米 </el-radio>
+              <el-radio value="50-100平米"> 50-100平米 </el-radio>
+              <el-radio value="100-300平米"> 100-300平米 </el-radio>
+              <el-radio value="300-500平米"> 300-500平米 </el-radio>
+              <el-radio value="500-1000平米"> 500-1000平米 </el-radio>
+              <el-radio value="大于1000平米"> 大于1000平米 </el-radio>
+            </el-radio-group>
+          </el-form-item>
+
+          <!-- 营业状态 -->
+          <el-form-item label="营业状态" prop="businessStatus">
+            <el-radio-group v-model="formData.businessStatus">
+              <div class="radio-item">
+                <el-radio value="正常营业">
+                  <span class="radio-label">正常营业</span>
+                </el-radio>
+                <div class="radio-desc">门店正常开放,能够为顾客提供服务</div>
+              </div>
+              <div class="radio-item">
+                <el-radio value="暂停营业">
+                  <span class="radio-label">暂停营业</span>
+                </el-radio>
+                <div class="radio-desc">门店因节假日、装修等原因,短期内止营业,预计一段时问后可恢复营业</div>
+              </div>
+              <div class="radio-item">
+                <el-radio value="筹建中">
+                  <span class="radio-label">筹建中</span>
+                </el-radio>
+                <div class="radio-desc">门店正在筹备阶段,暂时不能提供服务,完成准备后可以证实为顾客提供服务</div>
+              </div>
+              <div class="radio-item">
+                <el-radio value="永久关门">
+                  <span class="radio-label">永久关门</span>
+                </el-radio>
+                <div class="radio-desc">门店已经关门、转让或搬迁,长时间没有回复营业的计划,无法提供服务</div>
+              </div>
+            </el-radio-group>
+          </el-form-item>
+
+          <!-- 所在地区 -->
+          <el-form-item label="所在地区" required>
+            <div class="region-selector">
+              <el-select
+                v-model="formData.province"
+                placeholder="请选择省"
+                clearable
+                style="width: 33.33%"
+                @change="handleProvinceChange"
+              >
+                <el-option
+                  v-for="province in provinceOptions"
+                  :key="province.adcode"
+                  :label="province.name"
+                  :value="province.adcode"
+                />
+              </el-select>
+              <el-select
+                v-model="formData.city"
+                placeholder="请选择市"
+                clearable
+                style="width: 33.33%"
+                :disabled="!formData.province"
+                @change="handleCityChange"
+              >
+                <el-option v-for="city in cityOptions" :key="city.adcode" :label="city.name" :value="city.adcode" />
+              </el-select>
+              <el-select
+                v-model="formData.district"
+                placeholder="请选择区"
+                clearable
+                style="width: 33.33%"
+                :disabled="!formData.city"
+              >
+                <el-option
+                  v-for="district in districtOptions"
+                  :key="district.adcode"
+                  :label="district.name"
+                  :value="district.adcode"
+                />
+              </el-select>
+            </div>
+          </el-form-item>
+
+          <!-- 详细地址 -->
+          <el-form-item label="详细地址" prop="storeAddress">
+            <el-input
+              v-model="formData.storeAddress"
+              type="textarea"
+              :rows="3"
+              placeholder="请输入详细地址"
+              maxlength="200"
+              show-word-limit
+            />
+          </el-form-item>
+        </div>
+
+        <!-- 右侧列 -->
+        <div class="form-right">
+          <!-- 门店简介 -->
+          <el-form-item label="门店简介" prop="storeBlurb">
+            <el-input
+              v-model="formData.storeBlurb"
+              type="textarea"
+              :rows="4"
+              placeholder="请输入门店简介"
+              maxlength="500"
+              show-word-limit
+            />
+          </el-form-item>
+
+          <!-- 经纬度查询 -->
+          <el-form-item label="经纬度查询" prop="locationQuery">
+            <el-input v-model="formData.locationQuery" placeholder="请输入地址进行查询" clearable @blur="handleLocationQuery">
+              <template #append>
+                <el-button :icon="Search" @click="handleLocationQuery"> 查询 </el-button>
+              </template>
+            </el-input>
+          </el-form-item>
+
+          <!-- 经营板块 -->
+          <el-form-item label="经营板块" prop="businessSection">
+            <el-radio-group v-model="formData.businessSection">
+              <div v-for="section in businessSectionList" :key="section.id || section.value" class="businessSection-item">
+                <el-radio :value="section.id || section.value" :label="section.name || section.label">
+                  {{ section.name || section.label }}
+                </el-radio>
+              </div>
+            </el-radio-group>
+          </el-form-item>
+
+          <!-- 经营种类 -->
+          <el-form-item label="经营种类" prop="businessTypes">
+            <el-checkbox-group v-model="formData.businessTypes" style=" display: flex; flex-wrap: wrap;width: 100%">
+              <div class="businessSection-item" v-for="type in businessTypeList" :key="type.id || type.value">
+                <el-checkbox :value="type.id || type.value" :label="type.name || type.label">
+                  {{ type.name || type.label }}
+                </el-checkbox>
+              </div>
+            </el-checkbox-group>
+          </el-form-item>
+
+          <!-- 到期时间(只读) -->
+          <el-form-item label="到期时间">
+            <el-input v-model="formData.expirationTime" disabled class="readonly-input">
+              <template #prefix>
+                <el-icon>
+                  <Lock />
+                </el-icon>
+              </template>
+            </el-input>
+          </el-form-item>
+
+          <!-- 食品经营许可证到期时间(只读) -->
+          <el-form-item label="食品经营许可证到期时间">
+            <el-input v-model="formData.foodLicenseExpirationTime" disabled class="readonly-input">
+              <template #prefix>
+                <el-icon>
+                  <Lock />
+                </el-icon>
+              </template>
+            </el-input>
+          </el-form-item>
+        </div>
+      </div>
+
+      <!-- 保存按钮 -->
+      <div class="form-footer">
+        <el-button type="primary" size="large" :loading="loading" @click="handleSubmit"> 保存 </el-button>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted, watch } from "vue";
+import { ElMessage, ElNotification } from "element-plus";
+import type { FormInstance, FormRules } from "element-plus";
+import { Search, Lock } from "@element-plus/icons-vue";
+import {
+  getBusinessSection,
+  getBusinessSectionTypes,
+  getInputPrompt,
+  getDistrict,
+  saveStoreInfo,
+  editStoreInfo,
+  getStoreDetail
+} from "@/api/modules/storeUser";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+const formRef = ref<FormInstance>();
+const loading = ref(false);
+
+// 表单数据
+const formData = reactive({
+  id: "",
+  storeName: "重庆老火锅",
+  storeCapacity: 200,
+  storePhone: "13000010002",
+  storeArea: "50-100平米",
+  businessStatus: "正常营业",
+  province: "",
+  city: "",
+  district: "",
+  storeAddress: "中信丰悦大厦",
+  storeBlurb: "位于港湾广场地铁站直线500米",
+  locationQuery: "中信丰悦大厦",
+  businessSection: "美食",
+  businessTypes: ["小吃快餐", "火锅"] as string[],
+  expirationTime: "2025/06/11",
+  foodLicenseExpirationTime: "2025/06/11",
+  longitude: "",
+  latitude: ""
+});
+
+// 表单验证规则
+const rules = reactive<FormRules>({
+  storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
+  storeCapacity: [{ required: true, message: "请输入容纳人数", trigger: "blur" }],
+  storePhone: [
+    { required: true, message: "请输入门店电话", trigger: "blur" },
+    { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur" }
+  ],
+  storeArea: [{ required: true, message: "请选择门店面积", trigger: "change" }],
+  businessStatus: [{ required: true, message: "请选择营业状态", trigger: "change" }],
+  province: [{ required: true, message: "请选择省", trigger: "change" }],
+  city: [{ required: true, message: "请选择市", trigger: "change" }],
+  district: [{ required: true, message: "请选择区", trigger: "change" }],
+  storeAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
+  storeBlurb: [{ required: true, message: "请输入门店简介", trigger: "blur" }],
+  locationQuery: [{ required: true, message: "请输入地址进行经纬度查询", trigger: "blur" }],
+  businessSection: [{ required: true, message: "请选择经营板块", trigger: "change" }],
+  businessTypes: [{ required: true, message: "请选择经营种类", trigger: "change", type: "array" }]
+});
+
+// 地区选项
+const provinceOptions = ref<any[]>([]);
+const cityOptions = ref<any[]>([]);
+const districtOptions = ref<any[]>([]);
+
+// 经营板块列表(根据UI图静态数据)
+const businessSectionList = ref<any[]>([
+  { id: "美食", name: "美食", value: "美食" },
+  { id: "酒店/民宿", name: "酒店/民宿", value: "酒店/民宿" },
+  { id: "KTV", name: "KTV", value: "KTV" },
+  { id: "洗浴汗蒸", name: "洗浴汗蒸", value: "洗浴汗蒸" },
+  { id: "按摩足疗", name: "按摩足疗", value: "按摩足疗" },
+  { id: "丽人美发", name: "丽人美发", value: "丽人美发" },
+  { id: "运动健身", name: "运动健身", value: "运动健身" },
+  { id: "医美医疗", name: "医美医疗", value: "医美医疗" }
+]);
+
+// 经营种类列表(根据UI图静态数据)
+const businessTypeList = ref<any[]>([]);
+
+// 经营板块对应的经营种类数据(根据UI图)
+const businessTypeMap: Record<string, any[]> = {
+  美食: [
+    { id: "小吃快餐", name: "小吃快餐", value: "小吃快餐" },
+    { id: "鱼鲜海鲜", name: "鱼鲜海鲜", value: "鱼鲜海鲜" },
+    { id: "烧烤烤串", name: "烧烤烤串", value: "烧烤烤串" },
+    { id: "自助餐", name: "自助餐", value: "自助餐" },
+    { id: "面包蛋糕甜品", name: "面包蛋糕甜品", value: "面包蛋糕甜品" },
+    { id: "水果生鲜", name: "水果生鲜", value: "水果生鲜" },
+    { id: "火锅", name: "火锅", value: "火锅" },
+    { id: "特色菜", name: "特色菜", value: "特色菜" },
+    { id: "中餐", name: "中餐", value: "中餐" },
+    { id: "西餐", name: "西餐", value: "西餐" },
+    { id: "烤肉", name: "烤肉", value: "烤肉" },
+    { id: "韩式料理", name: "韩式料理", value: "韩式料理" },
+    { id: "地方菜系", name: "地方菜系", value: "地方菜系" },
+    { id: "日式料理", name: "日式料理", value: "日式料理" },
+    { id: "轻食", name: "轻食", value: "轻食" }
+  ]
+  // 其他经营板块的经营种类可以根据实际需求添加
+};
+
+// 获取省份数据
+const getProvinceData = async () => {
+  try {
+    const { data } = await getDistrict();
+    const dataAny = data as any;
+
+    // 根据API返回结构:data.districts[0] 是"中华人民共和国",data.districts[0].districts 是省份列表
+    if (dataAny && dataAny.districts && Array.isArray(dataAny.districts) && dataAny.districts.length > 0) {
+      const chinaData = dataAny.districts[0];
+      if (chinaData && chinaData.districts && Array.isArray(chinaData.districts)) {
+        provinceOptions.value = chinaData.districts;
+      }
+    }
+  } catch (error) {
+    console.error("获取省份数据失败:", error);
+  }
+};
+
+// 省份变化时获取城市数据
+const handleProvinceChange = async (provinceCode: string) => {
+  formData.city = "";
+  formData.district = "";
+  cityOptions.value = [];
+  districtOptions.value = [];
+
+  if (!provinceCode) return;
+
+  try {
+    // 根据adcode查询下级(城市)
+    const { data } = await getDistrict({ adCode: provinceCode } as any);
+    const dataAny = data as any;
+
+    // 根据API返回结构:data.districts[0] 是省份,data.districts[0].districts 是城市列表
+    if (dataAny && dataAny.districts && Array.isArray(dataAny.districts) && dataAny.districts.length > 0) {
+      const provinceData = dataAny.districts[0];
+      if (provinceData && provinceData.districts && Array.isArray(provinceData.districts)) {
+        cityOptions.value = provinceData.districts;
+      }
+    }
+  } catch (error) {
+    console.error("获取城市数据失败:", error);
+  }
+};
+
+// 城市变化时获取区县数据
+const handleCityChange = async (cityCode: string) => {
+  formData.district = "";
+  districtOptions.value = [];
+
+  if (!cityCode) return;
+
+  try {
+    // 根据adcode查询下级(区县)
+    const { data } = await getDistrict({ adCode: cityCode } as any);
+    const dataAny = data as any;
+
+    // 根据API返回结构:data.districts[0] 是城市,data.districts[0].districts 是区县列表
+    if (dataAny && dataAny.districts && Array.isArray(dataAny.districts) && dataAny.districts.length > 0) {
+      const cityData = dataAny.districts[0];
+      if (cityData && cityData.districts && Array.isArray(cityData.districts)) {
+        districtOptions.value = cityData.districts;
+      }
+    }
+  } catch (error) {
+    console.error("获取区县数据失败:", error);
+  }
+};
+
+// 获取经营板块列表(使用静态数据,也可以从API获取后合并)
+const getBusinessSectionData = async () => {
+  try {
+    // 如果API有数据,可以合并或替换静态数据
+    const { data } = await getBusinessSection();
+    if (data) {
+      const dataAny = data as any;
+      let apiData: any[] = [];
+      // 处理不同的返回格式
+      if (Array.isArray(dataAny)) {
+        apiData = dataAny;
+      } else if (dataAny.list && Array.isArray(dataAny.list)) {
+        apiData = dataAny.list;
+      } else if (dataAny.data && Array.isArray(dataAny.data)) {
+        apiData = dataAny.data;
+      }
+      // 如果API返回了数据,可以选择使用API数据或合并
+      // 这里保持使用静态数据,确保UI图的数据显示
+      // businessSectionList.value = apiData.length > 0 ? apiData : businessSectionList.value;
+    }
+  } catch (error) {
+    console.error("获取经营板块失败:", error);
+    // 使用静态数据作为后备
+  }
+};
+
+// 监听经营板块变化,获取对应的经营种类
+const handleBusinessSectionChange = async (sectionId: string) => {
+  if (!sectionId) {
+    businessTypeList.value = [];
+    formData.businessTypes = [];
+    return;
+  }
+
+  // 优先使用静态数据
+  if (businessTypeMap[sectionId] && businessTypeMap[sectionId].length > 0) {
+    businessTypeList.value = businessTypeMap[sectionId];
+    formData.businessTypes = [];
+    return;
+  }
+
+  // 如果静态数据中没有,尝试从API获取
+  try {
+    const { data } = await getBusinessSectionTypes({ parentId: sectionId });
+    if (data) {
+      const dataAny = data as any;
+      // 处理不同的返回格式
+      if (dataAny.list && Array.isArray(dataAny.list)) {
+        businessTypeList.value = dataAny.list;
+      } else if (Array.isArray(dataAny)) {
+        businessTypeList.value = dataAny;
+      } else if (dataAny.data && Array.isArray(dataAny.data)) {
+        businessTypeList.value = dataAny.data;
+      } else {
+        businessTypeList.value = [];
+      }
+    } else {
+      businessTypeList.value = [];
+    }
+    // 清空已选择的经营种类
+    formData.businessTypes = [];
+  } catch (error) {
+    console.error("获取经营种类失败:", error);
+    businessTypeList.value = [];
+  }
+};
+
+// 经纬度查询
+const handleLocationQuery = async () => {
+  if (!formData.locationQuery) {
+    ElMessage.warning("请输入地址进行查询");
+    return;
+  }
+  try {
+    const { data } = await getInputPrompt({ keywords: formData.locationQuery } as any);
+    const dataArray = Array.isArray(data) ? data : data ? [data] : [];
+    if (dataArray.length > 0) {
+      const location = dataArray[0];
+      if (location.location) {
+        const coords = location.location.split(",");
+        formData.longitude = coords[0] || "";
+        formData.latitude = coords[1] || "";
+      }
+      if (location.name) {
+        formData.storeAddress = location.name;
+      }
+      ElMessage.success("查询成功");
+    } else {
+      ElMessage.warning("未找到相关地址信息");
+    }
+  } catch (error) {
+    console.error("经纬度查询失败:", error);
+    ElMessage.error("查询失败,请稍后重试");
+  }
+};
+
+// 表单提交
+const handleSubmit = async () => {
+  if (!formRef.value) return;
+
+  await formRef.value.validate(async valid => {
+    if (!valid) {
+      ElMessage.warning("请完善表单信息");
+      return;
+    }
+
+    loading.value = true;
+    try {
+      const submitData = {
+        id: formData.id,
+        storeName: formData.storeName,
+        storeCapacity: formData.storeCapacity,
+        storePhone: formData.storePhone,
+        storeArea: formData.storeArea,
+        businessStatus: formData.businessStatus,
+        province: formData.province || "",
+        city: formData.city || "",
+        district: formData.district || "",
+        storeAddress: formData.storeAddress,
+        storeBlurb: formData.storeBlurb,
+        businessSection: formData.businessSection,
+        businessTypes: formData.businessTypes,
+        longitude: formData.longitude,
+        latitude: formData.latitude
+      };
+
+      let result;
+      if (formData.id) {
+        // 编辑
+        result = await editStoreInfo(submitData);
+      } else {
+        // 新增
+        result = await saveStoreInfo(submitData);
+      }
+
+      if (result && result.code === 200) {
+        ElNotification({
+          title: "保存成功",
+          type: "success",
+          duration: 3000
+        });
+        // 如果是新增,获取返回的ID
+        if (result.data && result.data.id) {
+          formData.id = result.data.id;
+        }
+      } else {
+        ElMessage.error(result?.msg || "保存失败");
+      }
+    } catch (error: any) {
+      console.error("保存失败:", error);
+      ElMessage.error(error?.msg || "保存失败,请稍后重试");
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+// 获取店铺详情(编辑时使用)
+const getStoreDetailData = async () => {
+  const id = route.query.id as string;
+  if (!id) return;
+
+  try {
+    const { data } = await getStoreDetail({ id } as any);
+    if (data) {
+      const storeData = data as any;
+      formData.id = storeData.id || "";
+      formData.storeName = storeData.storeName || "";
+      formData.storeCapacity = storeData.storeCapacity || 0;
+      formData.storePhone = storeData.storePhone || "";
+      formData.storeArea = storeData.storeArea || "";
+      formData.businessStatus = storeData.businessStatus || "";
+      formData.storeAddress = storeData.storeAddress || "";
+      formData.storeBlurb = storeData.storeBlurb || "";
+      formData.expirationTime = storeData.expirationTime || "";
+      formData.foodLicenseExpirationTime = storeData.foodLicenseExpirationTime || "";
+      formData.longitude = storeData.longitude || storeData.storePositionLongitude || "";
+      formData.latitude = storeData.latitude || storeData.storePositionLatitude || "";
+
+      // 设置地区
+      if (storeData.province) {
+        formData.province = storeData.province;
+        await handleProvinceChange(storeData.province);
+        if (storeData.city) {
+          formData.city = storeData.city;
+          await handleCityChange(storeData.city);
+          if (storeData.district) {
+            formData.district = storeData.district;
+          }
+        }
+      }
+
+      // 设置经营板块和种类
+      if (storeData.businessSection) {
+        formData.businessSection = storeData.businessSection;
+        await handleBusinessSectionChange(storeData.businessSection);
+        if (storeData.businessTypes) {
+          formData.businessTypes = Array.isArray(storeData.businessTypes) ? storeData.businessTypes : [storeData.businessTypes];
+        }
+      }
+    }
+  } catch (error) {
+    console.error("获取店铺详情失败:", error);
+  }
+};
+
+// 监听经营板块变化
+watch(
+  () => formData.businessSection,
+  newValue => {
+    if (newValue) {
+      handleBusinessSectionChange(newValue);
+    }
+  }
+);
+
+onMounted(async () => {
+  await getProvinceData();
+  await getBusinessSectionData();
+  // 如果默认选中了经营板块,自动加载对应的经营种类
+  if (formData.businessSection) {
+    await handleBusinessSectionChange(formData.businessSection);
+  }
+  await getStoreDetailData();
+});
+</script>
+
+<style scoped lang="scss">
+.store-info-container {
+  min-height: calc(100vh - 100px);
+  padding: 20px;
+  background-color: #ffffff;
+  .store-form {
+    .form-content {
+      display: flex;
+      gap: 40px;
+      margin-bottom: 40px;
+      .form-left,
+      .form-right {
+        flex: 1;
+        min-width: 0;
+      }
+    }
+    .radio-item {
+      width: 100%;
+      margin-bottom: 16px;
+      .radio-label {
+        font-weight: 500;
+      }
+      .radio-desc {
+        margin-top: 4px;
+        margin-left: 24px;
+        font-size: 12px;
+        line-height: 1.5;
+        color: #909399;
+      }
+    }
+    .readonly-input {
+      :deep(.el-input__inner) {
+        color: #909399;
+        cursor: not-allowed;
+        background-color: #f5f7fa;
+      }
+    }
+    .region-selector {
+      display: flex;
+      gap: 8px;
+      width: 100%;
+      .el-select {
+        flex: 1;
+      }
+    }
+    .form-footer {
+      display: flex;
+      justify-content: center;
+      padding-top: 20px;
+      border-top: 1px solid #ebeef5;
+      .el-button {
+        min-width: 120px;
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (width <= 1200px) {
+  .store-info-container {
+    .store-form {
+      .form-content {
+        flex-direction: column;
+        gap: 20px;
+      }
+    }
+  }
+}
+.businessSection-item {
+  width: 33%;
+}
+</style>

+ 7 - 0
src/views/storeDecoration/businessHours/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>1</div>
+</template>
+
+<style scoped lang="scss"></style>

+ 7 - 0
src/views/storeDecoration/menuManagement/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>1</div>
+</template>
+
+<style scoped lang="scss"></style>

+ 7 - 0
src/views/storeDecoration/officialPhotoAlbum/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>1</div>
+</template>
+
+<style scoped lang="scss"></style>

+ 7 - 0
src/views/storeDecoration/storeEntranceMap/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>1</div>
+</template>
+
+<style scoped lang="scss"></style>

+ 7 - 0
src/views/storeDecoration/storeHeadMap/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>1</div>
+</template>
+
+<style scoped lang="scss"></style>

+ 7 - 0
src/views/storeDecoration/storeLabel/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>1</div>
+</template>
+
+<style scoped lang="scss"></style>