我正在尝试编辑此 CRUD Web 应用程序,该应用程序使用 Google Sheet 作为数据库、Google Apps 脚本和 Vuetify 我从这个 YouTube 页面获得TechLever
我添加它是为了在我的表单时间字段中显示时间类型
<div v-if="fld.type === 'time'">
<v-text-field
v-model="editedItem[fld.key]"
:label="fld.label"
type="time"
:rules="fld.required ? [rules.required]:[]"
></v-text-field>
</div>
它可以工作,但格式看起来不太好..
这是我在进行任何编辑之前的 Apps 脚本的 index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/3.4.8/vuetify-labs.min.js"
integrity="sha512-5xeIAXqNP/DWGkolQzdPAL042aA4Lb8SCMy/Ju+9yzvf9SzfsbzICQwYyMrhbN8pG8m0LWhMl9BISpIDs8RquQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/3.4.8/vuetify-labs.min.css"
integrity="sha512-VP/8WyNQxaDeiVsCGXh7nLWVPt64+rqoCugT7xhZLhx9F8fTJpjpiCqHqJlhmKAMgyRU8TiAAxJmmxz260R03w=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<div id="app"></div>
<script type="module">
const { createApp } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();
createApp({
template: "#app-template",
data: () => ({
dialog: false,
dialogDelete: false,
schema: null,
prefs: null,
search: "",
headers: [],
rules: {
required: (value) => !!value || "Required.",
},
dataTable: [],
editedIndex: -1,
editedItem: {},
defaultItem: {},
snackbar: {
text: "",
show: false,
timeout: 2000,
color: "success",
multiline: true,
vertical: true,
right: true,
bottom: true,
},
}),
computed: {
formTitle() {
return this.editedIndex === -1 ? "New Item" : "Edit Item";
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.fetchSchema();
this.refresh();
},
methods: {
fetchSchema() {
google.script.run
.withSuccessHandler((res) => {
this.schema = res.schema;
this.prefs = res.appSettings;
const schema = res.schema;
this.headers = schema.map((item) => {
return {
title: item.label,
key: item.key,
sortable: item.sortable || true,
align: item.align || "start",
};
});
this.headers.push({
text: "Actions",
key: "actions",
sortable: false,
});
console.log("Headers", this.headers);
this.editedItem = schema.reduce((acc, item) => {
acc[item.key] = item.defaultValue || "";
return acc;
}, {});
this.defaultItem = this.editedItem;
})
.withFailureHandler((error) => {
console.log(error);
})
.getAppPrefs();
},
initialize() {
google.script.run
.withSuccessHandler((res) => {
this.dataTable = JSON.parse(res);
console.log(this.dataTable);
})
.withFailureHandler((error) => {
console.log(error);
})
.readAllRecords();
},
refresh() {
google.script.run
.withSuccessHandler((res) => {
// this.dataTable = JSON.parse(res);
let data = JSON.parse(res);
// convert date strings to date objects
const dateFlds = this.schema.filter(
(fld) => fld.type === "date"
);
data = data.map((item) => {
dateFlds.forEach((fld) => {
const dt = new Date(item[fld.key]);
item[fld.key] = `${dt.getFullYear()}-${(
"0" +
(dt.getMonth() + 1)
).slice(-2)}-${("0" + dt.getDate()).slice(-2)}`;
});
return item;
});
this.dataTable = data;
})
.withFailureHandler((error) => {
console.log(error);
})
.readAllRecords();
},
editItem(item) {
this.editedIndex = this.dataTable.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
deleteItem(item) {
this.editedIndex = this.dataTable.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialogDelete = true;
},
deleteItemConfirm() {
this.showSnackbar("Deleting item...", "warning");
google.script.run
.withSuccessHandler((res) => {
console.log(res);
this.dataTable.splice(this.editedIndex, 1);
this.closeDelete();
this.refresh();
this.showSnackbar("Item deleted successfully", "success");
})
.withFailureHandler((error) => {
console.log(error);
})
.deleteRecord(this.editedItem);
},
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
save() {
this.showSnackbar("Saving item...", "info");
if (this.editedIndex > -1) {
console.log("Edited Item", this.editedItem);
google.script.run
.withSuccessHandler((res) => {
this.showSnackbar(
`Item saved successfully!\nRefreshing data..`,
"success"
);
this.refresh();
this.resetForm();
})
.withFailureHandler((error) => {
console.log(error);
})
.updateRecordById(this.editedItem);
} else {
google.script.run
.withSuccessHandler((res) => {
this.showSnackbar(
`Item saved successfully!\nRefreshing data..`,
"success"
);
this.refresh();
this.resetForm();
})
.withFailureHandler((error) => {
console.log(error);
})
.createRecord(this.editedItem);
}
this.close();
},
resetForm() {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
},
showSnackbar(text, color) {
this.snackbar.text = text;
this.snackbar.color = color;
this.snackbar.show = true;
},
},
})
.use(vuetify)
.mount("#app");
</script>
<script type="text/x-template" id="app-template">
<v-app>
<v-data-table
:headers="headers"
:items="dataTable"
:search="search"
:sort-by="[{ key: 'calories', order: 'asc' }]"
fixed-header
>
<template v-slot:top>
<v-toolbar
:elevation="6"
>
<v-toolbar-title>{{prefs.AppName}}</v-toolbar-title>
<v-text-field
v-model="search"
prepend-inner-icon="mdi-magnify"
density="compact"
label="Search"
single-line
flat
hide-details
variant="solo-filled"
></v-text-field>
<v-spacer></v-spacer>
<v-dialog
v-model="dialog"
max-width="900px"
>
<template v-slot:activator="{ props }">
<v-btn
color="blue"
dark
class="mb-2"
v-bind="props"
prepend-icon="mdi-plus"
variant="elevated"
>
New Item
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col
v-for="fld in schema"
:key="fld.key"
cols="12"
sm="6"
md="4"
>
<div v-if="fld.type === 'text'">
<v-text-field
v-model="editedItem[fld.key]"
:label="fld.label"
:rules="fld.required ? [rules.required]:[]"
></v-text-field>
</div>
<div v-if="fld.type === 'textarea'">
<v-textarea
v-model="editedItem[fld.key]"
:label="fld.label"
:rules="fld.required ? [rules.required]:[]"
></v-textarea>
</div>
<div v-if="fld.type === 'select'">
<v-select
v-model="editedItem[fld.key]"
:label="fld.label"
:items="fld.options.split(',')"
:rules="fld.required ? [rules.required]:[]"
></v-select>
</div>
<div v-if="fld.type === 'checkbox'">
<v-checkbox
v-model="editedItem[fld.key]"
:label="fld.label"
:rules="fld.required ? [rules.required]:[]"
></v-checkbox>
</div>
<div v-if="fld.type === 'radio'">
<v-radio-group inline v-model="editedItem[fld.key]" :rules="fld.required ? [rules.required]:[]">
<template v-slot:label>
<div>{{fld.label}}</div>
</template>
<v-radio
v-for="opt in fld.options.split(',')"
:key="opt"
:label="opt"
:value="opt"
></v-radio>
</v-radio-group>
</div>
<div v-if="fld.type === 'date'">
<v-text-field
v-model="editedItem[fld.key]"
:label="fld.label"
type="date"
:rules="fld.required ? [rules.required]:[]"
></v-text-field>
</div>
<div v-if="fld.type === 'number'">
<v-text-field
v-model="editedItem[fld.key]"
:label="fld.label"
type="number"
:min="fld.min"
:max="fld.max"
:rules="fld.required ? [rules.required]:[]"
></v-text-field>
</div>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="blue-darken-1"
variant="text"
@click="close"
>
Cancel
</v-btn>
<v-btn
color="blue-darken-1"
variant="text"
@click="save"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="text-h5">Are you sure you want to delete this item?</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue-darken-1" variant="text" @click="closeDelete">Cancel</v-btn>
<v-btn color="blue-darken-1" variant="text" @click="deleteItemConfirm">OK</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.rating="{ item }">
<v-rating
:model-value="item.rating"
color="blue-darken-2"
density="compact"
size="small"
readonly
></v-rating>
</template>
<template v-slot:item.amount="{ item }">
<div class="text-end">
{{ item.amount.toLocaleString(prefs.Locale, {
style: "currency",
currency: prefs.Currency,
}) }}
</div>
</template>
<template v-slot:item.amount1="{ item }">
<div class="text-end">
{{ item.amount1.toLocaleString(prefs.Locale, {
style: "currency",
currency: prefs.Currency,
}) }}
</div>
</template>
<template v-slot:item.amount2="{ item }">
<div class="text-end">
{{ item.amount2.toLocaleString(prefs.Locale, {
style: "currency",
currency: prefs.Currency,
}) }}
</div>
</template>
<template v-slot:item.date="{ item }">
<div class="text-center">
{{ new Date(item.date).toLocaleDateString(prefs.Locale) }}
</div>
</template>
<template v-slot:item.date1="{ item }">
<div class="text-center">
{{ new Date(item.date1).toLocaleDateString(prefs.Locale) }}
</div>
</template>
<template v-slot:item.date2="{ item }">
<div class="text-center">
{{ new Date(item.date2).toLocaleDateString(prefs.Locale) }}
</div>
</template>
<template v-slot:item.stock="{ item }">
<div class="text-end">
<v-chip
:color="item.stock ? 'green' : 'red'"
:text="item.stock ? 'In stock' : 'Out of stock'"
class="text-uppercase"
label
size="small"
></v-chip>
</div>
</template>
<template v-slot:item.approval="{ item }">
<div class="text-center">
<v-chip
:color="item.approval == 'Yes' ? 'green' : 'red'"
:text="item.approval == 'Yes' ? 'Yes' : 'No'"
:prepend-icon="item.approval == 'Yes' ? 'mdi-checkbox-marked-circle' : 'mdi-close'"
class="ma-2"
size="small"
>
{{item.approval}}</v-chip>
</div>
</template>
<template v-slot:item.status="{ item }">
<div class="text-center">
<v-chip
class="ma-2"
:color="item.status ==='Completed' ?'teal': item.status ==='In-Progress' ? 'orange' : 'red'"
:prepend-icon="item.status ==='Completed' ?'mdi-checkbox-marked-circle': item.status ==='In-Progress' ? 'mdi-progress-alert' : 'mdi-alert'"
size="small"
>
{{item.status}}
</v-chip>
</div>
</template>
<template v-slot:item.priority="{ item }">
<div class="text-end">
<v-chip
:color="item.priority ==='High' ? 'red' : item.priority ==='Medium' ? 'orange' : 'green'"
:text="item.priority"
class="text-uppercase"
label
size="small"
></v-chip>
</div>
</template>
<template v-slot:item.actions="{ item }">
<v-icon
size="small"
class="me-2"
@click="editItem(item)"
>
mdi-pencil
</v-icon>
<v-icon
size="small"
@click="deleteItem(item)"
color="red"
>
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn
color="primary"
@click="initialize"
>
Reset
</v-btn>
</template>
</v-data-table>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="snackbar.timeout"
:vertical="snackbar.vertical"
:right="snackbar.right"
:bottom="snackbar.bottom"
multi-line
>
<strong>{{snackbar.text}}</strong>
<template v-slot:actions>
<v-btn
color="red"
variant="text"
@click="snackbar.show = false"
>
Close
</v-btn>
</template>
</v-snackbar>
</v-app>
</script>
</body>
</html>
例如,如果我在表单字段中输入 00:00,它将显示如下 1899-12-30T02:46:50.000Z 这是我的电子表格和应用程序脚本 和 网络应用程序
我尝试了很多方法,但我几乎没有知识,所以我放弃了..
有好心人帮帮我吗?还是谢谢你
这次我在编辑问题时放慢了速度,找到了时间格式问题的解决方案。
添加此功能以将时间字符串正确转换为时间对象并有效。
const timeFlds = this.schema.filter(
(fld) => fld.type === "time"
);
data = data.map((item) => {
timeFlds.forEach((fld) => {
const dt = new Date(item[fld.key]);
const hours = ("0" + dt.getHours()).slice(-2);
const minutes = ("0" + dt.getMinutes()).slice(-2);
item[fld.key] = `${hours}:${minutes}`;
});
return item;
});