This commit is contained in:
2026-03-31 12:28:51 +08:00
parent df29bbbfd9
commit 654724a213
2561 changed files with 108 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
const { t, locale } = useI18n();
const router = useRouter();
function goback() {
router.back();
}
function functionupdataTitle() {
document.title = "Operations." + t("errorpage.404_title");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
onMounted(() => {
//console.log("account mounted");
//username.value.value="Kevin";
functionupdataTitle();
});
</script>
<template>
<div>
<div class="page page-center">
<div class="container-tight py-4">
<div class="empty">
<div class="empty-header">404</div>
<p class="empty-title">{{ t("errorpage.404_msg_title") }}</p>
<p class="empty-subtitle text-secondary">
{{ t("errorpage.404_msg") }}
</p>
<div class="empty-action">
<button class="btn btn-outline-secondary m-3" @click="goback">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 12l14 0" />
<path d="M5 12l6 6" />
<path d="M5 12l6 -6" />
</svg>
{{ t("errorpage.404_previous_page") }}
</button>
<router-link to="/" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-home"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 12l-2 0l9 -9l9 9l-2 0" />
<path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" />
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
</svg>
{{ t("errorpage.404_back_home") }}
</router-link>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,15 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
@@ -0,0 +1,19 @@
<script setup>
import { ref } from "vue";
import { my_network_func } from "@/my_network_func";
import { useUserStore } from "@/stores/user";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import imageCropper from "@/components/imageCropper.vue";
const user = useUserStore();
const mos = ref();
</script>
<template>
<MyOffcanvas ref="mos" />
</template>
@@ -0,0 +1,3 @@
<template>
</template>
@@ -0,0 +1,97 @@
<script setup>
import { onMounted, watch, ref } from 'vue'
import MyOffcanvas from '@/components/MyOffcanvas.vue'
import { myfuncs } from '@/myfunc.js'
import { useI18n } from 'vue-i18n'
// 使用 vue-i18n 的 Composition API
const { t, locale } = useI18n()
const email = ref()
const mos = ref()
function resetPassword() {
// 在这里处理重置密码逻辑
const emailValue = email.value?.value
if (emailValue === undefined || emailValue.trim() === '') {
mos.value?.showAlert('info', t('message.please_enter_your_username'), 5000)
return
}
// if (!myfuncs.isValidEmail(emailValue)) {
// mos.value?.showAlert('warning', t('message.this_not_email'), 5000)
// return
// }
mos.value?.showAlert('warning', "功能未开发", 5000)
console.log('sending password reset to:', emailValue)
}
function functionupdataTitle() {
document.title = 'Operations.' + t('appname.forgot_password')
}
onMounted(() => {
functionupdataTitle()
})
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle()
})
</script>
<template>
<div class="container container-tight py-4">
<div class="text-center mb-4">
<a href="." class="navbar-brand navbar-brand-autodark">
<img
src="/static/logo.svg"
width="110"
height="32"
alt="Tabler"
class="navbar-brand-image"
/>
</a>
</div>
<div class="card card-md">
<div class="card-body">
<h2 class="card-title text-center mb-4">{{ t('message.forgot_password') }}</h2>
<p class="text-secondary mb-4">
{{ t('message.enter_your_username_to_reset_password') }}
</p>
<div class="mb-3">
<label class="form-label">{{ t('message.user_name') }}</label>
<input
ref="email"
type="text"
class="form-control"
:placeholder="t('message.your_user_name')"
/>
</div>
<div class="form-footer">
<button @click="resetPassword" class="btn btn-primary w-100">
<!-- Download SVG icon from http://tabler-icons.io/i/mail -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"
/>
<path d="M3 7l9 6l9 -6" />
</svg>
{{ t('button.send_me_new_password') }}
</button>
</div>
</div>
</div>
<div class="text-center text-secondary mt-3">
<router-link to="/login">{{ t('message.back_to_login') }}</router-link>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
+267
View File
@@ -0,0 +1,267 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import { useRouter } from "vue-router";
import { useUserStore } from "@/stores/user";
import { my_network_func } from "@/my_network_func";
import { myfuncs } from "@/myfunc.js";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import { useI18n } from "vue-i18n";
// 使用 vue-i18n 的 Composition API
const { t, locale } = useI18n();
const router = useRouter();
const userStore = useUserStore();
const mos = ref();
const username = ref();
const password = ref();
const isRemember = ref();
const isShowPassword = ref(false);
function togglePasswordVisibility() {
isShowPassword.value = !isShowPassword.value;
}
function login() {
// 在这里处理登录逻辑
const user = username.value?.value;
const pass = password.value?.value;
const remember = isRemember.value?.checked;
username.value?.classList.remove("is-invalid");
password.value?.classList.remove("is-invalid");
if (!user || !pass) {
if (!user) {
username.value?.classList.add("is-invalid");
}
if (!pass) {
password.value?.classList.add("is-invalid");
}
mos.value?.showAlert(
"info",
t("message.please_enter_username_and_password"),
5000
);
return;
}
//console.log("登录信息:", { user, pass, remember });
my_network_func.postJson(
"/users/login",
{
username: user,
userpass: pass,
remember: remember,
},
(r) => {
//console.log(r)
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case -41:
username.value?.classList.add("is-invalid");
mos.value?.showAlert(
"warning",
t("message.user_not_found"),
5000
);
break;
case -42:
username.value?.classList.add("is-invalid");
password.value?.classList.add("is-invalid");
mos.value?.showAlert(
"warning",
t("message.username_or_password_incorrect"),
5000
);
break;
case 0:
//登录成功,载入cookie
//临时保存cookie
userStore.cookieUpdata(r.data.return.cookie)
//更新用户信息
userStore.login(r.data.return.cookie)
mos.value?.showAlert(
"success",
t("message.login_successful"),
1000,
() => {
router.back()
}
);
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
}
);
}
onMounted(() => {
functionupdataTitle();
if (userStore.isLoggedIn) {
router.push("/");
}
});
function functionupdataTitle() {
document.title = "Operations." + t("appname.login");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
</script>
<template>
<div class="page page-center">
<div class="container container-normal py-6">
<div class="row align-items-center g-4">
<div class="col-lg">
<div class="container-tight">
<div class="card card-md">
<div class="card-body">
<h2 class="h2 text-center mb-4">
{{ t("message.login_to_your_account") }}
</h2>
<div class="mb-3">
<label class="form-label">{{ t("message.user_name") }}</label>
<input
ref="username"
type="text"
maxlength="64"
class="form-control"
:placeholder="t('message.your_user_name')"
autocomplete="off"
/>
</div>
<div class="mb-2">
<label class="form-label">
{{ t("message.password") }}
<span class="form-label-description">
<router-link to="/forgot_password">{{
t("message.i_forgot_password")
}}</router-link>
</span>
</label>
<div class="input-group input-group-flat">
<input
ref="password"
:type="isShowPassword ? 'text' : 'password'"
class="form-control"
:placeholder="t('message.your_password')"
autocomplete="off"
/>
<span class="input-group-text">
<div
class="link-secondary"
:title="
isShowPassword
? t('message.hidden_Password')
: t('message.show_password')
"
data-bs-toggle="tooltip"
>
<!-- Download SVG icon from http://tabler-icons.io/i/eye -->
<svg
v-if="!isShowPassword"
@click="togglePasswordVisibility"
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path
d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"
/>
</svg>
<svg
v-if="isShowPassword"
@click="togglePasswordVisibility"
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
<path
d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"
/>
<path d="M3 3l18 18" />
</svg>
</div>
</span>
</div>
</div>
<div class="mb-2">
<label class="form-check">
<input
ref="isRemember"
type="checkbox"
class="form-check-input"
/>
<span class="form-check-label">{{
t("message.remember_me_on_this_device")
}}</span>
</label>
</div>
<div class="form-footer">
<button @click="login" class="btn btn-primary w-100">
{{ t("button.sign_in") }}
</button>
</div>
</div>
</div>
<div class="text-center text-secondary mt-3">
{{ t("message.dont_have_account_yet") }}
<router-link to="/register">{{
t("message.register_now")
}}</router-link>
</div>
</div>
</div>
<div class="col-lg d-none d-lg-block">
<img
src="/static/illustrations/undraw_secure_login_pdn4.svg"
height="300"
class="d-block mx-auto"
alt=""
/>
</div>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
@@ -0,0 +1,481 @@
<script setup>
import { onMounted, watch, ref, reactive } from "vue";
import { useI18n } from "vue-i18n";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import tagadder from "@/components/tagadder.vue";
import dateTimePicker from "@/components/dateTimePicker.vue";
import useDropzone from "@/components/useDropzone.vue";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
import { useRouter } from "vue-router";
const router = useRouter();
import "tom-select/dist/css/tom-select.css";
import { my_network_func } from "@/my_network_func";
const textarea_maxlen = 256;
const title_input_dom = ref();
const photos_hash = ref();
const mos = ref();
const { t, locale } = useI18n();
//货币类型
const currency_type = reactive({
1: "RMB",
2: "MOP",
3: "HKD",
4: "USD",
});
//成本类型
const cost_type = reactive({
1: t("cost_type.unit_price"),
2: t("cost_type.freight"),
});
function update_cost_type() {
cost_type["1"] = t("cost_type.unit_price");
cost_type["2"] = t("cost_type.freight");
}
//订单状态
const order_status = reactive({
1: t("order_status.pending_order"),
2: t("order_status.order_placed"),
3: t("order_status.in_transit"),
4: t("order_status.compvared"),
5: t("order_status.refund_requested"),
6: t("order_status.returning"),
7: t("order_status.refunded"),
8: t("order_status.lost_package"),
});
function update_order_status() {
order_status["1"] = t("order_status.pending_order");
order_status["2"] = t("order_status.order_placed");
order_status["3"] = t("order_status.in_transit");
order_status["4"] = t("order_status.compvared");
order_status["5"] = t("order_status.refund_requested");
order_status["6"] = t("order_status.returning");
order_status["7"] = t("order_status.refunded");
order_status["8"] = t("order_status.lost_package");
}
const cost_sheet_tab = reactive([]);
// 表单对象
const cost_sheet = reactive({
type: "1",
int: 1,
cost: 0.0,
cost_t: 0.0,
currency_type: "1",
});
function del_cost(key) {
cost_sheet.type = cost_sheet_tab[key].type;
cost_sheet.int = cost_sheet_tab[key].int;
cost_sheet.cost = cost_sheet_tab[key].cost;
cost_sheet.cost_t = cost_sheet_tab[key].cost_t;
cost_sheet.currency_type = cost_sheet_tab[key].currency_type;
cost_sheet_tab.splice(key, 1);
}
function add_cost() {
if (cost_sheet.cost <= 0) {
} else {
// 四舍五入到2位小数
var t = parseFloat((cost_sheet.int * cost_sheet.cost).toFixed(2));
cost_sheet.cost_t = t;
cost_sheet_tab.push(JSON.parse(JSON.stringify(cost_sheet)));
cost_sheet.type = "1";
cost_sheet.int = 1;
cost_sheet.cost = 0.0;
cost_sheet.cost_t = 0.0;
cost_sheet.currency_type = "1";
}
}
const submit_sheet = reactive({
title: "",
remark: "",
photos: [],
link: "",
partname: "",
styles: "",
costs: [],
updatetime: "",
trackingnumber: "",
orderstatus: "1",
});
function submit_order() {
if (submit_sheet.title == "") {
title_input_dom.value.classList.add("is-invalid");
title_input_dom.value.addEventListener("input", function () {
if (this.value.trim() !== "") {
this.classList.remove("is-invalid");
//this.removeEventListener('input');
}
});
mos.value?.showAlert("danger", t("purchase_addorder.title"), 1000);
return;
}
//载入图片哈希列表
submit_sheet.photos = [];
var photos = photos_hash.value.return_files();
for (var i = 0; i < photos.length; i++) {
submit_sheet.photos.push(photos[i].hash);
}
//载入价格表
submit_sheet.costs = [];
for (var i = 0; i < cost_sheet_tab.length; i++) {
//var t=cost_sheet_tab[i]
submit_sheet.costs.push(JSON.parse(JSON.stringify(cost_sheet_tab[i])));
}
//修改价格表里的小数,将所有价值*100去掉小数
for (var i = 0; i < submit_sheet.costs.length; i++) {
submit_sheet.costs[i].cost *= 100;
submit_sheet.costs[i].cost_t *= 100;
}
console.log(submit_sheet);
my_network_func.postJson("/purchase/addorder", submit_sheet, (r) => {
console.log(r);
});
}
function functionupdataTitle() {
document.title = "Operations." + t("purchase.add_part");
}
onMounted(() => {
functionupdataTitle();
//sele_init();
if (!userStore.isLoggedIn) {
router.push("/login");
}
});
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
update_cost_type();
update_order_status();
});
// 监听 cost 变化,自动限制小数位
watch(
() => cost_sheet.cost,
(newVal) => {
if (newVal !== null && newVal !== undefined) {
// 四舍五入到2位小数
const fixed = parseFloat(newVal).toFixed(2);
if (parseFloat(fixed) !== newVal) {
cost_sheet.cost = parseFloat(fixed);
}
}
},
);
</script>
<template>
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">{{ t("purchase_addorder.add_order") }}</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">
{{ t("purchase_addorder.order_info") }}
</h4>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label required">{{
t("purchase_addorder.title")
}}</label>
<input
type="text"
class="form-control"
name="example-text-input"
:placeholder="t('purchase_addorder.title')"
v-model="submit_sheet.title"
ref="title_input_dom"
/>
</div>
<div class="mb-3">
<label class="form-label"
>{{ t("purchase_addorder.remarks") }}
<span class="form-label-description"
>{{ submit_sheet.remark.length }}/{{
textarea_maxlen
}}</span
></label
>
<textarea
class="form-control mt-2 mb-2"
name="example-textarea-input"
rows="6"
:placeholder="t('purchase_addorder.remarks_text')"
:maxlength="textarea_maxlen"
v-model="submit_sheet.remark"
></textarea>
</div>
<label class="form-label mb-0">{{
t("purchase_addorder.photo_remarks")
}}</label>
<useDropzone
acceptedFiles="image/*"
uploadURL="/api/files/upload/image"
maxFiles="10"
ref="photos_hash"
></useDropzone>
</div>
<div class="card-header">
<h4 class="card-title">
{{ t("purchase_addorder.purchase_channel") }}
</h4>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">{{
t("purchase_addorder.link")
}}</label>
<textarea
name="url"
type="url"
class="form-control"
placeholder="https"
v-model="submit_sheet.link"
></textarea>
<div class="mb-3 mt-3">
<label class="form-label">{{
t("purchase_addorder.part_name")
}}</label>
<input
type="text"
class="form-control"
name="example-text-input"
:placeholder="t('purchase_addorder.part_name')"
v-model="submit_sheet.partname"
/>
</div>
<div class="mt-3">
<label class="form-label">{{
t("purchase_addorder.style_remarks")
}}</label>
<tagadder
:placeholder="t('purchase_addorder.add_style')"
v-model="submit_sheet.styles"
></tagadder>
</div>
<div class="mt-3">
<label class="form-label">{{
t("purchase_addorder.cost")
}}</label>
<table
v-show="cost_sheet_tab.length"
class="table table-vcenter card-table table-striped"
>
<thead>
<tr>
<th>{{ t("purchase_addorder.type") }}</th>
<th>{{ t("purchase_addorder.quantity") }}</th>
<th>{{ t("purchase_addorder.fee") }}</th>
<th>{{ t("purchase_addorder.total_price") }}</th>
<th>{{ t("purchase_addorder.currency") }}</th>
<th class="w-1">
{{ t("purchase_addorder.operation") }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(value, key) in cost_sheet_tab">
<td>{{ cost_type[value.type] }}</td>
<td class="text-secondary">{{ value.int }}</td>
<td class="text-secondary">{{ value.cost }}</td>
<td class="text-secondary">
{{ value.cost_t }}
</td>
<td class="text-secondary">
{{ currency_type[value.currency_type] }}
</td>
<td>
<button
class="btn btn-outline-danger"
@click="del_cost(key)"
>
{{ t("purchase_addorder.change") }}
</button>
</td>
</tr>
<!-- <tr>
<td>运输</td>
<td class="text-secondary">1</td>
<td class="text-secondary">5</td>
<td class="text-secondary">MOP</td>
<td>
<button class="btn btn-outline-danger">Del</button>
</td>
</tr> -->
</tbody>
</table>
<div class="row g-5">
<div class="col-xl-2">
{{ t("purchase_addorder.fee_type") }}
<select
ref="select_type"
class="form-control"
autocompvare="off"
value="1"
v-model="cost_sheet.type"
>
<option v-for="(value, key) in cost_type" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="col-xl-3">
{{ t("purchase_addorder.input_quantity") }}
<input
type="number"
class="form-control"
min="1"
value="1"
v-model="cost_sheet.int"
/>
</div>
<div class="col-xl-3">
{{ t("purchase_addorder.input_fee") }}
<input
type="number"
class="form-control"
step="0.01"
min="0.0"
value="0.0"
v-model="cost_sheet.cost"
/>
</div>
<div class="col-xl-2">
{{ t("purchase_addorder.select_currency") }}
<select
ref="select_beast"
class="form-control"
autocompvare="off"
value="1"
v-model="cost_sheet.currency_type"
>
<option
v-for="(value, key) in currency_type"
:value="key"
>
{{ value }}
</option>
</select>
</div>
<div class="col-xl-2">
{{ t("purchase_addorder.operation") }}
<button
class="form-control btn btn-outline-primary"
@click="add_cost"
>
{{ t("purchase_addorder.add") }}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="card-header">
<h4 class="card-title">
{{ t("purchase_addorder.other_status") }}
</h4>
</div>
<div class="card-body">
<div class="mb-3">
<div class="row g-5">
<div class="col-xl-4">
<label class="form-label required">{{
t("purchase_addorder.update_time")
}}</label>
<dateTimePicker
v-model="submit_sheet.updatetime"
></dateTimePicker>
</div>
<div class="col-xl-4">
<label class="form-label">{{
t("purchase_addorder.tracking_number")
}}</label>
<input
type="text"
class="form-control"
:placeholder="
t('purchase_addorder.input_tracking_number')
"
v-model="submit_sheet.trackingnumber"
/>
</div>
<div class="col-xl-4">
{{ t("purchase_addorder.order_status") }}
<select
ref="select_beast"
class="form-control"
autocompvare="off"
v-model="submit_sheet.orderstatus"
>
<option v-for="(value, key) in order_status" :value="key">
{{ value }}
</option>
</select>
</div>
</div>
</div>
</div>
<div class="card-footer text-end">
<div class="d-flex">
<button
type="submit"
class="btn btn-primary ms-auto"
@click="submit_order"
>
{{ t("purchase_addorder.submit") }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
<style></style>
@@ -0,0 +1,422 @@
<script setup>
import { onMounted, watch, ref, reactive } from "vue";
import { useI18n } from "vue-i18n";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
const mos = ref();
import { my_network_func } from "@/my_network_func";
import { myfuncs } from "@/myfunc";
import { useRouter } from "vue-router";
const router = useRouter();
const { t, locale } = useI18n();
const all_items = ref(0);
const all_pages = ref(0);
const page_items = ref(10);
const now_page = ref(1);
const all_orders = ref({});
const page_start = ref(0);
const page_end = ref(0);
const page_input = ref();
const page_items_items = ref("10");
function jump_to_order(order_id) {
//console.log(order_id);
var order_str=order_id.toString()
const resolved = router.resolve({
path: "/purchase/showorder/" + order_str,
});
window.open(resolved.href, "_blank");
}
//获取订单列表
function get_orders() {
my_network_func.postJson(
"/purchase/getorders",
{
search: "",
entries: page_items.value,
page: now_page.value,
},
(r) => {
//console.log(r);
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case 0:
all_orders.value = r.data.return.all_orders;
all_items.value = r.data.return.all_count;
all_pages.value = Math.ceil(all_items.value / page_items.value);
if (now_page.value < 3) {
page_start.value = 1;
} else {
if (now_page.value > all_pages.value - 3) {
page_start.value = all_pages.value - 4;
if (page_start.value <= 0) {
page_start.value = 1;
}
} else {
page_start.value = now_page.value - 2;
}
}
if (now_page.value > all_pages.value - 3) {
page_end.value = all_pages.value;
} else {
if (now_page.value < 3) {
page_end.value = 5;
} else {
page_end.value = now_page.value + 2;
}
}
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
},
);
}
function change_page(page) {
now_page.value = page;
get_orders();
}
function functionupdataTitle() {
document.title = "Operations." + t("appname.purchase");
}
function range(start, end) {
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
function page_input_change(c) {
//console.log(page_input.value);
var t = parseInt(page_input.value);
if (t > 0) {
if (t <= all_pages.value) {
page_input.value = "";
change_page(t);
}
}
}
function page_input_input(c) {
page_input.value = page_input.value.replace(/[^\d]/g, "");
//console.log(c)
}
function page_items_input_change(c) {
var t = parseInt(page_items_items.value);
page_items.value = t;
now_page.value = 1;
get_orders();
//console.log(t)
}
function page_items_input_input(c) {
page_items_items.value = page_items_items.value.replace(/[^\d]/g, "");
var t = parseInt(page_items_items.value);
if (t > 300) {
page_items_items.value = "300";
}
//console.log(c)
}
onMounted(() => {
functionupdataTitle();
get_orders();
});
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
</script>
<template>
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ t("purchase.purchase_list") }}</h3>
</div>
<div class="card-body border-bottom py-3">
<div class="d-flex">
<div class="text-secondary">
<router-link to="/purchase/addorder" class="btn btn-info m-1">
{{ t("purchase.add_part") }}
</router-link>
<button class="btn m-1">
{{ t("purchase.exp_report") }}
</button>
</div>
<!-- //搜索dom -->
<div class="ms-auto text-secondary">
{{ t("purchase.search") }}
<div class="ms-2 d-inline-block mr-2">
<input
type="text"
class="form-control form-control-sm"
aria-label="Search invoice"
/>
</div>
</div>
<div class="ms-auto text-secondary"></div>
</div>
</div>
<div class="table-responsive">
<table class="table card-table table-vcenter text-nowrap datatable">
<thead>
<tr>
<th class="w-1">
<input
class="form-check-input m-0 align-middle"
type="checkbox"
aria-label="Select all invoices"
/>
</th>
<th class="col-1">
No.
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-up -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-sm icon-thick"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M6 15l6 -6l6 6" />
</svg>
</th>
<th class="col-3">{{ t("purchase.item_name") }}</th>
<th class="col-3">{{ t("purchase.purpose") }}</th>
<th class="w-1">{{ t("purchase.quantity") }}</th>
<th class="w-1">{{ t("purchase.created_at") }}</th>
<th class="w-1">{{ t("purchase.updated_at") }}</th>
<th class="w-1">{{ t("purchase.status") }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="value in all_orders"
class="element"
@click="jump_to_order(value.ID)"
>
<td>
<input
class="form-check-input m-0 align-middle"
type="checkbox"
aria-label="Select invoice"
/>
</td>
<td>
<span class="text-muted">{{ value.ID }}</span>
</td>
<td>
{{ value.Title }}
</td>
<td>{{ value.Remark }}</td>
<td>1</td>
<td>
{{ myfuncs.formatLocalizedDate(value.CreatedAt, locale) }}
</td>
<td>{{ myfuncs.formatLocalizedDate(value.UpdatedAt) }}</td>
<td>1</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer d-flex align-items-center">
<p class="m-0 text-secondary">
{{ t("purchase.show") }}
</p>
<div class="mx-2 d-inline-block">
<input
type="text"
class="form-control form-control-sm w-6"
v-model="page_items_items"
aria-label="Invoices count"
@change="page_items_input_change"
@input="page_items_input_input"
/>
</div>
<p class="m-0 text-secondary">
{{ t("purchase.entries") }}
{{ t("purchase.There_are_a_total_of") }} {{ all_items }}
{{ t("purchase.entries") }}
</p>
<ul class="pagination m-0 ms-auto">
<li class="page-item" :class="now_page == 1 ? 'disabled' : ''">
<div
class="page-link"
:tabindex="now_page == 1 ? '-1' : ''"
:aria-disabled="now_page == 1 ? 'true' : ''"
@click="change_page(1)"
>
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-left -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-bar-to-left"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12l10 0" />
<path d="M10 12l4 4" />
<path d="M10 12l4 -4" />
<path d="M4 4l0 16" />
</svg>
</div>
</li>
<li class="page-item" :class="now_page == 1 ? 'disabled' : ''">
<div
class="page-link"
:tabindex="now_page == 1 ? '-1' : ''"
:aria-disabled="now_page == 1 ? 'true' : ''"
@click="change_page(now_page - 1)"
>
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-left -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M15 6l-6 6l6 6" />
</svg>
<!-- prev -->
</div>
</li>
<li
v-for="value in range(page_start, page_end)"
class="page-item"
:class="value == now_page ? 'active' : ''"
>
<div class="page-link" @click="change_page(value)">
{{ value }}
</div>
</li>
<li
class="page-item"
:class="now_page == all_pages ? 'disabled' : ''"
>
<div
class="page-link"
:tabindex="now_page == all_pages ? '-1' : ''"
:aria-disabled="now_page == all_pages ? 'true' : ''"
@click="change_page(now_page + 1)"
>
<!-- next -->
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-right -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 6l6 6l-6 6" />
</svg>
</div>
</li>
<li
class="page-item"
:class="now_page == all_pages ? 'disabled' : ''"
>
<div
class="page-link"
:tabindex="now_page == all_pages ? '-1' : ''"
:aria-disabled="now_page == all_pages ? 'true' : ''"
@click="change_page(all_pages)"
>
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-right -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-bar-to-right"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M14 12l-10 0" />
<path d="M14 12l-4 4" />
<path d="M14 12l-4 -4" />
<path d="M20 4l0 16" />
</svg>
</div>
</li>
<li>
<input
type="text"
class="form-control form-control-sm w-6"
@change="page_input_change"
@input="page_input_input"
v-model="page_input"
/>
</li>
</ul>
</div>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
<style lang="scss" scoped>
.element:hover {
background-color: #4299e11c;
}
</style>
@@ -0,0 +1,14 @@
<script setup>
import { onMounted, watch, ref, reactive } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const dynamicParam = router
onMounted(() => {
console.log(dynamicParam);
});
</script>
<template>
</template>
@@ -0,0 +1,243 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import { useRouter } from "vue-router";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import { myfuncs } from "@/myfunc.js";
import { my_network_func } from "@/my_network_func";
import { useI18n } from "vue-i18n";
// 使用 vue-i18n 的 Composition API
const { t, locale } = useI18n();
const mos = ref();
const isShowPassword = ref(false);
const username = ref();
const useremail = ref();
const userpassword = ref();
const router = useRouter();
function functionupdataTitle() {
document.title = "Operations." + t("appname.register");
}
function togglePasswordVisibility() {
isShowPassword.value = !isShowPassword.value;
}
function createAccount() {
// 在这里处理创建新账户的逻辑
const user = username.value?.value;
const email = useremail.value?.value;
const pass = userpassword.value?.value;
username.value?.classList.remove("is-invalid");
useremail.value?.classList.remove("is-invalid");
userpassword.value?.classList.remove("is-invalid");
let isDataErr = false;
if (!user) {
isDataErr = true;
username.value?.classList.add("is-invalid");
}
if (!email) {
isDataErr = true;
useremail.value?.classList.add("is-invalid");
}
if (!pass) {
isDataErr = true;
userpassword.value?.classList.add("is-invalid");
}
if (isDataErr) {
mos.value?.showAlert(
"info",
t("message.please_enter_username_and_password"),
5000
);
return;
}
//判断长度
if (!myfuncs.isValidEmail(email)) {
useremail.value?.classList.add("is-invalid");
mos.value?.showAlert("warning", t("message.this_not_email"), 5000);
return;
}
// console.log("创建新账户信息:", {
// user: username.value?.value,
// email: useremail.value?.value,
// pass: userpassword.value?.value,
// });
my_network_func.postJson(
"/users/register",
{
username: username.value?.value,
useremail: useremail.value?.value,
userpass: userpassword.value?.value,
},
(r) => {
//console.log(r);
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case -4:
username.value?.classList.add("is-invalid");
mos.value?.showAlert("warning", t("message.username_dup"), 5000);
break;
case 0:
mos.value?.showAlert(
"success",
t("message.registration_successful"),
1000,
() => {
router.push("/login");
}
);
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
}
);
}
onMounted(() => {
functionupdataTitle();
});
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
</script>
<template>
<div class="page page-center">
<div class="container container-tight py-4">
<div class="text-center mb-4">
<router-link to="/" class="navbar-brand navbar-brand-autodark">
<img
src="/static/logo.svg"
width="110"
height="32"
alt="Tabler"
class="navbar-brand-image"
/>
</router-link>
</div>
<div class="card card-md">
<div class="card-body">
<h2 class="card-title text-center mb-4">
{{ t("message.create_new_account") }}
</h2>
<div class="mb-3">
<label class="form-label">{{ t("message.user_name") }}</label>
<input
ref="username"
type="text"
maxlength="64"
class="form-control"
:placeholder="t('message.your_user_name')"
/>
</div>
<div class="mb-3">
<label class="form-label">{{ t("message.email_address") }}</label>
<input
ref="useremail"
type="email"
maxlength="250"
class="form-control"
:placeholder="t('message.your_email_address')"
/>
</div>
<div class="mb-3">
<label class="form-label">{{ t("message.password") }}</label>
<div class="input-group input-group-flat">
<input
ref="userpassword"
:type="isShowPassword ? 'text' : 'password'"
class="form-control"
:placeholder="t('message.your_password')"
autocomplete="off"
/>
<span class="input-group-text">
<div
class="link-secondary"
:title="
isShowPassword
? t('message.hidden_Password')
: t('message.show_password')
"
data-bs-toggle="tooltip"
>
<!-- Download SVG icon from http://tabler-icons.io/i/eye -->
<svg
v-if="!isShowPassword"
@click="togglePasswordVisibility"
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path
d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"
/>
</svg>
<svg
v-if="isShowPassword"
@click="togglePasswordVisibility"
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
<path
d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"
/>
<path d="M3 3l18 18" />
</svg>
</div>
</span>
</div>
</div>
<!-- <div class="mb-3">
<label class="form-check">
<input type="checkbox" class="form-check-input"/>
<span class="form-check-label">Agree the <a href="./terms-of-service.html" tabindex="-1">terms and policy</a>.</span>
</label>
</div> -->
<div class="form-footer">
<button @click="createAccount" class="btn btn-primary w-100">
{{ t("message.create_new_account") }}
</button>
</div>
</div>
</div>
<div class="text-center text-secondary mt-3">
{{ t("message.already_have_an_account") }}
<router-link to="/login">{{ t("message.back_to_login") }}</router-link>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
@@ -0,0 +1,134 @@
<script setup>
import FullCalendar from "@fullcalendar/vue3";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction"; //拖动插件 需要用npm安装
import listPlugin from "@fullcalendar/list";
import { onMounted, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const calendar = ref(null);
const calendarOptions = ref({
height: "auto",
locale: locale.value,
plugins: [
dayGridPlugin,
timeGridPlugin,
interactionPlugin, //导入拖动插件
listPlugin,
],
fixedWeekCount: false, //是否固定显示6行
weekNumbers: true,
initialView: "dayGridMonth", //默认月视图 dayGridMonth timeGridWeek listWeek
editable: true,
selectable: true,
firstDay: 1,
dayCellDidMount(info) {
switch (info.dow) {
case 0:
info.el.style.backgroundColor = "#ffb5b5";
break;
case 6:
info.el.style.backgroundColor = "#ffb5b5";
break;
}
if (info.isToday) {
//info.el.style.backgroundColor = '#ffff7f';
}
info.el.style.border = "1px solid #4b4b4b"; // 浅蓝色边框
},
headerToolbar: {
left: "prevYearCustom,prevMonthCustom,todayCustom,nextMonthCustom,nextYearCustom",
center: "title",
right: "", //,timeGridWeek,timeGridDay'
},
// 自定义按钮
customButtons: {
prevYearCustom: {
text: t('schedule.previous_year'),
click: function () {
calendar.value.getApi().prevYear();
},
},
nextYearCustom: {
text: t('schedule.next_year'),
click: function () {
calendar.value.getApi().nextYear();
},
},
prevMonthCustom: {
text: t('schedule.previous_month'),
click: function () {
calendar.value.getApi().prev();
},
},
nextMonthCustom: {
text: t('schedule.next_month'),
click: function () {
calendar.value.getApi().next();
},
},
todayCustom: {
text: t('schedule.month'),
click: function () {
calendar.value.getApi().today();
},
},
},
events: [
{ title: "事件 1", start: "2025-11-10" },
{ title: "事件 2", start: "2025-11-15", end: "2024-06-17" },
{
title: "事件 3",
start: "2025-11-20T10:30:00",
end: "2024-06-20T12:30:00",
},
],
});
function functionupdataTitle() {
document.title = "Operations." + t("appname.schedule");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
calendarOptions.value.locale = locale.value;
// 更新自定义按钮文本
calendarOptions.value.customButtons.prevYearCustom.text = t('schedule.previous_year');
calendarOptions.value.customButtons.nextYearCustom.text = t('schedule.next_year');
calendarOptions.value.customButtons.prevMonthCustom.text = t('schedule.previous_month');
calendarOptions.value.customButtons.nextMonthCustom.text = t('schedule.next_month');
calendarOptions.value.customButtons.todayCustom.text = t('schedule.month');
});
onMounted(() => {
functionupdataTitle();
});
</script>
<template>
<FullCalendar ref="calendar" :options="calendarOptions" />
</template>
<style scoped>
/* .fc-prevYearCustom-button {
background-color: #4CAF50 !important;
color: white !important;
border: none !important;
border-radius: 5px !important;
padding: 8px 16px !important;
font-weight: bold !important;
} */
</style>
@@ -0,0 +1,251 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import settingNavigation from "@/components/settingNavigation.vue";
import { useI18n } from "vue-i18n";
import datePicker from "@/components/datePicker.vue";
import imageCropper from "@/components/imageCropper.vue";
import { useUserStore } from "@/stores/user";
import { my_network_func } from "@/my_network_func";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import { useRouter } from "vue-router";
const mos = ref();
const { t, locale } = useI18n();
const router = useRouter();
const birthday = ref();
const username = ref();
const userremark = ref();
const userStore = useUserStore();
const is_avatar_change = ref(false);
const avatar_temp_url = ref("");
const avatar_canvas=ref();
// 将 Base64 转换为 File 对象
function base64ToFile(base64Data, filename) {
const arr = base64Data.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1] || base64Data);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
function updataInfo() {
let isDataErr = false;
let birthdayValue = birthday.value.datepicker.value;
let usernameValue = username.value.value;
let userremarkValue = userremark.value.value;
username.value?.classList.remove("is-invalid");
userremark.value?.classList.remove("is-invalid");
birthday.value?.datepicker.classList.remove("is-invalid");
if (!usernameValue) {
isDataErr = true;
username.value?.classList.add("is-invalid");
}
if (!userremarkValue) {
isDataErr = true;
userremark.value?.classList.add("is-invalid");
}
if (!birthdayValue) {
isDataErr = true;
birthday.value?.datepicker.classList.add("is-invalid");
}
if (isDataErr) {
//console.log("用户信息有误,无法保存");
return;
}
//检查头像是否需要更新
if(is_avatar_change.value)
{
my_network_func.postflise("/users/updateAvatar",base64ToFile(avatar_temp_url.value,"avatar.png"),(r)=>{
is_avatar_change.value=false
//console.log(r)
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case 0:
//mos.value?.showAlert("success", t("message.save_ok"), 1000);
is_avatar_change.value=false
// 更新用户信息到store
//userStore.getUserInfoFromCookie();
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
})
}
my_network_func.postJson(
"/users/updateInfo",
{
username: usernameValue,
remark: userremarkValue,
birthday: birthdayValue,
},
(r) => {
//console.log(r);
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case 0:
mos.value?.showAlert("success", t("message.save_ok"), 1000);
// 更新用户信息到store
userStore.getUserInfoFromCookie();
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
}
);
}
function rev_avatar_canvas(canvas) {
is_avatar_change.value = true;
avatar_temp_url.value = canvas.toDataURL("image/png");
avatar_canvas.value=canvas
//console.log(url)
}
function cancel_change_avatar(){
is_avatar_change.value=false
}
function functionupdataTitle() {
document.title = "Operations." + t("settings.basic_information");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
onMounted(() => {
//console.log("account mounted");
//username.value.value="Kevin";
functionupdataTitle();
if (!userStore.isLoggedIn) {
router.push("/login");
}
});
</script>
<template>
<div class="page-wrapper">
<div class="page-header d-print-none">
<div class="container-xl">
<h2 class="page-title">{{ t("settings.my_account") }}</h2>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="row g-0">
<settingNavigation />
<div class="col-12 col-md-9 d-flex flex-column">
<div class="card-body">
<!-- <h2 class="mb-4">{{ t("settings.my_account") }}</h2> -->
<!-- <h3 class="card-title">
{{ t("settings.profile_information") }}
</h3> -->
<div class="row align-items-center">
<div class="col-auto">
<img
:src="
is_avatar_change
? avatar_temp_url
: userStore.getUserAvatarPath()
"
alt=""
class="avatar avatar-xl"
/>
</div>
<!-- <imageCropper /> -->
<div class="col-auto " >
<imageCropper @crop_to_canvas="rev_avatar_canvas"></imageCropper>
<button v-show="is_avatar_change" class="btn btn-outline-secondary " @click="cancel_change_avatar">
{{ t("settings.cancel") }}
</button>
</div>
</div>
<h3 class="card-title mt-4">-</h3>
<div class="row g-3">
<div class="col-md">
<div class="form-label">{{ t("settings.name") }}</div>
<input
ref="username"
type="text"
class="form-control"
:value="
userStore.userInfo ? userStore.userInfo.Username : ''
"
/>
</div>
<div class="col-md">
<div class="form-label">{{ t("settings.remark") }}</div>
<input
ref="userremark"
type="text"
class="form-control"
:value="
userStore.userInfo ? userStore.userInfo.FirstName : ''
"
/>
</div>
<div class="col-md">
<div class="form-label">{{ t("settings.birthday") }}</div>
<datePicker
ref="birthday"
:setdef="userStore.getUserBirthday()"
/>
</div>
<div>
<button class="btn" @click="updataInfo">
{{ t("settings.save_changes") }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
@@ -0,0 +1,135 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import settingNavigation from "@/components/settingNavigation.vue";
import { useI18n } from "vue-i18n";
import { useUserStore } from "@/stores/user";
import { my_network_func } from "@/my_network_func";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import { myfuncs } from "@/myfunc";
import { useRouter } from "vue-router";
const mos = ref();
const { t, locale } = useI18n();
const router = useRouter();
const emailInput = ref();
const userStore = useUserStore();
function changeEmail() {
if (emailInput.value.value == "") {
emailInput.value.classList.add("is-invalid");
mos.value?.showAlert("warning", t("message.please_enter_your_email"), 3000);
return;
} else {
emailInput.value.classList.remove("is-invalid");
}
//判断是否是合法邮箱
if (myfuncs.isValidEmail(emailInput.value.value) == false) {
emailInput.value.classList.add("is-invalid");
mos.value?.showAlert("danger", t("message.this_not_email"), 3000);
return;
} else {
emailInput.value.classList.remove("is-invalid");
}
my_network_func.postJson(
"/users/changeEmail",
{
newemail: emailInput.value.value,
},
(r) => {
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case 0:
mos.value?.showAlert("success", t("message.change_ok"), 5000);
// 更新用户信息到store
userStore.getUserInfoFromCookie();
break;
case -43:
emailInput.value.classList.add("is-invalid");
mos.value?.showAlert("danger", t("message.this_not_email"), 3000);
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
}
);
}
function functionupdataTitle() {
document.title = "Operations." + t("settings.contact_information");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
onMounted(() => {
//console.log("account mounted");
//username.value.value="Kevin";
functionupdataTitle();
if (!userStore.isLoggedIn) {
router.push("/login");
}
});
</script>
<template>
<div class="page-wrapper">
<div class="page-header d-print-none">
<div class="container-xl">
<h2 class="page-title">{{ t("settings.my_account") }}</h2>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="row g-0">
<settingNavigation />
<div class="col-12 col-md-9 d-flex flex-column">
<div class="card-body">
<h3 class="card-title mt-4">{{ t("settings.email") }}</h3>
<div>
<div class="row g-2">
<div class="col-auto">
<input
ref="emailInput"
type="text"
class="form-control w-auto"
:value="userStore.user.Email"
:placeholder="t('message.your_email_address')"
/>
</div>
<div class="col-auto">
<button class="btn" @click="changeEmail">
{{ t("settings.change_email") }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
@@ -0,0 +1,230 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import settingNavigation from "@/components/settingNavigation.vue";
import { useI18n } from "vue-i18n";
import { useUserStore } from "@/stores/user";
import { my_network_func } from "@/my_network_func";
import MyOffcanvas from "@/components/MyOffcanvas.vue";
import { useRouter } from "vue-router";
const mos = ref();
const { t, locale } = useI18n();
const router = useRouter();
const userStore = useUserStore();
const oldPassInput = ref();
const newPassInput = ref();
const cnfPassInput = ref();
const isShowPassword = ref(false);
function togglePasswordVisibility() {
isShowPassword.value = !isShowPassword.value;
}
function changePassword() {
let isDataErr = false;
let oldPass = oldPassInput.value.value;
let newPass = newPassInput.value.value;
let cnfPass = cnfPassInput.value.value;
oldPassInput.value.classList.remove("is-invalid");
newPassInput.value.classList.remove("is-invalid");
cnfPassInput.value.classList.remove("is-invalid");
if (!oldPass) {
isDataErr = true;
oldPassInput.value.classList.add("is-invalid");
}
if (!newPass) {
isDataErr = true;
newPassInput.value.classList.add("is-invalid");
}
if (!cnfPass) {
isDataErr = true;
cnfPassInput.value.classList.add("is-invalid");
}
if (newPass !== cnfPass) {
isDataErr = true;
newPassInput.value.classList.add("is-invalid");
cnfPassInput.value.classList.add("is-invalid");
mos.value?.showAlert(
"warning",
t("message.confirm_password_incorrect"),
3000
);
}
if (isDataErr) {
return;
}
my_network_func.postJson(
"/users/changePassword",
{
oldpass: oldPass,
newpass: newPass,
},
(r) => {
switch (r.statusCode) {
case 200:
switch (r.data.err_code) {
case 0:
// 清空输入框
oldPassInput.value.value = "";
newPassInput.value.value = "";
cnfPassInput.value.value = "";
mos.value?.showAlert(
"success",
t("message.change_ok"),
2000,
() => {
userStore.logout();
router.push("/");
}
);
break;
case -42:
oldPassInput.value.classList.add("is-invalid");
mos.value?.showAlert(
"danger",
t("message.old_pass_incorrect"),
3000
);
break;
default:
mos.value?.showAlert("danger", t("message.server_error"), 5000);
break;
}
break;
default:
mos.value?.showAlert("danger", t("message.network_err"), 5000);
break;
}
}
);
}
function functionupdataTitle() {
document.title = "Operations." + t("settings.security_settings");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
onMounted(() => {
//console.log("account mounted");
//username.value.value="Kevin";
functionupdataTitle();
if (!userStore.isLoggedIn) {
router.push("/login");
}
});
</script>
<template>
<div class="page-wrapper">
<div class="page-header d-print-none">
<div class="container-xl">
<h2 class="page-title">{{ t("settings.my_account") }}</h2>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="row g-0">
<settingNavigation />
<div class="col-12 col-md-9 d-flex flex-column">
<div class="card-body">
<h3 class="card-title mt-4">
{{ t("settings.password") }}
<!-- Download SVG icon from http://tabler-icons.io/i/eye -->
<svg
v-if="!isShowPassword"
@click="togglePasswordVisibility"
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path
d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"
/>
</svg>
<svg
v-if="isShowPassword"
@click="togglePasswordVisibility"
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
<path
d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"
/>
<path d="M3 3l18 18" />
</svg>
</h3>
<input
ref="oldPassInput"
:type="isShowPassword ? 'text' : 'password'"
class="form-control w-auto mb-1"
:placeholder="t('message.type_old_pass')"
/>
<input
ref="newPassInput"
:type="isShowPassword ? 'text' : 'password'"
class="form-control w-auto mb-1"
:placeholder="t('message.type_new_pass')"
/>
<input
ref="cnfPassInput"
:type="isShowPassword ? 'text' : 'password'"
class="form-control w-auto mb-1"
:placeholder="t('message.type_cof_pass')"
/>
<div>
<button class="btn" @click="changePassword">
{{ t("settings.set_new_password") }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<MyOffcanvas ref="mos" />
</template>
+11
View File
@@ -0,0 +1,11 @@
<script setup>
import useDropzone from '@/components/useDropzone.vue';
</script>
<template>
<useDropzone></useDropzone>
<!-- <useDropzoneBootstrap></useDropzoneBootstrap>
<useFilePond></useFilePond> -->
</template>
@@ -0,0 +1,72 @@
<template>
<div class="page-wrapper">
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">Cards</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-md-6 col-lg-3">
<div class="card">
<!-- Photo -->
<div
class="img-responsive img-responsive-21x9 card-img-top"
style="
background-image: url(./static/photos/home-office-desk-with-macbook-iphone-calendar-watch-and-organizer.jpg);
"
></div>
<div class="card-body">
<h3 class="card-title">Card with top image</h3>
<p class="text-secondary card_text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card">
<!-- Photo -->
<div
class="img-responsive img-responsive-21x9 card-img-top"
style="
background-image: url(./static/photos/home-office-desk-with-macbook-iphone-calendar-watch-and-organizer.jpg);
"
></div>
<div class="card-body">
<h3 class="card-title">Card with top image</h3>
<p class="text-secondary card_text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Aperiam deleniti fugit incidunt, iste, itaque minima neque
pariatur perferendis sed suscipit velit vitae voluptatem. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Illo optio et fuga omnis ipsa, odit repellendus iste doloremque est, nam eius quisquam perspiciatis deserunt. Quasi tempore velit architecto corporis voluptatibus!
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.card_text{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; /* 限制显示4行 */
overflow: hidden;
line-height: 1.5;
min-height: calc(1.5em * 2); /* 最小高度 */
}
</style>