功能基本完成

This commit is contained in:
2026-04-14 17:28:37 +08:00
parent 6eb6e76bfd
commit 0310fd9aa3
8 changed files with 110 additions and 11 deletions
+1 -1
View File
@@ -13,5 +13,5 @@
} }
] ]
}, },
"lastUpdated": 1776157357538 "lastUpdated": 1776158712744
} }
+38
View File
@@ -709,4 +709,42 @@ func ApiPurchase(r *gin.RouterGroup) {
ReturnJson(ctx, "apiOK", nil) ReturnJson(ctx, "apiOK", nil)
}) })
// 获取订单数量统计
r.POST("/getordercount", func(ctx *gin.Context) {
isAuth, _, _ := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type OrderCount struct {
Pending int64 `json:"pending"` // 待处理
Ordered int64 `json:"ordered"` // 已下单
Arrived int64 `json:"arrived"` // 已到达
Received int64 `json:"received"` // 已收件
Lost int64 `json:"lost"` // 丢件
Returned int64 `json:"returned"` // 退件
Total int64 `json:"total"` // 总数
}
var count OrderCount
models.DB.Model(&TabPurchaseOrder{}).Count(&count.Total)
models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "pending").Count(&count.Pending)
models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "ordered").Count(&count.Ordered)
models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "arrived").Count(&count.Arrived)
models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "received").Count(&count.Received)
models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "lost").Count(&count.Lost)
models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "returned").Count(&count.Returned)
ReturnJson(ctx, "apiOK", map[string]interface{}{
"pending": count.Pending,
"ordered": count.Ordered,
"arrived": count.Arrived,
"received": count.Received,
"lost": count.Lost,
"returned": count.Returned,
"total": count.Total,
})
})
} }
+5
View File
@@ -6,6 +6,11 @@ export const purchaseApi = {
return api.post('/purchase/getorders', params) return api.post('/purchase/getorders', params)
}, },
/** 获取订单数量统计 */
getOrderCount() {
return api.post('/purchase/getordercount', {})
},
/** 新增采购订单 */ /** 新增采购订单 */
addOrder(data) { addOrder(data) {
return api.post('/purchase/addorder', data) return api.post('/purchase/addorder', data)
+3 -1
View File
@@ -94,6 +94,7 @@
"link": "Link", "link": "Link",
"no_photos": "No photos", "no_photos": "No photos",
"open_link": "Open Link", "open_link": "Open Link",
"copy_link": "Copy Link",
"no_costs": "No cost records", "no_costs": "No cost records",
"cost_total": "Total", "cost_total": "Total",
"change_status": "Change Status", "change_status": "Change Status",
@@ -191,7 +192,8 @@
"today_schedule_count": "Today: {count} schedule(s)", "today_schedule_count": "Today: {count} schedule(s)",
"today_no_schedule": "No schedules today", "today_no_schedule": "No schedules today",
"loading": "Loading...", "loading": "Loading...",
"today": "Today: {date}" "today": "Today: {date}",
"pending_orders": "Pending orders"
}, },
"message": { "message": {
"functionality_not_yet_developed": "Functionality not yet developed", "functionality_not_yet_developed": "Functionality not yet developed",
+3 -1
View File
@@ -94,6 +94,7 @@
"link": "链接", "link": "链接",
"no_photos": "暂无图片", "no_photos": "暂无图片",
"open_link": "打开链接", "open_link": "打开链接",
"copy_link": "复制链接",
"no_costs": "暂无费用记录", "no_costs": "暂无费用记录",
"cost_total": "合计", "cost_total": "合计",
"change_status": "变更状态", "change_status": "变更状态",
@@ -191,7 +192,8 @@
"today_schedule_count": "今日共 {count} 个日程", "today_schedule_count": "今日共 {count} 个日程",
"today_no_schedule": "今日暂无日程", "today_no_schedule": "今日暂无日程",
"loading": "加载中...", "loading": "加载中...",
"today": "今日:{date}" "today": "今日:{date}",
"pending_orders": "待处理订单"
}, },
"message": { "message": {
"functionality_not_yet_developed": "功能未开发", "functionality_not_yet_developed": "功能未开发",
+33 -3
View File
@@ -4,6 +4,7 @@ import { useUserStore } from '@/stores/user'
import { useUsersStore } from '@/stores/users' import { useUsersStore } from '@/stores/users'
import { usePageTitle } from '@/composables/usePageTitle' import { usePageTitle } from '@/composables/usePageTitle'
import { scheduleApi } from '@/api/schedule' import { scheduleApi } from '@/api/schedule'
import { purchaseApi } from '@/api/purchase'
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
usePageTitle('appname.home') usePageTitle('appname.home')
@@ -15,6 +16,10 @@ const usersStore = useUsersStore()
const todaySchedules = ref([]) const todaySchedules = ref([])
const loadingSchedules = ref(false) const loadingSchedules = ref(false)
// 采购订单数据
const pendingOrderCount = ref(0)
const loadingOrders = ref(false)
// 获取今日日期字符串 // 获取今日日期字符串
const todayStr = computed(() => { const todayStr = computed(() => {
const today = new Date() const today = new Date()
@@ -57,6 +62,21 @@ async function fetchTodaySchedules() {
} }
} }
// 获取待处理订单数量
async function fetchPendingOrders() {
loadingOrders.value = true
try {
const { errCode, data } = await purchaseApi.getOrderCount()
if (errCode === 0 && data) {
pendingOrderCount.value = data.pending || 0
}
} catch (e) {
console.error('获取订单数量失败', e)
} finally {
loadingOrders.value = false
}
}
// 获取用户名 // 获取用户名
function getUsername(userId) { function getUsername(userId) {
if (!userId) return '' if (!userId) return ''
@@ -100,6 +120,9 @@ function getWeekday(dateStr) {
onMounted(() => { onMounted(() => {
fetchTodaySchedules() fetchTodaySchedules()
if (userStore.isLoggedIn) {
fetchPendingOrders()
}
}) })
</script> </script>
@@ -161,12 +184,19 @@ onMounted(() => {
<!-- 功能入口卡片 --> <!-- 功能入口卡片 -->
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div <RouterLink
to="/purchase"
class="rounded-xl border border-gray-200 bg-white px-5 py-4 transition-shadow hover:shadow-md dark:border-dk-muted dark:bg-dk-card" class="rounded-xl border border-gray-200 bg-white px-5 py-4 transition-shadow hover:shadow-md dark:border-dk-muted dark:bg-dk-card"
> >
<p class="mb-1 text-sm text-gray-500">{{ t('appname.purchase') }}</p> <p class="mb-1 text-sm text-gray-500">{{ t('appname.purchase') }}</p>
<p class="text-lg font-bold text-gray-900 dark:text-white"></p> <p class="text-lg font-bold text-gray-900 dark:text-white">
</div> <span v-if="loadingOrders">...</span>
<span v-else :class="{ 'text-yellow-600 dark:text-yellow-400': pendingOrderCount > 0 }">
{{ pendingOrderCount || '—' }}
</span>
</p>
<p class="text-xs text-gray-400">{{ t('home.pending_orders') }}</p>
</RouterLink>
</div> </div>
</div> </div>
</template> </template>
@@ -160,7 +160,7 @@ onMounted(fetchOrders)
@click="jumpToOrder(order.ID)" @click="jumpToOrder(order.ID)"
> >
<td class="px-6 py-3 text-gray-400">{{ order.ID }}</td> <td class="px-6 py-3 text-gray-400">{{ order.ID }}</td>
<td class="px-6 py-3 font-medium text-gray-900 dark:text-white">{{ order.Title }}</td> <td class="px-6 py-3 max-w-[200px] truncate font-medium text-gray-900 dark:text-white">{{ order.Title }}</td>
<td class="px-6 py-3 max-w-[200px] truncate text-gray-600 dark:text-gray-300">{{ order.Remark || '-' }}</td> <td class="px-6 py-3 max-w-[200px] truncate text-gray-600 dark:text-gray-300">{{ order.Remark || '-' }}</td>
<td class="px-6 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400">{{ formatDate(order.CreatedAt) }}</td> <td class="px-6 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400">{{ formatDate(order.CreatedAt) }}</td>
<td class="px-6 py-3"> <td class="px-6 py-3">
@@ -137,6 +137,14 @@ function openLink() {
window.open(url, "_blank"); window.open(url, "_blank");
} }
function copyLink() {
if (!order.value?.Link) return;
navigator.clipboard.writeText(order.value.Link.trim()).then(() => {
const toast = useToastStore()
toast.success('链接已复制')
})
}
function getStatusLabel(status) { function getStatusLabel(status) {
if (!status) return ""; if (!status) return "";
const opt = statusOptions.find((o) => o.value === status); const opt = statusOptions.find((o) => o.value === status);
@@ -450,12 +458,26 @@ onMounted(fetchOrder);
<label class="mb-1 block text-xs font-medium text-gray-400">{{ <label class="mb-1 block text-xs font-medium text-gray-400">{{
t("purchase.link") t("purchase.link")
}}</label> }}</label>
<div v-if="order?.Link" class="flex items-center gap-2"> <div v-if="order?.Link" class="flex flex-wrap items-center gap-2">
<p class="max-w-xs truncate text-blue-600 dark:text-blue-400"> <a
:href="order.Link.trim().startsWith('http') ? order.Link.trim() : 'https://' + order.Link.trim()"
target="_blank"
class="max-w-[400px] truncate rounded bg-gray-100 px-3 py-1.5 text-xs text-blue-600 hover:bg-gray-200 dark:bg-dk-base dark:text-blue-400 dark:hover:bg-gray-800"
:title="order.Link"
>
{{ order.Link }} {{ order.Link }}
</p> </a>
<button <button
class="inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-900/20" class="inline-flex items-center gap-1 rounded bg-blue-100 px-3 py-1.5 text-xs font-medium text-blue-700 hover:bg-blue-200 dark:bg-blue-900/40 dark:text-blue-300 dark:hover:bg-blue-900/60"
@click="copyLink"
>
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
{{ t('purchase.copy_link') }}
</button>
<button
class="inline-flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
@click="openLink" @click="openLink"
> >
<IconExternalLink :size="14" /> <IconExternalLink :size="14" />