|
|
@@ -36,6 +36,21 @@
|
|
|
<view class="floating-toolbar">
|
|
|
<view class="floating-field-name-display">{{ currentFieldName }}</view>
|
|
|
<view class="button-group">
|
|
|
+ <button
|
|
|
+ v-if="showTakePhotoButton"
|
|
|
+ class="upload-image-btn"
|
|
|
+ :disabled="!currentCell"
|
|
|
+ @click="openCamera"
|
|
|
+ >
|
|
|
+ 拍摄图片
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="special-symbol-btn"
|
|
|
+ :disabled="!currentCell"
|
|
|
+ @click="openSpecialSymbolDialog"
|
|
|
+ >
|
|
|
+ 特殊符号
|
|
|
+ </button>
|
|
|
<button
|
|
|
id="prevCellBtn"
|
|
|
class="prev-cell-btn"
|
|
|
@@ -57,14 +72,47 @@
|
|
|
<view class="floating-input-wrapper">
|
|
|
<textarea
|
|
|
id="floatingInput"
|
|
|
+ ref="floatingInputRef"
|
|
|
v-model="floatingInputValue"
|
|
|
placeholder="请选择单元格"
|
|
|
- @input="backfillValueToCell"
|
|
|
+ @blur="updateFloatingInputCursor"
|
|
|
+ @click="updateFloatingInputCursor"
|
|
|
+ @focus="updateFloatingInputCursor"
|
|
|
+ @input="handleFloatingInput"
|
|
|
+ @keyup="updateFloatingInputCursor"
|
|
|
/>
|
|
|
<button id="floatingClearBtn" class="clear-btn" @click="clearInput">×</button>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
+ <view
|
|
|
+ v-if="specialSymbolVisible"
|
|
|
+ class="dialog-overlay"
|
|
|
+ @click="specialSymbolVisible = false"
|
|
|
+ >
|
|
|
+ <view class="symbol-dialog-content" @click.stop>
|
|
|
+ <view class="symbol-dialog-header">
|
|
|
+ <text class="symbol-dialog-title">特殊符号</text>
|
|
|
+ <button class="symbol-dialog-close" @click="specialSymbolVisible = false">×</button>
|
|
|
+ </view>
|
|
|
+ <view class="symbol-groups">
|
|
|
+ <view v-for="group in specialSymbolGroups" :key="group.title" class="symbol-group">
|
|
|
+ <view class="symbol-group-title">{{ group.title }}</view>
|
|
|
+ <view class="symbol-grid">
|
|
|
+ <button
|
|
|
+ v-for="symbol in group.symbols"
|
|
|
+ :key="symbol"
|
|
|
+ class="symbol-item"
|
|
|
+ @click="insertSpecialSymbol(symbol)"
|
|
|
+ >
|
|
|
+ {{ symbol }}
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<view v-if="dialogVisible" class="dialog-overlay" @click="dialogVisible = false">
|
|
|
<view class="dialog-content" @click.stop>
|
|
|
<view class="modal-message">{{ dialogMessage }}</view>
|
|
|
@@ -126,8 +174,22 @@ const keyboardHeight = ref(0)
|
|
|
const selectedPicture = ref(null)
|
|
|
const currentFieldName = ref('-')
|
|
|
const fieldNameMapping = ref([])
|
|
|
+const showTakePhotoButton = ref(false)
|
|
|
const dialogVisible = ref(false)
|
|
|
const dialogMessage = ref('')
|
|
|
+const floatingInputRef = ref(null)
|
|
|
+const floatingInputCursor = ref(0)
|
|
|
+const specialSymbolVisible = ref(false)
|
|
|
+const specialSymbolGroups = [
|
|
|
+ {
|
|
|
+ title: '数学符号',
|
|
|
+ symbols: ['±', '×', '÷', '≈', '≠', '≤', '≥', '∞', '√', '∑', '∏', 'π', 'θ', 'α', 'β', 'γ', 'Δ', '∠', '⊥', '∥'],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '物理单位',
|
|
|
+ symbols: ['℃', '℉', 'Ω', 'μ', 'm²', 'm³', 'N', 'Pa', 'kPa', 'MPa', 'J', 'W', 'kW', 'V', 'A', 'Hz', 'kg', 'mol', 'L', 'ppm'],
|
|
|
+ },
|
|
|
+]
|
|
|
|
|
|
const designerConfig = computed(() => {
|
|
|
const config = GC.Spread.Sheets.Designer.DefaultConfig
|
|
|
@@ -331,7 +393,10 @@ function deepMergeSchemaValue(target, source) {
|
|
|
} else if (sourceValue !== undefined && sourceValue !== '') {
|
|
|
const trimmedSourceValue = sourceValue.trim()
|
|
|
// JSON字符串需要解析为对象或数组
|
|
|
- if (typeof trimmedSourceValue === 'string' && (trimmedSourceValue.startsWith("{") || trimmedSourceValue.startsWith("["))) {
|
|
|
+ if (
|
|
|
+ typeof trimmedSourceValue === 'string' &&
|
|
|
+ (trimmedSourceValue.startsWith('{') || trimmedSourceValue.startsWith('['))
|
|
|
+ ) {
|
|
|
result[key] = JSON.parse(trimmedSourceValue)
|
|
|
} else {
|
|
|
result[key] = trimmedSourceValue
|
|
|
@@ -636,9 +701,46 @@ function updateFieldNameDisplay(fieldName) {
|
|
|
|
|
|
function showFloatingInput() {
|
|
|
floatingInputValue.value = currentCellInfo.value.value
|
|
|
+ floatingInputCursor.value = floatingInputValue.value.length
|
|
|
floatingInputVisible.value = true
|
|
|
}
|
|
|
|
|
|
+function getFloatingInputElement(event) {
|
|
|
+ return event?.target || floatingInputRef.value?.$el || floatingInputRef.value || document.getElementById('floatingInput')
|
|
|
+}
|
|
|
+
|
|
|
+function updateFloatingInputCursor(event) {
|
|
|
+ const inputElement = getFloatingInputElement(event)
|
|
|
+ const cursor = event?.detail?.cursor ?? inputElement?.selectionStart
|
|
|
+ if (typeof cursor === 'number') {
|
|
|
+ floatingInputCursor.value = cursor
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleFloatingInput(event) {
|
|
|
+ updateFloatingInputCursor(event)
|
|
|
+ backfillValueToCell()
|
|
|
+}
|
|
|
+
|
|
|
+function openSpecialSymbolDialog() {
|
|
|
+ updateFloatingInputCursor()
|
|
|
+ specialSymbolVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function insertSpecialSymbol(symbol) {
|
|
|
+ const cursor = Math.min(floatingInputCursor.value, floatingInputValue.value.length)
|
|
|
+ floatingInputValue.value = `${floatingInputValue.value.slice(0, cursor)}${symbol}${floatingInputValue.value.slice(cursor)}`
|
|
|
+ floatingInputCursor.value = cursor + symbol.length
|
|
|
+ specialSymbolVisible.value = false
|
|
|
+ backfillValueToCell()
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ const inputElement = getFloatingInputElement()
|
|
|
+ inputElement?.focus?.()
|
|
|
+ inputElement?.setSelectionRange?.(floatingInputCursor.value, floatingInputCursor.value)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
function backfillValueToCell() {
|
|
|
if (!currentCell.value || !currentCellInfo.value) return
|
|
|
const { sheet, row, col } = currentCell.value
|
|
|
@@ -647,6 +749,9 @@ function backfillValueToCell() {
|
|
|
|
|
|
function clearInput() {
|
|
|
floatingInputValue.value = ''
|
|
|
+ if (!currentCell.value || !currentCellInfo.value) return
|
|
|
+ const { sheet, row, col } = currentCell.value
|
|
|
+ sheet.setValue(row, col, '')
|
|
|
}
|
|
|
|
|
|
function findNextEmptyBoundCell(direction = 'next') {
|
|
|
@@ -852,6 +957,7 @@ function handleWorkbookInitialized(spreadInstance) {
|
|
|
|
|
|
function initGenericEditorUI(uiConfig) {
|
|
|
navTitle.value = uiConfig.title
|
|
|
+ showTakePhotoButton.value = !!uiConfig.showTakePhotoButton
|
|
|
|
|
|
navButtons.value = []
|
|
|
const cancelBtn = {
|
|
|
@@ -1032,6 +1138,10 @@ function cancelEdit() {
|
|
|
emit('cancel')
|
|
|
}
|
|
|
|
|
|
+function openCamera() {
|
|
|
+ emit('openCamera')
|
|
|
+}
|
|
|
+
|
|
|
function goBack() {
|
|
|
uni.navigateBack({
|
|
|
delta: 1,
|
|
|
@@ -1205,6 +1315,35 @@ defineExpose({
|
|
|
border-color: #e6a23c;
|
|
|
}
|
|
|
|
|
|
+.special-symbol-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 60px;
|
|
|
+ height: 35px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: white;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #e6a23c;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.special-symbol-btn:hover:not(:disabled) {
|
|
|
+ background: #c87f0a;
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.special-symbol-btn:disabled {
|
|
|
+ color: #999999;
|
|
|
+ cursor: not-allowed;
|
|
|
+ background: #cccccc;
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
.floating-input-wrapper {
|
|
|
position: relative;
|
|
|
width: 100%;
|
|
|
@@ -1308,6 +1447,35 @@ defineExpose({
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
+.upload-image-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 60px;
|
|
|
+ height: 35px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: white;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #28a745;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-image-btn:hover:not(:disabled) {
|
|
|
+ background: #1e7e34;
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-image-btn:disabled {
|
|
|
+ color: #999999;
|
|
|
+ cursor: not-allowed;
|
|
|
+ background: #cccccc;
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
.prev-cell-btn {
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
@@ -1409,6 +1577,76 @@ defineExpose({
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
}
|
|
|
|
|
|
+.symbol-dialog-content {
|
|
|
+ width: min(92vw, 560px);
|
|
|
+ max-height: 70vh;
|
|
|
+ padding: 16px;
|
|
|
+ overflow-y: auto;
|
|
|
+ background-color: white;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-dialog-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-dialog-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-dialog-close {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #666;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border: none;
|
|
|
+ border-radius: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-group + .symbol-group {
|
|
|
+ margin-top: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-group-title {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-item {
|
|
|
+ height: 36px;
|
|
|
+ font-size: 15px;
|
|
|
+ color: #333;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border: 1px solid #dee2e6;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.symbol-item:hover {
|
|
|
+ color: #007aff;
|
|
|
+ background: #eef6ff;
|
|
|
+ border-color: #007aff;
|
|
|
+}
|
|
|
+
|
|
|
.modal-message {
|
|
|
font-size: 16px;
|
|
|
line-height: 1.5;
|