Compare commits

..

11 Commits

Author SHA1 Message Date
zhcnyuyang
2b26ce04f0 解决标题栏宽度问题并将包管理器从npm改为yarn。 2025-11-20 19:14:57 +08:00
zhcnyuyang
cd6abcea3b 更正 2025-08-30 21:39:12 +08:00
zhcnyuyang
18bfe171c0 解决布局跳动问题,以及媒体文件全面转移到CDN 2025-08-24 11:31:15 +08:00
zhcnyuyang
f57b7cb2ae 解决布局跳动问题,以及媒体文件全面转移到CDN 2025-08-24 10:47:14 +08:00
zhcnyuyang
06b4766c7f 更新说明文档 2025-08-21 21:19:46 +08:00
zhcnyuyang
6a6ecfd51f Merge branch 'newstartview' of https://gitea.yangprivate.site/yangprivate-web/kdofficial into newstartview 2025-08-21 21:15:48 +08:00
zhcnyuyang
7d34946fb5 适配Apache、iis。 2025-08-21 21:09:46 +08:00
Volankey
f3aaf89d25 fix: 上传失败时抛出错误&恢复 cdn 配置 2025-08-21 20:51:30 +08:00
zhcnyuyang
bacb60fb43 Merge remote-tracking branch 'origin/newstartview' into newstartview
# Conflicts:
#	src/publicstyle.scss
2025-08-21 18:27:29 +08:00
zhcnyuyang
5c2a9ab91b 解决布局跳动问题 2025-08-21 18:26:35 +08:00
zhcnyuyang
b4869ac005 解决布局跳动问题 2025-08-21 16:39:00 +08:00
12 changed files with 199 additions and 73 deletions

View File

@ -10,20 +10,26 @@ This template should help get you started developing with Vue 3 in Vite.
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
## 安装依赖
```sh
npm install
```
### Compile and Hot-Reload for Development
### 测试运行(热更新)
```sh
npm run dev
```
### Compile and Minify for Production
### 构建项目
```sh
npm run build
```
### 将文件同步至CDN
```sh
npm run upload-assets
```

View File

@ -25,5 +25,6 @@
"@vitejs/plugin-vue": "^4.0.0",
"esno": "^4.8.0",
"vite": "^4.0.0"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

19
public/.htaccess Normal file
View File

@ -0,0 +1,19 @@
<IfModule mod_rewrite.c>
# 开启URL重写引擎
RewriteEngine On
# 规则 1: 处理根路径
# 当用户请求确切的根路径 (例如 http://yourdomain.com/) 时
# 内部重写到 start.html。
# ^$ 匹配空路径 (即根目录)。[L] 表示这是最后一条规则,如果匹配成功则停止处理。
RewriteRule ^$ /start.html [L]
# 规则 2: 处理其他所有路径 (标准的SPA规则)
# 在处理其他路径之前,先确保请求的不是一个真实存在的文件或目录。
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# 如果以上条件满足 (不是文件或目录)
# 则将请求重写到 index.html让Vue Router处理。
RewriteRule . /index.html [L]
</IfModule>

28
public/web.config Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<!-- 规则 1: 将根路径请求重写到 start.html -->
<!-- 这个规则匹配空路径 (根目录) -->
<rule name="Rewrite root to start.html" stopProcessing="true">
<match url="^$" />
<action type="Rewrite" url="/start.html" />
</rule>
<!-- 规则 2: 将所有其他未匹配的请求重写到 index.html (标准SPA规则) -->
<!-- 这个规则匹配所有内容,但有条件限制 -->
<rule name="Rewrite all others to index.html" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<!-- 条件1: 请求的URL不是一个物理文件 -->
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<!-- 条件2: 请求的URL不是一个物理目录 -->
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

View File

