925 lines
33 KiB
Vue
925 lines
33 KiB
Vue
<template>
|
||
<div class="space-y-6">
|
||
<!-- 页面头部 -->
|
||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
|
||
<h1 class="text-2xl font-bold text-gray-900">订单管理</h1>
|
||
<div class="mt-4 sm:mt-0">
|
||
<button
|
||
@click="openCreateModal"
|
||
class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||
>
|
||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||
</svg>
|
||
创建订单
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||
<div class="p-5">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">总订单</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.total }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||
<div class="p-5">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">进行中</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.inProgress }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||
<div class="p-5">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">已完成</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.completed }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||
<div class="p-5">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">已取消</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.cancelled }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 筛选和搜索 -->
|
||
<div class="bg-white shadow rounded-lg">
|
||
<div class="p-6">
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<label for="search" class="block text-sm font-medium text-gray-700 mb-1">搜索订单</label>
|
||
<input
|
||
id="search"
|
||
v-model="searchQuery"
|
||
type="text"
|
||
placeholder="订单号、客户姓名、项目标题"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label for="status-filter" class="block text-sm font-medium text-gray-700 mb-1">订单状态</label>
|
||
<select
|
||
id="status-filter"
|
||
v-model="statusFilter"
|
||
class="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||
>
|
||
<option value="">全部状态</option>
|
||
<option value="pending">待处理</option>
|
||
<option value="confirmed">已确认</option>
|
||
<option value="in_progress">进行中</option>
|
||
<option value="completed">已完成</option>
|
||
<option value="cancelled">已取消</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="service-filter" class="block text-sm font-medium text-gray-700 mb-1">服务类型</label>
|
||
<select
|
||
id="service-filter"
|
||
v-model="serviceFilter"
|
||
class="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||
>
|
||
<option value="">全部服务</option>
|
||
<option value="voice">语音通话</option>
|
||
<option value="video">视频通话</option>
|
||
<option value="document">文档翻译</option>
|
||
<option value="interpretation">口译服务</option>
|
||
<option value="localization">本地化</option>
|
||
<option value="proofreading">校对服务</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="date-range" class="block text-sm font-medium text-gray-700 mb-1">创建时间</label>
|
||
<select
|
||
id="date-range"
|
||
v-model="dateRange"
|
||
class="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||
>
|
||
<option value="">全部时间</option>
|
||
<option value="today">今天</option>
|
||
<option value="week">本周</option>
|
||
<option value="month">本月</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 订单列表 -->
|
||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||
<div class="px-6 py-4 border-b border-gray-200">
|
||
<h3 class="text-lg font-medium text-gray-900">订单列表</h3>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="min-w-full divide-y divide-gray-200">
|
||
<thead class="bg-gray-50">
|
||
<tr>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
订单信息
|
||
</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
客户信息
|
||
</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
项目信息
|
||
</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
服务详情
|
||
</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
状态
|
||
</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
预估费用
|
||
</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
口译员
|
||
</th>
|
||
<th scope="col" class="relative px-6 py-3">
|
||
<span class="sr-only">操作</span>
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="bg-white divide-y divide-gray-200">
|
||
<tr v-for="order in filteredOrders" :key="order.id" class="hover:bg-gray-50">
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div class="text-sm font-medium text-gray-900">{{ order.order_number }}</div>
|
||
<div class="text-sm text-gray-500">{{ formatDate(order.created_at) }}</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div>
|
||
<div class="text-sm font-medium text-gray-900">{{ order.client_name }}</div>
|
||
<div class="text-sm text-gray-500">{{ order.client_email }}</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div>
|
||
<div class="text-sm font-medium text-gray-900">{{ order.project_name }}</div>
|
||
<div class="text-sm text-gray-500">{{ getUrgencyText(order.urgency) }}</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div>
|
||
<div class="text-sm font-medium text-gray-900">{{ getServiceTypeText(order.service_type) }}</div>
|
||
<div class="text-sm text-gray-500">{{ getLanguageName(order.source_language) }} → {{ getLanguageName(order.target_language) }}</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<span :class="getStatusClass(order.status)" class="inline-flex px-2 py-1 text-xs font-semibold rounded-full">
|
||
{{ getStatusText(order.status) }}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||
¥{{ order.estimated_cost || 0 }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||
{{ order.interpreter_name || '未分配' }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||
<button
|
||
@click="viewOrder(order)"
|
||
class="text-blue-600 hover:text-blue-900 mr-3"
|
||
>
|
||
查看
|
||
</button>
|
||
<button
|
||
@click="editOrder(order)"
|
||
class="text-green-600 hover:text-green-900 mr-3"
|
||
>
|
||
编辑
|
||
</button>
|
||
<button
|
||
@click="handleDeleteOrder(order)"
|
||
class="text-red-600 hover:text-red-900"
|
||
>
|
||
删除
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 空状态 -->
|
||
<div v-if="filteredOrders.length === 0" class="text-center py-12">
|
||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||
</svg>
|
||
<h3 class="mt-2 text-sm font-medium text-gray-900">暂无订单</h3>
|
||
<p class="mt-1 text-sm text-gray-500">开始创建您的第一个翻译订单</p>
|
||
<div class="mt-6">
|
||
<button
|
||
@click="openCreateModal"
|
||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||
>
|
||
创建订单
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 创建订单模态框 -->
|
||
<div v-if="showCreateModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||
<div class="relative top-10 mx-auto p-5 border w-11/12 md:w-2/3 lg:w-1/2 shadow-lg rounded-md bg-white max-h-[90vh] overflow-y-auto">
|
||
<div class="mt-3">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-medium text-gray-900">创建新订单</h3>
|
||
<button @click="showCreateModal = false" class="text-gray-400 hover:text-gray-600">
|
||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<form @submit.prevent="submitCreateOrder" class="space-y-6">
|
||
<!-- 基本信息 -->
|
||
<div class="border-b border-gray-200 pb-4">
|
||
<h4 class="text-md font-medium text-gray-900 mb-3">基本信息</h4>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">客户姓名 *</label>
|
||
<input
|
||
v-model="newOrder.client_name"
|
||
type="text"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="请输入客户姓名"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">客户邮箱 *</label>
|
||
<input
|
||
v-model="newOrder.client_email"
|
||
type="email"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="请输入客户邮箱"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">客户电话</label>
|
||
<input
|
||
v-model="newOrder.client_phone"
|
||
type="tel"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="请输入客户电话"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">项目标题 *</label>
|
||
<input
|
||
v-model="newOrder.project_name"
|
||
type="text"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="请输入项目标题"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 服务详情 -->
|
||
<div class="border-b border-gray-200 pb-4">
|
||
<h4 class="text-md font-medium text-gray-900 mb-3">服务详情</h4>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">服务类型 *</label>
|
||
<select
|
||
v-model="newOrder.service_type"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value="">请选择服务类型</option>
|
||
<option value="voice">语音通话</option>
|
||
<option value="video">视频通话</option>
|
||
<option value="document">文档翻译</option>
|
||
<option value="interpretation">口译服务</option>
|
||
<option value="localization">本地化</option>
|
||
<option value="proofreading">校对服务</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">源语言 *</label>
|
||
<select
|
||
v-model="newOrder.source_language"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value="">请选择源语言</option>
|
||
<option value="zh">中文</option>
|
||
<option value="en">英文</option>
|
||
<option value="ja">日文</option>
|
||
<option value="ko">韩文</option>
|
||
<option value="fr">法文</option>
|
||
<option value="de">德文</option>
|
||
<option value="es">西班牙文</option>
|
||
<option value="ru">俄文</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">目标语言 *</label>
|
||
<select
|
||
v-model="newOrder.target_language"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value="">请选择目标语言</option>
|
||
<option value="zh">中文</option>
|
||
<option value="en">英文</option>
|
||
<option value="ja">日文</option>
|
||
<option value="ko">韩文</option>
|
||
<option value="fr">法文</option>
|
||
<option value="de">德文</option>
|
||
<option value="es">西班牙文</option>
|
||
<option value="ru">俄文</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">紧急程度 *</label>
|
||
<select
|
||
v-model="newOrder.urgency"
|
||
required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value="normal">普通</option>
|
||
<option value="urgent">紧急</option>
|
||
<option value="emergency">特急</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 项目描述 -->
|
||
<div class="border-b border-gray-200 pb-4">
|
||
<h4 class="text-md font-medium text-gray-900 mb-3">项目描述</h4>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">详细描述</label>
|
||
<textarea
|
||
v-model="newOrder.project_description"
|
||
rows="4"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="请详细描述项目需求、要求等..."
|
||
></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 预算信息 -->
|
||
<div class="border-b border-gray-200 pb-4">
|
||
<h4 class="text-md font-medium text-gray-900 mb-3">预算信息</h4>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">预估费用(元)</label>
|
||
<input
|
||
v-model.number="newOrder.estimated_cost"
|
||
type="number"
|
||
min="0"
|
||
step="0.01"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="0.00"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">预计完成时间</label>
|
||
<input
|
||
v-model="newOrder.scheduled_date"
|
||
type="date"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 客户公司和预计时长 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">客户公司</label>
|
||
<input
|
||
v-model="newOrder.client_company"
|
||
type="text"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="可选"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">预计时长(分钟)</label>
|
||
<input
|
||
v-model.number="newOrder.expected_duration"
|
||
type="number"
|
||
min="1"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="60"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 预约时间 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">预约时间</label>
|
||
<input
|
||
v-model="newOrder.scheduled_time"
|
||
type="time"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">备注</label>
|
||
<textarea
|
||
v-model="newOrder.notes"
|
||
rows="3"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="其他说明..."
|
||
/>
|
||
</div>
|
||
|
||
<div class="bg-gray-50 p-4 rounded-lg">
|
||
<div id="estimated-cost" class="text-lg font-semibold text-gray-900">
|
||
预估费用: ¥{{ calculateOrderCost(
|
||
newOrder.service_type,
|
||
newOrder.source_language,
|
||
newOrder.target_language,
|
||
newOrder.urgency,
|
||
newOrder.expected_duration || 60
|
||
) }}
|
||
</div>
|
||
<div class="text-sm text-gray-600 mt-1">
|
||
费用会根据服务类型、语言对、紧急程度和预计时长自动计算
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 按钮 -->
|
||
<div class="flex justify-end space-x-3 pt-4">
|
||
<button
|
||
type="button"
|
||
@click="showCreateModal = false"
|
||
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
|
||
>
|
||
创建订单
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { useSupabaseData } from '~/composables/useSupabaseData'
|
||
import { useToast } from '~/composables/useToast'
|
||
|
||
// 定义页面meta
|
||
definePageMeta({
|
||
middleware: 'auth'
|
||
})
|
||
|
||
// 使用Supabase数据操作
|
||
const {
|
||
getOrders,
|
||
createOrder,
|
||
updateOrder,
|
||
deleteOrder,
|
||
getOrderStats,
|
||
calculateOrderCost
|
||
} = useSupabaseData()
|
||
|
||
const { showToast } = useToast()
|
||
|
||
// 响应式数据
|
||
const loading = ref(false)
|
||
const error = ref('')
|
||
const searchQuery = ref('')
|
||
const statusFilter = ref('')
|
||
const serviceFilter = ref('')
|
||
const dateRange = ref('')
|
||
const showCreateModal = ref(false)
|
||
|
||
// 订单数据
|
||
const allOrders = ref([])
|
||
const stats = ref({
|
||
total: 0,
|
||
pending: 0,
|
||
inProgress: 0,
|
||
completed: 0,
|
||
totalRevenue: 0
|
||
})
|
||
|
||
// 新订单表单数据
|
||
const newOrder = ref({
|
||
client_name: '',
|
||
client_email: '',
|
||
client_phone: '',
|
||
client_company: '',
|
||
project_name: '',
|
||
project_description: '',
|
||
source_language: 'zh',
|
||
target_language: 'en',
|
||
service_type: 'audio',
|
||
urgency: 'normal',
|
||
expected_duration: 60,
|
||
scheduled_date: '',
|
||
scheduled_time: '',
|
||
notes: ''
|
||
})
|
||
|
||
// 计算属性
|
||
const filteredOrders = computed(() => {
|
||
let filtered = allOrders.value
|
||
|
||
// 搜索过滤
|
||
if (searchQuery.value) {
|
||
const query = searchQuery.value.toLowerCase()
|
||
filtered = filtered.filter(order =>
|
||
order.client_name?.toLowerCase().includes(query) ||
|
||
order.client_email?.toLowerCase().includes(query) ||
|
||
order.project_name?.toLowerCase().includes(query) ||
|
||
order.order_number?.toLowerCase().includes(query)
|
||
)
|
||
}
|
||
|
||
// 状态过滤
|
||
if (statusFilter.value) {
|
||
filtered = filtered.filter(order => order.status === statusFilter.value)
|
||
}
|
||
|
||
// 服务类型过滤
|
||
if (serviceFilter.value) {
|
||
filtered = filtered.filter(order => order.service_type === serviceFilter.value)
|
||
}
|
||
|
||
// 日期范围过滤
|
||
if (dateRange.value) {
|
||
const today = new Date()
|
||
const filterDate = new Date()
|
||
|
||
switch (dateRange.value) {
|
||
case 'today':
|
||
filterDate.setHours(0, 0, 0, 0)
|
||
filtered = filtered.filter(order =>
|
||
new Date(order.created_at) >= filterDate
|
||
)
|
||
break
|
||
case 'week':
|
||
filterDate.setDate(today.getDate() - 7)
|
||
filtered = filtered.filter(order =>
|
||
new Date(order.created_at) >= filterDate
|
||
)
|
||
break
|
||
case 'month':
|
||
filterDate.setMonth(today.getMonth() - 1)
|
||
filtered = filtered.filter(order =>
|
||
new Date(order.created_at) >= filterDate
|
||
)
|
||
break
|
||
}
|
||
}
|
||
|
||
return filtered
|
||
})
|
||
|
||
// 获取状态文本
|
||
const getStatusText = (status) => {
|
||
const statusMap = {
|
||
pending: '待确认',
|
||
confirmed: '已确认',
|
||
in_progress: '进行中',
|
||
completed: '已完成',
|
||
cancelled: '已取消'
|
||
}
|
||
return statusMap[status] || status
|
||
}
|
||
|
||
// 获取状态样式类
|
||
const getStatusClass = (status) => {
|
||
const classMap = {
|
||
pending: 'bg-yellow-100 text-yellow-800',
|
||
confirmed: 'bg-blue-100 text-blue-800',
|
||
in_progress: 'bg-green-100 text-green-800',
|
||
completed: 'bg-green-100 text-green-800',
|
||
cancelled: 'bg-red-100 text-red-800'
|
||
}
|
||
return classMap[status] || 'bg-gray-100 text-gray-800'
|
||
}
|
||
|
||
// 获取服务类型文本
|
||
const getServiceTypeText = (type) => {
|
||
const typeMap = {
|
||
voice: '语音通话',
|
||
video: '视频通话',
|
||
document: '文档翻译',
|
||
interpretation: '口译服务',
|
||
localization: '本地化',
|
||
proofreading: '校对服务',
|
||
// 向后兼容
|
||
audio: '语音翻译',
|
||
text: '文本翻译'
|
||
}
|
||
return typeMap[type] || type
|
||
}
|
||
|
||
// 获取紧急程度文本
|
||
const getUrgencyText = (urgency) => {
|
||
const urgencyMap = {
|
||
normal: '普通',
|
||
urgent: '紧急',
|
||
emergency: '特急'
|
||
}
|
||
return urgencyMap[urgency] || urgency
|
||
}
|
||
|
||
// 获取语言名称
|
||
const getLanguageName = (code) => {
|
||
const languages = {
|
||
zh: '中文',
|
||
en: '英文',
|
||
ja: '日文',
|
||
ko: '韩文',
|
||
fr: '法文',
|
||
de: '德文',
|
||
es: '西班牙文',
|
||
ru: '俄文'
|
||
}
|
||
return languages[code] || code
|
||
}
|
||
|
||
// 格式化日期
|
||
const formatDate = (dateString) => {
|
||
if (!dateString) return '未知时间'
|
||
const date = new Date(dateString)
|
||
return date.toLocaleDateString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})
|
||
}
|
||
|
||
// 查看订单详情
|
||
const viewOrder = (order) => {
|
||
// 可以跳转到订单详情页面
|
||
console.log('查看订单:', order)
|
||
showToast('订单详情功能开发中', 'info')
|
||
}
|
||
|
||
// 编辑订单
|
||
const editOrder = (order) => {
|
||
// 可以打开编辑模态框或跳转到编辑页面
|
||
console.log('编辑订单:', order)
|
||
showToast('编辑订单功能开发中', 'info')
|
||
}
|
||
|
||
// 删除订单
|
||
const handleDeleteOrder = async (order) => {
|
||
if (confirm(`确定要删除订单 ${order.order_number} 吗?`)) {
|
||
try {
|
||
const success = await deleteOrder(order.id)
|
||
|
||
if (success) {
|
||
// 从列表中移除
|
||
const index = allOrders.value.findIndex(o => o.id === order.id)
|
||
if (index > -1) {
|
||
allOrders.value.splice(index, 1)
|
||
}
|
||
|
||
// 更新统计
|
||
stats.value.total--
|
||
if (order.status === 'pending') stats.value.pending--
|
||
else if (order.status === 'in_progress') stats.value.inProgress--
|
||
else if (order.status === 'completed') stats.value.completed--
|
||
|
||
showToast('订单删除成功', 'success')
|
||
} else {
|
||
throw new Error('删除失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('删除订单失败:', error)
|
||
showToast('删除订单失败', 'error')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 方法
|
||
const loadOrders = async () => {
|
||
try {
|
||
loading.value = true
|
||
|
||
// 从数据库加载真实数据
|
||
const [ordersData, statsData] = await Promise.all([
|
||
getOrders(),
|
||
getOrderStats()
|
||
])
|
||
|
||
allOrders.value = ordersData || []
|
||
stats.value = statsData || {
|
||
total: 0,
|
||
pending: 0,
|
||
inProgress: 0,
|
||
completed: 0,
|
||
cancelled: 0,
|
||
totalRevenue: 0
|
||
}
|
||
|
||
console.log('成功加载订单数据:', ordersData?.length || 0, '条订单')
|
||
} catch (err) {
|
||
console.error('加载订单数据失败:', err)
|
||
error.value = '加载订单数据失败'
|
||
showToast('加载订单数据失败,请稍后重试', 'error')
|
||
|
||
// 初始化空数据
|
||
allOrders.value = []
|
||
stats.value = {
|
||
total: 0,
|
||
pending: 0,
|
||
inProgress: 0,
|
||
completed: 0,
|
||
cancelled: 0,
|
||
totalRevenue: 0
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const openCreateModal = () => {
|
||
// 重置表单
|
||
newOrder.value = {
|
||
client_name: '',
|
||
client_email: '',
|
||
client_phone: '',
|
||
client_company: '',
|
||
project_name: '',
|
||
project_description: '',
|
||
source_language: 'zh',
|
||
target_language: 'en',
|
||
service_type: 'audio',
|
||
urgency: 'normal',
|
||
expected_duration: 60,
|
||
scheduled_date: '',
|
||
scheduled_time: '',
|
||
notes: ''
|
||
}
|
||
showCreateModal.value = true
|
||
}
|
||
|
||
const closeCreateModal = () => {
|
||
showCreateModal.value = false
|
||
}
|
||
|
||
const submitCreateOrder = async () => {
|
||
try {
|
||
loading.value = true
|
||
|
||
// 表单验证
|
||
if (!newOrder.value.client_name || !newOrder.value.client_email || !newOrder.value.project_name) {
|
||
throw new Error('请填写必填信息')
|
||
}
|
||
|
||
// 计算预估费用
|
||
const estimatedCost = calculateOrderCost(
|
||
newOrder.value.service_type,
|
||
newOrder.value.source_language,
|
||
newOrder.value.target_language,
|
||
newOrder.value.urgency,
|
||
newOrder.value.expected_duration
|
||
)
|
||
|
||
// 创建订单
|
||
const orderData = {
|
||
...newOrder.value,
|
||
estimated_cost: estimatedCost
|
||
}
|
||
|
||
const createdOrder = await createOrder(orderData)
|
||
|
||
if (createdOrder) {
|
||
// 添加到订单列表
|
||
allOrders.value.unshift(createdOrder)
|
||
|
||
// 更新统计
|
||
stats.value.total++
|
||
stats.value.pending++
|
||
|
||
showToast('订单创建成功', 'success')
|
||
closeCreateModal()
|
||
} else {
|
||
throw new Error('创建订单失败')
|
||
}
|
||
} catch (err) {
|
||
console.error('创建订单失败:', err)
|
||
showToast(err.message || '创建订单失败', 'error')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 重新计算费用
|
||
const recalculateCost = () => {
|
||
if (newOrder.value.service_type && newOrder.value.source_language &&
|
||
newOrder.value.target_language && newOrder.value.urgency) {
|
||
const cost = calculateOrderCost(
|
||
newOrder.value.service_type,
|
||
newOrder.value.source_language,
|
||
newOrder.value.target_language,
|
||
newOrder.value.urgency,
|
||
newOrder.value.expected_duration || 60
|
||
)
|
||
|
||
// 显示计算出的费用
|
||
nextTick(() => {
|
||
const costElement = document.querySelector('#estimated-cost')
|
||
if (costElement) {
|
||
costElement.textContent = `预估费用: ¥${cost}`
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 监听表单字段变化,自动重新计算费用
|
||
watch([
|
||
() => newOrder.value.service_type,
|
||
() => newOrder.value.source_language,
|
||
() => newOrder.value.target_language,
|
||
() => newOrder.value.urgency,
|
||
() => newOrder.value.expected_duration
|
||
], recalculateCost)
|
||
|
||
// 页面挂载时加载数据
|
||
onMounted(() => {
|
||
loadOrders()
|
||
})
|
||
</script>
|
||
|