《vite+vue3實現網頁版編輯器,帶高亮以及代碼提示(以SQL語言為例)》
## 引言:探索Vite與Vue3結合構建高效Web應用
隨著前端技術的飛速發展,Vite和Vue3已成為現代Web開發領域的熱門工具。Vite以其快速冷啟動、熱更新等特性讓開發者享受前所未有的開發體驗;而Vue3則憑借其優秀的組件化設計與Composition API,極大地提高了開發效率和代碼可維護性。本文將引導您如何利用這兩者搭建一款功能齊全、性能卓越的網頁版SQL編輯器,包括代碼高亮顯示及智能提示等功能。
## 一、項目初始化與環境配置
### 1. 創建項目
首先,確保已安裝Node.js和npm。然后通過Vite創建一個基于Vue3的新項目:
```bash
npm create vite@latest my-sql-editor --template vue
cd my-sql-editor
npm install
```
### 2. 安裝相關依賴
為了實現實時語法高亮和代碼提示,我們需要借助`codemirror`庫及其SQL相關的插件:
```bash
npm install codemirror @codemirror/lang-sql
```
## 二、編寫基礎HTML結構與Vue組件
### 1. 在App.vue中引入CodeMirror
```html
<template>
<div id="app">
<textarea ref="editor"></textarea>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import CodeMirror from 'codemirror';
onMounted(() => {
const editor = CodeMirror.fromTextArea(
document.querySelector('textarea'),
{
mode: 'text/x-sql',
lineNumbers: true,
theme: 'dracula', // 更多主題可以自定義選擇
}
);
});
</script>
```
此處我們已在App.vue中引入并初始化了一個基本的CodeMirror編輯器,并設置SQL模式以支持初步的語義高亮。
## 三、實現SQL代碼高亮
上述代碼已經實現了基礎的SQL高亮,CodeMirror內置了對SQL的支持。但為了讓效果更佳,我們可以進一步優化配置項,如添加SQL關鍵字高亮等。
### 高級配置示例:
```javascript
import '@codemirror/theme-dracula'; // 引入主題樣式
import { Extension } from '@codemirror/state';
const sqlExtensions: Extension[] = [
// SQL語言插件
langSql(),
// 添加代碼行號
lineNumbers(),
// 設置主題
EditorView.theme({
'&': { background: '#282a36' },
'.cm-comment': { color: '#6272a4' }, // 注釋顏色
'.cm-keyword': { color: '#ff79c6' }, // 關鍵字顏色
// ...其他樣式自定義
}),
];
onMounted(() => {
const editor = CodeMirror.fromTextArea(
document.querySelector('textarea'),
{
extensions: sqlExtensions,
}
);
});
```
## 四、實現SQL代碼提示
CodeMirror并沒有直接提供SQL的自動補全功能,但我們可以通過自定義擴展來實現。這里我們使用`hint`和`autocomplete`插件配合自定義數據源實現SQL代碼提示。
### 實現代碼提示功能:
```javascript
// 假設我們有一個包含所有SQL關鍵字和函數的數組
const sqlKeywords = ['SELECT', 'FROM', 'WHERE', 'LIKE', /*...*/ ];
function sqlHint(cm: EditorView) {
let cur = cm.state.field(EditorState.cursor).head;
let token = cm.getTokenAt(cur);
if (token.string.startsWith('@')) { // 示例:針對特定字符開頭觸發提示
let list: string[] = [];
for (let keyword of sqlKeywords) {
if (keyword.startsWith(token.string.slice(1))) {
list.push(keyword);
}
}
return {
from: cm.posFromIndex(cur - token.start),
to: cm.posFromIndex(cur),
list: list,
};
}
}
const hintExtension = [
Completion.of([
{ provide: ['completion'], get: () => sqlHint },
]),
];
sqlExtensions.push(...hintExtension);
```
## 結語:進階優化與未來展望
至此,我們已成功利用Vite+Vue3構建了一個具備SQL高亮和代碼提示功能的網頁版編輯器。然而,為了提升用戶體驗,還可以在此基礎上進行諸如錯誤檢測、實時預覽查詢結果等更多高級功能的開發。同時,對于SQL提示的完善,可以考慮接入數據庫API獲取實時表結構信息,實現更精準的智能提示。希望本文能為您的前端開發之旅注入新的靈感與動力,讓我們一起在前端世界里創造更多可能!
ue (讀音 /vju?/,類似于 view) 是一套用于構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注視圖層,不僅易于上手,還便于與第三方庫或既有項目整合。另一方面,當與現代化的工具鏈以及各種支持類庫結合使用時,Vue 也完全能夠為復雜的單頁應用提供驅動。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
<script src="js/axios.js"></script>
</head>
<body>
<div id="app">
{{name}}
<!-- 屬性設置-->
<h3 v-text="name">這里不顯示</h3>
<h3>武當掌門:{{name}}</h3>
<!-- 字符串的拼接-->
<h3 v-text="`武當掌門:`+name"></h3>
<h3 v-text="name + '!!!'"></h3>
<h3>xxx{{name + "!!!"}}</h3>
</div>
<script>
new Vue({
el: "#app",
data: {
"name": "張三豐"
}
})
</script>
</body>
</html>
<!-- 更新元素的 textContent (h3)。h3內的文本(這里不顯示)將不顯示-->
<h3 v-text="name">這里不顯示</h3>
<!-- 如果需要更新部分的textContent,那么可以使用插值語法{{name}}。-->
<h3>武當掌門:{{name}}</h3>
v-text的拼接
v-text="`武當掌門:`+name"
v-text="name + '!!!'"
這兩種方法都是允許的
家好,我是Echa 哥,祝大家開工大吉!萬事如意!
vue作為前端主流的3大框架之一,目前在國內有著非常廣泛的應用,由于其輕量和自底向上的漸進式設計思想,使其不僅僅被應用于PC系統,對于移動端,桌面軟件(electronjs)等也有廣泛的應用,與此誕生的優秀的開源框架比如elementUI,iView, ant-design-vue等也極大的降低了開發者的開發成本,并極大的提高了開發效率。筆者最初接觸vue時也是使用的iview框架,親自體會之后確實非常易用且強大。
筆者曾經有兩年的vue項目經驗,基于vue做過移動端項目和PC端的ERP系統,雖然平時工作中采用的是react技術棧,但平時還是會積累很多vue相關的最佳實踐和做一些基于vue的開源項目,所以說總結vue的項目經驗我覺得是最好的成長,也希望給今年想接觸vue框架或者想從事vue工作的朋友帶來一些經驗和思考。
你將收獲
本文不僅僅是總結一些vue使用踩過的一些坑和項目經驗,更多的是使用框架(vue/react)過程中的方法論和組件的設計思路,最后還會有一些個人對工程化的一些總結,希望有更多經驗的朋友們可以一起交流,探索vue的奧妙。
在開始文章之前,筆者建議大家對javascript, css, html基礎有一定的了解,因為會用框架不一定能很好的實現業務需求和功能,要想實現不同場景下不同復雜度的需求,一定要對web基礎有充足的了解,所以希望大家熟悉如下基礎知識,如果不太熟悉可以花時間研究了解一下。
javascript:
css:
html:
所以希望大家掌握好以上基礎知識,也是前端開發的基礎,接下來我們直接進入正文。
?
vue學習最快的方式就是實踐,根據官網多寫幾個例子是掌握vue最快的方式。 接下來筆者就來總結一下在開發vue項目中的一些實踐經驗。
?
以上是vue官網上的生命周期的方法,大致劃分一下分為創建前/后,掛載前/后,更新前/后,銷毀前/后這四個階段。各個階段的狀態總結如下:
根據以上介紹,頁面第一次加載時會執行 beforeCreate, created, beforeMount, mounted這四個生命周期,所以我們一般在created階段處理http請求獲取數據或者對data做一定的處理, 我們會在mounted階段操作dom,比如使用jquery,或者其他第三方dom庫。其次,根據以上不同周期下數據和頁面狀態的不同,我們還可以做其他更多操作,所以說每個生命周期的發展狀態非常重要,一定要理解,這樣才能對vue有更多的控制權。
指令 (Directives) 是帶有 v- 前綴的特殊屬性,vue常用的指令有:
以上是比較常用的指令,具體用法就不一一舉例了,其中v-cloak主要是用來避免頁面加載時出現閃爍的問題,可以結合css的[v-cloak] { display: none }方式解決這一問題。關于指令的動態參數,使用也很簡單,雖然是2.6.0 新增的,但是方法很靈活,具體使用如下:
<a v-on:[eventName]="doSomething"> ... </a>
復制代碼
我們可以根據具體情況動態切換事件名,從而綁定同一個函數。
<Tag @click.native="handleClick">ok</Tag>
復制代碼
用法如下:
<input type="text" v-model.trim="value">
復制代碼
還有很多修飾符比如鍵盤,鼠標等修飾符,感興趣的大家可以自行學習研究。
組件之間的通信方案:
父子組件:
組件的按需加載是項目性能優化的一個環節,也可以降低首屏渲染時間,筆者在項目中用到的組件按需加載的方式如下:
<template>
<div>
<ComponentA />
<ComponentB />
</div>
</template>
<script>
const ComponentA = () => import('./ComponentA')
const ComponentB = () => import('./ComponentB')
export default {
// ...
components: {
ComponentA,
ComponentB
},
// ...
}
</script>
復制代碼
<template>
<div>
<ComponentA />
</div>
</template>
<script>
const ComponentA = resolve => require(['./ComponentA'], resolve)
export default {
// ...
components: {
ComponentA
},
// ...
}
</script>
復制代碼
?
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
?
vuex的基本工作模式如下圖所示:
state的改變完全由mutations控制, 我們也沒必要任何項目都使用vuex,對于中大型復雜項目而言,需要共享的狀態很多時,使用vuex才是最佳的選擇。接下來我將詳細介紹各api的概念和作用。
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
// 訪問getters里的屬性
this.$store.getters.doneTodos
復制代碼
const store = new Vuex.Store({
state: {
num: 1
},
mutations: {
add (state) {
// 變更狀態
state.num++
}
}
})
// 在項目中使用mutation
store.commit('add')
// 添加額外參數
store.commit('add', 10)
復制代碼
const store = new Vuex.Store({
state: {
num: 0
},
mutations: {
add (state) {
state.num++
}
},
actions: {
add (context) {
context.commit('add')
},
asyncAdd ({ commit }) {
setTimeout(() => {
commit('add')
}
}
})
// 分發action
store.dispatch('add')
// 異步action
store.dispatch('asyncAdd')
// 異步傳參
store.dispatch('asyncAdd', { num: 10 })
筆者更具實際經驗總結了一套標準使用模式,就拿筆者之前的開源XPXMS舉例,如下:
store目錄是用來組織vuex代碼用的,我將action,mutation,state分文件管理,這樣項目大了之后也很容易管理和查詢。接下來看看是如何組織的:
// type.ts
// 用來定義state等的類型文件
export interface State {
name: string;
isLogin: boolean;
config: Config;
[propName: string]: any; // 用來定義可選的額外屬性
}
export interface Config {
header: HeaderType,
banner: Banner,
bannerSider: BannerSider,
supportPay: SupportPay
}
export interface Response {
[propName: string]: any;
}
// state.ts
// 定義全局狀態
import { State } from './type'
export const state: State = {
name: '',
isLogin: false,
curScreen: '0', // 0為pc, 1為移動
config: {
header: {
columns: ['首頁', '產品', '技術', '運營', '商業'],
height: '50',
backgroundColor: '#000000',
logo: ''
}
},
// ...
articleDetail: null
};
// mutation.ts
import {
State,
Config,
HeaderType,
Banner,
BannerSider,
SupportPay
} from './type'
export default {
// 預覽模式
setScreen(state: State, payload: string) {
state.curScreen = payload;
},
// 刪除banner圖
delBanner(state: State, payload: number) {
state.config.banner.bannerList.splice(payload, 1);
},
// 添加banner圖
addBanner(state: State, payload: object) {
state.config.banner.bannerList.push(payload);
},
// ...
};
// action.ts
import {
HeaderType,
Response
} from './type'
import http from '../utils/http'
import { uuid, formatTime } from '../utils/common'
import { message } from 'ant-design-vue'
export default {
/**配置 */
setConfig(context: any, paylod: HeaderType) {
http.get('/config/all').then((res:Response) => {
context.commit('setConfig', res.data)
}).catch((err:any) => {
message.error(err.data)
})
},
/**header */
saveHeader(context: any, paylod: HeaderType) {
http.post('/config/setHeader', paylod).then((res:Response) => {
message.success(res.data)
context.commit('saveHeader', paylod)
}).catch((err:any) => {
message.error(err.data)
})
},
// ...
};
// index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import { state } from './state';
import mutations from './mutation';
import actions from './action';
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
actions
});
// main.ts
// 最后掛載到入口文件的vue實例上
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store/';
import './component-class-hooks';
import './registerServiceWorker';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
我們在實際項目中都可以使用這種方式組織管理vuex相關的代碼。
vue-router使用大家想必不是很陌生,這里直接寫一個案例:
// router.ts
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/admin/Home.vue';
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
component: Home,
beforeEnter: (to, from, next) => {
next();
},
children: [
{
// 當 /user/:id/profile 匹配成功,
// UserProfile 會被渲染在 User 的 <router-view> 中
path: '',
name: 'header',
component: () => import(/* webpackChunkName: "header" */ './views/admin/subpage/Header.vue'),
},
{
path: '/banner',
name: 'banner',
component: () => import(/* webpackChunkName: "banner" */ './views/admin/subpage/Banner.vue'),
},
{
path: '/admin',
name: 'admin',
component: () => import(/* webpackChunkName: "admin" */ './views/admin/Admin.vue'),
},
],
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ './views/Login.vue'),
meta:{
keepAlive:false //不需要被緩存的組件
}
},
{
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "404" */ './views/404.vue'),
},
],
});
// 路由導航鉤子的用法
router.beforeEach((to, from, next) => {
if(from.path.indexOf('/preview') < 0) {
sessionStorage.setItem('prevToPreviewPath', from.path);
}
next();
})
export default router
以上案例是很典型的靜態路由配置和導航鉤子的用法(如何加載路由組件,動態加載路由組件,404頁面路由配置,路由導航鉤子使用)。如果在做后臺系統,往往會涉及到權限系統,所以一般會采用動態配置路由,通過前后端約定的路由方式,路由配置文件更具不同用戶的權限由后端處理后返。由于設計細節比較繁瑣,涉及到前后端協定,所以這里只講思路就好了。
受現代 JavaScript 的限制,Vue 無法檢測到對象屬性的添加或刪除。由于 Vue 會在初始化實例時對屬性執行 getter/setter 轉化,所以屬性必須在 data 對象上存在才能讓 Vue 將它轉換為響應式的。還有一種情況是,vue無法檢測到data屬性值為數組或對象的修改,所以我們需要用原對象與要混合進去的對象的屬性一起創建一個新的對象。可以使用this.$set或者對象的深拷貝,如果是數組則可以使用splice,擴展運算符等方法來更新。
keep-alive是Vue的內置組件,能在組件切換過程中將狀態保留在內存中,防止重復渲染DOM。我們可以使用以下方式設置某些頁面是否被緩存:
// routes 配置
export default [
{
path: '/A',
name: 'A',
component: A,
meta: {
keepAlive: true // 需要被緩存
}
}, {
path: '/B',
name: 'B',
component: B,
meta: {
keepAlive: false // 不需要被緩存
}
}
]
路由視圖配置:
// 路由設置
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!-- 會被緩存的視圖組件-->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 不需要緩存的視圖組件-->
</router-view>
<template>
<div id="app">
<keep-alive>
<router-view :key="key" />
</keep-alive>
</div>
</template>
<script lang="ts">
import { Vue } from 'vue-property-decorator';
import Component from 'vue-class-component';
@Component
export default class App extends Vue {
get key() {
// 緩存除預覽和登陸頁面之外的其他頁面
console.log(this.$route.path)
if(this.$route.path.indexOf('/preview') > -1) {
return '0'
}else if(this.$route.path === '/login') {
return '1'
}else {
return '2'
}
}
}
</script>
總結一下筆者在vue項目中的常用的工具函數。
/**
* 識別ie--淺識別
*/
export const isIe = () => {
let explorer = window.navigator.userAgent;
//判斷是否為IE瀏覽器
if (explorer.indexOf("MSIE") >= 0) {
return true;
}else {
return false
}
}
/**
* 顏色轉換16進制轉rgba
* @param {String} hex
* @param {Number} opacity
*/
export function hex2Rgba(hex, opacity) {
if(!hex) hex = "#2c4dae";
return "rgba(" + parseInt("0x" + hex.slice(1, 3)) + "," + parseInt("0x" + hex.slice(3, 5)) + "," + parseInt("0x" + hex.slice(5, 7)) + "," + (opacity || "1") + ")";
}
// 去除html標簽
export const htmlSafeStr = (str) => {
return str.replace(/<[^>]+>/g, "")
}
/* 獲取url參數 */
export const getQueryString = () => {
let qs = location.href.split('?')[1] || '',
args = {},
items = qs.length ? qs.split("&") : [];
items.forEach((item,i) => {
let arr = item.split('='),
name = decodeURIComponent(arr[0]),
value = decodeURIComponent(arr[1]);
name.length && (args[name] = value)
})
return args;
}
/* 解析url參數 */
export const paramsToStringify = (params) => {
if(params){
let query = [];
for(let key in params){
query.push(`${key}=${params[key]}`)
}
return `${query.join('&')}`
}else{
return ''
}
}
export const toArray = (data) => {
return Array.isArray(data) ? data : [data]
}
/**
* 帶參數跳轉url(hash模式)
* @param {String} url
* @param {Object} params
*/
export const toPage = (url, params) => {
if(params){
let query = [];
for(let key in params){
query.push(`${key}=${params[key]}`)
}
window.location.href = `./index.html#/${url}?${query.join('&')}`;
}else{
window.location.href = `./index.html#/${url}`;
}
}
/**
* 指定字符串 溢出顯示省略號
* @param {String} str
* @param {Number} num
*/
export const getSubStringSum = (str = "", num = 1) => {
let newStr;
if(str){
str = str + '';
if (str.trim().length > num ) {
newStr = str.trim().substring(0, num) + "...";
} else {
newStr = str.trim();
}
}else{
newStr = ''
}
return newStr;
}
/**
* 生成uuid
* @param {number} len 生成指定長度的uuid
* @param {number} radix uuid進制數
*/
export function uuid(len, radix) {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
let uuid = [], i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
} else {
let r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random()*16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
/**
* 生成指定格式的時間
* @param {*} timeStemp 時間戳
* @param {*} flag 格式符號
*/
export function formatTime(timeStemp, flag) {
let time = new Date(timeStemp);
let timeArr = [time.getFullYear(), time.getMonth() + 1, time.getDate()];
return timeArr.join(flag || '/')
}
這個主要是對axios的理解,大家可以學習axios官方文檔,這里給出一個二次封裝的模版:
import axios from 'axios'
import qs from 'qs'
// 請求攔截
axios.interceptors.request.use(config => {
// 此處可以封裝一些加載狀態
return config
}, error => {
return Promise.reject(error)
})
// 響應攔截
axios.interceptors.response.use(response => {
return response
}, error => {
return Promise.resolve(error.response)
})
function checkStatus (response) {
// 此處可以封裝一些加載狀態
// 如果http狀態碼正常,則直接返回數據
if(response) {
if (response.status === 200 || response.status === 304) {
return response.data
// 如果不需要除了data之外的數據,可以直接 return response.data
} else if (response.status === 401) {
location.href = '/login';
} else {
throw response.data
}
} else {
throw {data:'網絡錯誤'}
}
}
// axios默認參數配置
axios.defaults.baseURL = '/api/v0';
axios.defaults.timeout = 10000;
// restful API封裝
export default {
post (url, data) {
return axios({
method: 'post',
url,
data: qs.stringify(data),
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
get (url, params) {
return axios({
method: 'get',
url,
params, // get 請求時帶的參數
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
del (url, params) {
return axios({
method: 'delete',
url,
params, // get 請求時帶的參數
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(res) => {
return checkStatus(res)
}
)
}
}
該模版只是一個大致框架,大家可以細化成業務需求的樣子,該案例提供了restful接口方法,比如get/post/delete/put等。
筆者在做vue項目時為了提高開發效率也會直接用第三方插件,下面整理一下常用的vue社區組件和庫。
更多組件可以在vue插件社區查看。
在講完vue項目經驗之后,為了讓大家能獨立負責一個項目,我們還需要知道從0開始搭建項目的步驟,以及通過項目實際情況,自己配置一個符合的項目框架,比如有些公司會采用vue+element+vue+less搭建,有些公司采用vue+iview+vue+sass,或者其他更多的技術棧,所以我們要有把控能力,我們需要熟悉webpack或者vue-cli3腳手架的配置,筆者之前有些過詳細的webpack和vue-cli3搭建自定義項目的文章,這里由于篇幅有限就不一一舉例了。感興趣的朋友可以參考以下兩篇文章:
?
組件系統是 Vue 的另一個重要概念,因為它是一種抽象,允許我們使用小型、獨立和通常可復用的組件構建大型應用。幾乎任意類型的應用界面都可以抽象為一個組件樹。在一個大型應用中,有必要將整個應用程序劃分為組件,以使開發更易管理。
?
對于一個基礎組件來說,我們該如何下手去設計呢?首先筆者覺得應該先從需求和功能入手,先劃分好組件的功能邊界,組件能做什么,理清這些之后再開始寫組件。
如上圖組件的一個抽象,我們無論如何記住的第一步就是先理清需求在去著手開發,這樣可以避免大量效率的丟失。在上圖中我們要注意組件的解耦合,每個組件都負責特定的功能或者展現,這樣對組件后期維護性和擴展性有非常大的幫助。筆者總結了一下組件設計的注意事項:
筆者拿之前在開源社區發布的一個文件上傳組件為例子來說明舉例,代碼如下:
<template>
<div>
<a-upload
:action="action"
listType="picture-card"
:fileList="fileList"
@preview="handlePreview"
@change="handleChange"
:remove="delFile"
:data="data"
>
<template v-if="!fileList.length && defaultValue">
<img :src="defaultValue" alt="" style="width: 100%">
</template>
<template v-else>
<div v-if="fileList.length < 2">
<a-icon type="plus" />
<div class="ant-upload-text">上傳</div>
</div>
</template>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
@Component
export default class Upload extends Vue {
@Prop({ default: 'https://www.mocky.io/v2/5cc8019d300000980a055e76' })
action!: string;
@Prop()
defaultValue: string;
@Prop()
data: object;
@Prop({ default: function() {} })
onFileDel: any;
@Prop({ default: function() {} })
onFileChange: any;
public previewVisible: boolean = false;
public previewImage: string = '';
public fileList: object[] = [];
// 預覽圖片
public handlePreview(file: any) {
this.previewImage = file.url || file.thumbUrl;
this.previewVisible = true;
}
// 刪除文件和回調
public delFile(file: any) {
this.fileList = [];
this.onFileDel();
}
// 文件上傳變化的處理函數
public handleChange({ file }: any) {
this.fileList = [file];
if(file.status === 'done') {
this.onFileChange(file.response.url);
} else if(file.status === 'error') {
this.$message.error(file.response.msg)
}
}
// 取消預覽
public handleCancel() {
this.previewVisible = false;
}
}
</script>
以上文件上傳預覽采用的是ts來實現,但設計思路都是一致的,大家可以參考交流一下。 關于如何設計一個健壯的組件,筆者也寫過相關文章,大致思想都好似一樣的,可以參考一下:
組件的設計思想和方法與具體框架無關,所以組件設計的核心是方法論,我們只有在項目中不斷總結和抽象,才能對組件設計有更深刻的理解。
這里是筆者總結的一套思維導圖:
有點微前端架構的感覺,但是還有很多細節需要考慮。此架構適用于不同子系統獨立部署和開發的環境, 也不需要考慮具體的技術棧選擇,相同的框架采用同一套自建組件庫來達到組件的復用,這里提倡項目開始設計時就考慮組件化和模塊化,做出功能的最大的拆分和去耦合。筆者后面會單獨花時間總結微前端架構具體的一些設計思路和落地方案,感興趣的可以一起探討交流。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。