Compare commits

..

No commits in common. "main" and "v0.1.16" have entirely different histories.

13 changed files with 157 additions and 264 deletions

View file

@ -15,26 +15,13 @@ export const useZeroPrices = () => {
await apiClient.delete('/merch/zeroprices', list) await apiClient.delete('/merch/zeroprices', list)
} }
catch (error) { catch (error) {
console.log('Delete target zero prices error: ', error) console.log('Delete zero prices error: ', error)
throw error throw error
} }
} }
const deleteZeroPricesPeriod = async (start, end) => {
const params = new URLSearchParams({ start, end }).toString()
const url = `/merch/zeroprices/period${params ? `?${params}` : ''}`
const response = await apiClient.delete(url)
if (response.status === 200) {
return response
} else {
console.log('Delete period select zero prices error: ', response)
}
}
return { return {
getZeroPrices, getZeroPrices,
deleteZeroPrices, deleteZeroPrices,
deleteZeroPricesPeriod,
} }
} }

View file

@ -13,7 +13,6 @@ import {
} from 'chart.js' } from 'chart.js'
import { Line } from 'vue-chartjs' import { Line } from 'vue-chartjs'
import 'chartjs-adapter-date-fns' import 'chartjs-adapter-date-fns'
import { originColors } from '@/services/colors.js'
ChartJS.register( ChartJS.register(
Title, Title,
@ -34,6 +33,11 @@ const props = defineProps({
}, },
}) })
const originColors = {
surugaya: '#2d3081',
mandarake: '#924646',
}
const chartData = ref({ const chartData = ref({
datasets: [], datasets: [],
}) })

View file

@ -6,7 +6,7 @@ const message = useMessage()
const props = defineProps({ const props = defineProps({
text: { text: {
type: String, type: String,
default: '', required: true,
}, },
}) })

View file

@ -11,7 +11,6 @@ export const BASE_URL = 'https://api.nqws.ru/api/v2'
// export const BASE_URL = 'http://localhost:9090/api/v2' // export const BASE_URL = 'http://localhost:9090/api/v2'
export const BASE_MANDARAKE_LINK = 'https://order.mandarake.co.jp/order/listPage/list?soldOut=1&keyword=' export const BASE_MANDARAKE_LINK = 'https://order.mandarake.co.jp/order/listPage/list?soldOut=1&keyword='
export const BASE_AMIAMI_LINK = 'https://slist.amiami.jp/top/search/list?s_cate_tag=1&submit_btn=&s_st_list_preorder_available=1&s_st_list_backorder_available=1&s_st_list_newitem_available=1&s_st_condition_flg=1&pagemax=60&s_keywords='
// export const IMAGE_STORAGE_URL = 'http://localhost:9280' // export const IMAGE_STORAGE_URL = 'http://localhost:9280'
export const IMAGE_STORAGE_URL = 'https://images.nqws.ru' export const IMAGE_STORAGE_URL = 'https://images.nqws.ru'

View file

@ -1,5 +0,0 @@
export const originColors = {
surugaya: '#2d3081',
mandarake: '#924646',
amiami: '#F27024',
};

View file

