13 Комити 3b46d42752 ... 0e8819f155

Аутор SHA1 Порука Датум
  kiboky 0e8819f155 delete 기능 추가 пре 5 година
  kiboky 1265103f9b nuxt-link 네비게이션 적용 пре 5 година
  kiboky cce009f60c 상품 수정 기능 추가 пре 5 година
  kiboky 2eb95abb25 owner 기능 추가 пре 5 година
  kiboky 4d46012d5d 카테고리 저장 기능 추가 пре 5 година
  kiboky d6deeb0b76 cors 적용 пре 5 година
  kiboky f8678dccae 상품 post formData 로 저장 пре 5 година
  kiboky a20bbd39a1 비동기 요청 배열 처리 > await + Promise.all пре 5 година
  kiboky 4d78a6d846 product (파일업로드) 화면 디자인 пре 5 година
  kiboky 831f3dae64 조회 및 css 추가 пре 5 година
  kiboky 726c88821a 상품목록페이지 퍼블리싱 пре 5 година
  kiboky 021985cb83 amazon css 반영 пре 5 година
  kiboky 5af91131af improve пре 5 година

+ 3 - 1
admin/nuxt.config.js

@@ -12,7 +12,9 @@ export default {
       { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
     ],
     link: [
-      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
+      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
+      { rel: 'stylesheet',  href: '/css/font-awesome/css/all.css' },
+      { rel: 'stylesheet',  href: '/css/default.css' }
     ]
   },
   /*

+ 2 - 6
admin/package-lock.json

@@ -8830,7 +8830,6 @@
             "minipass": {
               "version": "2.9.0",
               "bundled": true,
-              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.2",
                 "yallist": "^3.0.0"
@@ -8939,7 +8938,6 @@
             "once": {
               "version": "1.4.0",
               "bundled": true,
-              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -9103,13 +9101,11 @@
             },
             "wrappy": {
               "version": "1.0.2",
-              "bundled": true,
-              "optional": true
+              "bundled": true
             },
             "yallist": {
               "version": "3.1.1",
-              "bundled": true,
-              "optional": true
+              "bundled": true
             }
           }
         },

+ 62 - 0
admin/pages/category.vue

@@ -0,0 +1,62 @@
+<template>
+  <main>
+    <div class="container-fluid c-section">
+      <div class="row">
+        <div class="col-sm-3"></div>
+        <div class="col-sm-6">
+          <div class="a-spacing-top-medium"></div>
+          <h2>Add a new Category</h2>
+          <form action="">
+            <div class="a-spacing-top-medium">
+              <label for="">Type</label>
+              <input v-model="type" type="text" class="a-input-text" />
+            </div>
+            <hr/>
+            <!--Button-->
+            <div class="a-spacing-top-large">
+              <span class="a-button-register">
+                <span @click="onAddCategory" class="a-button-text">Add Category</span>
+              </span>
+            </div>
+          </form>
+          <br/>
+          <ul class="list-group-item">
+            <li v-for="category in categories" :key="category._id">{{ category.type }}</li>
+          </ul>
+        </div>
+        <div class="col-sm-3"></div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script>
+export default {
+  async asyncData ({ $axios }) {
+    try {
+      let response = await $axios.$get('http://localhost:3000/api/categories')
+      return {
+        categories: response.categories
+      }
+    } catch (err) {
+      console.log(err)
+    }
+  },
+  data () {
+    return {
+      type: ''
+    }
+  },
+  methods: {
+    async onAddCategory () {
+      try {
+        let data = { type: this.type }
+        let response = await this.$axios.$post('http://localhost:3000/api/categories', data)
+        this.categories.push(data)
+      } catch (err) {
+        console.log(err)
+      }
+    }
+  }
+}
+</script>

+ 80 - 57
admin/pages/index.vue

@@ -1,72 +1,95 @@
 <template>
-  <div class="container">
-    <div>
-      <logo />
-      <h1 class="title">
-        admin
-      </h1>
-      <h2 class="subtitle">
-        My ultimate Nuxt.js project
-      </h2>
-      <div class="links">
-        <a
-          href="https://nuxtjs.org/"
-          target="_blank"
-          class="button--green"
-        >
-          Documentation
-        </a>
-        <a
-          href="https://github.com/nuxt/nuxt.js"
-          target="_blank"
-          class="button--grey"
-        >
-          GitHub
-        </a>
+  <main>
+    <div class="a-spacing-large"></div>
+    <div class="container-fluid browsing-history">
+      <div class="row">
+        <div class="col-sm-8 col-8">
+          <h1 class="a-size-large a-zpacing-none a-text-normal">All products</h1>
+          <div class="a-spacing-large"></div>
+
+          <!-- Burron -->
+          <nuxt-link to="/products" class="a-button-buy-again">Add a new product</nuxt-link>
+          <nuxt-link to="/category" class="a-button-history margin-right-10">Add a new category</nuxt-link>
+          <nuxt-link to="/owner" class="a-button-history margin-right-10">Add a new owner</nuxt-link>
+        </div>
+      </div>
+    </div>
+
+    <div class="a-spacing-large"></div>
+    <!-- Listing Page -->
+    <div class="container-fluid browsing-history">
+      <div class="row">
+        <div v-for="(product, index) in products" :key="product._id"  class="col-xl-2 col-lg-2 col-md3 col-sm-6 col-6 br bb">
+          <div class="history-box">
+            <!-- Product Image -->
+            <a href="" class="a-link-normal">
+              <img :src="product.photo" alt="" class="img-fluid" />
+            </a>
+
+            <div class="a-spacing-top-base asin-title">
+              <span class="a-text-normal">
+                <div class="p13n-sc-truncated">{{ product.title }}</div>
+              </span>
+            </div>
+
+            <div class="a-row">
+              <a href="">
+                <i class="fas fa-star"></i>
+                <i class="fas fa-star"></i>
+                <i class="fas fa-star"></i>
+                <i class="fas fa-star"></i>
+                <i class="fas fa-star"></i>
+              </a>
+              <span class="a-letter-space"></span>
+              <span class="a-color-tertiary a-size-small asin-reviews">(1732)</span>
+            </div>
+
+            <!-- Product price -->
+            <div class="a-row">
+              <span class="a-size-base a-color-price">
+                <span class="p13n-sc-price">{{ product.price }}</span>
+              </span>
+            </div>
+
+            <!-- Product Buttons -->
+            <div class="a-row">
+              <nuxt-link :to="`/products/${product._id}`" class="a-button-history margin-right-10">Update</nuxt-link>
+              <a href="#" @click="onDeleteProduct(product._id, index)" class="a-button-history margin-right-10">Delete</a>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
-  </div>
+  </main>
 </template>
 
 <script>
-import Logo from '~/components/Logo.vue'
 
 export default {
-  components: {
-    Logo
+  async asyncData ({ $axios }) {
+    try {
+      let response = await $axios.$get('http://localhost:3000/api/products')
+      console.log(response)
+      return {
+        products: response.products
+      }
+    } catch (err) {
+
+    }
+  },
+  methods: {
+    async onDeleteProduct (id, index) {
+      try {
+        let response = await this.$axios.$delete(`http://localhost:3000/api/products/${id}`)
+        this.products.splice(index, 1)
+      } catch (err) {
+        console.log(err)
+      }
+    }
   }
 }
 </script>
 
 <style>
-.container {
-  margin: 0 auto;
-  min-height: 100vh;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  text-align: center;
-}
-
-.title {
-  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
-    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
-  display: block;
-  font-weight: 300;
-  font-size: 100px;
-  color: #35495e;
-  letter-spacing: 1px;
-}
 
-.subtitle {
-  font-weight: 300;
-  font-size: 42px;
-  color: #526488;
-  word-spacing: 5px;
-  padding-bottom: 15px;
-}
-
-.links {
-  padding-top: 15px;
-}
 </style>

+ 98 - 0
admin/pages/owner.vue

@@ -0,0 +1,98 @@
+<template>
+  <main>
+    <div class="container-fluid c-section">
+      <div class="row">
+        <div class="col-sm-3"></div>
+        <div class="col-sm-6">
+          <div class="a-spacing-top-medium"></div>
+          <h2>Add a new Owner</h2>
+          <form action="">
+            <!-- name -->
+            <div class="a-spacing-top-medium">
+              <label for="">Name</label>
+              <input v-model="name" type="text" class="a-input-text" />
+            </div>
+
+            <!-- About -->
+            <div class="a-spacing-top-medium">
+              <label for="">About</label>
+              <input v-model="about" type="text" class="a-input-text" />
+            </div>
+
+            <!-- Photo input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Photo</label>
+              <div class="a-row a-spacing-top-medium">
+                <label class="choosefile-button">
+                  <i class="fa fa-plus"></i>
+                  <input @change="onFileSelected" type="file" />
+                  <p style="margin-top: -70px">{{ fileName }}</p>
+                </label>
+              </div>
+            </div>
+
+
+            <hr/>
+            <!--Button-->
+            <div class="a-spacing-top-large">
+              <span class="a-button-register">
+                <span @click="onAddOwner" class="a-button-text">Add Owner</span>
+              </span>
+            </div>
+          </form>
+          <br/>
+          <ul class="list-group-item">
+            <li v-for="owner in owners" :key="owner._id">{{ owner.name }}</li>
+          </ul>
+        </div>
+        <div class="col-sm-3"></div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script>
+export default {
+  async asyncData ({ $axios }) {
+    try {
+      let response = await $axios.$get('http://localhost:3000/api/owners')
+      console.log(response)
+      return {
+        owners: response.owners
+      }
+    } catch (err) {
+      console.log(err)
+    }
+  },
+  data () {
+    return {
+      name: '',
+      about: '',
+      selectedFile: null,
+      fileName: ''
+    }
+  },
+  methods: {
+    onFileSelected (event) {
+      this.selectedFile = event.target.files[0]
+      console.log(this.selectedFile)
+      this.fileName = event.target.files[0].name
+    },
+
+    async onAddOwner () {
+      try {
+        let data = new FormData()
+
+        data.append('name', this.name)
+        data.append('about', this.about)
+        data.append('photo', this.selectedFile, this.selectedFile.name)
+
+        let result = await this.$axios.$post('http://localhost:3000/api/owners', data)
+        this.owners.push({ name: this.name })
+      } catch (err) {
+        console.log(err)
+      }
+    }
+  }
+}
+</script>

+ 134 - 0
admin/pages/products/_id.vue

@@ -0,0 +1,134 @@
+<template>
+  <main>
+    <div class="container-fluid">
+
+        <div class="col-sm3"></div>
+        <div class="col-sm6">
+          <div class="a-spacing-top-medium"></div>
+          <h2 style="text-align: center">Update product</h2>
+          <form action="">
+            <!-- Category DropDown -->
+            <div class="a-spacing-top-medium">
+              <label for="">Category</label>
+              <select  v-model="categoryID" class="a-select-option">
+                <option v-for="category in categories" :key="category._id" :value="category._id">{{ category.type }}</option>
+              </select>
+            </div>
+
+            <!-- Owner DropDown -->
+            <div class="a-spacing-top-medium">
+              <label for="">Owner</label>
+              <select v-model="ownerID" class="a-select-option" >
+                <option v-for="owner in owners" :key="owner._id" :value="owner._id">{{ owner.name }}</option>
+              </select>
+            </div>
+
+            <!-- Title input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Title</label>
+              <input v-model="title" :placeholder="product.title" type="text" class="a-input-text" style="width: 100%"/>
+            </div>
+
+            <!-- stockQuantity input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">stockQuantity</label>
+              <input v-model="stockQuantity" :placeholder="product.stockQuantity" type="number" class="a-input-text" style="width: 100%"/>
+            </div>
+
+
+            <!-- Price input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Price</label>
+              <input v-model="price" :placeholder="product.price" type="number" class="a-input-text" style="width: 100%"/>
+            </div>
+
+            <!-- Description input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Description</label>
+              <textarea v-model="description" :placeholder="product.description" style="width: 100%"></textarea>
+            </div>
+
+            <!-- Photo input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Photo</label>
+              <div class="a-row a-spacing-top-medium">
+                <label class="choosefile-button">
+                  <i class="fa fa-plus"></i>
+                  <input @change="onFileSelected" type="file" />
+                  <p style="margin-top: -70px">{{ fileName }}</p>
+                </label>
+              </div>
+            </div>
+            <hr/>
+
+            <!--Button-->
+            <div class="a-spacing-top-large">
+              <span class="a-button-register">
+                <span @click="onUpdateProduct" class="a-button-text">Update Product</span>
+              </span>
+            </div>
+          </form>
+        </div>
+        <div class="col-sm3"></div>
+
+    </div>
+  </main>
+</template>
+
+<script>
+export default {
+  async asyncData( { $axios, params }) {
+    try {
+      let categories = $axios.$get('http://localhost:3000/api/categories')
+      let owners = $axios.$get('http://localhost:3000/api/owners')
+      let product = $axios.$get(`http://localhost:3000/api/products/${params.id}`)
+
+      const [catResponse, ownerResponse, productResponse] = await Promise.all([categories, owners, product])
+
+      return {
+        categories: catResponse.categories,
+        owners: ownerResponse.owners,
+        product: productResponse.product
+      }
+    } catch (err) {
+      console.log(err)
+    }
+  },
+
+  data () {
+    return {
+      categoryID: null,
+      ownerID: null,
+      title: '',
+      price: null,
+      description: '',
+      selectedFile: null,
+      fileName: '',
+      stockQuantity: null
+    }
+  },
+
+  methods: {
+    onFileSelected (event) {
+      this.selectedFile = event.target.files[0]
+      console.log(this.selectedFile)
+      this.fileName = event.target.files[0].name
+    },
+
+    async onUpdateProduct () {
+      let data = new FormData()
+      data.append('title', this.title)
+      data.append('price', this.price)
+      data.append('stockQuantity', this.stockQuantity)
+      data.append('description', this.description)
+      data.append('ownerID', this.ownerID)
+      data.append('categoryID', this.categoryID)
+      data.append('photo', this.selectedFile, this.selectedFile.name)
+
+      let result = await this.$axios.$put(`http://localhost:3000/api/products/${this.$route.params.id}`, data)
+
+      this.$router.push('/')
+    }
+  }
+}
+</script>

+ 133 - 0
admin/pages/products/index.vue

@@ -0,0 +1,133 @@
+<template>
+  <main>
+    <div class="container-fluid">
+
+        <div class="col-sm3"></div>
+        <div class="col-sm6">
+          <div class="a-spacing-top-medium"></div>
+          <h2 style="text-align: center">Add a new Product</h2>
+          <form action="">
+            <!-- Category DropDown -->
+            <div class="a-spacing-top-medium">
+              <label for="">Category</label>
+              <select  v-model="categoryID" class="a-select-option">
+                <option v-for="category in categories" :key="category._id" :value="category._id">{{ category.type }}</option>
+              </select>
+            </div>
+
+            <!-- Owner DropDown -->
+            <div class="a-spacing-top-medium">
+              <label for="">Owner</label>
+              <select v-model="ownerID" class="a-select-option" >
+                <option v-for="owner in owners" :key="owner._id" :value="owner._id">{{ owner.name }}</option>
+              </select>
+            </div>
+
+            <!-- Title input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Title</label>
+              <input v-model="title" type="text" class="a-input-text" style="width: 100%"/>
+            </div>
+
+            <!-- stockQuantity input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">stockQuantity</label>
+              <input v-model="stockQuantity" type="number" class="a-input-text" style="width: 100%"/>
+            </div>
+
+
+            <!-- Price input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Price</label>
+              <input v-model="price" type="number" class="a-input-text" style="width: 100%"/>
+            </div>
+
+            <!-- Description input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Description</label>
+              <textarea v-model="description" placeholder="Provide details product description" style="width: 100%"></textarea>
+            </div>
+
+            <!-- Photo input-->
+            <div class="a-spacing-top-medium">
+              <label for="" style="margin-bottom: 0px;">Photo</label>
+              <div class="a-row a-spacing-top-medium">
+                <label class="choosefile-button">
+                  <i class="fa fa-plus"></i>
+                  <input @change="onFileSelected" type="file" />
+                  <p style="margin-top: -70px">{{ fileName }}</p>
+                </label>
+              </div>
+            </div>
+            <hr/>
+
+            <!--Button-->
+            <div class="a-spacing-top-large">
+              <span class="a-button-register">
+                <span @click="onAddProduct" class="a-button-text">Add Product</span>
+              </span>
+            </div>
+          </form>
+        </div>
+        <div class="col-sm3"></div>
+
+    </div>
+  </main>
+</template>
+
+<script>
+export default {
+  async asyncData( { $axios }) {
+    try {
+      let categories = $axios.$get('http://localhost:3000/api/categories')
+      let owners = $axios.$get('http://localhost:3000/api/owners')
+
+      const [catResponse, ownerResponse] = await Promise.all([categories, owners])
+
+      console.log(catResponse)
+      return {
+        categories: catResponse.categories,
+        owners: ownerResponse.owners
+      }
+    } catch (err) {
+      console.log(err)
+    }
+  },
+
+  data () {
+    return {
+      categoryID: null,
+      ownerID: null,
+      title: '',
+      price: 0,
+      description: '',
+      selectedFile: null,
+      fileName: '',
+      stockQuantity: 1
+    }
+  },
+
+  methods: {
+    onFileSelected (event) {
+      this.selectedFile = event.target.files[0]
+      console.log(this.selectedFile)
+      this.fileName = event.target.files[0].name
+    },
+
+    async onAddProduct () {
+      let data = new FormData()
+      data.append('title', this.title)
+      data.append('price', this.price)
+      data.append('stockQuantity', this.stockQuantity)
+      data.append('description', this.description)
+      data.append('ownerID', this.ownerID)
+      data.append('categoryID', this.categoryID)
+      data.append('photo', this.selectedFile, this.selectedFile.name)
+
+      let result = await this.$axios.$post('http://localhost:3000/api/products', data)
+
+      this.$router.push('/')
+    }
+  }
+}
+</script>

Разлика између датотеке није приказан због своје велике величине
+ 4423 - 0
admin/static/css/font-awesome/css/all.css


+ 3 - 0
package-lock.json

@@ -0,0 +1,3 @@
+{
+  "lockfileVersion": 1
+}

+ 9 - 0
server/package-lock.json

@@ -171,6 +171,15 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
+    "cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "requires": {
+        "object-assign": "^4",
+        "vary": "^1"
+      }
+    },
     "debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",

+ 1 - 0
server/package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "aws-sdk": "^2.596.0",
     "body-parser": "^1.19.0",
+    "cors": "^2.8.5",
     "dotenv": "^8.2.0",
     "express": "^4.17.1",
     "mongoose": "^5.8.3",

+ 3 - 2
server/routes/owner.js

@@ -1,12 +1,13 @@
 const router = require('express').Router()
 const Owner = require('../models/owner')
+const upload = require('../middlewares/upload-photo')
 
-router.post('/owners', async (req, res)=> {
+router.post('/owners', upload.single('photo'), async (req, res)=> {
   try {
     const owner = new Owner()
     owner.name = req.body.name
     owner.about = req.body.about
-    
+    owner.photo = req.file.location
     await owner.save()
 
     res.json({

+ 3 - 0
server/routes/products.js

@@ -5,6 +5,9 @@ const upload = require('../middlewares/upload-photo')
 router.post('/products', upload.single('photo'), async (req, res)=> {
   try {
     let product = new Product()
+    product.ownerID = req.body.ownerID
+    product.categoryID = req.body.categoryID
+    product.price = req.body.price
     product.title = req.body.title
     product.description = req.body.description
     product.photo = req.file.location

+ 2 - 0
server/server.js

@@ -3,6 +3,7 @@ const morgan = require('morgan')
 const bodyParser = require('body-parser')
 const mongoose = require('mongoose')
 const dotenv = require('dotenv')
+const cors = require('cors')
 
 const User = require('./models/user')
 
@@ -21,6 +22,7 @@ mongoose.connect(
   }
 )
 
+app.use(cors())
 app.use(morgan('dev'))
 app.use(bodyParser.json())
 app.use(bodyParser.urlencoded({ extended: false }))