为了让用户轻松地从长列表中进行选择,具有自动完成功能的输入比普通选择下拉列表更可取,因为它使用户可以搜索所需的条目,而不是从列表中进行选择。这是Web应用程序的常见功能,因此开发人员开发了可自动添加功能的自动完成组件。
在本文中,我们将制作一个货币转换器,使用户可以选择要转换为的货币,并按基本货币列出汇率。我们将使用Vue.js来构建应用程序,使用位于https://exchangeratesapi.io/的Foreign Exchange Rate API 来获取汇率和位于http://openexchangerates.org的Open Exchange Rates API ,以得到我们的货币清单。
要开始构建应用程序,我们将运行Vue CLI创建项目。运行npx @vue/cli create currency-converter
以创建项目。在向导中,我们选择“手动选择功能”,然后从列表中选择Babel,CSS Preprocessor和Vuex,以及Vue Router。
接下来,我们安装一些库。我们将使用Axios进行HTTP请求,使用BootstrapVue进行样式设置,使用Vee-Validate进行表单验证,并使用Vue-Autosuggest进行自动完成输入。Vue-Autosuggest使我们可以自定义组件的所有部分。它对样式没有任何意见,这意味着它非常适合Bootstrap样式。
我们通过运行npm i axios bootstrap-vue vee-validate vue-autosuggest
安装所有库来安装所有软件包。
接下来,我们为应用程序编写代码。我们首先添加一个mixin,用于将HTTP请求发送到API以获取数据。mixins
在该src
文件夹中创建一个文件夹,然后添加requestsMixin.js
该src
文件夹,然后将以下代码添加到该文件中:
const APIURL = "https://api.exchangeratesapi.io";
const OPEN_EXCHANGE_RATES_URL =
"http://openexchangerates.org/api/currencies.json";
const axios = require("axios");
export const requestsMixin = {
methods: {
getCurrenciesList() {
return axios.get(OPEN_EXCHANGE_RATES_URL);
},
getExchangeRates(baseCurrency) {
return axios.get(`${APIURL}/latest?base=${baseCurrency}`);
}
}
};
我们正在使用Axios向API发出请求。
接下来,我们建立一个页面,让用户转换货币。ConvertCurrency.vue
在views
文件夹中创建并添加:
<template>
<div class="page">
<h1 class="text-center">Convert Currency</h1>
<ValidationObserver ref="observer" v-slot="{ invalid }">
<b-form @submit.prevent="onSubmit" novalidate>
<b-form-group label="Amount" label-for="title">
<ValidationProvider name="amount" rules="required|min_value:0" v-slot="{ errors }">
<b-form-input
v-model="form.amount"
type="text"
required
placeholder="Amount"
name="amount"
></b-form-input>
<b-form-invalid-feedback :state="errors.length == 0">Amount is required</b-form-invalid-feedback>
</ValidationProvider>
</b-form-group>
<b-form-group label="Currency to Convert From" label-for="start">
<ValidationProvider name="fromCurrency" rules="required" v-slot="{ errors }">
<vue-autosuggest
:suggestions="filteredFromCurrencies"
:input-props="{id:'autosuggest__input', placeholder:'Select Currency to Convert From', class: 'form-control'}"
v-model="form.fromCurrency"
:get-suggestion-value="getSuggestionValue"
:render-suggestion="renderSuggestion"
component-attr-class-autosuggest-results-container="result"
@selected="onSelectedFromCurrency"
></vue-autosuggest>
<b-form-invalid-feedback
:state="errors.length == 0"
>Currency to Convert From is required</b-form-invalid-feedback>
</ValidationProvider>
</b-form-group>
<b-form-group label="Currency to Convert To" label-for="end">
<ValidationProvider name="toCurrency" rules="required" v-slot="{ errors }">
<vue-autosuggest
:suggestions="filteredToCurrencies"
:input-props="{id:'autosuggest__input', placeholder:'Select Currency to Convert To', class: 'form-control'}"
v-model="form.toCurrency"
:get-suggestion-value="getSuggestionValue"
:render-suggestion="renderSuggestion"
component-attr-class-autosuggest-results-container="result"
@selected="onSelectedToCurrency"
></vue-autosuggest>
<b-form-invalid-feedback :state="errors.length == 0">Currency to Convert To is required</b-form-invalid-feedback>
</ValidationProvider>
</b-form-group>
<b-button type="submit" variant="primary">Convert</b-button>
</b-form>
</ValidationObserver>
<div v-if="convertedAmount" class="text-center">
<h2>Converted Amount</h2>
<p>{{form.amount}} {{selectedFromCurrencyCode}} is equal to {{convertedAmount}} {{selectedToCurrencyCode}}</p>
</div>
</div>
</template>
<script>
import { requestsMixin } from "@/mixins/requestsMixin";
export default {
name: "ConvertCurrency",
mixins: [requestsMixin],
computed: {
currencies() {
return Object.keys(this.$store.state.currencies).map(key => ({
value: key,
name: this.$store.state.currencies[key]
}));
},
filteredFromCurrencies() {
const filtered =
this.currencies.filter(
c =>
(c.value || "").toLowerCase() !=
(this.selectedToCurrencyCode || "").toLowerCase() &&
(c.value || "")
.toLowerCase()
.includes((this.form.fromCurrency || "").toLowerCase())
) ||
(c.name || "")
.toLowerCase()
.includes((this.form.fromCurrency || "").toLowerCase());
return [
{
data: filtered || []
}
];
},
filteredToCurrencies() {
const filtered =
this.currencies.filter(
c =>
(c.value || "").toLowerCase() !=
(this.selectedFromCurrencyCode || "").toLowerCase() &&
(c.value || "")
.toLowerCase()
.includes((this.form.toCurrency || "").toLowerCase())
) ||
(c.name || "")
.toLowerCase()
.includes((this.form.toCurrency || "").toLowerCase());
return [
{
data: filtered || []
}
];
}
},
data() {
return {
form: {
currency: ""
},
exchangeRates: {},
ratesFound: false,
selectedFromCurrencyCode: "",
selectedToCurrencyCode: "",
convertedAmount: 0
};
},
methods: {
getSuggestionValue(suggestion) {
return suggestion && suggestion.item.name;
},
renderSuggestion(suggestion) {
return suggestion && suggestion.item.name;
},
onSelectedFromCurrency(item) {
this.selectedFromCurrencyCode = item && item.item.value;
},
onSelectedToCurrency(item) {
this.selectedToCurrencyCode = item && item.item.value;
},
async onSubmit() {
const isValid = await this.$refs.observer.validate();
if (!isValid) {
return;
}
try {
const { data } = await this.getExchangeRates(
this.selectedFromCurrencyCode
);
const rate = data.rates[this.selectedToCurrencyCode];
this.convertedAmount = this.form.amount * rate;
} catch (error) {}
}
}
};
</script>
App.vue
加载时会检索货币列表并将其存储在Vuex商店中,因此我们可以在所有页面中使用它,而无需重新加载获取货币列表的请求。
我们使用Vee-Validate来验证我们的输入。我们使用ValidationObserver
组件来监视组件内部表单的有效性,并ValidationProvider
检查组件内部输入的输入值的验证规则。在中ValidationProvider
,我们有该amount
字段的BootstrapVue输入。
Vue-Autosuggest组件使用户可以选择要与之转换的货币。该suggestions
道具包含按用户输入内容过滤的货币列表,并过滤出其他字段设置为哪种货币。该input-props
道具包含一个带有输入占位符的对象。v-model
设置了用户到目前为止输入的内容,我们将在本scripts
节中使用该内容来过滤货币。get-suggestion-value
prop具有一个函数,该函数以您喜欢的方式返回建议的项目。render-suggestion
prop通过将函数传递给prop来以您喜欢的方式显示选择。该component-attr-class-autosuggest-results-container
让我们设置类的结果下拉列表和selected
事件处理程序,让我们设置所选择的终值。
在filteredFromCurrencies
和filteredToCurrencies
函数中,我们通过排除已经输入到另一个下拉菜单中的货币来过滤货币,并且还以不区分大小写的方式按用户到目前为止输入的内容进行过滤。
用户单击“保存”后,将onSubmit
调用该函数。在函数内部,this.$refs.observer.validate();
称为检查表单验证。observer
是的引用ValidationObserver
。此处观察到的表单验证值。如果解析为true
,我们通过调用getExchangeRates
从mixin添加的函数来获取基础货币的汇率,然后将其转换为最终转换金额,并将其显示在表格下方的模板中。
接下来的中Home.vue
,将现有代码替换为:
<template>
<div class="page">
<h1 class="text-center">Exchange Rates</h1>
<vue-autosuggest
:suggestions="filteredCurrencies"
:input-props="{id:'autosuggest__input', placeholder:'Select Currency', class: 'form-control'}"
v-model="form.currency"
:get-suggestion-value="getSuggestionValue"
:render-suggestion="renderSuggestion"
component-attr-class-autosuggest-results-container="result"
@selected="onSelected"
>
<div slot-scope="{suggestion}">
<span class="my-suggestion-item">{{suggestion.item.name}}</span>
</div>
</vue-autosuggest>
<h2>Rates</h2>
<b-list-group v-if="ratesFound">
<b-list-group-item v-for="(key, value) in exchangeRates.rates" :key="key">{{key}} - {{value}}</b-list-group-item>
</b-list-group>
<b-list-group v-else>
<b-list-group-item>Rate not found.</b-list-group-item>
</b-list-group>
</div>
</template>
<script>
import { requestsMixin } from "@/mixins/requestsMixin";
export default {
name: "home",
mixins: [requestsMixin],
computed: {
currencies() {
return Object.keys(this.$store.state.currencies).map(key => ({
value: key,
name: this.$store.state.currencies[key]
}));
},
filteredCurrencies() {
const filtered = this.currencies.filter(
c =>
(c.value || "")
.toLowerCase()
.includes(this.form.currency.toLowerCase()) ||
(c.name || "")
.toLowerCase()
.includes(this.form.currency.toLowerCase())
);
return [
{
data: filtered
}
];
}
},
data() {
return {
form: {
currency: ""
},
exchangeRates: {},
ratesFound: false
};
},
methods: {
getSuggestionValue(suggestion) {
return suggestion.item.name;
},
renderSuggestion(suggestion) {
return suggestion.item.name;
},
async onSelected(item) {
try {
const { data } = await this.getExchangeRates(item.item.value);
this.exchangeRates = data;
this.ratesFound = true;
} catch (error) {
this.ratesFound = false;
}
}
}
};
</script>
<style lang="scss" scoped>
</style>
这是我们应用程序的主页。在顶部,我们具有Vue-Autosuggest组件,可从货币列表中过滤用户输入。货币列表来自Vuex商店。用户选择了最终值后,我们this.getExchangeRates
便从运行,requestsMixin
以加载所选货币的最新汇率(如果找到)。
接下来的中App.vue
,将现有代码替换为:
<template>
<div id="app">
<b-navbar toggleable="lg" type="dark" variant="info">
<b-navbar-brand to="/">Currency Converter</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-navbar-nav>
<b-nav-item to="/" :active="path == '/'">Home</b-nav-item>
<b-nav-item to="/convertcurrency" :active="path == '/convertcurrency'">Convert Currency</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<router-view />
</div>
</template>
<style lang="scss">
.page {
padding: 20px;
}
.result {
position: absolute;
background-color: white;
min-width: 350px;
z-index: 1000;
ul {
margin: 0;
padding: 0;
border: 1px solid #ced4da;
border-radius: 3px;
li {
list-style-type: none;
padding-left: 10px;
}
}
}
</style>
<script>
import { requestsMixin } from "@/mixins/requestsMixin";
export default {
mixins: [requestsMixin],
data() {
return {
path: this.$route && this.$route.path
};
},
watch: {
$route(route) {
this.path = route.path;
}
},
beforeMount() {
this.getCurrencies();
},
methods: {
async getCurrencies() {
const { data } = await this.getCurrenciesList();
this.$store.commit("setCurrencies", data);
}
}
};
</script>
在这里,我们添加了BootstrapVue导航栏。我们也有router-view
显示路线的。在本scripts
节中,我们将观察$route
变量以获取用户导航以设置的active
道具的当前路线b-nav-item
。同样,当该组件加载时,我们获取货币并将其放入我们的Vuex存储中,以便我们获取所有组件中的数据。我们在这里加载它,因为这是应用程序的输入组件。
此组件还包含我们应用程序的全局样式。该result
班是为造型的自动完成下拉列表。我们设置position
为absolute
,使其显示在所有其他内容之上,并使其与其他项目重叠。我们还设置了下拉菜单的颜色并为其添加了边框。列表项的点被删除,list-style-type
设置为none
。我们有page
该类在页面上添加一些填充。
接下来,main.js
将现有代码替换为:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import BootstrapVue from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import VueAutosuggest from "vue-autosuggest";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required, min_value } from "vee-validate/dist/rules";
extend("required", required);
extend("min_value", min_value);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(VueAutosuggest);
Vue.use(BootstrapVue);
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
我们在此处将BootstrapVue,Vue-Autosuggest和Vee-Validate添加到我们的应用中。此外,我们添加了此处使用的Vee-Validate验证规则,其中包括required
确保所有内容均已填充的规则以及min_value
数量。Bootstrap CSS也包括在这里,以样式化我们所有的组件。
然后在中router.js
,将现有代码替换为:
import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import ConvertCurrency from "./views/ConvertCurrency.vue";
Vue.use(Router);
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "home",
component: Home
},
{
path: "/convertcurrency",
name: "convertcurrency",
component: ConvertCurrency
}
]
});
添加我们的路线,以便用户可以看到我们的页面。
在store.js
与替换现有的代码:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
currencies: {}
},
mutations: {
setCurrencies(state, payload) {
state.currencies = payload;
}
},
actions: {}
});
存储我们在所有组件中使用的货币列表。我们在mutation
对象中具有setter函数,并且currencies
组件具有观察到的状态。
然后在中index.html
,我们将现有代码替换为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE\_URL %>favicon.ico" />
<title>Currency Converter</title>
</head>
<body>
<noscript>
<strong
>We're sorry but vue-autocomplete-tutorial-app doesn't work properly
without JavaScript enabled. Please enable it to continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
更改标题。