Skip to content

Commit

Permalink
update: change index style;
Browse files Browse the repository at this point in the history
  • Loading branch information
jasoneri committed Nov 27, 2024
1 parent 5c00ed3 commit e141bff
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 53 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@


## 📑介绍
超简单 fastapi + vue3 项目, pc本地下漫画(或任意图片目录)后用手机浏览器进行局域网阅读
简单的 fastapi + vue3 前后端分离项目, pc本地下漫画(或任意图片目录)后用手机浏览器进行局域网阅读


▼ 预览 ▼

![](doc/assets/comic_viewer.jpg)

### 内容目录树参考
必改配置`backend/conf.yml``path`的值(默认`D:\Comic`,切本就改成`D:\Comic\本子`支持运行时热更改
配置`backend/conf.yml``path`的值(默认`D:\Comic`,切本就改成`D:\Comic\本子`现可在功能菜单 > 配置处更改
```shell
D:\Comic
├── web_handle # 程序创建的操作处理目录
Expand All @@ -37,11 +37,19 @@ D:\Comic

## 📢更新

### V1.6 | 2024-09-13
### V1.6.1 | 2024-11-27

增加筛选按钮(重新加载键的右侧),<br>
能根据关键字筛出使页面仅显示包含关键字的书,按重新加载就能恢复
> mark一下:用手机edge测试时输入消息框与输入法之间有空白需要划一下去掉空白才能输入筛选成功,已反馈官方,用手机自带浏览器倒是没问题
增加`更改配置`功能<br>目前的功能以及后续新增功能都将放在`功能菜单`


## 📚功能
仅针对部分功能做扩展说明

| 📚功能项 | 扩展说明 |
|:------|:----------------------------------------------------------------------------------|
| 配置 | 打开显示的即为`backend/conf.yml`的内容,格式参照yaml<br>注:改配置会导致静态资源锚点更新,改后首次出不了图时按下`下一排序`之类的就行了 |
| 筛选 | 筛选状态下,按重新加载就能恢复未筛选状态 |
| ... | - |

## ⚡️准备/部署
### 版本
Expand Down Expand Up @@ -76,7 +84,7 @@ npm start

## 🔰其他
### kemono
配置`backend/conf.yml`中有个`kemono_path`,可观看从`CGS`脚本集下的`kemono`内容,
配置中有个`kemono_path`,可观看从`CGS`脚本集下的`kemono`内容,
[点击查看`kemono`内容目录树参考](https://github.com/jasoneri/ComicGUISpider/blob/GUI/utils/script/script.md#%E8%BF%90%E8%A1%8C%E8%BF%87%E5%90%8E%E6%89%80%E5%BE%97%E7%9B%AE%E5%BD%95%E6%A0%91), <br>
观看链接为 `你的局域网ip:端口/kemono`

Expand Down
2 changes: 2 additions & 0 deletions backend/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,6 @@ def register_hook(app: FastAPI) -> None:
@app.middleware("http")
async def logger_request(request: Request, call_next) -> Response:
response = await call_next(request)
if request.url.path.startswith("/comic/conf") and request.method == "POST" and response.status_code == 200:
app.mount("/static", StaticFiles(directory=str(conf.comic_path)), name="static")
return response
14 changes: 14 additions & 0 deletions backend/api/routes/comic.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ async def get_books(request: Request, sort: str = Query(None)):
return sorted(books, key=getattr(QuerySort, func), reverse=getattr(QuerySort, _sort).value)


class ConfContent(BaseModel):
text: str


@index_router.get("/conf")
@index_router.post("/conf")
async def duel_conf(request: Request, conf_content: ConfContent = None):
if request.method == "GET":
return conf.get_content()
else:
conf.update(conf_content.text)
return "update conf successfully"


@index_router.get("/{book_name}")
async def get_book(request: Request, book_name: str):
book_md5 = hashlib.md5(book_name.encode('utf-8')).hexdigest()
Expand Down
18 changes: 17 additions & 1 deletion backend/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@
yaml.warnings({'YAMLLoadWarning': False})


def yaml_update(_f, yaml_string):
with open(_f, 'r+', encoding='utf-8') as fp:
fp.seek(0)
fp.truncate()
fp.write(yaml_string)


class Conf:
comic_path = None
handle_path = None
file = basepath.parent.joinpath('conf.yml')

def init(self):
with open(basepath.parent.joinpath('conf.yml'), 'r', encoding='utf-8') as f:
with open(self.file, 'r', encoding='utf-8') as f:
cfg = f.read()
yml_config = yaml.load(cfg, Loader=yaml.FullLoader)
for k, v in yml_config.items():
Expand Down Expand Up @@ -42,6 +50,14 @@ def __new__(cls, *args, **kwargs):
Conf._instance.init()
return Conf._instance

@staticmethod
def get_content():
return basepath.parent.joinpath('conf.yml').read_text()

def update(self, cfg_string):
yaml_update(self.file, cfg_string)
self.init()


conf = Conf()

Expand Down
7 changes: 5 additions & 2 deletions deploy/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ def headers(self):
def check_token(self):
if not self.gitee_t_file.exists():
self.download_t_file()
with open(self.gitee_t_file, 'r', encoding='utf-8') as f:
tokens = json.load(f)
try:
with open(self.gitee_t_file, 'r', encoding='utf-8') as f:
tokens = json.load(f)
except json.decoder.JSONDecodeError:
tokens = []
for _token in tokens:
token = f"Bearer {base64.b64decode(_token).decode()}"
with httpx.Client(headers={**headers, 'Authorization': token}) as client:
Expand Down
Binary file modified doc/assets/comic_viewer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.3.4",
"element-plus": "^2.7.3",
"element-plus": "^2.8.8",
"vue": "^3.2.45",
"vue-router": "^4.0.14",
"sass-embedded": "^1.77.8"
Expand Down
97 changes: 70 additions & 27 deletions frontend/src/components/TopBtnGroup.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<el-button-group style="width: 75%; height: 70%">
<el-button-group style="width: 100%; height: 70%">
<el-button text class="switch" :class="isDark ? 'isDark-switch' : 'noDark-switch'" style="width: 20%; height: 100%" @click="toggleDark">
<el-icon v-if="isDark">
<svg viewBox="0 0 24 24">
Expand All @@ -17,42 +17,66 @@
</el-icon>
</el-button>
<el-button type="primary" :icon="RefreshRight" @click="props.reload"
style="width: 58%; height: 100%; margin: 0 auto; display: block; font-size: 15px">
style="width: 65%; height: 100%; margin: 0 auto; display: block; font-size: 15px">
重新加载</el-button>
<el-button type="success" @click="open_filter" style="width: 22%; height: 100%; margin: 0 auto; display: block; font-size: 15px">
<el-icon><Filter /></el-icon>
</el-button>
</el-button-group>

<el-select v-model="select_value" placeholder="排序" style="width: 25%;" size="large">
<el-option
v-for="item in select_options" style="height: 100%"
:key="item.value" :label="item.label" :value="item.value"
@click="emit('send_sort', item.value)"
/>
<template #footer>
<el-button v-if="!isAdding" text bg size="small" @click="onAddOption">
自定义排序
</el-button>
<template v-else>
<el-input
v-model="optionName" class="option-input" size="small"
placeholder="自定义: 'time/name'+'_'+'asc/desc'"
/>
<el-button type="primary" size="small" @click="onConfirm">confirm</el-button>
<el-button size="small" @click="clear">cancel</el-button>
<el-dropdown trigger="click" style="width: 15%;height: 100%;" placement="bottom-end" size="large">
<el-button type="success" @click="menuVisible = true" style="width: 100%;height: 100%;">
<el-icon><Menu /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="Operation" @click="showConfDialog">配置</el-dropdown-item>
<el-dropdown-item :icon="Filter" @click="open_filter">筛选</el-dropdown-item>
<el-select v-model="select_value" placeholder="排序" placement="left-start">
<el-option
v-for="item in select_options" style="height: 100%" :icon="Sort"
:key="item.value" :label="item.label" :value="item.value"
@click="emit('send_sort', item.value)"
/>
<template #footer>
<el-button v-if="!isAdding" text bg size="small" @click="onAddOption">
自定义排序
</el-button>
<template v-else>
<el-input
v-model="optionName" class="option-input" size="small"
placeholder="自定义: 'time/name'+'_'+'asc/desc'"
/>
<el-button type="primary" size="small" @click="onConfirm">confirm</el-button>
<el-button size="small" @click="clear">cancel</el-button>
</template>
</template>
</el-select>
</el-dropdown-menu>
</template>
</template>
</el-select>
</el-dropdown>
</el-button-group>

<el-dialog
v-model="dialogVisible" title="修改配置" width="80%"
align-center center
>
<el-input type="textarea" v-model="confText"
:rows="5" :input-style='"white-space: nowrap;overflow-x: auto"'/>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfDialog(confText)">提交修改</el-button>
</div>
</template>

</el-dialog>
</template>

<script setup>
import {ref} from 'vue'
import {Filter, RefreshRight} from "@element-plus/icons-vue";
import {Filter, RefreshRight, Menu, Sort, Operation} from "@element-plus/icons-vue";
import {ElMessageBox} from 'element-plus'
const props = defineProps({
reload:{type: Function, required: true},
handleConf:{type: Function, required: false},
items: {type: Object, required: false}, filteredItems: {type: Object, required: false}
})
const isDark = ref(true)
Expand All @@ -70,7 +94,9 @@ const toggleDark = () => {
}
}
const dialogVisible = ref(false);
const isAdding = ref(false)
const confText = ref('')
const optionName = ref('')
const select_value = ref([])
const select_options = ref([
Expand All @@ -88,9 +114,26 @@ const open_filter = () => {
props.filteredItems.arr = props.items.arr.filter(item => item.book_name.includes(value))
})
.catch(( v ) => {
debugger;
// debugger;
})
}
const showConfDialog = () => {
function callBack(data){
confText.value = data
dialogVisible.value = true
}
if (props.handleConf) {
props.handleConf(callBack);
}
}
const handleConfDialog = (confText_) => {
if (confText_) {
props.handleConf(confText_);
dialogVisible.value = false;
} else {
this.$message.error('请输入内容');
}
}
const onAddOption = () => {
isAdding.value = true
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/topBottom.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<el-icon><ArrowUpBold /></el-icon>
</div>
</el-backtop>
<el-backtop :right="60" :bottom="60" visibility-height=0 @click="scrollToBottom" >
<el-backtop :right="60" :bottom="60" :visibility-height="0" @click="scrollToBottom" >
<div style="height: 100%; width: 100%; text-align: center; line-height: 43px; color: #00f5e1;">
<el-icon><ArrowDownBold /></el-icon>
</div>
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/static/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ export let indexPage = ref(1)
export const filteredBookList = reactive({arr: []})
export let sortVal = ref("")
export let pageSize = 20
export const kemonoArtistsList = reactive({arr: []})
export const kemonoBookList = reactive({arr: []})
export const kemonoData = {
ArtistsList: reactive({arr: []}),
BookList: reactive({arr: []})
}
33 changes: 32 additions & 1 deletion frontend/src/view/comic.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<template>
<el-container>
<el-header>
<TopBtnGroup :reload="reload" :items="bookList" :filtered-items="filteredBookList" @send_sort="sv_sort"/>
<TopBtnGroup :reload="reload" :items="bookList" :filtered-items="filteredBookList" :handle-conf="handleConf"
@send_sort="sv_sort"/>
</el-header>
<el-main>
<el-scrollbar>
Expand Down Expand Up @@ -71,6 +72,36 @@
const end = start + pageSize;
return filteredBookList.arr.slice(start, end);
});
const handleConf = async(param) => {
if (typeof param === "function") {
await axios.get(backend + '/comic/conf/')
.then(res => {param(res.data);})
.catch(function (error) {console.log(error);})
} else if (typeof param === "string") {
let body = {text: param};
await axios.post(backend + '/comic/conf/', body)
.then(res => {
reload();
ElNotification.success({
title: '配置更改已成功',
message: h('i', { style: 'white-space: pre-wrap; word-wrap: break-word;' }, `改配置后端会对静态资源锚点进行更新\n切换后点第一遍点书读图会404,再点下书就好了\n(目前未解,操作多了一点点而已)`),
offset: 100,
duration: 7000
})
})
.catch(
function (error) {
ElNotification.error({
title: 'Error',
message: '处理配置发生错误,自行去终端窗口查看报错堆栈',
offset: 100,
})
}
)
} else {
console.log("handleConf-param type = " + typeof param);
}
}
// ------------------------渲染相关
const init = () => {
getBooks(callBack)
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/view/kemono.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<script setup>
import {computed} from "vue";
import axios from "axios";
import {backend,kemonoArtistsList,sortVal} from "@/static/store.js";
import {backend,kemonoData,sortVal} from "@/static/store.js";
import topBottom from '@/components/topBottom.vue'
import TopBtnGroup from '@/components/TopBtnGroup.vue'
Expand All @@ -43,13 +43,13 @@
})
}
const pagedArtists = computed(() => {
return kemonoArtistsList.arr;
return kemonoData.ArtistsList.arr;
});
// ------------------------渲染相关
const init = () => {
getArtists(callBack)
function callBack(data){
kemonoArtistsList.arr = data
kemonoData.ArtistsList.arr = data
}
}
init()
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/view/kemono_book.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</template>

<script setup>
import {backend,kemonoBookList} from '@/static/store.js'
import {backend,kemonoData} from '@/static/store.js'
import axios from 'axios'
import {useRoute,useRouter} from 'vue-router'
import {reactive,markRaw,computed} from "vue";
Expand All @@ -50,7 +50,7 @@
})
}
const bookIndex = computed(() => {
return kemonoBookList.arr.findIndex(item => item.book === route.query.book)
return kemonoData.BookList.arr.findIndex(item => item.book === route.query.book)
});
const init = (_book) => {
getBook(_book, callBack)
Expand All @@ -64,8 +64,8 @@
router.replace({path:'kemono_book',query:{u_s: u_s, book:_book}})
init(_book)
}
function previousBook(){triggerInit(kemonoBookList.arr[bookIndex.value-1].book)}
function nextBook(){triggerInit(kemonoBookList.arr[bookIndex.value+1].book)}
function previousBook(){triggerInit(kemonoData.BookList.arr[bookIndex.value-1].book)}
function nextBook(){triggerInit(kemonoData.BookList.arr[bookIndex.value+1].book)}
function retainCallBack(done, path) {MsgOpen(done, Finished, path)}
function removeCallBack(done, path) {MsgOpen(done, Warning, path)}
Expand Down
Loading

0 comments on commit e141bff

Please sign in to comment.