@ -33,7 +33,7 @@ const fetchPrices = async (days = 7) => {
} }
onMounted(() => { onMounted(() => {
fetchPrices() fetchPrices(7)
}) })
function handleSelectDays(days) { function handleSelectDays(days) {

View file

@ -5,9 +5,10 @@ import router from '@/router/index.js'
import PeriodSelector from '@/components/PeriodSelector.vue' import PeriodSelector from '@/components/PeriodSelector.vue'
import ChartBlock from '@/components/ChartBlock.vue' import ChartBlock from '@/components/ChartBlock.vue'
import { useChartsApi } from '@/api/charts.js' import { useChartsApi } from '@/api/charts.js'
import EditLink from '@/views/DetailsView/EditLink.vue'
import CopyToClipboard from '@/components/CopyToClipboard.vue'
import DetailsViewImages from '@/views/DetailsView/DetailsViewImages.vue' import DetailsViewImages from '@/views/DetailsView/DetailsViewImages.vue'
import AttachLabel from '@/views/DetailsView/AttachLabel.vue' import AttachLabel from '@/views/DetailsView/AttachLabel.vue'
import OriginBlock from '@/views/DetailsView/OriginBlock.vue'
const { getMerchDetails, deleteMerch } = useMerchApi() const { getMerchDetails, deleteMerch } = useMerchApi()
const { getDistinctPrices } = useChartsApi() const { getDistinctPrices } = useChartsApi()
@ -19,6 +20,11 @@ const props = defineProps({
}, },
}) })
const editing = ref({
surugaya: false,
mandarake: false,
})
const merchDetails = ref(null) const merchDetails = ref(null)
const loading = ref(true) const loading = ref(true)
const error = ref(null) const error = ref(null)
@ -29,7 +35,7 @@ const fetchMerch = async () => {
merchDetails.value = response.data merchDetails.value = response.data
if (!response.status === 400) { if (!response.status === 400) {
await router.push({ name: 'collection' }) router.push({ name: 'collection' })
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
@ -52,7 +58,7 @@ const confirmDelete = async () => {
console.log(error) console.log(error)
} }
showModal.value = false showModal.value = false
await router.push({ name: 'collection' }) router.push({ name: 'collection' })
} }
const prices = ref(null) const prices = ref(null)
@ -66,7 +72,7 @@ const fetchPrices = async (days = 7) => {
const response = await getDistinctPrices(props.merch_uuid, days) const response = await getDistinctPrices(props.merch_uuid, days)
if (response.status === 400) { if (response.status === 400) {
await router.push({ name: 'collection' }) router.push({ name: 'collection' })
return return
} }
@ -78,6 +84,11 @@ const fetchPrices = async (days = 7) => {
} }
} }
function handleLinkUpdate(origin, newLink) {
merchDetails.value[`origin_${origin}`].link = newLink
editing.value[origin] = false
}
function handleSelectDays(days) { function handleSelectDays(days) {
fetchPrices(days) fetchPrices(days)
} }
@ -109,10 +120,75 @@ onMounted(() => {
<ChartBlock :charts-data="prices" /> <ChartBlock :charts-data="prices" />
</div> </div>
<div v-for="item in merchDetails.origins" :key="item"> <!-- Surugaya -->
<OriginBlock :merch_details="merchDetails" :merch_data="item" /> <CopyToClipboard :text="merchDetails.origin_surugaya?.link">
<n-divider title-placement="left">Surugaya</n-divider>
</CopyToClipboard>
<div v-if="!editing.surugaya">
<template v-if="merchDetails.origin_surugaya?.link">
<a
:href="merchDetails.origin_surugaya.link"
target="_blank"
rel="noopener"
class="default-color"
>
{{ merchDetails.origin_surugaya.link }}
</a>
<n-button type="primary" style="margin-left: 8px" @click="editing.surugaya = true">
Edit link
</n-button>
</template>
<template v-else>
<span class="default-color underline link-like-text" @click="editing.surugaya = true"
>Add link</span
>
</template>
</div> </div>
<EditLink
v-else
:merch-uuid="merch_uuid"
origin="surugaya"
:name="merchDetails.name"
:model-value="merchDetails.origin_surugaya?.link || ''"
@update:model-value="handleLinkUpdate('surugaya', $event)"
@cancel-edit="editing.surugaya = false"
/>
<!-- Mandarake -->
<CopyToClipboard :text="merchDetails.origin_mandarake?.link">
<n-divider title-placement="left">Mandarake</n-divider>
</CopyToClipboard>
<div v-if="!editing.mandarake">
<template v-if="merchDetails.origin_mandarake?.link">
<a
:href="merchDetails.origin_mandarake.link"
target="_blank"
rel="noopener"
class="default-color"
>
{{ merchDetails.origin_mandarake.link }}
</a>
<n-button type="primary" style="margin-left: 8px" @click="editing.mandarake = true">
Edit link
</n-button>
</template>
<template v-else>
<span class="default-color underline link-like-text" @click="editing.mandarake = true"
>Add link</span
>
</template>
</div>
<EditLink
v-else
:merch-uuid="merch_uuid"
origin="mandarake"
:name="merchDetails.name"
:model-value="merchDetails.origin_mandarake?.link || ''"
@update:model-value="handleLinkUpdate('mandarake', $event)"
@cancel-edit="editing.mandarake = false"
/>
</n-card> </n-card>
<div v-else>Not found</div> <div v-else>Not found</div>

View file

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, nextTick } from 'vue' import { ref, nextTick } from 'vue'
import { useMerchApi } from '@/api/merch.js' import { useMerchApi } from '@/api/merch.js'
import { BASE_AMIAMI_LINK, BASE_MANDARAKE_LINK } from '@/main.js' import { BASE_MANDARAKE_LINK } from '@/main.js'
const { updateMerch } = useMerchApi() const { updateMerch } = useMerchApi()
@ -101,29 +101,18 @@ const cancel = () => {
emit('cancel-edit') emit('cancel-edit')
} }
const insertMandarakeLink = () => { const insertAutoCompletedLink = () => {
tempValue.value = BASE_MANDARAKE_LINK+props.name tempValue.value = BASE_MANDARAKE_LINK+props.name
} }
const insertAmiamiLink = () => {
tempValue.value = BASE_AMIAMI_LINK+props.name
}
</script> </script>
<template> <template>
<div v-if="props.origin === 'mandarake'" class="button-container-center mb-10"> <div v-if="props.origin === 'mandarake'" class="button-container-center mb-10">
<n-button type="warning" class="center-button" @click="insertMandarakeLink" <n-button type="warning" class="center-button" @click="insertAutoCompletedLink"
>Insert auto-completed link</n-button >Insert auto-completed link</n-button
> >
</div> </div>
<div v-if="props.origin === 'amiami'" class="button-container-center mb-10">
<n-button type="warning" class="center-button" @click="insertAmiamiLink"
>Insert auto-completed link</n-button
>
</div>
<div class="editing-area"> <div class="editing-area">
<n-input <n-input
v-model:value="tempValue" v-model:value="tempValue"

