This commit is contained in:
parent
cd6ef161b9
commit
be76d0b434
@ -12,10 +12,12 @@
|
|||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"ant-design-vue": "4.x",
|
"ant-design-vue": "4.x",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
"mande": "^2.0.8",
|
"mande": "^2.0.8",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"radash": "^12.1.0",
|
"radash": "^12.1.0",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
"vue-echarts": "^7.0.3",
|
||||||
"vue-router": "4"
|
"vue-router": "4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -23,6 +25,7 @@
|
|||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"unocss": "^0.59.0",
|
"unocss": "^0.59.0",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
"unplugin-vue-router": "^0.10.7",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
src/api/articles.js
Normal file
36
src/api/articles.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import {mande} from "mande";
|
||||||
|
import {queryRemoveZero} from "@/api/utils.js";
|
||||||
|
|
||||||
|
const articles = mande('/api/v2/articles')
|
||||||
|
|
||||||
|
export const ListArticles = (q) => {
|
||||||
|
const query = queryRemoveZero(q)
|
||||||
|
return articles.get({query})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetArticle = (id) =>{
|
||||||
|
return articles.get(`/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateArticle = (article)=>{
|
||||||
|
return articles.patch(article)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetBrandsDict=()=>{
|
||||||
|
return articles.get('dict/brand')
|
||||||
|
}
|
||||||
|
export const GetProviderHistory=(id)=>{
|
||||||
|
return articles.get(`/provider/history/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetSellerHistory=(id)=>{
|
||||||
|
return articles.get(`/seller/history/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateProviderArticle=(article)=>{
|
||||||
|
return articles.patch('provider',article)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateSellerArticle=(article)=>{
|
||||||
|
return articles.patch('seller',article)
|
||||||
|
}
|
@ -1,25 +1,47 @@
|
|||||||
import {mande} from "mande";
|
import {mande} from "mande";
|
||||||
import {queryRemoveZero} from "@/api/utils.js";
|
import {queryRemoveZero} from "@/api/utils.js";
|
||||||
|
|
||||||
const product = mande('/api/v2/providers')
|
const providers = mande('/api/v2/providers')
|
||||||
|
|
||||||
export const ListProviders = (q) => {
|
export const ListProviders = (q) => {
|
||||||
const query = queryRemoveZero(q)
|
const query = queryRemoveZero(q)
|
||||||
return product.get({query})
|
return providers.get({query})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateProvider = (provider)=>{
|
export const CreateProvider = (provider)=>{
|
||||||
return product.post(provider)
|
return providers.post(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateProvider = (provider)=>{
|
export const UpdateProvider = (provider)=>{
|
||||||
return product.put(provider)
|
return providers.put(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GetProvider = (id) =>{
|
export const GetProvider = (id) =>{
|
||||||
return product.get(`/${id}`)
|
return providers.get(`/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteProvider = (id)=>{
|
export const DeleteProvider = (id)=>{
|
||||||
return product.delete(`/${id}`)
|
return providers.delete(`/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetProviderDictStatus = () =>{
|
||||||
|
return providers.get('/dict/status')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FindProviders= (q) =>{
|
||||||
|
const query = queryRemoveZero(q)
|
||||||
|
return providers.get('find',{query})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const provider = mande('/api/v2/provider')
|
||||||
|
|
||||||
|
// 拉取供应商所有商品价格
|
||||||
|
export const FetchProviderArticles = (providerId)=>{
|
||||||
|
return provider.post(`/${providerId}/pull`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FetchProviderArticle = (providerArticle)=>{
|
||||||
|
return provider.post(`/${providerArticle.providerId}/fetch/${providerArticle.id}`)
|
||||||
}
|
}
|
@ -1,25 +1,48 @@
|
|||||||
import {mande} from "mande";
|
import {mande} from "mande";
|
||||||
import {queryRemoveZero} from "@/api/utils.js";
|
import {queryRemoveZero} from "@/api/utils.js";
|
||||||
|
|
||||||
const product = mande('/api/v2/sellers')
|
const sellers = mande('/api/v2/sellers')
|
||||||
|
|
||||||
export const ListSellers = (q) => {
|
export const ListSellers = (q) => {
|
||||||
const query = queryRemoveZero(q)
|
const query = queryRemoveZero(q)
|
||||||
return product.get({query})
|
return sellers.get({query})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FindSellers= (q) =>{
|
||||||
|
const query = queryRemoveZero(q)
|
||||||
|
return sellers.get('find',{query})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateSeller = (seller)=>{
|
export const CreateSeller = (seller)=>{
|
||||||
return product.post(seller)
|
return sellers.post(seller)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateSeller = (seller)=>{
|
export const UpdateSeller = (seller)=>{
|
||||||
return product.put(seller)
|
return sellers.put(seller)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GetSeller = (id) =>{
|
export const GetSeller = (id) =>{
|
||||||
return product.get(`/${id}`)
|
return sellers.get(`/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteSeller = (id)=>{
|
export const DeleteSeller = (id)=>{
|
||||||
return product.delete(`/${id}`)
|
return sellers.delete(`/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetSellerDictStatus = () =>{
|
||||||
|
return sellers.get('/dict/status')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const seller = mande('/api/v2/seller')
|
||||||
|
export const FetchSellerArticles = (sellerId)=>{
|
||||||
|
return seller.post(`/${sellerId}/pull`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MatchSellerSku = (sellerArticle)=>{
|
||||||
|
return seller.post(`/${sellerArticle.sellerId}/match`, sellerArticle)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FetchSellerArticle = (sellerArticle)=>{
|
||||||
|
return seller.post(`/${sellerArticle.sellerId}/fetch/${sellerArticle.id}`)
|
||||||
}
|
}
|
170
src/componse/provider-article/provider-article-panel.vue
Normal file
170
src/componse/provider-article/provider-article-panel.vue
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<a-collapse-panel :key="`${provider.providerId}`" >
|
||||||
|
<template #extra>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<div>更新时间:{{dayjs(provider.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||||
|
<div v-if="provider.skuID">抓取:<a-switch :checked="!provider.exclude" @click="onClickExclude"/></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #header>
|
||||||
|
<span class="text-lg">{{header}}</span>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col space-y-8">
|
||||||
|
<div class="flex space-x-4 ">
|
||||||
|
<div class="w-50">货号:{{provider.pid || '-'}}</div>
|
||||||
|
<!-- <div class="w-50">SpuID: {{provider.spuID || '-'}} </div>-->
|
||||||
|
<div class="w-50">SkuID: {{provider.skuID || '-'}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-4 items-center">
|
||||||
|
<div class="w-50">当前售价:$ {{provider.cost.originalPrice || '-'}}</div>
|
||||||
|
<a-popover v-if="provider.cost.calMark" trigger="hover">
|
||||||
|
<template #content>
|
||||||
|
<span class="whitespace-pre">
|
||||||
|
{{provider.cost.calMark}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<div class="w-50">当前到手价:¥ {{provider.cost.finalPrice || '-'}}</div>
|
||||||
|
</a-popover>
|
||||||
|
<div class="w-50" v-else>当前到手价:¥ {{provider.cost.finalPrice || '-'}}</div>
|
||||||
|
<a-button type="primary" @click="fetchArticle(provider)" :loading="fetchLoading">立即拉取</a-button>
|
||||||
|
</div>
|
||||||
|
<v-chart class="h-400px w-full" :option="options" />
|
||||||
|
</div>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {GetProviderHistory, UpdateProviderArticle} from "@/api/articles.js";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
import {computed, ref, watch} from "vue";
|
||||||
|
import VChart from "vue-echarts";
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
provider: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
providerId: '',
|
||||||
|
updatedAt: 0,
|
||||||
|
skuID:'',
|
||||||
|
pid: '',
|
||||||
|
cost: {
|
||||||
|
originalPrice: 0,
|
||||||
|
finalPrice: 0,
|
||||||
|
calMark: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
providerDict:{
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const header = computed(() => {
|
||||||
|
return props.providerDict.find(s => s.providerId === props.provider.providerId)?.name
|
||||||
|
})
|
||||||
|
|
||||||
|
const onClickExclude = (checked, e) => {
|
||||||
|
props.provider.exclude = !checked
|
||||||
|
UpdateProviderArticle(props.provider).then(res => {
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(`更新失败:${res.msg}`)
|
||||||
|
} else {
|
||||||
|
message.success('更新成功')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
message.error('更新失败')
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.open, (newVal) => {
|
||||||
|
if (newVal && !(props.provider.historyPrice?.length > 0)) {
|
||||||
|
GetProviderHistory(props.provider.id).then(res => {
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(`获取历史数据失败:${res.msg}`)
|
||||||
|
} else {
|
||||||
|
props.provider.historyPrice = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图表
|
||||||
|
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
import { LineChart } from 'echarts/charts'
|
||||||
|
import { GridComponent, TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import {FetchProviderArticle} from "@/api/provider.js";
|
||||||
|
|
||||||
|
use([GridComponent, LineChart, CanvasRenderer, TitleComponent , TooltipComponent, LegendComponent ])
|
||||||
|
|
||||||
|
const options = computed(()=> {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: "历史价格",
|
||||||
|
left: "center"
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b} : {c}'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
name: '日期',
|
||||||
|
data: (props.provider.historyPrice|| []).map(p => dayjs(p.createdAt).format('YYYY-MM-DD'))
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '价格'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
name: '原价',
|
||||||
|
data: (props.provider.historyPrice|| []).map(p => p.originalPrice)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
name: '到手价',
|
||||||
|
data: (props.provider.historyPrice|| []).map(p => p.finalPrice)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchLoading = ref(false)
|
||||||
|
// 拉取单个商品价格
|
||||||
|
const fetchArticle = (pArticle) => {
|
||||||
|
fetchLoading.value = true
|
||||||
|
FetchProviderArticle(pArticle).then(res=>{
|
||||||
|
if(res.code !== 200){
|
||||||
|
message.error(`拉取失败:${res.msg}`)
|
||||||
|
}else {
|
||||||
|
message.success('拉取成功')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
message.error('拉取失败')
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
fetchLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
197
src/componse/seller-article/seller-article-panel.vue
Normal file
197
src/componse/seller-article/seller-article-panel.vue
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<a-collapse-panel :key="`${seller.sellerId}`" >
|
||||||
|
<template #extra>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<div>更新时间:{{dayjs(seller.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||||
|
<div v-if="seller.skuID">抓取:<a-switch :checked="!seller.exclude" @click="onClickExclude(seller)"/></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #header>
|
||||||
|
<span>{{header}}</span>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col space-y-4">
|
||||||
|
<div class="flex space-x-4 justify-between">
|
||||||
|
<div class="w-50">货号:{{seller.pid || '-'}}</div>
|
||||||
|
<div class="w-50">SpuID: {{seller.spuID || '-'}} </div>
|
||||||
|
<div class="w-100 flex items-center space-x-4">
|
||||||
|
<div>SkuID: {{seller.skuID || '-'}} </div>
|
||||||
|
<div class="text-red-4 flex items-center space-x-4" v-if="!seller.skuID">
|
||||||
|
<div>(未匹配到销售商sku)</div>
|
||||||
|
<a-button @click="matchSkuID(seller)" :disabled="loading">重新匹配</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-4 justify-between">
|
||||||
|
<div class="w-50">当前售价:¥ {{seller.sell.originalPrice || '-'}}</div>
|
||||||
|
<a-popover v-if="seller.sell.calMark" trigger="hover">
|
||||||
|
<template #content>
|
||||||
|
<span class="whitespace-pre">
|
||||||
|
{{seller.sell.calMark}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<div class="w-50">当前到手价:¥ {{seller.sell.finalPrice || '-'}}</div>
|
||||||
|
</a-popover>
|
||||||
|
<div class="w-50" v-else>当前到手价:¥ {{seller.sell.finalPrice || '-'}}</div>
|
||||||
|
<a-button v-if="seller.skuID" type="primary" @click="fetchArticle(seller)" :loading="fetchLoading">立即拉取</a-button>
|
||||||
|
</div>
|
||||||
|
<v-chart class="h-400px w-full" :option="options" />
|
||||||
|
</div>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {GetSellerHistory, UpdateSellerArticle} from "@/api/articles.js";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
import {computed, ref, watch} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
seller: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
sellerId: '',
|
||||||
|
updatedAt: 0,
|
||||||
|
spuID: '',
|
||||||
|
skuID:'',
|
||||||
|
pid: '',
|
||||||
|
sell: {
|
||||||
|
originalPrice: 0,
|
||||||
|
finalPrice: 0,
|
||||||
|
calMark: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sellerDict:{
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const header = computed(()=>{
|
||||||
|
return props.sellerDict.find(s => s.sellerId === props.seller.sellerId)?.name
|
||||||
|
})
|
||||||
|
|
||||||
|
const onClickExclude = (sellerArticle)=> {
|
||||||
|
sellerArticle.exclude = !sellerArticle.exclude
|
||||||
|
UpdateSellerArticle(sellerArticle).then(res=>{
|
||||||
|
if(res.code !== 200){
|
||||||
|
message.error(`更新失败:${res.msg}`)
|
||||||
|
}else {
|
||||||
|
message.success('更新成功')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
message.error('更新失败')
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(()=>props.open, (newVal)=> {
|
||||||
|
if(newVal && !(props.seller.historyPrice?.length > 0)){
|
||||||
|
GetSellerHistory(props.seller.id).then(res=>{
|
||||||
|
if(res.code !== 200){
|
||||||
|
message.error(`获取历史数据失败:${res.msg}`)
|
||||||
|
}else {
|
||||||
|
props.seller.historyPrice = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图表
|
||||||
|
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
import { LineChart } from 'echarts/charts'
|
||||||
|
import { GridComponent, TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import VChart from "vue-echarts";
|
||||||
|
import {MatchSellerSku} from "@/api/seller.js";
|
||||||
|
import {FetchSellerArticle} from "@/api/seller.js";
|
||||||
|
|
||||||
|
use([GridComponent, LineChart, CanvasRenderer, TitleComponent , TooltipComponent, LegendComponent ])
|
||||||
|
|
||||||
|
const options = computed(()=> {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: "历史价格",
|
||||||
|
left: "center"
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b} : {c}'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
name: '日期',
|
||||||
|
data: (props.seller.historyPrice|| []).map(p => dayjs(p.createdAt).format('YYYY-MM-DD'))
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '价格'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
name: '原售价',
|
||||||
|
data: (props.seller.historyPrice|| []).map(p => p.originalPrice)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
name: '到手价',
|
||||||
|
data: (props.seller.historyPrice|| []).map(p => p.finalPrice)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 重新匹配下sku
|
||||||
|
const matchSkuID = (sellerArticle)=>{
|
||||||
|
loading.value = true
|
||||||
|
MatchSellerSku(sellerArticle).then(res=>{
|
||||||
|
if(res.code !== 200){
|
||||||
|
message.error(`匹配失败:${res.msg}`)
|
||||||
|
}else {
|
||||||
|
message.success('匹配成功')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
message.error('匹配失败')
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchLoading = ref(false)
|
||||||
|
// 拉取单个商品价格
|
||||||
|
const fetchArticle = (sArticle) => {
|
||||||
|
fetchLoading.value = true
|
||||||
|
FetchSellerArticle(sArticle).then(res=>{
|
||||||
|
if(res.code !== 200){
|
||||||
|
message.error(`拉取失败:${res.msg}`)
|
||||||
|
}else {
|
||||||
|
message.success('拉取成功')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
message.error('拉取失败')
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
fetchLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,50 +1,15 @@
|
|||||||
import {createRouter, createWebHashHistory} from "vue-router";
|
import {createRouter, createWebHashHistory} from "vue-router";
|
||||||
|
import { routes } from 'vue-router/auto-routes';
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
redirect: '/watcher'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/watcher',
|
|
||||||
name: 'watcher',
|
|
||||||
component: ()=>import('@/views/Watcher/index.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/us-coach',
|
|
||||||
name: 'us-coach',
|
|
||||||
component: ()=>import('@/views/Product/USCoachOutlet.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/cn-coach',
|
|
||||||
name: 'cn-coach',
|
|
||||||
component: ()=>import('@/views/Product/CNCoachOutlet.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/cn-coach-outlet',
|
|
||||||
name: 'cn-coach-outlet',
|
|
||||||
component: ()=>import('@/views/Product/CNCoachOutlet.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/pusher',
|
|
||||||
name: 'pusher',
|
|
||||||
component: ()=>import('@/views/Pusher/index.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/provider',
|
|
||||||
name: 'provider',
|
|
||||||
component: ()=>import('@/views/Provider/index.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/seller',
|
|
||||||
name: 'seller',
|
|
||||||
component: ()=>import('@/views/Seller/index.vue')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes
|
routes:[
|
||||||
|
...routes,
|
||||||
|
{ path: '/',
|
||||||
|
redirect: '/watcher'
|
||||||
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
180
src/views/article/detail.vue
Normal file
180
src/views/article/detail.vue
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<div class="overflow-overlay h-full m-4 bg-white rounded-2 shadow-lg p-8 flex flex-col justify-between space-y-4">
|
||||||
|
<div class="border-solid border-2 border-gray-100 rounded-lg p-4 flex space-x-8">
|
||||||
|
<a-image :src="data.image" :width="200"></a-image>
|
||||||
|
<div class="flex justify-between w-full pr-16">
|
||||||
|
<div class="p-4 flex flex-col justify-between">
|
||||||
|
<div class="text-xl font-bold">名称:{{data.name}}</div>
|
||||||
|
<div class="text-xl font-bold">品牌:{{data.brand}}</div>
|
||||||
|
<div class="text-xl font-bold">货号:{{data.pid}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 flex flex-col space-y-2 justify-between">
|
||||||
|
<div class="flex space-x-12">
|
||||||
|
<div class="text-xl font-bold">成本价:{{data.costPrice}}</div>
|
||||||
|
<div class="text-xl font-bold">售价:{{data.sellPrice}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xl font-bold">收益率:{{data.rate}}%</div>
|
||||||
|
<div class="flex space-x-4 items-center">
|
||||||
|
<span class="text-xl font-bold mr-4 whitespace-nowrap">备注:</span>
|
||||||
|
<span v-if="!remarkEdit">{{data.remark || '-'}}</span>
|
||||||
|
<EditOutlined v-if="!remarkEdit" class="cursor-pointer" @click="remarkEdit=true" />
|
||||||
|
<a-input v-else v-model:value="data.remark"></a-input>
|
||||||
|
<SaveOutlined v-show="remarkEdit" class="cursor-pointer" @click="onSaveRemark"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 flex flex-col space-y-2 justify-between">
|
||||||
|
<div class="text-xl font-bold">更新时间:{{dayjs(data.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||||
|
<div class="text-xl font-bold">可购买:
|
||||||
|
<CheckOutlined v-if="data.available" class="text-green-600"/>
|
||||||
|
<CloseOutlined v-else class="text-red-7"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-xl font-bold">
|
||||||
|
抓取:<a-switch :checked="!data.exclude" @click="onClickExclude(data)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-full overflow-overlay">
|
||||||
|
<a-card
|
||||||
|
class="h-full flex flex-col w-full"
|
||||||
|
:bodyStyle="{height: '100%', flex: '1 1 0%', overflow: 'overlay'}"
|
||||||
|
:tab-list="[{key:'providers',tab:'供应商'},{key:'sellers',tab:'销售商'}]"
|
||||||
|
v-model:active-tab-key="activeTabKey"
|
||||||
|
@tab-change="onTabChange"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<a-collapse v-model:activeKey="openTabKey" >
|
||||||
|
<seller-article-panel v-if="activeTabKey === 'sellers'" v-for="item in data.sellers || []" :key="`${item.sellerId}`"
|
||||||
|
:seller="item" :seller-dict="dict.sellers" :open="openTabKey.indexOf(item.sellerId) > -1">
|
||||||
|
|
||||||
|
</seller-article-panel>
|
||||||
|
<provider-article-panel v-else-if="activeTabKey === 'providers'" v-for="item in data.providers || []" :key="`${item.providerId}`"
|
||||||
|
:provider="item" :provider-dict="dict.providers" :open="openTabKey.indexOf(item.providerId) > -1">
|
||||||
|
|
||||||
|
</provider-article-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {onMounted, reactive, ref} from "vue";
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {GetArticle, GetBrandsDict, UpdateArticle} from "@/api/articles.js";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {CheckOutlined, CloseOutlined, EditOutlined, SaveOutlined} from "@ant-design/icons-vue";
|
||||||
|
import {FindSellers} from "@/api/seller.js";
|
||||||
|
import {FindProviders} from "@/api/provider.js";
|
||||||
|
import SellerArticlePanel from "@/componse/seller-article/seller-article-panel.vue";
|
||||||
|
import ProviderArticlePanel from "@/componse/provider-article/provider-article-panel.vue";
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const data = ref({
|
||||||
|
image:'',
|
||||||
|
name:'',
|
||||||
|
providers: [],
|
||||||
|
sellers: [],
|
||||||
|
remark:''
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
load()
|
||||||
|
loadDict()
|
||||||
|
})
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const load = ()=>{
|
||||||
|
loading.value = true
|
||||||
|
GetArticle(route.query.id).then(res=>{
|
||||||
|
if(res.code === 200){
|
||||||
|
data.value = res.data
|
||||||
|
}else{
|
||||||
|
message.error(res.msg)
|
||||||
|
}
|
||||||
|
}).catch((error)=>{
|
||||||
|
message.error('获取详情失败')
|
||||||
|
console.log(error)
|
||||||
|
}).finally(()=>{
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickExclude=(record)=>{
|
||||||
|
record.exclude = !record.exclude
|
||||||
|
UpdateArticle(record).then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(`${record.exclude ? '关闭':'开启'}失败`)
|
||||||
|
}else {
|
||||||
|
message.success(`${record.exclude ? '关闭':'开启'}成功`)
|
||||||
|
}
|
||||||
|
}).catch(err=>{
|
||||||
|
message.error(`${record.exclude ? '关闭':'开启'}失败`)
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
load()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有字典信息
|
||||||
|
const dict = reactive({
|
||||||
|
brands: [],
|
||||||
|
providers: [],
|
||||||
|
sellers: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadDict = ()=>{
|
||||||
|
Promise.all([FindSellers({}), FindProviders({}), GetBrandsDict()]).then(([sellers, providers, brands])=>{
|
||||||
|
if (sellers.code === 200 && providers.code === 200 && brands.code === 200) {
|
||||||
|
dict.sellers = sellers.data
|
||||||
|
dict.providers = providers.data
|
||||||
|
dict.brands = brands.data
|
||||||
|
}else if(sellers.code !== 200){
|
||||||
|
message.error('获取销售商列表失败')
|
||||||
|
}else if(providers.code !== 200){
|
||||||
|
message.error('获取供应商列表失败')
|
||||||
|
}else if(brands.code !== 200){
|
||||||
|
message.error('获取品牌列表失败')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
message.error('获取字典信息失败')
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTabKey = ref('providers')
|
||||||
|
|
||||||
|
const onTabChange = (key)=>{
|
||||||
|
activeTabKey.value = key
|
||||||
|
}
|
||||||
|
|
||||||
|
const openTabKey = ref([])
|
||||||
|
|
||||||
|
|
||||||
|
// 编辑备注
|
||||||
|
|
||||||
|
const remarkEdit = ref(false)
|
||||||
|
|
||||||
|
const onSaveRemark = ()=>{
|
||||||
|
UpdateArticle(data.value).then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error('保存失败')
|
||||||
|
}else {
|
||||||
|
message.success('保存成功')
|
||||||
|
}
|
||||||
|
}).catch(err=>{
|
||||||
|
message.error('保存失败')
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
remarkEdit.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
205
src/views/article/index.vue
Normal file
205
src/views/article/index.vue
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<template>
|
||||||
|
<div class="h-full m-4 bg-white rounded-2 shadow-lg p-8 flex flex-col justify-between space-y-4">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div></div>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<a-input placeholder="请输入关键词" v-model:value="query.keyword"></a-input>
|
||||||
|
<a-button type="primary" :disabled="loading" @click="list">搜索</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-full border-0 border-t-1 border-solid border-gray-300 pt-4">
|
||||||
|
<a-spin :spinning="loading" :indicator="indicator">
|
||||||
|
<a-table :dataSource="data.list" :columns="columns" :pagination="false" :scroll="{y:1000}" @change="tableChange" rowKey="id">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'image'">
|
||||||
|
<a-image :width="75" :src="record.image"/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'name'">
|
||||||
|
<span class="cursor-pointer text-blue" title="查看详情" @click="toDetail(record.id)">{{record.name}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'pid'">
|
||||||
|
<span class="cursor-pointer text-blue" title="查看详情" @click="toDetail(record.id)">{{record.pid}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'rate'">
|
||||||
|
<span>{{record.rate > 0 ? `${record.rate}%`:'--' }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'available'">
|
||||||
|
<span :class="[record.available ? 'text-green-700':'text-red-500']">{{record.available ? '是':'否'}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'exclude'">
|
||||||
|
<a-switch :checked="!record.exclude" @click="onClickExclude(record)"/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'remark'">
|
||||||
|
{{record.remark===''?'-':record.remark}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" v-model:page-size="query.size" :total="data.total" show-less-items @change="list"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {computed, h, onMounted, reactive, ref, watch} from "vue";
|
||||||
|
import {LoadingOutlined} from "@ant-design/icons-vue";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
import {ListArticles, UpdateArticle} from "@/api/articles.js";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const query = reactive({
|
||||||
|
keyword: "",
|
||||||
|
page: 1,
|
||||||
|
size: 8,
|
||||||
|
pid:'',
|
||||||
|
brand: '',
|
||||||
|
available: null,
|
||||||
|
rate_sort:null,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(()=>query.available, ()=>{
|
||||||
|
console.log(query)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
list()
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
// 展示数据
|
||||||
|
const data = reactive({
|
||||||
|
list: [],
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = () => {
|
||||||
|
loading.value = true
|
||||||
|
ListArticles(query).then(res => {
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(res.msg)
|
||||||
|
}else {
|
||||||
|
data.list = res.data.list
|
||||||
|
data.total = res.data.total
|
||||||
|
}
|
||||||
|
}).finally(()=>{
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = computed(()=>[
|
||||||
|
{
|
||||||
|
title: '货号',
|
||||||
|
dataIndex: 'pid',
|
||||||
|
key: 'pid',
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
key: 'name',
|
||||||
|
width: 350
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '图片',
|
||||||
|
key: 'image',
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最低成本价',
|
||||||
|
dataIndex: 'costPrice',
|
||||||
|
key: 'costPrice',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最低售价',
|
||||||
|
dataIndex: 'sellPrice',
|
||||||
|
key: 'sellPrice',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '利润率',
|
||||||
|
key: 'rate',
|
||||||
|
dataIndex: 'rate',
|
||||||
|
width: 150,
|
||||||
|
sorter:true,
|
||||||
|
sortOrder: query.rate_sort
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '可购买',
|
||||||
|
key: 'available',
|
||||||
|
dataIndex: 'available',
|
||||||
|
width: 150,
|
||||||
|
filteredValue: query.available,
|
||||||
|
filterMultiple:false,
|
||||||
|
filters:[
|
||||||
|
{
|
||||||
|
text: '可购买',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '不可购买',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '需要更新',
|
||||||
|
key: 'exclude',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
key: 'remark',
|
||||||
|
dataIndex: 'remark',
|
||||||
|
ellipsis: true,
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const indicator = h(LoadingOutlined, {
|
||||||
|
style: {
|
||||||
|
fontSize: '32px',
|
||||||
|
},
|
||||||
|
spin: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClickExclude=(record)=>{
|
||||||
|
record.exclude = !record.exclude
|
||||||
|
UpdateArticle(record).then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(`${record.exclude ? '关闭':'开启'}失败`)
|
||||||
|
}else {
|
||||||
|
message.success(`${record.exclude ? '关闭':'开启'}成功`)
|
||||||
|
}
|
||||||
|
}).catch(err=>{
|
||||||
|
message.error(`${record.exclude ? '关闭':'开启'}失败`)
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
// 跳到详情页面
|
||||||
|
const toDetail=(id)=>{
|
||||||
|
router.push({
|
||||||
|
path: '/article/detail',
|
||||||
|
query: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
const tableChange = (pagination, filters, sorter, { action, currentDataSource })=>{
|
||||||
|
if(sorter.columnKey === 'rate') {
|
||||||
|
query.rate_sort = sorter.order
|
||||||
|
}
|
||||||
|
query.available = filters.available
|
||||||
|
list()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -16,7 +16,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const onclick = ({key}) => {
|
const onclick = ({key}) => {
|
||||||
router.push({
|
router.push({
|
||||||
name: key
|
path: '/'+key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,28 +28,11 @@ const items = [
|
|||||||
title: '蹲货',
|
title: '蹲货',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'product',
|
key: 'article',
|
||||||
icon: () => h(DollarCircleOutlined),
|
icon: () => h(DollarCircleOutlined),
|
||||||
label: '商品',
|
label: '商品',
|
||||||
title: '商品',
|
title: '商品',
|
||||||
onTitleClick: onclick,
|
onTitleClick: onclick,
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'us-coach',
|
|
||||||
label: '美国coach outlet',
|
|
||||||
title: '美国coach outlet',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'cn-coach-outlet',
|
|
||||||
label: '中国coach outlet',
|
|
||||||
title: '中国coach outlet',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'cn-coach',
|
|
||||||
label: '中国coach',
|
|
||||||
title: '中国coach',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'pusher',
|
key: 'pusher',
|
||||||
@ -66,8 +49,8 @@ const items = [
|
|||||||
{
|
{
|
||||||
key: 'seller',
|
key: 'seller',
|
||||||
icon: () => h(DeploymentUnitOutlined),
|
icon: () => h(DeploymentUnitOutlined),
|
||||||
label: '出货商',
|
label: '销售商',
|
||||||
title: '出货商',
|
title: '销售商',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-full w-full flex flex-col">
|
<div class="h-full w-full flex flex-col">
|
||||||
<Header></Header>
|
<Header></Header>
|
||||||
<div class="h-full w-full flex">
|
<div class="h-full w-full flex overflow-overlay">
|
||||||
<Aside></Aside>
|
<Aside></Aside>
|
||||||
<Main></Main>
|
<Main></Main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,9 +16,16 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'name'">
|
<template v-else-if="column.dataIndex === 'name'">
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'ticker'">
|
||||||
|
<span>{{record.config.ticker}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<a-tag :color="getStatusDict(record.status).color">{{getStatusDict(record.status).title}}</a-tag>
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'opt'">
|
<template v-else-if="column.key === 'opt'">
|
||||||
<a-button type="link" @click="onClickUpdate(record.id)">编辑</a-button>
|
<a-button type="link" :disabled="['provider_status_normal', 'provider_status_error'].indexOf(getStatusDict(record.status).key) < 0" @click="onClickPull(record)">拉取</a-button>
|
||||||
<a-button type="link" danger @click="onClickDelete(record.id)"> 删除</a-button>
|
<a-button type="link" :disabled="['provider_status_normal', 'provider_status_error'].indexOf(getStatusDict(record.status).key) < 0" @click="onClickUpdate(record.id)">编辑</a-button>
|
||||||
|
<a-button type="link" :disabled="['provider_status_normal', 'provider_status_error'].indexOf(getStatusDict(record.status).key) < 0" danger @click="onClickDelete(record.id)"> 删除</a-button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
@ -76,10 +83,18 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import {h, onMounted, reactive, ref} from "vue";
|
import {createVNode, h, onMounted, reactive, ref} from "vue";
|
||||||
import {CreateProvider, DeleteProvider, GetProvider, ListProviders, UpdateProvider} from "@/api/provider.js";
|
import {
|
||||||
import {message} from "ant-design-vue";
|
CreateProvider,
|
||||||
import {LoadingOutlined} from "@ant-design/icons-vue";
|
DeleteProvider,
|
||||||
|
GetProvider,
|
||||||
|
ListProviders,
|
||||||
|
FetchProviderArticles,
|
||||||
|
UpdateProvider,
|
||||||
|
GetProviderDictStatus
|
||||||
|
} from "@/api/provider.js";
|
||||||
|
import {message, Modal} from "ant-design-vue";
|
||||||
|
import {ExclamationCircleOutlined, LoadingOutlined} from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
@ -90,6 +105,11 @@ const query = reactive({
|
|||||||
id: '',
|
id: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 状态的列表
|
||||||
|
const configs = reactive({
|
||||||
|
status: []
|
||||||
|
})
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
// 展示数据
|
// 展示数据
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@ -111,10 +131,29 @@ const list = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadStatus = () => {
|
||||||
|
GetProviderDictStatus().then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(res.msg)
|
||||||
|
}else {
|
||||||
|
configs.status = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusDict = (value)=>{
|
||||||
|
return configs.status.find(s => s.value === value) || {
|
||||||
|
title: '未知',
|
||||||
|
color: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
list()
|
list()
|
||||||
|
loadStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//**********添加对话框**********//
|
||||||
const addModal = reactive({
|
const addModal = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
data: {
|
data: {
|
||||||
@ -197,6 +236,17 @@ const columns = [
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
width: 250
|
width: 250
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '抓取时间',
|
||||||
|
key: 'ticker',
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
dataIndex: 'remark',
|
dataIndex: 'remark',
|
||||||
@ -241,6 +291,29 @@ const onClickDelete=(id)=>{
|
|||||||
list()
|
list()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onClickPull = (record)=>{
|
||||||
|
Modal.confirm({
|
||||||
|
title: '提示',
|
||||||
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
content: `确认拉取${record.name}商品价格?`,
|
||||||
|
onOk() {
|
||||||
|
FetchProviderArticles(record.providerId).then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(res.msg)
|
||||||
|
}else {
|
||||||
|
message.success("开始抓取")
|
||||||
|
}
|
||||||
|
}).catch(err =>{
|
||||||
|
console.log(err)
|
||||||
|
message.error("抓取失败")
|
||||||
|
}).finally(()=>{
|
||||||
|
list()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
centered: true
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
@ -16,9 +16,16 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'name'">
|
<template v-else-if="column.dataIndex === 'name'">
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<a-tag :color="getStatusDict(record.status).color">{{getStatusDict(record.status).title}}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'ticker'">
|
||||||
|
<span>{{record.config.ticker}}</span>
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'opt'">
|
<template v-else-if="column.key === 'opt'">
|
||||||
<a-button type="link" @click="onClickUpdate(record.id)">编辑</a-button>
|
<a-button type="link" :disabled="['seller_status_normal', 'seller_status_error'].indexOf(getStatusDict(record.status).key) < 0" @click="onClickPull(record)">拉取</a-button>
|
||||||
<a-button type="link" danger @click="onClickDelete(record.id)"> 删除</a-button>
|
<a-button type="link" :disabled="['seller_status_normal', 'seller_status_error'].indexOf(getStatusDict(record.status).key) < 0" @click="onClickUpdate(record.id)">编辑</a-button>
|
||||||
|
<a-button type="link" :disabled="['seller_status_normal', 'seller_status_error'].indexOf(getStatusDict(record.status).key) < 0" danger @click="onClickDelete(record.id)"> 删除</a-button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
@ -26,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" :total="data.total" show-less-items @change="list"/>
|
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" :total="data.total" show-less-items @change="list"/>
|
||||||
</div>
|
</div>
|
||||||
<a-modal v-model:open="addModal.visible" :title="`${addModal.edit ? '编辑' : '添加'}出货商`" @ok="handleOk" centered width="80%">
|
<a-modal v-model:open="addModal.visible" :title="`${addModal.edit ? '编辑' : '添加'}销售商`" @ok="handleOk" centered width="80%">
|
||||||
<a-spin :spinning="addModal.loading" :indicator="indicator">
|
<a-spin :spinning="addModal.loading" :indicator="indicator">
|
||||||
<a-form
|
<a-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
@ -37,10 +44,10 @@
|
|||||||
@finish="cleanAddModal"
|
@finish="cleanAddModal"
|
||||||
@finishFailed="cleanAddModal"
|
@finishFailed="cleanAddModal"
|
||||||
>
|
>
|
||||||
<a-form-item label="标识" name="sellerId" :rules="[{ required: true, message: '请填写出货商标识' }]">
|
<a-form-item label="标识" name="sellerId" :rules="[{ required: true, message: '请填写销售商标识' }]">
|
||||||
<a-input v-model:value="addModal.data.sellerId" placeholder="请输入标识,比如:dewu" :disabled="addModal.edit"/>
|
<a-input v-model:value="addModal.data.sellerId" placeholder="请输入标识,比如:dewu" :disabled="addModal.edit"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="名称" name="name" :rules="[{ required: true, message: '请填写出货商名称' }]">
|
<a-form-item label="名称" name="name" :rules="[{ required: true, message: '请填写销售商名称' }]">
|
||||||
<a-input v-model:value="addModal.data.name" placeholder="请输入名称,比如:得物"/>
|
<a-input v-model:value="addModal.data.name" placeholder="请输入名称,比如:得物"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="抓取定时" name="config.ticker">
|
<a-form-item label="抓取定时" name="config.ticker">
|
||||||
@ -70,10 +77,18 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import {h, onMounted, reactive, ref} from "vue";
|
import {createVNode, h, onMounted, reactive, ref} from "vue";
|
||||||
import {CreateSeller, DeleteSeller, GetSeller, ListSellers, UpdateSeller} from "@/api/seller.js";
|
import {
|
||||||
import {message} from "ant-design-vue";
|
CreateSeller,
|
||||||
import {LoadingOutlined} from "@ant-design/icons-vue";
|
DeleteSeller,
|
||||||
|
GetSeller,
|
||||||
|
ListSellers,
|
||||||
|
UpdateSeller,
|
||||||
|
FetchSellerArticles,
|
||||||
|
GetSellerDictStatus
|
||||||
|
} from "@/api/seller.js";
|
||||||
|
import {message, Modal} from "ant-design-vue";
|
||||||
|
import {ExclamationCircleOutlined, LoadingOutlined} from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
@ -84,6 +99,11 @@ const query = reactive({
|
|||||||
id: '',
|
id: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 状态的列表
|
||||||
|
const configs = reactive({
|
||||||
|
status: []
|
||||||
|
})
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
// 展示数据
|
// 展示数据
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@ -105,8 +125,26 @@ const list = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadStatus = () => {
|
||||||
|
GetSellerDictStatus().then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(res.msg)
|
||||||
|
}else {
|
||||||
|
configs.status = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusDict = (value)=>{
|
||||||
|
return configs.status.find(s => s.value === value) || {
|
||||||
|
title: '未知',
|
||||||
|
color: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
list()
|
list()
|
||||||
|
loadStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
const addModal = reactive({
|
const addModal = reactive({
|
||||||
@ -187,6 +225,17 @@ const columns = [
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
width: 250
|
width: 250
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '抓取时间',
|
||||||
|
key: 'ticker',
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
dataIndex: 'remark',
|
dataIndex: 'remark',
|
||||||
@ -231,6 +280,29 @@ const onClickDelete=(id)=>{
|
|||||||
list()
|
list()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onClickPull = (record)=>{
|
||||||
|
Modal.confirm({
|
||||||
|
title: '提示',
|
||||||
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
content: `确认拉取${record.name}商品价格?`,
|
||||||
|
onOk() {
|
||||||
|
FetchSellerArticles(record.sellerId).then(res=>{
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(res.msg)
|
||||||
|
}else {
|
||||||
|
message.success("开始抓取")
|
||||||
|
}
|
||||||
|
}).catch(err =>{
|
||||||
|
console.log(err)
|
||||||
|
message.error("抓取失败")
|
||||||
|
}).finally(()=>{
|
||||||
|
list()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
centered: true
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
@ -4,12 +4,17 @@ import UnoCSS from 'unocss/vite'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
import VueRouter from 'unplugin-vue-router/vite';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
VueRouter({
|
||||||
|
routesFolder: 'src/views'
|
||||||
|
}),
|
||||||
UnoCSS(),
|
UnoCSS(),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [
|
resolvers: [
|
||||||
@ -22,41 +27,6 @@ export default defineConfig({
|
|||||||
server:{
|
server:{
|
||||||
open: true,
|
open: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
// '/storage/v1/products': {
|
|
||||||
// // target: 'https://ht.timerzz.com:20443/',
|
|
||||||
// target: 'http://192.168.31.163:31828/',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// secure: false,
|
|
||||||
// ws: true,
|
|
||||||
// },
|
|
||||||
// '/storage/v1/push': {
|
|
||||||
// target: 'https://ht-dev.timerzz.com:20443/',
|
|
||||||
// // target: 'http://192.168.31.55:2280/',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// secure: false,
|
|
||||||
// ws: true,
|
|
||||||
// },
|
|
||||||
// '/storage/v1/pushers': {
|
|
||||||
// target: 'https://ht-dev.timerzz.com:20443/',
|
|
||||||
// // target: 'http://192.168.31.55:2280/',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// secure: false,
|
|
||||||
// ws: true,
|
|
||||||
// },
|
|
||||||
// '/storage/v1/watchers': {
|
|
||||||
// target: 'https://ht-dev.timerzz.com:20443/',
|
|
||||||
// // target: 'http://192.168.31.55:2280/',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// secure: false,
|
|
||||||
// ws: true,
|
|
||||||
// },
|
|
||||||
// '/storage/v1/proxies': {
|
|
||||||
// target: 'https://ht-dev.timerzz.com:20443/',
|
|
||||||
// // target: 'http://192.168.31.55:2280/',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// secure: false,
|
|
||||||
// ws: true,
|
|
||||||
// },
|
|
||||||
'/api/v1': {
|
'/api/v1': {
|
||||||
target: 'https://ht.timerzz.com:20443/',
|
target: 'https://ht.timerzz.com:20443/',
|
||||||
// target: 'http://192.168.31.55:2280/',
|
// target: 'http://192.168.31.55:2280/',
|
||||||
@ -64,12 +34,40 @@ export default defineConfig({
|
|||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
'/api/v2': {
|
'/api/v2/sellers': {
|
||||||
target: 'http://localhost:8081/',
|
target: 'http://localhost:8081/',
|
||||||
// target: 'http://192.168.31.55:2280/',
|
// target: 'http://192.168.31.55:2280/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
|
},
|
||||||
|
'/api/v2/seller': {
|
||||||
|
target: 'http://localhost:8083/',
|
||||||
|
// target: 'http://192.168.31.55:2280/',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
'/api/v2/providers': {
|
||||||
|
target: 'http://localhost:8080/',
|
||||||
|
// target: 'http://192.168.31.55:2280/',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
'/api/v2/provider': {
|
||||||
|
target: 'http://172.21.195.130:8082/',
|
||||||
|
// target: 'http://192.168.31.55:2280/',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
'/api/v2/articles': {
|
||||||
|
target: 'http://localhost:8085/',
|
||||||
|
// target: 'http://192.168.31.55:2280/',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user