1
This commit is contained in:
parent
b1728bbc47
commit
bbcd07a595
@ -18,7 +18,7 @@ import {
|
|||||||
} from '@/lib/workspace'
|
} from '@/lib/workspace'
|
||||||
import { collectActiveProjectSessionLocks, initProjectSessionLock } from '@/lib/projectSessionLock'
|
import { collectActiveProjectSessionLocks, initProjectSessionLock } from '@/lib/projectSessionLock'
|
||||||
import { listenProjectDeleted, listenResetAll } from '@/lib/projectEvents'
|
import { listenProjectDeleted, listenResetAll } from '@/lib/projectEvents'
|
||||||
import { createProject, listProjects, type ProjectMeta } from '@/lib/projectRegistry'
|
import { listProjects, type ProjectMeta } from '@/lib/projectRegistry'
|
||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -130,9 +130,8 @@ const openProjectInNewTab = (projectId: string, options?: { newProject?: boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createProjectAndOpen = () => {
|
const createProjectAndOpen = () => {
|
||||||
const project = createProject(t('xmInfo.defaultProjectName'))
|
|
||||||
refreshConflictProjectList()
|
refreshConflictProjectList()
|
||||||
openProjectInNewTab(project.id, { newProject: true })
|
openProjectInNewTab(DEFAULT_PROJECT_ID, { newProject: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncRouteRequestFlags = () => {
|
const syncRouteRequestFlags = () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@ -47,6 +47,7 @@ import {
|
|||||||
} from '@/lib/workspace'
|
} from '@/lib/workspace'
|
||||||
import { createProject, listProjects, upsertProject } from '@/lib/projectRegistry'
|
import { createProject, listProjects, upsertProject } from '@/lib/projectRegistry'
|
||||||
import { createProjectKvAdapter } from '@/lib/projectKvStore'
|
import { createProjectKvAdapter } from '@/lib/projectKvStore'
|
||||||
|
import { collectActiveProjectSessionLocks } from '@/lib/projectSessionLock'
|
||||||
|
|
||||||
interface QuickProjectInfoState {
|
interface QuickProjectInfoState {
|
||||||
projectIndustry?: string
|
projectIndustry?: string
|
||||||
@ -75,7 +76,6 @@ interface ProjectInfoState {
|
|||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||||
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||||
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||||
const PROJECT_INIT_CHANGED_EVENT = 'xm-project-init-changed'
|
|
||||||
const getActiveProjectId = () => readCurrentProjectId()
|
const getActiveProjectId = () => readCurrentProjectId()
|
||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
@ -96,6 +96,8 @@ const existingProjectDialogOpen = ref(false)
|
|||||||
const existingProjects = ref<Array<{ id: string; name: string; updatedAt: string }>>([])
|
const existingProjects = ref<Array<{ id: string; name: string; updatedAt: string }>>([])
|
||||||
const existingProjectLoading = ref(false)
|
const existingProjectLoading = ref(false)
|
||||||
const hasExistingProjects = ref(false)
|
const hasExistingProjects = ref(false)
|
||||||
|
const openedProjectIds = ref<string[]>([])
|
||||||
|
let existingProjectPollTimer: ReturnType<typeof setInterval> | null = null
|
||||||
const projectIndustryLabel = computed(() => {
|
const projectIndustryLabel = computed(() => {
|
||||||
const target = String(projectIndustry.value || '').trim()
|
const target = String(projectIndustry.value || '').trim()
|
||||||
if (!target) return ''
|
if (!target) return ''
|
||||||
@ -175,8 +177,19 @@ const openProjectCalc = async () => {
|
|||||||
projectDialogOpen.value = true
|
projectDialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshExistingProjects = async () => {
|
const syncExistingProjectOpenedState = (projectIds: string[]) => {
|
||||||
|
openedProjectIds.value = Array.from(collectActiveProjectSessionLocks(projectIds))
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExistingProjectOpened = (projectIdRaw: string) => {
|
||||||
|
const projectId = String(projectIdRaw || '').trim()
|
||||||
|
return projectId ? openedProjectIds.value.includes(projectId) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshExistingProjects = async (options?: { showLoading?: boolean }) => {
|
||||||
|
if (options?.showLoading !== false) {
|
||||||
existingProjectLoading.value = true
|
existingProjectLoading.value = true
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const projects = listProjects()
|
const projects = listProjects()
|
||||||
.filter(item => item.id !== QUICK_PROJECT_ID)
|
.filter(item => item.id !== QUICK_PROJECT_ID)
|
||||||
@ -191,23 +204,42 @@ const refreshExistingProjects = async () => {
|
|||||||
updatedAt: project.updatedAt
|
updatedAt: project.updatedAt
|
||||||
}))
|
}))
|
||||||
hasExistingProjects.value = projects.length > 0
|
hasExistingProjects.value = projects.length > 0
|
||||||
|
syncExistingProjectOpenedState(projects.map(project => project.id))
|
||||||
} finally {
|
} finally {
|
||||||
|
if (options?.showLoading !== false) {
|
||||||
existingProjectLoading.value = false
|
existingProjectLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopExistingProjectPolling = () => {
|
||||||
|
if (existingProjectPollTimer == null) return
|
||||||
|
clearInterval(existingProjectPollTimer)
|
||||||
|
existingProjectPollTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const startExistingProjectPolling = () => {
|
||||||
|
stopExistingProjectPolling()
|
||||||
|
existingProjectPollTimer = setInterval(() => {
|
||||||
|
if (!existingProjectDialogOpen.value) return
|
||||||
|
void refreshExistingProjects({ showLoading: false })
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
const openExistingProjectDialog = async () => {
|
const openExistingProjectDialog = async () => {
|
||||||
existingProjectDialogOpen.value = true
|
existingProjectDialogOpen.value = true
|
||||||
await refreshExistingProjects()
|
await refreshExistingProjects()
|
||||||
|
startExistingProjectPolling()
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeExistingProjectDialog = () => {
|
const closeExistingProjectDialog = () => {
|
||||||
existingProjectDialogOpen.value = false
|
existingProjectDialogOpen.value = false
|
||||||
|
stopExistingProjectPolling()
|
||||||
}
|
}
|
||||||
|
|
||||||
const enterExistingProject = (projectIdRaw: string) => {
|
const enterExistingProject = (projectIdRaw: string) => {
|
||||||
const projectId = String(projectIdRaw || '').trim()
|
const projectId = String(projectIdRaw || '').trim()
|
||||||
if (!projectId) return
|
if (!projectId || isExistingProjectOpened(projectId)) return
|
||||||
upsertProject(projectId, resolveProjectRegistryName(projectId))
|
upsertProject(projectId, resolveProjectRegistryName(projectId))
|
||||||
if (!navigateToWorkspace(projectId, 'project')) return
|
if (!navigateToWorkspace(projectId, 'project')) return
|
||||||
tabStore.enterWorkspace({
|
tabStore.enterWorkspace({
|
||||||
@ -229,8 +261,6 @@ const confirmProjectCalc = async () => {
|
|||||||
|
|
||||||
projectSubmitting.value = true
|
projectSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
const activeProjectId = getActiveProjectId()
|
|
||||||
if (activeProjectId === DEFAULT_PROJECT_ID) {
|
|
||||||
const project = createProject(t('xmInfo.defaultProjectName'))
|
const project = createProject(t('xmInfo.defaultProjectName'))
|
||||||
const kvAdapter = createProjectKvAdapter(project.id)
|
const kvAdapter = createProjectKvAdapter(project.id)
|
||||||
await kvAdapter.setItem<ProjectInfoState>(PROJECT_INFO_KEY, {
|
await kvAdapter.setItem<ProjectInfoState>(PROJECT_INFO_KEY, {
|
||||||
@ -249,24 +279,6 @@ const confirmProjectCalc = async () => {
|
|||||||
)
|
)
|
||||||
writeWorkspaceMode('project')
|
writeWorkspaceMode('project')
|
||||||
window.location.href = buildProjectUrl(project.id, { forceHome: false, newProject: false })
|
window.location.href = buildProjectUrl(project.id, { forceHome: false, newProject: false })
|
||||||
return
|
|
||||||
}
|
|
||||||
await kvStore.setItem<ProjectInfoState>(PROJECT_INFO_KEY, {
|
|
||||||
projectIndustry: industry,
|
|
||||||
projectName: t('xmInfo.defaultProjectName'),
|
|
||||||
preparedBy: '',
|
|
||||||
reviewedBy: '',
|
|
||||||
preparedCompany: '',
|
|
||||||
preparedDate: getTodayDateString()
|
|
||||||
})
|
|
||||||
await initializeProjectFactorStates(
|
|
||||||
kvStore,
|
|
||||||
industry,
|
|
||||||
PROJECT_CONSULT_CATEGORY_FACTOR_KEY,
|
|
||||||
PROJECT_MAJOR_FACTOR_KEY
|
|
||||||
)
|
|
||||||
window.dispatchEvent(new CustomEvent<boolean>(PROJECT_INIT_CHANGED_EVENT, { detail: true }))
|
|
||||||
enterProjectCalc()
|
|
||||||
} finally {
|
} finally {
|
||||||
projectSubmitting.value = false
|
projectSubmitting.value = false
|
||||||
projectDialogOpen.value = false
|
projectDialogOpen.value = false
|
||||||
@ -369,10 +381,22 @@ const confirmHomeImport = async () => {
|
|||||||
cancelHomeImportConfirm()
|
cancelHomeImportConfirm()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleHomeWindowFocus = () => {
|
||||||
|
if (!existingProjectDialogOpen.value) return
|
||||||
|
void refreshExistingProjects({ showLoading: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleHomeVisibilityChange = () => {
|
||||||
|
if (document.visibilityState !== 'visible') return
|
||||||
|
handleHomeWindowFocus()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void refreshExistingProjects()
|
void refreshExistingProjects()
|
||||||
void loadProjectDefaults()
|
void loadProjectDefaults()
|
||||||
void loadQuickDefaults()
|
void loadQuickDefaults()
|
||||||
|
window.addEventListener('focus', handleHomeWindowFocus)
|
||||||
|
document.addEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||||
try {
|
try {
|
||||||
const url = new URL(window.location.href)
|
const url = new URL(window.location.href)
|
||||||
const isNewProject = url.searchParams.get(NEW_PROJECT_QUERY_KEY) === '1'
|
const isNewProject = url.searchParams.get(NEW_PROJECT_QUERY_KEY) === '1'
|
||||||
@ -392,6 +416,12 @@ onMounted(() => {
|
|||||||
// ignore url parsing errors
|
// ignore url parsing errors
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopExistingProjectPolling()
|
||||||
|
window.removeEventListener('focus', handleHomeWindowFocus)
|
||||||
|
document.removeEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -580,11 +610,20 @@ onMounted(() => {
|
|||||||
v-for="project in existingProjects"
|
v-for="project in existingProjects"
|
||||||
:key="project.id"
|
:key="project.id"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full cursor-pointer items-center justify-between rounded-lg border border-slate-200 px-3 py-2 text-left transition hover:border-slate-300 hover:bg-slate-50"
|
:disabled="isExistingProjectOpened(project.id)"
|
||||||
|
class="flex w-full items-center justify-between rounded-lg border border-slate-200 px-3 py-2 text-left transition"
|
||||||
|
:class="isExistingProjectOpened(project.id)
|
||||||
|
? 'cursor-not-allowed border-slate-200 bg-slate-100/80 opacity-70'
|
||||||
|
: 'cursor-pointer hover:border-slate-300 hover:bg-slate-50'"
|
||||||
@click="enterExistingProject(project.id)"
|
@click="enterExistingProject(project.id)"
|
||||||
>
|
>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="truncate text-sm font-medium text-slate-800">{{ project.name }}</div>
|
<div class="truncate text-sm font-medium text-slate-800">
|
||||||
|
{{ project.name }}
|
||||||
|
<span v-if="isExistingProjectOpened(project.id)" class="ml-1 text-xs text-slate-500">
|
||||||
|
{{ t('tab.toolbar.opened') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="mt-0.5 text-xs text-slate-500">{{ project.id }}</div>
|
<div class="mt-0.5 text-xs text-slate-500">{{ project.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shrink-0 pl-2 text-xs text-slate-500">
|
<div class="shrink-0 pl-2 text-xs text-slate-500">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onActivated, onMounted, ref, watch } from 'vue'
|
import { computed, onActivated, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { Check, ChevronDown, Circle, CircleDot } from 'lucide-vue-next'
|
import { Circle, CircleDot } from 'lucide-vue-next'
|
||||||
import {
|
import {
|
||||||
getQuickCalcGroups,
|
getQuickCalcGroups,
|
||||||
getMajorDictItemById,
|
getMajorDictItemById,
|
||||||
@ -17,18 +17,6 @@ import { getIndustryMajorEntry } from '@/lib/pricingScaleCalc'
|
|||||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
import { QUICK_PROJECT_INFO_KEY } from '@/lib/workspace'
|
import { QUICK_PROJECT_INFO_KEY } from '@/lib/workspace'
|
||||||
import { initializeProjectFactorStates } from '@/lib/projectWorkspace'
|
import { initializeProjectFactorStates } from '@/lib/projectWorkspace'
|
||||||
import {
|
|
||||||
SelectContent,
|
|
||||||
SelectIcon,
|
|
||||||
SelectItem,
|
|
||||||
SelectItemIndicator,
|
|
||||||
SelectItemText,
|
|
||||||
SelectPortal,
|
|
||||||
SelectRoot,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
SelectViewport
|
|
||||||
} from 'reka-ui'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
contractId: string
|
contractId: string
|
||||||
@ -140,6 +128,15 @@ const industryLabel = computed(() => {
|
|||||||
return getIndustryDisplayName(target, locale.value) || t('quickCalc.notSelected')
|
return getIndustryDisplayName(target, locale.value) || t('quickCalc.notSelected')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isIndustrySelected = (industryId: string | number) => {
|
||||||
|
return projectIndustry.value.trim() === String(industryId).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleIndustrySelect = (industryId: string | number) => {
|
||||||
|
const nextIndustry = String(industryId).trim()
|
||||||
|
projectIndustry.value = isIndustrySelected(nextIndustry) ? '' : nextIndustry
|
||||||
|
}
|
||||||
|
|
||||||
const selectedConsultOption = computed(() =>
|
const selectedConsultOption = computed(() =>
|
||||||
quickCalcGroups
|
quickCalcGroups
|
||||||
.find(item => item.key === 'consult')
|
.find(item => item.key === 'consult')
|
||||||
@ -446,41 +443,44 @@ watch(canUseLandScale, enabled => {
|
|||||||
|
|
||||||
|
|
||||||
<div class="quick-calc-toolbar">
|
<div class="quick-calc-toolbar">
|
||||||
<label class="quick-calc-toolbar__field">
|
<label class="quick-calc-toolbar__field quick-calc-toolbar__field--cards">
|
||||||
<span class="quick-calc-field__label">{{ t('quickCalc.fields.industry') }}</span>
|
<span class="quick-calc-field__label">{{ t('quickCalc.fields.industry') }}</span>
|
||||||
<SelectRoot v-model="projectIndustry">
|
<div class="quick-calc-industry-grid" role="radiogroup" :aria-label="t('quickCalc.fields.industry')">
|
||||||
<SelectTrigger class="quick-calc-toolbar__trigger">
|
<label
|
||||||
<SelectValue :placeholder="t('quickCalc.selectIndustry')" />
|
|
||||||
<SelectIcon as-child>
|
|
||||||
<ChevronDown class="h-4 w-4 text-[var(--qc-muted)]" />
|
|
||||||
</SelectIcon>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectPortal>
|
|
||||||
<SelectContent
|
|
||||||
:side-offset="6"
|
|
||||||
position="popper"
|
|
||||||
class="z-[120] w-[var(--reka-select-trigger-width)] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-xl"
|
|
||||||
>
|
|
||||||
<SelectViewport class="p-1">
|
|
||||||
<SelectItem
|
|
||||||
v-for="item in industryTypeList"
|
v-for="item in industryTypeList"
|
||||||
:key="`quick-workbench-${item.id}`"
|
:key="`quick-workbench-${item.id}`"
|
||||||
:value="String(item.id)"
|
class="quick-calc-industry-card"
|
||||||
class="relative flex h-9 w-full cursor-default select-none items-center rounded-md pl-3 pr-8 text-sm outline-none data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[state=checked]:bg-slate-100"
|
:class="{ 'is-selected': isIndustrySelected(item.id) }"
|
||||||
|
:aria-checked="isIndustrySelected(item.id)"
|
||||||
|
role="radio"
|
||||||
|
tabindex="0"
|
||||||
|
@click.prevent="handleIndustrySelect(item.id)"
|
||||||
|
@keydown.enter.prevent="handleIndustrySelect(item.id)"
|
||||||
|
@keydown.space.prevent="handleIndustrySelect(item.id)"
|
||||||
>
|
>
|
||||||
<SelectItemText>{{ getIndustryDisplayName(item.id, locale) }}</SelectItemText>
|
<input
|
||||||
<SelectItemIndicator class="absolute right-2 inline-flex items-center text-slate-700">
|
:checked="isIndustrySelected(item.id)"
|
||||||
<Check class="h-4 w-4" />
|
type="radio"
|
||||||
</SelectItemIndicator>
|
name="quick-calc-industry-choice"
|
||||||
</SelectItem>
|
class="quick-calc-option__input"
|
||||||
</SelectViewport>
|
tabindex="-1"
|
||||||
</SelectContent>
|
>
|
||||||
</SelectPortal>
|
<span
|
||||||
</SelectRoot>
|
class="quick-calc-industry-card__icon"
|
||||||
|
:class="{ 'is-selected': isIndustrySelected(item.id) }"
|
||||||
|
>
|
||||||
|
<CircleDot v-if="isIndustrySelected(item.id)" class="h-3.5 w-3.5" />
|
||||||
|
<Circle v-else class="h-3.5 w-3.5" />
|
||||||
|
</span>
|
||||||
|
<span class="quick-calc-industry-card__text">
|
||||||
|
{{ getIndustryDisplayName(item.id, locale) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="quick-calc-toolbar__meta">
|
<span class="quick-calc-toolbar__meta">
|
||||||
{{ industrySaving ? t('quickCalc.saving') : hasSelectedIndustry ? t('quickCalc.synced') : t('quickCalc.notSelectedIndustry') }}
|
{{ industrySaving ? t('quickCalc.saving') : industryLabel }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -739,30 +739,76 @@ watch(canUseLandScale, enabled => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-toolbar__trigger {
|
.quick-calc-toolbar__field--cards {
|
||||||
display: inline-flex;
|
align-content: start;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 42px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border: 1px solid var(--qc-border);
|
|
||||||
border-radius: 12px;
|
|
||||||
background: color-mix(in srgb, white 55%, var(--qc-surface));
|
|
||||||
color: var(--qc-text);
|
|
||||||
box-shadow: inset 0 1px 0 color-mix(in srgb, white 80%, transparent);
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-toolbar__trigger:focus-visible {
|
.quick-calc-industry-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(136px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid var(--qc-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: color-mix(in srgb, var(--background) 92%, var(--card));
|
||||||
|
color: var(--qc-text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
border-color: var(--qc-border-strong);
|
border-color: var(--qc-border-strong);
|
||||||
box-shadow: 0 0 0 3px color-mix(in srgb, hsl(var(--destructive)) 14%, transparent);
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: color-mix(in srgb, hsl(var(--destructive)) 42%, var(--qc-border));
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in srgb, hsl(var(--destructive)) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card.is-selected {
|
||||||
|
border-color: color-mix(in srgb, hsl(var(--destructive)) 42%, var(--qc-border));
|
||||||
|
background: color-mix(in srgb, hsl(var(--destructive)) 7%, white);
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in srgb, hsl(var(--destructive)) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex: 0 0 14px;
|
||||||
|
color: color-mix(in srgb, var(--foreground) 44%, transparent);
|
||||||
|
transition: color 140ms ease, transform 140ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card__icon.is-selected {
|
||||||
|
color: hsl(var(--destructive));
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-industry-card__text {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-toolbar__meta {
|
.quick-calc-toolbar__meta {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--qc-muted);
|
color: var(--qc-muted);
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-empty-state {
|
.quick-calc-empty-state {
|
||||||
@ -1100,22 +1146,23 @@ watch(canUseLandScale, enabled => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-panel__title {
|
.quick-calc-panel--form .quick-calc-panel__title {
|
||||||
font-size: clamp(0.88rem, 0.94vw, 1rem);
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-panel__eyebrow {
|
.quick-calc-panel--form .quick-calc-panel__eyebrow {
|
||||||
font-size: 9px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-status__item {
|
.quick-calc-panel--form .quick-calc-status__item {
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
font-size: 9px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form {
|
.quick-calc-panel--form .quick-calc-form {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form-stack {
|
.quick-calc-panel--form .quick-calc-form-stack {
|
||||||
@ -1166,11 +1213,13 @@ watch(canUseLandScale, enabled => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form-section__eyebrow {
|
.quick-calc-panel--form .quick-calc-form-section__eyebrow {
|
||||||
font-size: 9px;
|
font-size: 14px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form-section__title {
|
.quick-calc-panel--form .quick-calc-form-section__title {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-form-grid {
|
.quick-calc-form-grid {
|
||||||
@ -1222,14 +1271,17 @@ watch(canUseLandScale, enabled => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-field__label {
|
.quick-calc-panel--form .quick-calc-field__label {
|
||||||
font-size: 9px;
|
font-size: 14px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: none;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-field__input,
|
.quick-calc-panel--form .quick-calc-field__input,
|
||||||
.quick-calc-panel--form .quick-calc-field__readonly {
|
.quick-calc-panel--form .quick-calc-field__readonly {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1288,8 +1340,8 @@ watch(canUseLandScale, enabled => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form-hint {
|
.quick-calc-panel--form .quick-calc-form-hint {
|
||||||
font-size: 9px;
|
font-size: 14px;
|
||||||
line-height: 1.15;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-segment {
|
.quick-calc-segment {
|
||||||
@ -1325,6 +1377,15 @@ watch(canUseLandScale, enabled => {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-calc-toolbar {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-toolbar__meta {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.quick-calc-layout {
|
.quick-calc-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,8 +67,8 @@ export const zhCN = {
|
|||||||
projectList: '项目列表',
|
projectList: '项目列表',
|
||||||
projectCount: '项目数量:{count}',
|
projectCount: '项目数量:{count}',
|
||||||
createProject: '新建项目',
|
createProject: '新建项目',
|
||||||
backHome: '返回首页',
|
backHome: '返回入口',
|
||||||
resetAll: '重置全部项目',
|
resetAll: '清除全部项目',
|
||||||
opened: '(已打开)',
|
opened: '(已打开)',
|
||||||
lastEdited: '最后编辑:{time}'
|
lastEdited: '最后编辑:{time}'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1169,11 +1169,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
|
|
||||||
const projectScale = projectScaleSource.roughCalcEnabled ? [] : toExportScaleRows(projectScaleSource.detailRows)
|
const projectScale = projectScaleSource.roughCalcEnabled ? [] : toExportScaleRows(projectScaleSource.detailRows)
|
||||||
const projectScaleCost = toFiniteNumber(projectScaleSource.totalAmount) ?? sumNumbers(projectScale.map(item => item.cost))
|
const projectScaleCost = toFiniteNumber(projectScaleSource.totalAmount) ?? sumNumbers(projectScale.map(item => item.cost))
|
||||||
projectScale.push({
|
|
||||||
majorid: -1,
|
|
||||||
major: -1, cost: projectScaleCost,
|
|
||||||
area: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const projectServiceCoes = buildProjectServiceCoes(consultCategoryFactorState.resolved?.detailRows)
|
const projectServiceCoes = buildProjectServiceCoes(consultCategoryFactorState.resolved?.detailRows)
|
||||||
const projectMajorCoes = buildProjectMajorCoes(majorFactorState.resolved?.detailRows)
|
const projectMajorCoes = buildProjectMajorCoes(majorFactorState.resolved?.detailRows)
|
||||||
@ -1374,11 +1370,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
const reserveFee = toMoney(reserve ? reserve.fee : 0)
|
const reserveFee = toMoney(reserve ? reserve.fee : 0)
|
||||||
const contractFee = toMoney(addNumbers(serviceFee, addtionalFee, reserveFee))
|
const contractFee = toMoney(addNumbers(serviceFee, addtionalFee, reserveFee))
|
||||||
const contractScale = htInfoRaw?.roughCalcEnabled ? [] : toExportScaleRows(htInfoRaw?.detailRows)
|
const contractScale = htInfoRaw?.roughCalcEnabled ? [] : toExportScaleRows(htInfoRaw?.detailRows)
|
||||||
contractScale.push({
|
|
||||||
majorid: -1,
|
|
||||||
major: -1, cost: contractFee,
|
|
||||||
area: null
|
|
||||||
})
|
|
||||||
const contractServiceCoesRaw = buildProjectServiceCoes(htConsultCategoryFactorState.resolved?.detailRows)
|
const contractServiceCoesRaw = buildProjectServiceCoes(htConsultCategoryFactorState.resolved?.detailRows)
|
||||||
const contractMajorCoesRaw = buildProjectMajorCoes(htMajorFactorState.resolved?.detailRows)
|
const contractMajorCoesRaw = buildProjectMajorCoes(htMajorFactorState.resolved?.detailRows)
|
||||||
console.log('[export][contract factor rows]', {
|
console.log('[export][contract factor rows]', {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user