View file

@ -1,62 +0,0 @@
<script setup>
import { computed, ref } from 'vue'
import CopyToClipboard from '@/components/CopyToClipboard.vue'
import EditLink from '@/views/DetailsView/EditLink.vue'
const props = defineProps({
merch_data: {
type: Object,
required: true,
},
merch_details: {
type: Object,
required: true,
}
})
const editing = ref(false)
const formattedName = computed(() => {
const name = props.merch_data?.name
if (!name) return ''
return name.charAt(0).toUpperCase() + name.slice(1)
})
function handleLinkUpdate(origin, newLink) {
props.merch_data.link = newLink
editing.value = false
}
</script>
<template>
<CopyToClipboard :text="merch_data.link">
<n-divider title-placement="left">{{ formattedName }}</n-divider>
</CopyToClipboard>
<div v-if="!editing">
<template v-if="merch_data.link">
<a :href="merch_data.link" target="_blank" rel="noopener" class="default-color">
{{ merch_data.link }}
</a>
<n-button type="primary" style="margin-left: 8px" @click="editing = true">
Edit link
</n-button>
</template>
<template v-else>
<span class="default-color underline link-like-text" @click="editing = true"> Add link </span>
</template>
</div>
<EditLink
v-else
:merch-uuid="merch_details.merch_uuid"
:origin="merch_data.name"
:name="merch_details.name"
:model-value="merch_data.link"
@update:model-value="handleLinkUpdate(merch_data.name, $event)"
@cancel-edit="editing=false"
/>
</template>
<style scoped></style>

View file

@ -1,20 +1,69 @@
<script setup> <script setup>
import ScrollToTopButton from '@/components/ScrollToTopButton.vue' import { onMounted, ref } from 'vue'
import TargetZeroesTab from '@/views/ZeroPricesView/TargetZeroesTab.vue' import { useZeroPrices } from '@/api/zeroPrices.js'
import PeriodSelectTab from '@/views/ZeroPricesView/PeriodSelectTab.vue' import ZeroPriceCard from '@/views/ZeroPricesView/ZeroPriceCard.vue'
import ZeroPricesToolbar from '@/views/ZeroPricesView/ZeroPricesToolbar.vue'
const { getZeroPrices } = useZeroPrices()
const zeroPrices = ref([])
const toDelete = ref([])
const handleToggle = ({ id, merch_uuid, checked }) => {
if (checked) {
toDelete.value.push({ id, merch_uuid });
} else {
toDelete.value = toDelete.value.filter(item => item.id !== id);
}
}
const handleSelectAll = () => {
toDelete.value = zeroPrices.value.map(item => ({
id: item.id,
merch_uuid: item.merch_uuid
}))
}
const fetchZeroPrices = async () => {
try {
const response = await getZeroPrices()
zeroPrices.value = Array.isArray(response.data) ? response.data : []
} catch (error) {
console.error(error)
}
}
const handleDeleted = () => {
toDelete.value = []
fetchZeroPrices()
}
onMounted(() => {
fetchZeroPrices()
})
</script> </script>
<template> <template>
<n-tabs type="line" animated> <div v-if="zeroPrices.length === 0">
<n-tab-pane name="Target zeroes" tab="Target zeroes"> <n-h2 class="text-center">Zero prices</n-h2>
<TargetZeroesTab /> <n-h3 class="text-center">No data</n-h3>
</n-tab-pane> </div>
<n-tab-pane name="Period select" tab="Period select"> <div v-else>
<PeriodSelectTab /> <div class="sticky-search-container">
</n-tab-pane> <ZeroPricesToolbar
</n-tabs> :selected="toDelete"
<ScrollToTopButton /> @deleted="handleDeleted"
@selectAll="handleSelectAll"
/>
</div>
<div v-for="item in zeroPrices" :key="item.created_at">
<ZeroPriceCard
:zero-price="item"
@toggle="handleToggle"
:checked="toDelete.some(t => t.id === item.id)"
/>
</div>
</div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View file