@ -8,6 +8,7 @@
import { TosClient } from '@volcengine/tos-sdk';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
// 配置信息
@ -73,7 +74,10 @@ async function getAllFiles(dirPath: string, fileList: string[] = []): Promise<st
async function uploadFile(filePath: string, basePath: string): Promise<void> {
try {
// 计算相对路径作为对象键名
const key = filePath.replace(basePath, '').replace(/^\//, '');
let key = filePath.replace(basePath, '').replace(/^[\/\\]/, '');
// 将Windows路径分隔符转换为正斜杠
key = key.replace(/\\/g, '/');
const contentType = getContentType(filePath);
const fileContent = fs.readFileSync(filePath);
@ -89,6 +93,7 @@ async function uploadFile(filePath: string, basePath: string): Promise<void> {
console.log(`上传成功: ${key}, 请求ID: ${response.requestId}`);
} catch (error) {
console.error(`上传文件 ${filePath} 失败:`, error);
throw error;
}
}
@ -114,11 +119,10 @@ async function uploadDirectory(dirPath: string): Promise<void> {
// 主函数
async function main() {
// 使用 import.meta.url 替代 __dirname (ES 模块兼容)
const currentFileUrl = import.meta.url;
const currentFilePath = new URL(currentFileUrl).pathname;
const currentDir = path.dirname(currentFilePath);
const assetsDir = path.resolve(currentDir, '../dist/assets');
// ES 模块中获取当前文件目录的正确方式
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const assetsDir = path.resolve(__dirname, '../dist/assets');
console.log(`开始上传目录: ${assetsDir}`);
await uploadDirectory(assetsDir);
}
@ -128,4 +132,3 @@ main().catch(error => {
console.error('程序执行失败:', error);
process.exit(1);
});

View File

@ -9,7 +9,8 @@ export default {
psginfo:null,
error:null,
isLoading: false,
cmsroot: '${this.$axios.defaults.baseURL}'
// cmsroot: '${this.$axios.defaults.baseURL}'
cmsroot: 'https://cdn.kdesign.top'
}
},
setup() {
@ -54,6 +55,20 @@ export default {
}else{
}
},
getImageUrl(url) {
if (!url) return '';
// URLhttphttps
if (url.startsWith('http://') || url.startsWith('https://')) {
// <EFBFBD><EFBFBD><EFBFBD>
const urlObj = new URL(url);
const relativePath = urlObj.pathname;
return `${this.$axios.defaults.baseCDNURL}${relativePath}`;
} else {
// CDN URL
return `${this.$axios.defaults.baseCDNURL}${url}`;
}
}
},
created() {
@ -75,9 +90,9 @@ export default {
<template>
<div id="passageroot" class="pageroot" v-if="psginfo">
<div id="passagebanner" class="pagebanner">
<img :src="`${this.$axios.defaults.baseURL}${psginfo.mobileheadimage.url}`"
<img :src="getImageUrl(psginfo.mobileheadimage?.url)"
v-if="breakpoint==='xs'||breakpoint==='sm'" alt="" id="bannerimg"/>
<img :src="`${this.$axios.defaults.baseURL}${psginfo.headimage.url}`"
<img :src="getImageUrl(psginfo.headimage?.url)"
v-else alt="" id="bannerimg"/>
<div id="bannercontentfill"
style="width: 100%;height: 100%;
@ -119,11 +134,11 @@ export default {
<div v-for="(item, index) in psginfo.images" :key="index"
class="imglistitem"
style="width: 100%; height: auto;">
<img :src="`${this.$axios.defaults.baseURL}${item.url}`" v-if="item.mime.startsWith('image',0)" alt="" />
<img :src="getImageUrl(item.url)" v-if="item.mime.startsWith('image',0)" alt="" />
<video v-else-if="item.mime.startsWith('video',0)" controls="controls">
<source :src="`${this.$axios.defaults.baseURL}${item.url}`" type="video/mp4">
<source :src="`${this.$axios.defaults.baseURL}${item.url}`" type="video/ogg">
<source :src="`${this.$axios.defaults.baseURL}${item.url}`" type="video/webm">
<source :src="getImageUrl(item.url)" type="video/mp4">
<source :src="getImageUrl(item.url)" type="video/ogg">
<source :src="getImageUrl(item.url)" type="video/webm">
</video>
</div>
</div>

View File

@ -2,21 +2,6 @@
<!--<div class="pageroot" style="background-color: white;">-->
<div v-if="this.error==null" id="examplelist">
<div id="typegrid">
<!-- <a href="/Examples" style="text-decoration: none;">-->
<!-- <div class="typebar"-->
<!-- :class="{-->
<!-- active: (!$route.query.type || $route.query.type === '')}">-->
<!-- 全部-->
<!-- </div>-->
<!-- </a>-->
<!-- <a v-for="(item, index) in this.types"-->
<!-- :key="index":href="`/Examples?type=${item}`"-->
<!-- style="text-decoration: none;">-->
<!-- <div class="typebar"-->
<!-- :class="{ active: ($route.query.type === item)}">-->
<!-- {{item}}-->
<!-- </div>-->
<!-- </a>-->
<router-link to="/Examples" style="text-decoration: none;">
<div class="typebar"
:class="{
@ -41,7 +26,13 @@
:href="`/ExampleItem?createdAt=${item.createdAt}`"
>
<div class="listblock">
<img :src="`${this.$axios.defaults.baseURL}${item.headimage.formats.large.url}`" alt="" />
<img
:src="getImageUrl(item.headimage)"
alt=""
@error="handleImageError"
/>
<div id="itemon">
<div class="itemtext">
<span class="upspan">
{{item.brandname_cn}}
@ -51,6 +42,7 @@
</span>
</div>
</div>
</div>
</a>
</div>
</div>
@ -80,10 +72,10 @@ export default {
if (Array.isArray(resp)) {
this.types = resp;
if (this.types.length === 0) {
this.error = "返回的数组为";
this.error = "返回的数组为<EFBFBD><EFBFBD><EFBFBD>";
}
} else {
this.error = "返回的据不是数组";
this.error = "返回的<EFBFBD><EFBFBD><EFBFBD>据不是数组";
this.types = [];
}
})
@ -108,7 +100,7 @@ export default {
reqarg='/api/examples?fields=id,brandname_cn,projectname_cn,createdAt&populate=headimage&filters[example_type]='+type+'&sort=createdAt:desc';
}
if (this.$route.name != 'Examples'){
let reqarg='/api/examples?fields=id,brandname_cn,projectname_cn,createdAt&populate=headimage&pagination[page]=1&pagination[pageSize]=5&sort=createdAt:desc';
reqarg='/api/examples?fields=id,brandname_cn,projectname_cn,createdAt&populate=headimage&pagination[page]=1&pagination[pageSize]=5&sort=createdAt:desc';
}
axios.get(reqarg)
.then(response =>{
@ -134,6 +126,24 @@ export default {
.finally(() => {
this.isLoading = false;
});
},
getImageUrl(image) {
const url = image.url;
// URLhttphttps
if (url.startsWith('http://') || url.startsWith('https://')) {
//
const urlObj = new URL(url);
const relativePath = urlObj.pathname;
return `${this.$axios.defaults.baseCDNURL}${relativePath}`;
} else {
// CDN URL
return `${this.$axios.defaults.baseCDNURL}${url}`;
}
},
handleImageError(event) {
//
event.target.src = 'path/to/default/image.jpg'; //
}
},
mounted() {
@ -155,6 +165,16 @@ export default {
<style scoped lang="scss">
@import "src/publicstyle.scss";
#itemon{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.3);
z-index: 2;
}
#listmain{
display: grid;
@include media-breakpoint-between(xs, md) {
@ -188,6 +208,9 @@ export default {
}
}
.listblock img {
position: absolute;
top: 0;
left: 0;
z-index: 0;
@include media-breakpoint-between(xs, md){
width: 100%;
@ -230,7 +253,7 @@ export default {
}
@include media-breakpoint-up(md) {
grid-template-columns: repeat(4,minmax(0,300px)) !important;
grid-template-columns: repeat(4,1fr) !important;
gap: 32px;
}

View File

@ -10,7 +10,7 @@ const { breakpoint } = useBootstrapBreakpoint();
<template>
<div class="pageroot" id="serviceprocessroot">
<div id="serviceprocessbanner" class="videorootbanner">
<div id="serviceprocessbanner">
<img class="videoimg" :src="Newbanner"/>
<div id="expbanner" class="bannerovervideo">
<div class="bannercontent">
@ -131,6 +131,12 @@ export default {
@import "src/publicstyle.scss";
#serviceprocessbanner{
width: 100% !important;
background-color: black !important;
position: relative !important;
z-index: 0 !important;
overflow: hidden !important;
@include media-breakpoint-between(xs, md) {
aspect-ratio: 1.92;
}

View File

@ -9,7 +9,7 @@
<div v-for="item in logos"
class="partneritempanel" id="ptn01">
<div class="partnerlogodiv">
<img :src="`${this.$axios.defaults.baseURL}${item.url}`" alt="" />
<img :src="getImageUrl(item.url)" alt="" />
</div>
</div>
</div>
@ -44,6 +44,20 @@ export default {
console.error(err);
this.logos = [];
});
},
getImageUrl(url) {
if (!url) return '';
// URLhttphttps
if (url.startsWith('http://') || url.startsWith('https://')) {
//
const urlObj = new URL(url);
const relativePath = urlObj.pathname;
return `${this.$axios.defaults.baseCDNURL}${relativePath}`;
} else {
// CDN URL
return `${this.$axios.defaults.baseCDNURL}${url}`;
}
}
},
mounted() {

View File

@ -11,6 +11,7 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js'
const app = createApp(App)
axios.defaults.baseURL = 'https://cms.kdesign.top'
axios.defaults.baseCDNURL = 'https://cdn.kdesign.top'
// axios.defaults.baseURL = 'http://localhost:8082'
// 全局挂载 axios所有组件都可以 this.$axios 访问
app.config.globalProperties.$axios = axios

View File

@ -35,37 +35,41 @@
}
.videorootbanner{
width: 100%;
background-color: black;
position: relative;
z-index: 0;
overflow: hidden;
width: 100% !important;
background-color: black !important;
position: relative !important;
z-index: 0 !important;
overflow: hidden !important;
// 默认状态视频未加载时使用固定aspect-ratio
// 始终保持固定的aspect-ratio作为占位确保立即渲染
@include media-breakpoint-between(xs, md) {
aspect-ratio: 1.92;
aspect-ratio: 1.92 !important;
}
@include media-breakpoint-up(md){
aspect-ratio: 2.62026969;
aspect-ratio: 2.62026969 !important;
}
// 视频元素默认样式
video {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
// 视频/图片元素的默认样式
video, .videoimg {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
display: block !important;
}
// 当视频加载完成后切换为自适应高度模式
// 媒体加载完成后重新调整容器和媒体尺寸
&.video-loaded {
aspect-ratio: unset; // 移除固定比例
height: auto; // 高度自适应
aspect-ratio: unset !important; // 移除固定比例
height: auto !important; // 高度自适应
video {
width: 100%; // 宽度占满
height: auto; // 高度自适应
object-fit: contain; // 保持视频比例完整显示
video, .videoimg {
position: static !important; // 改为文档流布局
width: 100% !important; // 宽度占满
height: auto !important; // 高度自适应
object-fit: contain !important; // 保持媒体比例完整显示
}
}
}

View File

@ -5,6 +5,7 @@ import fs from 'fs'
export default defineConfig({
base: process.env.NODE_ENV === 'production' ? 'https://cdn.kdesign.top/' : '/',
// base: '/',
plugins: [
vue(),
// 添加自定义中间件来处理根路径
@ -37,6 +38,11 @@ export default defineConfig({
input: {
main: fileURLToPath(new URL('./index.html', import.meta.url)),
start: fileURLToPath(new URL('./start.html', import.meta.url))
},
output: {
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]'
}
}
},