refactor: 优化样式与打包

This commit is contained in:
AfyerCu 2025-08-16 20:12:10 +08:00
parent 87588007e0
commit a9e2b8e120
17 changed files with 156 additions and 402 deletions

2
.gitignore vendored
View File

@ -29,6 +29,8 @@ docs/.vitepress/cache
src/types
unpackage
# lock 文件还是不要了,我主要的版本写死就好了
# pnpm-lock.yaml
# package-lock.json

4
env/.env vendored
View File

@ -1,7 +1,7 @@
VITE_APP_TITLE = 'unibest'
VITE_APP_TITLE = '象乐学老师端'
VITE_APP_PORT = 9000
VITE_UNI_APPID = '__UNI__D1E5001'
VITE_UNI_APPID = '__UNI__2890BA7'
VITE_WX_APPID = 'wxa2abb91f64032a2b'
# h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base

View File

@ -44,7 +44,7 @@ const questionPickerColumns = computed(() => [
<template>
<!-- 横屏布局 -->
<view v-if="isLandscape" class="h-screen flex flex-col bg-gray-100">
<view v-if="isLandscape" class="h-0 w-100vw flex flex-grow flex-col bg-gray-100">
<!-- 顶部导航栏 -->
<view class="h-48px flex items-center border-b bg-white px-16px shadow-sm">
<!-- 左侧返回按钮 -->
@ -132,7 +132,7 @@ const questionPickerColumns = computed(() => [
</view>
<!-- 竖屏布局 -->
<view v-else class="h-screen flex flex-col bg-gray-100">
<view v-else class="h-0 w-100vw flex flex-grow flex-col bg-gray-100">
<!-- 顶部导航栏 -->
<view class="h-12 flex items-center bg-white px-4 py-2 shadow-sm">
<!-- 左侧 -->

View File

@ -73,8 +73,8 @@ function syncFormData(data: ProblemSheetData) {
//
function start() {
ProblemPromise.start()
showDialog.value = true
return ProblemPromise.start()
}
defineExpose({
@ -112,11 +112,12 @@ defineExpose({
>
<wd-cell-group border>
<wd-picker
v-if="problemTypeOptions.length > 0"
v-model="formData.problemType"
label="问题卷类型"
label-width="100px"
placeholder="请选择问题卷类型"
:options="problemTypeOptions"
:columns="problemTypeOptions"
root-portal
/>
<wd-input

View File

@ -69,7 +69,6 @@ function handleMarkingChange(questionIndex: number, imageIndex: number, data: Ko
<style scoped>
.multi-question-renderer {
width: 100%;
height: 100%;
overflow: auto;
padding: 8rpx 16rpx;
padding-bottom: 96rpx;

View File

@ -50,6 +50,9 @@ const toolbarRef = ref<HTMLElement>()
// 使 SessionStorage
const position = useSessionStorage('marking-toolbar-position', { x: 16, y: 16 })
//
const isCollapsed = useSessionStorage('marking-toolbar-collapsed', false)
//
const isDragging = ref(false)
const dragState = ref({
@ -74,19 +77,19 @@ const pendingMarkType = ref<'correct' | 'wrong' | 'half' | null>(null)
//
const baseButtonClass = computed(() => {
return props.isLandscape
? 'size-32rpx !p-0 rounded flex items-center justify-center transition-colors after:border-none'
: 'size-8 !p-0 rounded flex items-center justify-center transition-colors after:border-none'
? 'size-32rpx lg:size-8 !p-0 rounded-lg flex items-center justify-center transition-all duration-200 ease-in-out shadow-sm hover:shadow-md active:scale-95 after:border-none'
: 'size-8 lg:size-10 !p-0 rounded-lg flex items-center justify-center transition-all duration-200 ease-in-out shadow-sm hover:shadow-md active:scale-95 after:border-none'
})
const separatorClass = computed(() => {
return props.isLandscape
? 'mx-4rpx h-4 w-px flex-shrink-0 bg-gray-300'
: 'h-5 w-px bg-blue-300'
? 'mx-4rpx lg:mx-1 h-4 lg:h-6 w-px flex-shrink-0 bg-gradient-to-b from-transparent via-gray-300 to-transparent'
: 'h-5 lg:h-6 w-px bg-gradient-to-b from-transparent via-gray-300 to-transparent'
})
const iconClass = computed(() => {
return props.isLandscape
? 'size-20rpx'
: 'size-4 w-4'
? 'size-20rpx lg:size-4'
: 'size-5 lg:size-5 w-4 lg:w-5'
})
/**
@ -96,7 +99,9 @@ function getToolButtonClass(tool: MarkingTool) {
const isActive = currentTool.value === tool
return [
baseButtonClass.value,
isActive ? 'bg-blue-600 text-white' : 'text-blue-600 hover:bg-blue-600 hover:text-white',
isActive
? 'bg-gradient-to-r from-blue-600 to-blue-700 text-white border border-blue-500'
: 'bg-white text-blue-600 border border-blue-200 hover:bg-gradient-to-r hover:from-blue-50 hover:to-blue-100 hover:border-blue-300 hover:text-blue-700',
]
}
@ -110,9 +115,18 @@ function getMarkButtonClass(type: 'correct' | 'wrong' | 'half') {
half: MarkingTool.HALF,
}
const colorMap = {
correct: { active: 'bg-green-500 text-white', inactive: 'text-green-600 hover:bg-green-500 hover:text-white' },
wrong: { active: 'bg-red-500 text-white', inactive: 'text-red-600 hover:bg-red-500 hover:text-white' },
half: { active: 'bg-yellow-500 text-white', inactive: 'text-yellow-600 hover:bg-yellow-500 hover:text-white' },
correct: {
active: 'bg-gradient-to-r from-green-500 to-green-600 text-white border border-green-400',
inactive: 'bg-white text-green-600 border border-green-200 hover:bg-gradient-to-r hover:from-green-50 hover:to-green-100 hover:border-green-300 hover:text-green-700',
},
wrong: {
active: 'bg-gradient-to-r from-red-500 to-red-600 text-white border border-red-400',
inactive: 'bg-white text-red-600 border border-red-200 hover:bg-gradient-to-r hover:from-red-50 hover:to-red-100 hover:border-red-300 hover:text-red-700',
},
half: {
active: 'bg-gradient-to-r from-yellow-500 to-yellow-600 text-white border border-yellow-400',
inactive: 'bg-white text-yellow-600 border border-yellow-200 hover:bg-gradient-to-r hover:from-yellow-50 hover:to-yellow-100 hover:border-yellow-300 hover:text-yellow-700',
},
}
const isActive = currentTool.value === toolMap[type]
@ -128,15 +142,21 @@ function getSpecialButtonClass(type: 'excellent' | 'typical' | 'problem') {
const isActive = props.specialMarks[type]
const colorMap = {
excellent: {
active: 'bg-yellow-500 text-white',
inactive: 'text-yellow-500 hover:bg-yellow-500 hover:text-white',
active: 'bg-gradient-to-r from-yellow-500 to-yellow-600 text-white border border-yellow-400',
inactive: 'bg-white text-yellow-600 border border-yellow-200 hover:bg-gradient-to-r hover:from-yellow-50 hover:to-yellow-100 hover:border-yellow-300 hover:text-yellow-700',
},
typical: {
active: 'bg-gradient-to-r from-blue-600 to-blue-700 text-white border border-blue-500',
inactive: 'bg-white text-blue-600 border border-blue-200 hover:bg-gradient-to-r hover:from-blue-50 hover:to-blue-100 hover:border-blue-300 hover:text-blue-700',
},
problem: {
active: 'bg-gradient-to-r from-red-500 to-red-600 text-white border border-red-400',
inactive: 'bg-white text-red-600 border border-red-200 hover:bg-gradient-to-r hover:from-red-50 hover:to-red-100 hover:border-red-300 hover:text-red-700',
},
typical: { active: 'bg-blue-600 text-white', inactive: 'text-blue-500 hover:bg-blue-600 hover:text-white' },
problem: { active: 'bg-red-500 text-white', inactive: 'text-red-500 hover:bg-red-500 hover:text-white' },
}
const colors = colorMap[type]
return [baseButtonClass.value, props.isLandscape ? 'text-12rpx' : 'text-sm', isActive ? colors.active : colors.inactive]
return [baseButtonClass.value, props.isLandscape ? 'text-12rpx lg:text-sm' : 'text-sm lg:text-base', 'font-medium', isActive ? colors.active : colors.inactive]
}
/**
@ -268,6 +288,11 @@ function updateTextColor(color: string | null) {
* 处理鼠标按下开始拖动
*/
function handleMouseDown(event: MouseEvent) {
//
if (isCollapsed.value) {
return
}
//
const target = event.target as HTMLElement
if (
@ -336,6 +361,13 @@ function handleMouseUp() {
document.removeEventListener('mouseup', handleMouseUp)
}
/**
* 切换收起/展开状态
*/
function toggleCollapse() {
isCollapsed.value = !isCollapsed.value
}
/**
* 键盘快捷键处理
*/
@ -413,18 +445,48 @@ defineExpose({
<template>
<div
ref="toolbarRef"
class="fixed bottom-0 left-0 z-10 w-fit cursor-move select-none bg-blue-500 shadow-lg"
:class="{
'px-2 py-1': !isLandscape,
'px-4rpx py-2rpx': isLandscape,
}"
class="rounded-tr-base fixed bottom-0 left-0 z-10 select-none border border-white/20 shadow-xl backdrop-blur-sm transition-all duration-300"
:class="[
{
'px-2 py-1 w-fit cursor-move lg:px-3 lg:py-2': !isLandscape && !isCollapsed,
'px-4rpx py-2rpx': isLandscape && !isCollapsed,
'bg-gradient-to-r from-slate-800/95 to-slate-900/95': !isCollapsed,
'backdrop-saturate-150': !isCollapsed,
},
]"
@mousedown="handleMouseDown"
>
<!-- 收起按钮 -->
<div
v-if="isCollapsed"
class="h-full w-8 flex transform cursor-pointer items-center justify-center border border-gray-200 rounded-tr-2xl from-white to-gray-50 bg-gradient-to-r shadow-inner"
:class="{
'w-64px': isLandscape,
}"
@click="toggleCollapse"
@mousedown.stop
>
<div class="i-carbon:chevron-right text-base text-blue-600 transition-colors lg:text-lg hover:text-blue-700" />
</div>
<!-- 工具栏内容 -->
<div
v-show="!isCollapsed"
class="flex items-center" :class="{
'gap-1': !isLandscape,
'gap-4rpx': isLandscape,
'gap-4rpx lg:gap-2': !isLandscape,
'gap-4rpx lg:gap-1': isLandscape,
}"
>
<!-- 收起按钮展开状态时显示 -->
<button
class="border border-gray-200 from-white to-gray-50 bg-gradient-to-r text-slate-600 transition-all hover:border-gray-300 hover:from-gray-50 hover:to-gray-100 hover:text-slate-700"
:class="baseButtonClass"
@click="toggleCollapse"
>
<div :class="iconClass" class="i-carbon:chevron-left" />
</button>
<div :class="separatorClass" />
<!-- 对错半对标记 -->
<wd-tooltip content="正确标记" placement="top">
<button :class="getMarkButtonClass('correct')" @click="handleCorrectMark">

View File

@ -1,6 +1,6 @@
{
"name": "unibest",
"appid": "__UNI__D1E5001",
"name": "象乐学老师端",
"appid": "__UNI__2890BA7",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",

View File

@ -1,48 +0,0 @@
<script lang="ts" setup>
import type { IFooItem } from '@/api/foo'
import { getFooAPI } from '@/api/foo'
const recommendUrl = ref('http://laf.run/signup?code=ohaOgIX')
// const initialData = {
// name: 'initialData',
// id: '1234',
// }
const initialData = undefined
const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲鸽'), {
immediate: true,
initialData,
})
function reset() {
data.value = initialData
}
</script>
<template>
<view class="p-6 text-center">
<view class="my-2 text-center">
<button type="primary" size="mini" class="w-160px" @click="run">
发送请求
</button>
</view>
<view class="h-16">
<view v-if="loading">
loading...
</view>
<block v-else>
<view class="text-xl">
请求数据如下
</view>
<view class="text-green leading-8">
{{ JSON.stringify(data) }}
</view>
</block>
</view>
<view class="my-6 text-center">
<button type="warn" size="mini" class="w-160px" :disabled="!data" @click="reset">
重置数据
</button>
</view>
</view>
</template>

View File

@ -1,34 +0,0 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "分包页面"
}
}
</route>
<script lang="ts" setup>
// code here
import RequestComp from './components/request.vue'
</script>
<template>
<view class="text-center">
<view class="m-8">
http://localhost:9000/#/pages-sub/demo/index
</view>
<view class="my-4 text-green-500">
分包页面demo
</view>
<view class="text-blue-500">
分包页面里面的components示例
</view>
<view>
<RequestComp />
</view>
</view>
</template>
<style lang="scss" scoped>
//
</style>

View File

@ -73,30 +73,6 @@
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/about/about",
"type": "page",
"layout": "tabbar",
"style": {
"navigationBarTitleText": "关于"
}
},
{
"path": "pages/about/alova",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "Alova 请求演示"
}
},
{
"path": "pages/about/vue-query",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "Vue Query 请求演示"
}
},
{
"path": "pages/auth/index",
"type": "page",
@ -116,7 +92,10 @@
"path": "pages/marking/grading",
"type": "page",
"style": {
"navigationStyle": "custom"
"navigationStyle": "custom",
"enablePullDownRefresh": false,
"disableScroll": true,
"bounce": "none"
}
},
{
@ -127,19 +106,5 @@
}
}
],
"subPackages": [
{
"root": "pages-sub",
"pages": [
{
"path": "demo/index",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "分包页面"
}
}
]
}
]
"subPackages": []
}

View File

@ -1,84 +0,0 @@
<route lang="jsonc" type="page">
{
"layout": "tabbar",
"style": {
"navigationBarTitleText": "关于"
}
}
</route>
<script lang="ts" setup>
import RequestComp from './components/request.vue'
// vue .ts
// const testOxlint = (name: string) => {
// console.log('oxlint')
// }
// testOxlint('oxlint')
console.log('about')
function gotoAlova() {
uni.navigateTo({
url: '/pages/about/alova',
})
}
function gotoVueQuery() {
uni.navigateTo({
url: '/pages/about/vue-query',
})
}
function gotoSubPage() {
uni.navigateTo({
url: '/pages-sub/demo/index',
})
}
// uniLayout expose onReady onLoad
const uniLayout = ref()
onLoad(() => {
console.log('onLoad:', uniLayout.value) // onLoad: undefined
})
onReady(() => {
console.log('onReady:', uniLayout.value) // onReady: Proxy(Object)
console.log('onReady:', uniLayout.value.testUniLayoutExposedData) // onReady: testUniLayoutExposedData
})
</script>
<template>
<view>
<view class="mt-8 text-center text-xl text-gray-400">
组件使用请求调用unocss
</view>
<RequestComp />
<view class="mb-6 h-1px bg-#eee" />
<view class="text-center">
<button type="primary" size="mini" class="w-160px" @click="gotoAlova">
前往 alova 示例页面
</button>
</view>
<view class="text-center">
<button type="primary" size="mini" class="w-160px" @click="gotoVueQuery">
vue-query 示例页面
</button>
</view>
<view class="text-center">
<button type="primary" size="mini" class="w-160px" @click="gotoSubPage">
前往分包页面
</button>
</view>
<view class="mt-6 text-center text-sm">
<view class="inline-block w-80% text-gray-400">
为了方便脚手架动态生成不同UI模板本页的按钮统一使用UI库无关的原生button
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.test-css {
// 16rpx=>0.5rem
padding-bottom: 16rpx;
// mt-4=>1rem=>16px;
margin-top: 16px;
text-align: center;
}
</style>

View File

@ -1,56 +0,0 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "Alova 请求演示"
}
}
</route>
<script lang="ts" setup>
import { useRequest } from 'alova/client'
import { foo } from '@/api/foo-alova'
const initialData = undefined
const { loading, data, send } = useRequest(foo, {
initialData,
immediate: true,
})
console.log(data)
function reset() {
data.value = initialData
}
</script>
<template>
<view class="p-6 text-center">
<button type="primary" size="mini" class="my-6 w-160px" @click="send">
发送请求
</button>
<view class="h-16">
<view v-if="loading">
loading...
</view>
<block v-else>
<view class="text-xl">
请求数据如下
</view>
<view class="text-green leading-8">
{{ JSON.stringify(data) }}
</view>
</block>
<view class="text-red">
{{ data?.id }}
</view>
</view>
<button type="default" size="mini" class="my-6 w-160px" @click="reset">
重置数据
</button>
</view>
</template>
<style lang="scss" scoped>
//
</style>

View File

@ -1,54 +0,0 @@
<script lang="ts" setup>
import type { IFooItem } from '@/api/foo'
import { getFooAPI } from '@/api/foo'
// const initialData = {
// name: 'initialData',
// id: '1234',
// }
const initialData = undefined
const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲鸽'), {
immediate: true,
initialData,
})
function reset() {
data.value = initialData
}
</script>
<template>
<view class="p-6 text-center">
<view class="my-2">
pages 里面的 vue 文件会扫描成页面将自动添加到 pages.json 里面
</view>
<view class="my-2 text-green-400">
但是 pages/components 里面的 vue 不会
</view>
<view class="my-4 text-center">
<button type="primary" size="mini" class="w-160px" @click="run">
发送请求
</button>
</view>
<view class="h-16">
<view v-if="loading">
loading...
</view>
<block v-else>
<view class="text-xl">
请求数据如下
</view>
<view class="text-green leading-8">
{{ JSON.stringify(data) }}
</view>
</block>
</view>
<view class="my-4 text-center">
<button type="warn" size="mini" class="w-160px" :disabled="!data" @click="reset">
重置数据
</button>
</view>
</view>
</template>

