Просмотр исходного кода

实现添加商店的部分代码,此次提交主要为了测试前后端跨域情况

chillqi 2 лет назад
Родитель
Сommit
62afd6cb3e

+ 3 - 6
components.d.ts

@@ -8,16 +8,13 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Dashboard: typeof import('./src/components/Dashboard.vue')['default']
-    ElAside: typeof import('element-plus/es')['ElAside']
-    ElAvatar: typeof import('element-plus/es')['ElAvatar']
+    DropFile: typeof import('./src/components/DropFile.vue')['default']
+    ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
-    ElContainer: typeof import('element-plus/es')['ElContainer']
-    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
-    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
-    ElMain: typeof import('element-plus/es')['ElMain']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
     Header: typeof import('./src/components/Header.vue')['default']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
     Login: typeof import('./src/components/Login.vue')['default']

+ 10 - 0
src/api/_prefix.ts

@@ -0,0 +1,10 @@
+export const API_MODULE = '/api'
+
+//用户模块
+export const USER_MODULE= `${API_MODULE}/user`
+//商店模块
+export const STORE_MODULE= `${API_MODULE}/store`
+//商品模块
+export const PRODUCT_MODULE= `${API_MODULE}/product`
+//工具模块
+export const TOOL_MODULE= `${API_MODULE}/tool`

+ 10 - 0
src/api/product.ts

@@ -0,0 +1,10 @@
+import {axios} from '../utils/request'
+import {PRODUCT_MODULE} from './_prefix'
+
+// 创建商店
+export const createProduct = payload => {
+    return axios.post(`${PRODUCT_MODULE}/create`, payload)
+        .then(res => {
+            return res;
+        })
+}

+ 26 - 0
src/api/store.ts

@@ -0,0 +1,26 @@
+import {axios} from '../utils/request'
+import {STORE_MODULE} from './_prefix'
+
+// 创建商店
+export const createStore = payload => {
+    return axios.post(`${STORE_MODULE}/create`, payload)
+        .then(res => {
+        return res;
+    })
+}
+
+// 获取全部商店
+export const getAllStore = payload => {
+    return axios.get(`${STORE_MODULE}/all`, payload)
+        .then(res => {
+            return res;
+        })
+}
+
+// 获取指定商店
+export const getStoreById = storeId => {
+    return axios.get(`${STORE_MODULE}/?id=${storeId}`)
+        .then(res => {
+            return res;
+        })
+}

+ 10 - 0
src/api/tool.ts

@@ -0,0 +1,10 @@
+import {axios} from '../utils/request'
+import {TOOL_MODULE} from './_prefix'
+
+// 上传文件
+export const uploadFile = payload => {
+    return axios.post(`${TOOL_MODULE}/upload`, payload)
+        .then(res => {
+            return res;
+        })
+}

+ 91 - 0
src/components/DropFile.vue

@@ -0,0 +1,91 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const isDragging = ref(false)
+const files = ref([])
+const fileInputRef = ref(null)
+
+function onChange() {
+  files.value = [...fileInputRef.value.files]
+}
+
+function dragover(e) {
+  e.preventDefault()
+  isDragging.value = true
+}
+
+function dragleave() {
+  isDragging.value = false
+}
+
+function drop(e) {
+  e.preventDefault()
+  fileInputRef.value.files = e.dataTransfer.files
+  onChange()
+  isDragging.value = false
+}
+
+function remove(i) {
+  files.value.splice(i, 1);
+}
+
+function generateURL(file) {
+  let fileSrc = URL.createObjectURL(file);
+  setTimeout(() => {
+    URL.revokeObjectURL(fileSrc);
+  }, 1000);
+  return fileSrc;
+}
+</script>
+
+<template>
+  <div class="main">
+    <div
+        class="dropzone-container"
+        @dragover="dragover"
+        @dragleave="dragleave"
+        @drop="drop"
+    >
+      <input
+          type="file"
+          multiple
+          name="file"
+          id="fileInput"
+          class="hidden-input"
+          @change="onChange"
+          ref="fileInputRef"
+          accept=".pdf,.jpg,.jpeg,.png"
+      />
+
+      <label for="fileInput" class="file-label">
+        <div v-if="isDragging">释放以将文件放到此处。</div>
+        <div v-else>将文件拖到此处或单击此处上传。</div>
+      </label>
+
+      <div class="preview-container mt-4" v-if="files.length">
+        <div v-for="file in files" :key="file.name" class="preview-card">
+          <div>
+            <img class="preview-img" :src="generateURL(file)" />
+            <p>
+              {{ file.name }}
+            </p>
+          </div>
+          <div>
+            <button
+                class="ml-2"
+                type="button"
+                @click="remove(files.indexOf(file))"
+                title="Remove file"
+            >
+              <b>×</b>
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 2 - 1
src/components/Header.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import {UserFilled, SwitchButton} from "@element-plus/icons-vue";
+
 </script>
 
 <template>
