Files
assistant-storefront/app/javascript/shared/components/ChatTable.vue
Liang XJ 092fb2e083
Some checks failed
Lock Threads / action (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Run Linux nightly installer / nightly (push) Has been cancelled
Initial commit: Add logistics and order_detail message types
- Add Logistics component with progress tracking
- Add OrderDetail component for order information
- Support data-driven steps and actions
- Add blue color scale to widget SCSS
- Fix node overflow and progress bar rendering issues
- Add English translations for dashboard components

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-26 11:16:56 +08:00

374 lines
7.8 KiB
Vue

<script>
import { mapActions } from 'vuex';
export default {
props: {
title: {
type: String,
default: '',
},
headers: {
type: Array,
default: () => [],
},
rows: {
type: Array,
default: () => [],
},
},
computed: {
// Check if any row has a button
hasButtonColumn() {
return this.rows.some(row => {
const lastItem = row[row.length - 1];
return lastItem && typeof lastItem === 'object' && lastItem.button;
});
},
// Get the first row with button to determine column count
firstRowWithButton() {
return this.rows.find(row => {
const lastItem = row[row.length - 1];
return lastItem && typeof lastItem === 'object' && lastItem.button;
});
},
// Get number of data columns (excluding button)
dataColumnCount() {
if (this.firstRowWithButton) {
return this.firstRowWithButton.length - 1;
}
// If no button row, check first row
if (this.rows.length > 0) {
return this.rows[0].length;
}
return this.headers.length;
},
// Check if headers has button header column
hasButtonHeader() {
return this.headers.length > this.dataColumnCount;
},
// Get data headers (all headers if no button header, otherwise all except last)
displayHeaders() {
if (this.hasButtonHeader) {
return this.headers.slice(0, -1);
}
return this.headers;
},
// Get button header text
buttonHeaderText() {
if (this.hasButtonHeader) {
return this.headers[this.headers.length - 1];
}
return '';
},
// Check if button header should be shown
showButtonHeader() {
return this.hasButtonColumn && this.hasButtonHeader;
},
},
methods: {
...mapActions('conversation', ['sendMessage']),
onButtonClick(button) {
if (!button) return;
const { reply } = button;
if (reply) {
this.sendMessage({ content: reply });
}
},
// Get display cells for a row
getDisplayCells(row) {
const lastItem = row[row.length - 1];
const hasButton = lastItem && typeof lastItem === 'object' && lastItem.button;
if (hasButton) {
return row.slice(0, -1);
}
return row;
},
// Get button from row
getRowButton(row) {
const lastItem = row[row.length - 1];
if (lastItem && typeof lastItem === 'object' && lastItem.button) {
return lastItem.button;
}
return null;
},
},
};
</script>
<template>
<div class="table-message-container">
<div class="table-bubble">
<h4 v-if="title" class="table-title">
{{ title }}
</h4>
<div class="table-scroll">
<table class="table-content">
<thead>
<tr class="header-row">
<th
v-for="(header, index) in displayHeaders"
:key="'header-' + index"
class="header-cell"
>
{{ header }}
</th>
<th v-if="showButtonHeader" class="header-cell sticky-header">
{{ buttonHeaderText }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(row, rowIndex) in rows"
:key="'row-' + rowIndex"
class="data-row"
>
<td
v-for="(cell, cellIndex) in getDisplayCells(row)"
:key="'cell-' + cellIndex"
class="data-cell"
>
{{ cell }}
</td>
<td v-if="hasButtonColumn" class="data-cell sticky-cell">
<button
v-if="getRowButton(row)"
class="table-action-button"
@click="onButtonClick(getRowButton(row))"
>
{{ getRowButton(row).text }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<style scoped>
.table-message-container {
display: flex;
width: 100%;
margin-bottom: 8px;
}
.table-bubble {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.05);
border: 1px solid #e2e8f0;
width: 100%;
max-width: 100%;
overflow: hidden;
}
.dark .table-bubble {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
border-color: #475569;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.15);
}
.table-title {
font-size: 15px;
font-weight: 600;
line-height: 1.4;
color: #1e293b;
margin: 0 0 12px 0;
padding-bottom: 10px;
border-bottom: 2px solid #e2e8f0;
}
.dark .table-title {
color: #f1f5f9;
border-bottom-color: #475569;
}
.table-scroll {
overflow-x: auto;
overflow-y: hidden;
border-radius: 8px;
max-width: 100%;
}
.table-content {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: 13px;
}
.header-row {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
}
.header-cell {
padding: 10px 12px;
text-align: left;
font-weight: 600;
color: #ffffff;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.header-cell:first-child {
border-top-left-radius: 6px;
}
.sticky-header {
position: sticky;
right: 0;
background: inherit;
z-index: 10;
min-width: 100px;
text-align: center;
box-shadow: -2px 0 4px rgba(0, 0, 0, 0.1);
}
.data-row {
background-color: #ffffff;
transition: all 0.15s ease;
}
.data-row:hover {
background-color: #f1f5f9;
}
.dark .data-row {
background-color: #1e293b;
}
.dark .data-row:hover {
background-color: #334155;
}
.data-row:nth-child(even) {
background-color: #f8fafc;
}
.dark .data-row:nth-child(even) {
background-color: #0f172a;
}
.data-cell {
padding: 10px 12px;
color: #475569;
border-bottom: 1px solid #e2e8f0;
border-right: 1px solid #f1f5f9;
white-space: nowrap;
}
.dark .data-cell {
color: #cbd5e1;
border-bottom-color: #334155;
border-right-color: #1e293b;
}
.sticky-cell {
position: sticky;
right: 0;
background: inherit;
z-index: 10;
text-align: center;
padding: 8px;
box-shadow: -2px 0 4px rgba(0, 0, 0, 0.1);
}
.dark .sticky-cell {
background-color: #1e293b;
box-shadow: -2px 0 4px rgba(0, 0, 0, 0.3);
}
.data-row:hover .sticky-cell {
background-color: #f1f5f9;
}
.dark .data-row:hover .sticky-cell {
background-color: #334155;
}
.data-row:nth-child(even) .sticky-cell {
background-color: #f8fafc;
}
.dark .data-row:nth-child(even) .sticky-cell {
background-color: #0f172a;
}
.data-row:last-child .data-cell {
border-bottom: none;
}
.data-row:last-child .data-cell:first-child {
border-bottom-left-radius: 6px;
}
.data-row:last-child .sticky-cell {
border-bottom-right-radius: 6px;
border-bottom: none;
}
.table-action-button {
padding: 6px 12px;
font-size: 12px;
font-weight: 500;
color: #3b82f6;
background: transparent;
border: 1px solid #3b82f6;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.table-action-button:hover {
background: #3b82f6;
color: #ffffff;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
}
.table-action-button:active {
transform: translateY(0);
}
.dark .table-action-button {
color: #60a5fa;
border-color: #60a5fa;
}
.dark .table-action-button:hover {
background: #60a5fa;
color: #1e293b;
}
/* 响应式设计 */
@media (max-width: 480px) {
.table-bubble {
padding: 12px;
border-radius: 10px;
}
.table-title {
font-size: 14px;
margin-bottom: 10px;
}
.header-cell,
.data-cell {
padding: 8px 10px;
font-size: 12px;
}
.table-action-button {
padding: 5px 10px;
font-size: 11px;
}
}
</style>