View File

@ -1,53 +0,0 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "Vue Query 请求演示"
}
}
</route>
<script lang="ts" setup>
import { useQuery } from '@tanstack/vue-query'
import { foo } from '@/api/foo'
import { getFooQueryOptions } from '@/api/foo-vue-query'
// 使
onShow(async () => {
const res = await foo()
console.log('res: ', res)
})
// vue-query
const {
data,
error,
isLoading: loading,
refetch: send,
} = useQuery(getFooQueryOptions('菲鸽-vue-query'))
</script>
<template>
<view class="p-6 text-center">
<button type="primary" size="mini" class="my-6 w-160px" @click="send">
发送请求
</button>
<view class="h-16">
<view v-if="loading">
loading...
</view>
<block v-else>
<view class="text-xl">
请求数据如下
</view>
<view class="text-green leading-8">
{{ JSON.stringify(data) }}
</view>
</block>
</view>
</view>
</template>
<style lang="scss" scoped>
//
</style>

View File

@ -208,7 +208,7 @@ function handleQuickScoreSelect(score: number) {
</script>
<template>
<div class="relative h-screen">
<div class="relative h-screen w-100vw flex flex-col touch-pan-x touch-pan-y overflow-hidden overscroll-none pt-safe">
<MarkingLayout
:is-landscape="isLandscape"
:is-fullscreen="isFullscreen"
@ -266,3 +266,39 @@ function handleQuickScoreSelect(score: number) {
/>
</div>
</template>
<style scoped>
/* 禁用橡皮筋效果 */
.h-screen {
position: fixed;
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
}
/* H5端额外的橡皮筋禁用 */
/* #ifdef H5 */
body,
html {
overscroll-behavior: none;
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
/* #endif */
/* 微信小程序端橡皮筋禁用 */
/* #ifdef MP-WEIXIN */
page {
overflow: hidden;
overscroll-behavior: none;
}
/* #endif */
/* APP端橡皮筋禁用 */
/* #ifdef APP-PLUS */
page {
overflow: hidden;
}
/* #endif */
</style>

View File

@ -63,7 +63,7 @@ export default ({ command, mode }) => {
exclude: ['**/components/**/**.*'],
// homePage 通过 vue 文件的 route-block 的type="home"来设定
// pages 目录为 src/pages分包目录不能配置在pages目录下
subPackages: ['src/pages-sub'], // 是个数组可以配置多个但是不能为pages里面的目录
subPackages: [], // 是个数组可以配置多个但是不能为pages里面的目录
dts: 'src/types/uni-pages.d.ts',
}),
UniLayouts(),

View File

@ -0,0 +1,18 @@
{
"folders": [
{
"path": "."
},
{
"path": "../xlx_client/art-design-pro"
}
],
"settings": {
"files.associations": {
"pages.json": "jsonc",
"manifest.json": "jsonc"
},
"typescript.tsdk": "node_modules\\typescript\\lib",
"explorer.fileNesting.expand": false
}
}