Procházet zdrojové kódy

feat: 前台查询、详情页面-接口调整

dx před 6 měsíci
rodič
revize
2682bdc962

+ 1 - 2
.env.development

@@ -5,5 +5,4 @@ VITE_APP_TITLE = xxx
 VITE_APP_ENV = 'development'
 
 # 若依管理系统/开发环境
-VITE_APP_BASE_API = '/dev-api'
-# VITE_APP_BASE_API = 'http://172.16.10.65:8080'
+VITE_APP_BASE_API = 'http://172.16.10.65:8080'

+ 2 - 0
package.json

@@ -11,8 +11,10 @@
     "format": "prettier --write src/"
   },
   "dependencies": {
+    "@vant/touch-emulator": "^1.4.0",
     "@vant/use": "^1.6.0",
     "axios": "^1.6.8",
+    "clipboard": "^2.0.11",
     "element-plus": "^2.7.2",
     "pinia": "^2.1.7",
     "vant": "^4.9.0",

+ 18 - 0
src/api/index.js

@@ -7,3 +7,21 @@ export const getRouters = () => {
     method: 'get'
   })
 }
+
+// 查询参数列表
+export function listTools(query) {
+  return request({
+    url: '/tool/basis/reception/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 详情
+export function detailList(data) {
+  return request({
+    url: '/tool/basis/reception/detailList',
+    method: 'post',
+    data: data
+  })
+}

binární
src/assets/images/detail_bg.png


binární
src/assets/images/detail_top_bg.png


binární
src/assets/images/download.png


+ 1 - 0
src/main.js

@@ -7,6 +7,7 @@ import App from './App.vue'
 import router from './router'
 import ElementPlus from 'element-plus' // 引入element-plus
 import 'element-plus/dist/index.css' // 引入element-plus的样式
+import '@vant/touch-emulator' // 在桌面端自动将 mouse 事件转换成对应的 touch 事件
 
 const app = createApp(App)
 

+ 21 - 0
src/utils/request.js

@@ -10,4 +10,25 @@ const service = axios.create({
   timeout: 10000
 })
 
+// 响应拦截
+service.interceptors.response.use(
+  (res) => {
+    // 未设置状态码则默认成功状态
+    const code = res.data.code || 200
+    // 二进制数据则直接返回
+    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
+      return res.data
+    }
+    if (code !== 200) {
+      return Promise.reject('error')
+    } else {
+      return Promise.resolve(res.data)
+    }
+  },
+  (error) => {
+    console.log('err' + error)
+    return Promise.reject(error)
+  }
+)
+
 export default service

+ 222 - 2
src/views/DetailView.vue

@@ -1,7 +1,227 @@
 <template>
   <div class="AboutView">
-    <h1 class="text-3xl font-bold underline" @click="goBack">详情页</h1>
+    <div class="w-full h-[180px] relative flex items-center md:h-[380px]">
+      <div
+        class="w-full h-full bg-[url('/src/assets/images/detail_top_bg.png')] bg-no-repeat bg-center bg-cover absolute left-0 top-0 z-0"
+      ></div>
+      <div class="relative z-1 p-4 w-full md:w-[780px] m-auto">
+        <div
+          class="font-['PingFang-Heavy'] px-3 font-semibold text-xl md:text-[32px] md:leading-tight"
+        >
+          工具数字化展示
+        </div>
+        <div class="flex mt-6">
+          <div
+            class="ml-3"
+            v-for="typeItem in typeOptions1Ref"
+            :key="typeItem.id"
+            @click="handleReturn(typeItem)"
+          >
+            {{ typeItem.toolName }}
+          </div>
+        </div>
+      </div>
+    </div>
+    <section
+      class="w-full bg-[url('/src/assets/images/detail_bg.png')] bg-no-repeat bg-center bg-cover left-0 top-0 z-0 flex justify-center"
+    >
+      <div
+        class="w-[1440px] max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-7xl"
+      >
+        <div class="md:flex">
+          <div class="w-full md:w-[450px] md:shrink-0 p-3">
+            <div class="w-full h-[450px] tool-img border-2 flex items-center justify-center">
+              <van-image class="max-w-[450px] max-h-[450px] tool-img" :src="devPath + sytPath" />
+            </div>
+            <van-grid
+              square
+              :column-num="4"
+              class="h-full w-full object-cover md:h-[105px] mt-4"
+              :gutter="10"
+            >
+              <van-grid-item
+                v-for="syt in sytList"
+                :key="syt.id"
+                @click="changeImg(syt)"
+                :class="[syt.id === detailImg ? 'active' : '']"
+              >
+                <van-image class="max-w-[105px] max-h-[105px]" :src="devPath + syt.filePath" />
+              </van-grid-item>
+            </van-grid>
+          </div>
+          <div class="p-8 w-full">
+            <div
+              class="font-['PingFang-Heavy'] font-semibold text-xl md:text-[20px] md:leading-tight"
+            >
+              {{ queryToolName }}
+            </div>
+            <div
+              class="bg-[#E6F7FF] border border-[#004EA2] rounded px-3 text-[#004EA2] text-center mt-9 font-normal text-xs w-[200px] h-[22px] leading-[22px] truncate"
+            >
+              {{ breadTitle }}
+            </div>
+            <div class="font-['PingFang-SC'] text-sm font-normal mt-10 flex">
+              <div>物料名称:</div>
+              <div class="font-['PingFang-SC'] text-sm font-normal">{{ selectMaterialName }}</div>
+            </div>
+            <div class="font-['PingFang-SC'] text-sm font-normal mt-10 flex">
+              <div class="min-w-20">物料规格:</div>
+              <div>
+                <van-tag
+                  v-for="i in wlList"
+                  :key="i.id"
+                  :color="i.id === wlSelectId ? '#004EA2' : '#F0F0F0'"
+                  :text-color="i.id === wlSelectId ? '#FFFFFF' : '#000000'"
+                  class="m-1 h-[35px] leading-[35px]"
+                  @click="changeWlCode(i)"
+                  >{{ i.specifications }}</van-tag
+                >
+              </div>
+            </div>
+            <div class="font-['PingFang-SC'] text-sm font-normal mt-10 flex items-center">
+              <div>物料编码:</div>
+              <div>{{ selectWlCode }}</div>
+              <van-icon
+                v-show="selectWlCode"
+                name="description-o"
+                class="text-[#004EA2] description-o ml-1 cursor-pointer"
+                @click="codeCopy"
+              />
+            </div>
+            <div class="font-['PingFang-SC'] text-sm font-normal mt-10">
+              <div>附件:</div>
+              <van-cell-group>
+                <van-cell v-for="file in fileList" :key="file.id">
+                  <template #title>
+                    <span class="cursor-pointer" @click="fileDownload(file)">{{
+                      file.fileName
+                    }}</span>
+                  </template>
+                  <template #right-icon>
+                    <img
+                      class="w-[18px] h-[18px] cursor-pointer"
+                      src="../assets/images/download.png"
+                      alt=""
+                      @click="fileDownload(file)"
+                    />
+                  </template>
+                </van-cell>
+              </van-cell-group>
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+    <!-- <h1 class="text-3xl font-bold underline" @click="goBack">详情页</h1> -->
   </div>
 </template>
 
-<script setup name="AboutView"></script>
+<script setup name="AboutView">
+import { ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { detailList } from '@/api/index.js'
+import Clipboard from 'clipboard'
+import { listTools } from '@/api/index.js'
+
+const router = useRouter()
+const route = useRoute()
+const queryId = ref(route.query.id)
+const queryToolName = ref(route.query.toolName)
+console.log('route.query.id', queryId.value)
+
+const wlList = ref([])
+const wlSelectId = ref(0)
+const selectWlCode = ref('')
+const selectMaterialName = ref('')
+const sytList = ref([])
+const fileList = ref([])
+const detailImg = ref(0)
+const sytPath = ref('')
+const devPath = import.meta.env.VITE_APP_BASE_API
+const breadTitle = ref('')
+const typeOptions1Ref = ref([])
+const init = () => {
+  detailList({ id: queryId.value }).then((res) => {
+    const toolDetails = res.data.toolDetails
+    wlList.value = toolDetails.filter((item) => item.informationType === 'wl')
+    console.log(wlList.value)
+    if (wlList.value.length > 0) {
+      selectWlCode.value = wlList.value[0].materialNo
+      selectMaterialName.value = wlList.value[0].materialName
+      wlSelectId.value = wlList.value[0].id
+    }
+    sytList.value = toolDetails.filter((item) => item.informationType === 'syt')
+    if (sytList.value.length > 0) {
+      detailImg.value = sytList.value[0].id
+      sytPath.value = sytList.value[0].filePath
+    }
+    fileList.value = toolDetails.filter(
+      (item) => item.informationType !== 'syt' && item.informationType !== 'wl'
+    )
+
+    const ttoolBasis = res.data.ttoolBasis
+    breadTitle.value = ttoolBasis.map((item) => item.toolName).join(' > ')
+  })
+
+  listTools({
+    level: '1',
+    pageSize: 999
+  }).then((res) => {
+    typeOptions1Ref.value = res.rows
+  })
+}
+init()
+const changeImg = ({ id, filePath }) => {
+  detailImg.value = id
+  sytPath.value = filePath
+}
+
+const codeCopy = async () => {
+  const clipboard = new Clipboard('.description-o', {
+    text: () => selectWlCode.value
+  })
+  // 添加成功事件处理函数
+  clipboard.on('success', (e) => {
+    e.clearSelection()
+  })
+  // 添加失败事件处理函数
+  clipboard.on('error', () => {
+    console.warn('无法复制到剪贴板!')
+  })
+}
+
+const changeWlCode = ({ id, materialNo, materialName }) => {
+  wlSelectId.value = id
+  selectWlCode.value = materialNo
+  selectMaterialName.value = materialName
+}
+//  informationType 物料  wl  点击切换 物料编码 materialNo
+//  informationType 示意图 syt 左侧图
+const fileDownload = ({ fileName, filePath }) => {
+  console.log('filePath', filePath)
+  const el = document.createElement('a')
+  el.style.display = 'none'
+  el.setAttribute('target', '_blank')
+
+  fileName && el.setAttribute('download', fileName)
+  el.href = devPath + filePath
+  document.body.appendChild(el)
+  el.click()
+  document.body.removeChild(el)
+}
+
+const handleReturn = (typeItem) => {
+  router.push({ path: '/', query: { id: typeItem.id, toolName: typeItem.toolName } })
+}
+</script>
+
+<style lang="scss" scoped>
+:deep(.van-grid-item__content--surround:after) {
+  border: 4px solid #e5e7eb;
+}
+.active {
+  :deep(.van-grid-item__content--surround:after) {
+    border: 4px solid #004ea2;
+  }
+}
+</style>

+ 133 - 39
src/views/HomeView.vue

@@ -20,6 +20,7 @@
             left-icon=""
             right-icon="search"
             @search="handleSearch"
+            @clear="handleClear"
             @click-right-icon="handleSearch(searchValueRef)"
           />
         </div>
@@ -49,12 +50,24 @@
             <van-dropdown-menu class="font-semibold">
               <van-dropdown-item
                 v-model="type1ValueRef"
-                :options="typeOptions1Ref"
+                :options="
+                  typeOptions1Ref.map((item) => ({
+                    text: item.toolName,
+                    value: item.id
+                  }))
+                "
+                title="一级分类"
                 @change="handleChangeDropdownOfType1"
               />
               <van-dropdown-item
                 v-model="type2ValueRef"
-                :options="typeOptions2Ref"
+                :options="
+                  typeOptions2Ref.map((item) => ({
+                    text: item.toolName,
+                    value: item.id
+                  }))
+                "
+                title="二级分类"
                 @change="handleChangeDropdownOfType2"
               />
             </van-dropdown-menu>
@@ -68,12 +81,12 @@
               <div class="menu-content">
                 <div
                   class="menu-item"
-                  :class="[typeItem.value === type1ValueRef ? 'active' : '']"
+                  :class="[typeItem.id === type1ValueRef ? 'active' : '']"
                   v-for="typeItem in typeOptions1Ref"
-                  :key="typeItem.value"
-                  @click="handleChangeDropdownOfType1(typeItem.value)"
+                  :key="typeItem.id"
+                  @click="handleChangeDropdownOfType1(typeItem.id)"
                 >
-                  {{ typeItem.text }}
+                  {{ typeItem.toolName }}
                 </div>
               </div>
             </div>
@@ -82,12 +95,12 @@
               <div class="menu-content">
                 <div
                   class="menu-item"
-                  :class="[typeItem.value === type2ValueRef ? 'active' : '']"
+                  :class="[typeItem.id === type2ValueRef ? 'active' : '']"
                   v-for="typeItem in typeOptions2Ref"
-                  :key="typeItem.value"
-                  @click="handleChangeDropdownOfType2(typeItem.value)"
+                  :key="typeItem.id"
+                  @click="handleChangeDropdownOfType2(typeItem.id)"
                 >
-                  {{ typeItem.text }}
+                  {{ typeItem.toolName }}
                 </div>
               </div>
             </div>
@@ -102,15 +115,17 @@
       @load="onLoad"
     >
       <div class="tool-card-content w-full px-4 flex flex-wrap md:max-w-[1440px] mx-auto">
-        <div class="tool-card" v-for="item in toolsListRef" :key="item">
-          <div class="tool-img bg-[url('/src/assets/images/2-001.png')]"></div>
+        <div class="tool-card" v-for="item in toolsListRef" :key="item" @click="handleDetail(item)">
+          <div class="tool-img" :class="`bg-[url('${devPath + item.picturePath}')]`"></div>
           <div class="px-3 pt-2 flex-1 flex flex-col">
-            <div class="font-[PingFang-Heavy] truncate">手枪式铆枪</div>
+            <div class="font-[PingFang-Heavy] truncate">{{ item.toolName }}</div>
             <div class="text-[#585F66] text-xs mt-1 line-clamp-2 break-all">
-              配10#20#30#40#拉头配10#20#30#40#拉头配10#20#30#40#拉头拉头配10#20#30#40#拉头
+              {{ item.describe }}
             </div>
             <div class="flex-1 flex items-center justify-end">
-              <span class="text-2xl font-['DIN'] font-semibold text-[#004EA2]">146</span>
+              <span class="text-2xl font-['DIN'] font-semibold text-[#004EA2]">{{
+                item.toolNum
+              }}</span>
               <span class="text-sm font-['PingFang-Heavy'] font-semibold ml-1 text-[#004EA2]"
                 >件</span
               >
@@ -129,6 +144,12 @@ import { ref, computed } from 'vue'
 import useAppStore from '@/stores/app.js'
 import { getFillNums } from '@/utils'
 import { hotToolsList, menuList1, menuList2, menuList3 } from './mock'
+import { listTools } from '@/api/index.js'
+import { useRoute, useRouter } from 'vue-router'
+
+const devPath = import.meta.env.VITE_APP_BASE_API
+const router = useRouter()
+const route = useRoute()
 
 const appStore = useAppStore()
 // 当前设备类型
@@ -143,44 +164,88 @@ const hotToolsRef = ref(hotToolsList)
 const type1ValueRef = ref('1')
 const type2ValueRef = ref('')
 // 分类列表1级
-const typeOptions1Ref = ref(menuList1)
+const typeOptions1Ref = ref([])
 // 分类列表2级
-const typeOptions2Ref = ref(menuList2)
+const typeOptions2Ref = ref([])
 
 const toolsListRef = ref([])
 const loadingRef = ref(false)
 const finished = ref(false)
 // 末尾补齐的个数
 const noneCardNum = ref(0)
-
 const onLoad = () => {
-  loadingRef.value = true
   // 异步更新数据
   // setTimeout 仅做示例,真实场景中一般为 ajax 请求
-  setTimeout(() => {
-    for (let i = 0; i < 20; i++) {
-      toolsListRef.value.push(toolsListRef.value.length + 1)
-    }
-    noneCardNum.value = getFillNums('.tool-card-content', toolsListRef.value, 220)
-
-    // 加载状态结束
-    loadingRef.value = false
-
-    // 数据全部加载完成
-    if (toolsListRef.value.length >= 100) {
-      finished.value = true
-    }
-  }, 1000)
+  // setTimeout(() => {
+  //   for (let i = 0; i < 20; i++) {
+  //     toolsListRef.value.push(toolsListRef.value.length + 1)
+  //   }
+  noneCardNum.value = getFillNums('.tool-card-content', toolsListRef.value, 220)
+  //   // 加载状态结束
+  loadingRef.value = false
+  //   // 数据全部加载完成
+  //   if (toolsListRef.value.length >= 100) {
+  //     finished.value = true
+  //   }
+  // }, 1000)
 }
+const init = () => {
+  loadingRef.value = false
+  // 一级分类
+  listTools({
+    level: '1',
+    pageSize: 999
+  }).then((res) => {
+    typeOptions1Ref.value = res.rows
+  })
+  if (route.query.id) {
+    searchValueRef.value = route.query.toolName
+    type1ValueRef.value = route.query.id
+    listTools({
+      level: '2',
+      parentId: route.query.id,
+      pageSize: 999
+    }).then((res) => {
+      typeOptions2Ref.value = res.rows
+    })
+    listTools({
+      level: '3',
+      parentId: route.query.id,
+      pageSize: 999,
+      delFlag: 0
+    }).then((res) => {
+      toolsListRef.value = res.rows
+    })
+    type2ValueRef.value = ''
+  } else {
+    Promise.allSettled([
+      // 二级分类
+      listTools({
+        level: '2',
+        pageSize: 999
+      }),
+      // 未选中分类 默认列表
+      listTools({
+        level: '3',
+        delFlag: 0,
+        pageSize: 999
+      })
+    ]).then((res) => {
+      typeOptions2Ref.value = res[0].value.rows
+      toolsListRef.value = res[1].value.rows
+      onLoad()
+    })
+  }
+}
+init()
 
 const resetList = () => {
-  toolsListRef.value = [];
+  toolsListRef.value = []
   noneCardNum.value = 0
 }
 
 // 搜索
-const handleSearch = (value) => {
-  console.log('搜索', value)
+const handleSearch = () => {
   onLoad()
 }
 // 点击热门关键字搜索
@@ -193,15 +258,44 @@ const handleKeySearch = (toolName) => {
 const handleChangeDropdownOfType1 = (value) => {
   resetList()
   type1ValueRef.value = value
-  typeOptions2Ref.value = value === '1' ? menuList2 : menuList3
+  listTools({
+    level: '2',
+    parentId: value,
+    pageSize: 999
+  }).then((res) => {
+    typeOptions2Ref.value = res.rows
+  })
+  listTools({
+    level: '3',
+    parentId: value,
+    pageSize: 999,
+    delFlag: 0
+  }).then((res) => {
+    toolsListRef.value = res.rows
+    handleSearch(value)
+  })
   type2ValueRef.value = ''
-  handleSearch(value)
 }
 // 点击二级菜单
 const handleChangeDropdownOfType2 = (value) => {
   resetList()
+  listTools({
+    level: '3',
+    parentId: value,
+    pageSize: 999
+  }).then((res) => {
+    toolsListRef.value = res.rows
+    handleSearch(value)
+  })
   type2ValueRef.value = value
-  handleSearch(value)
+}
+
+const handleClear = () => {
+  searchValueRef.value = ''
+}
+const handleDetail = (row) => {
+  console.log(row)
+  router.push({ path: '/detail', query: { id: row.id, toolName: row.toolName } })
 }
 </script>