refactor: App.vue 样式隔离,使其可作为组件被嵌入
- 全局重置(* / body)抽到 src/styles/global.css,仅独立 app 的 main.js 加载
- App.vue <style> 改 scoped,根及内部类名加 ttf- 前缀,避免与宿主冲突
- 根 class app -> ttf-app,min-height 改为填满容器以适配嵌入
- 加 defineOptions({ name: 'timetable' }) 供宿主 KeepAlive 匹配
- README 嵌入说明改为构建期组件集成 + git submodule
- 不影响独立运行:npm test 42 项通过,npm run build 通过
This commit is contained in:
23
README.md
23
README.md
@@ -122,14 +122,27 @@ sudo ufw allow 8888/tcp
|
|||||||
|
|
||||||
### 6. 嵌入主页(可选)
|
### 6. 嵌入主页(可选)
|
||||||
|
|
||||||
本项目也可作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的一个页签嵌入展示。方式是在 mainPage 所在站点的 Apache 配置中,把 `/ttf/` 反向代理到本地 8888:
|
本项目可作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的一个页签集成展示。采用**构建期组件集成 + git submodule**:
|
||||||
|
|
||||||
```apache
|
- mainPage 通过 git submodule 把本项目作为子目录(`third_party/timeTableFix`)引入,并直接 `import` 本项目的 `src/App.vue` 作为懒加载路由组件。
|
||||||
ProxyPass /ttf/ http://127.0.0.1:8888/
|
- 集成时共享 mainPage 的构建、依赖(`ical.js`)与 KeepAlive 缓存,不再是 iframe,无运行期割裂。
|
||||||
ProxyPassReverse /ttf/ http://127.0.0.1:8888/
|
- 本项目 `App.vue` 已做样式隔离(scoped + `ttf-` 前缀类名),全局重置仅在独立 app(`main.js`)加载,作为组件嵌入时不污染宿主。
|
||||||
|
|
||||||
|
**升级流程**(mainPage 侧更新本项目到新版本):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mainPage
|
||||||
|
git submodule update --remote third_party/timeTableFix # 拉取最新
|
||||||
|
cd third_party/timeTableFix && git checkout master # 固定到目标分支
|
||||||
|
cd ../..
|
||||||
|
git add third_party/timeTableFix # 更新 gitlink
|
||||||
|
git commit -m "chore: 升级 timeTableFix 子模块"
|
||||||
|
npm run build # 重新构建 mainPage
|
||||||
```
|
```
|
||||||
|
|
||||||
mainPage 的 `src/config/app.config.js` 中 `timeTableFix.pageUrl = '/ttf/'` 即指向此代理。代理时 Apache 会把 Host 改写为 `127.0.0.1:8888`(localhost),无需在本项目的 `allowedHosts` 中加 `zikai.wang`。本项目本身完全独立,移除该代理与 mainPage 页签不影响其独立运行。
|
本项目本身完全独立:脱离 mainPage 仍可独立 `npm run build` / `npm test` / systemd 部署。移除 mainPage 中的页签仅需删其 `src/modules/timetable/` 并 `git submodule deinit`,不影响本项目。
|
||||||
|
|
||||||
|
> 旧的 iframe 嵌入方式(Apache `/ttf/` 反代到 8888)仍可保留,用于经 `https://zikai.wang/ttf/` 单独访问独立 app,与组件集成互不冲突。
|
||||||
|
|
||||||
## 使用(启动 / 关闭)
|
## 使用(启动 / 关闭)
|
||||||
|
|
||||||
|
|||||||
62
src/App.vue
62
src/App.vue
@@ -1,4 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
// 组件名与 mainPage 模块 id 一致,供 <KeepAlive :include> 按 id 精确匹配。
|
||||||
|
// 作为独立 app 运行时该名不影响行为。
|
||||||
|
defineOptions({ name: 'timetable' })
|
||||||
|
|
||||||
import { useCalendar } from './composables/useCalendar.js'
|
import { useCalendar } from './composables/useCalendar.js'
|
||||||
import DropZone from './components/DropZone.vue'
|
import DropZone from './components/DropZone.vue'
|
||||||
import HeaderBar from './components/HeaderBar.vue'
|
import HeaderBar from './components/HeaderBar.vue'
|
||||||
@@ -11,11 +15,11 @@ const { isLoaded, state, setViewMode } = useCalendar()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app">
|
<div class="ttf-app">
|
||||||
<header class="app-header">
|
<header class="ttf-header">
|
||||||
<div class="header-left">
|
<div class="ttf-header-left">
|
||||||
<h1>ICS 日程整理器</h1>
|
<h1>ICS 日程整理器</h1>
|
||||||
<div v-if="isLoaded" class="view-switcher">
|
<div v-if="isLoaded" class="ttf-view-switcher">
|
||||||
<button
|
<button
|
||||||
:class="{ active: state.viewMode === 'edit' }"
|
:class="{ active: state.viewMode === 'edit' }"
|
||||||
@click="setViewMode('edit')"
|
@click="setViewMode('edit')"
|
||||||
@@ -32,11 +36,11 @@ const { isLoaded, state, setViewMode } = useCalendar()
|
|||||||
</div>
|
</div>
|
||||||
<HeaderBar />
|
<HeaderBar />
|
||||||
</header>
|
</header>
|
||||||
<main class="app-main">
|
<main class="ttf-main">
|
||||||
<DropZone v-if="!isLoaded" />
|
<DropZone v-if="!isLoaded" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<EventList v-if="state.viewMode === 'edit'" />
|
<EventList v-if="state.viewMode === 'edit'" />
|
||||||
<div class="detail-area">
|
<div class="ttf-detail-area">
|
||||||
<EventDetail v-if="state.viewMode === 'edit'" />
|
<EventDetail v-if="state.viewMode === 'edit'" />
|
||||||
<MonthPreview v-else-if="state.viewMode === 'month'" />
|
<MonthPreview v-else-if="state.viewMode === 'month'" />
|
||||||
<WeekPreview v-else-if="state.viewMode === 'week'" />
|
<WeekPreview v-else-if="state.viewMode === 'week'" />
|
||||||
@@ -46,36 +50,38 @@ const { isLoaded, state, setViewMode } = useCalendar()
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
/* scoped:样式仅作用于本组件,作为组件被嵌入时不污染宿主。
|
||||||
body {
|
全局重置(* / body)已移至 src/styles/global.css,仅独立 app 加载。 */
|
||||||
font-family: -apple-system, "Segoe UI", Roboto, "Microsoft YaHei", sans-serif;
|
.ttf-app {
|
||||||
background: #f5f6f8;
|
display: flex;
|
||||||
color: #222;
|
flex-direction: column;
|
||||||
line-height: 1.5;
|
/* 适配嵌入:填满宿主 <main> 区域;独立 app 下 body 仍提供背景 */
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
.app { min-height: 100vh; display: flex; flex-direction: column; }
|
.ttf-header {
|
||||||
.app-header {
|
|
||||||
background: #2c3e50;
|
background: #2c3e50;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 14px 24px;
|
padding: 14px 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.header-left {
|
.ttf-header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
.app-header h1 { font-size: 18px; font-weight: 600; white-space: nowrap; }
|
.ttf-header h1 { font-size: 18px; font-weight: 600; white-space: nowrap; }
|
||||||
.view-switcher {
|
.ttf-view-switcher {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #1a252f;
|
background: #1a252f;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.view-switcher button {
|
.ttf-view-switcher button {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #bdc3c7;
|
color: #bdc3c7;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -84,28 +90,28 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
}
|
}
|
||||||
.view-switcher button:hover { color: #fff; }
|
.ttf-view-switcher button:hover { color: #fff; }
|
||||||
.view-switcher button.active {
|
.ttf-view-switcher button.active {
|
||||||
background: #3498db;
|
background: #3498db;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.app-main { flex: 1; display: flex; overflow: hidden; }
|
.ttf-main { flex: 1; display: flex; overflow: hidden; min-height: 0; }
|
||||||
.detail-area { flex: 1; overflow-y: auto; }
|
.ttf-detail-area { flex: 1; overflow-y: auto; }
|
||||||
|
|
||||||
/* 移动端适配 */
|
/* 移动端适配 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.app-header {
|
.ttf-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
.header-left {
|
.ttf-header-left {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.app-header h1 { font-size: 15px; }
|
.ttf-header h1 { font-size: 15px; }
|
||||||
.view-switcher button { padding: 5px 10px; font-size: 12px; }
|
.ttf-view-switcher button { padding: 5px 10px; font-size: 12px; }
|
||||||
.app-main { flex-direction: column; }
|
.ttf-main { flex-direction: column; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import './styles/global.css'
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
createApp(App).mount('#app')
|
||||||
|
|||||||
9
src/styles/global.css
Normal file
9
src/styles/global.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* 全局重置 -- 仅独立 app(main.js)加载。
|
||||||
|
作为组件被嵌入时由宿主提供重置,本文件不参与,避免污染宿主。 */
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, "Segoe UI", Roboto, "Microsoft YaHei", sans-serif;
|
||||||
|
background: #f5f6f8;
|
||||||
|
color: #222;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user