@ -1,54 +0,0 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useZeroPrices } from '@/api/zeroPrices.js'
const range = ref(null)
const setTodayRange = () => {
const now = new Date()
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000)
range.value = [start.getTime(), end.getTime()]
}
onMounted(() => {
setTodayRange()
})
const toRFCtime = (timestamp) => {
return new Date(timestamp).toISOString()
}
const { deleteZeroPricesPeriod } = useZeroPrices()
const deleteEnabled = computed(() => {
return range.value === null
})
const deletePeriod = async () => {
if (range.value !== null) {
const start = toRFCtime(range.value[0])
const end = toRFCtime(range.value[1])
await deleteZeroPricesPeriod(start, end)
} else {
console.log('Delete period select zero prices error')
}
}
</script>
<template>
<n-date-picker
v-model:value="range"
type="datetimerange"
format="HH:mm:ss dd-MM-yyyy"
clearable
/>
<div class="button-container-center">
<n-button class="center-button w360" type="error" :disabled="deleteEnabled" @click="deletePeriod">Delete</n-button>
</div>
</template>
<style scoped></style>

View file

@ -1,71 +0,0 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useZeroPrices } from '@/api/zeroPrices.js'
import ZeroPriceCard from '@/views/ZeroPricesView/ZeroPriceCard.vue'
import ZeroPricesToolbar from '@/views/ZeroPricesView/ZeroPricesToolbar.vue'
const { getZeroPrices } = useZeroPrices()
const zeroPrices = ref([])
const toDelete = ref([])
const handleToggle = ({ id, merch_uuid, checked }) => {
if (checked) {
toDelete.value.push({ id, merch_uuid });
} else {
toDelete.value = toDelete.value.filter(item => item.id !== id);
}
}
const handleSelectAll = () => {
toDelete.value = zeroPrices.value.map(item => ({
id: item.id,
merch_uuid: item.merch_uuid
}))
}
const fetchZeroPrices = async () => {
try {
const response = await getZeroPrices()
zeroPrices.value = Array.isArray(response.data) ? response.data : []
} catch (error) {
console.error(error)
}
}
const handleDeleted = () => {
toDelete.value = []
fetchZeroPrices()
}
onMounted(() => {
fetchZeroPrices()
})
</script>
<template>
<div v-if="zeroPrices.length === 0">
<n-h2 class="text-center">Zero prices</n-h2>
<n-h3 class="text-center">No data</n-h3>
</div>
<div v-else>
<div class="sticky-search-container">
<ZeroPricesToolbar
:selected="toDelete"
@deleted="handleDeleted"
@selectAll="handleSelectAll"
/>
</div>
<div v-for="item in zeroPrices" :key="item.created_at">
<ZeroPriceCard
:zero-price="item"
@toggle="handleToggle"
:checked="toDelete.some(t => t.id === item.id)"
/>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -1,7 +1,4 @@
<script setup> <script setup>
import { computed } from 'vue'
import { originColors } from '@/services/colors.js'
const props = defineProps({ const props = defineProps({
zeroPrice: { zeroPrice: {
type: Object, type: Object,
@ -22,10 +19,6 @@ const handleCheckboxChange = (newValue) => {
checked: newValue, checked: newValue,
}) })
} }
const currentOriginColor = computed(() => {
return originColors[props.zeroPrice.origin] || '#fff';
});
</script> </script>
<template> <template>
@ -38,12 +31,7 @@ const currentOriginColor = computed(() => {
</n-gi> </n-gi>
<n-gi><strong>Name:</strong> {{ props.zeroPrice.name }}</n-gi> <n-gi><strong>Name:</strong> {{ props.zeroPrice.name }}</n-gi>
<n-gi><strong>Created:</strong> {{ props.zeroPrice.created_at }}</n-gi> <n-gi><strong>Created:</strong> {{ props.zeroPrice.created_at }}</n-gi>
<n-gi <n-gi><strong>Origin:</strong> {{ props.zeroPrice.origin }}</n-gi>
><strong>Origin:</strong>
<span class="bordered" :style="{ borderColor: currentOriginColor }">
{{ props.zeroPrice.origin }}
</span>
</n-gi>
</n-grid> </n-grid>
</div> </div>
</template> </template>
@ -61,11 +49,4 @@ const currentOriginColor = computed(() => {
gap: 12px; gap: 12px;
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
} }
.bordered {
border: 1px solid;
border-radius: 4px;
padding: 2px;
margin: 3px;
}
</style> </style>