@@ -33,4 +34,4 @@ import {UserFilled, SwitchButton} from "@element-plus/icons-vue";
 </template>
 
 <style scoped>
-</style>
+</style>

+ 5 - 5
src/components/Login.vue

@@ -2,11 +2,11 @@
   import {ElForm, ElFormItem} from "element-plus";
   import { ref, computed } from 'vue';
   import axios from "axios";
-  import {router} from '../router'
+  import {router} from '../router';
 
   // 输入框值
-  const tel = ref('')
-  const password = ref('')
+  const tel = ref('');
+  const password = ref('');
 
   // 用于前端阻拦不合法输入
   // const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -33,7 +33,7 @@
   async function handleLogin() {
 
     try {
-      const response = await axios.post('/user/login', {
+      const response = await axios.post('api/user/login', {
         params: {
           phone: tel.value,
           password: password.value,
@@ -130,4 +130,4 @@
 
 <style scoped>
 
-</style>
+</style>

+ 1 - 1
src/main.ts

@@ -4,7 +4,7 @@ import {router} from './router'
 import App from './App.vue'
 import axios from 'axios'
 
-axios.defaults.baseURL = ("http://localhost:1234/api")
+axios.defaults.baseURL = ("http://localhost:8080/api")
 axios.defaults.timeout = 30000;
 
 createApp(App).use(router).mount('#app')

+ 4 - 1
src/router/index.ts

@@ -25,7 +25,10 @@ const router = createRouter({
                 }
             },
         ]
+    }, {
+        path: '/manager/createStore',
+        component: () => import('../views/MANAGER/createStore.vue'),
     }]
 })
 
-export {router};
+export {router};

+ 32 - 0
src/style.css

@@ -20,3 +20,35 @@ html, body {
   padding: 0;
   height: 100%;
 }
+
+@layer components {
+  .main {
+    @apply flex grow items-center justify-center h-screen text-center;
+    height: auto;
+  }
+
+  .dropzone-container {
+    @apply p-16 bg-[#f7fafc] border border-solid border-[#e2e8f0];
+  }
+
+  .hidden-input {
+    @apply opacity-0 overflow-hidden absolute w-1 h-2;
+  }
+
+  .file-label {
+    @apply text-xl block cursor-pointer;
+    font-size: 10pt;
+  }
+
+  .preview-container {
+    @apply flex mt-8;
+  }
+
+  .preview-card {
+    @apply flex border border-solid border-[#a2a2a2] p-2 ml-2;
+  }
+
+  .preview-img {
+    @apply w-14 h-14 rounded-md border border-solid border-[#a2a2a2] bg-[#a2a2a2];
+  }
+}

+ 34 - 0
src/utils/request.ts

@@ -0,0 +1,34 @@
+import axios from 'axios';
+
+const service = axios.create({
+    baseURL: "",
+});
+
+service.interceptors.request.use(
+    config => {
+        return config;
+    },
+    error => {
+        console.log(error);
+        return Promise.reject();
+    }
+);
+
+service.interceptors.response.use(
+    response => {
+        if (response.status === 200) {
+            return response.data;
+        } else {
+            Promise.reject();
+        }
+    },
+    error => {
+        console.log(error);
+        return Promise.reject();
+    }
+);
+
+export default service;
+export {
+    service as axios
+}

+ 167 - 0
src/views/MANAGER/createStore.vue

@@ -0,0 +1,167 @@
+<script setup lang="ts">
+import {ElForm, ElFormItem, ElMessage} from "element-plus";
+import {ref, computed} from 'vue';
+import {createStore} from "../../api/store.ts";
+import {uploadFile} from "../../api/tool.ts";
+
+// 输入框值
+const name = ref('');
+const location = ref('')
+
+// 用于前端阻拦不合法输入
+const hasNameInput = computed(() => name.value != '')
+const hasLocationInput = computed(() => location.value != '')
+
+const nameLegal = hasNameInput
+const locationLegal = hasLocationInput
+
+const isDragging = ref(false)
+const files = ref([])
+const fileInputRef = ref(null)
+
+function onChange() {
+  files.value = [...fileInputRef.value.files]
+}
+
+function dragover(e) {
+  e.preventDefault()
+  isDragging.value = true
+}
+
+function dragleave() {
+  isDragging.value = false
+}
+
+function drop(e) {
+  e.preventDefault()
+  fileInputRef.value.files = e.dataTransfer.files
+  onChange()
+  isDragging.value = false
+}
+
+function remove(i) {
+  files.value.splice(i, 1);
+  console.log(files.value)
+}
+
+const createDisabled = computed(() => {
+  return !(nameLegal.value && locationLegal.value && files.value);
+})
+
+function handleCreateStore() {
+  const payload1 = {
+    name: name,
+    location: location,
+    logoUrl: logoUrl
+  };
+  const payload2 = {
+    file: files[0]
+  };
+  createStore(payload1).then(res => {
+    uploadFile(payload2).then(res => {
+      ElMessage.success(`添加商户成功!`);
+    });
+  });
+
+
+}
+</script>
+
+<template>
+  <div class="flex flex-col items-center justify-center px-6 py-6 space-y-10 min-h-screen">
+
+    <span class="flex items-center text-2xl font-semibold text-gray-900">
+      Blue Whale Shopping Online
+    </span>
+
+    <el-card class="w-full bg-white rounded-lg shadow-md md:mt-0 sm:max-w-md xl:p-0">
+
+      <div class="p-6 space-y-4 md:space-y-6 sm:p-8">
+
+        <h1 class="text-2xl font-bold leading-tight tracking-tight text-gray-900">
+          添加商店
+        </h1>
+
+        <el-form class="space-y-4 md:space-y-6">
+
+          <el-form-item>
+            <label for="name" class="inline-block mb-2 text-sm font-medium text-gray-900">商店名</label>
+            <input id="name" v-model="name"
+                   required
+                   class="bg-gray-50 border border-gray-300 text-gray-900 focus:outline-primary-600 sm:text-sm rounded-lg block w-full p-2.5"
+                   placeholder="请输入商店名">
+          </el-form-item>
+
+          <el-form-item>
+            <label for="location" class="inline-block mb-2 text-sm font-medium text-gray-900">商店地址</label>
+            <input id="location" v-model="location"
+                   required
+                   class="bg-gray-50 border border-gray-300 text-gray-900 focus:outline-primary-600 sm:text-sm rounded-lg block w-full p-2.5"
+                   placeholder="楼层-门牌号 如:3楼-305">
+          </el-form-item>
+
+          <el-form-item>
+            <label for="logoUrl" class="inline-block mb-2 text-sm font-medium text-gray-900">商店Logo</label>
+            <div class="main">
+              <div
+                  class="dropzone-container"
+                  @dragover="dragover"
+                  @dragleave="dragleave"
+                  @drop="drop"
+              >
+                <input
+                    type="file"
+                    multiple
+                    name="file"
+                    id="fileInput"
+                    class="hidden-input"
+                    @change="onChange"
+                    ref="fileInputRef"
+                    accept=".pdf,.jpg,.jpeg,.png"
+                />
+
+                <label for="fileInput" class="file-label">
+                  <div v-if="isDragging">释放以将文件放到此处。</div>
+                  <div v-else>将文件拖到此处或单击此处上传。</div>
+                </label>
+
+                <div class="preview-container mt-4" v-if="files.length">
+                  <div v-for="file in files" :key="file.name" class="preview-card">
+                    <div>
+                      <img class="preview-img" :src="generateURL(file)" />
+                      <p>
+                        {{ file.name }}
+                      </p>
+                    </div>
+                    <div>
+                      <button
+                          class="ml-2"
+                          type="button"
+                          @click="remove(files.indexOf(file))"
+                          title="Remove file"
+                      >
+                        <b>×</b>
+                      </button>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-form-item>
+
+          <el-form-item>
+            <button @click.prevent="handleCreateStore()"
+                    :disabled="createDisabled"
+                    class="block my-3 w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-3 text-center disabled:bg-primary-100">
+              添加
+            </button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 29 - 14
vite.config.ts

@@ -1,25 +1,40 @@
-import { defineConfig } from 'vite'
+import {defineConfig} from 'vite'
 import vue from '@vitejs/plugin-vue'
 import tailwindcss from 'tailwindcss'
 
 // Element UI 自动导入支持
 import AutoImport from 'unplugin-auto-import/vite'
 import Components from 'unplugin-vue-components/vite'
-import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+import {ElementPlusResolver} from 'unplugin-vue-components/resolvers'
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [
-      vue(),
-      AutoImport({
-        resolvers: [ElementPlusResolver()],
-      }),
-      Components({
-        resolvers: [ElementPlusResolver()],
-      })],
+    plugins: [
+        vue(),
+        AutoImport({
+            resolvers: [ElementPlusResolver()],
+        }),
+        Components({
+            resolvers: [ElementPlusResolver()],
+        })],
     css: {
-      postcss: {
-          plugins: [tailwindcss],
-      }
-    }
+        postcss: {
+            plugins: [tailwindcss],
+        }
+    },
+    // server: {
+    //     proxy: {
+    //         '/api': {
+    //             target: 'http://localhost:8080/api',
+    //             changeOrigin: true,
+    //             rewrite: (path) => path.replace(/^\/api/, '') // 不可以省略rewrite
+    //         }
+    //     }
+    // },
+    devServer: {
+        port: 5173,
+        open: true,
+        hot: true,//自动保存
+    },
+
 })