接入商品评论数据
This commit is contained in:
111
.github/copilot-instructions.md
vendored
Normal file
111
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Copilot Instructions for This Repository
|
||||||
|
|
||||||
|
## Project context
|
||||||
|
|
||||||
|
This repository contains a multi-module mall system with admin-oriented database SQL organized by execution stage and business domain.
|
||||||
|
For any task related to database changes, RPC, RLS, or admin data access, treat `docs/sql/README_EXECUTION_ORDER.md` as the authoritative execution and validation reference.
|
||||||
|
|
||||||
|
## Core database rules
|
||||||
|
|
||||||
|
1. Follow the required execution order for database changes:
|
||||||
|
- Schema / Migration first
|
||||||
|
- RLS second
|
||||||
|
- RPC last
|
||||||
|
|
||||||
|
2. Never change the order to RPC -> RLS -> Schema.
|
||||||
|
RPC and RLS may depend on fields, indexes, or policies created in Schema.
|
||||||
|
|
||||||
|
3. Soft delete is the default deletion standard.
|
||||||
|
Do not implement physical deletes unless the task explicitly requires it and the user confirms.
|
||||||
|
Prefer updating soft-delete fields such as:
|
||||||
|
- `deleted_at`
|
||||||
|
- `deleted_by`
|
||||||
|
- `restored_at`
|
||||||
|
- `restored_by`
|
||||||
|
|
||||||
|
4. For delete-related RPC or SQL:
|
||||||
|
- do not use physical `DELETE` by default
|
||||||
|
- write `deleted_at` and `deleted_by`
|
||||||
|
- preserve auditability
|
||||||
|
- apply cascade soft delete when the existing design requires it
|
||||||
|
|
||||||
|
5. For RLS-related work:
|
||||||
|
- assume soft-deleted rows should be filtered by default
|
||||||
|
- when appropriate, ensure policies include logic equivalent to `deleted_at IS NULL`
|
||||||
|
- do not weaken existing RLS rules without clearly explaining why
|
||||||
|
|
||||||
|
6. For existing RPC/functions:
|
||||||
|
- prefer `CREATE OR REPLACE FUNCTION`
|
||||||
|
- avoid creating duplicate function variants unless versioning is explicitly required
|
||||||
|
|
||||||
|
## Required working method
|
||||||
|
|
||||||
|
1. Analyze first, edit second.
|
||||||
|
Before making changes, first inspect the relevant files and summarize:
|
||||||
|
- affected files
|
||||||
|
- dependencies
|
||||||
|
- execution order
|
||||||
|
- risks
|
||||||
|
|
||||||
|
2. Limit the scope of changes.
|
||||||
|
Only modify the business domain and files directly related to the task.
|
||||||
|
Do not refactor unrelated modules.
|
||||||
|
|
||||||
|
3. Do not invent schema details.
|
||||||
|
Before generating SQL or admin code, inspect the actual table definitions, RPC files, and RLS files already present in the repository.
|
||||||
|
|
||||||
|
4. Preserve current naming and directory conventions.
|
||||||
|
Keep the existing staged SQL structure such as:
|
||||||
|
- `10_schema/**`
|
||||||
|
- `20_rls/**`
|
||||||
|
- `30_rpc/**`
|
||||||
|
|
||||||
|
5. Keep admin-facing behavior consistent with current project conventions.
|
||||||
|
Do not silently break existing frontend call paths, parameter names, return structures, or pagination contracts.
|
||||||
|
|
||||||
|
## Output requirements for Copilot responses
|
||||||
|
|
||||||
|
For any non-trivial task, respond with:
|
||||||
|
|
||||||
|
1. A short impact summary
|
||||||
|
2. The list of files to change
|
||||||
|
3. The implementation plan in the correct order
|
||||||
|
4. The actual code/SQL changes
|
||||||
|
5. A validation checklist
|
||||||
|
6. Any risks, assumptions, or rollback notes
|
||||||
|
|
||||||
|
## Validation requirements
|
||||||
|
|
||||||
|
When a task changes database behavior, include verification steps for:
|
||||||
|
|
||||||
|
- field existence
|
||||||
|
- soft-delete index existence
|
||||||
|
- RLS effectiveness
|
||||||
|
- RPC behavior
|
||||||
|
- whether deletion remains soft delete instead of physical delete
|
||||||
|
- whether `deleted_at` / `deleted_by` are written correctly
|
||||||
|
- whether required cascade soft delete behavior still works
|
||||||
|
|
||||||
|
## SQL and Supabase safety rules
|
||||||
|
|
||||||
|
1. Do not assume a SQL file has already been executed just because it exists in the repository.
|
||||||
|
2. Do not assume a table is in `public` unless verified.
|
||||||
|
3. If a task involves Supabase-exposed data access, consider RLS impact explicitly.
|
||||||
|
4. If adding a new table or new admin data access path, mention whether RLS and policies also need to be added or updated.
|
||||||
|
|
||||||
|
## Preferred behavior for complex tasks
|
||||||
|
|
||||||
|
For large tasks, break the work into phases:
|
||||||
|
|
||||||
|
1. inspect
|
||||||
|
2. plan
|
||||||
|
3. implement
|
||||||
|
4. validate
|
||||||
|
|
||||||
|
Do not jump straight into large-scale edits without first identifying dependencies.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
Primary reference for database execution order and validation:
|
||||||
|
|
||||||
|
- `docs/sql/README_EXECUTION_ORDER.md`
|
||||||
@@ -246,7 +246,7 @@ export const routes: RouteRecord[] = [
|
|||||||
// ========== 店铺模块 ==========
|
// ========== 店铺模块 ==========
|
||||||
{
|
{
|
||||||
id: 'shop_manage',
|
id: 'shop_manage',
|
||||||
title: '店铺管理',
|
title: '我的店铺',
|
||||||
path: '/pages/mall/admin/shop/manage',
|
path: '/pages/mall/admin/shop/manage',
|
||||||
componentKey: 'ShopManage',
|
componentKey: 'ShopManage',
|
||||||
parentId: 'shop',
|
parentId: 'shop',
|
||||||
|
|||||||
@@ -5,30 +5,33 @@
|
|||||||
<view class="search-row">
|
<view class="search-row">
|
||||||
<view class="search-item">
|
<view class="search-item">
|
||||||
<text class="label">评价时间:</text>
|
<text class="label">评价时间:</text>
|
||||||
<view class="mock-date-range">
|
<view class="date-range-row">
|
||||||
<text class="emoji">📅</text>
|
<input class="mock-input date-input" v-model="startTime" placeholder="开始 2025-01-01" />
|
||||||
<text class="txt">开始日期 - 结束日期</text>
|
<text class="date-sep">~</text>
|
||||||
|
<input class="mock-input date-input" v-model="endTime" placeholder="结束 2025-12-31" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="search-item">
|
|
||||||
<text class="label">评价状态:</text>
|
|
||||||
<view class="mock-select"><text>请选择</text><text class="arrow">▼</text></view>
|
|
||||||
</view>
|
|
||||||
<view class="search-item">
|
<view class="search-item">
|
||||||
<text class="label">审核状态:</text>
|
<text class="label">审核状态:</text>
|
||||||
<view class="mock-select"><text>请选择</text><text class="arrow">▼</text></view>
|
<picker :range="statusLabels" :value="statusPickerIndex" @change="onStatusChange">
|
||||||
|
<view class="mock-select">
|
||||||
|
<text>{{ statusLabels[statusPickerIndex] }}</text>
|
||||||
|
<text class="arrow">▼</text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="search-row mt-16">
|
<view class="search-row mt-16">
|
||||||
<view class="search-item">
|
<view class="search-item">
|
||||||
<text class="label">商品信息:</text>
|
<text class="label">商品信息:</text>
|
||||||
<input class="mock-input" placeholder="请输入商品信息" />
|
<input class="mock-input" v-model="searchProduct" placeholder="请输入商品信息" />
|
||||||
</view>
|
</view>
|
||||||
<view class="search-item">
|
<view class="search-item">
|
||||||
<text class="label">用户名称:</text>
|
<text class="label">用户名称:</text>
|
||||||
<input class="mock-input" placeholder="请输入" />
|
<input class="mock-input" v-model="searchUser" placeholder="请输入" />
|
||||||
</view>
|
</view>
|
||||||
<button class="btn-primary">查询</button>
|
<button class="btn-primary" @click="onSearch">查询</button>
|
||||||
|
<button class="btn-white" @click="onReset">重置</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -40,53 +43,75 @@
|
|||||||
|
|
||||||
<!-- 3. 数据表格 -->
|
<!-- 3. 数据表格 -->
|
||||||
<view class="list-card">
|
<view class="list-card">
|
||||||
|
<!-- 错误提示 -->
|
||||||
|
<view v-if="fetchError !== ''" class="error-tip">
|
||||||
|
<text class="error-txt">{{ fetchError }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<view v-if="loading" class="loading-tip">
|
||||||
|
<text class="loading-txt">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="table-v5">
|
<view class="table-v5">
|
||||||
<view class="th-row">
|
<view class="th-row">
|
||||||
<view class="th col-check"><text>□</text></view>
|
<view class="th col-check"><text>□</text></view>
|
||||||
<view class="th col-id"><text>评论ID</text></view>
|
<view class="th col-id"><text>评论ID</text></view>
|
||||||
<view class="th col-product"><text>商品信息</text></view>
|
<view class="th col-product"><text>商品信息</text></view>
|
||||||
<view class="th col-spec"><text>规格</text></view>
|
|
||||||
<view class="th col-user"><text>用户名称</text></view>
|
<view class="th col-user"><text>用户名称</text></view>
|
||||||
<view class="th col-score"><text>评分</text></view>
|
<view class="th col-score"><text>评分</text></view>
|
||||||
<view class="th col-content"><text>评价内容</text></view>
|
<view class="th col-content"><text>评价内容</text></view>
|
||||||
<view class="th col-reply"><text>回复内容</text></view>
|
<view class="th col-reply"><text>回复内容</text></view>
|
||||||
<view class="th col-status"><text>审核状态</text></view>
|
<view class="th col-status"><text>状态</text></view>
|
||||||
<view class="th col-time"><text>评价时间</text></view>
|
<view class="th col-time"><text>评价时间</text></view>
|
||||||
<view class="th col-op"><text>操作</text></view>
|
<view class="th col-op"><text>操作</text></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-for="item in pagedList" :key="item.id" class="tr-row">
|
<!-- 空态 -->
|
||||||
|
<view v-if="reviewList.length === 0 && !loading" class="empty-row">
|
||||||
|
<text class="empty-txt">暂无评价数据</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-for="item in reviewList" :key="item.id" class="tr-row">
|
||||||
<view class="td col-check"><text>□</text></view>
|
<view class="td col-check"><text>□</text></view>
|
||||||
<view class="td col-id"><text>{{ item.id }}</text></view>
|
<view class="td col-id">
|
||||||
<view class="td col-product">
|
<text class="id-txt">{{ item.id.substring(0, 8) }}...</text>
|
||||||
<image class="p-img" :src="item.image" mode="aspectFill" />
|
</view>
|
||||||
<text class="p-name-txt">{{ item.productName }}</text>
|
<view class="td col-product">
|
||||||
|
<image v-if="item.product_image != null && item.product_image !== ''" class="p-img" :src="item.product_image" mode="aspectFill" />
|
||||||
|
<view v-else class="p-img-placeholder" />
|
||||||
|
<text class="p-name-txt">{{ item.product_name !== '' ? item.product_name : '—' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="td col-user">
|
||||||
|
<text>{{ item.username != null && item.username !== '' ? item.username : '匿名用户' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="td col-score"><text>{{ item.rating }}星</text></view>
|
||||||
|
<view class="td col-content">
|
||||||
|
<text class="blue-link">{{ item.content != null && item.content !== '' ? item.content : '—' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="td col-reply">
|
||||||
|
<text>{{ item.merchant_reply != null && item.merchant_reply !== '' ? item.merchant_reply : '无' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="td col-spec"><text>{{ item.spec }}</text></view>
|
|
||||||
<view class="td col-user"><text>{{ item.username }}</text></view>
|
|
||||||
<view class="td col-score"><text>{{ item.score }}</text></view>
|
|
||||||
<view class="td col-content"><text class="blue-link">{{ item.content }}</text></view>
|
|
||||||
<view class="td col-reply"><text>{{ item.reply || '无' }}</text></view>
|
|
||||||
<view class="td col-status">
|
<view class="td col-status">
|
||||||
<text class="status-tag" :class="item.status === 1 ? 'pass' : 'wait'">
|
<text class="status-tag" :class="getStatusClass(item.status)">
|
||||||
{{ item.status === 1 ? '通过' : '待审核' }}
|
{{ getStatusText(item.status) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="td col-time"><text>{{ item.time }}</text></view>
|
<view class="td col-time"><text>{{ formatTime(item.created_at) }}</text></view>
|
||||||
<view class="td col-op">
|
<view class="td col-op">
|
||||||
<text class="op-link">通过</text>
|
<text v-if="item.status !== 1" class="op-link" @click="handleApprove(item.id)">通过</text>
|
||||||
<text class="op-link">驳回</text>
|
<text v-if="item.status === 1" class="op-link warn" @click="handleReject(item.id)">驳回</text>
|
||||||
<text class="op-link">回复</text>
|
<text class="op-link" @click="openReplyModal(item.id, item.merchant_reply)">回复</text>
|
||||||
<text class="op-link red">删除</text>
|
<text class="op-link red" @click="handleDelete(item.id)">删除</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<CommonPagination
|
<CommonPagination
|
||||||
v-if="true"
|
v-if="total > 0"
|
||||||
:total="total"
|
:total="total"
|
||||||
:loading="false"
|
:loading="loading"
|
||||||
:currentPage="currentPage"
|
:currentPage="currentPage"
|
||||||
:pageSize="pageSize"
|
:pageSize="pageSize"
|
||||||
:pageSizeOptionLabels="pageSizeOptionLabels"
|
:pageSizeOptionLabels="pageSizeOptionLabels"
|
||||||
@@ -100,81 +125,232 @@
|
|||||||
@jump-page="handleJumpPage"
|
@jump-page="handleJumpPage"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 回复弹窗 -->
|
||||||
|
<view class="reply-modal-mask" v-if="showReplyModal" @click="closeReplyModal">
|
||||||
|
<view class="reply-modal" @click.stop="">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">回复评价</text>
|
||||||
|
<text class="modal-close" @click="closeReplyModal">✕</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<textarea class="reply-textarea" v-model="replyContent" placeholder="请输入回复内容..." :maxlength="500" />
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="btn-white btn-sm" @click="closeReplyModal">取消</button>
|
||||||
|
<button class="btn-primary btn-sm" @click="submitReply">
|
||||||
|
{{ replySubmitting ? '提交中...' : '确认回复' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
|
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
|
||||||
|
import {
|
||||||
|
fetchAdminProductReviews,
|
||||||
|
approveProductReview,
|
||||||
|
rejectProductReview,
|
||||||
|
replyProductReview,
|
||||||
|
deleteProductReview,
|
||||||
|
type ProductReviewItem
|
||||||
|
} from '@/services/admin/productReviewService.uts'
|
||||||
|
|
||||||
const replyList = ref([
|
// ========== 搜索筛选状态 ==========
|
||||||
{
|
const searchProduct = ref('')
|
||||||
id: 1069,
|
const searchUser = ref('')
|
||||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
const startTime = ref('')
|
||||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
const endTime = ref('')
|
||||||
spec: 'XL,卡其',
|
// 审核状态选项:index 0 → 全部(null), 1 → 正常(1), 2 → 已删除(2), 3 → 已隐藏(3)
|
||||||
username: 'demo998',
|
const statusLabels = ['全部', '正常', '已删除', '已隐藏']
|
||||||
score: 3.5,
|
const statusPickerIndex = ref(0)
|
||||||
content: '22',
|
|
||||||
reply: '',
|
function onStatusChange(e : any) {
|
||||||
status: 0,
|
statusPickerIndex.value = Number(e.detail.value)
|
||||||
time: '2025-02-19 14:56:43'
|
}
|
||||||
},
|
|
||||||
{
|
// ========== 数据状态 ==========
|
||||||
id: 1059,
|
const loading = ref(false)
|
||||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
const fetchError = ref('')
|
||||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
const reviewList = ref<ProductReviewItem[]>([])
|
||||||
spec: 'XL,卡其',
|
|
||||||
username: '你好呀',
|
// ========== 数据加载(服务端分页,按需请求) ==========
|
||||||
score: 3.5,
|
async function loadReviews(page : number) {
|
||||||
content: '的',
|
if (loading.value) return
|
||||||
reply: '',
|
loading.value = true
|
||||||
status: 0,
|
fetchError.value = ''
|
||||||
time: '2025-01-07 15:35:36'
|
try {
|
||||||
},
|
// index 0 → null(不过滤),其余直接映射到真实 status 值 1/2/3
|
||||||
{
|
const selectedStatus : number | null = statusPickerIndex.value === 0 ? null : statusPickerIndex.value
|
||||||
id: 980,
|
const result = await fetchAdminProductReviews({
|
||||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
searchProduct: searchProduct.value !== '' ? searchProduct.value : null,
|
||||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
searchUser: searchUser.value !== '' ? searchUser.value : null,
|
||||||
spec: 'XL,卡其',
|
status: selectedStatus,
|
||||||
username: 'wx209638',
|
startTime: startTime.value !== '' ? startTime.value : null,
|
||||||
score: 5,
|
endTime: endTime.value !== '' ? endTime.value : null,
|
||||||
content: '好',
|
page: page,
|
||||||
reply: '',
|
pageSize: pageSize.value
|
||||||
status: 1,
|
})
|
||||||
time: '2024-09-12 14:20:12'
|
reviewList.value = result.items
|
||||||
|
total.value = result.total
|
||||||
|
currentPage.value = page
|
||||||
|
} catch (e) {
|
||||||
|
fetchError.value = '加载失败,请稍后重试'
|
||||||
|
console.error('加载评价列表失败:', e)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
])
|
}
|
||||||
|
|
||||||
// ========== PAGINATION STATE ==========
|
function onSearch() {
|
||||||
|
loadReviews(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReset() {
|
||||||
|
searchProduct.value = ''
|
||||||
|
searchUser.value = ''
|
||||||
|
startTime.value = ''
|
||||||
|
endTime.value = ''
|
||||||
|
statusPickerIndex.value = 0
|
||||||
|
loadReviews(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadReviews(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ========== 字段格式化 ==========
|
||||||
|
function formatTime(t : string | null) : string {
|
||||||
|
if (t == null || t === '') return '—'
|
||||||
|
return t.replace('T', ' ').substring(0, 19)
|
||||||
|
}
|
||||||
|
|
||||||
|
// status: 1=正常 2=已删除 3=已隐藏
|
||||||
|
function getStatusText(status : number) : string {
|
||||||
|
if (status === 1) return '正常'
|
||||||
|
if (status === 2) return '已删除'
|
||||||
|
if (status === 3) return '已隐藏'
|
||||||
|
return '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusClass(status : number) : string {
|
||||||
|
if (status === 1) return 'pass'
|
||||||
|
if (status === 2) return 'deleted'
|
||||||
|
return 'wait'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 操作:通过(status → 1) ==========
|
||||||
|
async function handleApprove(id : string) {
|
||||||
|
const ok = await approveProductReview(id)
|
||||||
|
if (ok) {
|
||||||
|
uni.showToast({ title: '已通过', icon: 'success' })
|
||||||
|
loadReviews(currentPage.value)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 操作:驳回(status → 3) ==========
|
||||||
|
async function handleReject(id : string) {
|
||||||
|
const ok = await rejectProductReview(id)
|
||||||
|
if (ok) {
|
||||||
|
uni.showToast({ title: '已驳回', icon: 'success' })
|
||||||
|
loadReviews(currentPage.value)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 操作:删除(status → 2,软删除) ==========
|
||||||
|
function handleDelete(id : string) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要删除该评价吗?此操作将标记为已删除状态。',
|
||||||
|
success: async (res : any) => {
|
||||||
|
if (res.confirm === true) {
|
||||||
|
const ok = await deleteProductReview(id)
|
||||||
|
if (ok) {
|
||||||
|
uni.showToast({ title: '已删除', icon: 'success' })
|
||||||
|
loadReviews(currentPage.value)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 回复弹窗 ==========
|
||||||
|
const showReplyModal = ref(false)
|
||||||
|
const replyTargetId = ref('')
|
||||||
|
const replyContent = ref('')
|
||||||
|
const replySubmitting = ref(false)
|
||||||
|
|
||||||
|
function openReplyModal(id : string, existingReply : string | null) {
|
||||||
|
replyTargetId.value = id
|
||||||
|
replyContent.value = existingReply ?? ''
|
||||||
|
showReplyModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeReplyModal() {
|
||||||
|
showReplyModal.value = false
|
||||||
|
replyTargetId.value = ''
|
||||||
|
replyContent.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitReply() {
|
||||||
|
if (replyContent.value.trim() === '') {
|
||||||
|
uni.showToast({ title: '请输入回复内容', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replySubmitting.value = true
|
||||||
|
try {
|
||||||
|
const ok = await replyProductReview(replyTargetId.value, replyContent.value.trim())
|
||||||
|
if (ok) {
|
||||||
|
uni.showToast({ title: '回复成功', icon: 'success' })
|
||||||
|
closeReplyModal()
|
||||||
|
loadReviews(currentPage.value)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '回复失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
replySubmitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== PAGINATION STATE(服务端分页) ==========
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
const jumpPageInput = ref('')
|
const jumpPageInput = ref('')
|
||||||
const pageSizeOptions = [10, 20, 30, 50]
|
const pageSizeOptions = [10, 20, 30, 50]
|
||||||
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
|
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n : number) => `${n}条/页`))
|
||||||
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
|
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
|
||||||
const total = computed(() => replyList.value.length)
|
const total = ref(0) // 来自服务端 total_count
|
||||||
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
|
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
|
||||||
const pagedList = computed(() => {
|
|
||||||
const start = (currentPage.value - 1) * pageSize.value
|
|
||||||
return replyList.value.slice(start, start + pageSize.value)
|
|
||||||
})
|
|
||||||
const visiblePages = computed((): number[] => {
|
const visiblePages = computed((): number[] => {
|
||||||
const t = totalPage.value; const cur = currentPage.value
|
const t = totalPage.value; const cur = currentPage.value
|
||||||
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
|
if (t <= 7) return Array.from({ length: t }, (_ : any, i : number) => i + 1)
|
||||||
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
|
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
|
||||||
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
|
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
|
||||||
return [1, -1, cur - 1, cur, cur + 1, -1, t]
|
return [1, -1, cur - 1, cur, cur + 1, -1, t]
|
||||||
})
|
})
|
||||||
const handlePageChange = (p: number) => { currentPage.value = p }
|
const handlePageChange = (p : number) => {
|
||||||
const handlePageSizeChange = (e: any) => {
|
if (p < 1 || p > totalPage.value) return
|
||||||
|
loadReviews(p)
|
||||||
|
}
|
||||||
|
const handlePageSizeChange = (e : any) => {
|
||||||
const idx = Number(e.detail.value)
|
const idx = Number(e.detail.value)
|
||||||
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
|
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
|
||||||
currentPage.value = 1
|
loadReviews(1)
|
||||||
}
|
}
|
||||||
const handleJumpPage = () => {
|
const handleJumpPage = () => {
|
||||||
const p = parseInt(jumpPageInput.value)
|
const p = parseInt(jumpPageInput.value)
|
||||||
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
|
if (!isNaN(p) && p >= 1 && p <= totalPage.value) {
|
||||||
|
loadReviews(p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// ========== END PAGINATION STATE ==========
|
// ========== END PAGINATION STATE ==========
|
||||||
</script>
|
</script>
|
||||||
@@ -208,11 +384,9 @@ const handleJumpPage = () => {
|
|||||||
.label { font-size: 14px; color: #606266; width: 80px; text-align: right; }
|
.label { font-size: 14px; color: #606266; width: 80px; text-align: right; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.mock-date-range {
|
.date-range-row {
|
||||||
width: 280px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
display: flex; flex-direction: row; align-items: center; gap: 8px;
|
||||||
display: flex; flex-direction: row; align-items: center; padding: 0 12px; gap: 8px;
|
.date-sep { font-size: 13px; color: #606266; }
|
||||||
.emoji { font-size: 14px; }
|
|
||||||
.txt { font-size: 13px; color: #c0c4cc; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mock-select {
|
.mock-select {
|
||||||
@@ -226,8 +400,11 @@ const handleJumpPage = () => {
|
|||||||
width: 200px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; font-size: 13px;
|
width: 200px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-input { width: 140px; }
|
||||||
|
|
||||||
.btn-primary { background: #1890ff; color: #fff; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; border: none; }
|
.btn-primary { background: #1890ff; color: #fff; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; border: none; }
|
||||||
.btn-white { background: #fff; color: #606266; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; border: 1px solid #dcdfe6; }
|
.btn-white { background: #fff; color: #606266; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; border: 1px solid #dcdfe6; }
|
||||||
|
.btn-sm { height: 32px; padding: 0 20px; font-size: 13px; }
|
||||||
|
|
||||||
.action-bar {
|
.action-bar {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@@ -242,6 +419,16 @@ const handleJumpPage = () => {
|
|||||||
padding: var(--admin-card-padding);
|
padding: var(--admin-card-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 错误 & 加载提示 */
|
||||||
|
.error-tip {
|
||||||
|
padding: 12px 16px; background: #fff2f0; border-radius: 4px; margin-bottom: 12px;
|
||||||
|
.error-txt { font-size: 13px; color: #f5222d; }
|
||||||
|
}
|
||||||
|
.loading-tip {
|
||||||
|
padding: 24px 0; display: flex; justify-content: center;
|
||||||
|
.loading-txt { font-size: 14px; color: #909399; }
|
||||||
|
}
|
||||||
|
|
||||||
.table-v5 { width: 100%; }
|
.table-v5 { width: 100%; }
|
||||||
|
|
||||||
.th-row {
|
.th-row {
|
||||||
@@ -266,10 +453,16 @@ const handleJumpPage = () => {
|
|||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 空态行 */
|
||||||
|
.empty-row {
|
||||||
|
padding: 48px 0;
|
||||||
|
display: flex; justify-content: center;
|
||||||
|
.empty-txt { font-size: 14px; color: #c0c4cc; }
|
||||||
|
}
|
||||||
|
|
||||||
.col-check { width: 50px; }
|
.col-check { width: 50px; }
|
||||||
.col-id { width: 80px; }
|
.col-id { width: 100px; }
|
||||||
.col-product { flex: 1.5; justify-content: flex-start; gap: 12px; }
|
.col-product { flex: 1.5; justify-content: flex-start; gap: 12px; }
|
||||||
.col-spec { width: 100px; }
|
|
||||||
.col-user { width: 120px; }
|
.col-user { width: 120px; }
|
||||||
.col-score { width: 80px; }
|
.col-score { width: 80px; }
|
||||||
.col-content { flex: 1; }
|
.col-content { flex: 1; }
|
||||||
@@ -279,16 +472,51 @@ const handleJumpPage = () => {
|
|||||||
.col-op { width: 180px; display: flex; flex-direction: row; }
|
.col-op { width: 180px; display: flex; flex-direction: row; }
|
||||||
|
|
||||||
.p-img { width: 40px; height: 40px; border-radius: 4px; }
|
.p-img { width: 40px; height: 40px; border-radius: 4px; }
|
||||||
|
.p-img-placeholder { width: 40px; height: 40px; border-radius: 4px; background: #f5f5f5; }
|
||||||
.p-name-txt { font-size: 13px; line-height: 1.4; color: #1890ff; }
|
.p-name-txt { font-size: 13px; line-height: 1.4; color: #1890ff; }
|
||||||
|
.id-txt { font-size: 12px; color: #909399; }
|
||||||
|
|
||||||
.blue-link { color: #1890ff; }
|
.blue-link { color: #1890ff; }
|
||||||
|
|
||||||
.status-tag {
|
.status-tag {
|
||||||
padding: 2px 8px; border-radius: 2px; font-size: 12px;
|
padding: 2px 8px; border-radius: 2px; font-size: 12px;
|
||||||
&.pass { background: rgba(82, 196, 26, 0.1); color: #52c41a; }
|
&.pass { background: rgba(82, 196, 26, 0.1); color: #52c41a; }
|
||||||
&.wait { background: rgba(250, 173, 20, 0.1); color: #faad14; }
|
&.wait { background: rgba(250, 173, 20, 0.1); color: #faad14; }
|
||||||
|
&.deleted { background: rgba(245, 34, 45, 0.1); color: #f5222d; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-link { font-size: 13px; color: #1890ff; margin: 0 4px; cursor: pointer; &.red { color: #f5222d; } }
|
.op-link {
|
||||||
/* 分页区域已迁至 CommonPagination 组件 */
|
font-size: 13px; color: #1890ff; margin: 0 4px; cursor: pointer;
|
||||||
|
&.red { color: #f5222d; }
|
||||||
|
&.warn { color: #faad14; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复弹窗 */
|
||||||
|
.reply-modal-mask {
|
||||||
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.reply-modal {
|
||||||
|
background: #fff; border-radius: 6px; width: 480px; max-width: 90vw;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
padding: 16px 20px; border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex; flex-direction: row; align-items: center; justify-content: space-between;
|
||||||
|
.modal-title { font-size: 16px; font-weight: 500; color: #333; }
|
||||||
|
.modal-close { font-size: 16px; color: #909399; cursor: pointer; padding: 4px; }
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
.reply-textarea {
|
||||||
|
width: 100%; height: 100px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||||
|
padding: 8px 12px; font-size: 13px; resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
padding: 12px 20px; border-top: 1px solid #f0f0f0;
|
||||||
|
display: flex; flex-direction: row; justify-content: flex-end; gap: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,44 +1,12 @@
|
|||||||
<!-- 商家端 - 聊天页面 -->
|
<!-- 商家端 - 聊天页面 -->
|
||||||
<template>
|
<template>
|
||||||
<view class="chat-page">
|
<view class="chat-page">
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="chat-header">
|
|
||||||
=======
|
|
||||||
<!-- 聊天头部 -->
|
<!-- 聊天头部 -->
|
||||||
<view class="chat-header" :style="{ paddingTop: navPaddingTop }">
|
<view class="chat-header" :style="{ paddingTop: navPaddingTop }">
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<view class="header-back" @click="goBack">
|
<view class="header-back" @click="goBack">
|
||||||
<text class="back-icon">‹</text>
|
<text class="back-icon">‹</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="header-info">
|
<view class="header-info">
|
||||||
<<<<<<< HEAD
|
|
||||||
<text class="chat-title">{{ chatTitle }}</text>
|
|
||||||
<text class="chat-status">在线</text>
|
|
||||||
</view>
|
|
||||||
<view class="header-actions"></view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<scroll-view scroll-y class="chat-content" :scroll-into-view="scrollToView" scroll-with-animation>
|
|
||||||
<view class="chat-messages">
|
|
||||||
<view v-for="msg in chatMessages" :key="msg.id" :class="['message-item', msg.is_from_user ? 'me' : 'received']" :id="'msg-' + msg.id">
|
|
||||||
<view v-if="!msg.is_from_user" class="message-wrapper">
|
|
||||||
<image class="avatar" src="/static/images/default-avatar.png" mode="aspectFill" />
|
|
||||||
<view class="message-content-wrapper">
|
|
||||||
<view class="message-bubble">
|
|
||||||
<text class="message-text">{{ msg.content }}</text>
|
|
||||||
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-else class="message-wrapper me">
|
|
||||||
<view class="message-content-wrapper">
|
|
||||||
<view class="message-bubble me">
|
|
||||||
<text class="message-text">{{ msg.content }}</text>
|
|
||||||
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<image class="avatar me" src="/static/images/default-shop.png" mode="aspectFill" />
|
|
||||||
=======
|
|
||||||
<view class="header-info-text-wrapper">
|
<view class="header-info-text-wrapper">
|
||||||
<text class="chat-title">{{ chatTitle }}</text>
|
<text class="chat-title">{{ chatTitle }}</text>
|
||||||
<text class="chat-status">在线</text>
|
<text class="chat-status">在线</text>
|
||||||
@@ -103,20 +71,11 @@
|
|||||||
:src="shopAvatar"
|
:src="shopAvatar"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
/>
|
/>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="chat-input">
|
|
||||||
<input v-model="inputText" class="input-field" placeholder="请输入消息..." confirm-type="send" @confirm="sendMessage" />
|
|
||||||
<view class="send-btn" @click="sendMessage">
|
|
||||||
<text class="send-icon">➤</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
=======
|
|
||||||
<!-- 聊天输入区 -->
|
<!-- 聊天输入区 -->
|
||||||
<view class="chat-input">
|
<view class="chat-input">
|
||||||
<view class="input-tools">
|
<view class="input-tools">
|
||||||
@@ -156,23 +115,16 @@
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="uts">
|
<script lang="uts">
|
||||||
import supa from '@/components/supadb/aksupainstance.uts'
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
type ChatMessageType = {
|
|
||||||
id: string
|
|
||||||
=======
|
|
||||||
import type { AkSupaRealtimeChannel } from '@/components/supadb/aksupa.uts'
|
import type { AkSupaRealtimeChannel } from '@/components/supadb/aksupa.uts'
|
||||||
|
|
||||||
type ChatMessageType = {
|
type ChatMessageType = {
|
||||||
id: string
|
id: string
|
||||||
viewId: string
|
viewId: string
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
session_id: string
|
session_id: string
|
||||||
sender_id: string
|
sender_id: string
|
||||||
receiver_id: string
|
receiver_id: string
|
||||||
@@ -181,10 +133,7 @@
|
|||||||
is_read: boolean
|
is_read: boolean
|
||||||
is_from_user: boolean
|
is_from_user: boolean
|
||||||
created_at: string
|
created_at: string
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
time: string
|
time: string
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -196,9 +145,6 @@
|
|||||||
inputText: '',
|
inputText: '',
|
||||||
chatMessages: [] as ChatMessageType[],
|
chatMessages: [] as ChatMessageType[],
|
||||||
scrollToView: '',
|
scrollToView: '',
|
||||||
<<<<<<< HEAD
|
|
||||||
merchantId: ''
|
|
||||||
=======
|
|
||||||
merchantId: '',
|
merchantId: '',
|
||||||
userAvatar: '/static/images/default-avatar.png',
|
userAvatar: '/static/images/default-avatar.png',
|
||||||
shopAvatar: '/static/images/default-shop.png',
|
shopAvatar: '/static/images/default-shop.png',
|
||||||
@@ -212,19 +158,15 @@
|
|||||||
computed: {
|
computed: {
|
||||||
emojiList(): string[] {
|
emojiList(): string[] {
|
||||||
return ['😊', '😂', '🤣', '😍', '😘', '🥰', '😭', '😡', '👍', '👏', '🙏', '🎉', '❤️', '🔥', '⭐']
|
return ['😊', '😂', '🤣', '😍', '😘', '🥰', '😭', '😡', '👍', '👏', '🙏', '🎉', '❤️', '🔥', '⭐']
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad(options: any) {
|
onLoad(options: any) {
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
const sysInfo = uni.getSystemInfoSync()
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
const statusBarH = sysInfo.statusBarHeight
|
const statusBarH = sysInfo.statusBarHeight
|
||||||
this.navPaddingTop = (statusBarH + 10) + 'px'
|
this.navPaddingTop = (statusBarH + 10) + 'px'
|
||||||
|
|
||||||
console.log('chat page onLoad options:', options)
|
console.log('chat page onLoad options:', options)
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
if (options.session_id) {
|
if (options.session_id) {
|
||||||
this.sessionId = options.session_id
|
this.sessionId = options.session_id
|
||||||
}
|
}
|
||||||
@@ -238,9 +180,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
<<<<<<< HEAD
|
|
||||||
this.loadChatMessages()
|
|
||||||
=======
|
|
||||||
console.log('chat page onShow, chatUserId:', this.chatUserId, 'merchantId:', this.merchantId)
|
console.log('chat page onShow, chatUserId:', this.chatUserId, 'merchantId:', this.merchantId)
|
||||||
if (this.merchantId) {
|
if (this.merchantId) {
|
||||||
this.loadChatMessages()
|
this.loadChatMessages()
|
||||||
@@ -257,61 +196,12 @@
|
|||||||
if (this.realtimeChannel != null) {
|
if (this.realtimeChannel != null) {
|
||||||
supa.removeChannel(this.realtimeChannel!)
|
supa.removeChannel(this.realtimeChannel!)
|
||||||
}
|
}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async initMerchantId() {
|
async initMerchantId() {
|
||||||
try {
|
try {
|
||||||
const session = supa.getSession()
|
const session = supa.getSession()
|
||||||
<<<<<<< HEAD
|
|
||||||
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadChatMessages() {
|
|
||||||
try {
|
|
||||||
let query
|
|
||||||
if (this.sessionId) {
|
|
||||||
query = supa
|
|
||||||
.from('ml_chat_messages')
|
|
||||||
.select('*')
|
|
||||||
.eq('session_id', this.sessionId)
|
|
||||||
.order('created_at', { ascending: true })
|
|
||||||
} else if (this.chatUserId && this.merchantId) {
|
|
||||||
query = supa
|
|
||||||
.from('ml_chat_messages')
|
|
||||||
.select('*')
|
|
||||||
.or(`and(sender_id.eq.${this.chatUserId},receiver_id.eq.${this.merchantId}),and(sender_id.eq.${this.merchantId},receiver_id.eq.${this.chatUserId})`)
|
|
||||||
.order('created_at', { ascending: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
const response = await query.execute()
|
|
||||||
if (response.data && (response.data as any[]).length > 0) {
|
|
||||||
const rawData = response.data as any[]
|
|
||||||
const messages: ChatMessageType[] = []
|
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
|
||||||
const item = rawData[i] as UTSJSONObject
|
|
||||||
const senderId = item.getString('sender_id')
|
|
||||||
messages.push({
|
|
||||||
id: item.getString('id') || '',
|
|
||||||
session_id: item.getString('session_id') || '',
|
|
||||||
sender_id: senderId || '',
|
|
||||||
receiver_id: item.getString('receiver_id') || '',
|
|
||||||
content: item.getString('content') || '',
|
|
||||||
msg_type: item.getString('msg_type') || 'text',
|
|
||||||
is_read: item.getBoolean('is_read') || false,
|
|
||||||
is_from_user: senderId === this.merchantId,
|
|
||||||
created_at: item.getString('created_at') || ''
|
|
||||||
} as ChatMessageType)
|
|
||||||
}
|
|
||||||
this.chatMessages = messages
|
|
||||||
this.scrollToView = messages.length > 0 ? 'msg-' + messages[messages.length - 1].id : ''
|
|
||||||
|
|
||||||
this.markAsRead()
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
if (session != null && session.user != null) {
|
if (session != null && session.user != null) {
|
||||||
this.merchantId = session.user.getString('id') || ''
|
this.merchantId = session.user.getString('id') || ''
|
||||||
}
|
}
|
||||||
@@ -405,15 +295,12 @@
|
|||||||
} else {
|
} else {
|
||||||
console.log('没有找到聊天记录')
|
console.log('没有找到聊天记录')
|
||||||
this.chatMessages = []
|
this.chatMessages = []
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载聊天记录失败:', e)
|
console.error('加载聊天记录失败:', e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
setupRealtimeSubscription(): void {
|
setupRealtimeSubscription(): void {
|
||||||
console.log('开始建立聊天实时订阅...')
|
console.log('开始建立聊天实时订阅...')
|
||||||
|
|
||||||
@@ -492,25 +379,12 @@
|
|||||||
}, 100)
|
}, 100)
|
||||||
},
|
},
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
async markAsRead() {
|
async markAsRead() {
|
||||||
try {
|
try {
|
||||||
await supa
|
await supa
|
||||||
.from('ml_chat_messages')
|
.from('ml_chat_messages')
|
||||||
.update({ is_read: true })
|
.update({ is_read: true })
|
||||||
.eq('receiver_id', this.merchantId)
|
.eq('receiver_id', this.merchantId)
|
||||||
<<<<<<< HEAD
|
|
||||||
.eq('is_read', false)
|
|
||||||
.execute()
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
|
||||||
|
|
||||||
async sendMessage() {
|
|
||||||
if (!this.inputText.trim()) return
|
|
||||||
|
|
||||||
const content = this.inputText.trim()
|
|
||||||
this.inputText = ''
|
|
||||||
=======
|
|
||||||
.eq('sender_id', this.chatUserId)
|
.eq('sender_id', this.chatUserId)
|
||||||
.eq('is_read', false)
|
.eq('is_read', false)
|
||||||
.execute()
|
.execute()
|
||||||
@@ -525,7 +399,6 @@
|
|||||||
|
|
||||||
this.inputText = ''
|
this.inputText = ''
|
||||||
this.showEmoji = false
|
this.showEmoji = false
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newMessage = {
|
const newMessage = {
|
||||||
@@ -543,26 +416,6 @@
|
|||||||
.insert([newMessage])
|
.insert([newMessage])
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (!response.error) {
|
|
||||||
this.loadChatMessages()
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('发送消息失败:', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
goBack() {
|
|
||||||
uni.navigateBack()
|
|
||||||
},
|
|
||||||
|
|
||||||
formatTime(timeStr: string): string {
|
|
||||||
if (!timeStr) return ''
|
|
||||||
const date = new Date(timeStr)
|
|
||||||
const hours = date.getHours().toString().padStart(2, '0')
|
|
||||||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
|
||||||
return `${hours}:${minutes}`
|
|
||||||
=======
|
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
console.error('发送消息失败:', response.error)
|
console.error('发送消息失败:', response.error)
|
||||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||||
@@ -621,40 +474,12 @@
|
|||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
<<<<<<< HEAD
|
|
||||||
.chat-page { display: flex; flex-direction: column; height: 100vh; background-color: #f5f5f5; }
|
|
||||||
.chat-header { display: flex; align-items: center; padding: 20rpx 30rpx; background-color: #fff; border-bottom: 1rpx solid #eee; }
|
|
||||||
.header-back { padding: 10rpx 20rpx 10rpx 0; }
|
|
||||||
.back-icon { font-size: 48rpx; color: #333; font-weight: bold; }
|
|
||||||
.header-info { flex: 1; display: flex; flex-direction: column; align-items: center; }
|
|
||||||
.chat-title { font-size: 32rpx; color: #333; font-weight: 500; }
|
|
||||||
.chat-status { font-size: 22rpx; color: #4CAF50; }
|
|
||||||
.header-actions { padding: 10rpx; }
|
|
||||||
.chat-content { flex: 1; padding: 20rpx; }
|
|
||||||
.chat-messages { display: flex; flex-direction: column; }
|
|
||||||
.message-item { margin-bottom: 30rpx; }
|
|
||||||
.message-wrapper { display: flex; align-items: flex-start; }
|
|
||||||
.message-wrapper.me { flex-direction: row-reverse; }
|
|
||||||
.avatar { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin: 0 20rpx; }
|
|
||||||
.message-content-wrapper { max-width: 70%; }
|
|
||||||
.message-bubble { background-color: #fff; padding: 20rpx; border-radius: 12rpx; position: relative; }
|
|
||||||
.message-bubble.me { background-color: #007AFF; }
|
|
||||||
.me .message-text { color: #fff; }
|
|
||||||
.me .message-time { color: rgba(255,255,255,0.7); }
|
|
||||||
.message-text { font-size: 28rpx; color: #333; line-height: 1.4; }
|
|
||||||
.message-time { display: block; font-size: 20rpx; color: #999; margin-top: 10rpx; text-align: right; }
|
|
||||||
.chat-input { display: flex; align-items: center; padding: 20rpx 30rpx; background-color: #fff; border-top: 1rpx solid #eee; }
|
|
||||||
.input-field { flex: 1; height: 72rpx; background-color: #f5f5f5; border-radius: 36rpx; padding: 0 30rpx; font-size: 28rpx; }
|
|
||||||
.send-btn { margin-left: 20rpx; width: 72rpx; height: 72rpx; background-color: #007AFF; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
|
|
||||||
.send-icon { font-size: 32rpx; color: #fff; }
|
|
||||||
=======
|
|
||||||
.chat-page { width: 100%; flex: 1; background-color: #f5f5f5; display: flex; flex-direction: column; overflow: hidden; }
|
.chat-page { width: 100%; flex: 1; background-color: #f5f5f5; display: flex; flex-direction: column; overflow: hidden; }
|
||||||
|
|
||||||
.chat-header { background-color: white; padding-left: 15px; padding-right: 15px; padding-bottom: 10px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eee; flex-shrink: 0; }
|
.chat-header { background-color: white; padding-left: 15px; padding-right: 15px; padding-bottom: 10px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eee; flex-shrink: 0; }
|
||||||
@@ -700,5 +525,4 @@
|
|||||||
.image-bubble { padding: 0 !important; background-color: transparent !important; }
|
.image-bubble { padding: 0 !important; background-color: transparent !important; }
|
||||||
.image-bubble .message-time { margin-top: 5px; text-align: right; }
|
.image-bubble .message-time { margin-top: 5px; text-align: right; }
|
||||||
.message-image { width: 150px; border-radius: 8px; }
|
.message-image { width: 150px; border-radius: 8px; }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,142 +1,4 @@
|
|||||||
<<<<<<< HEAD
|
<!-- 商家端首页 -->
|
||||||
<!-- 商家端首页 - UTS Android 兼容 -->
|
|
||||||
<template>
|
|
||||||
<view class="merchant-container">
|
|
||||||
<!-- 头部导航 -->
|
|
||||||
<view class="header">
|
|
||||||
<view class="header-content">
|
|
||||||
<view class="shop-info">
|
|
||||||
<image :src="shopInfo.shop_logo || '/static/images/default-shop.png'" class="shop-logo" mode="aspectFit" />
|
|
||||||
<view class="shop-details">
|
|
||||||
<text class="shop-name">{{ shopInfo.shop_name || '我的店铺' }}</text>
|
|
||||||
<view class="shop-stats">
|
|
||||||
<text class="stat-item">评分: {{ shopInfo.rating_avg || 5.0 }}</text>
|
|
||||||
<text class="stat-item">销量: {{ shopInfo.total_sales || 0 }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="header-actions">
|
|
||||||
<text class="action-btn" @click="goToMessages">消息</text>
|
|
||||||
<text class="action-btn" @click="goToSettings">设置</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 数据概览 -->
|
|
||||||
<view class="overview-section">
|
|
||||||
<text class="section-title">今日数据</text>
|
|
||||||
<view class="overview-grid">
|
|
||||||
<view class="overview-item">
|
|
||||||
<text class="overview-value">{{ todayStats.orders }}</text>
|
|
||||||
<text class="overview-label">订单数</text>
|
|
||||||
</view>
|
|
||||||
<view class="overview-item">
|
|
||||||
<text class="overview-value">¥{{ formatNumber(todayStats.sales) }}</text>
|
|
||||||
<text class="overview-label">销售额</text>
|
|
||||||
</view>
|
|
||||||
<view class="overview-item">
|
|
||||||
<text class="overview-value">{{ todayStats.visitors }}</text>
|
|
||||||
<text class="overview-label">访客数</text>
|
|
||||||
</view>
|
|
||||||
<view class="overview-item">
|
|
||||||
<text class="overview-value">{{ todayStats.conversion }}%</text>
|
|
||||||
<text class="overview-label">转化率</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 待处理事项 -->
|
|
||||||
<view class="pending-section">
|
|
||||||
<text class="section-title">待处理</text>
|
|
||||||
<view class="pending-list">
|
|
||||||
<view class="pending-item" @click="goToOrders('pending')">
|
|
||||||
<text class="pending-icon">📦</text>
|
|
||||||
<text class="pending-text">待发货订单</text>
|
|
||||||
<text class="pending-count">{{ pendingCounts.pending_shipment }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="pending-item" @click="goToOrders('refund')">
|
|
||||||
<text class="pending-icon">↩️</text>
|
|
||||||
<text class="pending-text">退款处理</text>
|
|
||||||
<text class="pending-count">{{ pendingCounts.refund_requests }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="pending-item" @click="goToProducts('low_stock')">
|
|
||||||
<text class="pending-icon">⚠️</text>
|
|
||||||
<text class="pending-text">库存预警</text>
|
|
||||||
<text class="pending-count">{{ pendingCounts.low_stock }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="pending-item" @click="goToReviews">
|
|
||||||
<text class="pending-icon">💬</text>
|
|
||||||
<text class="pending-text">待回复评价</text>
|
|
||||||
<text class="pending-count">{{ pendingCounts.pending_reviews }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 快捷功能 -->
|
|
||||||
<view class="shortcuts-section">
|
|
||||||
<text class="section-title">快捷功能</text>
|
|
||||||
<view class="shortcuts-grid">
|
|
||||||
<view class="shortcut-item" @click="goToProducts('add')">
|
|
||||||
<text class="shortcut-icon">➕</text>
|
|
||||||
<text class="shortcut-text">添加商品</text>
|
|
||||||
</view>
|
|
||||||
<view class="shortcut-item" @click="goToOrders('all')">
|
|
||||||
<text class="shortcut-icon">📋</text>
|
|
||||||
<text class="shortcut-text">订单管理</text>
|
|
||||||
</view>
|
|
||||||
<view class="shortcut-item" @click="goToProducts('manage')">
|
|
||||||
<text class="shortcut-icon">📦</text>
|
|
||||||
<text class="shortcut-text">商品管理</text>
|
|
||||||
</view>
|
|
||||||
<view class="shortcut-item" @click="goToPromotions">
|
|
||||||
<text class="shortcut-icon">🎯</text>
|
|
||||||
<text class="shortcut-text">营销活动</text>
|
|
||||||
</view>
|
|
||||||
<view class="shortcut-item" @click="goToStatistics">
|
|
||||||
<text class="shortcut-icon">📊</text>
|
|
||||||
<text class="shortcut-text">数据统计</text>
|
|
||||||
</view>
|
|
||||||
<view class="shortcut-item" @click="goToFinance">
|
|
||||||
<text class="shortcut-icon">💰</text>
|
|
||||||
<text class="shortcut-text">财务结算</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 最新订单 -->
|
|
||||||
<view class="recent-orders-section">
|
|
||||||
<view class="section-header">
|
|
||||||
<text class="section-title">最新订单</text>
|
|
||||||
<text class="section-more" @click="goToOrders('all')">查看全部</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="recentOrders.length === 0" class="no-orders">
|
|
||||||
<text class="no-orders-text">暂无订单</text>
|
|
||||||
</view>
|
|
||||||
<view v-else class="orders-list">
|
|
||||||
<view v-for="order in recentOrders" :key="order.id" class="order-item" @click="goToOrderDetail(order.id)">
|
|
||||||
<view class="order-header">
|
|
||||||
<text class="order-no">{{ order.order_no }}</text>
|
|
||||||
<text class="order-status" :class="getOrderStatusClass(order.order_status)">{{ getOrderStatusText(order.order_status) }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="order-products">
|
|
||||||
<view v-for="item in order.items" :key="item.id" class="product-item">
|
|
||||||
<image :src="item.image_url || '/static/images/default-product.png'" class="product-image" mode="aspectFit" />
|
|
||||||
<view class="product-info">
|
|
||||||
<text class="product-name">{{ item.product_name }}</text>
|
|
||||||
<text class="product-spec">{{ item.sku_specifications || '' }}</text>
|
|
||||||
<text class="product-price">¥{{ item.price }} × {{ item.quantity }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="order-footer">
|
|
||||||
<text class="order-amount">合计: ¥{{ order.total_amount }}</text>
|
|
||||||
<text class="order-time">{{ formatTime(order.created_at) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
=======
|
|
||||||
<!-- 商家端首页 -->
|
|
||||||
<template>
|
<template>
|
||||||
<view class="merchant-container">
|
<view class="merchant-container">
|
||||||
<scroll-view scroll-y class="main-scroll" :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
<scroll-view scroll-y class="main-scroll" :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||||
@@ -373,7 +235,6 @@
|
|||||||
<view class="safe-bottom"></view>
|
<view class="safe-bottom"></view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -459,9 +320,6 @@
|
|||||||
low_stock: 0,
|
low_stock: 0,
|
||||||
pending_reviews: 0
|
pending_reviews: 0
|
||||||
} as PendingCountsType,
|
} as PendingCountsType,
|
||||||
<<<<<<< HEAD
|
|
||||||
recentOrders: [] as OrderType[]
|
|
||||||
=======
|
|
||||||
recentOrders: [] as OrderType[],
|
recentOrders: [] as OrderType[],
|
||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
refreshing: false
|
refreshing: false
|
||||||
@@ -472,7 +330,6 @@
|
|||||||
currentDate(): string {
|
currentDate(): string {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
return `${now.getMonth() + 1}月${now.getDate()}日`
|
return `${now.getMonth() + 1}月${now.getDate()}日`
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -482,37 +339,16 @@
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
if (this.merchantId) {
|
if (this.merchantId) {
|
||||||
<<<<<<< HEAD
|
|
||||||
this.loadMerchantData()
|
|
||||||
this.loadTodayStats()
|
|
||||||
this.loadPendingCounts()
|
|
||||||
this.loadRecentOrders()
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loadMerchantData()
|
|
||||||
this.loadTodayStats()
|
|
||||||
this.loadPendingCounts()
|
|
||||||
this.loadRecentOrders()
|
|
||||||
=======
|
|
||||||
this.loadAllData()
|
this.loadAllData()
|
||||||
this.startRealtimeSubscription()
|
this.startRealtimeSubscription()
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loadAllData()
|
this.loadAllData()
|
||||||
this.startRealtimeSubscription()
|
this.startRealtimeSubscription()
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
methods: {
|
|
||||||
formatNumber(value: number | null): string {
|
|
||||||
if (value == null) return '0.00'
|
|
||||||
return value.toFixed(2)
|
|
||||||
},
|
|
||||||
|
|
||||||
=======
|
|
||||||
onHide() {
|
onHide() {
|
||||||
this.stopRealtimeSubscription()
|
this.stopRealtimeSubscription()
|
||||||
},
|
},
|
||||||
@@ -522,7 +358,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
async initMerchantId() {
|
async initMerchantId() {
|
||||||
try {
|
try {
|
||||||
const session = supa.getSession()
|
const session = supa.getSession()
|
||||||
@@ -537,8 +372,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
startRealtimeSubscription() {
|
startRealtimeSubscription() {
|
||||||
if (!this.merchantId) return
|
if (!this.merchantId) return
|
||||||
|
|
||||||
@@ -586,7 +419,6 @@
|
|||||||
return value.toFixed(2)
|
return value.toFixed(2)
|
||||||
},
|
},
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
async loadMerchantData() {
|
async loadMerchantData() {
|
||||||
try {
|
try {
|
||||||
const response = await supa
|
const response = await supa
|
||||||
@@ -596,12 +428,8 @@
|
|||||||
.limit(1)
|
.limit(1)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null || !response.data || (response.data as any[]).length === 0) {
|
|
||||||
=======
|
|
||||||
if (response.error != null) { console.error('ml_shops请求500报错', response.error) }
|
if (response.error != null) { console.error('ml_shops请求500报错', response.error) }
|
||||||
if (response.error != null || !response.data || (response.data as any[]).length === 0) {
|
if (response.error != null || !response.data || (response.data as any[]).length === 0) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
this.shopInfo = {
|
this.shopInfo = {
|
||||||
id: null,
|
id: null,
|
||||||
merchant_id: this.merchantId,
|
merchant_id: this.merchantId,
|
||||||
@@ -620,11 +448,7 @@
|
|||||||
|
|
||||||
const rawData = (response.data as any[])[0] as UTSJSONObject
|
const rawData = (response.data as any[])[0] as UTSJSONObject
|
||||||
this.shopInfo = {
|
this.shopInfo = {
|
||||||
<<<<<<< HEAD
|
|
||||||
id: rawData.getString('id') || null,
|
|
||||||
=======
|
|
||||||
id: rawData.getString('id') || null,
|
id: rawData.getString('id') || null,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
merchant_id: rawData.getString('merchant_id') || null,
|
merchant_id: rawData.getString('merchant_id') || null,
|
||||||
shop_name: rawData.getString('shop_name') || '我的店铺',
|
shop_name: rawData.getString('shop_name') || '我的店铺',
|
||||||
shop_logo: rawData.getString('shop_logo') || null,
|
shop_logo: rawData.getString('shop_logo') || null,
|
||||||
@@ -636,8 +460,6 @@
|
|||||||
total_sales: rawData.getNumber('total_sales') || 0,
|
total_sales: rawData.getNumber('total_sales') || 0,
|
||||||
status: rawData.getNumber('status') || 1
|
status: rawData.getNumber('status') || 1
|
||||||
}
|
}
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
// 重新动态查询并计算该店铺下所有商品的真实销量总和
|
// 重新动态查询并计算该店铺下所有商品的真实销量总和
|
||||||
try {
|
try {
|
||||||
@@ -672,7 +494,6 @@
|
|||||||
console.error('获取店铺真实销量失败:', e)
|
console.error('获取店铺真实销量失败:', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载店铺信息失败:', e)
|
console.error('加载店铺信息失败:', e)
|
||||||
}
|
}
|
||||||
@@ -680,21 +501,6 @@
|
|||||||
|
|
||||||
async loadTodayStats() {
|
async loadTodayStats() {
|
||||||
try {
|
try {
|
||||||
<<<<<<< HEAD
|
|
||||||
const response = await supa
|
|
||||||
.from('ml_orders')
|
|
||||||
.select('total_amount, order_status', { count: 'exact' })
|
|
||||||
.eq('merchant_id', this.merchantId)
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
if (response.error != null) {
|
|
||||||
console.error('获取统计数据失败:', response.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalOrders = 0
|
|
||||||
let totalSales = 0
|
|
||||||
=======
|
|
||||||
// 1. 获取所有订单
|
// 1. 获取所有订单
|
||||||
const response = await supa
|
const response = await supa
|
||||||
.from('ml_orders')
|
.from('ml_orders')
|
||||||
@@ -716,18 +522,12 @@
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
// 获取今日0点的毫秒数 (本地时间)
|
// 获取今日0点的毫秒数 (本地时间)
|
||||||
const todayStartMs = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
|
const todayStartMs = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|
||||||
const rawData = response.data as any[]
|
const rawData = response.data as any[]
|
||||||
if (rawData != null) {
|
if (rawData != null) {
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
const item = rawData[i] as UTSJSONObject
|
const item = rawData[i] as UTSJSONObject
|
||||||
const status = item.getNumber('order_status')
|
const status = item.getNumber('order_status')
|
||||||
<<<<<<< HEAD
|
|
||||||
if (status >= 2) {
|
|
||||||
totalOrders++
|
|
||||||
totalSales += item.getNumber('total_amount') || 0
|
|
||||||
=======
|
|
||||||
|
|
||||||
// 有效订单(已支付、已发货、已完成) >= 2
|
// 有效订单(已支付、已发货、已完成) >= 2
|
||||||
// 如果是退款(0)或取消(5),可能不计入今日销售额,这里按需调整
|
// 如果是退款(0)或取消(5),可能不计入今日销售额,这里按需调整
|
||||||
@@ -753,18 +553,10 @@
|
|||||||
todaySales += item.getNumber('total_amount') || 0
|
todaySales += item.getNumber('total_amount') || 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
this.todayStats = {
|
|
||||||
orders: totalOrders,
|
|
||||||
sales: totalSales,
|
|
||||||
visitors: Math.floor(totalOrders * 3),
|
|
||||||
conversion: totalOrders > 0 ? 15 : 0
|
|
||||||
=======
|
|
||||||
// 更新店铺总销量显示
|
// 更新店铺总销量显示
|
||||||
let currentShopSales = Number(this.shopInfo.total_sales || 0)
|
let currentShopSales = Number(this.shopInfo.total_sales || 0)
|
||||||
if (allTimeSalesVolume > currentShopSales) {
|
if (allTimeSalesVolume > currentShopSales) {
|
||||||
@@ -776,7 +568,6 @@
|
|||||||
sales: todaySales,
|
sales: todaySales,
|
||||||
visitors: Math.floor(todayOrders * (2.5 + Math.random())) + 5, // 模拟访客数
|
visitors: Math.floor(todayOrders * (2.5 + Math.random())) + 5, // 模拟访客数
|
||||||
conversion: todayOrders > 0 ? (12 + Math.floor(Math.random() * 8)) : 0 // 模拟转化率
|
conversion: todayOrders > 0 ? (12 + Math.floor(Math.random() * 8)) : 0 // 模拟转化率
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取今日统计异常:', e)
|
console.error('获取今日统计异常:', e)
|
||||||
@@ -792,39 +583,24 @@
|
|||||||
.eq('order_status', 2)
|
.eq('order_status', 2)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const refundRes = await supa
|
|
||||||
=======
|
|
||||||
if (pendingShipmentRes.error != null) { console.error('pendingShipment报错', pendingShipmentRes.error) }
|
if (pendingShipmentRes.error != null) { console.error('pendingShipment报错', pendingShipmentRes.error) }
|
||||||
const refundRes = await supa
|
const refundRes = await supa
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
.from('ml_orders')
|
.from('ml_orders')
|
||||||
.select('id', { count: 'exact' })
|
.select('id', { count: 'exact' })
|
||||||
.eq('merchant_id', this.merchantId)
|
.eq('merchant_id', this.merchantId)
|
||||||
.eq('order_status', 0)
|
.eq('order_status', 0)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const lowStockRes = await supa
|
|
||||||
=======
|
|
||||||
if (refundRes.error != null) { console.error('refundRes报错', refundRes.error) }
|
if (refundRes.error != null) { console.error('refundRes报错', refundRes.error) }
|
||||||
const lowStockRes = await supa
|
const lowStockRes = await supa
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
.from('ml_products')
|
.from('ml_products')
|
||||||
.select('id', { count: 'exact' })
|
.select('id', { count: 'exact' })
|
||||||
.eq('merchant_id', this.merchantId)
|
.eq('merchant_id', this.merchantId)
|
||||||
.lte('total_stock', 10)
|
.lte('total_stock', 10)
|
||||||
<<<<<<< HEAD
|
|
||||||
.gte('total_stock', 0)
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
this.pendingCounts = {
|
|
||||||
=======
|
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
if (lowStockRes.error != null) { console.error('lowStockRes报错', lowStockRes.error) }
|
if (lowStockRes.error != null) { console.error('lowStockRes报错', lowStockRes.error) }
|
||||||
this.pendingCounts = {
|
this.pendingCounts = {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
pending_shipment: pendingShipmentRes.total || 0,
|
pending_shipment: pendingShipmentRes.total || 0,
|
||||||
refund_requests: refundRes.total || 0,
|
refund_requests: refundRes.total || 0,
|
||||||
low_stock: lowStockRes.total || 0,
|
low_stock: lowStockRes.total || 0,
|
||||||
@@ -841,11 +617,7 @@
|
|||||||
.from('ml_orders')
|
.from('ml_orders')
|
||||||
.select(`
|
.select(`
|
||||||
*,
|
*,
|
||||||
<<<<<<< HEAD
|
|
||||||
order_items!inner (
|
|
||||||
=======
|
|
||||||
order_items (
|
order_items (
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
id,
|
id,
|
||||||
product_id,
|
product_id,
|
||||||
product_name,
|
product_name,
|
||||||
@@ -860,15 +632,8 @@
|
|||||||
.limit(5)
|
.limit(5)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null || !response.data) {
|
|
||||||
this.recentOrders = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
if (response.error != null) { console.error('recentOrders报错', response.error) }
|
if (response.error != null) { console.error('recentOrders报错', response.error) }
|
||||||
if (response.error != null || !response.data) { this.recentOrders = []; return; }
|
if (response.error != null || !response.data) { this.recentOrders = []; return; }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|
||||||
const rawData = response.data as any[]
|
const rawData = response.data as any[]
|
||||||
const ordersData: OrderType[] = []
|
const ordersData: OrderType[] = []
|
||||||
@@ -909,33 +674,6 @@
|
|||||||
|
|
||||||
this.recentOrders = ordersData
|
this.recentOrders = ordersData
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
<<<<<<< HEAD
|
|
||||||
console.error('加载最新订单异常:', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getOrderStatusClass(status: number): string {
|
|
||||||
switch (status) {
|
|
||||||
case 1: return 'status-pending'
|
|
||||||
case 2: return 'status-paid'
|
|
||||||
case 3: return 'status-shipped'
|
|
||||||
case 4: return 'status-delivered'
|
|
||||||
case 5: return 'status-completed'
|
|
||||||
default: return 'status-default'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getOrderStatusText(status: number): string {
|
|
||||||
switch (status) {
|
|
||||||
case 1: return '待付款'
|
|
||||||
case 2: return '待发货'
|
|
||||||
case 3: return '已发货'
|
|
||||||
case 4: return '已收货'
|
|
||||||
case 5: return '已完成'
|
|
||||||
case 0: return '退款中'
|
|
||||||
default: return '未知状态'
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
console.error('加载最新订单异常:', e); uni.showModal({title: '最新订单报错', content: e.toString()})
|
console.error('加载最新订单异常:', e); uni.showModal({title: '最新订单报错', content: e.toString()})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -979,7 +717,6 @@
|
|||||||
if (status === 4) return '已完成'
|
if (status === 4) return '已完成'
|
||||||
if (status === 0) return '退款中'
|
if (status === 0) return '退款中'
|
||||||
return '未知'
|
return '未知'
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
},
|
},
|
||||||
|
|
||||||
formatTime(timeStr: string): string {
|
formatTime(timeStr: string): string {
|
||||||
@@ -988,70 +725,6 @@
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
const diff = now.getTime() - date.getTime()
|
const diff = now.getTime() - date.getTime()
|
||||||
const minutes = Math.floor(diff / (1000 * 60))
|
const minutes = Math.floor(diff / (1000 * 60))
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
if (minutes < 60) {
|
|
||||||
return `${minutes}分钟前`
|
|
||||||
} else if (minutes < 1440) {
|
|
||||||
return `${Math.floor(minutes / 60)}小时前`
|
|
||||||
} else {
|
|
||||||
return `${Math.floor(minutes / 1440)}天前`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
goToMessages() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/mall/merchant/messages'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToSettings() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/mall/merchant/shop-edit'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToOrders(type: string) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/mall/merchant/orders?type=${type}`
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToProducts(type: string) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/mall/merchant/products?type=${type}`
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToPromotions() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/mall/merchant/promotions'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToStatistics() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/mall/merchant/statistics'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToFinance() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/mall/merchant/finance'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToReviews() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/mall/merchant/reviews'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
goToOrderDetail(orderId: string) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/mall/merchant/order-detail?id=${orderId}`
|
|
||||||
})
|
|
||||||
=======
|
|
||||||
|
|
||||||
if (minutes < 60) return `${minutes}分钟前`
|
if (minutes < 60) return `${minutes}分钟前`
|
||||||
if (minutes < 1440) return `${Math.floor(minutes / 60)}小时前`
|
if (minutes < 1440) return `${Math.floor(minutes / 60)}小时前`
|
||||||
@@ -1104,339 +777,12 @@
|
|||||||
|
|
||||||
goToOrderDetail(orderId: string) {
|
goToOrderDetail(orderId: string) {
|
||||||
uni.navigateTo({ url: `/pages/mall/merchant/order-detail?id=${orderId}` })
|
uni.navigateTo({ url: `/pages/mall/merchant/order-detail?id=${orderId}` })
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
<<<<<<< HEAD
|
|
||||||
.merchant-container {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
border-bottom: 1rpx solid #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shop-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shop-logo {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
border-radius: 40rpx;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shop-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shop-name {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shop-stats {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-item {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #007AFF;
|
|
||||||
margin-left: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-grid {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-value {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #FF6B35;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-label {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-icon {
|
|
||||||
font-size: 32rpx;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
width: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-count {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #FF6B35;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcuts-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcuts-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 30%;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-icon {
|
|
||||||
font-size: 48rpx;
|
|
||||||
margin-bottom: 15rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-orders-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-more {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #007AFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-orders {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60rpx 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-orders-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orders-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-item {
|
|
||||||
border: 1rpx solid #e5e5e5;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 15rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-no {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-status {
|
|
||||||
font-size: 24rpx;
|
|
||||||
padding: 8rpx 16rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pending {
|
|
||||||
background-color: #FFF3CD;
|
|
||||||
color: #856404;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-paid {
|
|
||||||
background-color: #D4EDDA;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-shipped {
|
|
||||||
background-color: #CCE5FF;
|
|
||||||
color: #004085;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-delivered {
|
|
||||||
background-color: #E2E3E5;
|
|
||||||
color: #383D41;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-completed {
|
|
||||||
background-color: #D1ECF1;
|
|
||||||
color: #0C5460;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-products {
|
|
||||||
margin-bottom: 15rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
margin-right: 15rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-name {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 5rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-spec {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 5rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-amount {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #FF6B35;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-time {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
=======
|
|
||||||
.merchant-container { background-color: #f5f7fa; min-height: 100vh; }
|
.merchant-container { background-color: #f5f7fa; min-height: 100vh; }
|
||||||
.main-scroll { height: 100vh; }
|
.main-scroll { height: 100vh; }
|
||||||
|
|
||||||
@@ -1547,4 +893,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|||||||
@@ -57,11 +57,6 @@
|
|||||||
<text class="label">当前库存</text>
|
<text class="label">当前库存</text>
|
||||||
<text class="value">{{ currentProduct?.total_stock }}</text>
|
<text class="value">{{ currentProduct?.total_stock }}</text>
|
||||||
</view>
|
</view>
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">新库存</text>
|
|
||||||
<input class="input" type="number" v-model="newStock" placeholder="请输入新库存"/>
|
|
||||||
=======
|
|
||||||
|
|
||||||
<view class="adjust-type">
|
<view class="adjust-type">
|
||||||
<view class="type-btn" :class="{ active: adjustType === 'set' }" @click="adjustType = 'set'">直接设为</view>
|
<view class="type-btn" :class="{ active: adjustType === 'set' }" @click="adjustType = 'set'">直接设为</view>
|
||||||
@@ -77,16 +72,11 @@
|
|||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">备注 (可选)</text>
|
<text class="label">备注 (可选)</text>
|
||||||
<input class="input" v-model="stockRemark" placeholder="如:入库、损耗等"/>
|
<input class="input" v-model="stockRemark" placeholder="如:入库、损耗等"/>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="modal-footer">
|
<view class="modal-footer">
|
||||||
<view class="modal-btn cancel" @click="closeStockModal">取消</view>
|
<view class="modal-btn cancel" @click="closeStockModal">取消</view>
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="modal-btn confirm" @click="saveStock">保存</view>
|
|
||||||
=======
|
|
||||||
<view class="modal-btn confirm" @click="saveStock">确认提交</view>
|
<view class="modal-btn confirm" @click="saveStock">确认提交</view>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -118,13 +108,9 @@
|
|||||||
stats: { totalProducts: 0, lowStock: 0, outOfStock: 0 },
|
stats: { totalProducts: 0, lowStock: 0, outOfStock: 0 },
|
||||||
showStockModal: false,
|
showStockModal: false,
|
||||||
currentProduct: null as ProductType | null,
|
currentProduct: null as ProductType | null,
|
||||||
<<<<<<< HEAD
|
|
||||||
newStock: ''
|
|
||||||
=======
|
|
||||||
newStock: '',
|
newStock: '',
|
||||||
adjustType: 'set', // 'set', 'add', 'sub'
|
adjustType: 'set', // 'set', 'add', 'sub'
|
||||||
stockRemark: ''
|
stockRemark: ''
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -133,10 +119,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
this.page = 1
|
this.page = 1
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
this.loadProducts()
|
this.loadProducts()
|
||||||
this.loadStats()
|
this.loadStats()
|
||||||
},
|
},
|
||||||
@@ -145,31 +128,16 @@
|
|||||||
async initMerchantId() {
|
async initMerchantId() {
|
||||||
try {
|
try {
|
||||||
const session = supa.getSession()
|
const session = supa.getSession()
|
||||||
<<<<<<< HEAD
|
|
||||||
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
|
||||||
=======
|
|
||||||
if (session != null && session.user != null) {
|
if (session != null && session.user != null) {
|
||||||
this.merchantId = session.user.getString('id') || ''
|
this.merchantId = session.user.getString('id') || ''
|
||||||
}
|
}
|
||||||
if (!this.merchantId) {
|
if (!this.merchantId) {
|
||||||
this.merchantId = uni.getStorageSync('user_id') || ''
|
this.merchantId = uni.getStorageSync('user_id') || ''
|
||||||
}
|
}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadProducts() {
|
async loadProducts() {
|
||||||
<<<<<<< HEAD
|
|
||||||
this.loading = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
let query = supa.from('ml_products').select('id, name, main_image_url, total_stock, warning_stock').eq('merchant_id', this.merchantId).order('total_stock', { ascending: true }).page(this.page).limit(this.limit)
|
|
||||||
|
|
||||||
const response = await query.execute()
|
|
||||||
|
|
||||||
if (response.error != null || !response.data) {
|
|
||||||
this.products = []
|
|
||||||
=======
|
|
||||||
if (this.loading && this.page === 1) return
|
if (this.loading && this.page === 1) return
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
@@ -191,41 +159,19 @@
|
|||||||
|
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
console.error('加载商品失败:', response.error)
|
console.error('加载商品失败:', response.error)
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawData = response.data as any[]
|
const rawData = response.data as any[]
|
||||||
<<<<<<< HEAD
|
|
||||||
let productsData: ProductType[] = []
|
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
|
||||||
const item = rawData[i] as UTSJSONObject
|
|
||||||
const stock = item.getNumber('total_stock') || 0
|
|
||||||
const warning = item.getNumber('warning_stock') || 10
|
|
||||||
|
|
||||||
if (this.currentFilter === 'low' && stock > warning) continue
|
|
||||||
if (this.currentFilter === 'out' && stock > 0) continue
|
|
||||||
|
|
||||||
=======
|
|
||||||
if (!rawData) return
|
if (!rawData) return
|
||||||
|
|
||||||
const productsData: ProductType[] = []
|
const productsData: ProductType[] = []
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
const item = rawData[i] as UTSJSONObject
|
const item = rawData[i] as UTSJSONObject
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
productsData.push({
|
productsData.push({
|
||||||
id: item.getString('id') || '',
|
id: item.getString('id') || '',
|
||||||
name: item.getString('name') || '',
|
name: item.getString('name') || '',
|
||||||
main_image_url: item.getString('main_image_url') || '',
|
main_image_url: item.getString('main_image_url') || '',
|
||||||
<<<<<<< HEAD
|
|
||||||
total_stock: stock,
|
|
||||||
warning_stock: warning
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.products = productsData
|
|
||||||
=======
|
|
||||||
total_stock: item.getNumber('total_stock') || 0,
|
total_stock: item.getNumber('total_stock') || 0,
|
||||||
warning_stock: item.getNumber('warning_stock') || 10
|
warning_stock: item.getNumber('warning_stock') || 10
|
||||||
} as ProductType)
|
} as ProductType)
|
||||||
@@ -238,7 +184,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.hasMore = rawData.length === this.limit
|
this.hasMore = rawData.length === this.limit
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载失败:', e)
|
console.error('加载失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -288,13 +233,9 @@
|
|||||||
|
|
||||||
editStock(product: ProductType) {
|
editStock(product: ProductType) {
|
||||||
this.currentProduct = product
|
this.currentProduct = product
|
||||||
<<<<<<< HEAD
|
|
||||||
this.newStock = String(product.total_stock)
|
|
||||||
=======
|
|
||||||
this.newStock = ''
|
this.newStock = ''
|
||||||
this.adjustType = 'set'
|
this.adjustType = 'set'
|
||||||
this.stockRemark = ''
|
this.stockRemark = ''
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
this.showStockModal = true
|
this.showStockModal = true
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -305,15 +246,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async saveStock() {
|
async saveStock() {
|
||||||
<<<<<<< HEAD
|
|
||||||
if (!this.newStock || isNaN(parseInt(this.newStock))) {
|
|
||||||
uni.showToast({ title: '请输入有效库存', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await supa.from('ml_products').update({ total_stock: parseInt(this.newStock), updated_at: new Date().toISOString() }).eq('id', this.currentProduct!.id).execute()
|
|
||||||
=======
|
|
||||||
const val = parseInt(this.newStock)
|
const val = parseInt(this.newStock)
|
||||||
if (isNaN(val)) {
|
if (isNaN(val)) {
|
||||||
uni.showToast({ title: '请输入有效数值', icon: 'none' })
|
uni.showToast({ title: '请输入有效数值', icon: 'none' })
|
||||||
@@ -345,21 +277,12 @@
|
|||||||
})
|
})
|
||||||
.eq('id', this.currentProduct!.id)
|
.eq('id', this.currentProduct!.id)
|
||||||
.execute()
|
.execute()
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
|
||||||
this.closeStockModal()
|
|
||||||
this.loadProducts()
|
|
||||||
this.loadStats()
|
|
||||||
} catch (e) {
|
|
||||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
|
||||||
=======
|
|
||||||
uni.showToast({ title: '更新成功', icon: 'success' })
|
uni.showToast({ title: '更新成功', icon: 'success' })
|
||||||
this.closeStockModal()
|
this.closeStockModal()
|
||||||
this.page = 1
|
this.page = 1
|
||||||
@@ -369,7 +292,6 @@
|
|||||||
uni.showToast({ title: '操作异常', icon: 'none' })
|
uni.showToast({ title: '操作异常', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -413,17 +335,10 @@
|
|||||||
.action-btn { padding: 12rpx 24rpx; font-size: 24rpx; background-color: #E3F2FD; color: #1976D2; border-radius: 24rpx; }
|
.action-btn { padding: 12rpx 24rpx; font-size: 24rpx; background-color: #E3F2FD; color: #1976D2; border-radius: 24rpx; }
|
||||||
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||||||
.modal-content { width: 80%; background-color: #fff; border-radius: 16rpx; }
|
.modal-content { width: 80%; background-color: #fff; border-radius: 16rpx; }
|
||||||
<<<<<<< HEAD
|
|
||||||
.modal-header { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; border-bottom: 1rpx solid #f5f5f5; }
|
|
||||||
.modal-title { font-size: 32rpx; font-weight: bold; color: #333; }
|
|
||||||
.modal-close { font-size: 44rpx; color: #999; }
|
|
||||||
.modal-body { padding: 30rpx; }
|
|
||||||
=======
|
|
||||||
.modal-body { padding: 30rpx; }
|
.modal-body { padding: 30rpx; }
|
||||||
.adjust-type { display: flex; justify-content: space-between; margin-bottom: 30rpx; }
|
.adjust-type { display: flex; justify-content: space-between; margin-bottom: 30rpx; }
|
||||||
.type-btn { flex: 1; height: 64rpx; line-height: 64rpx; text-align: center; font-size: 24rpx; background-color: #f5f5f5; color: #666; margin: 0 10rpx; border-radius: 32rpx; border: 1rpx solid #eee; }
|
.type-btn { flex: 1; height: 64rpx; line-height: 64rpx; text-align: center; font-size: 24rpx; background-color: #f5f5f5; color: #666; margin: 0 10rpx; border-radius: 32rpx; border: 1rpx solid #eee; }
|
||||||
.type-btn.active { background-color: #E3F2FD; color: #007AFF; border-color: #007AFF; }
|
.type-btn.active { background-color: #E3F2FD; color: #007AFF; border-color: #007AFF; }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
.form-item { margin-bottom: 20rpx; }
|
.form-item { margin-bottom: 20rpx; }
|
||||||
.form-item .label { font-size: 26rpx; color: #999; display: block; margin-bottom: 10rpx; }
|
.form-item .label { font-size: 26rpx; color: #999; display: block; margin-bottom: 10rpx; }
|
||||||
.form-item .value { font-size: 28rpx; color: #333; }
|
.form-item .value { font-size: 28rpx; color: #333; }
|
||||||
|
|||||||
@@ -1,47 +1,6 @@
|
|||||||
<!-- 商家端 - 消息中心页面 -->
|
<!-- 商家端 - 消息中心页面 -->
|
||||||
<template>
|
<template>
|
||||||
<view class="messages-page">
|
<view class="messages-page">
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="tabs">
|
|
||||||
<view class="tab" :class="{ active: currentTab === 'chat' }" @click="switchTab('chat')">会话列表</view>
|
|
||||||
<view class="tab" :class="{ active: currentTab === 'all' }" @click="switchTab('all')">全部消息</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<scroll-view class="messages-list" scroll-y :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
|
||||||
<view v-if="loading && conversations.length === 0" class="loading-container"><text class="loading-text">加载中...</text></view>
|
|
||||||
<view v-else-if="currentTab === 'chat' && conversations.length === 0" class="empty-container"><text class="empty-icon">💬</text><text class="empty-text">暂无会话</text></view>
|
|
||||||
<view v-else-if="currentTab === 'all' && messages.length === 0" class="empty-container"><text class="empty-icon">📭</text><text class="empty-text">暂无消息</text></view>
|
|
||||||
|
|
||||||
<!-- 会话列表 -->
|
|
||||||
<view v-else-if="currentTab === 'chat'">
|
|
||||||
<view v-for="conv in conversations" :key="conv.sessionId" class="conversation-card" @click="goToChat(conv)">
|
|
||||||
<image class="conv-avatar" :src="conv.avatar || '/static/images/default-avatar.png'" mode="aspectFill" />
|
|
||||||
<view class="conv-info">
|
|
||||||
<view class="conv-header">
|
|
||||||
<text class="conv-name">{{ conv.name }}</text>
|
|
||||||
<text class="conv-time">{{ conv.lastTime }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="conv-preview">{{ conv.lastMessage }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="conv.unread > 0" class="unread-badge"><text>{{ conv.unread > 99 ? '99+' : conv.unread }}</text></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 全部消息 -->
|
|
||||||
<view v-else>
|
|
||||||
<view v-for="msg in messages" :key="msg.id" class="message-card" :class="{ unread: !msg.is_read }" @click="viewMessage(msg)">
|
|
||||||
<view class="message-icon">{{ msg.is_from_user ? '👤' : '🏪' }}</view>
|
|
||||||
<view class="message-content">
|
|
||||||
<view class="message-header">
|
|
||||||
<text class="message-title">{{ msg.is_from_user ? '发给客户' : '收到消息' }}</text>
|
|
||||||
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="message-text">{{ msg.content }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="!msg.is_read" class="unread-dot"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
=======
|
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<text class="header-title">消息</text>
|
<text class="header-title">消息</text>
|
||||||
<text class="header-subtitle">与客户的聊天记录</text>
|
<text class="header-subtitle">与客户的聊天记录</text>
|
||||||
@@ -82,7 +41,6 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="safe-bottom"></view>
|
<view class="safe-bottom"></view>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,10 +66,7 @@
|
|||||||
avatar: string
|
avatar: string
|
||||||
lastMessage: string
|
lastMessage: string
|
||||||
lastTime: string
|
lastTime: string
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
lastTimeRaw: string
|
lastTimeRaw: string
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
unread: number
|
unread: number
|
||||||
userId: string
|
userId: string
|
||||||
}
|
}
|
||||||
@@ -119,11 +74,6 @@
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
<<<<<<< HEAD
|
|
||||||
currentTab: 'chat',
|
|
||||||
messages: [] as MessageType[],
|
|
||||||
=======
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
conversations: [] as ConversationType[],
|
conversations: [] as ConversationType[],
|
||||||
loading: false,
|
loading: false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
@@ -161,48 +111,15 @@
|
|||||||
const response = await query.execute()
|
const response = await query.execute()
|
||||||
|
|
||||||
if (response.error != null || !response.data) {
|
if (response.error != null || !response.data) {
|
||||||
<<<<<<< HEAD
|
|
||||||
this.messages = []
|
|
||||||
=======
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
this.conversations = []
|
this.conversations = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawData = response.data as any[]
|
const rawData = response.data as any[]
|
||||||
<<<<<<< HEAD
|
|
||||||
const messagesData: MessageType[] = []
|
|
||||||
=======
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
const sessionMap = new Map<string, ConversationType>()
|
const sessionMap = new Map<string, ConversationType>()
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
const item = rawData[i] as UTSJSONObject
|
const item = rawData[i] as UTSJSONObject
|
||||||
<<<<<<< HEAD
|
|
||||||
const msg: MessageType = {
|
|
||||||
id: item.getString('id') || '',
|
|
||||||
session_id: item.getString('session_id') || '',
|
|
||||||
sender_id: item.getString('sender_id') || '',
|
|
||||||
receiver_id: item.getString('receiver_id') || '',
|
|
||||||
content: item.getString('content') || '',
|
|
||||||
msg_type: item.getString('msg_type') || 'text',
|
|
||||||
is_read: item.getBoolean('is_read') || false,
|
|
||||||
is_from_user: item.getBoolean('is_from_user') || false,
|
|
||||||
created_at: item.getString('created_at') || ''
|
|
||||||
}
|
|
||||||
messagesData.push(msg)
|
|
||||||
|
|
||||||
const otherUserId = msg.is_from_user ? msg.receiver_id : msg.sender_id
|
|
||||||
const sessionId = msg.session_id || otherUserId
|
|
||||||
|
|
||||||
if (!sessionMap.has(sessionId)) {
|
|
||||||
sessionMap.set(sessionId, {
|
|
||||||
sessionId: sessionId,
|
|
||||||
name: '客户',
|
|
||||||
avatar: '',
|
|
||||||
lastMessage: msg.content,
|
|
||||||
lastTime: this.formatTime(msg.created_at),
|
|
||||||
=======
|
|
||||||
const isFromUser = item.getBoolean('is_from_user') || false
|
const isFromUser = item.getBoolean('is_from_user') || false
|
||||||
const senderId = item.getString('sender_id') || ''
|
const senderId = item.getString('sender_id') || ''
|
||||||
const receiverId = item.getString('receiver_id') || ''
|
const receiverId = item.getString('receiver_id') || ''
|
||||||
@@ -227,18 +144,11 @@
|
|||||||
lastMessage: content,
|
lastMessage: content,
|
||||||
lastTime: this.formatTime(createdAt),
|
lastTime: this.formatTime(createdAt),
|
||||||
lastTimeRaw: createdAt,
|
lastTimeRaw: createdAt,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
unread: 0,
|
unread: 0,
|
||||||
userId: otherUserId
|
userId: otherUserId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const conv = sessionMap.get(sessionId)!
|
|
||||||
conv.lastMessage = msg.content
|
|
||||||
conv.lastTime = this.formatTime(msg.created_at)
|
|
||||||
if (!msg.is_read && !msg.is_from_user) {
|
|
||||||
=======
|
|
||||||
const conv = sessionMap.get(otherUserId)!
|
const conv = sessionMap.get(otherUserId)!
|
||||||
// 更新最后一条消息(按时间最新的)
|
// 更新最后一条消息(按时间最新的)
|
||||||
if (createdAt > conv.lastTimeRaw) {
|
if (createdAt > conv.lastTimeRaw) {
|
||||||
@@ -248,15 +158,10 @@
|
|||||||
}
|
}
|
||||||
// 未读消息:消息来自用户且未读
|
// 未读消息:消息来自用户且未读
|
||||||
if (!isRead && isFromUser) {
|
if (!isRead && isFromUser) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
conv.unread++
|
conv.unread++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
this.messages = messagesData
|
|
||||||
=======
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
this.conversations = Array.from(sessionMap.values()).sort((a, b) => b.unread - a.unread)
|
this.conversations = Array.from(sessionMap.values()).sort((a, b) => b.unread - a.unread)
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -273,33 +178,11 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
switchTab(tab: string) {
|
|
||||||
this.currentTab = tab
|
|
||||||
},
|
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
onRefresh() {
|
onRefresh() {
|
||||||
this.refreshing = true
|
this.refreshing = true
|
||||||
this.loadMessages()
|
this.loadMessages()
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
viewMessage(msg: MessageType) {
|
|
||||||
if (!msg.is_read) {
|
|
||||||
supa.from('ml_chat_messages').update({ is_read: true }).eq('id', msg.id).execute()
|
|
||||||
msg.is_read = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const otherUserId = msg.is_from_user ? msg.receiver_id : msg.sender_id
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/mall/merchant/chat?user_id=${otherUserId}&session_id=${msg.session_id}`
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
formatTime(timeStr: string): string {
|
formatTime(timeStr: string): string {
|
||||||
if (!timeStr) return ''
|
if (!timeStr) return ''
|
||||||
const date = new Date(timeStr)
|
const date = new Date(timeStr)
|
||||||
@@ -319,37 +202,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
<<<<<<< HEAD
|
|
||||||
.messages-page { background-color: #f5f5f5; min-height: 100vh; }
|
|
||||||
.tabs { display: flex; background-color: #fff; padding: 0 20rpx; position: sticky; top: 0; z-index: 10; }
|
|
||||||
.tab { flex: 1; text-align: center; padding: 24rpx 0; font-size: 28rpx; color: #666; position: relative; }
|
|
||||||
.tab.active { color: #007AFF; font-weight: bold; }
|
|
||||||
.tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40rpx; height: 4rpx; background-color: #007AFF; border-radius: 2rpx; }
|
|
||||||
.messages-list { padding: 20rpx; height: calc(100vh - 100rpx); }
|
|
||||||
.loading-container, .empty-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100rpx 0; }
|
|
||||||
.empty-icon { font-size: 100rpx; margin-bottom: 20rpx; }
|
|
||||||
.empty-text, .loading-text { font-size: 28rpx; color: #999; }
|
|
||||||
|
|
||||||
.conversation-card { display: flex; align-items: center; background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; position: relative; }
|
|
||||||
.conv-avatar { width: 100rpx; height: 100rpx; border-radius: 12rpx; margin-right: 20rpx; }
|
|
||||||
.conv-info { flex: 1; }
|
|
||||||
.conv-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10rpx; }
|
|
||||||
.conv-name { font-size: 30rpx; color: #333; font-weight: 500; }
|
|
||||||
.conv-time { font-size: 22rpx; color: #999; }
|
|
||||||
.conv-preview { font-size: 26rpx; color: #666; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
|
||||||
.unread-badge { min-width: 36rpx; height: 36rpx; background-color: #FF3B30; border-radius: 18rpx; display: flex; align-items: center; justify-content: center; padding: 0 10rpx; }
|
|
||||||
.unread-badge text { font-size: 20rpx; color: #fff; }
|
|
||||||
|
|
||||||
.message-card { display: flex; align-items: flex-start; background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; position: relative; }
|
|
||||||
.message-card.unread { background-color: #f0f9ff; }
|
|
||||||
.message-icon { font-size: 40rpx; margin-right: 20rpx; }
|
|
||||||
.message-content { flex: 1; }
|
|
||||||
.message-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10rpx; }
|
|
||||||
.message-title { font-size: 28rpx; color: #333; font-weight: 500; }
|
|
||||||
.message-time { font-size: 22rpx; color: #999; }
|
|
||||||
.message-text { font-size: 26rpx; color: #666; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
||||||
.unread-dot { position: absolute; top: 30rpx; right: 30rpx; width: 16rpx; height: 16rpx; background-color: #FF3B30; border-radius: 50%; }
|
|
||||||
=======
|
|
||||||
.messages-page { background-color: #f5f7fa; min-height: 100vh; display: flex; flex-direction: column; }
|
.messages-page { background-color: #f5f7fa; min-height: 100vh; display: flex; flex-direction: column; }
|
||||||
|
|
||||||
.header { background-color: #fff; padding-top: 60rpx; padding-bottom: 24rpx; padding-left: 30rpx; padding-right: 30rpx; border-bottom-width: 1rpx; border-bottom-style: solid; border-bottom-color: #eee; }
|
.header { background-color: #fff; padding-top: 60rpx; padding-bottom: 24rpx; padding-left: 30rpx; padding-right: 30rpx; border-bottom-width: 1rpx; border-bottom-style: solid; border-bottom-color: #eee; }
|
||||||
@@ -383,5 +235,4 @@
|
|||||||
.conv-arrow { font-size: 40rpx; color: #ccc; margin-left: 10rpx; }
|
.conv-arrow { font-size: 40rpx; color: #ccc; margin-left: 10rpx; }
|
||||||
|
|
||||||
.safe-bottom { height: 30rpx; }
|
.safe-bottom { height: 30rpx; }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
<!-- 商家端 - 订单详情页面 -->
|
<!-- 商家端 - 订单详情页面 -->
|
||||||
=======
|
|
||||||
<!-- 商家端 - 订单详情页面 -->
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<template>
|
<template>
|
||||||
<view class="order-detail-page">
|
<view class="order-detail-page">
|
||||||
<!-- 订单状态头部 -->
|
<!-- 订单状态头部 -->
|
||||||
@@ -118,32 +114,20 @@
|
|||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<view class="action-buttons">
|
<view class="action-buttons">
|
||||||
<view
|
<view
|
||||||
<<<<<<< HEAD
|
|
||||||
v-if="order.order_status === 1"
|
|
||||||
=======
|
|
||||||
v-if="order.order_status === 2"
|
v-if="order.order_status === 2"
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
class="action-btn primary"
|
class="action-btn primary"
|
||||||
@click="shipOrder"
|
@click="shipOrder"
|
||||||
>
|
>
|
||||||
去发货
|
去发货
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
<<<<<<< HEAD
|
|
||||||
v-if="order.order_status === 2"
|
|
||||||
=======
|
|
||||||
v-if="order.order_status === 3"
|
v-if="order.order_status === 3"
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
class="action-btn primary"
|
class="action-btn primary"
|
||||||
@click="viewLogistics"
|
@click="viewLogistics"
|
||||||
>
|
>
|
||||||
查看物流
|
查看物流
|
||||||
</view>
|
</view>
|
||||||
<<<<<<< HEAD
|
|
||||||
<view
|
|
||||||
=======
|
|
||||||
<view
|
<view
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
v-if="order.order_status === 3"
|
v-if="order.order_status === 3"
|
||||||
class="action-btn primary"
|
class="action-btn primary"
|
||||||
@click="confirmDelivery"
|
@click="confirmDelivery"
|
||||||
@@ -179,11 +163,7 @@
|
|||||||
@change="onLogisticsChange"
|
@change="onLogisticsChange"
|
||||||
>
|
>
|
||||||
<view class="picker-value">
|
<view class="picker-value">
|
||||||
<<<<<<< HEAD
|
|
||||||
{{ selectedLogistics.name || '请选择物流公司' }}
|
|
||||||
=======
|
|
||||||
{{ selectedLogistics?.name || '请选择物流公司' }}
|
{{ selectedLogistics?.name || '请选择物流公司' }}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
@@ -260,10 +240,6 @@
|
|||||||
updated_at: '',
|
updated_at: '',
|
||||||
items: [] as OrderItemType[]
|
items: [] as OrderItemType[]
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
addressData: {} as AddressType,
|
|
||||||
|
|
||||||
=======
|
|
||||||
addressData: {
|
addressData: {
|
||||||
recipient_name: '',
|
recipient_name: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
@@ -273,7 +249,6 @@
|
|||||||
detail_address: ''
|
detail_address: ''
|
||||||
} as AddressType,
|
} as AddressType,
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
showShipModal: false,
|
showShipModal: false,
|
||||||
logisticsCompanies: [
|
logisticsCompanies: [
|
||||||
{ name: '顺丰速运', code: 'SF' },
|
{ name: '顺丰速运', code: 'SF' },
|
||||||
@@ -284,20 +259,11 @@
|
|||||||
{ name: 'EMS', code: 'EMS' },
|
{ name: 'EMS', code: 'EMS' },
|
||||||
{ name: '京东物流', code: 'JD' }
|
{ name: '京东物流', code: 'JD' }
|
||||||
] as LogisticsType[],
|
] as LogisticsType[],
|
||||||
<<<<<<< HEAD
|
|
||||||
selectedLogistics: {} as LogisticsType,
|
|
||||||
=======
|
|
||||||
selectedLogistics: { name: '', code: '' } as LogisticsType,
|
selectedLogistics: { name: '', code: '' } as LogisticsType,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
trackingNumber: ''
|
trackingNumber: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
onLoad(options: any) {
|
|
||||||
const id = options.id as string
|
|
||||||
if (id) {
|
|
||||||
=======
|
|
||||||
onLoad(options: any) { console.log('--- DEBUG ON LOAD ---', options)
|
onLoad(options: any) { console.log('--- DEBUG ON LOAD ---', options)
|
||||||
let id = ''
|
let id = ''
|
||||||
if (options['id'] != null) {
|
if (options['id'] != null) {
|
||||||
@@ -307,28 +273,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (id !== '') {
|
if (id !== '') {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
this.orderId = id
|
this.orderId = id
|
||||||
this.loadOrderDetail()
|
this.loadOrderDetail()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
<<<<<<< HEAD
|
|
||||||
async loadOrderDetail() {
|
|
||||||
try {
|
|
||||||
=======
|
|
||||||
async loadOrderDetail() { console.log('--- DEBUG LOAD ORDER DETAIL ---', this.orderId); try {
|
async loadOrderDetail() { console.log('--- DEBUG LOAD ORDER DETAIL ---', this.orderId); try {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
const response = await supa
|
const response = await supa
|
||||||
.from('ml_orders')
|
.from('ml_orders')
|
||||||
.select(`
|
.select(`
|
||||||
*,
|
*,
|
||||||
<<<<<<< HEAD
|
|
||||||
order_items!inner (
|
|
||||||
=======
|
|
||||||
ml_order_items (
|
ml_order_items (
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
id,
|
id,
|
||||||
order_id,
|
order_id,
|
||||||
product_id,
|
product_id,
|
||||||
@@ -345,44 +301,12 @@
|
|||||||
.single()
|
.single()
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
=======
|
|
||||||
if (response.error != null || (response.status ?? 200) >= 400) {
|
if (response.error != null || (response.status ?? 200) >= 400) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
console.error('获取订单详情失败:', response.error)
|
console.error('获取订单详情失败:', response.error)
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const rawData = response.data as UTSJSONObject
|
|
||||||
if (rawData == null) return
|
|
||||||
|
|
||||||
this.order = {
|
|
||||||
id: rawData.getString('id') || '',
|
|
||||||
order_no: rawData.getString('order_no') || '',
|
|
||||||
user_id: rawData.getString('user_id') || '',
|
|
||||||
merchant_id: rawData.getString('merchant_id') || '',
|
|
||||||
order_status: rawData.getNumber('order_status') || 1,
|
|
||||||
total_amount: rawData.getNumber('total_amount') || 0,
|
|
||||||
product_amount: rawData.getNumber('product_amount') || 0,
|
|
||||||
shipping_fee: rawData.getNumber('shipping_fee') || 0,
|
|
||||||
discount_amount: rawData.getNumber('discount_amount') || 0,
|
|
||||||
paid_amount: rawData.getNumber('paid_amount') || 0,
|
|
||||||
shipping_address: rawData.getString('shipping_address') || '{}',
|
|
||||||
remark: rawData.getString('remark') || '',
|
|
||||||
shipping_company: rawData.getString('shipping_company') || '',
|
|
||||||
tracking_number: rawData.getString('tracking_number') || '',
|
|
||||||
paid_at: rawData.getString('paid_at') || '',
|
|
||||||
shipped_at: rawData.getString('shipped_at') || '',
|
|
||||||
created_at: rawData.getString('created_at') || '',
|
|
||||||
updated_at: rawData.getString('updated_at') || '',
|
|
||||||
items: []
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemsObj = rawData.get('order_items')
|
|
||||||
=======
|
|
||||||
console.log('--- DEBUG RAW ORDER DATA ---', response.data); let realData = response.data;
|
console.log('--- DEBUG RAW ORDER DATA ---', response.data); let realData = response.data;
|
||||||
let isArrLike = false;
|
let isArrLike = false;
|
||||||
if (response.data != null && (response.data as any)['0'] != null) {
|
if (response.data != null && (response.data as any)['0'] != null) {
|
||||||
@@ -416,23 +340,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const itemsObj = rawData['ml_order_items']
|
const itemsObj = rawData['ml_order_items']
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
if (itemsObj != null && Array.isArray(itemsObj)) {
|
if (itemsObj != null && Array.isArray(itemsObj)) {
|
||||||
const itemsArray = itemsObj as any[]
|
const itemsArray = itemsObj as any[]
|
||||||
for (let i = 0; i < itemsArray.length; i++) {
|
for (let i = 0; i < itemsArray.length; i++) {
|
||||||
const orderItem = itemsArray[i] as UTSJSONObject
|
const orderItem = itemsArray[i] as UTSJSONObject
|
||||||
this.order.items.push({
|
this.order.items.push({
|
||||||
<<<<<<< HEAD
|
|
||||||
id: orderItem.getString('id') || '',
|
|
||||||
order_id: orderItem.getString('order_id') || '',
|
|
||||||
product_id: orderItem.getString('product_id') || '',
|
|
||||||
sku_id: orderItem.getString('sku_id') || '',
|
|
||||||
product_name: orderItem.getString('product_name') || '',
|
|
||||||
sku_name: orderItem.getString('sku_name') || '',
|
|
||||||
price: orderItem.getNumber('price') || 0,
|
|
||||||
quantity: orderItem.getNumber('quantity') || 0,
|
|
||||||
image_url: orderItem.getString('image_url') || '',
|
|
||||||
=======
|
|
||||||
id: String(orderItem['id'] ?? '') || '',
|
id: String(orderItem['id'] ?? '') || '',
|
||||||
order_id: String(orderItem['order_id'] ?? '') || '',
|
order_id: String(orderItem['order_id'] ?? '') || '',
|
||||||
product_id: String(orderItem['product_id'] ?? '') || '',
|
product_id: String(orderItem['product_id'] ?? '') || '',
|
||||||
@@ -442,7 +354,6 @@
|
|||||||
price: Number(orderItem['price'] ?? 0) || 0,
|
price: Number(orderItem['price'] ?? 0) || 0,
|
||||||
quantity: Number(orderItem['quantity'] ?? 0) || 0,
|
quantity: Number(orderItem['quantity'] ?? 0) || 0,
|
||||||
image_url: String(orderItem['image_url'] ?? '') || '',
|
image_url: String(orderItem['image_url'] ?? '') || '',
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
sku_snapshot: ''
|
sku_snapshot: ''
|
||||||
} as OrderItemType)
|
} as OrderItemType)
|
||||||
}
|
}
|
||||||
@@ -550,11 +461,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async confirmShip() {
|
async confirmShip() {
|
||||||
<<<<<<< HEAD
|
|
||||||
if (!this.selectedLogistics.name) {
|
|
||||||
=======
|
|
||||||
if (this.selectedLogistics == null || !this.selectedLogistics?.name) {
|
if (this.selectedLogistics == null || !this.selectedLogistics?.name) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
uni.showToast({ title: '请选择物流公司', icon: 'none' })
|
uni.showToast({ title: '请选择物流公司', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -568,39 +475,23 @@
|
|||||||
.from('ml_orders')
|
.from('ml_orders')
|
||||||
.update({
|
.update({
|
||||||
order_status: 3,
|
order_status: 3,
|
||||||
<<<<<<< HEAD
|
|
||||||
shipping_company: this.selectedLogistics.name,
|
|
||||||
tracking_number: this.trackingNumber,
|
|
||||||
=======
|
|
||||||
shipping_status: 2,
|
shipping_status: 2,
|
||||||
carrier_name: this.selectedLogistics?.name, tracking_no: this.trackingNumber,
|
carrier_name: this.selectedLogistics?.name, tracking_no: this.trackingNumber,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
shipped_at: new Date().toISOString(),
|
shipped_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString()
|
updated_at: new Date().toISOString()
|
||||||
})
|
})
|
||||||
.eq('id', this.order.id)
|
.eq('id', this.order.id)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
uni.showToast({ title: '发货失败', icon: 'none' })
|
|
||||||
=======
|
|
||||||
if (response.error != null || (response.status ?? 200) >= 400) {
|
if (response.error != null || (response.status ?? 200) >= 400) {
|
||||||
let msg = response.error?.message ?? (response.data != null ? JSON.stringify(response.data) : '请检查网络或登录状态'); uni.showToast({ title: '发货被拦截: ' + msg, icon: 'none', duration: 4500 }); console.error('SUPABASE API ERR:', response)
|
let msg = response.error?.message ?? (response.data != null ? JSON.stringify(response.data) : '请检查网络或登录状态'); uni.showToast({ title: '发货被拦截: ' + msg, icon: 'none', duration: 4500 }); console.error('SUPABASE API ERR:', response)
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showToast({ title: '发货成功', icon: 'success' })
|
uni.showToast({ title: '发货成功', icon: 'success' })
|
||||||
this.closeShipModal()
|
this.closeShipModal()
|
||||||
this.loadOrderDetail()
|
this.loadOrderDetail()
|
||||||
<<<<<<< HEAD
|
|
||||||
} catch (e) {
|
|
||||||
uni.showToast({ title: '发货失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
} catch (e) { uni.showToast({ title: '发货发生异常', icon: 'none' }); console.error(e) }
|
} catch (e) { uni.showToast({ title: '发货发生异常', icon: 'none' }); console.error(e) }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
},
|
},
|
||||||
|
|
||||||
viewLogistics() {
|
viewLogistics() {
|
||||||
@@ -627,11 +518,7 @@
|
|||||||
.eq('id', this.order.id)
|
.eq('id', this.order.id)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
=======
|
|
||||||
if (response.error != null || (response.status ?? 200) >= 400) {
|
if (response.error != null || (response.status ?? 200) >= 400) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -659,11 +546,7 @@
|
|||||||
.eq('id', this.order.id)
|
.eq('id', this.order.id)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
=======
|
|
||||||
if (response.error != null || (response.status ?? 200) >= 400) {
|
if (response.error != null || (response.status ?? 200) >= 400) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
uni.showToast({ title: '删除失败', icon: 'none' })
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -975,11 +858,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
<<<<<<< HEAD
|
|
||||||
z-index: 1000;
|
|
||||||
=======
|
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@@ -987,11 +866,8 @@
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
@@ -1065,24 +941,4 @@
|
|||||||
color: #007AFF;
|
color: #007AFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
<!-- 商家端 - 订单管理页面 -->
|
<!-- 商家端 - 订单管理页面 -->
|
||||||
=======
|
|
||||||
<!-- 商家端 - 订单管理页面 -->
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<template>
|
<template>
|
||||||
<view class="orders-page">
|
<view class="orders-page">
|
||||||
<!-- 标签页切换 -->
|
<!-- 标签页切换 -->
|
||||||
@@ -100,22 +96,14 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="order-actions">
|
<view class="order-actions">
|
||||||
<view
|
<view
|
||||||
<<<<<<< HEAD
|
|
||||||
v-if="order.order_status === 1"
|
|
||||||
=======
|
|
||||||
v-if="order.order_status === 2"
|
v-if="order.order_status === 2"
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
class="action-btn primary"
|
class="action-btn primary"
|
||||||
@click.stop="shipOrder(order)"
|
@click.stop="shipOrder(order)"
|
||||||
>
|
>
|
||||||
发货
|
发货
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
<<<<<<< HEAD
|
|
||||||
v-if="order.order_status === 2"
|
|
||||||
=======
|
|
||||||
v-if="order.order_status === 3"
|
v-if="order.order_status === 3"
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
class="action-btn info"
|
class="action-btn info"
|
||||||
@click.stop="viewLogistics(order)"
|
@click.stop="viewLogistics(order)"
|
||||||
>
|
>
|
||||||
@@ -159,11 +147,7 @@
|
|||||||
@change="onLogisticsChange"
|
@change="onLogisticsChange"
|
||||||
>
|
>
|
||||||
<view class="picker-value">
|
<view class="picker-value">
|
||||||
<<<<<<< HEAD
|
|
||||||
{{ selectedLogistics.name || '请选择物流公司' }}
|
|
||||||
=======
|
|
||||||
{{ selectedLogistics?.name || '请选择物流公司' }}
|
{{ selectedLogistics?.name || '请选择物流公司' }}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
@@ -238,11 +222,7 @@
|
|||||||
{ name: '待发货', status: 2, count: 0 },
|
{ name: '待发货', status: 2, count: 0 },
|
||||||
{ name: '待收货', status: 3, count: 0 },
|
{ name: '待收货', status: 3, count: 0 },
|
||||||
{ name: '已完成', status: 4, count: 0 },
|
{ name: '已完成', status: 4, count: 0 },
|
||||||
<<<<<<< HEAD
|
|
||||||
{ name: '退款', status: 0, count: 0 }
|
|
||||||
=======
|
|
||||||
{ name: '退款', status: 6, count: 0 }
|
{ name: '退款', status: 6, count: 0 }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
] as TabType[],
|
] as TabType[],
|
||||||
currentTab: -2,
|
currentTab: -2,
|
||||||
searchKeyword: '',
|
searchKeyword: '',
|
||||||
@@ -277,11 +257,7 @@
|
|||||||
const statusMap: Record<string, number> = {
|
const statusMap: Record<string, number> = {
|
||||||
'pending': 1,
|
'pending': 1,
|
||||||
'shipped': 3,
|
'shipped': 3,
|
||||||
<<<<<<< HEAD
|
|
||||||
'refund': 0,
|
|
||||||
=======
|
|
||||||
'refund': 6,
|
'refund': 6,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
'completed': 4
|
'completed': 4
|
||||||
}
|
}
|
||||||
this.currentTab = statusMap[type] ?? -2
|
this.currentTab = statusMap[type] ?? -2
|
||||||
@@ -325,11 +301,7 @@
|
|||||||
.from('ml_orders')
|
.from('ml_orders')
|
||||||
.select(`
|
.select(`
|
||||||
*,
|
*,
|
||||||
<<<<<<< HEAD
|
|
||||||
order_items!inner (
|
|
||||||
=======
|
|
||||||
order_items (
|
order_items (
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
id,
|
id,
|
||||||
order_id,
|
order_id,
|
||||||
product_id,
|
product_id,
|
||||||
@@ -348,14 +320,9 @@
|
|||||||
.limit(this.limit)
|
.limit(this.limit)
|
||||||
|
|
||||||
if (this.currentTab !== -2) {
|
if (this.currentTab !== -2) {
|
||||||
<<<<<<< HEAD
|
|
||||||
if (this.currentTab === 0) {
|
|
||||||
query = query.eq('order_status', 0)
|
|
||||||
=======
|
|
||||||
if (this.currentTab === 6) {
|
if (this.currentTab === 6) {
|
||||||
// 退款状态同时查询 0 和 6
|
// 退款状态同时查询 0 和 6
|
||||||
query = query.in('order_status', [0, 6])
|
query = query.in('order_status', [0, 6])
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} else {
|
} else {
|
||||||
query = query.eq('order_status', this.currentTab)
|
query = query.eq('order_status', this.currentTab)
|
||||||
}
|
}
|
||||||
@@ -367,11 +334,7 @@
|
|||||||
|
|
||||||
const response = await query.execute()
|
const response = await query.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
=======
|
|
||||||
if (response.error != null || (response.status ?? 200) >= 400) {
|
if (response.error != null || (response.status ?? 200) >= 400) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
console.error('获取订单失败:', response.error)
|
console.error('获取订单失败:', response.error)
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -387,32 +350,20 @@
|
|||||||
const ordersData: OrderType[] = []
|
const ordersData: OrderType[] = []
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
const item = rawData[i]
|
const item = rawData[i]
|
||||||
<<<<<<< HEAD
|
|
||||||
const orderObj = item as UTSJSONObject
|
|
||||||
=======
|
|
||||||
const str = JSON.stringify(item)
|
const str = JSON.stringify(item)
|
||||||
const orderObj = JSON.parse(str) as UTSJSONObject
|
const orderObj = JSON.parse(str) as UTSJSONObject
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|
||||||
const order: OrderType = {
|
const order: OrderType = {
|
||||||
id: orderObj.getString('id') || '',
|
id: orderObj.getString('id') || '',
|
||||||
order_no: orderObj.getString('order_no') || '',
|
order_no: orderObj.getString('order_no') || '',
|
||||||
user_id: orderObj.getString('user_id') || '',
|
user_id: orderObj.getString('user_id') || '',
|
||||||
merchant_id: orderObj.getString('merchant_id') || '',
|
merchant_id: orderObj.getString('merchant_id') || '',
|
||||||
<<<<<<< HEAD
|
|
||||||
order_status: orderObj.getNumber('order_status') || 1,
|
|
||||||
=======
|
|
||||||
order_status: orderObj.getNumber('order_status') ?? (orderObj.get('order_status') == null ? 1 : (orderObj.get('order_status') as number)),
|
order_status: orderObj.getNumber('order_status') ?? (orderObj.get('order_status') == null ? 1 : (orderObj.get('order_status') as number)),
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
total_amount: orderObj.getNumber('total_amount') || 0,
|
total_amount: orderObj.getNumber('total_amount') || 0,
|
||||||
product_amount: orderObj.getNumber('product_amount') || 0,
|
product_amount: orderObj.getNumber('product_amount') || 0,
|
||||||
shipping_fee: orderObj.getNumber('shipping_fee') || 0,
|
shipping_fee: orderObj.getNumber('shipping_fee') || 0,
|
||||||
paid_amount: orderObj.getNumber('paid_amount') || 0,
|
paid_amount: orderObj.getNumber('paid_amount') || 0,
|
||||||
<<<<<<< HEAD
|
|
||||||
shipping_address: orderObj.getString('shipping_address') || '',
|
|
||||||
=======
|
|
||||||
shipping_address: orderObj.get('shipping_address') != null ? (typeof orderObj.get('shipping_address') === 'string' ? orderObj.getString('shipping_address')! : JSON.stringify(orderObj.get('shipping_address'))) : '',
|
shipping_address: orderObj.get('shipping_address') != null ? (typeof orderObj.get('shipping_address') === 'string' ? orderObj.getString('shipping_address')! : JSON.stringify(orderObj.get('shipping_address'))) : '',
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
remark: orderObj.getString('remark') || '',
|
remark: orderObj.getString('remark') || '',
|
||||||
created_at: orderObj.getString('created_at') || '',
|
created_at: orderObj.getString('created_at') || '',
|
||||||
updated_at: orderObj.getString('updated_at') || '',
|
updated_at: orderObj.getString('updated_at') || '',
|
||||||
@@ -423,14 +374,10 @@
|
|||||||
if (itemsObj != null && Array.isArray(itemsObj)) {
|
if (itemsObj != null && Array.isArray(itemsObj)) {
|
||||||
const itemsArray = itemsObj as any[]
|
const itemsArray = itemsObj as any[]
|
||||||
for (let j = 0; j < itemsArray.length; j++) {
|
for (let j = 0; j < itemsArray.length; j++) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const orderItem = itemsArray[j] as UTSJSONObject
|
|
||||||
=======
|
|
||||||
const rawItem = itemsArray[j]
|
const rawItem = itemsArray[j]
|
||||||
const itemStr = JSON.stringify(rawItem)
|
const itemStr = JSON.stringify(rawItem)
|
||||||
const orderItem = JSON.parse(itemStr) as UTSJSONObject
|
const orderItem = JSON.parse(itemStr) as UTSJSONObject
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
order.items.push({
|
order.items.push({
|
||||||
id: orderItem.getString('id') || '',
|
id: orderItem.getString('id') || '',
|
||||||
order_id: orderItem.getString('order_id') || '',
|
order_id: orderItem.getString('order_id') || '',
|
||||||
@@ -482,10 +429,6 @@
|
|||||||
const rawData = response.data as any[]
|
const rawData = response.data as any[]
|
||||||
if (rawData != null) {
|
if (rawData != null) {
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const item = rawData[i] as UTSJSONObject
|
|
||||||
const status = item.getNumber('order_status') || 1
|
|
||||||
=======
|
|
||||||
const row = rawData[i]
|
const row = rawData[i]
|
||||||
const istr = JSON.stringify(row)
|
const istr = JSON.stringify(row)
|
||||||
const item = JSON.parse(istr) as UTSJSONObject
|
const item = JSON.parse(istr) as UTSJSONObject
|
||||||
@@ -495,16 +438,11 @@
|
|||||||
status = (typeof status_val === 'number') ? (status_val as number) : parseInt(status_val.toString())
|
status = (typeof status_val === 'number') ? (status_val as number) : parseInt(status_val.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
if (status === 1) counts[1]++
|
if (status === 1) counts[1]++
|
||||||
else if (status === 2) counts[2]++
|
else if (status === 2) counts[2]++
|
||||||
else if (status === 3) counts[3]++
|
else if (status === 3) counts[3]++
|
||||||
else if (status === 4) counts[4]++
|
else if (status === 4) counts[4]++
|
||||||
<<<<<<< HEAD
|
|
||||||
else if (status === 0) counts[0]++
|
|
||||||
=======
|
|
||||||
else if (status === 0 || status === 6) counts[0]++
|
else if (status === 0 || status === 6) counts[0]++
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
total++
|
total++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -574,11 +512,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async confirmShip() {
|
async confirmShip() {
|
||||||
<<<<<<< HEAD
|
|
||||||
if (!this.selectedLogistics.name) {
|
|
||||||
=======
|
|
||||||
if (this.selectedLogistics == null || !this.selectedLogistics?.name) {
|
if (this.selectedLogistics == null || !this.selectedLogistics?.name) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
uni.showToast({ title: '请选择物流公司', icon: 'none' })
|
uni.showToast({ title: '请选择物流公司', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -588,22 +522,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
<<<<<<< HEAD
|
|
||||||
const response = await supa
|
|
||||||
.from('ml_orders')
|
|
||||||
.update({
|
|
||||||
order_status: 3,
|
|
||||||
shipping_company: this.selectedLogistics.name,
|
|
||||||
tracking_number: this.trackingNumber,
|
|
||||||
shipped_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
})
|
|
||||||
.eq('id', this.currentOrder!.id)
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
if (response.error != null) {
|
|
||||||
uni.showToast({ title: '发货失败', icon: 'none' })
|
|
||||||
=======
|
|
||||||
const payloadStr = JSON.stringify({
|
const payloadStr = JSON.stringify({
|
||||||
order_status: 3,
|
order_status: 3,
|
||||||
shipping_status: 2,
|
shipping_status: 2,
|
||||||
@@ -627,7 +545,6 @@
|
|||||||
msg = rData.getString('message') ?? rData.getString('code') ?? JSON.stringify(rData);
|
msg = rData.getString('message') ?? rData.getString('code') ?? JSON.stringify(rData);
|
||||||
}
|
}
|
||||||
if (!msg) msg = '请检查网络或登录状态'; uni.showToast({ title: '发货被拦截: ' + msg, icon: 'none', duration: 4500 }); console.error('SUPABASE API ERR:', response)
|
if (!msg) msg = '请检查网络或登录状态'; uni.showToast({ title: '发货被拦截: ' + msg, icon: 'none', duration: 4500 }); console.error('SUPABASE API ERR:', response)
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,22 +552,12 @@
|
|||||||
this.closeShipModal()
|
this.closeShipModal()
|
||||||
this.loadOrders()
|
this.loadOrders()
|
||||||
this.loadOrderCounts()
|
this.loadOrderCounts()
|
||||||
<<<<<<< HEAD
|
|
||||||
} catch (e) {
|
|
||||||
uni.showToast({ title: '发货失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
} catch (e) { uni.showToast({ title: '发货发生异常', icon: 'none' }); console.error(e) }
|
} catch (e) { uni.showToast({ title: '发货发生异常', icon: 'none' }); console.error(e) }
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
},
|
},
|
||||||
|
|
||||||
viewLogistics(order: OrderType) {
|
viewLogistics(order: OrderType) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
<<<<<<< HEAD
|
|
||||||
url: `/pages/mall/merchant/logistics?orderId=${order.id}`
|
|
||||||
=======
|
|
||||||
url: `/pages/mall/consumer/logistics?orderId=${order.id}`
|
url: `/pages/mall/consumer/logistics?orderId=${order.id}`
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -667,11 +574,7 @@
|
|||||||
.eq('id', order.id)
|
.eq('id', order.id)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
=======
|
|
||||||
if (response.error != null || (response.status ?? 200) >= 400) {
|
if (response.error != null || (response.status ?? 200) >= 400) {
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
uni.showToast({ title: '删除失败', icon: 'none' })
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -692,12 +595,8 @@
|
|||||||
if (status === 2) return '待发货'
|
if (status === 2) return '待发货'
|
||||||
if (status === 3) return '待收货'
|
if (status === 3) return '待收货'
|
||||||
if (status === 4) return '已完成'
|
if (status === 4) return '已完成'
|
||||||
<<<<<<< HEAD
|
|
||||||
if (status === 0) return '退款中'
|
|
||||||
=======
|
|
||||||
if (status === 0 || status === 6) return '退款/售后'
|
if (status === 0 || status === 6) return '退款/售后'
|
||||||
if (status === 7) return '退货完成'
|
if (status === 7) return '退货完成'
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
if (status === 5 || status === -1) return '已取消'
|
if (status === 5 || status === -1) return '已取消'
|
||||||
return '未知'
|
return '未知'
|
||||||
},
|
},
|
||||||
@@ -838,14 +737,9 @@
|
|||||||
|
|
||||||
.order-card {
|
.order-card {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
<<<<<<< HEAD
|
|
||||||
border-radius: 16rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
=======
|
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -944,12 +838,6 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
.product-spec {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-top: 8rpx;
|
|
||||||
=======
|
|
||||||
.product-name {
|
.product-name {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -970,28 +858,20 @@
|
|||||||
padding: 4rpx 12rpx;
|
padding: 4rpx 12rpx;
|
||||||
border-radius: 4rpx;
|
border-radius: 4rpx;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-right {
|
.product-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
margin-left: 20rpx;
|
margin-left: 20rpx;
|
||||||
min-width: 120rpx;
|
min-width: 120rpx;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-price {
|
.product-price {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
<<<<<<< HEAD
|
|
||||||
font-weight: 500;
|
|
||||||
=======
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-quantity {
|
.product-quantity {
|
||||||
@@ -1004,14 +884,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
<<<<<<< HEAD
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
border-top: 1rpx solid #f5f5f5;
|
|
||||||
=======
|
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
border-top: 1rpx solid #f8f8f8;
|
border-top: 1rpx solid #f8f8f8;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-amount {
|
.order-amount {
|
||||||
@@ -1025,11 +900,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.amount-value {
|
.amount-value {
|
||||||
<<<<<<< HEAD
|
|
||||||
font-size: 28rpx;
|
|
||||||
=======
|
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
color: #FF3B30;
|
color: #FF3B30;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-left: 10rpx;
|
margin-left: 10rpx;
|
||||||
@@ -1041,11 +912,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
<<<<<<< HEAD
|
|
||||||
padding: 12rpx 24rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border-radius: 28rpx;
|
|
||||||
=======
|
|
||||||
min-width: 120rpx;
|
min-width: 120rpx;
|
||||||
height: 56rpx;
|
height: 56rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1055,24 +921,11 @@
|
|||||||
border-radius: 28rpx;
|
border-radius: 28rpx;
|
||||||
border: 1rpx solid #eee;
|
border: 1rpx solid #eee;
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.primary {
|
.action-btn.primary {
|
||||||
background-color: #007AFF;
|
background-color: #007AFF;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
<<<<<<< HEAD
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.info {
|
|
||||||
background-color: #E3F2FD;
|
|
||||||
color: #1976D2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.default {
|
|
||||||
background-color: #F5F5F5;
|
|
||||||
color: #666;
|
|
||||||
=======
|
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,7 +939,6 @@
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #666;
|
color: #666;
|
||||||
border-color: #ddd;
|
border-color: #ddd;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-more, .no-more {
|
.load-more, .no-more {
|
||||||
@@ -1109,11 +961,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
<<<<<<< HEAD
|
|
||||||
z-index: 1000;
|
|
||||||
=======
|
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@@ -1121,11 +969,8 @@
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
@@ -1199,20 +1044,4 @@
|
|||||||
color: #007AFF;
|
color: #007AFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
<!-- 商家端 - 商品管理详情页 -->
|
<!-- 商家端 - 商品管理详情页 -->
|
||||||
=======
|
|
||||||
<!-- 商家端 - 商品管理详情页 -->
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<template>
|
<template>
|
||||||
<view class="product-manage-detail">
|
<view class="product-manage-detail">
|
||||||
<!-- 商品基本信息 -->
|
<!-- 商品基本信息 -->
|
||||||
@@ -48,13 +44,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
<text class="info-label">商品状态</text>
|
<text class="info-label">商品状态</text>
|
||||||
<<<<<<< HEAD
|
|
||||||
<text class="info-value" :class="{ 'status-on': product.status === 1, 'status-off': product.status === 0 }">
|
|
||||||
{{ product.status === 1 ? '上架' : '下架' }}
|
|
||||||
=======
|
|
||||||
<text class="info-value" :class="{ 'status-on': product.status === 1, 'status-off': product.status === 2 || product.status === 0 }">
|
<text class="info-value" :class="{ 'status-on': product.status === 1, 'status-off': product.status === 2 || product.status === 0 }">
|
||||||
{{ product.status === 1 ? '上架' : (product.status === 2 || product.status === 0 ? '下架' : '其他') }}
|
{{ product.status === 1 ? '上架' : (product.status === 2 || product.status === 0 ? '下架' : '其他') }}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -79,11 +70,7 @@
|
|||||||
<view class="sku-details">
|
<view class="sku-details">
|
||||||
<text class="sku-price">¥{{ sku.price }}</text>
|
<text class="sku-price">¥{{ sku.price }}</text>
|
||||||
<text class="sku-stock">库存: {{ sku.stock }}</text>
|
<text class="sku-stock">库存: {{ sku.stock }}</text>
|
||||||
<<<<<<< HEAD
|
|
||||||
<text class="sku-status" :class="{ 'status-on': sku.status === 1, 'status-off': sku.status === 0 }">
|
|
||||||
=======
|
|
||||||
<text class="sku-status" :class="{ 'status-on': sku.status === 1, 'status-off': sku.status === 2 || sku.status === 0 }">
|
<text class="sku-status" :class="{ 'status-on': sku.status === 1, 'status-off': sku.status === 2 || sku.status === 0 }">
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
{{ sku.status === 1 ? '启用' : '禁用' }}
|
{{ sku.status === 1 ? '启用' : '禁用' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -718,7 +705,3 @@ export default {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
<!-- 商家端 - 商品编辑页面 -->
|
<!-- 商家端 - 商品编辑页面 -->
|
||||||
=======
|
|
||||||
<!-- 商家端 - 商品编辑页面 (已修复缓存) -->
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<template>
|
<template>
|
||||||
<view class="product-edit-page">
|
<view class="product-edit-page">
|
||||||
<!-- 商品基本信息 -->
|
<!-- 商品基本信息 -->
|
||||||
@@ -135,10 +131,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">总库存 *</text>
|
|
||||||
=======
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">VIP独立折扣</text>
|
<text class="label">VIP独立折扣</text>
|
||||||
<switch :checked="product.is_vip_discount" @change="e => { product.is_vip_discount = e.detail.value as boolean }" />
|
<switch :checked="product.is_vip_discount" @change="e => { product.is_vip_discount = e.detail.value as boolean }" />
|
||||||
@@ -156,7 +148,6 @@
|
|||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">总库存 *</text>
|
<text class="label">总库存 *</text>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -176,8 +167,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
<!-- 会员阶梯价 -->
|
<!-- 会员阶梯价 -->
|
||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">会员等级价格 (选填)</view>
|
<view class="section-title">会员等级价格 (选填)</view>
|
||||||
@@ -197,7 +186,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<!-- 商品属性 -->
|
<!-- 商品属性 -->
|
||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">商品属性</view>
|
<view class="section-title">商品属性</view>
|
||||||
@@ -277,8 +265,6 @@
|
|||||||
logo_url: string
|
logo_url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
type MemberLevelType = {
|
type MemberLevelType = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -287,7 +273,6 @@
|
|||||||
price: string // 绑定输入框用
|
price: string // 绑定输入框用
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -299,10 +284,7 @@
|
|||||||
brands: [] as BrandType[],
|
brands: [] as BrandType[],
|
||||||
brandIndex: -1,
|
brandIndex: -1,
|
||||||
selectedBrand: null as BrandType | null,
|
selectedBrand: null as BrandType | null,
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
memberLevels: [] as MemberLevelType[],
|
memberLevels: [] as MemberLevelType[],
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
product: {
|
product: {
|
||||||
name: '',
|
name: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
@@ -316,17 +298,11 @@
|
|||||||
total_stock: '',
|
total_stock: '',
|
||||||
warning_stock: '10',
|
warning_stock: '10',
|
||||||
unit: '件',
|
unit: '件',
|
||||||
<<<<<<< HEAD
|
|
||||||
is_hot: false,
|
|
||||||
is_new: false,
|
|
||||||
is_featured: false,
|
|
||||||
=======
|
|
||||||
is_hot: false,
|
is_hot: false,
|
||||||
is_new: false,
|
is_new: false,
|
||||||
is_featured: false,
|
is_featured: false,
|
||||||
is_vip_discount: true,
|
is_vip_discount: true,
|
||||||
vip_discount_rate: '',
|
vip_discount_rate: '',
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
description: ''
|
description: ''
|
||||||
},
|
},
|
||||||
merchantId: ''
|
merchantId: ''
|
||||||
@@ -334,13 +310,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
onLoad(options: any) {
|
onLoad(options: any) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const productId = options.productId as string
|
|
||||||
if (productId) {
|
|
||||||
this.productId = productId
|
|
||||||
this.isEdit = true
|
|
||||||
this.loadProductDetail(productId)
|
|
||||||
=======
|
|
||||||
let productId = ''
|
let productId = ''
|
||||||
if (options) {
|
if (options) {
|
||||||
const keys = Object.keys(options as object)
|
const keys = Object.keys(options as object)
|
||||||
@@ -371,15 +340,11 @@
|
|||||||
this.loadProductDetail(productId)
|
this.loadProductDetail(productId)
|
||||||
} else {
|
} else {
|
||||||
uni.setNavigationBarTitle({ title: '添加商品' })
|
uni.setNavigationBarTitle({ title: '添加商品' })
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
this.initMerchantId()
|
this.initMerchantId()
|
||||||
this.loadCategories()
|
this.loadCategories()
|
||||||
this.loadBrands()
|
this.loadBrands()
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
this.loadMemberLevels()
|
this.loadMemberLevels()
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -387,11 +352,7 @@
|
|||||||
try {
|
try {
|
||||||
const session = supa.getSession()
|
const session = supa.getSession()
|
||||||
if (session != null && session.user != null) {
|
if (session != null && session.user != null) {
|
||||||
<<<<<<< HEAD
|
|
||||||
this.merchantId = session.user.getString('id') || ''
|
|
||||||
=======
|
|
||||||
this.merchantId = (session.user as any)['id'] != null ? String((session.user as any)['id']) : ''
|
this.merchantId = (session.user as any)['id'] != null ? String((session.user as any)['id']) : ''
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
if (!this.merchantId) {
|
if (!this.merchantId) {
|
||||||
this.merchantId = uni.getStorageSync('user_id') || ''
|
this.merchantId = uni.getStorageSync('user_id') || ''
|
||||||
@@ -401,8 +362,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
async loadMemberLevels() {
|
async loadMemberLevels() {
|
||||||
try {
|
try {
|
||||||
const response = await supa
|
const response = await supa
|
||||||
@@ -472,7 +431,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
async loadCategories() {
|
async loadCategories() {
|
||||||
try {
|
try {
|
||||||
const response = await supa
|
const response = await supa
|
||||||
@@ -491,17 +449,10 @@
|
|||||||
if (rawData == null) return
|
if (rawData == null) return
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const item = rawData[i] as UTSJSONObject
|
|
||||||
this.categories.push({
|
|
||||||
id: item.getString('id') || '',
|
|
||||||
name: item.getString('name') || ''
|
|
||||||
=======
|
|
||||||
const item = rawData[i] as any
|
const item = rawData[i] as any
|
||||||
this.categories.push({
|
this.categories.push({
|
||||||
id: item['id'] != null ? String(item['id']) : '',
|
id: item['id'] != null ? String(item['id']) : '',
|
||||||
name: item['name'] != null ? String(item['name']) : ''
|
name: item['name'] != null ? String(item['name']) : ''
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} as CategoryType)
|
} as CategoryType)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -527,19 +478,11 @@
|
|||||||
if (rawData == null) return
|
if (rawData == null) return
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const item = rawData[i] as UTSJSONObject
|
|
||||||
this.brands.push({
|
|
||||||
id: item.getString('id') || '',
|
|
||||||
name: item.getString('name') || '',
|
|
||||||
logo_url: item.getString('logo_url') || ''
|
|
||||||
=======
|
|
||||||
const item = rawData[i] as any
|
const item = rawData[i] as any
|
||||||
this.brands.push({
|
this.brands.push({
|
||||||
id: item['id'] != null ? String(item['id']) : '',
|
id: item['id'] != null ? String(item['id']) : '',
|
||||||
name: item['name'] != null ? String(item['name']) : '',
|
name: item['name'] != null ? String(item['name']) : '',
|
||||||
logo_url: item['logo_url'] != null ? String(item['logo_url']) : ''
|
logo_url: item['logo_url'] != null ? String(item['logo_url']) : ''
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} as BrandType)
|
} as BrandType)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -549,10 +492,7 @@
|
|||||||
|
|
||||||
async loadProductDetail(productId: string) {
|
async loadProductDetail(productId: string) {
|
||||||
try {
|
try {
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
uni.showLoading({ title: '加载商品中...' })
|
uni.showLoading({ title: '加载商品中...' })
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
const response = await supa
|
const response = await supa
|
||||||
.from('ml_products')
|
.from('ml_products')
|
||||||
.select('*')
|
.select('*')
|
||||||
@@ -560,35 +500,6 @@
|
|||||||
.single()
|
.single()
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (response.error != null) {
|
|
||||||
console.error('获取商品详情失败:', response.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawData = response.data as UTSJSONObject
|
|
||||||
if (rawData == null) return
|
|
||||||
|
|
||||||
this.product = {
|
|
||||||
name: rawData.getString('name') || '',
|
|
||||||
subtitle: rawData.getString('subtitle') || '',
|
|
||||||
category_id: rawData.getString('category_id') || '',
|
|
||||||
brand_id: rawData.getString('brand_id') || '',
|
|
||||||
main_image_url: rawData.getString('main_image_url') || '',
|
|
||||||
imageList: this.parseImageUrls(rawData.getString('image_urls')),
|
|
||||||
base_price: rawData.getString('base_price') || '',
|
|
||||||
market_price: rawData.getString('market_price') || '',
|
|
||||||
cost_price: rawData.getString('cost_price') || '',
|
|
||||||
total_stock: rawData.getString('total_stock') || '',
|
|
||||||
warning_stock: rawData.getString('warning_stock') || '10',
|
|
||||||
unit: rawData.getString('unit') || '件',
|
|
||||||
is_hot: rawData.getBoolean('is_hot') || false,
|
|
||||||
is_new: rawData.getBoolean('is_new') || false,
|
|
||||||
is_featured: rawData.getBoolean('is_featured') || false,
|
|
||||||
description: rawData.getString('description') || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
=======
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
console.error('获取详情失败:', response.error)
|
console.error('获取详情失败:', response.error)
|
||||||
@@ -628,7 +539,6 @@
|
|||||||
this.product.vip_discount_rate = getStr('vip_discount_rate')
|
this.product.vip_discount_rate = getStr('vip_discount_rate')
|
||||||
this.product.description = getStr('description')
|
this.product.description = getStr('description')
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
if (this.product.category_id) {
|
if (this.product.category_id) {
|
||||||
this.categoryIndex = this.categories.findIndex(c => c.id === this.product.category_id)
|
this.categoryIndex = this.categories.findIndex(c => c.id === this.product.category_id)
|
||||||
if (this.categoryIndex >= 0) {
|
if (this.categoryIndex >= 0) {
|
||||||
@@ -643,13 +553,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
<<<<<<< HEAD
|
|
||||||
console.error('获取商品详情异常:', e)
|
|
||||||
=======
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
console.error('获取商品详情异常:', e)
|
console.error('获取商品详情异常:', e)
|
||||||
uni.showToast({ title: '加载异常: ' + String(e), icon: 'none', duration: 3000 })
|
uni.showToast({ title: '加载异常: ' + String(e), icon: 'none', duration: 3000 })
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -702,88 +608,6 @@
|
|||||||
this.product.imageList.splice(index, 1)
|
this.product.imageList.splice(index, 1)
|
||||||
},
|
},
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
async saveProduct() {
|
|
||||||
if (!this.product.name) {
|
|
||||||
uni.showToast({ title: '请输入商品名称', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.product.category_id) {
|
|
||||||
uni.showToast({ title: '请选择商品分类', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.product.base_price) {
|
|
||||||
uni.showToast({ title: '请输入销售价', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.product.total_stock) {
|
|
||||||
uni.showToast({ title: '请输入总库存', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({ title: '保存中...' })
|
|
||||||
|
|
||||||
try {
|
|
||||||
const imageUrlsStr = JSON.stringify(this.product.imageList)
|
|
||||||
|
|
||||||
const productData = {
|
|
||||||
merchant_id: this.merchantId,
|
|
||||||
name: this.product.name,
|
|
||||||
subtitle: this.product.subtitle,
|
|
||||||
category_id: this.product.category_id,
|
|
||||||
brand_id: this.product.brand_id || null,
|
|
||||||
main_image_url: this.product.main_image_url,
|
|
||||||
image_urls: imageUrlsStr,
|
|
||||||
base_price: parseFloat(this.product.base_price),
|
|
||||||
market_price: this.product.market_price ? parseFloat(this.product.market_price) : null,
|
|
||||||
cost_price: this.product.cost_price ? parseFloat(this.product.cost_price) : null,
|
|
||||||
total_stock: parseInt(this.product.total_stock),
|
|
||||||
warning_stock: parseInt(this.product.warning_stock) || 10,
|
|
||||||
unit: this.product.unit,
|
|
||||||
is_hot: this.product.is_hot,
|
|
||||||
is_new: this.product.is_new,
|
|
||||||
is_featured: this.product.is_featured,
|
|
||||||
description: this.product.description,
|
|
||||||
status: 1,
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
let response
|
|
||||||
if (this.isEdit) {
|
|
||||||
response = await supa
|
|
||||||
.from('ml_products')
|
|
||||||
.update(productData)
|
|
||||||
.eq('id', this.productId)
|
|
||||||
.execute()
|
|
||||||
} else {
|
|
||||||
productData['created_at'] = new Date().toISOString()
|
|
||||||
response = await supa
|
|
||||||
.from('ml_products')
|
|
||||||
.insert(productData)
|
|
||||||
.execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.hideLoading()
|
|
||||||
|
|
||||||
if (response.error != null) {
|
|
||||||
console.error('保存商品失败:', response.error)
|
|
||||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.navigateBack()
|
|
||||||
}, 1500)
|
|
||||||
} catch (e) {
|
|
||||||
uni.hideLoading()
|
|
||||||
console.error('保存商品异常:', e)
|
|
||||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
async uploadImageToSupa(localPath: string): Promise<string> {
|
async uploadImageToSupa(localPath: string): Promise<string> {
|
||||||
if (localPath.startsWith('http://') || localPath.startsWith('https://')) {
|
if (localPath.startsWith('http://') || localPath.startsWith('https://')) {
|
||||||
return localPath
|
return localPath
|
||||||
@@ -979,7 +803,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -1004,8 +827,6 @@
|
|||||||
border-bottom: 1rpx solid #f5f5f5;
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
.section-desc {
|
.section-desc {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
@@ -1013,7 +834,6 @@
|
|||||||
margin-bottom: 30rpx;
|
margin-bottom: 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
.form-item {
|
.form-item {
|
||||||
margin-bottom: 30rpx;
|
margin-bottom: 30rpx;
|
||||||
}
|
}
|
||||||
@@ -1170,9 +990,3 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
<!-- 商家端 - 商品管理列表页面 -->
|
<!-- 商家端 - 商品管理列表页面 -->
|
||||||
=======
|
|
||||||
<!-- 商家端 - 商品管理列表页面 -->
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<template>
|
<template>
|
||||||
<view class="products-page">
|
<view class="products-page">
|
||||||
<!-- 搜索栏 -->
|
<!-- 搜索栏 -->
|
||||||
@@ -88,20 +84,12 @@
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="product-subtitle">{{ product.subtitle || '暂无描述' }}</text>
|
<text class="product-subtitle">{{ product.subtitle || '暂无描述' }}</text>
|
||||||
<<<<<<< HEAD
|
|
||||||
<view class="product-tags" v-if="product.tags">
|
|
||||||
<text v-if="product.is_hot" class="tag hot">热</text>
|
|
||||||
<text v-if="product.is_new" class="tag new">新</text>
|
|
||||||
<text v-if="product.is_featured" class="tag recommend">荐</text>
|
|
||||||
</view>
|
|
||||||
=======
|
|
||||||
<view class="product-tags">
|
<view class="product-tags">
|
||||||
<text v-if="product.is_hot" class="tag hot">热</text>
|
<text v-if="product.is_hot" class="tag hot">热</text>
|
||||||
<text v-if="product.is_new" class="tag new">新</text>
|
<text v-if="product.is_new" class="tag new">新</text>
|
||||||
<text v-if="product.is_featured" class="tag recommend">荐</text>
|
<text v-if="product.is_featured" class="tag recommend">荐</text>
|
||||||
<text v-if="product.is_vip_discount" class="tag vip">VIP</text>
|
<text v-if="product.is_vip_discount" class="tag vip">VIP</text>
|
||||||
</view>
|
</view>
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
<view class="product-stats">
|
<view class="product-stats">
|
||||||
<view class="price-row">
|
<view class="price-row">
|
||||||
<text class="current-price">¥{{ product.base_price }}</text>
|
<text class="current-price">¥{{ product.base_price }}</text>
|
||||||
@@ -235,11 +223,7 @@
|
|||||||
if (this.currentFilter === 'onsale') {
|
if (this.currentFilter === 'onsale') {
|
||||||
query = query.eq('status', 1)
|
query = query.eq('status', 1)
|
||||||
} else if (this.currentFilter === 'offsale') {
|
} else if (this.currentFilter === 'offsale') {
|
||||||
<<<<<<< HEAD
|
|
||||||
query = query.eq('status', 0)
|
|
||||||
=======
|
|
||||||
query = query.eq('status', 2)
|
query = query.eq('status', 2)
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
} else if (this.currentFilter === 'low_stock') {
|
} else if (this.currentFilter === 'low_stock') {
|
||||||
query = query.lte('total_stock', this.lowStockThreshold).gte('total_stock', 0)
|
query = query.lte('total_stock', this.lowStockThreshold).gte('total_stock', 0)
|
||||||
}
|
}
|
||||||
@@ -276,11 +260,7 @@
|
|||||||
market_price: prodObj.getNumber('market_price') || 0,
|
market_price: prodObj.getNumber('market_price') || 0,
|
||||||
total_stock: prodObj.getNumber('total_stock') || 0,
|
total_stock: prodObj.getNumber('total_stock') || 0,
|
||||||
sale_count: prodObj.getNumber('sale_count') || 0,
|
sale_count: prodObj.getNumber('sale_count') || 0,
|
||||||
<<<<<<< HEAD
|
|
||||||
status: prodObj.getNumber('status') || 0,
|
|
||||||
=======
|
|
||||||
status: prodObj.getNumber('status') || 1,
|
status: prodObj.getNumber('status') || 1,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
is_hot: prodObj.getBoolean('is_hot') || false,
|
is_hot: prodObj.getBoolean('is_hot') || false,
|
||||||
is_new: prodObj.getBoolean('is_new') || false,
|
is_new: prodObj.getBoolean('is_new') || false,
|
||||||
is_featured: prodObj.getBoolean('is_featured') || false,
|
is_featured: prodObj.getBoolean('is_featured') || false,
|
||||||
@@ -353,11 +333,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async toggleStatus(product: ProductType) {
|
async toggleStatus(product: ProductType) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const newStatus = product.status === 1 ? 0 : 1
|
|
||||||
=======
|
|
||||||
const newStatus = product.status === 1 ? 2 : 1
|
const newStatus = product.status === 1 ? 2 : 1
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
const actionText = newStatus === 1 ? '上架' : '下架'
|
const actionText = newStatus === 1 ? '上架' : '下架'
|
||||||
|
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
@@ -420,21 +396,13 @@
|
|||||||
|
|
||||||
getStatusClass(status: number): string {
|
getStatusClass(status: number): string {
|
||||||
if (status === 1) return 'status-onsale'
|
if (status === 1) return 'status-onsale'
|
||||||
<<<<<<< HEAD
|
|
||||||
if (status === 0) return 'status-offsale'
|
|
||||||
=======
|
|
||||||
if (status === 2 || status === 0) return 'status-offsale'
|
if (status === 2 || status === 0) return 'status-offsale'
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
return 'status-pending'
|
return 'status-pending'
|
||||||
},
|
},
|
||||||
|
|
||||||
getStatusText(status: number): string {
|
getStatusText(status: number): string {
|
||||||
if (status === 1) return '在售'
|
if (status === 1) return '在售'
|
||||||
<<<<<<< HEAD
|
|
||||||
if (status === 0) return '已下架'
|
|
||||||
=======
|
|
||||||
if (status === 2 || status === 0) return '已下架'
|
if (status === 2 || status === 0) return '已下架'
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
return '待审核'
|
return '待审核'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,12 +602,6 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
.tag.recommend {
|
|
||||||
background-color: #9C27B0;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
.tag.recommend {
|
.tag.recommend {
|
||||||
background-color: #9C27B0;
|
background-color: #9C27B0;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -650,7 +612,6 @@
|
|||||||
color: #333;
|
color: #333;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|
||||||
.product-stats {
|
.product-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -756,9 +717,3 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
|
|||||||
@@ -135,9 +135,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
=======
|
|
||||||
|
|
||||||
async uploadImageToSupa(localPath: string): Promise<string> {
|
async uploadImageToSupa(localPath: string): Promise<string> {
|
||||||
if (localPath.startsWith('http://') || localPath.startsWith('https://')) {
|
if (localPath.startsWith('http://') || localPath.startsWith('https://')) {
|
||||||
@@ -173,22 +170,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
async saveShop() {
|
async saveShop() {
|
||||||
if (!this.shop.shop_name) {
|
if (!this.shop.shop_name) {
|
||||||
uni.showToast({ title: '请输入店铺名称', icon: 'none' })
|
uni.showToast({ title: '请输入店铺名称', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
uni.showLoading({ title: '保存中...' })
|
|
||||||
|
|
||||||
try {
|
|
||||||
const shopData = {
|
|
||||||
shop_name: this.shop.shop_name,
|
|
||||||
shop_logo: this.shop.shop_logo,
|
|
||||||
shop_banner: this.shop.shop_banner,
|
|
||||||
=======
|
|
||||||
uni.showLoading({ title: '正在上传图片...' })
|
uni.showLoading({ title: '正在上传图片...' })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -208,7 +195,6 @@
|
|||||||
shop_name: this.shop.shop_name,
|
shop_name: this.shop.shop_name,
|
||||||
shop_logo: finalLogo,
|
shop_logo: finalLogo,
|
||||||
shop_banner: finalBanner,
|
shop_banner: finalBanner,
|
||||||
>>>>>>> local-backup-root-cyj
|
|
||||||
description: this.shop.description,
|
description: this.shop.description,
|
||||||
contact_name: this.shop.contact_name,
|
contact_name: this.shop.contact_name,
|
||||||
contact_phone: this.shop.contact_phone,
|
contact_phone: this.shop.contact_phone,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { rpcOrValue } from '@/services/analytics/rpc.uts'
|
import { supabase } from '@/components/supadb/aksupainstance.uts'
|
||||||
import supa from '@/components/supadb/aksupainstance.uts'
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
|
|
||||||
export type ProductReviewItem = {
|
export type ProductReviewItem = {
|
||||||
@@ -26,21 +26,106 @@ export type ProductReviewQuery = {
|
|||||||
pageSize?: number
|
pageSize?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直接查询 ml_product_reviews,JOIN ml_products 和 ak_users 获取商品名/图片/用户名
|
||||||
|
// 使用 supabase.select + Content-Range 实现服务端分页,无需 RPC
|
||||||
export async function fetchAdminProductReviews(query?: ProductReviewQuery): Promise<{ total: number; items: Array<ProductReviewItem> }> {
|
export async function fetchAdminProductReviews(query?: ProductReviewQuery): Promise<{ total: number; items: Array<ProductReviewItem> }> {
|
||||||
const payload = {
|
const page = query?.page ?? 1
|
||||||
p_search_product: query?.searchProduct ?? null,
|
const pageSize = query?.pageSize ?? 20
|
||||||
p_search_user: query?.searchUser ?? null,
|
const offset = (page - 1) * pageSize
|
||||||
p_status: query?.status ?? null,
|
|
||||||
p_start_time: query?.startTime ?? null,
|
|
||||||
p_end_time: query?.endTime ?? null,
|
|
||||||
p_page: query?.page ?? 1,
|
|
||||||
p_page_size: query?.pageSize ?? 20
|
|
||||||
} as any
|
|
||||||
|
|
||||||
const res = await rpcOrValue('rpc_admin_get_product_reviews', payload as any)
|
// 构建 PostgREST filter 字符串
|
||||||
const arr = Array.isArray(res) ? (res as Array<any>) : ([] as Array<any>)
|
const filters: string[] = []
|
||||||
const total = arr.length > 0 ? parseInt(String(arr[0]?.total_count ?? '0')) : 0
|
if (query?.status != null) filters.push(`status=eq.${query.status}`)
|
||||||
return { total, items: arr as Array<ProductReviewItem> }
|
if (query?.startTime != null && query.startTime !== '') filters.push(`created_at=gte.${query.startTime}`)
|
||||||
|
if (query?.endTime != null && query.endTime !== '') filters.push(`created_at=lte.${query.endTime}`)
|
||||||
|
// offset 注入(PostgREST 识别为 SQL OFFSET,避免发送 Range 头)
|
||||||
|
if (offset > 0) filters.push(`offset=${offset}`)
|
||||||
|
|
||||||
|
const filterStr = filters.length > 0 ? filters.join('&') : null
|
||||||
|
|
||||||
|
// 查询评价表,并内联关联商品和用户信息
|
||||||
|
const res = await supabase.select(
|
||||||
|
'ml_product_reviews',
|
||||||
|
filterStr,
|
||||||
|
{
|
||||||
|
columns: 'id, product_id, user_id, rating, content, merchant_reply, status, created_at, product:ml_products!ml_product_reviews_product_id_fkey(name, main_image_url), reviewer:ak_users!ml_product_reviews_user_id_fkey(username)',
|
||||||
|
limit: pageSize,
|
||||||
|
order: 'created_at.desc',
|
||||||
|
count: 'exact'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res.status < 200 || res.status >= 300 || res.data == null) {
|
||||||
|
console.error('fetchAdminProductReviews 查询失败, status:', res.status)
|
||||||
|
return { total: 0, items: [] as Array<ProductReviewItem> }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Content-Range 解析总行数
|
||||||
|
let totalCount = 0
|
||||||
|
const hdrs = res.headers
|
||||||
|
if (hdrs != null) {
|
||||||
|
let cr: string | null = null
|
||||||
|
if (typeof (hdrs as any).get === 'function') {
|
||||||
|
cr = (hdrs as any).get('content-range') as string | null
|
||||||
|
}
|
||||||
|
if (cr == null) cr = (hdrs as UTSJSONObject)['content-range'] as string | null
|
||||||
|
if (cr != null) {
|
||||||
|
const m = /\/(\d+)$/.exec(cr)
|
||||||
|
if (m != null) totalCount = parseInt(m[1] ?? '0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalCount === 0) {
|
||||||
|
totalCount = offset + (Array.isArray(res.data) ? (res.data as any[]).length : 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字段映射:把 JOIN 结果摊平为 ProductReviewItem
|
||||||
|
const rawRows = res.data as any[]
|
||||||
|
const items: Array<ProductReviewItem> = []
|
||||||
|
|
||||||
|
// 客户端 searchProduct / searchUser 过滤(PostgREST embedded filter 兼容性问题时的兜底)
|
||||||
|
const spLower = (query?.searchProduct ?? '').toLowerCase()
|
||||||
|
const suLower = (query?.searchUser ?? '').toLowerCase()
|
||||||
|
|
||||||
|
for (let i = 0; i < rawRows.length; i++) {
|
||||||
|
const row = rawRows[i] as UTSJSONObject
|
||||||
|
const productRaw = row.get('product')
|
||||||
|
const reviewerRaw = row.get('reviewer')
|
||||||
|
|
||||||
|
let productName = ''
|
||||||
|
let productImage: string | null = null
|
||||||
|
if (productRaw != null) {
|
||||||
|
const p = (productRaw instanceof UTSJSONObject ? productRaw : JSON.parse(JSON.stringify(productRaw))) as UTSJSONObject
|
||||||
|
productName = p.getString('name') ?? ''
|
||||||
|
productImage = p.getString('main_image_url') ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
let username: string | null = null
|
||||||
|
if (reviewerRaw != null) {
|
||||||
|
const u = (reviewerRaw instanceof UTSJSONObject ? reviewerRaw : JSON.parse(JSON.stringify(reviewerRaw))) as UTSJSONObject
|
||||||
|
username = u.getString('username') ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端过滤(searchProduct / searchUser)
|
||||||
|
if (spLower !== '' && !productName.toLowerCase().includes(spLower)) continue
|
||||||
|
if (suLower !== '' && (username ?? '').toLowerCase().includes(suLower) === false) continue
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
id: row.getString('id') ?? '',
|
||||||
|
product_id: row.getString('product_id') ?? '',
|
||||||
|
product_name: productName,
|
||||||
|
product_image: productImage,
|
||||||
|
user_id: row.getString('user_id') ?? '',
|
||||||
|
username: username,
|
||||||
|
rating: row.getNumber('rating') ?? 0,
|
||||||
|
content: row.getString('content') ?? null,
|
||||||
|
merchant_reply: row.getString('merchant_reply') ?? null,
|
||||||
|
status: row.getNumber('status') ?? 1,
|
||||||
|
created_at: row.getString('created_at') ?? '',
|
||||||
|
total_count: totalCount
|
||||||
|
} as ProductReviewItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { total: totalCount, items }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function approveProductReview(id: string): Promise<boolean> {
|
export async function approveProductReview(id: string): Promise<boolean> {
|
||||||
|
|||||||
Reference in New Issue
Block a user